Skip to content

fix(validate,catalog): URL con query-string e pattern_probes min_bytes - #435

Merged
Gabrymi93 merged 2 commits into
mainfrom
fix/query-string-validate-pattern-probes
Oct 26, 2026
Merged

Gabrymi93 merged 2 commits into
mainfrom
fix/query-string-validate-pattern-probes

Conversation

@Gabrymi93

@Gabrymi93 Gabrymi93 commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

Sintesi

Due difetti piccoli ma silenziosi nel catalog engine, emersi osservando il portale MEF analisi_stat (fonte del repo dichiarazioni-italia) e ricondotti a un caso minimo riproducibile. Entrambi falsano il segnale senza errori: il catalogo di una fonte risulta "validato" o "nuovo contenuto" quando non lo è.

Contesto collegato

Closes #433

Cosa cambia

  • Modifica script (radar, inventory, source-check, MCP)
  • Nuova fonte o modifica registro (sources_registry.yaml)
  • Source-check o inventory-triage
  • Modifica funnel o criteri di osservazione
  • Workflow CI (radar.yml, observatory.yml)
  • Skills o MCP tools
  • Documentazione (docs/runbook.md)

1. validate troncava l'URL a ?

scripts/collectors/_validate_base.py faceva url = url_raw.split("?")[0] prima del probe. Per le fonti che servono il dato via query-string — index.php?tree=2025CIVATOT0201&export=3 — validava la pagina HTML del portale invece del file: is_csv falso, probe sul target sbagliato, reachable/readiness sbagliati.

Ora l'URL intero va al probe; il path senza query serve solo a indovinare il formato.

2. pattern_probes: 200 vuoto contava come disponibile

available = 200 <= status < 300. MEF risponde 200 con 0 byte per l'anno non ancora pubblicato, quindi new_content_available sarebbe scattato a ogni build, per sempre: il segnale "nuovo contenuto" diventava rumore.

Nuovo campo opzionale min_bytes per entry (default 0, retro-compatibile), con below_min_bytes: true nel risultato per il debug e validazione in [tool.observatory] (intero ≥ 0, errore leggibile).

Documentazione

pattern_probes non era documentato da nessuna parte: aggiunta una sottosezione in docs/runbook.md (catalog per-repo, sintassi, min_bytes) con l'esempio MEF.

Checklist

Se modifichi script o MCP

  • pytest tests/ passa
  • ruff check . passa
  • mypy scripts/ so_mcp/ passa
  • Comando manuale verificato (sotto)

Verifica

Test aggiunti (8):

  • validate con URL a query-string: l'URL integro arriva al probe e resta in result["url"]
  • URL senza estensione nel path (index.php?...&export=3): è il campo format a decidere il probe
  • probe_pattern: 2xx con 0 byte + min_bytes=200 → available=False, below_min_bytes=True
  • probe_pattern: 2xx sopra soglia → available=True, nessun below_min_bytes
  • run_pattern_probes: min_bytes dell'entry arriva al probe (e valore non valido → 0)
  • config: pattern_probes.min_bytes rifiutato se non intero ≥ 0 (2 test: negativo, stringa)

Aggiornati i fake che mockavano l'URL troncato (MEF_URL_NO_QUERY), che documentavano il comportamento precedente.

Verifica manuale sulla fonte reale:

caso esito
validate su index.php?tree=2025CIVATOT0201&export=3 reachable=True, result["url"] con query integra, probe sul file reale (200). Lo sniff dello schema resta debole (num_columns=1, delim ,) per il preambolo di 9 righe: limite dichiarato, rimandato a #434
probe_pattern su tree=2025DIRTOT020001 (0 byte) senza soglia → available=True ❌ · con min_bytes=200 → available=False ✅
probe_pattern su tree=2024DIRTOT020001 (dato presente) available=True, 5381 byte ✅

Note

Gabrymi93 added a commit that referenced this pull request Oct 26, 2026
Cherry-pick da #435: serve a far girare lint/mypy/pytest su questa PR.
La CI installa duckdb 2.0.0 (il pin è >=1.5.3) i cui stubs tipizzano
duckdb.sql() come Optional: mypy falliva su _validate_base.py con
'union-attr', e con mypy rosso pytest viene saltato.

