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

7.8 KiB
Raw Permalink Blame History

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

-- 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.
{
  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:

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

# 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