-- 024_aep_donatii.sql
-- AEP — Autoritatea Electorală Permanentă — donații electorale & finanțare partide.
--
-- Sursă oficială (mandatată prin Legea 334/2006): rapoartele anuale + rapoartele
-- de venituri și cheltuieli (RVC) ale partidelor + listele de donatori publicate
-- în Monitorul Oficial pentru donații > 10 salarii minime brute.
--
-- Vehicul de ingest: portalul Expert Forum (banipartide.ro) care a aggregat-o
-- deja în SQLite și o expune via endpoint base64-SQL la
-- https://www.banipartide.ro/app/json.php?mode=dt&ssid=<base64>.
-- (Sursele primare AEP sunt PDF/Excel + reCAPTCHA, deci EFOR este path de
-- minim efort. Validate against AEP RVC PDFs as v2.)
--
-- Volume @ 2026-05-09:
--   Donatori persoane juridice (>10 sal MO): 3,612 (2006-2024)
--   Donatori persoane fizice    (>10 sal MO): 30,792 (2006-2024)
--   Donatori RVC (rapoarte venituri/cheltuieli, granular complet): 353,473
--
-- GDPR: CNP-urile sunt expuse în clear pe banipartide.ro (publicate în MO conf.
-- legii). Le hash-ăm SHA256 pe ingest — la noi NU stocăm CNP raw. Numele
-- complet e public prin lege și rămâne. Adresa pe firme (PJ) e public,
-- la persoane fizice (PF) NU avem adresă la sursă.
--
-- Cross-source value: aep.donatii_pj.donator_cui ⨯ seap.announcements.supplier_cui
--   = "donator X a donat Y RON partidului Z, apoi a câștigat W RON contracte SEAP".

CREATE SCHEMA IF NOT EXISTS aep;

COMMENT ON SCHEMA aep IS
  'Autoritatea Electorală Permanentă — donații, finanțare partide, RVC. Sursă: banipartide.ro (EFOR) → AEP/MO.';

-- ──────────────────────────────────────────────────────────────────────────
-- aep.partide — registru partide normalizat (codes from banipartide source)
-- ──────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS aep.partide (
  id            text PRIMARY KEY,         -- 'PSD', 'PNL', 'USR', 'AUR', 'UDMR', etc.
  nume_oficial  text,                     -- 'Partidul Social Democrat'
  fondat        date,
  sediu_cui     text,                     -- CIF al partidului dacă cunoscut
  status        text,                     -- 'activ' | 'dizolvat' | 'fuzionat'
  fetched_at    timestamptz DEFAULT now()
);

COMMENT ON TABLE aep.partide IS
  'Registru partide politice (cheie naturală = abreviere normalizată din sursa banipartide).';

-- ──────────────────────────────────────────────────────────────────────────
-- aep.donatii_pj — donații de la persoane juridice (>10 salarii minime, MO)
-- ──────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS aep.donatii_pj (
  id                  bigserial PRIMARY KEY,
  source_hash         char(40) NOT NULL UNIQUE,   -- sha1(nume|cui|partid|an|suma|data_donatie) for idempotent upsert
  donator_nume        text NOT NULL,
  donator_cui         text,                       -- CUI normalizat (numerals only, RO prefix stripped)
  donator_cui_raw     text,                       -- forma originală (poate avea typos / "RO")
  reprezentant        text,
  sediu               text,
  nationalitate       text,                       -- "română" / etc.
  partid_id           text REFERENCES aep.partide(id) ON UPDATE CASCADE,
  filiala_partid      text,
  suma_lei            numeric(14,2) NOT NULL,
  an                  smallint NOT NULL,
  data_donatie_text   text,                       -- format mixt în sursă: "11.10.2019; 13.11.2019" sau "10042010" — păstrăm raw
  data_donatie        date,                       -- best-effort parsed (NULL când format incompatibil sau multiple)
  tip_donatie         text,                       -- "Bani" / "Natură" / etc.
  felul_donatie       text,                       -- "Bani" / "Ordin De Plată" / "Spațiu Publicitar"
  source_url          text NOT NULL DEFAULT 'https://www.banipartide.ro/donatori-persoane-juridice.html',
  fetched_at          timestamptz DEFAULT now()
);

