a6c03a091e
Moved from gov-agreg/src/pages/achizitii/* to root (drop prefix). - 22 pages migrated, 127 files total - All internal links: /achizitii/X → /X (176 occurrences fixed) - AchizitiiLayout subnav rewritten: /X paths, top-right link to vreaudigital.ro hub - BaseLayout new (vreau.digital branding, OG tags, site URL) - astro.config.mjs: site https://vreau.digital, server output (was static) - docker-compose: port 5096 (vreaudigital is 5095), container vreau-digital - deploy.sh: paths /opt/vreau-digital, log /var/log/vreau-digital-deploy.log Backend shared with gov-agreg: - PostgreSQL satra (same schemas: seap, firms, anaf, anre, ...) - Photon, Martin tiles - Infisical /vreaudigital path (DATABASE_URL etc. shared) build: PASS (npx astro check 0 errors, npm run build 5s vite + 10s server)
204 lines
13 KiB
SQL
204 lines
13 KiB
SQL
-- 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).';
|