-- 123_macro_indicator.sql
-- #945 (Site Finder v2 / «GG-форсайт» ТЗ §8.8) — EPIC «Унифицированная сущность Макропоказатель».
-- PR A: создать единую типизированную таблицу временных рядов макропоказателей
--       и сделать backfill из уже существующих разрозненных macro-таблиц.
--
-- Архитектурный принцип (фиксирован): ДЕТЕРМИНИРОВАННО, БЕЗ LLM — это чистый SQL
-- + тонкий reader (backend/app/services/site_finder/macro.py). Никаких внешних вызовов.
--
-- Это фундамент для (последующие эпики, НЕ этот PR):
--   • Mortgage Affordability Index, макро-коэффициент, key-rate sensitivity.
--
-- Зависимости (источники backfill, НЕ дропаются — продолжают писать своими импортёрами):
--   • cbr_mortgage_series   (43_anton_import.sql) — period — РУССКАЯ строка
--                            'Месяц YYYY' (напр. 'Август 2019'), ключ ряда —
--                            устойчивый series_id (region в prod = 'sverdl').
--   • domrf_mortgage_rates  (20_schema_domrf_extras.sql) — per-bank, агрегируем в median.
--
-- indicator_type 'key_rate' НАМЕРЕННО остаётся НЕзаполненным в этом PR —
--   LIVE-скрапер ключевой ставки ЦБ это отдельный follow-up (PR B), он и заполнит 'key_rate'.
--
-- Deploy: auto-applied by deploy.yml через _schema_migrations tracking (ровно один раз).
--   Idempotent: CREATE TABLE/INDEX IF NOT EXISTS + INSERT ... ON CONFLICT DO UPDATE —
--   повторный прогон (или re-apply после refresh источников) безопасен и обновит value.
-- Migration order: эта SQL-миграция применяется ПЕРВОЙ, backend macro.py — следом.

BEGIN;

-- ── 1. Единая таблица типизированных рядов макропоказателей (ТЗ §8.8) ───────────
CREATE TABLE IF NOT EXISTS macro_indicator (
    indicator_type  TEXT NOT NULL,                  -- 'key_rate','mortgage_rate_primary','mortgage_rate_secondary',
                                                     -- 'mortgage_rate_weighted','mortgage_issued_count','mortgage_issued_volume',
                                                     -- 'mortgage_debt','mortgage_overdue_share','income_per_capita','cpi',
                                                     -- 'housing_input','construction_price_index', ...
    region          TEXT NOT NULL DEFAULT 'rf',      -- 'rf' | 'sverdl' | ...
    obs_date        DATE NOT NULL,                   -- нормализовано к 1-му числу месяца для monthly-рядов
    value           NUMERIC,
    source          TEXT NOT NULL,                   -- 'cbr' | 'domrf' | 'rosstat' | ...
    frequency       TEXT NOT NULL DEFAULT 'monthly', -- 'daily'|'monthly'|'quarterly'|'yearly'
    unit            TEXT,                            -- '%','шт','млрд руб','руб', ...
    comment         TEXT,
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (indicator_type, region, obs_date)
);

CREATE INDEX IF NOT EXISTS idx_macro_type_region_date
    ON macro_indicator (indicator_type, region, obs_date DESC);

COMMENT ON TABLE macro_indicator IS
    'Унифицированный типизированный временной ряд макропоказателей (ТЗ §8.8, #945). '
    'Long-формат: (indicator_type, region, obs_date) → value. Backfill из cbr_mortgage_series '
    '+ domrf_mortgage_rates (источники не дропаются). indicator_type=key_rate заполняет PR B '
    '(LIVE-скрапер ставки ЦБ). Детерминированно, без LLM. Потребители: affordability/'
    'sensitivity-эпики через backend/app/services/site_finder/macro.py.';

