feat(tradein): populate offer_price_history for yandex from gate price.previous/trend #1754
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#1754
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/yandex-price-history"
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?
Проблема
offer_price_historyдля yandex фактически пуст (50 строк против cian 8582). Источник истории цен — gate-ответ уже несётprice.previous+price.trend(парсятся вScrapedLot.price_previous_rub/price_trend, yandex_realty.py #1725), ноsave_listingsих роняет. Разведка показала: отдельный otsenka-скрапер НЕ нужен — gate-данных достаточно.Решение
Новый сервис
yandex_price_history.py(зеркало паттернаcian_price_history.py) пишет вoffer_price_historyиз уже-распарсенных gate-полей. Вызывается сразу послеsave_listingsвrun_yandex_city_sweep(per-anchor) иrun_yandex_full_load(on_bucket).Логика (idempotent), per yandex-lot:
price_previous_rubзадан и ≠ текущей → seed previous-точка(price_previous_rub, change_time = now−1d)+ current(price_rub, now);(price_rub, now);Forward-наблюдения строят time-series по ходу свипов;
price.previousдаёт одну стартовую историческую точку. Дизайн-выбор для ревью: timestamp previous-точки синтетический (now−1d) — gate реального времени смены не несёт, только направление (trend) и предыдущее значение. Если для чарта нужна иная семантика — скажите, переделаю.Изменения
app/services/yandex_price_history.py(нов) —record_yandex_price_history(db, lots) -> int. Резолв listing_id по(source='yandex', source_id); per-lotbegin_nested()SAVEPOINT (битый lot не валит батч); psycopg v3 (CAST(:x AS bigint/numeric);change_time— нативный datetime БЕЗ CAST, т.к. CAST ломает SQLite-фикстуру).app/services/scrape_pipeline.py—price_history_rowsвYandexCitySweepCounters/YandexFullLoadCounters; вызов после каждогоsave_listings, обёрнут в try/except+rollback (провал истории НЕ роняет сейв листингов).tests/test_yandex_price_history.py(нов, 12 тестов).Схема
offer_price_historyне менялась.yandex_realty.pyне тронут (там чужой активный WIP).Тесты
Full suite (prescribed deselects): 1975 passed, 1 skipped, 0 failed. Ruff clean.
Прод-смоук после merge
Дождаться yandex-свипа (или ручной trigger) →
SELECT source,count(*) FROM offer_price_history GROUP BY 1— yandex должен расти с ~50.✅ APPROVE — clean worktree head
6c82528d.Зеркало cian_price_history, psycopg v3-дисциплина соблюдена:
with db.begin_nested()SAVEPOINT — битый lot не валит батч (откат только его). db.commit() в конце.Схема прод сверена: offer_price_history.{listing_id,change_time,price_rub} NOT NULL (null-price → SAVEPOINT-откат, graceful), source nullable; все 7328 yandex-листингов имеют source_id → резолв работает; oph baseline 50.
Дизайн-выбор synthetic now-1d для previous-точки принят (gate реального change-time не несёт). Если для чарта нужна иная семантика — отдельным PR.
Тесты на чистом дереве: 1975 passed, 0 failed. Ruff clean. Merge (merge style).