Skip to content

fix(drift): missing solo con prova di assenza — regola unica, non patch per kind - #439

Merged
Gabrymi93 merged 2 commits into
mainfrom
fix/drift-positive-evidence
Oct 28, 2026
Merged

Gabrymi93 merged 2 commits into
mainfrom
fix/drift-positive-evidence

Conversation

@Gabrymi93

@Gabrymi93 Gabrymi93 commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Sintesi

Il drift dichiarava missing ogni volta che un ref non si trovava nell'inventario, assumendo che l'inventario descrivesse tutto ciò che la fonte pubblica. Non è vero per 3 dei 5 kind, e ognuno di quelli che poggia sull'assunzione produce falsi positivi.

Emerso mergiando i catalog dei 9 repo: open-mur segnava 10 item "spariti" che invece esistono.

Questa PR sostituisce i rattoppi per kind con una regola sola.

Cosa cambia

La regola: missing solo con prova positiva di assenza dell'item esatto. La prova richiesta è dichiarata per kind in drift.ABSENCE_EVIDENCE:

kind Prova Perché
dataflow inventory una riga per dataflow nel catalogo SDMX
ckan_dataset inventory una riga per package (il nome è l'identità)
ckan_resource probe l'inventario tiene una distribuzione per item, ma un package ne ha decine
url probe l'inventario può descrivere un altro endpoint
script_type inventory ID logico: match per token sull'area della fonte

Un kind non dichiarato non può dichiarare missing: al massimo unknown. La tabella è il registro dei kind: _state_for la consulta come primo passo, e il gate (TestAbsenceEvidence) verifica che dichiarato e instradabile coincidano — un kind dichiarato ma non instradabile è una promessa vuota, un kind instradabile ma non dichiarato potrebbe dire missing senza prova.

Precisazione onesta su cosa decide cosa: la tabella decide se un kind può dichiarare missing (e un kind fuori tabella non entra nemmeno nel check); come si ottiene la prova lo sceglie il kind — matcher sull'inventario, oppure probe (url → HTTP, ckan_resource → resource_show). Per script_type la voce è inventory ma il match resta un'euristica per token: il suo missing significa "la famiglia c'è, la variante no", non un'assenza provata come per dataflow.

Due bug chiusi (entrambi su open-mur):

  1. ckan_resource — l'inventario tiene una distribuzione per item (mur_ustat: media 10,9 resource per package, max 59). Non trovare un resource ID non prova nulla. Ora: inventario conferma → matched; altrimenti _CkanResourceProbe chiede a resource_show sulla fonte CKAN (endpoint derivato dal base_url); senza probe → unknown.
  2. ckan_dataset — il matcher toglieva il placeholder e faceva startswith: funziona solo con la parte fissa in testa. Con {year}-nome la parte fissa è un suffisso e il match falliva sempre. Ora i placeholder sono wildcard (fullmatch).

Verifica

Repo checked matched missing unknown probe
open-mur 13 13 0 (era 10) 0 ckan 9
appalti-pubblici 13 13 0 0 http 1
indice-pa 9 9 0 0 http 9
open-consip 21 20 1 0 http 15

Il missing residuo di Consip è drift reale (consumi-in-convenzione-2023.csv → 404): la regola non nasconde i problemi veri, toglie solo quelli inventati.

open-mur: 10 missing → 0, con 9 resource confermate da resource_show e 1 package confermato dal matcher corretto.

Note per chi revisiona

  • Il probe CKAN è iniettato (resource_probe), come url_probe: drift.py resta puro, senza I/O. Il cablaggio sta in build.py.
  • resource_show è una richiesta per resource non confermata (open-mur: 9), con cache per run. 403/429/rete → non concludente, coerente con url_probe.
  • probe_pattern resta l'unico punto HTTP per gli URL; il probe CKAN è separato perché è un'API, non un file.
  • La tabella è la cosa da guardare in review: è l'unico posto dove è scritto cosa autorizza a dire "sparito".
Tipo: root fix
Problema reale: il drift assumeva un inventario completo. Per `ckan_resource`
  l'inventario tiene una distribuzione per item (i package ne hanno decine) e
  per `ckan_dataset` il matcher non gestiva i placeholder in testa → 10 falsi
  positivi su open-mur, e una classe pronta a ripetersi su ogni kind nuovo.
Contratto riusato/sostituito: la regola di prova sostituisce i rattoppi per
  kind; riusa il pattern del probe iniettato già introdotto in #437.
Codice rimosso/duplicazione: il fallback booleano non decide più da solo
  (`_state_for` consulta la tabella); `_match_ckan_dataset` perde l'euristica
  `startswith`.
Test aggiunti: 13 (ratchet sui kind, placeholder-first, 6 su `ckan_resource`,
  4 sul probe CKAN, 2 sull'endpoint).
Rischio residuo: una richiesta HTTP per resource non confermata per run.
Follow-up obbligatorio: rigenerare il catalog di open-mur (10 missing → 0).

…atch per kind

Il drift dichiarava `missing` quando un ref non si trovava nell'inventario,
assumendo che l'inventario descrivesse tutto ciò che la fonte pubblica. Non è
vero per 3 dei 5 kind, e ogni kind che poggia sull'assunzione perdeva:

- `ckan_resource`: l'inventario tiene UNA distribuzione per item, ma un
  package CKAN ne ha in media decine → open-mur segnava 9 resource "sparite"
  che invece esistono (verificato con `resource_show`);
- `ckan_dataset`: il matcher toglieva il placeholder e faceva `startswith`,
  che funziona solo con la parte fissa in testa: `{year}-nome` non matchava
  mai → 1 falso positivo su open-mur;
- `url`: già risolto con la verifica HTTP (#437), stesso principio.

Ora la regola è una sola, dichiarata in `drift.ABSENCE_EVIDENCE`: `missing`
solo con prova positiva di assenza — inventario **completo** per quel kind,
oppure verifica diretta dell'item. Un kind non dichiarato non può dichiarare
`missing`: al massimo `unknown`.

- drift.py: tabella `ABSENCE_EVIDENCE` (load-bearing in `_state_for`),
  `_match_ckan_dataset` con placeholder come wildcard, nuovo
  `_ckan_resource_state` (probe o `unknown`)
- build.py: `_CkanResourceProbe` — `resource_show` sull'endpoint CKAN della
  fonte, cache per run, UA di config; 403/429/rete → non concludente
- runbook: la regola della prova di assenza, con la tabella per kind

Misurato: open-mur **10 missing → 0** (13/13 matched, 9 via `resource_show`);
nessuna regressione su appalti-pubblici (13/13), indice-pa (9/9), e il drift
**reale** di Consip resta segnalato (1 missing, il 404 del 2023).

Verifiche: 452 passed / 2 skipped, mypy e ruff puliti.
Follow-up dei finding della review su #439.

- `_MATCHERS` non elenca più `ckan_resource` (kind a tre stati): ogni kind ha
  un solo percorso
- `_state_for` consulta `ABSENCE_EVIDENCE` come **primo** passo: un kind non
  dichiarato non entra nel check, e la prova dichiarata decide se l'assenza
  può diventare `missing`
- gate a **uguaglianza** (dichiarato ⇔ instradabile) invece che a
  sottoinsieme: copre anche "dichiarato ma non instradabile"
- nuovo test: un kind fuori tabella non entra nel check

Comportamento invariato, verificato su open-mur (13/13, 9 via resource_show),
appalti-pubblici (13/13) e open-consip (1 missing reale).

Verifiche: 453 passed / 2 skipped, mypy e ruff puliti.
@Gabrymi93
Gabrymi93 merged commit 07ec856 into main Oct 28, 2026
1 check passed
@Gabrymi93
Gabrymi93 deleted the fix/drift-positive-evidence branch October 28, 2026 18:56
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.

1 participant