feat(tradein/cian): admin endpoints + UI for detail+newbuilding history scrape #525
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#525
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/cian-detail-nb-endpoints"
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?
What
Activates Cian historical-data ingestion. Parsers and DB writers were already complete (cian_detail.fetch_detail/save_detail_enrichment, cian_newbuilding.fetch_newbuilding/save_newbuilding_enrichment) — only the HTTP trigger layer + UI form were missing. Two endpoints + two functional forms.
Backend
POST /api/v1/admin/scrape/cian-detail?offer_url=...&listing_id=...— fetch+parse Cian offer detail. Iflisting_idprovided → writes tooffer_price_historyand updateslistings. Without it → debug-only.POST /api/v1/admin/scrape/cian-newbuilding?zhk_url=...&house_id=...— fetch+parse Cian ЖК (newbuilding) landing. Ifhouse_idprovided → writeshouses_price_dynamics+management_companies+ updateshouses. Without it → debug-only.Both mirror Yandex equivalent pattern (admin.py:829–933).
save_newbuilding_enrichmentproperly awaited (async).Frontend
/scrapers/cianpage sections 2 + 3 (previously red banner "404 — planned") replaced with working forms reusing existingapiFetch/useMutation/<ResultPanel>patterns. Section 4 (Valuation Calculator) keeps placeholder text but loses red banner (Calculator is called automatically from /estimate flow Stage 9, separate admin trigger TBD).Why now
Predecessor: PR #523 (047 UNIQUE + 026/044 column fixes) — without it offer_price_history
ON CONFLICT DO NOTHINGwas silently inserting duplicates. Now both writers are safe.Follow-up PRs:
Files
tradein-mvp/backend/app/api/v1/admin.py(+89, mirroring Yandex endpoints)tradein-mvp/frontend/src/app/scrapers/cian/page.tsx(+140/-31, sections 2+3+4 rewrite)Test plan
82a78c3); re-review ✅saved: false,price_changes_count > 0saved: trueand rows inoffer_price_historyDeep Code Review — PR #525
Summary
Findings
Security
fetch_detail(offer_url)/fetch_newbuilding(zhk_url)pass user-supplied URL directly toAsyncSession.get(..., allow_redirects=True). No domain allow-list. This is pre-existing class — identical posture inscrape_yandex_detail/scrape_yandex_newbuilding/scrape_yandex_valuationalready in main. Gated only by Caddybasic_authon/trade-in/api/v1/admin/*per file docstring. Not a regression; flag for a follow-up hardening pass across all admin scrape triggers (allow-listcian.ru/yandex.ruhosts).ResultPanelrenders via<pre>{JSON.stringify(mut.data, null, 2)}</pre>andmut.error.messageas text — nodangerouslySetInnerHTML, no<a href={userInput}>(safeUrl helper from #512 not relevant here).URLSearchParamssingle-encodes; no double-encoding regression (test plan confirms this was the pre-push fix in82a78c3).Correctness
fetch_detailis async,save_detail_enrichmentis sync,fetch_newbuildingasync,save_newbuilding_enrichmentasync → allawait/non-awaitcalls in admin.py match signatures (verified at cian_detail.py:59,216 and cian_newbuilding.py:78,391).db.commit()at cian_detail.py:318, cian_newbuilding.py:541).Depends(get_db)session is fine — no nested transaction risk.offer_price_historyUPSERT relies on UNIQUE constraint from PR #523 (047_cian_history_sanitize.sql) — merged 2026-05-24 13:44 UTC, before this PR.houses_price_dynamicsusesON CONFLICT ON CONSTRAINT houses_price_dynamics_dim_key DO UPDATE. Both clean.offer_price_history.listing_id REFERENCES listings(id)— badlisting_id→ IntegrityError → FastAPI 500 (not silent corruption). UX could be improved with a pre-check returning 404, but acceptable for an admin debug endpoint. Nit, not blocking.CAST(:x AS type)— compliant.Project conventions
cian_detail/cian_newbuildingsymbols (admin.py:956,999) deviate from the file's top-level-import norm. Justified: top-levelfetch_detail/save_detail_enrichmentare already bound to the Avito versions at line 27 — function-scope rebinding cleanly avoids the symbol collision and keeps the Avito-detail endpoint at line 471 unaffected. Acceptable trade-off; alternative would be aliased top-level imports.scrape_yandex_detail/scrape_yandex_newbuilding(admin.py:829–933) is exact: same response-model style, same query-param trigger pattern, same optional-persist semantics.Performance
Tests
Frontend UX
parseInt(value, 10)aftervalue !== ""guard → NaN avoided for empty case; garbage fromtype="number"blocked by browser. Acceptable./scrapers/cian-cookies, but the top-of-page Cookies callout still does → no dangling-link regression.Verdict: APPROVE
Clean, well-scoped, low-risk parity work. Merge unblocked.
Recommended follow-ups (non-blocking)
listing_id/house_idnot found in DB before invoking parsers (better UX, no integrity error surface).