fix(tradein-estimator): extend cohort filter to Tier S + Tier H, fix late_soviet overlap #522
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#522
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/tradein-cohort-extend-tiers"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary — reviewer follow-up on PR #519
Deep-code-reviewer on PR #519 flagged two issues:
1. Cohort filter applied only on Tier W (MEDIUM #1)
PR #519 added the cohort hard-filter, but only to Tier W's inline SQL. Tier S (address ILIKE) and Tier H (year ±15, total_floors ±30%) share a
_COMMON_WHEREconstant which was not extended. Consequence: fortarget_year=1978(brezhnev = 1970–1989), Tier H allowedyear_built ∈ [1963, 1993]— a 1965 (khrushchev) or 1992 (late_soviet) match could win before cohort ever evaluated →analog_tier='H'returned with leaked cross-cohort data.Fix:
_COMMON_WHEREso Tier S and Tier H inherit it.cohort_year_min/cohort_year_maxbinds tobase_params(auto-propagates to both tiers via{**base_params, ...})._COMMON_WHEREbecause the relevance_score CASE expressions require inline form. Added comment near_COMMON_WHEREdocumenting the dual code path so future changes keep them in sync.2.
late_sovietcohort overlap (MINOR #5)Original range
1985–1999overlapped withbrezhnev=1970–1989._target_cohort_rangeuses first-match → 1985–1989 always picked brezhnev → late_soviet de facto covered only1990–1999. Misleading comment claimed "overlap intentional".Fix:
1990–1999. No semantic change (no input was ever mapped differently due to first-match), just clarity.Tests
Added
tests/test_estimator_cohort.pywith parametrizedtest_target_cohort_range— 20 cases covering None, out-of-range (1900, 2200), boundary years for each cohort, and the audit target (1978 → brezhnev).Result: 20/20 PASSED locally (0.93s).
Diff stats
estimator.py: +17 / -1tests/test_estimator_cohort.py: +43 (new file)Out of scope (deferred from reviewer feedback)
cohort_appliedin API response) — separate UX PR if needed.Test plan
year_built=2020(modern). Expect no брежневки in analogs even if Tier H would have allowed[2005, 2035].year_built=None: behaviour identical to pre-PR (cohort skipped, Tier 0 not attempted, Tier S→H→W as before).Deep Code Review — verdict
Summary
What this PR does
year_built BETWEENclause into_COMMON_WHERE→ Tier S и Tier H теперь тоже фильтруют по когорте (раньше bypass).cohort_year_min/cohort_year_maxtobase_paramsso all three tiers (S/H/W) get the bind.1985-1999→1990-1999(removes formal overlap with brezhnev 1970-1989; first-match semantics meant 1985-1989 was dead code, fix is purely cosmetic but clarifies coverage)._target_cohort_range— 21 cases incl. None, out-of-range, all boundaries, audit's 1978→brezhnev.Correctness checks
CAST(:cohort_year_min AS integer) IS NULL+ CAST on both BETWEEN bounds. Pattern is identical to merged Tier W inline copy.CAST(NULL AS integer) IS NULLevaluates TRUE → filter прозрачен when bind is None.None→base_paramsinjects None → SQL short-circuits. No behavior change for non-cohort paths._target_cohort_range(None)→ None → cohort_range is None → skip Tier 0 → fall through to Tier A without cohort binds. Logic intact.base_paramsdict spread (**base_params) ensures both tiers receivecohort_year_min/max; tier_s_params + tier_h params dict do not shadow these. Verified at lines 1097-1100, 1209-1220.cohort_year_min/max(was already there from #519). No regression.Tests
test_target_cohort_range— 21 parametrized cases. Covers the 1978 audit target, both boundary years of every cohort, out-of-range below/above, None input._fetch_analogswithcohort_year_min=Noneto prove CAST trap doesn't fire on Tier S/H paths. Pattern is byte-identical to Tier W (already running in prod since #519 + #518 fix), so risk is low — not blocking.Conventions
:x::typeshorthand: ✓relevance_scoreCASE expressions require inline form), comment near_COMMON_WHEREflags dual-path sync requirement.Risk / blast radius
Recommended next step
After merge: monitor
analogs tier=H/tier=Slog lines on prod for unexpected count drops on edge cases (cross-cohort buildings near boundaries).Verdict: APPROVE — auto-merge per any-scope policy.