Миграция 178 засеяла deal_city_price_bands разово (N>=30 сделок), без scheduler'а
на refresh. Город без строки падал на глобальный DEAL_MIN_PPM2=50000 (ЕКБ-калибровка)
— для дешёвых городов области это не anti-outlier guard, а cut-off легитимного рынка
(Североуральск median ~21.7k). Замер по прод-данным: 289 из 369 не-ЕКБ городов
(1265 сделок) не имели строки и падали на ЕКБ-порог.
- 194_deal_city_price_bands_tiers.sql — трёхуровневая схема (tier колонка):
full (N>=30, own p1/p99, unchanged) / rough (N 10-29, own p1 floor + фикс.
ceiling 800000) / region_fallback (N 1-9, pooled областной p1=15263 вместо
ЕКБ-порога). Екатеринбург по-прежнему не в таблице — estimator fallback
byte-identical.
- 195_scrape_schedules_seed_deal_city_price_bands_refresh.sql — scrape_schedules
row, окно 07:00-08:00 UTC (после rosreestr_dkp_import + asking_to_sold_ratio_refresh).
- app/tasks/deal_city_price_bands_refresh.py — периодический re-derive (kit-scheduler,
byte-identical 194 derivation), без DELETE (множество городов монотонно растёт).
- app/services/product_handlers.py — регистрация Handler для нового source.
Валидация: scratch-БД (syntax_check) в прод-контейнере, synthetic данные на
границах тиров (N=9/10/29/30) + Екатеринбург/non-rosreestr/NULL exclusion, оба
файла применены дважды (идемпотентность подтверждена), scratch-БД удалена.
Добавляет sber_freshness_monitor по образцу deals_freshness_monitor:
staleness данных СберИндекса теперь видна на MONITOR-частоте, а не тонет
в per-estimate warning'ах estimator._load_sber_index_series (#audit-5a).
- app/tasks/sber_freshness_monitor.py: чистая evaluate_sber_freshness()
(frozen-now, без БД) + check_sber_freshness() (один SELECT
max(period_month) вторичного сегмента по региону, #R2-H1 фильтр как в
эстиматоре; WARNING при stale, mark_done при алерте — это монитор, не
сбой; mark_failed только при пустой таблице).
- app/services/product_handlers.py: _job_sber_freshness_monitor + Handler
в build_product_handlers (run_in_executor, как deals-монитор).
- data/sql/180_seed_sber_freshness_monitor.sql: seed scrape_schedules
(enabled, daily 09:00-10:00 UTC, lag_allowance_days=25).
- tests/test_sber_freshness_monitor.py: frozen-now (fresh/stale/границы) +
FakeDB (fresh/stale/empty/кастомный lag) + свойства миграции + registry.
Порог алерта: sber_index_max_age_days (35) + lag_allowance (25) = 60д.
+25 — запас на инхерентный лаг публикации источника (1-2 мес), чтобы не
шуметь на штатном отставании. Прод 2026-07-12: max=2026-05-01, age=72д >
60 → alert=1.
Финальный PR issue #2045 (BE-3): GET /api/v1/trade-in/location-coef для
LocationDrawer. FDW foreign table -> локальное зеркало osm_poi_ekb_local
(TRUNCATE+INSERT, тот же паттерн что cad_buildings_local/cadastral_geo_match,
избегает ~1.16s/row FDW round-trip) -> straight-line POI-скоринг, портированный
из Site Finder poi_score.py::compute_poi_weighted_top7 (CATEGORY_WEIGHTS as-is,
радиус 1200м для квартир вместо Ptica 2000м для участков). score->coef -
новая MVP-эвристика (0.95..1.05, не откалибрована на реальных дельтах).
Graceful fallback (не 500, не фабрикуем факторы): пустая/не отрефрешенная
osm_poi_ekb_local или отсутствие lat/lon у оценки -> coef=1.0, factors=[],
geo_source="unavailable".
Scheduler: source=osm_poi_ekb_refresh, daily, зарегистрирован и в боевом
dispatch (scheduler.py), и в kit-registry (product_handlers.py) - иначе
test_kit_registry_completeness падает на ship-dark инварианте (#2192).
Frontend wiring (mappers.ts/LocationDrawer.tsx) - вне scope, отдельная задача
после проверки endpoint'а curl'ом на деплое.