CREATE INDEX IF NOT EXISTS idx_aep_donatii_pj_cui ON aep.donatii_pj(donator_cui) WHERE donator_cui IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_aep_donatii_pj_partid ON aep.donatii_pj(partid_id);
CREATE INDEX IF NOT EXISTS idx_aep_donatii_pj_an ON aep.donatii_pj(an);
CREATE INDEX IF NOT EXISTS idx_aep_donatii_pj_suma ON aep.donatii_pj(suma_lei DESC);

COMMENT ON TABLE aep.donatii_pj IS
  'Donații de la persoane juridice către partide, peste pragul de 10 salarii minime brute (publicate în MO). Sursă: banipartide.ro → AEP. Granularitate: o linie per (donator, partid, an, sumă, dată).';
COMMENT ON COLUMN aep.donatii_pj.source_hash IS
  'sha1(nume_lower|cui|partid|an|suma|data_text). Garantează idempotenta scraperului.';

-- ──────────────────────────────────────────────────────────────────────────
-- aep.donatii_pf — donații de la persoane fizice (>10 sal min, MO)
-- CNP hash-uit (NICIODATĂ raw în DB).
-- ──────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS aep.donatii_pf (
  id                  bigserial PRIMARY KEY,
  source_hash         char(40) NOT NULL UNIQUE,   -- sha1(nume|cnp_hash|partid|an|suma|data)
  donator_nume        text NOT NULL,
  donator_cnp_sha256  char(64),                   -- SHA-256 hex of CNP (only if CNP was non-empty in source)
  partid_id           text REFERENCES aep.partide(id) ON UPDATE CASCADE,
  organizatia         text,                       -- filiala / organizatia partidului
  suma_lei            numeric(14,2) NOT NULL,
  an                  smallint NOT NULL,
  data_donatie_text   text,
  data_donatie        date,
  tip_donatie         text,                       -- "Donație" / "Cotizație" / "Împrumut"
  ce_s_a_donat        text,                       -- "Bani" / "Bunuri" / etc.
  source_url          text NOT NULL DEFAULT 'https://www.banipartide.ro/donatori-persoane-fizice.html',
  fetched_at          timestamptz DEFAULT now()
);

CREATE INDEX IF NOT EXISTS idx_aep_donatii_pf_cnp_hash ON aep.donatii_pf(donator_cnp_sha256) WHERE donator_cnp_sha256 IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_aep_donatii_pf_nume ON aep.donatii_pf(donator_nume);
CREATE INDEX IF NOT EXISTS idx_aep_donatii_pf_partid ON aep.donatii_pf(partid_id);
CREATE INDEX IF NOT EXISTS idx_aep_donatii_pf_an ON aep.donatii_pf(an);

COMMENT ON TABLE aep.donatii_pf IS
  'Donații de la persoane fizice către partide, peste pragul de 10 salarii minime (publicate în MO). CNP-urile sunt SHA-256 hashed la ingest. Sursă: banipartide.ro.';
COMMENT ON COLUMN aep.donatii_pf.donator_cnp_sha256 IS
  'SHA-256 hex digest al CNP. Permite re-identificare dacă cineva are CNP-ul, dar nu dezvăluie CNP-ul. NU e key-uit cu salt — scopul e doar de-duplicare cross-an, nu protecție criptografică împotriva brute-force pe spațiul CNP-urilor românești.';

-- ──────────────────────────────────────────────────────────────────────────
-- aep.donatii_rvc — toți donatorii din rapoartele de venituri/cheltuieli
-- (donații + cotizații + împrumuturi, fără pragul de 10 salarii)
-- ──────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS aep.donatii_rvc (
  id                  bigserial PRIMARY KEY,
  source_hash         char(40) NOT NULL UNIQUE,
  donator_nume        text NOT NULL,
  judet               text,                       -- "Alba", "București"
  cod_judet           text,                       -- "AB", "B"
  tip_venit           text,                       -- "Cotizație" | "Donație" | "Împrumut"
  partid_id           text REFERENCES aep.partide(id) ON UPDATE CASCADE,
  suma_lei            numeric(14,2) NOT NULL,
  mod_incasare        text,                       -- "Banca" | "Numerar" | etc.
  an                  smallint NOT NULL,
  data_donatie_text   text,
  data_donatie        date,
  source_url          text NOT NULL DEFAULT 'https://www.banipartide.ro/donatori-rvc.html',
  fetched_at          timestamptz DEFAULT now()
);

