feat(tradein-api): surface backfilled houses + placement history #528
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#528
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-surface-houses-history-api"
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?
Context
PR #527 bootstrap'нул 6,507 houses (562 Avito + 5,945 derived) и слинковал 18,197 listings. Существующий
/estimate/{id}/housesфильтровалsource='avito'(мало совпадений),house_placement_historyвообще не surfaced.Changes
GET /estimate/{id}/houses(reworked)Два пути + union-dedup:
tradein_normalize_short_addrSQL func из PR #527)'avito'удалён)Requires: estimate's
addressfield (раньше толькоlat/lon).GET /estimate/{id}/placement-history(new)Ищет
house_idsпо адресу target → возвращает до 50 lots изhouse_placement_historyотсортированных поlast_price_date DESC. Геофоллбэк 100м если addr пустой.HouseInfoForEstimate.source: str | NoneДобавлено поле — на UI видно
avito/derived/cian_newbuildingetc.Roadmap
Follow-up to PR #527 (backfill) и
[[Decision_TradeIn_DataQuality_8PR_Roadmap]]. UI part — отдельный PR (frontendPlacementHistoryCard).Verify
Dependencies
Merge после #527 (нужен
tradein_normalize_short_addrфункция в БД).Deep Code Review — verdict
Summary
9c4c137tradein_normalize_short_addrfunction + houses bootstrap confirmed deployed.Correctness — verified
CAST(:id AS uuid)used everywhere — psycopg v3 contract respected.house_placement_historycolumns in SELECT exist (017 + 045 migrations).houses.short_addressis populated forderivedrows (063 Step 3) but NOT for non-derived (avito/cian) rows. The endpoint's WHERE clause hasOR tradein_normalize_short_addr(h.address) = ...as fallback — covers both cases correctly.direct + nearbybyhouse_idis correct.house_ids = ANY(:house_ids)with Pythonlist[int]— psycopg v3 native array binding._HOUSE_SELECT_COLSis static (no user input) — no SQL injection.Findings (non-blocking)
Medium —
placement-historylacksresponse_modeltradein-mvp/backend/app/api/v1/trade_in.py:434—def get_estimate_placement_history(...) -> list[dict]без Pydantic schema. Inconsistent with/houses(response_model=list[HouseInfoForEstimate]) и/imv-benchmark(IMVBenchmarkResponse). Consequences: нет OpenAPI schema, нет validation, UI consumesAny. Follow-up suggestion:Then
@router.get(..., response_model=list[PlacementHistoryEntry]).Medium — truthy lat/lon check
trade_in.py:411, 484—if target.lat and target.lon:ловитlat=0.0как falsy. Для EKB (lat≈56.8) невозможно, но проектный стандартis not None. Defends against future region expansion.Low — no
houses(short_address)indexMigration 063 explicitly notes this is acceptable for one-shot. На каждом запросе делается seq scan по 6.5k rows (~ms сейчас). Follow-up для full sweep target (≥10k houses):
Low — hardcoded LIMIT 50, no pagination
OK for current 160-row table. Add
limit: int = Query(50, le=200)if growth expected.Low —
from typing import AnyUsed только в
list[Any] = []для row mappings — мог бы бытьlistбез аннотации. Стилистическое.Vault / memory cross-check
:param IS NOT NULLпредикатов.tradein_normalize_short_addrdeployed.Cross-file impact
HouseInfoForEstimate.source: str | None— additive optional поле, backward-compatible. Frontend consumers старых версий продолжат работать (просто не покажут source label).source='avito'filter — это intentional roadmap step (см. body PR). Frontend сейчас не использует source-specific логику в/houses.Risk & blast radius