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:
Claude VM
2026-05-13 00:10:32 +03:00
commit a6c03a091e
352 changed files with 75295 additions and 0 deletions
@@ -0,0 +1,96 @@
-- 025_anaf_datornici.sql
-- ANAF — Lista contribuabililor cu obligații fiscale restante (datornici).
-- Source: https://www.anaf.ro/restante/ (publicare trimestrială, Ord. 558/2016).
-- Plus lista albă (contribuabili FĂRĂ datorii) la /restante/listaalba.xhtml.
--
-- Bazele legale: ANAF publică trimestrial sumele restante peste plafoane —
-- 500.000 lei (mari contribuabili), 250.000 lei (mijlocii), 100.000 lei
-- (mici), 10.000 lei (instituții publice). Sub plafon nu se publică.
--
-- KILLER USE CASE: cross-reference cu seap.announcements pentru a găsi
-- "firme datornice care au câștigat contracte publice" — interzis prin
-- art. 165 Legea 98/2016 dacă sunt obligații fiscale executorii.
--
-- IMPORTANT — limitări surse de date (2026-05-09):
-- 1. anaf.ro/restante/index.xhtml e o aplicație JSF/PrimeFaces cu CAPTCHA
-- de tip kaptcha pe submit. Nu e bulk-scrapeable fără OCR/captcha-solver
-- pentru cele ~5K-15K rânduri per trimestru (×4 trim × ~10 ani = ~500K).
-- 2. data.gov.ro publică UN SINGUR snapshot Q1-2016 (mari/mijlocii/micijuridice
-- CSV) — 140,780 rânduri, util ca baseline istoric.
-- 3. listafirme.eu agregă ANAF datornici în spatele unui paywall API.
--
-- Strategia ingest:
-- - Faza 1 (THIS): schema + importer CSV pentru data.gov.ro Q1-2016 snapshot.
-- ~140K rânduri reale, validează schema end-to-end.
-- - Faza 2 (TODO): scraper cu captcha-solver extern (anti-captcha.com /
-- 2captcha) pentru anaf.ro/restante/ live + arhive trimestriale dacă găsim.
-- - Faza 3: integrare cu firms.entities pentru profile badges + recipe-uri.
CREATE SCHEMA IF NOT EXISTS anaf;
-- ── Tabelă principală: datornici per (CUI × dată publicare) ─────────────────
CREATE TABLE IF NOT EXISTS anaf.datornici (
cui text NOT NULL, -- fără prefix RO
name text, -- denumirea contribuabilului
judet text, -- 2026: nu e disponibil în CSV-urile data.gov.ro Q1-2016, dar e expus în XHTML live
publication_date date NOT NULL, -- prima zi a trimestrului (2016-01-01 = T1 2016)
period_label text NOT NULL, -- 'T1 2016' / 'T2 2024' etc.
debtor_category text, -- 'mari' | 'mijlocii' | 'mici' | 'institutii_publice' | 'persoane_fizice'
debt_total numeric(20,2), -- suma RON (principal + accesorii la toate cele 4 bugete)
debt_principal numeric(20,2), -- suma RON (principal la toate cele 4 bugete)
debt_penalty numeric(20,2), -- suma RON (accesorii la toate cele 4 bugete)
debt_contested numeric(20,2), -- suma RON contestată (necontestată = total - contested)
-- Detaliu per buget (păstrăm pentru forensică, deși total/principal/penalty
-- agregat e suficient pentru majoritatea recipes):
budget_state_principal numeric(20,2),
budget_state_penalty numeric(20,2),
budget_state_contested numeric(20,2),
budget_social_principal numeric(20,2),
budget_social_penalty numeric(20,2),
budget_social_contested numeric(20,2),
budget_unemployment_principal numeric(20,2),
budget_unemployment_penalty numeric(20,2),
budget_unemployment_contested numeric(20,2),
budget_health_principal numeric(20,2),
budget_health_penalty numeric(20,2),
budget_health_contested numeric(20,2),
source_url text, -- URL original al CSV / XHTML
fetched_at timestamptz DEFAULT now(),
PRIMARY KEY (cui, publication_date)
);
CREATE INDEX IF NOT EXISTS idx_anaf_datornici_cui ON anaf.datornici(cui);
CREATE INDEX IF NOT EXISTS idx_anaf_datornici_pub_date ON anaf.datornici(publication_date DESC);
CREATE INDEX IF NOT EXISTS idx_anaf_datornici_total ON anaf.datornici(debt_total DESC NULLS LAST);
CREATE INDEX IF NOT EXISTS idx_anaf_datornici_category ON anaf.datornici(debtor_category);
-- ── Lista albă: firme FĂRĂ obligații restante (eligibile la SEAP) ───────────
-- Se publică separat la /restante/listaalba.xhtml. Mai puțin acționabilă, dar
-- utilă pentru a confirma negativ "firma X NU avea datorii când a câștigat
-- contractul Y" (când lipsește din .datornici nu înseamnă neapărat că nu
-- avea — poate fi sub plafon).
CREATE TABLE IF NOT EXISTS anaf.lista_alba (
cui text NOT NULL,
name text,
publication_date date NOT NULL,
period_label text NOT NULL,
source_url text,
fetched_at timestamptz DEFAULT now(),
PRIMARY KEY (cui, publication_date)
);
CREATE INDEX IF NOT EXISTS idx_anaf_lista_alba_cui ON anaf.lista_alba(cui);
CREATE INDEX IF NOT EXISTS idx_anaf_lista_alba_pub_date ON anaf.lista_alba(publication_date DESC);
-- ── View: cea mai recentă publicare per CUI (latest debt status) ────────────
CREATE OR REPLACE VIEW anaf.datornici_latest AS
SELECT DISTINCT ON (cui)
cui, name, judet, publication_date, period_label, debtor_category,
debt_total, debt_principal, debt_penalty, debt_contested
FROM anaf.datornici
ORDER BY cui, publication_date DESC;
COMMENT ON SCHEMA anaf IS 'ANAF (Agenția Națională de Administrare Fiscală) public registries';
COMMENT ON TABLE anaf.datornici IS 'Lista contribuabililor cu obligații restante, publicată trimestrial (Ord. 558/2016)';
COMMENT ON TABLE anaf.lista_alba IS 'Lista albă: contribuabili FĂRĂ obligații restante la data publicării';
COMMENT ON VIEW anaf.datornici_latest IS 'Cel mai recent snapshot al datoriilor per CUI';