CREATE INDEX IF NOT EXISTS idx_aep_donatii_rvc_partid ON aep.donatii_rvc(partid_id);
CREATE INDEX IF NOT EXISTS idx_aep_donatii_rvc_an ON aep.donatii_rvc(an);
CREATE INDEX IF NOT EXISTS idx_aep_donatii_rvc_nume ON aep.donatii_rvc(donator_nume);
CREATE INDEX IF NOT EXISTS idx_aep_donatii_rvc_judet ON aep.donatii_rvc(judet);

COMMENT ON TABLE aep.donatii_rvc IS
  'Toate donațiile/cotizațiile/împrumuturile din rapoartele de venituri și cheltuieli (RVC) ale partidelor, fără pragul de 10 salarii. ~353K rânduri. Sursă: banipartide.ro → AEP.';

-- ──────────────────────────────────────────────────────────────────────────
-- aep.scrape_log — audit trail al scraperelor (per tabel × per zi)
-- ──────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS aep.scrape_log (
  id           bigserial PRIMARY KEY,
  scraper      text NOT NULL,                    -- 'donatii_pj' | 'donatii_pf' | 'donatii_rvc'
  source_url   text NOT NULL,
  rows_seen    integer NOT NULL,
  rows_inserted integer NOT NULL,
  rows_updated integer NOT NULL,
  rows_skipped integer NOT NULL,
  duration_ms  integer NOT NULL,
  started_at   timestamptz NOT NULL,
  finished_at  timestamptz NOT NULL DEFAULT now(),
  error        text
);

CREATE INDEX IF NOT EXISTS idx_aep_scrape_log_scraper ON aep.scrape_log(scraper, started_at DESC);

-- ──────────────────────────────────────────────────────────────────────────
-- Materialized view: agregare per CUI pentru profile firmă rapid.
-- (refresh-ed by cron post-scrape; see refresh-mvs.sh)
-- ──────────────────────────────────────────────────────────────────────────
CREATE MATERIALIZED VIEW IF NOT EXISTS aep.mv_donatii_per_cui AS
SELECT
  donator_cui                                    AS cui,
  COUNT(*)                                       AS nr_donatii,
  SUM(suma_lei)                                  AS total_lei,
  COUNT(DISTINCT partid_id)                      AS nr_partide,
  array_agg(DISTINCT partid_id)
    FILTER (WHERE partid_id IS NOT NULL)         AS partide,
  MIN(an)                                        AS prima_donatie_an,
  MAX(an)                                        AS ultima_donatie_an
FROM aep.donatii_pj
WHERE donator_cui IS NOT NULL
GROUP BY donator_cui;

CREATE UNIQUE INDEX IF NOT EXISTS idx_aep_mv_donatii_per_cui ON aep.mv_donatii_per_cui(cui);

COMMENT ON MATERIALIZED VIEW aep.mv_donatii_per_cui IS
  'Pre-aggregat pentru profile firmă: donații totale per CUI. Refresh după fiecare scrape.';

-- ──────────────────────────────────────────────────────────────────────────
-- Materialized view: top donatori per partid (folosit pe pagini publice)
-- ──────────────────────────────────────────────────────────────────────────
CREATE MATERIALIZED VIEW IF NOT EXISTS aep.mv_top_donatori_partid AS
SELECT
  partid_id,
  donator_nume,
  donator_cui,
  COUNT(*)                                       AS nr_donatii,
  SUM(suma_lei)                                  AS total_lei,
  MIN(an)                                        AS prima_donatie_an,
  MAX(an)                                        AS ultima_donatie_an
FROM aep.donatii_pj
WHERE partid_id IS NOT NULL
GROUP BY partid_id, donator_nume, donator_cui;

CREATE INDEX IF NOT EXISTS idx_aep_mv_top_donatori_partid_partid ON aep.mv_top_donatori_partid(partid_id, total_lei DESC);
CREATE INDEX IF NOT EXISTS idx_aep_mv_top_donatori_partid_cui ON aep.mv_top_donatori_partid(donator_cui) WHERE donator_cui IS NOT NULL;

COMMENT ON MATERIALIZED VIEW aep.mv_top_donatori_partid IS
  'Top donatori per partid pentru afișare publică. Datele sunt deja publice prin lege (MO).';
