initial: split from gov-agreg — vreau.digital standalone platform

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)
This commit is contained in:
Claude VM
2026-05-13 00:10:32 +03:00
commit a6c03a091e
352 changed files with 75295 additions and 0 deletions
@@ -0,0 +1,203 @@
-- 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).';