Il venv locale ha duckdb 1.5.6, quindi il problema non è riproducibile in
locale: verificato sul log CI (Downloading duckdb-2.0.0).
Gabrymi93 added a commit that referenced this pull request Oct 26, 2026
#429)

* feat(catalog): referenced-item drift — verifica upstream vs inventario

Per ogni dataset con upstream refs, controlla che gli item referenziati
esistano ancora nell'inventario della fonte. Se ne sparisce uno →
trigger referenced_item_missing nel verdict (label REFERENCED_MISSING).

Match per kind:
- dataflow: item_id == flow ID
- ckan_dataset: item_name == dataset name (gestisce placeholder {year})
- ckan_resource: distribution_url contiene resource ID
- url: prefix match sul path statico (rimuove {year} e query)
- script_type: keyword matching (il tipo e' un ID logico, non un
  suffisso diretto — es. 'ricorsi-pervenuti-class' matcha
  'ricorsi-pervenuti-per-classificazione' via keywords)

Verificato su GA: 11/11 upstream matchati, 0 missing, verdict STABLE.
+11 test (match per kind, placeholder, missing, mixed).

* fix(catalog): drift su URL a query-string — match discriminante e stato unknown

Il matcher `url` troncava l'URL a '?' e faceva prefix match sul path: per le
fonti che servono il dato via query-string il prefisso statico e' tutto il
portale. Due conseguenze, entrambe misurate sull'inventario reale di MEF
analisi_stat (fonte di dichiarazioni-italia):

- falso positivo: 5 dei 7 dataset dichiarati REFERENCED_MISSING pur essendo
  sani (inventario homepage, URL del dataset su .../public/index.php);
- falso negativo: con l'inventario dell'albero, qualunque export parte da
  index.php → 'base in url' sempre vero, il drift non viene mai rilevato.

Ora:
- match sull'URL completo con i placeholder {year} come wildcard, con fallback
  sul solo path quando la query e' un parametro di serializzazione (ECB
  ?format=csvdata) → la query non e' piu' ne' identita' per forza ne' rumore;
- esito a tre stati: matched / missing / unknown. 'unknown' = l'inventario non
  copre l'area del ref (o il pattern matcha tutto l'inventario, quindi non
  discrimina): niente falso allarme, niente silenzio.
- area coperta (→ missing) se il prefisso statico del ref esiste nell'inventario
  oppure se il ref condivide con alcuni item un prefisso che entra nel path ma
  non con tutti.

Verifiche (inventari reali dei repo del workspace, old vs new):
  giustizia-amministrativa 11/0 → 11/0/0   eurostat 34/0 → 34/0/0
  oecd-benchmark 11/0 → 11/0/0             appalti-pubblici 12/0 → 12/0/0
  sistema-bancario 4/0 → 4/0/0 (ECB: fix del falso missing da query)
  open-mur 3/10 → 3/10/0 (invariato)
  indice-pa 0/9 → 0/8/1 (drift preservato, 1 ref non coperto → unknown)
  opencivitas 3/1 → 1/0/3 (2 'matched' vacui + 1 missing non concludibile)
  MEF: inventario homepage 2 matched / 0 missing / 5 unknown (era 5 falsi
  missing); albero con vista rimossa → missing rilevato; albero completo → 7/0/0

5 test nuovi su query-string, serializzazione, match vacuo e area non coperta.

* fix(ci): guard su duckdb.sql() None (stubs duckdb 2.0)

Cherry-pick da #435: serve a far girare lint/mypy/pytest su questa PR.
La CI installa duckdb 2.0.0 (il pin è >=1.5.3) i cui stubs tipizzano
duckdb.sql() come Optional: mypy falliva su _validate_base.py con
'union-attr', e con mypy rosso pytest viene saltato.

Il venv locale ha duckdb 1.5.6, quindi il problema non è riproducibile in
locale: verificato sul log CI (Downloading duckdb-2.0.0).

