Design tecnico per l'issue #176 (D5), sotto-fase dell'analisi #170. Approfondisce la sezione D5 di
copilota-open-data.md: layout dei motori, contratto degli endpoint, forma degli artefatti e backlog implementativo. Riusa l'esistente, niente riscritture.
Nuovo motore puro opendata_core/dataplan/, non estensione di maturity/.
Motivazione. La pianificazione (cosa aprire, in che ordine, con quale policy)
è un dominio distinto dalla valutazione di maturità (assess_entity misura ciò
che esiste). Fonderli inquinerebbe entrambi. dataplan/ riusa per iniezione i
motori esistenti (maturity/harvest+assess_entity, value/, maturity/hvd,
Data Quality Lab, convertitori, connettori nazionali, opendata-monitor) senza
duplicarli. Coerente con la Capability Layer (motori puri in opendata_core/,
orchestrazione + LLM nel backend, UI dedicata).
Stato di realizzazione:
| Componente | Stato |
|---|---|
dataplan/catalog.py + catalog_data.yaml (D1) |
✅ #172 (in main) |
dataplan/prioritize.py (D2) |
✅ #173 (in main) |
dataplan/policy.py (D3 — struttura policy/piano) |
⏳ #174 |
dataplan/privacy.py (D4 — checklist GDPR) |
⏳ #175 |
backend opendata_backend/dataplan/service.py + router |
⏳ (questo design) |
| UI percorso Copilota | ⏳ (D13 #184) |
opendata_core/dataplan/ # MOTORE PURO (no FastAPI/LLM/rete)
models.py # CandidateDataset, GiaAperto ✅
catalog.py # load_catalog(), catalog_by_area() ✅ (#172)
prioritize.py # prioritize() → RankedCandidate ✅ (#173)
policy.py # struttura Politica + Piano (template) ⏳ #174
privacy.py # checklist GDPR per famiglia ⏳ #175
opendata_backend/
dataplan/service.py # orchestrazione: harvest + adempimenti
# nazionali, iniezione catalogo/pesi/LLM,
# persistenza warehouse
routers/dataplan.py # GET/POST /dataplan/{istat}/... (auth+ratelimit)
L'LLM vive solo nel backend (via opendata_backend.llm.complete /
resolve_provider, R11), mai nei motori puri.
Tutti autenticati (Clerk / dev-bypass) + rate-limited come gli altri. {istat} =
codice ISTAT del comune (6 cifre). Tutti fail-safe: fonti live non raggiungibili
→ sezione marcata "non disponibile", mai 500.
Fotografia "quanto sei aperto oggi", a costo zero per l'ente.
- Riusa:
maturity.harvest+assess_entity(portale regionale del comune); sonda gli adempimenti nazionali già aperti (BDAP/SIOPE, ANAC, OpenCoesione/ OpenPNRR, ISTAT) via i connettori esistenti. - Response:
Il catalogo D1 contestualizzato: candidati con fonte/HVD/privacy/sforzo e flag "già presente sul portale del comune?" (incrociato con la diagnosi).
- Riusa:
dataplan.load_catalog(); incrocio con l'harvest della diagnosi. - Response:
{ "istat", "candidati": [CandidateDataset + {gia_pubblicato: bool}], ... }
Il cuore: candidati prioritizzati (D2) + lotto quick-win + piano di pubblicazione.
- Riusa:
dataplan.prioritize(catalogo, reuse_boost=…)dovereuse_boostderiva dalla domanda di riuso non soddisfatta del report Territorio (anello Fase 5);policy.build_piano()(#174) per cadenze/ruoli/metadati DCAT. - Response:
{
"istat": "072021",
"ranking": [ { "candidate": {…}, "valore": 75, "sforzo": 1,
"quadrante": "quick_win", "motivazione": "…" }, … ],
"quick_win": [ /* sottoinsieme quadrante=quick_win */ ],
"piano": { "voci": [ { "id", "cadenza", "licenza", "ufficio",
"metadati_dcat": {…} } ] }
}Bozza della Politica Open Data dell'ente (atto di indirizzo).
- LLM (R11) + fallback offline deterministico (template compilato).
- Body:
{ "ente_nome"?, "licenza_preferita"? }. Response:{ "titolo", "sezioni": [{ "titolo", "testo" }], "licenza", "generato_con": "llm|offline" }
"Export brief" operativo per UN dataset (D9 #180): istruzione per l'ufficio su come estrarre/de-identificare/pubblicare quel dato.
- Body:
{ "candidate_id": "rifiuti-differenziata" }. - Riusa:
privacy.checklist()(#175),quality/dcat.generate_dcat, convertitori (#101/#157). LLM per la prosa, fallback offline. - Response:
{ "candidate_id", "passi": [str], "privacy": {regola, soglia}, "metadati_dcat": {…}, "ufficio": "…", "generato_con": "llm|offline" }
- Diagnosi / Inventario / Piano: JSON (sopra), consumati dalla UI e ri-esportabili (CSV/PDF) come le scorecard di maturità.
- Politica: documento a sezioni (Markdown/PDF) — bozza di delibera.
- Brief: scheda operativa per-dataset (passi + privacy + metadati + ufficio).
- Persistenza (warehouse
opendata.*, DDL dialect-aware R4): baseline inmaturity_assessments(riuso); piano/politica in una nuovadataplan_plans(istat, generato_il, ranking_jsonb, piano_jsonb) — append-only, storicizza l'avanzamento (come gli snapshot civici).
Sempre via resolve_provider, con fallback offline deterministico:
- redazione Politica e Piano in linguaggio amministrativo;
- export brief per dataset (prosa dei passi + caveat privacy);
- (opz.) classificazione semantica dei candidati non ovvi.
Mai LLM: scoring maturità/valore, prioritizzazione (D2), stima HVD, qualità ISO 25012 — restano nei motori puri, spiegabili e riproducibili.
Nuova sezione/percorso guidato (macchina a stati zero→maturo):
- Diagnosi — inserisci il comune → "quanto sei aperto oggi".
- Inventario + Piano — matrice valore×sforzo, lotto quick-win evidenziato.
- Politica — genera la bozza di delibera.
- Produzione — per un dataset: carica l'estratto → Data Quality Lab + convertitori + check privacy → dataset pronto (il dato non è custodito dalla piattaforma: si pubblica sul portale ufficiale).
- Valore & Mantenimento — mostra l'analisi sbloccata + attiva i watch di
opendata-monitor.
R14: alla realizzazione, README + copy pagina + diagramma del percorso.
| Ordine | Issue | Deliverable |
|---|---|---|
| ✅ | #172 | catalogo D1 |
| ✅ | #173 | prioritizzazione D2 |
| 1 | #174 | dataplan/policy.py — Politica + Piano + template DCAT |
| 2 | #175 | dataplan/privacy.py — checklist GDPR per famiglia |
| 3 | (nuova) | backend dataplan/service.py + router + migrazione dataplan_plans |
| 4 | #180 | endpoint/artefatto brief per dataset |
| 5 | #181 | routing dataset → ufficio → ruolo (arricchisce piano/brief) |
| 6 | #184 | percorso UI (accompagnamento attivo) |
| 7 | #179 / #187 | validazione + KPI sul pilota Gioia del Colle |
| — | #186 | (scope) pubblicazione su CKAN via write API, con gate umano |
- Pivot: gli open data sono l'unica fonte ufficiale; il Copilota non custodisce copie (niente upload/KG surrogato) — assiste la produzione, il dato vive sul portale. I dataset mancanti = domanda di riuso non soddisfatta resa azionabile.
- Regole: R1 (build context), R4 (schema
opendata, DDL dialect-aware), R7 (auth), R11 (LLM via resolve_provider + fallback), R14 (docs con la feature).
- Analisi madre:
copilota-open-data.md(#170). - Motori realizzati:
opendata_core/dataplan/{catalog,prioritize}.py(#172/#173). - Riuso:
opendata_core/{maturity,value}/,maturity/{harvest,hvd}.py,opendata_core/quality/(Data Quality Lab + convertitori),opencoesione/, connettori MCP (bdap/openpnrr/istat/ckan-ANAC). Docs:architettura.md,data-model.md.
{ "istat": "072021", "comune": "Gioia del Colle", "pubblicato": { // da assess_entity (baseline ODM) "n_dataset": 3, "overall": 28.0, "level": "Beginner", "dimensioni": { "policy": 20, "portal": 30, "quality": 25, "impact": 15 } }, "gia_aperto_nazionale": [ // adempimenti già coperti (link, non produzione) { "id": "bilancio-siope", "fonte": "BDAP/SIOPE", "disponibile": true }, { "id": "appalti-anac", "fonte": "ANAC", "disponibile": true } ], "sources": [ /* URL citabili */ ] }