feat(tradein): avito_imv.py — Avito IMV evaluation API client (2 requests) #444
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#444
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-avito-imv-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?
Summary
Stage 2d of Avito Scraper v2. NEW module
avito_imv.py— клиент Avito IMV (Index of Market Value) — official price evaluation API (/web/1/realty-imv/get-data). Используется как 5-й source в Stage 3 estimator (on-demand с 24h cache).Параллельно с Stage 2b (
avito_detail.py) — независимые файлы, no conflict.LOC:
avito_imv.py~320 lines,test_avito_imv_parse.py18 tests.Changes
tradein-mvp/backend/app/services/scrapers/avito_imv.pyevaluate_via_imv(address, rooms, area, floor, ...)— async 2-request flow:GET /web/1/coords/by_address→geoHash(JWT) + geo IDs (lat/lon, address/location/metro/district IDs)POST /web/1/realty-imv/get-datawith geoHash + params → price + placement_history + suggestionsIMVEvaluationdataclass: cache_key (sha256), IMVGeo, recommended/lower/higher prices, market_count, placement_history с removed_date NOT NULL, suggestionscompute_imv_cache_key(...)— deterministic sha256 hash для 24h cache lookup (на (address|params) tuple)save_imv_evaluation(db, e)— UPSERT вavito_imv_evaluationsON CONFLICT (cache_key)(refresh price + raw_response)save_imv_placement_history(db, items)— INSERT сsource='avito_imv'иremoved_date NOT NULL— критичное отличие от Stage 2c widget save (widget без removedDate, IMV единственный источник точной даты снятия)IMVAddressNotFoundError,IMVCityMismatchErrorexceptionstradein-mvp/backend/tests/test_avito_imv_parse.py(18 tests offline)tradein-mvp/backend/tests/__init__.py(pytest package resolution)Smoke
geoHashбез активной browser-сессии. В prod env с cookies Avito вернёт JWT.IMVAddressNotFoundErrorкорректно поднимается когда geoHash отсутствует.DDL alignment (verified против Stage 1 migrations)
018_avito_imv_evaluations.sql— все колонки присутствуют:cache_key,estimate_id,geo_hash,fetched_at✅017_house_placement_history.sql— колонкаscraped_at(НЕfetched_at) — INSERT адаптированON CONFLICT (estimate_id):COALESCE(EXCLUDED, existing)— не перезаписывает уже-привязанную evaluationOut-of-scope (для Stage 3)
save_imv_suggestions(append IMV suggestions в listings) — отложено до Stage 3, т.к. требует решения поfrom_imv_suggestflag columnTest plan
Behavioral
Stage 2d of AvitoScraper_v2. - evaluate_via_imv(address, rooms, area, floor, ...) — async 2-request flow: 1) GET /web/1/coords/by_address → geoHash + geo IDs 2) POST /web/1/realty-imv/get-data → price + placementHistory + suggestions - IMVEvaluation dataclass: cache_key (sha256), geo, price (recommended/lower/higher/market_count), placement_history with removed_date, suggestions - compute_imv_cache_key(...) — deterministic hash for 24h cache lookup - save_imv_evaluation(db, e) — UPSERT в avito_imv_evaluations ON CONFLICT(cache_key) - save_imv_placement_history(db, items) — INSERT с source='avito_imv' и removed_date NOT NULL (отличие от Stage 2c widget save flow) - IMVAddressNotFoundError / IMVCityMismatchError exceptions - pytest offline smoke (cache_key determinism + unix→date + dataclass construct) Note: suggestions saving в listings skipped — Stage 3 решит when integrating. Note: house_placement_history использует scraped_at (не fetched_at) — по DDL 017. avito_imv_evaluations сохраняет geo_hash в отдельную колонку — по DDL 018.Merged via deep-code-reviewer — verdict APPROVE (minor notes).
DDL alignment: verified против data/sql/018_avito_imv_evaluations.sql и 017_house_placement_history.sql — все колонки match (
cache_key,estimate_id,geo_hash,fetched_at;scraped_atдля placement history). UNIQUE(source, ext_item_id) ✅.removed_dateNULLABLE в DDL — code пишет NOT NULL только для source='avito_imv' (если IMV вернул unix timestamp).Cache key determinism: 4 теста (rooms / balcony / address / sha256 hex len) ✅. Edge cases для Stage 3 wrapper:
"Ленина 1"vs"ленина, 1"→ разные key (caller должен преднормализовать).42.0vs42.00стабильно черезf"{x}", но45.5000001≠45.5(callсайт должен round).Conflict с PR #443:
tests/__init__.pyempty в обоих PR (additions=0/deletions=0), git auto-merge clean — order безразличен.Minor (для Stage 3 / follow-up):
IMVCityMismatchErrordefined but never raised — reserved для sanity check Stage 3.db.commit()внутри save-функций — caller теряет control над transaction; ок для standalone module, refactor при orchestrator integration.save_imv_placement_historyбез per-item SAVEPOINT — один failing INSERT → rollback всех; ок для pilot, watch при высоких volumes.ON CONFLICT (estimate_id)— на самом деле в кодеON CONFLICT (cache_key)(что и правильно по DDLcache_key NOT NULL). Не блокирующее, но docs drift._unix_to_date(None)возвращает None → возможны NULL inserts. DDL NULLABLE покрывает.Stage 3 unblocked для cache lookup wrapper.