* fix(catalog): script_type a tre stati — un match parziale non e' un match

Review #429 (finding Medium). Il matcher a keyword (`>=2 token`) era troppo
largo: un token generico bastava a restare 'matched' anche dopo la rimozione
dell'intera famiglia referenziata. Prova avversariale sull'inventario reale
openga (436 item):

  ref ricorsi-pervenuti-class  → 155 match (ricorsi 248, pervenuti 93, class 124)
  rimossi tutti i package 'pervenuti'      → restava 'matched' (via ricorsi+class)
  ref ricorsi-tipo-decisione   → 93 match
  rimossi tutti i 'tipo-di-decisione'      → restava 'matched' (via ricorsi+tipo)

Ora `_script_type_state` ha tre esiti, coerenti con il matcher URL:
  - matched: un item contiene TUTTI i token del ref;
  - missing: nessun item li contiene tutti ma almeno un token compare ancora
    (la famiglia c'e', la variante no) — o ref mono-token assente (la famiglia
    stessa e' sparita, caso motivante 'package *-sentenze');
  - unknown: ref multi-token di cui nessun token compare nell'inventario.

Verifiche:
  GA normale 11/11 matched (invariato); rimozione di 'pervenuti',
  'tipo-di-decisione', 'sentenze', 'sentenze-brevi', 'ricorsi-definiti' →
  tutti missing (prima 2/11 restavano matched).
  Nessuna regressione sugli altri repo: sistema-bancario 4/4, eurostat 34/34,
  oecd 11/11, appalti 12/12, open-mur 3 matched/10 missing, indice-pa 8/1,
  opencivitas 1/3 — identici a prima.

Review #429 (finding Medium) — test di integrazione mancanti: nuovo
TestReferencedItemsIntegration in test_catalog_build.py (4 test): campo
referenced_items nell'e2e, caso missing con trigger/label/score/next_action,
label non riscritta quando non era STABLE, coesistenza drift + new_content.

3 test unit nuovi sul matcher script_type (multi-token, famiglia rimossa,
vocabolario assente).
Due difetti emersi osservando il portale MEF analisi_stat (fonte del repo
dichiarazioni-italia) con il catalog engine per-repo.

1. validate_tabular_group troncava l'URL a '?' prima del probe: per le fonti
   che servono il dato via query-string (index.php?tree=...&export=3) validava
   la pagina HTML del portale, non il file → reachable/readiness sbagliati.
   Ora l'URL intero va al probe; il path senza query serve solo a indovinare
   il formato.

2. probe_pattern considerava disponibile qualsiasi 2xx: MEF risponde 200 con
   0 byte per l'anno non ancora pubblicato, quindi new_content_available
   scattava a ogni run. Nuovo campo opzionale min_bytes per entry (default 0,
   retro-compatibile) + validazione in [tool.observatory].

Test: 4 nuovi (query-string nel validate, min_bytes su probe e pass-through),
aggiornati i fake che mockavano l'URL troncato. Verificato sulla fonte reale:
vista 2025 → reachable, colonne lette; 2025 IRAP (0 byte) → unavailable con
min_bytes=200, 2024 → available.

Closes #433
Review #435 (finding Low). Da quando l'URL intero (query inclusa) va al probe,
la query finisce nei parquet tracciati: data/pipeline/validated.parquet qui e
out/so/validated.parquet nei repo dati. Oggi i parametri osservati sono
format/d/t, nessuna credenziale — ma una fonte con URL firmati o tokenizzati
verrebbe persistita in chiaro.

Nota esplicita nel runbook: la redazione dei parametri sensibili va fatta
prima di onboardare una fonte di quel tipo.
@Gabrymi93
Gabrymi93 merged commit c8c724a into main Oct 26, 2026
1 check passed
@Gabrymi93
Gabrymi93 deleted the fix/query-string-validate-pattern-probes branch October 26, 2026 23:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

validate/pattern_probes: URL con query-string e risposte 200 vuote (MEF analisi_stat)

1 participant