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

176 lines
7.8 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.
# ANAF Datornici — recipes & integration handoff
Status la **2026-05-09**: schema `anaf.*` aplicată, 140,777 firme T1-2016 ingerate
(83.2 mld RON datorie totală). Surse live (anaf.ro/restante/) **CAPTCHA-blocked**
— vezi limitări mai jos.
## Ce există acum în DB
```sql
-- 140,777 firme cu obligații restante la 2016-03-31
anaf.datornici -- mari (164) + mijlocii (2,132) + mici (138,481)
anaf.lista_alba -- gol (lista albă necesită live scrape — captcha-blocked)
anaf.datornici_latest -- view DISTINCT ON (cui) ORDER BY pub_date DESC
```
Coloane importante:
- `cui` (text, fără prefix RO)
- `publication_date` (date) — `2016-03-31` pentru singura publicare ingerată
- `period_label``'T1 2016'`
- `debtor_category``'mari'` | `'mijlocii'` | `'mici'`
- `debt_total`, `debt_principal`, `debt_penalty`, `debt_contested` (numeric RON)
- detaliu per buget (state, social, unemployment, health) × (principal, penalty, contested)
Index-uri: `cui`, `publication_date DESC`, `debt_total DESC`, `debtor_category`.
## Limitări — citește înainte de a planifica scraperul live
1. **anaf.ro/restante/index.xhtml** e o aplicație JSF/PrimeFaces cu **CAPTCHA**
pe submit. Am încercat:
- JSF AJAX submit fără CAPTCHA → `rowCount=0` silent (nu eroare, dar tabel gol)
- Replay cu cookie + ViewState valid → același rezultat (CAPTCHA validată
server-side, nu client-side)
- Nu există endpoint JSON public alternativ
2. **anaf.ro nu publică arhive trimestriale istorice public**. Doar trimestrul
curent e accesibil prin UI (cu CAPTCHA). Pentru istorie trebuie:
- archive.org snapshots (manual, fragmentar)
- sau colaborare cu listafirme.eu (paywall API ~€/lună)
3. **data.gov.ro** publică doar Q1-2016 ca CSV (3 fișiere mari/mijlocii/
micijuridice) — `dataset/datoriile-catre-bugetul-de-stat`. Nu se actualizează.
Pentru live scrape, trebuie integrat un **captcha solver extern** (2captcha sau
anti-captcha, ~$1-3 / 1000 captcha-uri). Stub în
`src/scrape-anaf-datornici.ts::scrapeAnafLive()` (comentat). Workflow:
```
1. GET /restante/index.xhtml → ViewState + JSESSIONID
2. GET /restante/kaptcha.jpg?pfdrid_c=true → bytes (PNG)
3. POST img la 2captcha.com/in.php → ID, polled la /res.php?action=get
4. POST /restante/index.xhtml cu form:inputc=<solution>
5. Parse <update id="form:dataTable"> XML → extract rows
6. PrimeFaces dataTable_paginator → POST cu page param până la `(N of N)`
```
Estimare: ~5-15K rânduri × ~30 secunde/captcha-iterație × 1 trimestru = ~1-2h
per trimestru. Dacă vrem 4 trimestre × 5 ani = 20 trimestre = ~20h totale.
## Recipe propus pentru recipes.ts (Phase 4 ANI agent owns recipes.ts)
> **NU edita recipes.ts în această sesiune** — Phase 4 ANI a commit-uit deja
> `politicianFirmaFurnizorStat`. Această secțiune documentează ce **trebuie
> adăugat** în următoarea sesiune unde recipes.ts e disponibil.
### `firmeDatorniceCuContracteSeap` — KILLER red-flag
Firme care apăreau pe lista ANAF datornici la o dată X, ȘI au câștigat contracte
publice SEAP **după** acea dată — interzis prin art. 165 Legea 98/2016 (pentru
obligații executorii).
**Date validation pe data live (Q1-2016 snapshot):**
- 1,561 firme datornice → 36,403 contracte → 5.83 mld RON
- Top: URBAN SA (485 mil debt → 64 contracte), SOCIETATEA COMPLEXUL ENERGETIC
HUNEDOARA (477 mil debt), HIDROELECTRICA (214 mil debt → 48 contracte 79
mil RON post-publicare), ROMAERO, SRTV.
```ts
{
slug: 'firme-datornice-cu-contracte-seap',
title: 'Firme datornice ANAF care au câștigat contracte SEAP',
desc: 'Firme care apăreau pe lista ANAF cu datorii la stat — și au luat contracte publice imediat după (interzis Legea 98/2016 art. 165).',
category: 'red-flags',
badge: '🚨 datornic + contract',
sql: `
SELECT
d.cui,
d.name AS firma,
d.period_label,
ROUND(d.debt_total/1000000.0, 2) AS datorie_mil_ron,
d.debtor_category AS categorie_datornic,
COUNT(DISTINCT a.id) AS contracte,
ROUND(SUM(a.awarded_value)::numeric/1000000.0, 2) AS contracte_mil_ron,
MAX(a.publication_date::date) AS ultim_contract,
e.adr_judet AS judet
FROM anaf.datornici d
JOIN seap.announcements a ON a.supplier_cui = d.cui
LEFT JOIN firms.entities e ON e.cui = d.cui
WHERE a.publication_date::date > d.publication_date
AND a.awarded_value IS NOT NULL
AND a.awarded_value > 0
GROUP BY d.cui, d.name, d.period_label, d.debt_total, d.debtor_category, e.adr_judet
HAVING SUM(a.awarded_value) > 100000 -- filter zgomot
ORDER BY SUM(a.awarded_value) DESC
LIMIT 200;
`,
cols: [
{ key: 'cui', label: 'CUI' },
{ key: 'firma', label: 'Firmă', link: (r) => `/achizitii/firma/${r.cui}` },
{ key: 'period_label', label: 'Trimestrul publicării' },
{ key: 'datorie_mil_ron', label: 'Datorie (mil RON)', numeric: true },
{ key: 'categorie_datornic', label: 'Categorie ANAF' },
{ key: 'contracte', label: 'Nr. contracte SEAP', numeric: true },
{ key: 'contracte_mil_ron', label: 'Valoare contracte (mil RON)', numeric: true },
{ key: 'ultim_contract', label: 'Ultim contract' },
{ key: 'judet', label: 'Județ' },
],
}
```
**Caveats pentru recipe:**
- Cu doar T1-2016 ingerat, recipe-ul reflectă **doar acel snapshot** — toate
contractele post-2016-03-31, fără să știm dacă firma și-a plătit datoriile
ulterior. Pentru rigoare, ar trebui să comparăm cu snapshot mai recent (live)
ca să excludem firmele care au stins datoriile.
- Multe state-owned (HIDROELECTRICA, ROMAERO, COMPLEXUL ENERGETIC HUNEDOARA) —
legitimitate parțială (datorii încrucișate stat-stat). Filtru viitor:
`EXCEPT companii cu acționar stat majoritar`.
- `e.judet` join opțional — `firms.entities` are 100% acoperire CUI privat;
unele datornic-i sunt dispărute / radiate.
## Integration points pentru profile pages (viitor)
Pe `/achizitii/firma/[cui]` adaugă badge dacă apare în `anaf.datornici_latest`:
```sql
SELECT period_label, debt_total, debt_principal, debt_penalty, debtor_category
FROM anaf.datornici_latest WHERE cui = $1;
```
UI badge similar cu RegAS / EU funds:
- 🚨 Roșu: `debt_total > 1_000_000` (datornic mare)
- 🟠 Portocaliu: orice apariție în lista datornici
Dacă vrem contrast pozitiv, când avem `anaf.lista_alba` populated:
- ✅ Verde: cui în `lista_alba` la cel mai recent trimestru
## Cum re-rulez ingest-ul
```bash
# Re-import data.gov.ro Q1-2016 (idempotent, ON CONFLICT DO UPDATE)
ssh satra "sudo /opt/vreaudigital/services/seap-scraper/cron/scrape-anaf-datornici.sh"
# Doar dry-run (parsează fără DB writes)
ssh satra "sudo DRY_RUN=1 /opt/vreaudigital/services/seap-scraper/cron/scrape-anaf-datornici.sh"
# Live scrape (NU e implementat — necesită captcha solver):
# ssh satra "sudo SOURCE=live /opt/vreaudigital/services/seap-scraper/cron/scrape-anaf-datornici.sh"
```
## Next steps prioritizate
1. **MVP scoreboard** (1h): adaugă `getAnafDebtStatus(cui)` în profile-queries.ts
(după ce Phase 3/4 dau drumul la lib/) + badge pe firma profile.
2. **Recipe** (30 min): adaugă `firmeDatorniceCuContracteSeap` în recipes.ts.
3. **Live scraper cu captcha solver** (3-4h): integrare 2captcha în
`scrapeAnafLive()` + cron lunar pentru trimestrul curent.
4. **Backfill istoric** (variabil): dacă găsim arhive (archive.org / partner)
ingerăm trimestru-cu-trimestru. Schemă deja suportă (PK = cui+pub_date).
5. **Lista albă scrape**: același endpoint cu CAPTCHA, 100x mai rar lookup
(~50-100K firme curate per trimestru). Useful pentru contraste.
## Files
- Schema: `services/seap-scraper/sql/025_anaf_datornici.sql`
- Scraper: `services/seap-scraper/src/scrape-anaf-datornici.ts`
- Cron wrapper: `services/seap-scraper/cron/scrape-anaf-datornici.sh`
- This doc: `services/seap-scraper/ANAF-DATORNICI-RECIPES.md`