-- ── 2. Backfill из cbr_mortgage_series ─────────────────────────────────────────
-- period — РУССКАЯ строка 'Месяц YYYY' (напр. 'Август 2019', 'Апрель 2020').
--   to_date(period,'Mon YYYY') здесь НЕ работает: на русском месяце Postgres падает
--   с «invalid value "Август" for "Mon"». Поэтому парсим руками:
--   make_date(YYYY, <номер месяца>, 1), где номер месяца — CASE по русскому названию
--   (split_part(period,' ',1)). 1-е число месяца зашито в make_date — date_trunc не нужен.
-- Маппинг по УСТОЙЧИВОМУ КЛЮЧУ series_id (НЕ по title!). В prod строка «в рублях»
--   живёт в series_id, а title — общий («Средневзвешенная ставка по ипотечным жилищным
--   кредитам, предоставленным физическим лицам-резидентам») и НЕ содержит «в рублях»,
--   поэтому прежний title-ILIKE матчил 0 строк. series_id-значения чистые/стабильные,
--   поэтому сопоставляем ТОЧНЫМ равенством series_id = '...'. Всё прочее НЕ матчится
--   (детерминированно, без побочных эффектов). Скипнутые ряды задокументированы ниже.
--
-- Маппинг (series_id ТОЧНО → indicator_type, unit) — семейство _mortgage_ (ИЖК), форма 02.
--   У каждого показателя есть варианты _в_рублях и _итого; берём ТОЛЬКО _в_рублях —
--   маппинг обоих на один indicator_type коллиднул бы по PK (type,region,obs_date):
--     '02_13_Rates_mortgage_ставка_в_рублях'              → mortgage_rate_weighted   (%)
--         [льготно-средневзвешенная ИЖК-ставка ~7-8% в рублях; та же, что читает
--          analytics_queries._current_mortgage_rate; РЫНОЧНОЙ ставки без льгот в БД нет]
--     '02_13_Rates_mortgage_срок_в_руб'                   → mortgage_term            (мес)
--     '02_10_Quantity_mortgage_в_рублях'                  → mortgage_issued_count    (шт)
--     '02_11_New_loans_mortgage_в_рублях'                 → mortgage_issued_volume   (млн руб)
--     '02_14_Debt_mortgage_в_рублях'                      → mortgage_debt            (млн руб)
--     '02_14_Debt_mortgage_в_т.ч._просроч._в_рублях'      → mortgage_overdue         (млн руб)
--   НЕ берём: _housing_ (02_06/07/08 — все жилищные, шире ИЖК), _scpa_mortgage_
--   (02_15-18), _Share_ (02_44 — доли), и все _итого-варианты.
--
-- Guard: пропускаем строки с NULL value; строки с непарсящимся period (в prod в
--   period встречается мусор вроде '10054588.0') — отсекаем регуляркой
--   '^[А-Яа-яЁё]+ [0-9]{4}$' (русское слово + 4-значный год) ДО парсинга, плюс
--   требуем, чтобы CASE по названию месяца дал не-NULL номер (неизвестное слово →
--   month_num IS NULL → строка скипается, make_date никогда не получает NULL);
--   и строки, чей series_id не входит в маппинг (indicator_type IS NULL).
-- region берётся из источника КАК ЕСТЬ (в prod это 'sverdl' — единственный регион в
--   cbr_mortgage_series; 'rf'-рядов там нет).
--
-- Замечание про единицы: фактические единицы (млн/млрд руб, шт/единиц) зависят от
--   конкретного XLSX-листа ЦБ; ставим наиболее вероятную единицу формы 02. Это поле
--   справочное (не участвует в PK) и легко правится отдельным UPDATE при необходимости.

INSERT INTO macro_indicator
    (indicator_type, region, obs_date, value, source, frequency, unit, comment)
SELECT
    m.indicator_type,
    s.region,
    make_date(split_part(s.period, ' ', 2)::int, m.month_num, 1) AS obs_date,
    s.value,
    'cbr'                                                    AS source,
    'monthly'                                                AS frequency,
    m.unit,
    'backfill #945 from cbr_mortgage_series; series_id=' || s.series_id AS comment
FROM cbr_mortgage_series AS s
CROSS JOIN LATERAL (
    SELECT
        CASE s.series_id
            WHEN '02_13_Rates_mortgage_ставка_в_рублях'         THEN 'mortgage_rate_weighted'
            WHEN '02_13_Rates_mortgage_срок_в_руб'              THEN 'mortgage_term'
            WHEN '02_10_Quantity_mortgage_в_рублях'             THEN 'mortgage_issued_count'
            WHEN '02_11_New_loans_mortgage_в_рублях'            THEN 'mortgage_issued_volume'
            WHEN '02_14_Debt_mortgage_в_рублях'                 THEN 'mortgage_debt'
            WHEN '02_14_Debt_mortgage_в_т.ч._просроч._в_рублях' THEN 'mortgage_overdue'
            ELSE NULL
        END AS indicator_type,
        CASE s.series_id
            WHEN '02_13_Rates_mortgage_ставка_в_рублях'         THEN '%'
            WHEN '02_13_Rates_mortgage_срок_в_руб'              THEN 'мес'
            WHEN '02_10_Quantity_mortgage_в_рублях'             THEN 'шт'
            WHEN '02_11_New_loans_mortgage_в_рублях'            THEN 'млн руб'
            WHEN '02_14_Debt_mortgage_в_рублях'                 THEN 'млн руб'
            WHEN '02_14_Debt_mortgage_в_т.ч._просроч._в_рублях' THEN 'млн руб'
            ELSE NULL
        END AS unit,
        -- русское название месяца → номер (1..12); неизвестное слово → NULL (скип ниже)
        CASE split_part(s.period, ' ', 1)
            WHEN 'Январь'   THEN 1  WHEN 'Февраль' THEN 2  WHEN 'Март'     THEN 3
            WHEN 'Апрель'   THEN 4  WHEN 'Май'     THEN 5  WHEN 'Июнь'     THEN 6
            WHEN 'Июль'     THEN 7  WHEN 'Август'  THEN 8  WHEN 'Сентябрь' THEN 9
            WHEN 'Октябрь'  THEN 10 WHEN 'Ноябрь'  THEN 11 WHEN 'Декабрь'  THEN 12
            ELSE NULL
        END AS month_num
) AS m
WHERE s.value IS NOT NULL
  AND m.indicator_type IS NOT NULL
  -- period — русская строка 'Месяц YYYY'; отсекаем мусор (напр. '10054588.0')
  AND s.period ~ '^[А-Яа-яЁё]+ [0-9]{4}$'
  -- неизвестное название месяца → month_num NULL → скип (make_date не получит NULL)
  AND m.month_num IS NOT NULL
