fix(estimator): radius (source,source_id) dedup + split-house corridor disclosure (audit #1871 P2) #1880

Merged
bot-backend merged 1 commit from audit/1871-p2-estimator-dedup-split into main 2026-06-23 09:49:12 +00:00
Collaborator

Summary

Audit-issue #1871 🟠 P2 — два фикса в чувствительном estimator.py (4500 строк), оба за флагами.

Dedup (estimate_radius_dedup_enabled, default ON)

radius-путь _fetch_analogs (Tier S-canonical/S-fallback/H/W) кэпил только per-address (rn_addr ≤ 5), но (source,source_id)-дубли делят один address и выживали на разных рангах → раздували n_analogs (prod 2026-06-23: yandex 48, cian 9, n1 5 excess). Добавлен rn_dup-window PARTITION BY source, CASE(id:/url:/ctid:) ORDER BY scraped_at DESC + AND rn_dup=1 во все 4 тира — зеркалит anchor-dedup (:1556). CASE/ctid-guard не схлопывает legacy NULL-source_id (все тиры FROM listings single-table → ctid в scope). psycopg3: column::type легально, bind-param :: отсутствует.

Split disclosure (estimate_wide_corridor_disclosure_enabled, default OFF)

Tier A матчит по address-regex (намеренно — дом дробится на house_id), на split-доме разной этажности коридор растягивается до 148-170%. При corridor_pct > 0.6 → понизить confidence + caveat «дом разбит на секции». НЕ трогает point/median/range — только confidence+explanation (есть регресс-тест). Merge house_id отклонён (recon: вреден — Tier A намеренно обходит house_id).

Test plan

  • +22 теста (dedup 4 тира + семантика + psycopg3-regex guard; split через реальный _run_estimate harness + does-not-touch-median/range + helper)
  • 399 estimator passed, ruff clean, mypy pre-existing-only
  • code-reviewer APPROVE (sensitive-файл, SQL window + psycopg3 проверены)
  • post-deploy: n_analogs не раздут дублями; split-дом (Хохрякова 48) при включённом флаге — caveat + понижен confidence

Rollout

  • dedup default ON (window всегда в SQL, флаг — pure WHERE-toggle для отката)
  • split disclosure default OFF — включить осознанно после проверки порога 0.6 на реальных split-домах

Deploy: deploy-tradein.yml.

Refs #1871

🤖 Generated with Claude Code

## Summary Audit-issue #1871 🟠 P2 — два фикса в **чувствительном `estimator.py`** (4500 строк), оба за флагами. ### Dedup (`estimate_radius_dedup_enabled`, default ON) radius-путь `_fetch_analogs` (Tier S-canonical/S-fallback/H/W) кэпил только per-address (`rn_addr ≤ 5`), но `(source,source_id)`-дубли делят один address и выживали на разных рангах → раздували `n_analogs` (prod 2026-06-23: yandex 48, cian 9, n1 5 excess). Добавлен `rn_dup`-window `PARTITION BY source, CASE(id:/url:/ctid:) ORDER BY scraped_at DESC` + `AND rn_dup=1` во все 4 тира — зеркалит anchor-dedup (:1556). CASE/ctid-guard не схлопывает legacy NULL-source_id (все тиры `FROM listings` single-table → ctid в scope). **psycopg3**: `column::type` легально, bind-param `::` отсутствует. ### Split disclosure (`estimate_wide_corridor_disclosure_enabled`, default OFF) Tier A матчит по address-regex (намеренно — дом дробится на house_id), на split-доме разной этажности коридор растягивается до 148-170%. При `corridor_pct > 0.6` → понизить confidence + caveat «дом разбит на секции». **НЕ трогает point/median/range** — только confidence+explanation (есть регресс-тест). Merge house_id отклонён (recon: вреден — Tier A намеренно обходит house_id). ## Test plan - [x] +22 теста (dedup 4 тира + семантика + psycopg3-regex guard; split через реальный _run_estimate harness + does-not-touch-median/range + helper) - [x] 399 estimator passed, ruff clean, mypy pre-existing-only - [x] code-reviewer ✅ APPROVE (sensitive-файл, SQL window + psycopg3 проверены) - [ ] post-deploy: n_analogs не раздут дублями; split-дом (Хохрякова 48) при включённом флаге — caveat + понижен confidence ## Rollout - dedup default ON (window всегда в SQL, флаг — pure WHERE-toggle для отката) - split disclosure default **OFF** — включить осознанно после проверки порога 0.6 на реальных split-домах Deploy: `deploy-tradein.yml`. Refs #1871 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bot-backend added 1 commit 2026-06-23 09:48:25 +00:00
fix(estimator): radius (source,source_id) dedup + split-house corridor disclosure (audit #1871 P2)
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
1b23fc5d95
Два P2-фикса в чувствительном estimator.py, оба за флагами.

DEDUP (estimate_radius_dedup_enabled, default ON):
radius-путь _fetch_analogs (Tier S-canonical/S-fallback/H/W) кэпил только
per-address (rn_addr ≤ MAX_ANALOGS_PER_ADDRESS=5), но (source,source_id)-дубли
делят один address и выживали на разных rn_addr рангах → раздували n_analogs
(prod 2026-06-23: yandex 48, cian 9, n1 5 excess). Добавлен rn_dup-window
PARTITION BY source, CASE(id:source_id|url:source_url|ctid) ORDER BY scraped_at
DESC + AND rn_dup=1 во все 4 тира — зеркалит anchor-dedup (:1556). CASE/ctid-guard
не схлопывает legacy NULL-source_id. ctid доступен (все тиры FROM listings
single-table). psycopg3: column::type (легально), bind-param :: отсутствует.

SPLIT DISCLOSURE (estimate_wide_corridor_disclosure_enabled, default OFF):
Tier A матчит по address-regex (намеренно — дом дробится на house_id), на
split-доме разной этажности comp_min..max растягивает коридор до 148-170%.
При corridor_pct > threshold (0.6) → понизить confidence на ступень + caveat
«дом разбит на секции разной этажности — оценка ориентировочная». НЕ трогает
point/median/range — только confidence+explanation. Helper _downgrade_confidence.
Default OFF (no-regress, включим осознанно). Merge house_id отклонён (recon:
вреден — Tier A намеренно обходит house_id-группировку).

Тесты: +22 (dedup 4 тира + семантика + psycopg3-regex guard; split disclosure
через реальный _run_estimate harness + does-not-touch-median/range + helper).
399 estimator passed, ruff clean, mypy pre-existing-only.

Refs #1871
bot-backend merged commit 7ae96ad1af into main 2026-06-23 09:49:12 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1880
No description provided.