.
This commit is contained in:
parent
909e241907
commit
1ee796b282
29 changed files with 250 additions and 126 deletions
|
|
@ -315,7 +315,7 @@ def test_run_tenure_history_tracks_rent_owner_transitions(tmp_path: Path):
|
|||
|
||||
def test_run_tenure_history_empty_when_always_owner_occupied(tmp_path: Path):
|
||||
# A property only ever observed as owner-occupied has no tenure change worth
|
||||
# surfacing — the timeline column is null (no events), not a noisy baseline.
|
||||
# surfacing: the timeline column is null (no events), not a noisy baseline.
|
||||
zip_path = tmp_path / "domestic-csv.zip"
|
||||
_write_epc_zip(
|
||||
zip_path,
|
||||
|
|
@ -339,6 +339,37 @@ def test_run_tenure_history_empty_when_always_owner_occupied(tmp_path: Path):
|
|||
assert df.get_column("tenure_history").to_list() == [None]
|
||||
|
||||
|
||||
def test_run_latest_tenure_status_reflects_most_recent_certificate(tmp_path: Path):
|
||||
# Two certificates for one dwelling: an older social-rented cert and a newer
|
||||
# owner-occupied one. The published latest_tenure_status must carry the
|
||||
# LATEST certificate's normalized tenure ("Owner-occupied"), while
|
||||
# was_council_house stays "Yes" because the dwelling was social at some
|
||||
# point. This also confirms latest_tenure_status reaches the epc_pp parquet.
|
||||
zip_path = tmp_path / "domestic-csv.zip"
|
||||
_write_epc_zip(
|
||||
zip_path,
|
||||
[
|
||||
_row(inspection_date="2016-04-01", tenure="Rented (social)"),
|
||||
_row(inspection_date="2024-04-01", tenure="owner-occupied"),
|
||||
],
|
||||
)
|
||||
|
||||
price_paid_path = tmp_path / "price-paid.parquet"
|
||||
_price_paid_frame(prices=[250_000], dates=[date(2024, 2, 3)]).write_parquet(
|
||||
price_paid_path
|
||||
)
|
||||
|
||||
output_path = tmp_path / "epc-pp.parquet"
|
||||
_run(zip_path, price_paid_path, output_path, tmp_path)
|
||||
|
||||
df = pl.read_parquet(output_path)
|
||||
|
||||
assert df.height == 1
|
||||
assert df.select("latest_tenure_status", "was_council_house").to_dicts() == [
|
||||
{"latest_tenure_status": "Owner-occupied", "was_council_house": "Yes"}
|
||||
]
|
||||
|
||||
|
||||
def test_run_dedup_prefers_valid_dated_cert_over_garbled_date(tmp_path: Path):
|
||||
# Two certificates for the same property. The cert with the garbled,
|
||||
# unparseable inspection_date must NOT be chosen as "latest": a string sort
|
||||
|
|
@ -565,7 +596,7 @@ def test_run_new_build_keeps_early_first_transfer_when_sub_min_price(tmp_path: P
|
|||
price_paid_path = tmp_path / "price-paid.parquet"
|
||||
pl.DataFrame(
|
||||
{
|
||||
# 5_000 is below MIN_PRICE (10_000) — a nominal/junk transfer that
|
||||
# 5_000 is below MIN_PRICE (10_000), a nominal/junk transfer that
|
||||
# must still anchor the construction year but stay out of the price
|
||||
# aggregations.
|
||||
"price": [5_000, 300_000],
|
||||
|
|
@ -603,7 +634,7 @@ def test_run_caps_band_year_at_first_transfer_year(tmp_path: Path):
|
|||
# lands AFTER its first Land Registry sale (1998). A dwelling cannot have
|
||||
# been built after it was first sold, so the published build year must be
|
||||
# capped at the first transfer year (1998), not the later band estimate.
|
||||
# It stays flagged as an estimate (approximate=1) — it is still EPC-derived.
|
||||
# It stays flagged as an estimate (approximate=1). It is still EPC-derived.
|
||||
zip_path = tmp_path / "domestic-csv.zip"
|
||||
_write_epc_zip(
|
||||
zip_path, [_row(construction_age_band="England and Wales: 2003 onwards")]
|
||||
|
|
@ -627,7 +658,7 @@ def test_run_caps_band_year_at_first_transfer_year(tmp_path: Path):
|
|||
|
||||
def test_run_keeps_band_year_when_earlier_than_first_transfer(tmp_path: Path):
|
||||
# The common case: the EPC band (1950-1966 -> 1958) predates the first
|
||||
# recorded sale (2020). The cap must NOT fire — the band estimate stands.
|
||||
# recorded sale (2020). The cap must NOT fire. The band estimate stands.
|
||||
zip_path = tmp_path / "domestic-csv.zip"
|
||||
_write_epc_zip(zip_path)
|
||||
|
||||
|
|
@ -649,7 +680,7 @@ def test_run_keeps_band_year_when_earlier_than_first_transfer(tmp_path: Path):
|
|||
def test_run_keeps_sale_above_lowered_min_price(tmp_path: Path):
|
||||
# A genuine cheap sale of 30_000 sits between the OLD floor (50k) and the
|
||||
# NEW floor (10k): it must now be RETAINED in the price aggregations. This
|
||||
# pins the 50k->10k change — it fails on the pre-fix 50k floor (where 30k was
|
||||
# pins the 50k->10k change: it fails on the pre-fix 50k floor (where 30k was
|
||||
# excluded, giving historical_prices length 1 / latest_price 250_000).
|
||||
zip_path = tmp_path / "domestic-csv.zip"
|
||||
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as archive:
|
||||
|
|
@ -748,8 +779,8 @@ def test_run_collapses_duplicate_transactions(tmp_path: Path):
|
|||
# The duplicated 250_000 sale collapses to one entry; two distinct sales.
|
||||
assert df.get_column("historical_prices").to_list() == [
|
||||
[
|
||||
{"year": 2020, "month": 2, "price": 200_000},
|
||||
{"year": 2024, "month": 2, "price": 250_000},
|
||||
{"year": 2020, "month": 2, "price": 200_000, "is_new": False},
|
||||
{"year": 2024, "month": 2, "price": 250_000, "is_new": False},
|
||||
]
|
||||
]
|
||||
assert df.get_column("latest_price").to_list() == [250_000]
|
||||
|
|
@ -777,7 +808,7 @@ def test_run_excludes_implausible_price_jump_but_keeps_property(tmp_path: Path):
|
|||
assert df.height == 1
|
||||
assert df.get_column("latest_price").to_list() == [140_000]
|
||||
assert df.get_column("historical_prices").to_list() == [
|
||||
[{"year": 2016, "month": 6, "price": 140_000}]
|
||||
[{"year": 2016, "month": 6, "price": 140_000, "is_new": False}]
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue