fix(tradein-imv): adapt to Avito 3-step API contract (2026-05+) #524
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#524
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-imv-fix-3step-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?
Problem
Avito changed their IMV flow. Our
_geocode()expectedgeoHashdirectly from/web/1/coords/by_address, but that endpoint no longer returns it — onlycomponents/normalizedAddress/point/postalCode. Effect:IMVAddressNotFoundErrorfor EVERY estimate since contract change →imv-benchmarkavailable:falseeverywhere. PR #505 retry-with-cleaned-address never helped because both attempts went through the same broken flow.Discovery (via playwright in production browser)
Current contract has 3 steps:
GET /web/1/coords/by_address?address=…POST /js/v2/geo/positiongeoFieldsHash) withaddressId/locationId/metroId/districtId← we were missing thisPOST /web/1/realty-imv/get-datarecommendedPrice/lowerPrice/higherPrice/count/suggestions/placementHistorygeoFieldsHashfrom step B is what/realty-imv/get-dataexpects asgeoHashfield.Step B request body
Step B response
Verified live
Full chain works for production estimate
a0a0b820-e8a8-4eee-aa73-0ab3b98ac233(Заводская 44-а):Avito IMV = 7.52 М ₽ vs our estimator 6.58 М ₽ (standard repair) — разница +14% это разумный benchmark gap (Avito count=800 включая стройку, у нас 13 secondary-only analogs).
Fix
_geocode()rewritten as two-step (A + B) with proper exception handling (IMVAddressNotFoundError/IMVTransientError/_raise_for_status_categorized)POSITION_ENDPOINT = "/js/v2/geo/position"_imv_evaluate()(step C) unchanged — already readsdata.price.recommendedPrice/lowerPrice/higherPrice/countevaluate_via_imv()entrypointVerify after deploy
Roadmap follow-up
Follow-up to
[[Decision_TradeIn_DataQuality_8PR_Roadmap]]PR 5 (#505) and[[Event_TradeIn_DataQuality_8PR_Done_May24]]open item «Avito IMV fallback». Discovered via playwright when investigating whyimv-benchmarkstillavailable:falseafter roadmap completion.Avito changed IMV flow — /web/1/coords/by_address no longer returns geoHash. New flow has 3 endpoints: 1. /web/1/coords/by_address -> normalize + lat/lon 2. /js/v2/geo/position -> rich JWT (geoFieldsHash) with addressId/ locationId/metroId/districtId 3. /web/1/realty-imv/get-data -> recommendedPrice/lowerPrice/higherPrice Old code skipped step 2 -> IMVAddressNotFoundError for every address since contract change. PR #505 retry-with-cleaned-address never had a chance. Verified via playwright browser for production estimate a0a0b820-e8a8-4eee-aa73-0ab3b98ac233 (Zavodskaya 44-a): Avito IMV recommendedPrice=7,520,000 RUB (range 7,294,400-7,896,000).Deep Code Review — PR #524 verdict: APPROVE
Status: ✅ APPROVE
Files: 1 (P1: tradein-mvp/backend/app/services/scrapers/avito_imv.py)
Lines: +79 / -31
SHA:
c897d57(verified head match)Summary
Surgical fix adapting
_geocode()to Avito's current 3-step contract (2026-05+). Replaces the broken 1-call geocode (which expectedgeoHashdirectly from/coords/by_address— endpoint no longer returns it) with a two-step A→B sequence: A normalizes address + returns coords, B issuesPOST /js/v2/geo/positionand returnsgeoFieldsHash(JWT) used asgeoHashin step C. Step C (_imv_evaluate) unchanged.Cross-file impact
evaluate_via_imv(...)signature unchanged.IMVEvaluation/IMVGeodataclasses unchanged. Callerservices/estimator.py::_get_or_fetch_imv_cachedcatchesIMVAddressNotFoundError+ genericException→ returnsNone(graceful). NewIMVAuthError/IMVTransientErrorraises are subsumed byexcept Exception(intended).avito_imv_evaluations.geo_hashcolumn unchanged — still stores the value used asgeoHashin step C body (now a JWT instead of a hash, but the column is text). No migration needed.IMVEvaluationwithout re-calling step C → JWT expiry inside cachedgeo_hashis irrelevant on hit.tests/test_avito_imv_parse.pyis offline-only (cache_key, parse_title, parse_placement_item, dataclasses) — unaffected.cleaned|retry|Склад|Гараж|strip_prefixвapp/services/{estimator.py,scrapers/avito_imv.py}— noIMV-retry logic exists in current code. PR diff doesn't touch retry; description's PR #505 reference is context, not a code removal.Seven-dimension scan
address,categoryId, lat/lon) are derived from step A response or fixed constants. No user input to JSON injection vector. ✅try/except._own_sessionis always defined whenfinallyexecutes (ImportError raises beforetry). ✅_raise_for_status_categorizedreused for both A+B (consistent),_COMMON_HEADERSreused.logger.infofor step boundaries (observable). ✅Bug_Avito_Imv_Houses_Catalog_Broken_OPEN.mdplan verbatim:point.lat/lonextraction, POST/js/v2/geo/position, returngeoFieldsHashasgeo_hash,_imv_evaluate()unchanged, DB schema unchanged. ✅🟡 Minor (non-blocking)
avito_imv.py:355—if not lat or not lon or not normalized:trips onlat=0.0/lon=0.0(Gulf of Guinea edge case). For real RU addresses negligible, butis Nonewould be precise:if lat is None or lon is None or not normalized:.avito_imv.py:367—categoryId: 24hardcoded — current caller is apartments-only, fine. Worth a constantCATEGORY_ID_APARTMENT = 24for future expansion.asyncio.sleepbetween A→B→C. Browser does the same so likely OK, but if Avito starts 429-ing keep it on the radar.IMVCityMismatchErrordefined but never raised (pre-existing dead exception, not introduced here).🟢 Positive
IMVAddressNotFoundError/IMVAuthError/IMVTransientError) applied symmetrically across step A and step B network/HTTP failures.errorText=from response — debuggable.logger.infolines logaddressId/locationId/metroIdafter B — observability for future contract drift detection.Raises:list._geocode()rewritten, no churn elsewhere.Live verification
PR includes concrete production trace
a0a0b820-e8a8-4eee-aa73-0ab3b98ac233(Заводская 44-а) →recommended=7,520,000 range=(7,294,400, 7,896,000) count=800. Step B confirmed returningaddressId=22292600 locationId=654070 metroId=2045. Probe command in PR body for post-deploy revalidation.Recommended next steps
recommendednon-zero on a fresh address.errorTextwe want to alarm on.Complexity / blast radius