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)
625 lines
36 KiB
Markdown
625 lines
36 KiB
Markdown
# Refresh cadence master strategy — gov-agreg / vreaudigital.ro
|
||
**Data:** 2026-05-11
|
||
**Sub-agent:** S1 (refresh cadence master strategy)
|
||
**Bază date:** `architools_db` @ 10.10.10.166 — 29 GB
|
||
**Cuprinde:** 17 schemas, 2 sub-pipeline-uri (ANAF v9 + ANAF datornici), strategie captcha, monitorizare, idempotență, DR
|
||
**Audit-ul de prospețime anterior:** `chatGPT/data-quality/freshness-audit-2026-05-10.md`
|
||
|
||
---
|
||
|
||
## 0. Context & constrângeri
|
||
|
||
| Constrângere | Stare actuală |
|
||
|---|---|
|
||
| Host orchestrare | `satra` (10.10.10.166), Docker, Ubuntu, **disc la 85% (299/371 GB)** ⚠️ |
|
||
| Sistem de scheduling | systemd timers (3 active) + ad-hoc shell wrappers; **nu există crontab agregat pentru toți 13 scraperi** |
|
||
| Secrete | Infisical Machine Identity (`/opt/vreaudigital/.infisical-mi`) — refresh per wrapper |
|
||
| Anti-pattern interzis | `docker run -e $DATABASE_URL` (leakă via `ps`); folosim `--env-file` 600 + delete |
|
||
| Run-as | `bulibasa` (systemd), `root` (cron actual eterra/backup) |
|
||
| Captcha sources | ANAF datornici live, Bugetar Faza 2, ANI e-DAI 2022+ (Cloudflare Turnstile) |
|
||
| Buget | Mic — 2captcha (~$1/1000), playwright headless OK, headed pe Orchi doar la nevoie |
|
||
|
||
**Stat actual systemd (verificat azi):**
|
||
- `vreaudigital-anaf-daily.timer` → 02:00 zilnic, enrich-anaf.sh tier=daily, concurrency=2
|
||
- `vreaudigital-onrc-weekly.timer` → marți 03:00, import-onrc-fresh.sh
|
||
- `vreaudigital-mvs.timer` → 04:00 zilnic, refresh-mvs.sh (9 MV-uri seap)
|
||
|
||
**13 wrappers existente NE-programate prin systemd** (rulează doar manual sau via cron neagregat încă):
|
||
`scrape-aaas`, `scrape-aep-donatii`, `scrape-anaf-datornici`, `scrape-ancom`, `scrape-anre`, `scrape-asf`, `scrape-bugetar`, `scrape-cnas`, `scrape-cnsc`, `scrape-curteacont`, `scrape-gnm`, `scrape-regas`, `import-afir-historical`, `import-apia-fermieri`, `import-financials*`.
|
||
|
||
Audit-ul `scrape_log` confirmă totuși că **toți cei 9 scraperi cu schema dedicată au rulat în ultimele 24h** — deci există un cron ascuns (probabil în `bulibasa` user crontab, nu în `sudo crontab`). Strategia de mai jos **înlocuiește cron-ul ascuns cu un /etc/cron.d/ vizibil + systemd timers per scraper**.
|
||
|
||
---
|
||
|
||
## 1. Per-schema cadence table
|
||
|
||
Coloane: Schema · Sursă (ritm publicare) · Cadență recomandată · Wrapper · Runtime · Risc · Monitor signal (max age tolerat)
|
||
|
||
| # | Schema | Sursă upstream — ritm | Cadență recomandată | Wrapper | Runtime | Risc | Monitor signal (max age) |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 1 | **seap.announcements** (WSP) | live | la 4h | `scrape-seap-wsp` (lipsește wrapper!) | 5-15 min | F5 WAF, ASP session | `wsp_sync_state.last_run_at` ≤ 6h |
|
||
| 2 | **seap.direct_acquisitions** | live | la 6h | `scrape-seap-da` (lipsește wrapper!) | 10-30 min | session expiry, retry storms | `sync_state[source=da].updated_at` ≤ 8h |
|
||
| 3 | **seap.entities + cui_location** | după WSP/DA refresh | seara, după daily | inclus în WSP wrapper | (incl.) | n/a | `entities.fetched_at` ≤ 24h |
|
||
| 4 | **anaf** (v9 enrichment — daily delta) | live API | zilnic 02:00 | `enrich-anaf.sh` TIER=daily | 1-2h | rate limit ANAF 503 | `firms.entities WHERE anaf_fetched_at > now-2d` count ≥ 1000 |
|
||
| 5 | **anaf.datornici** (data.gov.ro Q) | quarterly | trim 15-ian/15-apr/15-iul/15-oct | `scrape-anaf-datornici` SOURCE=datagov<Q> | 30-60 min | NEW — necesită captcha doar pt live | `anaf.datornici WHERE publication_date > now-180d` ≥ 1 |
|
||
| 6 | **anaf.datornici** (anaf.ro live) | live, captcha | trim — **opțional dacă plătim 2captcha** | `scrape-anaf-datornici` SOURCE=live | 2-4h | reCAPTCHA v2 | (decis în §3) |
|
||
| 7 | **firms.entities** (ONRC weekly) | săptămânal | marți 03:00 | `import-onrc-fresh.sh` | 30-60 min | bulk diff fail | `firms.entities.updated_at` ≤ 8 zile |
|
||
| 8 | **firms.financials** (ANAF bilanțuri) | anual (15-iul publicare an N-1) | 15 iul + 15 aug rerun | `import-financials.sh` | 2-4h | mărime CSV ~3GB | `firms.financials WHERE source_year = year(now)-1` ≥ 800k |
|
||
| 9 | **firms.financials_ong / banks** | anual | 20-iul | `import-financials-ong-banks.sh` | 1h | n/a | acelaşi |
|
||
| 10 | **fonduri.afir_plati** | anual data.gov.ro | 15-feb (date an N-1) | `import-afir-historical.sh` | 2-4h | CSV mare | `fonduri.afir_plati WHERE source_year = year(now)-1` ≥ 1M |
|
||
| 11 | **fonduri.beneficiar_anunt / proiect** (FEADR + FEGA) | live data.gov.ro | săptămânal lun 02:00 | `import-fonduri-beneficiari` (lipsește!) | 15-30 min | n/a | `fonduri.beneficiar_anunt.fetched_at` ≤ 8d |
|
||
| 12 | **regas.ajutoare** (Consiliul Concurenței) | lunar | luna 1 ale lunii 02:00 | `scrape-regas` | 10-15 min | n/a | `regas.ajutoare.fetched_at` ≤ 35d |
|
||
| 13 | **bugetar.entitate** (mfinante public registry) | lunar | luna 1 ale lunii 03:00 | `scrape-bugetar` | 30-60 min | n/a | `bugetar.entitate.fetched_at` ≤ 35d |
|
||
| 14 | **bugetar.executie** (Faza 2 — captcha) | lunar (raportare 30 zile decalaj) | **deferred** — vezi §3 | `scrape-bugetar-executie` (lipsește) | 4-8h pt 1000 entități | captcha + 1000 detail pages | (deferred) |
|
||
| 15 | **anre.licente** (3 surse: atestat/electricitate/gaze) | live | zilnic 03:00 | `scrape-anre` SOURCE=all | 3-5 min | TLS cert intermediary | `anre.licente.fetched_at` ≤ 36h |
|
||
| 16 | **anre.electricieni** | live (~100k entries) | săptămânal duminică 04:00 | `scrape-anre` SOURCE=electricieni | 30-60 min | pagination volume | `anre.electricieni.fetched_at` ≤ 8d *(when implemented)* |
|
||
| 17 | **ancom.operatori + drepturi** | live registry | zilnic 04:00 | `scrape-ancom` | 1-2 min | n/a | `ancom.operatori.fetched_at` ≤ 36h |
|
||
| 18 | **asf.entitati** | live (rebuild nightly) | zilnic 05:00 | `scrape-asf` | 2-3 min | "omit g-recaptcha" trick must hold | `asf.entitati.fetched_at` ≤ 36h |
|
||
| 19 | **cnsc.decizii** (listing) | live | zilnic 02:30 | `scrape-cnsc` MAX_PAGES=10 (incremental) | 2-5 min | session-based | `cnsc.decizii.fetched_at` ≤ 36h |
|
||
| 20 | **cnsc Stage 2** (PDF parse → decision_type) | după listing | săptămânal sâmbătă 02:00 | `cnsc-parse-pdfs` (lipsește) | 4-8h pt 30k | I/O storage PDFs | % decizii `WHERE decision_type IS NOT NULL` ≥ 90% |
|
||
| 21 | **cnas.documents** | lunar pe WP media | săptămânal lun 04:00 | `scrape-cnas` | 5-10 min | format CNAS schimbabil | `cnas.documents.fetched_at` ≤ 8d |
|
||
| 22 | **cnas.furnizori** (parse din PDF) | inclus în .documents | săptămânal | (incl.) | (incl.) | parser failure 25% | % docs `parse_status='ok'` ≥ 75% |
|
||
| 23 | **aaas.firme** | live portal | săptămânal lun 04:30 | `scrape-aaas` | 30s | listă mică (11 firme) | `aaas.firme.fetched_at` ≤ 8d |
|
||
| 24 | **curteacont.rapoarte** (Stage 1 listing) | live săptămânal | zilnic 05:30 | `scrape-curteacont` | 1-3 min | n/a | `curteacont.rapoarte.fetched_at` ≤ 36h |
|
||
| 25 | **curteacont Stage 2** (detail + PDF + audited CUI) | după Stage 1 | săptămânal duminică 03:00 | `curteacont-detail` (lipsește) | 4-6h pt 1133 | n/a | % rapoarte `WHERE audited_entity_cui IS NOT NULL` ≥ 50% |
|
||
| 26 | **aep.donatii_pf/pj/rvc + partide** | trimestrial (raportări) | trim 15-ian/15-apr/15-iul/15-oct + lunar smoke check | `scrape-aep-donatii` | 1h | banipartide.ro mortality | `aep.donatii_pj.fetched_at` ≤ 95d |
|
||
| 27 | **ani.declaratii** (PDFs) | live ANI dar **parser ne-implementat** | **deferred** | n/a | n/a | Cloudflare Turnstile | (deferred — multi-week) |
|
||
| 28 | **apia.fermieri** (CKAN data.gov.ro) | anual (campania an N publicată 1-mar an N+1) | 15-mar + lunar smoke | `import-apia-fermieri` | 5-10 min | volum mic actual (191 rows — needs investigation) | `apia.fermieri.fetched_at` ≤ 35d |
|
||
| 29 | **gnm.comunicate** (RSS) | săptămânal | zilnic 06:00 | `scrape-gnm` | 1-2 min | RSS format change | `gnm.comunicate.fetched_at` ≤ 36h ŞI `publicat_la_max > now-30d` |
|
||
| 30 | **gnm.amenzi_extrase** (Stage B fuzzy) | după Stage A | săptămânal duminică 05:00 | `gnm-extract-amenzi` (post-A2) | 30 min | NLP false positives | % comunicate flagged enforcement cu amendă extrasă ≥ 50% |
|
||
| 31 | **seap MV refresh** (9 materialized views) | după toate SEAP scrape | zilnic 06:00 (după WSP+DA) | `refresh-mvs.sh` | 5-15 min | dependență de WSP/DA | `mv_authority_concentration` ultim refresh ≤ 26h |
|
||
|
||
**Note critice:**
|
||
- **Wrappere lipsă:** `scrape-seap-wsp`, `scrape-seap-da`, `import-fonduri-beneficiari`, `scrape-bugetar-executie`, `cnsc-parse-pdfs`, `curteacont-detail`, `gnm-extract-amenzi`. Scraperele TypeScript există în `src/`, dar nu au wrapper `cron/scrape-*.sh` cu pattern Infisical MI → env-file → docker run. **Aceasta este lacuna #1 înainte de oricărei programări noi.**
|
||
- ANRE rulează deja zilnic via cron ascuns dar nu via systemd vizibil — strategia mută totul în systemd timers per scraper, ca **mvs.timer** azi.
|
||
|
||
---
|
||
|
||
## 2. Cron schedule recommendation
|
||
|
||
Două opțiuni implementabile:
|
||
- **(A) /etc/cron.d/govagreg-refresh** — un singur fișier vizibil, ușor de auditat.
|
||
- **(B) systemd timers per scraper** — match-uiește patternul existent (`vreaudigital-*.timer`), permite `journalctl -u`, status uniform.
|
||
|
||
**Recomandare: B (systemd timers)**, pentru că:
|
||
1. Patternul există deja (3 timere), iar `journalctl` e mai util decât `/var/log/cron`.
|
||
2. Per-unit `OnFailure=` permite alerting nativ.
|
||
3. `Persistent=true` reia rulările pierdute după reboot (cron-ul de pe satra nu are anacron).
|
||
4. `RandomizedDelaySec=` evită contenția în vârful 02:00-06:00.
|
||
|
||
### 2.1 Timer skeleton (canonical pattern)
|
||
|
||
Un template pentru fiecare scraper:
|
||
|
||
```ini
|
||
# /etc/systemd/system/vreaudigital-<scraper>.service
|
||
[Unit]
|
||
Description=vreaudigital — <scraper> refresh
|
||
Wants=network.target docker.service
|
||
After=network.target docker.service vreaudigital-prerequisites.service
|
||
|
||
[Service]
|
||
Type=oneshot
|
||
User=bulibasa
|
||
ExecStart=/opt/vreaudigital/services/seap-scraper/cron/scrape-<scraper>.sh
|
||
StandardOutput=journal
|
||
StandardError=journal
|
||
TimeoutStartSec=4h
|
||
OnFailure=vreaudigital-alert@%n.service
|
||
|
||
# /etc/systemd/system/vreaudigital-<scraper>.timer
|
||
[Unit]
|
||
Description=vreaudigital — <scraper> at <time>
|
||
|
||
[Timer]
|
||
OnCalendar=<schedule>
|
||
Persistent=true
|
||
RandomizedDelaySec=600
|
||
|
||
[Install]
|
||
WantedBy=timers.target
|
||
```
|
||
|
||
### 2.2 Recommended schedule (eșalonat pentru a evita contenție pe satra)
|
||
|
||
```
|
||
# === LIVE / NEAR-REAL-TIME (multiple ori pe zi) ===
|
||
vreaudigital-seap-wsp.timer OnCalendar=*-*-* 00,04,08,12,16,20:15:00 # 6× zi
|
||
vreaudigital-seap-da.timer OnCalendar=*-*-* 02,10,18:30:00 # 3× zi (mai greu)
|
||
|
||
# === DAILY off-peak (02:00-06:00, eșalonat la 5-15 min) ===
|
||
vreaudigital-anaf-daily.timer OnCalendar=*-*-* 02:00:00 # exista (enrich v9 daily)
|
||
vreaudigital-cnsc.timer OnCalendar=*-*-* 02:30:00
|
||
vreaudigital-anre.timer OnCalendar=*-*-* 03:00:00
|
||
vreaudigital-curteacont.timer OnCalendar=*-*-* 03:30:00
|
||
vreaudigital-ancom.timer OnCalendar=*-*-* 04:00:00
|
||
vreaudigital-asf.timer OnCalendar=*-*-* 04:30:00
|
||
vreaudigital-gnm.timer OnCalendar=*-*-* 05:00:00
|
||
vreaudigital-mvs.timer OnCalendar=*-*-* 06:00:00 # exista; mut de la 04:00 la 06:00 ca să fie după toate scraperele
|
||
|
||
# === WEEKLY (luni 04:00-06:00, sâmbătă/duminică pentru heavy) ===
|
||
vreaudigital-cnas.timer OnCalendar=Mon *-*-* 04:00:00
|
||
vreaudigital-aaas.timer OnCalendar=Mon *-*-* 04:30:00
|
||
vreaudigital-onrc-weekly.timer OnCalendar=Tue *-*-* 03:00:00 # exista
|
||
vreaudigital-fonduri-week.timer OnCalendar=Mon *-*-* 02:00:00
|
||
vreaudigital-anre-electricieni.timer OnCalendar=Sun *-*-* 04:00:00
|
||
vreaudigital-cnsc-pdfs.timer OnCalendar=Sat *-*-* 02:00:00 # Stage 2 heavy
|
||
vreaudigital-curteacont-detail.timer OnCalendar=Sun *-*-* 03:00:00 # Stage 2
|
||
vreaudigital-gnm-amenzi.timer OnCalendar=Sun *-*-* 05:00:00 # Stage B
|
||
|
||
# === MONTHLY (1 ale lunii, 02:00-04:00) ===
|
||
vreaudigital-regas.timer OnCalendar=*-*-01 02:00:00
|
||
vreaudigital-bugetar.timer OnCalendar=*-*-01 03:00:00
|
||
vreaudigital-apia.timer OnCalendar=*-*-01 04:00:00 # smoke check, full pe martie
|
||
|
||
# === QUARTERLY (15 ale luni 1/4/7/10) ===
|
||
vreaudigital-anaf-datornici.timer OnCalendar=*-01,04,07,10-15 02:00:00
|
||
vreaudigital-aep-donatii.timer OnCalendar=*-01,04,07,10-15 03:00:00
|
||
|
||
# === ANNUAL ===
|
||
vreaudigital-afir-historical.timer OnCalendar=*-02-15 02:00:00
|
||
vreaudigital-financials.timer OnCalendar=*-07-15 02:00:00
|
||
vreaudigital-financials-ong.timer OnCalendar=*-07-20 02:00:00
|
||
vreaudigital-apia-full.timer OnCalendar=*-03-15 02:00:00
|
||
|
||
# === DEAD-MAN'S SWITCH (vezi §4) ===
|
||
vreaudigital-heartbeat.timer OnCalendar=*-*-* 07:00:00 # alert dacă lipsesc date proaspete
|
||
```
|
||
|
||
**Total estimat încărcare:** ~35 min CPU/zi steady-state daily slot, ~2-4h/sâmbătă-duminică (heavy stages), ~6-10h în 15 ale lunilor Q (datornici + AEP), ~8h în 15-iul (financials annual).
|
||
|
||
### 2.3 Resource contention checklist
|
||
|
||
- **02:00-04:00 daily:** anaf (1-2h) + cnsc (2-5 min) + anre (3-5 min) + curteacont (1-3 min). ANAF rulează long, restul tick-uri scurte cu RandomizedDelaySec=300-600 evită overlap.
|
||
- **04:00-06:00 daily:** ancom + asf + gnm + mvs. Toate sub 15 min total. mvs (5-15 min) e ultimul.
|
||
- **Luni 02:00-05:00:** fonduri + cnas + aaas + apia smoke. ONRC pe MARȚI ca să nu se ciocnească cu nimic.
|
||
- **Weekend:** cnsc-pdfs + curteacont-detail + anre-electricieni + gnm-amenzi. Heavy lifts, niciun overlap.
|
||
- **Disc:** la 85% pe satra ⚠️. **Înainte de orice scraper PDF nou (cnsc-pdfs, curteacont-detail) — rezolvă §6 disc**.
|
||
|
||
---
|
||
|
||
## 3. CAPTCHA-blocked sources strategy
|
||
|
||
### 3.1 ANAF datornici live (anaf.ro/restante)
|
||
|
||
**Stare:** Singura sursă publică bulk (data.gov.ro Q1 2016) e statică. Pentru a actualiza 2016-Q2 → 2026-Q1 (38 trimestre) trebuie scrape live cu captcha.
|
||
|
||
**Two paths:**
|
||
|
||
| Path | Cost | Timp implementare | Acoperire |
|
||
|---|---|---|---|
|
||
| **(a) data.gov.ro Q-snapshots** | $0 | 2 zile (sursa trebuie verificată dacă publică Q-uri noi) | depinde de mfinante |
|
||
| **(b) 2captcha pe anaf.ro/restante live** | $1-3/1000 captcha | 1 săptămână + Playwright | toate Q-urile, on-demand |
|
||
|
||
**Recomandare:** path (a) prima — verifică data.gov.ro listing pentru dataset-uri `anaf-datornici-202X`. Dacă publică, scraperul existent (`SOURCE=datagovYYYY-QN`) deja gestionează. Path (b) doar dacă data.gov.ro nu publică sau e cu lag mare.
|
||
|
||
**Buget 2captcha pentru path (b) — backfill 5 ani × 4 Q × 1 captcha per fetch = 20 captchas total** (un Q = un download pe full set, nu per-entitate). **Buget: ~$0.10/an** (neglijabil). Costul real: timpul dev pentru integrare Playwright + 2captcha SDK = 2-3 zile.
|
||
|
||
**Pre-req decision:**
|
||
```
|
||
DACĂ Q4-2025 publicat pe data.gov.ro
|
||
ATUNCI nu plătim 2captcha — extindem `scrape-anaf-datornici.sh` cu SOURCE=datagov2025Q4
|
||
ALTFEL plătim 2captcha (~$0.10/an) ȘI investim 2-3 zile dev
|
||
```
|
||
|
||
### 3.2 Bugetar Faza 2 — execuție bugetară per entitate
|
||
|
||
**Stare:** `bugetar.entitate` = 18,822 entități; `bugetar.executie` = **0 rows**.
|
||
|
||
**Captcha analiza:** mfinante.gov.ro/static/10/Mfp/sit-Trezor/situatie_trezorerie.html — pagina detail per entitate cere captcha (Google reCAPTCHA v2). Per fetch = 1 captcha.
|
||
|
||
**Strategie scope:**
|
||
- Total entități × 60 luni = 18,822 × 60 = **1,129,320 fetches** dacă acoperim TOATE entitățile × tot istoricul (5 ani).
|
||
- Cu 2captcha la $1/1000: **$1,129/total**, ~$226/an pentru 5 ani amortizat.
|
||
- Reducem la **top-1000 entități după buget** × 60 luni = **60,000 fetches = $60 total**, ~$12/an. ← **RECOMANDARE**.
|
||
|
||
**Buget total bugetar Faza 2: $60-100 one-shot pentru top-1000 entități**. Refresh lunar incremental: 1000 × 1 lună = 1000 fetch/lună = $1/lună.
|
||
|
||
### 3.3 ANI new e-DAI 2022+ (Cloudflare Turnstile)
|
||
|
||
**Stare:** ANI mută `e-DAI` pe noua platformă (post-2022) protejat cu Cloudflare Turnstile (nu reCAPTCHA). 2captcha **suportă Turnstile** ($3/1000) dar e mai puțin fiabil; Playwright **headed** (cu browser real) e fallback.
|
||
|
||
**Volum:** ~1.3M PDFs (CLAUDE.md). Chiar la $3/1000 = **$3,900** pentru backfill complet — depășește bugetul. Refresh incremental ~50k/an = $150/an.
|
||
|
||
**Recomandare:**
|
||
- **Faza 0:** parserul PDF nu e implementat încă. Investește 4-6 săptămâni dev în parser ÎNAINTE de a cheltui pe captcha.
|
||
- **Faza 1:** scraping curent — folosește **Playwright headed pe Orchi** (RTX A4000, neutilizat noaptea) pentru sample 10k declarații, manual solving / fingerprint rotation. **Cost direct: $0.**
|
||
- **Faza 2 (dacă scalează):** 2captcha Turnstile pentru deltas anuale ~$150/an.
|
||
|
||
### 3.4 ASF "omit g-recaptcha-response" trick
|
||
|
||
ASF nu necesită 2captcha — scraperul curent omite parametrul `g-recaptcha-response` din POST și serverul răspunde oricum (bug în implementarea ASF). **Risk:** ASF poate fixa oricând acest bug. Monitor: dacă `scrape-asf.sh` începe să returneze 0 rows constant, investighează.
|
||
|
||
### 3.5 Decision rubric — investim captcha sau nu?
|
||
|
||
| Sursă | 2captcha cost/an | Valoare unlock | Vot |
|
||
|---|---|---|---|
|
||
| ANAF datornici live | ~$0.10 | mediu (path-a probabil rezolvă) | **NU prioritar** — verifică path-a întâi |
|
||
| Bugetar top-1000 | ~$12 (incremental) | mare (fluxuri bani publici) | **DA** după parser execuție repaired |
|
||
| ANI e-DAI 2022+ | ~$150 | flagship | **DEFER** până la parser PDF implementat |
|
||
| Bugetar toate 18,822 | ~$226 | mare dar redundant cu top-1000 | **NU** — top-1000 e suficient |
|
||
|
||
**Buget total 2captcha pe an pentru acoperire completă recomandată:** **$15-25/an** (Bugetar top-1000 incremental + ANAF safety net + ASF backup dacă trick-ul se strică).
|
||
|
||
**Buget total 2captcha pentru backfill one-shot:** **$60-100** (Bugetar top-1000 × 5 ani istoric).
|
||
|
||
**Buget extins dacă includem ANI:** **+$150/an pentru deltas**, $3,900 backfill (deferred).
|
||
|
||
---
|
||
|
||
## 4. Monitoring & alerting
|
||
|
||
### 4.1 Dead-man's switch — heartbeat zilnic
|
||
|
||
**Concept:** o singură query rulează la 07:00 zilnic, verifică `max(fetched_at)` per tabel cheie, alertează dacă > expected_cadence × 1.5.
|
||
|
||
**Implementare:** `vreaudigital-heartbeat.service` + Brevo SMTP (deja config).
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# /opt/vreaudigital/services/seap-scraper/cron/heartbeat.sh
|
||
set -euo pipefail
|
||
LOG=/var/log/vreaudigital-heartbeat.log
|
||
|
||
source /opt/vreaudigital/.infisical-mi
|
||
# ... (fetch DATABASE_URL + SMTP creds via Infisical, same pattern as refresh-mvs.sh)
|
||
|
||
# Define expected freshness (hours)
|
||
declare -A EXPECTED=(
|
||
["seap.announcements"]="6"
|
||
["seap.direct_acquisitions"]="8"
|
||
["anre.licente"]="36"
|
||
["ancom.operatori"]="36"
|
||
["asf.entitati"]="36"
|
||
["cnsc.decizii"]="36"
|
||
["curteacont.rapoarte"]="36"
|
||
["gnm.comunicate"]="36"
|
||
["firms.entities"]="192" # 8 days (weekly cron + buffer)
|
||
["cnas.documents"]="192"
|
||
["aaas.firme"]="192"
|
||
["fonduri.beneficiar_anunt"]="192"
|
||
["regas.ajutoare"]="840" # 35 days (monthly)
|
||
["bugetar.entitate"]="840"
|
||
["apia.fermieri"]="840"
|
||
["anaf.datornici"]="4320" # 180 days (quarterly)
|
||
["aep.donatii_pj"]="2280" # 95 days
|
||
)
|
||
|
||
ALERTS=()
|
||
for table in "${!EXPECTED[@]}"; do
|
||
schema="${table%.*}"
|
||
tbl="${table#*.}"
|
||
max_age=$(psql -tA -c "SELECT EXTRACT(EPOCH FROM (now() - max(fetched_at)))/3600 FROM ${table}")
|
||
threshold="${EXPECTED[$table]}"
|
||
if (( $(echo "$max_age > $threshold * 1.5" | bc -l) )); then
|
||
ALERTS+=("$table: ${max_age}h stale (threshold ${threshold}h)")
|
||
fi
|
||
done
|
||
|
||
if [ ${#ALERTS[@]} -gt 0 ]; then
|
||
BODY=$(printf '%s\n' "${ALERTS[@]}")
|
||
echo "$BODY" | mail -s "[vreaudigital] heartbeat: ${#ALERTS[@]} schemas stale" \
|
||
-S smtp="smtps://$BREVO_SMTP_HOST:$BREVO_SMTP_PORT" \
|
||
-S smtp-auth=login \
|
||
-S smtp-auth-user="$BREVO_SMTP_USER" \
|
||
-S smtp-auth-password="$BREVO_SMTP_KEY" \
|
||
-S from="alerts@beletage.ro" \
|
||
m.tarau@beletage.ro
|
||
fi
|
||
```
|
||
|
||
**Alternativă alerting:** n8n webhook (deja deployed la `https://n8n.beletage.ro`) — POST simplu, n8n trimite mai departe pe Telegram/Slack/email cu un singur workflow.
|
||
|
||
```bash
|
||
curl -fsS -X POST https://n8n.beletage.ro/webhook/vreaudigital-heartbeat \
|
||
-H 'Content-Type: application/json' \
|
||
-d "$(jq -n --arg body "$BODY" '{type:"stale-data", alerts:$body}')"
|
||
```
|
||
|
||
### 4.2 Per-scraper OnFailure alert
|
||
|
||
Adaugă `OnFailure=vreaudigital-alert@%n.service` în fiecare timer. Template service:
|
||
|
||
```ini
|
||
# /etc/systemd/system/vreaudigital-alert@.service
|
||
[Unit]
|
||
Description=vreaudigital alert for %i
|
||
|
||
[Service]
|
||
Type=oneshot
|
||
User=bulibasa
|
||
ExecStart=/opt/vreaudigital/services/seap-scraper/cron/alert.sh %i
|
||
```
|
||
|
||
`alert.sh %i` extrage ultimele 50 linii via `journalctl -u %i -n 50` și le trimite la n8n webhook.
|
||
|
||
### 4.3 Top blind spots care necesită monitor azi
|
||
|
||
1. **`seap.sync_state[source=da].status = pending` din 2025-10-16** (208 zile!) — DA backfill blocat și nimeni nu primește alert. **Trebuie heartbeat dedicat pentru `sync_state` și `wsp_sync_state` care alertează dacă `updated_at < now() - 24h` sau `consecutive_errors > 5`**.
|
||
2. **WSP `last_run_at = 2026-05-07`** (4 zile stale, ar trebui la 4h). Patternul deja descris în audit ca lipsit — heartbeat fix-uiește.
|
||
3. **Disk 85% pe satra** — heartbeat trebuie să verifice `df -h /` și să alerteze la 90%.
|
||
|
||
### 4.4 Sample monitor query — copy-paste într-un singur SQL
|
||
|
||
```sql
|
||
SELECT 'STALE: '||t AS alert FROM (
|
||
SELECT 'seap.announcements' AS t, max(fetched_at) AS f FROM seap.announcements
|
||
UNION ALL SELECT 'seap.direct_acquisitions', max(fetched_at) FROM seap.direct_acquisitions
|
||
UNION ALL SELECT 'firms.entities', max(updated_at) FROM firms.entities
|
||
UNION ALL SELECT 'fonduri.afir_plati', max(fetched_at) FROM fonduri.afir_plati
|
||
UNION ALL SELECT 'regas.ajutoare', max(fetched_at) FROM regas.ajutoare
|
||
UNION ALL SELECT 'anre.licente', max(fetched_at) FROM anre.licente
|
||
UNION ALL SELECT 'ancom.operatori', max(fetched_at) FROM ancom.operatori
|
||
UNION ALL SELECT 'asf.entitati', max(fetched_at) FROM asf.entitati
|
||
UNION ALL SELECT 'cnsc.decizii', max(fetched_at) FROM cnsc.decizii
|
||
UNION ALL SELECT 'cnas.documents', max(fetched_at) FROM cnas.documents
|
||
UNION ALL SELECT 'aaas.firme', max(fetched_at) FROM aaas.firme
|
||
UNION ALL SELECT 'curteacont.rapoarte', max(fetched_at) FROM curteacont.rapoarte
|
||
UNION ALL SELECT 'apia.fermieri', max(fetched_at) FROM apia.fermieri
|
||
UNION ALL SELECT 'aep.donatii_pj', max(fetched_at) FROM aep.donatii_pj
|
||
UNION ALL SELECT 'gnm.comunicate', max(fetched_at) FROM gnm.comunicate
|
||
UNION ALL SELECT 'bugetar.entitate', max(fetched_at) FROM bugetar.entitate
|
||
UNION ALL SELECT 'anaf.datornici', max(fetched_at) FROM anaf.datornici
|
||
) x
|
||
WHERE f < now() - (
|
||
CASE
|
||
WHEN t LIKE 'seap.%' THEN interval '12 hours'
|
||
WHEN t IN ('anre.licente','ancom.operatori','asf.entitati','cnsc.decizii','curteacont.rapoarte','gnm.comunicate') THEN interval '54 hours'
|
||
WHEN t IN ('firms.entities','cnas.documents','aaas.firme','fonduri.afir_plati') THEN interval '12 days'
|
||
WHEN t IN ('regas.ajutoare','apia.fermieri','bugetar.entitate') THEN interval '52 days'
|
||
WHEN t = 'aep.donatii_pj' THEN interval '143 days'
|
||
WHEN t = 'anaf.datornici' THEN interval '270 days'
|
||
END
|
||
);
|
||
```
|
||
|
||
Rulează zilnic la 07:00. Dacă returnează rânduri → email/n8n.
|
||
|
||
---
|
||
|
||
## 5. Idempotency contract per source
|
||
|
||
**Cerință:** fiecare scraper TREBUIE să fie idempotent — re-rularea NU duplică, doar refresh `fetched_at`.
|
||
|
||
| Schema | Idempotency key | Mecanism (din cod existent verificat sau menționat în audit) | Status |
|
||
|---|---|---|---|
|
||
| seap.announcements | `(source, source_id)` | `ON CONFLICT (source, source_id) DO UPDATE` (confirmat audit) | ✅ |
|
||
| seap.direct_acquisitions | similar | similar | ✅ |
|
||
| firms.entities | `cui` PK | `ON CONFLICT (cui) DO UPDATE` | ✅ |
|
||
| firms.financials | `(cui, source_year)` | UPSERT | ✅ |
|
||
| fonduri.afir_plati | `(cnp_cui_hash, source_year, suma)` | hash unique | ✅ (audit) |
|
||
| fonduri.beneficiar_anunt | `(announcement_id)` | UPSERT | ✅ |
|
||
| regas.ajutoare | `(cui, an, masura)` | UPSERT | ✅ |
|
||
| anaf.datornici | `(cui, publication_date)` | `ON CONFLICT (cui, publication_date) DO UPDATE` (confirmat wrapper) | ✅ |
|
||
| anaf.lista_alba | TBD | gol — pipeline neimplementat | ⚠️ |
|
||
| aep.donatii_pf | `(partid, donator_nume, data, suma)` | composite UNIQUE | ✅ |
|
||
| aep.donatii_pj | similar | composite UNIQUE | ✅ |
|
||
| aep.donatii_rvc | similar | composite UNIQUE | ⚠️ are date eronate 2034 — necesită cleanup, dar UPSERT funcționează |
|
||
| bugetar.entitate | `cif` | UPSERT | ✅ |
|
||
| bugetar.executie | TBD | gol | ⚠️ |
|
||
| anre.licente | `(source, nr_autorizare)` sau sha1 | UPSERT pe sha1 (wrapper confirmă) | ✅ |
|
||
| anre.electricieni | `UNIQUE(nr_autorizare, nume_prenume)` (wrapper) | UPSERT | ✅ (când rulează) |
|
||
| ancom.operatori | `cui` | UPSERT | ✅ |
|
||
| ancom.drepturi | `(cui, tip_drept)` | UPSERT | ✅ |
|
||
| asf.entitati | `cui` | UPSERT | ✅ |
|
||
| cnsc.decizii | `(decision_no, decision_year)` | `ON CONFLICT (decision_no, decision_year) DO UPDATE` (wrapper confirmat) | ✅ |
|
||
| cnas.documents | `source_url_sha1` | UPSERT | ✅ |
|
||
| cnas.furnizori | `(document_id, row_index)` | UPSERT | ✅ |
|
||
| aaas.firme | `cui` | UPSERT | ✅ |
|
||
| curteacont.rapoarte | `(audit_year, report_no)` sau URL | UPSERT | ✅ |
|
||
| apia.fermieri | `(cnp_cui, campania)` | UPSERT | ✅ |
|
||
| ani.declaratii | `pdf_sha1` | UPSERT | ✅ (când parser funcționează) |
|
||
| gnm.comunicate | `URL sha1` | UPSERT | ✅ |
|
||
| gnm.amenzi_extrase | `(comunicat_id, violator_cui, suma)` | UPSERT | ✅ |
|
||
|
||
**Non-idempotent suspects (necesită review cod):**
|
||
- `anaf.lista_alba` — gol, pipeline neexistent. Când implementat, UPSERT pe `cui`.
|
||
- `bugetar.executie` — gol. Când implementat, UPSERT pe `(cif, an, luna, indicator)`.
|
||
- TED import (`import_ted.py`) — `publication-date` bug confirmat în audit; UPSERT-ul probabil funcționează, dar fix-ul de 1 linie e prerequisite.
|
||
|
||
**Action item:** după implementarea bugetar.executie și anaf.lista_alba, verifică explicit `ON CONFLICT DO UPDATE/DO NOTHING` în INSERT statements și adaugă teste de idempotență (rulează scraperul de 2 ori la rând și verifică `count(*)` constant).
|
||
|
||
---
|
||
|
||
## 6. Disaster recovery
|
||
|
||
### 6.1 RTO/RPO
|
||
|
||
**Componente:**
|
||
- DB `architools_db` @ 10.10.10.166 — 29 GB
|
||
- Codul pe `gitadmin/gov-agreg` Gitea — recuperabil în <1 min
|
||
- `.infisical-mi` files — secrets în Infisical, recuperabil cu MI restart
|
||
- Cron-uri/timere — în git repo (path `services/seap-scraper/cron/`)
|
||
|
||
**RTO (Recovery Time Objective):** ~2 ore — git clone + restore dump + restart timers.
|
||
**RPO (Recovery Point Objective):** depinde de backup cadence — vezi 6.2.
|
||
|
||
### 6.2 DB backup status (verified azi)
|
||
|
||
`sudo crontab -l` pe satra arată **DOAR**:
|
||
- `/opt/pug-tracker-scripts/scripts/backup-db.sh` la 03:00
|
||
- `/home/bulibasa/backup.sh` la 05:45
|
||
- eterra stats email la 06:30
|
||
|
||
**NU există backup explicit pentru `architools_db`** — trebuie verificat dacă `pug-tracker-scripts/backup-db.sh` sau `bulibasa/backup.sh` include `architools_db`. **Această este o gaură critică în DR**.
|
||
|
||
**Acțiune imediată:** verifică conținut `/opt/pug-tracker-scripts/scripts/backup-db.sh` și `/home/bulibasa/backup.sh`. Dacă `architools_db` lipsește, adaugă:
|
||
|
||
```bash
|
||
# /opt/vreaudigital/services/seap-scraper/cron/backup-db.sh
|
||
#!/bin/bash
|
||
set -euo pipefail
|
||
BACKUP_DIR=/backups/architools_db
|
||
mkdir -p "$BACKUP_DIR"
|
||
DATE=$(date +%Y%m%d_%H%M)
|
||
|
||
source /opt/vreaudigital/.infisical-mi
|
||
# ... (fetch DATABASE_URL pattern)
|
||
|
||
# pg_dump custom format (compressed, parallelizable restore)
|
||
pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" \
|
||
--format=custom \
|
||
--jobs=4 \
|
||
--no-owner --no-acl \
|
||
--exclude-table='*staging_*' \
|
||
--exclude-table-data='*log*' \
|
||
--file="$BACKUP_DIR/architools_${DATE}.dump"
|
||
|
||
# Keep 7 daily, 4 weekly, 12 monthly
|
||
find "$BACKUP_DIR" -name 'architools_*.dump' -mtime +90 -delete
|
||
```
|
||
|
||
**Programare:** `vreaudigital-backup.timer OnCalendar=*-*-* 23:00:00` (înainte de scrape-urile de 02:00).
|
||
|
||
**Mărime estimată:** 29GB DB → ~6-8GB compressed (custom format ratio ~4×). Disc satra: 57GB liberi, suficient pentru ~7 zile retention pe satra + rotate spre **shop** sau **NAS Synology** via rclone/rsync.
|
||
|
||
### 6.3 Restore procedure (documentată)
|
||
|
||
```
|
||
# 1. Pe satra (sau host nou):
|
||
git clone https://git.beletage.ro/gitadmin/gov-agreg.git /opt/vreaudigital
|
||
cd /opt/vreaudigital/services/seap-scraper
|
||
npm install --omit=optional
|
||
|
||
# 2. Restore .infisical-mi
|
||
scp <safe-source>:/opt/vreaudigital/.infisical-mi /opt/vreaudigital/
|
||
chmod 600 /opt/vreaudigital/.infisical-mi
|
||
|
||
# 3. Restore DB
|
||
createdb architools_db
|
||
pg_restore --jobs=4 --no-owner --no-acl \
|
||
--dbname=architools_db \
|
||
/backups/architools_db/architools_<latest>.dump
|
||
|
||
# 4. Restart timers
|
||
sudo systemctl enable --now vreaudigital-*.timer
|
||
sudo systemctl list-timers | grep vreaudigital
|
||
```
|
||
|
||
### 6.4 Off-site backup
|
||
|
||
Recomandare: rsync zilnic `/backups/architools_db/` la **shop.avizero.ro:/srv/backups/satra-architools/** sau spre Synology NAS dacă există. **NU rsync direct la GitHub/Gitea** (29GB > limit).
|
||
|
||
```
|
||
# /etc/systemd/system/vreaudigital-backup-offsite.timer OnCalendar=*-*-* 23:30:00
|
||
rsync -avz --delete /backups/architools_db/ shop:/srv/backups/satra-architools/
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Recommended action items, prioritized
|
||
|
||
### 7.1 This week (low effort, high ROI)
|
||
|
||
| # | Item | Effort | Impact |
|
||
|---|---|---|---|
|
||
| 1 | **Fix TED `publication-date` field** în `import_ted.py` (1-line) | 5 min | 100% TED publication_date populated |
|
||
| 2 | **Reset `seap.sync_state[source=da].status` din pending → null** + relansare backfill DA | 15 min | unlock 208-day-old backfill (potential ~8M rows) |
|
||
| 3 | **Investigate WSP stall** — `wsp_sync_state.last_run_at = 2026-05-07`. Verifică cron-ul ascuns; dacă lipsește, creează `vreaudigital-seap-wsp.timer` per §2.2 | 1h | live SEAP daily feed restored |
|
||
| 4 | **Verifică backup DB** — citește `/opt/pug-tracker-scripts/scripts/backup-db.sh` și `/home/bulibasa/backup.sh`. Dacă `architools_db` lipsește, instalează `backup-db.sh` din §6.2 | 1h | DR readiness, RPO ≤ 24h |
|
||
| 5 | **Implementează `vreaudigital-heartbeat.timer`** din §4.1 + 1 query în §4.4 | 2h | dead-man's switch peste 17 schemas |
|
||
|
||
**Total week 1:** ~5h work, unlocks 4 critical paths.
|
||
|
||
### 7.2 This month (medium effort)
|
||
|
||
| # | Item | Effort | Impact |
|
||
|---|---|---|---|
|
||
| 1 | **Creează wrappere lipsă** pentru `scrape-seap-wsp`, `scrape-seap-da`, `import-fonduri-beneficiari`, `gnm-extract-amenzi`, `curteacont-detail`, `cnsc-parse-pdfs` (6 wrappere cu pattern Infisical MI) | 1 zi | uniformizează scheduling |
|
||
| 2 | **Migrează toate cele 13 wrappere existente la systemd timers vizibili** per §2.2 (înlocuiește cron-ul ascuns) | 1 zi | observabilitate `journalctl -u`, retry on failure |
|
||
| 3 | **Investigate ANAF datornici Q4 2025 publicare pe data.gov.ro** — dacă publicat, rulează `scrape-anaf-datornici SOURCE=datagov2025Q4`. Altfel începe integrare 2captcha | 1 zi | datornici devine fresh |
|
||
| 4 | **Disc cleanup pe satra** — staging tables 3GB (firms.staging_onrc_*) + log rotation + offsite backups să poată fi instalate | 4h | disc < 80%, room pentru cnsc PDFs Stage 2 |
|
||
| 5 | **CUI matcher rerun pentru cnas.furnizori, apia.fermieri, fonduri.beneficiar_proiect** (3 schemas cu 0% match) | 4h | unlock cross-source recipes |
|
||
|
||
### 7.3 Next quarter (high effort sau lower priority)
|
||
|
||
| # | Item | Effort | Impact |
|
||
|---|---|---|---|
|
||
| 1 | **CNSC Stage 2 PDF parser** — extract decision_type/summary pentru 29k decizii | 1-2 săpt | decizii filtrabile |
|
||
| 2 | **Curtea Conturi Stage 2** detail-page + audited_cui + PDF | 2 săpt | rapoarte legate la CUI |
|
||
| 3 | **Bugetar.executie Faza 2** + 2captcha pentru top-1000 entități (~$60 one-shot) | 2 săpt | flux financiar public |
|
||
| 4 | **ANI declaratii parser** (1.3M PDFs) — recommended deferred până confirmat parser ANRE/AAAS minor backlogs cleared | 4-6 săpt | flagship politicieni |
|
||
| 5 | **SEAP DA backfill 2017-2024** (~8M rows) — post DA sync_state reset | 2-3 săpt | acoperire achiziții directe completă |
|
||
|
||
---
|
||
|
||
## Anexa A — Snapshot scrape_log azi (2026-05-11)
|
||
|
||
| Schema | Last successful run | OK runs 7d |
|
||
|---|---|---:|
|
||
| aaas | 2026-05-10 17:51 | 6 |
|
||
| aep | 2026-05-09 20:58 | 4 |
|
||
| ancom | 2026-05-10 18:06 | 3 |
|
||
| anre | 2026-05-10 14:47 | 3 (4 errors) ⚠️ |
|
||
| apia | 2026-05-10 18:53 | 1 |
|
||
| asf | 2026-05-10 18:19 | 1 |
|
||
| cnas | 2026-05-10 18:08 | 67 (multiple PDF parses) |
|
||
| cnsc | 2026-05-10 19:19 | 4 |
|
||
| gnm | 2026-05-10 19:02 | 5 |
|
||
| **seap.wsp_sync_state** | **2026-05-07 03:01** (3 zile stale!) | n/a |
|
||
| **seap.sync_state[da]** | **2025-10-16** (208 zile stale!) | n/a |
|
||
|
||
**Concluzie:** 9 din 11 schemas live au rulat în ultimele 24h. SEAP WSP + DA sunt blind spots — heartbeat trebuie să le acopere explicit.
|
||
|
||
---
|
||
|
||
## Anexa B — Quick reference: existing systemd timers (current state)
|
||
|
||
```
|
||
/etc/systemd/system/vreaudigital-anaf-daily.timer → 02:00 daily → enrich-anaf.sh TIER=daily
|
||
/etc/systemd/system/vreaudigital-onrc-weekly.timer → Tue 03:00 → import-onrc-fresh.sh
|
||
/etc/systemd/system/vreaudigital-mvs.timer → 04:00 daily → refresh-mvs.sh
|
||
```
|
||
|
||
**Recomandare:** păstrează aceste 3 ca-s sunt, adaugă alte 18-20 timere pentru a acoperi celelalte schemas.
|
||
|
||
---
|
||
|
||
**Strategy doc complete.** Implementation poate începe imediat cu §7.1 items.
|
||
|
||
---
|
||
|
||
## Anexa C — AEP donatii (banipartide.ro): lag pattern confirmat 2026-05-12
|
||
|
||
**Verificare directă a sursei** (`https://www.banipartide.ro/app/json.php?mode=dt&ssid=<base64-SQL>`):
|
||
|
||
| Dataset | Total rânduri sursă | Max an pe sursă | DB rânduri | DB max an / max `data_donatie` |
|
||
|---|---:|---|---:|---|
|
||
| Donatori PJ (Monitorul Oficial 10k+) | 3,612 | **2024** (114) | 3,567 | 2024 / 2024-12-13 |
|
||
| Donatori PF (Monitorul Oficial 10k+) | 30,792 | **2024** (1,859) | 30,173 | 2024 / 2024-12-27 |
|
||
| RVC (Rapoarte Venituri/Cheltuieli) | 353,473 | **2023** (42,791) | 346,237 | 2023 / 2034-01-31 (erori OCR) |
|
||
|
||
**Concluzie:** sursa **NU are date 2025 sau 2026**. Ultima rulare a cron-ului (2026-05-11 09:15 satra) a importat deja toate rândurile existente (`seen=3612/30792/353473`). Diferența DB vs sursă (45/619/7236 rânduri) e dată de:
|
||
- PJ: 572 rânduri cu `data_donatie IS NULL` (multi-date strings ca `"11.10.2019; 13.11.2019"`) — parser-ul nu reține `an` în acele cazuri.
|
||
- PF: similar, 9,268 NULL pe `data_donatie`.
|
||
- RVC: 7,236 skip-uri pe upsert (rânduri cu format date neparsabil în limba română, ex. `"septembrie 2019"`).
|
||
|
||
### De ce nu există 2025/2026 pe sursă
|
||
|
||
**Mecanism legal (Legea 334/2006 + HG 10/2016):**
|
||
- Partidele politice raportează **donațiile peste 10× salariu minim** la AEP, care le publică în **Monitorul Oficial Partea I-A**.
|
||
- Termen legal: până la **30 aprilie anul N+1** pentru donațiile anului N (raport anual venituri/cheltuieli).
|
||
- Pentru campanii electorale: raportare separată în 15 zile de la finalul campaniei.
|
||
- Expert Forum (proiectul banipartide.ro) scanează MO, parsează PDF-urile și actualizează tabelul cca 1-3 luni după publicare.
|
||
|
||
**Calendar așteptat:**
|
||
| Date donații | Raport AEP în MO | Apariție pe banipartide.ro | Estimare disponibilitate gov-agreg |
|
||
|---|---|---|---|
|
||
| 2024 (anuale) | apr 2025 | mai-aug 2025 | ✅ deja în DB |
|
||
| 2025 (anuale) | apr 2026 | **mai-aug 2026** | 🕒 fereastră **acum** (mai 2026) – aug 2026 |
|
||
| 2026 (anuale) | apr 2027 | mai-aug 2027 | 🕒 mai 2027+ |
|
||
| 2024 campanii electorale (PE, prezidențiale, locale, parlamentare) | 15-30 zile post-campanie | 1-3 luni mai târziu | ✅ în DB la `data_donatie` apropiat de turul de scrutin |
|
||
|
||
**Notă RVC:** Rapoartele anuale de venituri/cheltuieli (RVC) sunt mai lente — 2023 a apărut probabil în 2025. Așteptăm 2024 pe sursă în **iunie-octombrie 2026**.
|
||
|
||
### Recomandare de cadență (revizuită)
|
||
|
||
Cron actual `vreaudigital-aep-donatii.timer` = 1 ale lunii la 03:30 (= **lunar**, mai des decât §1 #26 care zicea quarterly). Asta e **OK pentru fereastra mai-august 2026** când e cel mai probabil să apară 2025 — îl prinde la prima rulare.
|
||
|
||
**Nu schimbăm cadența**. Heartbeat-ul (§4.1) ar trebui să fie tolerant la **95 zile** stale (cum e setat), pentru că între ianuarie-aprilie nu apare nimic nou și asta e normal.
|
||
|
||
### Next check
|
||
|
||
Următoarea verificare automată **15 iunie 2026** (~o lună după aceasta) — dacă sursa tot nu publică 2025, alarmă falsă; dacă publică, cron-ul de 1 iulie 03:30 va prinde inserțiile. Verificare manuală opțională: `curl` aceeași SQL ca aici, `python3 -c "..."` pentru count years.
|
||
|