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:
@@ -0,0 +1,175 @@
|
||||
# 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`
|
||||
Reference in New Issue
Block a user