new demo mode & tenure
Some checks failed
Build and publish Docker image / build-and-push (push) Successful in 8m43s
CI / Check (push) Failing after 8m49s

This commit is contained in:
Andras Schmelczer 2026-06-17 07:54:30 +01:00
parent 7656f24544
commit 4a0f00f2a4
64 changed files with 2875 additions and 338 deletions

View file

@ -220,6 +220,77 @@ def test_fuzzy_join_matches_high_score_number_less_pair():
assert result["right_address"].to_list() == ["THE OLD RECTORY"]
def test_fuzzy_join_keep_unmatched_right_appends_epc_only_rows():
# Models "price-paid LEFT JOIN epc" with keep_unmatched_right: matched pairs
# keep both sides; an unmatched LEFT (sold-but-no-EPC) row keeps null right
# columns; an unmatched RIGHT (EPC-only, never sold) row is appended with
# null left columns so the dwelling still enters the property universe.
left = pl.LazyFrame(
{
"left_id": ["sold_with_epc", "sold_no_epc"],
"left_address": ["10 High Street", "12 High Street"],
"left_postcode": ["AB1 2CD", "AB1 2CD"],
}
)
right = pl.LazyFrame(
{
"right_id": ["epc_for_10", "epc_only"],
"right_address": ["10 HIGH STREET", "14 High Street"],
"right_postcode": ["AB1 2CD", "AB1 2CD"],
}
)
result = (
fuzzy_join_on_postcode(
left=left,
right=right,
left_address_col="left_address",
right_address_col="right_address",
left_postcode_col="left_postcode",
right_postcode_col="right_postcode",
keep_unmatched_right=True,
)
.sort("left_id", "right_id", nulls_last=True)
.collect()
)
assert result.select("left_id", "right_id").to_dicts() == [
# Sold but no EPC: left present, right null (ordinary left-join row).
{"left_id": "sold_no_epc", "right_id": None},
# Matched pair: both sides present.
{"left_id": "sold_with_epc", "right_id": "epc_for_10"},
# EPC-only dwelling: appended with null left columns.
{"left_id": None, "right_id": "epc_only"},
]
# The appended EPC-only row carries its own address/postcode and a null score.
epc_only = result.filter(pl.col("right_id") == "epc_only")
assert epc_only["right_address"].to_list() == ["14 High Street"]
assert epc_only["left_address"].to_list() == [None]
assert epc_only["_match_score"].to_list() == [None]
def test_fuzzy_join_keep_unmatched_right_off_by_default():
# Without the flag the join stays left-only: an EPC-only right row is dropped.
left = pl.LazyFrame(
{"left_address": ["10 High Street"], "left_postcode": ["AB1 2CD"]}
)
right = pl.LazyFrame(
{"right_address": ["14 High Street"], "right_postcode": ["AB1 2CD"]}
)
result = fuzzy_join_on_postcode(
left=left,
right=right,
left_address_col="left_address",
right_address_col="right_address",
left_postcode_col="left_postcode",
right_postcode_col="right_postcode",
).collect()
assert result.height == 1
assert result["right_address"].to_list() == [None]
def test_numbers_compatible_treats_letter_suffix_as_part_of_the_number():
# 8A, 8B and plain 8 are three different properties on the same street;
# digit-only extraction collapsed all three to {8} and let them match.
@ -252,9 +323,7 @@ def test_numbers_compatible_gates_single_letter_flats():
assert not _numbers_compatible(
"FLAT D 39 GERTRUDE STREET", "FLAT F 39 GERTRUDE STREET"
)
assert _numbers_compatible(
"FLAT D 39 GERTRUDE STREET", "39 GERTRUDE STREET FLAT D"
)
assert _numbers_compatible("FLAT D 39 GERTRUDE STREET", "39 GERTRUDE STREET FLAT D")
assert not _numbers_compatible("FLAT B ROSE COURT", "ROSE COURT")
# A letter glued to a number ("A3") is a unit name, not a flat letter.
assert _numbers_compatible("FLAT A3 CHESHAM HEIGHTS", "FLAT A3 CHESHAM HEIGHTS")
@ -263,9 +332,9 @@ def test_numbers_compatible_gates_single_letter_flats():
def test_admissible_variants_allows_locality_suffix_only():
# Locality words may differ between a variant and its primary; digits and
# flat designators may not (the gate ran on the primary only).
assert _admissible_variants(
"12 OAK ROAD", ["12 OAK ROAD HALE", "12 OAK ROAD"]
) == ("12 OAK ROAD HALE",)
assert _admissible_variants("12 OAK ROAD", ["12 OAK ROAD HALE", "12 OAK ROAD"]) == (
"12 OAK ROAD HALE",
)
# Dropping "FLAT 1" (digit) or "FLAT B" (flat designator) is inadmissible:
# the variant would score a single flat as the whole building.
assert (
@ -278,9 +347,7 @@ def test_admissible_variants_allows_locality_suffix_only():
# disagrees with the combined address must not smuggle in a different
# street for scoring.
assert _admissible_variants("12 OAK ROAD", ["12 ELM ROAD"]) == ()
assert (
_admissible_variants("1 TOTALLY DIFFERENT ROAD", ["1 EXAMPLE STREET"]) == ()
)
assert _admissible_variants("1 TOTALLY DIFFERENT ROAD", ["1 EXAMPLE STREET"]) == ()
def test_fuzzy_join_variant_recovers_locality_suffix_mismatch():