feat(tradein): integrate Avito IMV as 5th evaluation source (on-demand cached) #452
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#452
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-estimator-imv-integration"
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?
Summary
Stage 3 of Avito Scraper v2 — интеграция Avito IMV (Index of Market Value) как 5-й source в
estimator.py. On-demand only (per estimator call), с 24h cache по sha256 hash address+params. IMV НЕ в cron (per план update — bulk IMV → rate-limit risk + stale cache).LOC: estimator.py
+190/-3(net +187), новый test file 102 lines.Changes
tradein-mvp/backend/app/services/estimator.py_get_or_fetch_imv_cached(db, **params) -> IMVEvaluation | Nonecache_key(sha256 of address + IMV input params)avito_imv_evaluations WHERE cache_key AND fetched_at > NOW() - 24h→ reconstruct IMVEvaluationevaluate_via_imv(2 HTTP requests to Avito) →save_imv_evaluation→ return_IMV_HOUSE_TYPE_MAP,_IMV_REPAIR_MAP— переводят наши enum-значения в Avito IMV formatestimate_quality():_get_or_fetch_imv_cachedесли payload has required fields'avito_imv'вsources_used(saved row + returnedAggregatedEstimate)estimate_idвavito_imv_evaluationsrow для analytics joiningimv=<recommended_price>в final estimate log linetradein-mvp/backend/tests/test_estimator_imv_integration.pyanyio.run()(pytest-asyncio не в venv tradein-mvp — anyio есть)Smoke
Behavioral changes
avito_imv(если params valid + IMV доступен)avito_imv_evaluationstable, TTL 24h по sha256 cache_keysources_used[avito, cian, rosreestr, yandex, n1](whatever есть)avito_imvопциональноavito_imv_evaluations.estimate_idtrade_in_estimates.idдля analyticsOut-of-scope
avito_imvbenchmark в frontend — Stage 4b (EstimateResult badge)from_imv_suggestflag для save_imv_suggestions в listings — отложеноTest plan
sources_usedсодержитavito_imvИavito_imv_evaluationsrow createdRefs
avito_imv.py)Deep Code Review — 🟠 HIGH (blocker is in dependency, not this PR)
Summary
cache_keysave↔lookup consistency: PASS — both sides callcompute_imv_cache_keywith same 9 args in same order; produces matching sha256.except ExceptionreturnsNone; estimator continues without IMV.sources_usedconsistency: PASS — both saved JSON column and returnedAggregatedEstimate.sources_usedget"avito_imv"appended.estimate_idlinking strategy: PASS — first-call wins (UPDATE clauseWHERE estimate_id IS NULL OR estimate_id = :id), matches recommended COALESCE-equivalent semantics.🔴 Blocker (NOT introduced by this PR — discovered by it)
tradein-mvp/backend/data/sql/018_avito_imv_evaluations.sql:54—cache_keycolumn has noUNIQUEconstraint (only non-uniqueimv_cache_key_idx).But
save_imv_evaluationinavito_imv.py:481usesON CONFLICT (cache_key) DO UPDATE. PostgreSQL will raise at runtime:Live impact: first
POST /api/v1/trade-in/estimatewith IMV-compatible payload → save fails → caught byexcept Exceptionin_get_or_fetch_imv_cached→ logged as warning, returnsNone. Cache never populates. Every subsequent call re-fetches from Avito (rate-limit risk + 2-5s latency per request).Why not caught earlier: PR #444 (Stage 2d) tests likely mocked
save_imv_evaluation. This PR's tests also mock save. Bug only manifests on first live POST.Fix required (separate PR, merge BEFORE #452)
(No
CONCURRENTLYneeded if table empty / fresh.)🟡 Medium / nits (non-blocking)
estimator.py:128-150) —placement_history=[],suggestions=[],search_similar_url=Noneset by dataclass defaults. Estimator current scope only readsrecommended_price, but Stage 4a/4b consumers will see empty lists on cache hit. Document or persist+restore.has_loggia=Falsehardcoded (estimator.py:328) — addinghas_loggiato payload later → mass cache invalidation. Note indecisions/or module doc.house_type == "other"silently skips IMV without log. Addlogger.info("imv: skipped — house_type=%s not in IMV map")for observability.os.environ.setdefault("DATABASE_URL", ...)in test file — should be in sharedconftest.py. Follow-up cleanup.Recommended next steps
030_avito_imv_cache_key_unique.sql).POST /api/v1/trade-in/estimateЕКБ address → verify in logsimv: fresh recommended=...on first call, thenimv: cache HIT key=...on second call within 24h. If permanentcache MISS→ DDL fix not applied.Vault updates needed
fixes/Avito_IMV_Cache_Key_Missing_Unique.md— document the DDL bug + fixcode/modules/tradein/Estimator_IMV_Integration.md— describe 24h cache flow, graceful degradation, first-call-wins estimate_id link,has_loggia=FalselimitationVerdict
Not merging this PR yet — blocker is in DDL dependency. Once
030_avito_imv_cache_key_unique.sqlmigration lands, this PR is ready for merge (squash).Merged via deep-code-reviewer — verdict ✅ APPROVE (re-review after DDL blocker resolved).
Blocker resolution confirmed:
0db71033) at 13:04:22Z — adds030_avito_imv_cache_key_unique.sqlcreatingimv_cache_key_uniq_idxUNIQUE INDEX onavito_imv_evaluations.cache_keyON CONFLICT (cache_key) DO UPDATEвsave_imv_evaluationтеперь имеет требуемый UNIQUE constraintPre-merge verification:
7af97c2c594db169ab293966654d62709061ded3) с момента previous review — no unauthorized changes1b3edff6(post-PR #455 merge, lineage includes #454)Squash merge:
5051ebde2e8037cc8abcbf001c01f6f0d95b2148Post-merge verify (для пользователя, ~3 min after deploy):
deploy-tradein.ymlauto-trigger наtradein-mvp/backend/**pathsPOST /api/v1/trade-in/estimateдля ЕКБ address → responsesources_usedсодержитavito_imvSELECT count(*) FROM avito_imv_evaluations WHERE cache_key IS NOT NULL→ > 0SELECT estimate_id FROM avito_imv_evaluations ORDER BY fetched_at DESC LIMIT 1→ не NULLimv: cache HIT key=...(не fresh fetch)Unblocked:
avito_imvbenchmark в EstimateResult badge