feat(tradein): house analytics section (chart + KPI + sold list) #546
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#546
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-house-analytics-frontend"
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?
Что
Новая секция Аналитика дома на trade-in странице — раскрывает уже собранные данные
house_placement_history(15k+ rows от avito_imv + yandex_valuation backfill).Backend
GET /api/v1/trade-in/estimate/{id}/house-analyticsprice_history— median ₽/м² по году (percentile_cont)recent_sold— последние 12 мес снятые с фактической ценой + торгом (limit 20)kpi— median exposure_days, median bargain %, sold_rate %Frontend
HouseAnalyticsSectionподPlacementHistoryCardс 3 блоками:PriceHistoryChart— Recharts line chartHouseAnalyticsKpiRow— 3 KPI cards (экспозиция · торг · sold-rate)RecentSoldList— таблица снятых лотов 12 месNew dep:
recharts@^2.15.4.Что НЕ делает
Test plan
/trade-in?id=d1570e00-f87f-4467-bbb8-161beb927be9после deploy и убедиться что секция рендерится (target house 8731 имеет 16 hist rows; в 300m radius — 577)Demo завтра
Этот PR — для demo, показывает реальную ценность от уже собранных backfill данных, без создания новых ETL.
Backend: GET /api/v1/trade-in/estimate/{id}/house-analytics — resolves target house_ids через нормализованный адрес или geo-fallback 100m, expand до 300m если в самом доме <8 hist rows. Возвращает: - price_history: median ₽/м² по году (из house_placement_history) - recent_sold: последние 12 мес снятые с фактической ценой + торгом - kpi: median exposure_days, median bargain %, sold_rate % Frontend: HouseAnalyticsSection (под PlacementHistoryCard) c 3 блоками: - PriceHistoryChart (Recharts line, median ₽/м²) - HouseAnalyticsKpiRow (3 KPI cards) - RecentSoldList (таблица снятых лотов) Использует уже собранные данные house_placement_history (15k+ rows от backfill scripts: avito_imv + yandex_valuation). Новых таблиц нет.Deep Code Review — PR #546
Status: APPROVE — clean implementation, no blockers. 5 low-severity nits below.
Files: 11 (P0: 1 SQL endpoint + 1 schema · P1: 4 components + 1 hooks file · P2: types, page wiring, lockfile, package.json)
SHA:
7eeffe3matched · Lines: +4250 / -1 (3705 lockfile, ~250 code)Strengths
CAST(:id AS uuid)everywhere (psycopg v3 compliant), explicitEXTRACT(YEAR ...)::int,NULLIF(area_m2, 0)/NULLIF(start_price, 0)guards, sensible row filters (last_price > 100000,area_m2 > 10), correctpercentile_cont(...) FILTERfor bargain median.hph_house_idx (house_id, last_price_date DESC)for price_history + recent_sold ordering,hph_removed_idx WHERE removed_date IS NOT NULLfor the recent-sold 12mo filter,hph_house_source_extid_uniq_idxfor dedup. No new indexes needed.placement-historyendpoint shape.dangerouslySetInnerHTML, no user-supplied URLs/anchors, no SVG injection — recharts renders only numeric arrays.staleTime: 10min,enabledguard on null id, structured queryKey).interfacefield-by-field match (PriceHistoryYearPoint, RecentSoldEntry, HouseAnalyticsKpi, HouseAnalyticsResponse).nullwhentotal_lots === 0instead of rendering empty cards.Low-severity findings (non-blocking)
L1 — perf,
PriceHistoryChart.tsx:2-10Staticimport { LineChart, Line, ... } from "recharts"ships ~200KB+ to every/trade-inpage load, even when the section short-circuits tonull(no house data). Wrap withnext/dynamic:L2 — display bug,
HouseAnalyticsKpiRow.tsx:46-49Always prefixes−formedian_bargain_pct. If price rose (negative value), UI shows−−5.0%. Mirror theRecentSoldList+/−logic:Low risk in practice (most rows are bargains), but inconsistent with sibling component.
L3 — a11y,
HouseAnalyticsSection.tsx:18+ chart/list articles Noaria-labelon the section / chart<article>/ list<article>. Peers (HouseInfoCard,CianValuationCard,IMVBenchmark,DealsCard) all carryaria-label="...". Add:<section aria-label="Аналитика дома"><article aria-label="Динамика цен в доме">on chart<article aria-label="Недавно снятые лоты">on RecentSoldListL4 — copy precision,
PriceHistoryChart.tsx:21Title"Динамика цен в доме"is shown even when data comes fromradius_m=300neighbors (header inHouseAnalyticsSectioncorrectly disambiguates with "в радиусе 300м", but chart subtitle doesn't). Optionally passradiusMprop and append(включая соседние дома)when > 0.L5 — dep lockfile mismatch,
pnpm-lock.yamlPR addspnpm-lock.yaml(3705 lines) buttradein-mvp/frontend/Dockerfileusesnpm installwithpackage-lock.json*glob. The pnpm lockfile is unused at build time (npm resolves deps fresh frompackage.json). Won't break deploy (usesnpm install, notnpm ci), but signals pkg-manager confusion. Either:package-lock.jsoninstead and drop pnpm-lock, orcorepack enable && pnpm install --frozen-lockfile.Cross-PR check
<svg>sparkline (CianValuationCard.tsx:15-32). This PR introduces recharts as a new dep. Trade-off accepted — recharts gives axis/tooltip out of box vs hand-rolled sparkline.tradein_normalize_short_addr+ ST_DWithin 100m resolve pattern. No regression on placement-history endpoint (same address-resolve block).Smoke test
PR description target: estimate
d1570e00-f87f-4467-bbb8-161beb927be9(house 8731, 16 hist rows direct, 577 in 300m). Validate post-deploy thatradius_m=0andprice_history.length >= 2.— deep-code-reviewer