ON CONFLICT (indicator_type, region, obs_date) DO UPDATE
SET value      = EXCLUDED.value,
    source     = EXCLUDED.source,
    frequency  = EXCLUDED.frequency,
    unit       = EXCLUDED.unit,
    comment    = EXCLUDED.comment,
    updated_at = now();

-- ── 3. Backfill из domrf_mortgage_rates (per-bank → market median) ─────────────
-- Источник — ставки топ-банков по дате снапшота. Агрегируем к рыночному ряду:
--   median(primary_rate) по snapshot_date (set-based, NULL игнорируются percentile_cont).
-- ДОМ.РФ-ставка по первичке хранится под ОТДЕЛЬНЫМ indicator_type, чтобы не
--   коллидировать по PK с возможной cbr-ставкой на ту же дату (PK = type+region+obs_date).
--   ЦБ-ряд выше = mortgage_rate_weighted (льготная, ~7-8%); это иное измерение, поэтому
--   их сосуществование корректно и без disambiguation, но рыночную первичку ДОМ.РФ всё
--   равно держим отдельным типом 'mortgage_rate_primary_domrf' для прозрачности источника.
-- snapshot_date берём как obs_date КАК ЕСТЬ (это не строго monthly-ряд — это снапшоты);
--   frequency помечаем 'daily' (нерегулярные снапшоты), НЕ нормализуем к 1-му числу,
--   чтобы не схлопнуть два снапшота одного месяца в одну строку.

INSERT INTO macro_indicator
    (indicator_type, region, obs_date, value, source, frequency, unit, comment)
SELECT
    'mortgage_rate_primary_domrf'                       AS indicator_type,
    'rf'                                                AS region,
    r.snapshot_date                                     AS obs_date,
    percentile_cont(0.5) WITHIN GROUP (ORDER BY r.primary_rate) AS value,
    'domrf'                                             AS source,
    'daily'                                             AS frequency,
    '%'                                                 AS unit,
    'backfill #945 median(primary_rate) per snapshot from domrf_mortgage_rates' AS comment
FROM domrf_mortgage_rates AS r
WHERE r.primary_rate IS NOT NULL
GROUP BY r.snapshot_date
HAVING percentile_cont(0.5) WITHIN GROUP (ORDER BY r.primary_rate) IS NOT NULL
ON CONFLICT (indicator_type, region, obs_date) DO UPDATE
SET value      = EXCLUDED.value,
    source     = EXCLUDED.source,
    frequency  = EXCLUDED.frequency,
    unit       = EXCLUDED.unit,
    comment    = EXCLUDED.comment,
    updated_at = now();

-- ── Skipped / не сопоставлено (детерминированно, документировано) ──────────────
--   • sber_ddf_metrics — пропущено намеренно (формат «2 точки value_2019/value_2025»
--     без месячной шкалы; не временной ряд, ТЗ допускает пропуск).
--   • Любые cbr_mortgage_series, чей series_id не входит в маппинг выше (все _итого-
--     варианты, _housing_ 02_06/07/08, _scpa_mortgage_ 02_15-18, _Share_ 02_44 и др.) —
--     НЕ вставлены. Это видно по COUNT источника vs вставленных строк; маппинг
--     расширяется отдельной миграцией добавлением series_id в оба CASE.
--   • indicator_type='key_rate' — пусто (заполнит PR B, LIVE-скрапер ставки ЦБ).

COMMIT;
