fix(ppd): an exact house number is not ambiguous, and a partial one is not a match - #70
Merged
Merged
Conversation
…s not a match
Both sources match `paon` by SUBSTRING, so asking for "5 Alexandra Road" also
returns 15, 25 and 5A. Two separate faults followed, and the uniqueness check
solved neither.
**"5" was refused.** 15 came back with it, the check saw two buildings and
returned None -- though 5 was exactly one of the candidates and exactly what was
asked for. Measured across six outcodes of the real artifact: 14% of addresses,
one in seven, were unresolvable this way. In every case of that class the exact
match is present, because its presence is WHY the collision happened.
**"2" was answered with 25.** 25 was the only partial match, and a single
candidate passes a uniqueness check unchallenged. That is another property's
sale history returned as yours, with its price feeding
`subject_price_percentile` and `subject_vs_median_pct` -- a confident wrong
answer, which is the failure this module exists to prevent. Found by a test
written for the first fault; it had been reachable the whole time.
Narrowing to an exact `paon` before the uniqueness check fixes both, and is not
a relaxation: nothing is selected that the query did not already return, and the
check still runs afterwards on what survives. Where no candidate matches
exactly, every one of them merely contains the number asked for, so none is the
property the caller meant and None is right.
`_normalise_paon` compares on case and spacing only. Deliberately no more: "5"
and "5A" are different properties, and anything equating them would be inventing
identity rather than comparing it.
Verified against the real artifact:
5 Alexandra Road -> 3 sales, latest 2026-01-23 £225,000 (was None)
9 Alexandra Road -> 2 sales, latest 2022-12-05 £330,000 (was None)
27 Havenwood Rise -> 1 sale (was None)
"Alexandra Road" -> None 9999 Nowhere Street -> None
The 2026 sale matches the live answer retained from 2026-08-31; the 2008 and
2004 sales are history the eleven-year artifact could not have held.
One test from PR #69 asserted the old refusal for "27 Havenwood Rise" and is
updated, with its docstring recording that the assertion described the defect
rather than the intent. Two tests replace it for the cases that ARE ambiguous:
no exact match among partial ones, and an exact number on two streets with no
street given to discriminate.
This changes both paths identically -- the narrowing happens after the fetch, so
live and snapshot behave the same, which is what keeps the fallback honest.
./scripts/validate.sh: 2220 passed, 28 skipped.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Both sources match
paonby substring, so "5 Alexandra Road" also returns15, 25 and 5A. Two faults followed, and the uniqueness check solved neither.
1. "5" was refused — 14% of addresses
15 came back with it, the check saw two buildings, returned
None. But 5 wasexactly one of the candidates and exactly what was asked for.
Measured across six outcodes of the real artifact: 14% of addresses, 1 in 7,
unresolvable this way — 5/15, 1/11/21, 7/17. In every case of that class the
exact match is present, because its presence is why the collision happened.
2. "2" was answered with 25 — a silent wrong answer
25 was the only partial match, and a single candidate passes a uniqueness
check unchallenged. That is another property's sale history returned as yours,
its price feeding
subject_price_percentileandsubject_vs_median_pct.Found by a test written for fault 1. It had been reachable the whole time, on
both paths.
The fix
Narrow to an exact
paonbefore the uniqueness check. Not a relaxation:nothing is selected that the query did not already return, and the check still
runs afterwards on what survives. Where nothing matches exactly, every candidate
merely contains the number asked for, so none is the property meant —
Noneis right.
_normalise_paoncompares case and spacing only. Deliberately no more: "5" and"5A" are different properties, and equating them would invent identity rather
than compare it.
Verified against the real artifact
The 2026 sale matches the live answer retained on 2026-08-31. The 2008 and
2004 sales are history the eleven-year artifact could not have held — the
rebuild paying off.
Tests
One test from #69 asserted the old refusal for "27 Havenwood Rise"; updated,
with its docstring recording that the assertion described the defect rather than
the intent. Two tests replace it for what is genuinely ambiguous: no exact
match among partial ones, and an exact number on two streets with no street to
discriminate.
Changes both paths identically — narrowing happens after the fetch, so live and
snapshot behave the same, which keeps the fallback honest.
./scripts/validate.sh→ 2220 passed, 28 skipped.