-- 034_asf.sql
-- ASF — Autoritatea de Supraveghere Financiară.
-- Public registries of authorized financial entities (insurers, brokers, pension
-- funds, asset managers, intermediaries) scraped from data.asfromania.ro.
--
-- Sources (all return JSON{raspuns:HTML, status:100} via POST cautare):
--   1. /scr/ra/cautare?l=ro  (Registrul asigurătorilor + intermediarilor)
--      sectiune=1 tipCompanie=0 → Societăți de asigurare - companii active
--      sectiune=2 tipCompanie=0 → Societăți de asigurare - companii radiate
--      sectiune=1 tipCompanie=1 → Intermediari principali - companii active
--      sectiune=2 tipCompanie=1 → Intermediari principali - companii radiate
--      Fields per panel: register_no (RA-XXX/RBK-XXX), LEI, CUI, RC code,
--      authorization no/date, registration date, radiation date, type, legal form,
--      address, phone, fax, observations, authorized classes (general/life),
--      executives. Total: ~768 insurers + ~801 brokers ≈ 1.5K entities.
--
--   2. /scr/ra/cautare endpoint accepts free-text 'termen' (≥4 chars). Search
--      hits denumire, CUI, adresă, județ, classes. NO captcha required when
--      'g-recaptcha-response' field is OMITTED from the POST body. (When sent
--      with any non-empty value the server tries to verify and returns
--      "Verificare captcha eșuată".)
--
--   3. Pension funds + AIFM/UCITS register pages exist on asfromania.ro/ro/a/...
--      but most are F5-WAF-protected from non-browser clients. We start with the
--      ra portal which has cleanest data; document handoff for additional
--      registers in ASF-PLAN.md.
--
-- Cross-source value: asf.entitati.cui (extracted directly from response, no
-- fuzzy match needed) × seap.announcements.supplier_cui = "ASF-licensed firms
-- with state contracts". Red-flag: insurance firm wins SEAP contract for state
-- insurance services but has been radiated by ASF; broker active in SEAP but
-- with suspended/withdrawn ASF authorization.

CREATE SCHEMA IF NOT EXISTS asf;

-- ── 1. Authorized entities (insurers, brokers, pension funds, AIFM, UCITS) ──
-- One row per distinct ASF register entry. Every entity has a register_no
-- (RA-NNN for insurers, RBK-NNN for brokers, etc.) which is globally unique
-- per register_type.
CREATE TABLE IF NOT EXISTS asf.entitati (
  id              bigserial PRIMARY KEY,
  register_type   text NOT NULL,          -- 'asigurator' | 'broker' | 'fond_pensii' | 'aifm' | 'ucits' | 'intermediar_secundar'
  section_status  text NOT NULL,          -- 'activ' | 'radiat'  (mirrors source sectiune=1/2 split)
  register_no     text NOT NULL,          -- e.g. "RA-057", "RBK-123"  (unique within register_type)
  name            text NOT NULL,          -- raw "Denumire"
  name_normalized text,                   -- firms.normalize_company_name(name) — for trigram fallback
  cui             text,                   -- "Cod unic de identificare (CUI)"
  cod_rc          text,                   -- "Cod unic RC" (e.g. J40/2226/2006)
  cod_lei         text,                   -- LEI 20-char
  nr_autorizatie  text,                   -- "Număr autorizație"  (e.g. 114.146)
  data_autorizare date,                   -- "Dată autorizare"
  data_inmatriculare date,                -- "Dată înmatriculare"
  data_radiere    date,                   -- "Dată radiere" (NULL when active)
  tip_companie    text,                   -- "Tip companie" (Societate de asigurare / Intermediar principal / etc.)
  forma_juridica  text,                   -- "Formă juridică"
  adresa          text,                   -- "Adresă"
  telefon         text,
  fax             text,
  email           text,
  web             text,
  observatii      text,                   -- free-text remarks
  clase_autorizate jsonb,                 -- {"asigurari_generale":[...], "asigurari_viata":[...]}
  conducere       jsonb,                  -- [{"nume":"X","functie":"Y","din":"DD.MM.YYYY"}]
  raw_html        text,                   -- raw panel HTML for traceability
  fetched_at      timestamptz NOT NULL DEFAULT now(),
  UNIQUE (register_type, register_no)
);

