# ANCOM — Registrul Furnizorilor de Comunicatii Electronice **Status:** ingest implementat și aplicat (2026-05-10). **Sursă:** ANCOM (Autoritatea Națională pentru Administrare și Reglementare în Comunicații) **Lege:** Legea 159/2010 (registru public, transparență) ## Surse URL listă autorizați (server-rendered HTML, paginat 10/pag, ~57 pag → ~570 furnizori): ``` https://www.ancom.ro/reglementare-ro/comunicatii-electronice/ furnizori-comunicatii-electronice/ lista-furnizorilor-de-retele-si-servicii-de-comunicatii-autorizati/ ``` Pagination: POST `paged=N` (form `id="ms_form"`). URL detaliu (per furnizor, `ancom_id` din lista): ``` https://www.ancom.ro/sablon/furnizorinew_23/?id={ancom_id}&pid=4186 ``` Pagina de detaliu conține: Denumire, Adresa/Oras/Judet, **CUI direct** (Cod unic de înregistrare), EUID (Registrul Comerțului), tipuri de retele R1..R11 + servicii S1..S12 cu data nasterii dreptului. ## Schema SQL Fișier: `services/seap-scraper/sql/029_ancom.sql` 3 tabele + 1 MV: - `ancom.operatori` — flat, PK `ancom_id` (din URL `?id=N`); CUI direct (no fuzzy) - `ancom.drepturi` — long table: 1 rând per (operator, R/S code) cu `data_nasterii` - `ancom.scrape_log` — mirror la convenția `anre.scrape_log` - `ancom.mv_operatori_per_cui` — rollup join cu `seap.announcements.supplier_cui` ## Fișiere | Fișier | Linii | Rol | |---|---|---| | `sql/029_ancom.sql` | 113 | Schema (3 tabele + MV) | | `src/scrape-ancom.ts` | ~410 | Scraper TS (list paginate + detail HTML parser) | | `cron/scrape-ancom.sh` | 73 | Wrapper docker + Infisical Machine Identity | | `cron/match-cui-ancom.sh` | 175 | Stage A+B+C fallback pentru CUI lipsă | ## Pattern Identic cu `scrape-anre.ts`: 1. Infisical Machine Identity → env-file → `docker run --env-file` (NEVER `-e $VAR`) 2. Idempotent (UPSERT pe `ancom_id`) 3. CUI extras direct din pagina de detaliu (`

Cod unic de înregistrare: N

