Files
Claude VM a6c03a091e 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)
2026-05-13 00:10:32 +03:00

200 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AEP Donații — Plan de Ingest pentru vreaudigital.ro
**Status:** scaffold complet, smoke test reușit (99 rows în 0.9s, 11/62 donatori PJ au și contracte SEAP — match instant pentru recipe-ul "money-to-power").
**Last update:** 2026-05-09
**Author:** Phase 5 AEP agent
## 1. Sursa și de ce nu direct AEP
Legea 334/2006 mandatează AEP (Autoritatea Electorală Permanentă) să publice:
1. **Donațiile peste 10 salarii minime brute** — în Monitorul Oficial, anual, per partid.
2. **Rapoartele de venituri și cheltuieli (RVC)** — anual, per partid + filiale.
3. **Subvențiile de la stat** — lunar, per partid (75% senate + 25% local).
Surse oficiale candidate:
| Sursa | Format | Acces | Pro | Contra |
| ---------------------------------- | ----------------- | ----------------------------- | ---------------------------- | ----------------------------------------------------------------------- |
| `roaep.ro/finantare/` | HTML + PDF | reCAPTCHA pe nivel rădăcină | Sursa primară mandatată | Bot detection blochează WebFetch & curl simplu; PDF parsing dureros |
| `finantarepartide.ro` | Portal AEP | reCAPTCHA | Date oficiale | Idem reCAPTCHA + structură variabilă per an |
| **`banipartide.ro` / Expert Forum** | **SQLite expus prin endpoint base64-SQL** | **HTTP simplu, fără protecție** | **Date deja agregate, normalizate, cu CUI** | Proiect terț; cuprinde aceleași date publice prin lege |
| `data.gov.ro` (CSV-uri DataGov) | CSV neagregat | HTTP simplu | Oficial | Lipsesc anii vechi; mapping per partid manual; nu acoperă RVC granular |
**Decizie:** ingest-ăm **întâi din banipartide.ro** (path de minim efort, calitate maximă), apoi cross-validăm cu AEP RVC PDFs ca v2 (citație în UI: "sursa primară: AEP — agregare via Expert Forum").
### Endpoint-ul banipartide.ro
```
GET https://www.banipartide.ro/app/json.php?mode=dt&ssid=<base64(SQL)>
→ { "data": [ [col1, col2, ...], ...], "distinctData": {} }
```
Backendul e SQLite (verificat cu `SELECT name FROM sqlite_master WHERE type="table"`). 18 tabele, dintre care relevante:
| Tabelă SQLite | Rows | → Tabela noastră |
| -------------------------------------------------------- | ---------- | -------------------------- |
| `Donatori persoane juridice` | **3,612** | `aep.donatii_pj` |
| `Donatori persoane fizice` | **30,792** | `aep.donatii_pf` |
| `Donatori rapoarte de venituri și cheltuieli` | **353,473**| `aep.donatii_rvc` |
| `Subvenții pe an`, `Cheltuieli subvenții`, ... | varies | (faza 2 — vezi §6) |
**Riscul de schimbare:** EFOR poate scoate offline endpoint-ul. **Mitigare:** într-o iterație v2, scrape-ăm direct PDF-urile AEP cu Playwright headless (rezolvă reCAPTCHA-ul) și cross-validăm cu banipartide.
## 2. Schema DB — `aep.*`
Migrație: `services/seap-scraper/sql/024_aep_donatii.sql`**APLICATĂ.** 5 tabele + 2 MVs.
```
aep.partide (id PK, nume_oficial, fondat, sediu_cui, status)
aep.donatii_pj (source_hash UNIQUE, donator_nume, donator_cui, partid_id, suma_lei, an, ...)
aep.donatii_pf (source_hash UNIQUE, donator_nume, donator_cnp_sha256, partid_id, suma_lei, an, ...)
aep.donatii_rvc (source_hash UNIQUE, donator_nume, judet, tip_venit, partid_id, suma_lei, an, ...)
aep.scrape_log (audit per scrape run)
aep.mv_donatii_per_cui MV → folosit pe pagina firmă
aep.mv_top_donatori_partid MV → folosit pe pagina partid
```
### Decizii de design
- **`source_hash`** (sha1 al cheilor naturale) ca UNIQUE constraint → ON CONFLICT DO UPDATE: scraperul e 100% idempotent, poate rula zilnic fără duplicare.
- **`donator_cui_raw`** păstrat lângă `donator_cui` normalizat — sursa are typos / "RO" prefix / stringuri non-numerice; `cui_matcher` (deja în `firms`) poate ajuta la rezoluție fuzzy în faza 2.
- **CNP-urile sunt SHA-256 hashed la ingest.** Niciodată stocate raw în DB. Numele rămâne pentru că e public prin lege (publicat în MO).
- **Partidele sunt auto-create** la prima donație observată — registru natural, no manual seeding required.
- **Date parsing** — best effort. Sursa are format haotic (`"11.10.2019; 13.11.2019"`, `10042010`, `4102019`, `9/7/20`). În tested smoke (99 rows): **94% parsate**, 6% null pe multi-date strings (intenționat — nu putem alege una).
## 3. Scraperul
Fișier: `services/seap-scraper/src/scrape-aep-donatii.ts` (~570 linii TS).
### Comenzi
```bash
# Smoke test (100 rows)
npx tsx src/scrape-aep-donatii.ts --table=pj --limit=100
# Full ingest per tabel
npx tsx src/scrape-aep-donatii.ts --table=pj
npx tsx src/scrape-aep-donatii.ts --table=pf
npx tsx src/scrape-aep-donatii.ts --table=rvc
# Toate trei + refresh MVs
npx tsx src/scrape-aep-donatii.ts --table=all
```
Wrapper de cron: `services/seap-scraper/cron/scrape-aep-donatii.sh` — same pattern ca enrich-anaf.sh / scrape-regas.sh (Infisical MI → env-file → docker run --env-file → cleanup).
### Smoke test result
```
[aep] table=pj limit=100
[aep:pj] fetching from banipartide.ro (limit=100)...
[aep:pj] fetched 100 rows; upserting...
[aep:pj] done in 0.9s seen=100 ins=99 upd=1 skip=0
[aep] refreshing materialized views...
[aep] done.
```
99 rows în 0.9s. Single 100-row "full" fetch ar fi ~30s pentru 3.6K PJ, ~5min pentru 30K PF, ~30min pentru 353K RVC. **Ingest-ul total estimat: <40 min, single shot.**
## 4. Cross-source — primele recipe-uri descoperite din 99-row sample
Test query împotriva `seap.announcements` (642K rows existing):
```sql
SELECT d.donator_nume, d.donator_cui, d.partid_id,
SUM(d.suma_lei) AS donat_lei,
COUNT(DISTINCT s.ref_number) AS nr_contracte_seap,
SUM(s.awarded_value)::bigint AS valoare_seap_lei
FROM aep.donatii_pj d
JOIN seap.announcements s ON s.supplier_cui = d.donator_cui
GROUP BY d.donator_nume, d.donator_cui, d.partid_id
ORDER BY nr_contracte_seap DESC;
```
Rezultate (extras din primele 99 rânduri ingest):
| Donator | CUI | Partid | Donat (lei) | Contracte SEAP | Valoare SEAP (lei) |
| ------------------------------- | --------- | ------- | ----------: | -------------: | -----------------: |
| **ORANGE ROMANIA - S.A.** | 9010105 | UDMR | 1,555,403 | **829** | **305,284,218** |
| IGO S.A. | 7186084 | PDL | 65,000 | 13 | 337,118 |
| ROMEC SRL | 2075123 | PDL | 1,500 | 10 | 6,843 |
| SC Mokatti Exim SRL | 4660530 | UDMR | 1,800 | 9 | 36,101 |
| S.C. COMISION TRADE - S.R.L. | 5443785 | PNL | 270,000 | 9 | 88,002 |
| VALENTINO PRODEX | 4813200 | PDL | 15,000 | 5 | 2,547,082 |
| S.C. Iridex Group Import Export | 398284 | PSD+PC | 48,100 | 1 | 69,853 |
**62 donatori cu CUI, 51 match în firms.entities (82%), 11 cu contracte SEAP** — toate astea din primele 99 rânduri. Full ingest = >>multe astfel de match-uri.
## 5. Entity resolution pe `donator_cui`
Sursa banipartide are CUI-ul ca text — uneori cu typos, "RO" prefix, sau gol. **Plan de resolution în faza 2:**
1. **CUI direct**`firms.entities.cui` (text = text). Acoperă deja ~80%.
2. **CUI fuzzy** → folosim `firms.cui_matcher_index` (deja existent — vezi 019_cui_matcher.sql) pentru match pe nume + sediu când CUI lipsește.
3. **Pentru donații PF (persoane fizice)** — fără CUI. Match-ul cu ANI declarații de avere (când 030_ani_* aterizează) se face pe `nume_normalized`. Cross-recipe: "demnitarii care au donat partidului lor".
## 6. Roadmap — ce urmează (NU în această sesiune)
### Faza 5b — full ingest + MV-uri pe RVC (1 sesiune)
- Run `--table=all` (estimat 40 min total)
- Add MV-uri și pe `donatii_pf` și `donatii_rvc`
- Add MV cross-source `aep.mv_donator_seap_overlap` (donator + total donat + total câștigat SEAP, sortat după ratio)
### Faza 5c — pagini publice pe vreaudigital.ro (1 sesiune)
- `/finantare-partide` — landing cu top 20 donatori per partid, top partide după volum, evoluție temporală
- `/finantare-partide/[partid]` — toate donațiile per partid, filtrabile per an, donator type, sumă
- `/finantare-partide/donator/[cui]` — istoricul de donații per donator + cross-link cu profile-ul firmei (`/firma/[cui]`)
- Adăugare badge pe `/firma/[cui]` și `/achizitii/firma/[cui]` — "🪙 Donator politic — X RON către Y partide" (folosind `aep.mv_donatii_per_cui`)
### Faza 5d — recipes (în `lib/recipes.ts`, după ce Phase 3 RegAS termină)
- **`donatori-care-au-castigat-seap`** — JOIN pe `aep.donatii_pj × seap.announcements`. Sortabil după (suma_donata, valoare_seap, ratio). Coloane: donator, partid, suma donat, contracte câștigate, autoritate care a contractat.
- **`concentrare-donatii-per-partid`** — top donatori per partid, % din total donații pentru partid, evoluție temporală.
- **`donator-stat-revolving`** — `aep.donatii_pj × firms.entities` (filtru pe `forma_juridica IN ('SA stat', 'CN', 'RA')`) — companii de stat care au donat. (Ilegal după 2006, dar verificare empirică.)
- **`demnitar-donator-propriul-partid`** — când 030_ani_declaratii aterizează, JOIN `aep.donatii_pf.donator_nume` cu `ani.declaratii.nume_complet`.
### Faza 5e — validare cu sursa primară (opțional, 1 sesiune)
- Scraper Playwright pentru `roaep.ro/finantare/` (rezolvă reCAPTCHA cu un click manual la prima rulare, cookie-uri salvate)
- Download PDF-urile MO oficiale per partid per an
- Parse cu `pdfplumber` (Python sidecar, deja avem `import_*.py`)
- Compară cu `aep.donatii_pj` — log diff → tabela `aep.validation_diffs`
- Adaugă `verified_by_aep_pdf` boolean în `aep.donatii_pj`
### Faza 5f — date suplimentare din banipartide
- `Subvenții pe an``aep.subventii` (banii de la stat per partid per an, 2008+)
- `Contracte subventii``aep.contracte_subventii` (cum cheltuie partidele subvențiile)
- `Contributii campanie` + `Venituri și cheltuieli campanie``aep.campanie_*` (date electorale specifice, finanțare campanii)
- `Rezultate alegeri` × `Subvenții pe an` → recipe "subvenția per vot" (paritatea democratică)
## 7. Observații GDPR & legal
- **Date publicate prin Legea 334/2006**, art. 13 (donații PJ) și art. 14 (donații PF >10 salarii) — explicit publicate în Monitorul Oficial. **GDPR-safe** prin baza legală art. 6(1)(c) GDPR (obligație legală de publicare).
- **CNP-urile** apar în sursă în clear (în RVC publicat de partide). Le hash-uim SHA-256 — nu publicăm CNP-uri raw pe vreaudigital.ro. Numele complet rămâne (e public prin lege).
- **Adresa sediului PJ** e publică (din MO + ONRC). Pentru PF, sursa NU are adresă, doar nume + organizație partid.
- **Right to be forgotten:** dacă cineva contestă, păstrăm un endpoint `/aep/redact` care setează `donator_nume = '(redactat la cerere)'` și `donator_cnp_sha256 = NULL` cu audit log. Sumele/an/partid rămân (interes public).
## 8. Operare
**Cron (sugerat):** lunar, prima zi a lunii, 03:00. Date la AEP se publică anual la 30 aprilie pentru anul precedent (RVC) + ad-hoc în MO. Update lunar e suficient — nu e dataset live.
```cron
0 3 1 * * /opt/vreaudigital/services/seap-scraper/cron/scrape-aep-donatii.sh >> /var/log/vreaudigital-aep.log 2>&1
```
**Volumul stocat (estimare full):**
- `aep.donatii_pj`: 3,612 rows × ~1KB = ~4MB
- `aep.donatii_pf`: 30,792 × ~500B = ~15MB
- `aep.donatii_rvc`: 353,473 × ~400B = ~140MB
- Total: **~160MB cu indexuri**, neglijabil față de seap (~3GB) sau firms (~1GB).
## 9. Files touched
```
services/seap-scraper/sql/024_aep_donatii.sql (NEW, applied to satra)
services/seap-scraper/src/scrape-aep-donatii.ts (NEW, ~570 LOC, smoke-tested)
services/seap-scraper/cron/scrape-aep-donatii.sh (NEW, executable, cron pattern)
services/seap-scraper/AEP-PLAN.md (NEW, this file)
```
Zero edituri în `src/lib/`, `src/pages/`, `src/components/` (per regulile de exclusion-zone — Phase 3/4 agents own those).