CREATE INDEX IF NOT EXISTS idx_asf_entitati_cui            ON asf.entitati(cui)         WHERE cui IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_asf_entitati_name_norm_trgm ON asf.entitati USING gin (name_normalized gin_trgm_ops);
CREATE INDEX IF NOT EXISTS idx_asf_entitati_type_status    ON asf.entitati(register_type, section_status);
CREATE INDEX IF NOT EXISTS idx_asf_entitati_radiere        ON asf.entitati(data_radiere) WHERE data_radiere IS NOT NULL;

COMMENT ON TABLE asf.entitati IS
  'ASF authorized entities — insurers, brokers, pension funds, AIFM/UCITS, intermediaries. Source: data.asfromania.ro/scr/ra (and other registers).';
COMMENT ON COLUMN asf.entitati.register_type IS
  'asigurator (RA-NNN) / broker (RBK-NNN) / fond_pensii / aifm / ucits / intermediar_secundar';
COMMENT ON COLUMN asf.entitati.section_status IS
  'activ / radiat — mirrors source sectiune=1/sectiune=2 split. Active record has data_radiere=NULL.';

-- ── 2. Scrape log (mirrors anre.scrape_log convention) ──────────────────────
CREATE TABLE IF NOT EXISTS asf.scrape_log (
  id            bigserial PRIMARY KEY,
  scraper       text NOT NULL,            -- 'asigurator_activ' / 'asigurator_radiat' / 'broker_activ' / ...
  source_url    text NOT NULL,
  rows_seen     integer NOT NULL DEFAULT 0,
  rows_inserted integer NOT NULL DEFAULT 0,
  rows_updated  integer NOT NULL DEFAULT 0,
  rows_skipped  integer NOT NULL DEFAULT 0,
  duration_ms   integer NOT NULL DEFAULT 0,
  started_at    timestamptz NOT NULL,
  finished_at   timestamptz NOT NULL DEFAULT now(),
  error         text
);

CREATE INDEX IF NOT EXISTS idx_asf_scrape_log_started ON asf.scrape_log(started_at DESC);

-- ── 3. Materialized view: per-CUI ASF rollup ────────────────────────────────
-- Joinable with seap.announcements.supplier_cui to detect financial firms
-- holding state contracts.
CREATE MATERIALIZED VIEW IF NOT EXISTS asf.mv_entitati_per_cui AS
SELECT
  cui,
  COUNT(*)                                                        AS nr_total,
  COUNT(*) FILTER (WHERE register_type = 'asigurator')            AS nr_asigurator,
  COUNT(*) FILTER (WHERE register_type = 'broker')                AS nr_broker,
  COUNT(*) FILTER (WHERE register_type = 'fond_pensii')           AS nr_fond_pensii,
  COUNT(*) FILTER (WHERE register_type = 'aifm')                  AS nr_aifm,
  COUNT(*) FILTER (WHERE register_type = 'ucits')                 AS nr_ucits,
  COUNT(*) FILTER (WHERE section_status = 'activ')                AS nr_active,
  COUNT(*) FILTER (WHERE section_status = 'radiat')               AS nr_radiate,
  array_agg(DISTINCT register_type)                               AS register_types,
  array_agg(DISTINCT register_no ORDER BY register_no)            AS register_numbers,
  MIN(data_autorizare)                                            AS prima_autorizare,
  MAX(data_radiere)                                               AS ultima_radiere
FROM asf.entitati
WHERE cui IS NOT NULL
GROUP BY cui;

CREATE UNIQUE INDEX IF NOT EXISTS idx_asf_mv_entitati_per_cui ON asf.mv_entitati_per_cui(cui);

COMMENT ON MATERIALIZED VIEW asf.mv_entitati_per_cui IS
  'Rollup of ASF entities per CUI. Refresh: REFRESH MATERIALIZED VIEW CONCURRENTLY asf.mv_entitati_per_cui';