`) 4. `match-cui-ancom.sh` rulat **după** scrape pentru rândurile eventual rămase fără CUI ## Knobs ```bash # Smoke (1 pagină = 10 operatori) sudo MAX_PAGES=1 /opt/vreaudigital/services/seap-scraper/cron/scrape-ancom.sh # Subset (limit primele N după dedup) sudo LIMIT=50 /opt/vreaudigital/services/seap-scraper/cron/scrape-ancom.sh # Full sudo /opt/vreaudigital/services/seap-scraper/cron/scrape-ancom.sh # CUI matcher (idempotent, doar NULL-urile) sudo /opt/vreaudigital/services/seap-scraper/cron/match-cui-ancom.sh ``` ## Cross-source recipes — DRAFT ### R1: Furnizori telco SEAP fără autorizație ANCOM (red flag) Furnizori care au câștigat contracte SEAP cu CPV-uri telco (32xx — telecomm equipment, 64xx — postal & telecom services) dar **NU sunt** în registrul ANCOM de furnizori autorizați. Caz potențial: subcontractare, revânzare, sau activitate care necesită licență dar n-a fost solicitată. ```sql -- Furnizori SEAP cu contracte telco pe ultimii 24 luni dar absent ANCOM WITH telco_seap AS ( SELECT a.supplier_cui, a.supplier_name, COUNT(*) AS nr_contracte, SUM(a.value_ron) AS valoare_totala_ron, array_agg(DISTINCT a.cpv_code) FILTER (WHERE a.cpv_code IS NOT NULL) AS cpv_codes FROM seap.announcements a WHERE a.supplier_cui IS NOT NULL AND a.publication_date >= now() - interval '24 months' AND ( a.cpv_code LIKE '32%' OR -- echipamente telco a.cpv_code LIKE '64%' OR -- servicii postale & telecom a.cpv_code LIKE '72400%' -- internet services ) GROUP BY a.supplier_cui, a.supplier_name ) SELECT t.supplier_cui, t.supplier_name, t.nr_contracte, t.valoare_totala_ron, t.cpv_codes, -- profil firmă (caen + judet) pentru context e.caen_principal, e.adr_judet FROM telco_seap t LEFT JOIN ancom.mv_operatori_per_cui m ON m.cui = t.supplier_cui LEFT JOIN firms.entities e ON e.cui = t.supplier_cui WHERE m.cui IS NULL -- ! NU are autorizatie ANCOM AND t.valoare_totala_ron > 100000 -- relevant business volume ORDER BY t.valoare_totala_ron DESC LIMIT 100; ``` ### R2: Furnizori ANCOM autorizați — câți au câștigat contracte publice? Inversul lui R1. Câți operatori autorizați ANCOM au cel puțin un contract SEAP? Care e concentrarea pe top 10? ```sql SELECT m.cui, m.nr_autorizatii, m.retele, m.servicii, o_first.titular_name, COUNT(a.id) AS nr_contracte_seap, SUM(a.value_ron) AS valoare_seap_ron, MIN(a.publication_date) AS prima_castiga, MAX(a.publication_date) AS ultima_castiga FROM ancom.mv_operatori_per_cui m LEFT JOIN LATERAL ( SELECT titular_name FROM ancom.operatori WHERE titular_cui = m.cui LIMIT 1 ) o_first ON TRUE LEFT JOIN seap.announcements a ON a.supplier_cui = m.cui GROUP BY 1,2,3,4,5 ORDER BY valoare_seap_ron DESC NULLS LAST LIMIT 50; ``` ### R3: Concentrare pe județe pentru drept S2 (mobil) sau R3 (fibră) ```sql SELECT o.judet, COUNT(*) FILTER (WHERE d.cod = 'S2') AS nr_mobil, COUNT(*) FILTER (WHERE d.cod = 'R3') AS nr_fibra, COUNT(*) FILTER (WHERE d.cod = 'S1') AS nr_internet_fix, COUNT(DISTINCT o.titular_cui) AS nr_furnizori_unici FROM ancom.operatori o JOIN ancom.drepturi d ON d.ancom_id = o.ancom_id WHERE o.status = 'autorizat' GROUP BY 1 ORDER BY nr_furnizori_unici DESC NULLS LAST LIMIT 25; ``` ## Limitări cunoscute - Doar lista **autorizați** este ingest-ată. ANCOM mai publică: - lista furnizorilor radiați - lista furnizorilor sancționați (suspendare drepturi) - lista celor în libertate de prestare (cross-border) Toate folosesc același pattern `?pid={X}` și pot fi adăugate ca surse extra cu `status='radiat'`/`'sanctionat'`/`'cross-border'`. - `data_nasterii` per drept e data inițială — ANCOM nu publică data revocării per-drept, doar pe statusul global al furnizorului. - ~570 operatori / scrape ~3 min cu sleep 150ms per detail. Rulare lunară e suficientă; date public oarecum statice. ## Next steps 1. ~~Ingest autorizați~~ ✓ DONE 2. Adaugă scrape-ancom-radiati.ts (sursa: lista furnizorilor radiați, pid=4318 sau similar) 3. Crează recipe cross-source `furnizori_telco_neautorizati` în `src/lib/recipes.ts` (NU eu — exclusion zone) — pattern listat la R1 mai sus 4. Pagină profil pe `/registru/ancom/[cui]` (similar cu beneficiar-privat) — NU eu 5. CUI matcher cron lunar — adaugă în refresh-mvs.sh sau systemd timer dedicat