-- 171_scrape_schedules_seed_geoportal_coords_backfill.sql
-- Seed row делающий recurring уже существовавший manual-only geoportal coords backfill.
--
-- Context (#1967): avito ~47% активных листингов имели NULL lat/lon → невидимы для
-- ST_DWithin радиус-поиска аналогов в estimator'е. Root cause — avito SERP-карточки
-- намеренно сохраняются с lat=lon=NULL (координаты приходят точнее из detail-страницы
-- или из geocode-cron по address). geocode_missing_listings (migration 110) с 2026-06-16
-- уже закрывает большую часть разрыва через Nominatim/Yandex (rate-limited, coarse
-- city-centroid когда geocoder не доходит до дома).
--
-- app/tasks/backfill_listings_coords_geoportal.py существует с #1841 (точный street+house
-- матч против местного реестра зданий ЕКБ, ekb_geoportal_buildings) и даёт house-level
-- precision БЕЗ единого внешнего HTTP-запроса (в отличие от Nominatim) — но был ТОЛЬКО
-- manual script (`python -m app.tasks.backfill_listings_coords_geoportal`), ни разу не
-- запускавшийся на recurring основе. Один прошлый ручной прогон (#1841): 17241
-- кандидатов → 1008 проставлено (не-ЕКБ адреса не матчатся — корректно, EKB-only реестр).
--
-- Решение: wire в in-app scheduler (source='geoportal_coords_backfill') по паттерну
-- cadastral_geo_match (migration 125) — pure internal DB op, SAFE to enable=true.
--
-- Window 05:00-06:00 UTC — ДО geocode_missing_listings (06:00-09:00 UTC, migration 110):
--   точный house-level матч должен получить шанс ПЕРВЫМ. Если Nominatim успевает
--   проставить (более грубые) coords раньше — адрес выпадает из WHERE lat IS NULL и
--   более точный geoportal-матч уже не применяется до следующего появления NULL-строки.
--
-- default_params:
--   limit       — None (без ограничений, весь backlog за один прогон; pure local DB —
--                  быстро, никакого wall-clock budget как у geocode_missing_listings не нужно).
--   batch_size  — 500 строк на SELECT + commit-интервал (см. DEFAULT_BATCH_SIZE в модуле).
--   sources     — None (все источники; ekb_geoportal_buildings сам ограничивает scope до ЕКБ).
--
-- ЗАВИСИМОСТИ: 052_scrape_schedules.sql (таблица + UNIQUE(source)).
-- Idempotent: ON CONFLICT (source) DO NOTHING — безопасно запускать повторно.

BEGIN;

INSERT INTO scrape_schedules (
    source,
    enabled,
    window_start_hour,
    window_end_hour,
    next_run_at,
    default_params
)
VALUES
(
    'geoportal_coords_backfill',
    true,                                        -- ACTIVE: pure internal DB op, no external
                                                 -- HTTP/anti-bot/rate-limit exposure.
    5,
    6,
    ((CURRENT_DATE + INTERVAL '1 day') + make_interval(hours => 5)) AT TIME ZONE 'UTC',
    '{"batch_size": 500}'::jsonb
)
ON CONFLICT (source) DO NOTHING;

COMMENT ON TABLE scrape_schedules IS
    'In-app scheduler config (заменяет cron-script setup). '
    'Sources: avito_city_sweep, yandex_city_sweep (dormant, #561), '
    'cian_history_backfill, rosreestr_dkp_import, listing_source_snapshot (#570), '
    'asking_to_sold_ratio_refresh (#648), refresh_search_matview (#769), '
    'yandex_address_backfill (#855, EKB pilot), '
    'sber_index_pull (#887, monthly СберИндекс city-level price index), '
    'rosreestr_quarter_poll (#888, monthly Rosreestr new-quarter availability check), '
    'cian_city_sweep (dormant, #973), '
    'yandex_newbuilding_sweep (dormant, #974), '
    'geocode_missing_listings (#1: listings geom backfill, all sources), '
    'geoportal_coords_backfill (#1967: exact EKB house-level coords match, recurring '
    'wiring of the former manual-only #1841 script — runs BEFORE geocode_missing_listings).';

COMMIT;
