Skip to content

Commit 03ab14b

Browse files
aborrusoclaude
andauthored
fix(senato-votes): derive legislature from --ddl-uri, not default 19 (#61)
* fix(senato-votes): derive legislature from --ddl-uri, not default 19 L'URI Senato dati.senato.it/ddl/{N} non codifica la legislatura, ma il DDL espone osr:legislatura. Prima il filtro osr:legislatura era cablato al default 19: --ddl-uri di un DDL di leg. diversa, senza --legislature esplicito, dava un falso negativo silenzioso ("Nessuna votazione trovata"). Ora la legislatura effettiva è derivata dal DDL e usata in tutte le query, con fallback all'input. Allinea il comportamento a votes --bill-code (Camera). +1 test di integrazione. Chiude #60 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(log): headline accurata — legislatura derivata dal DDL, non dall'URI L'URI dati.senato.it/ddl/{N} non codifica la legislatura (identifica solo il DDL); la legislatura è letta da osr:legislatura del DDL. Allinea l'headline al corpo della voce. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: rimuovi job live_integration (sempre rosso, zero segnale) I test live interrogano dati.camera.it/dati.senato.it, che bloccano in modo sistematico i runner GitHub (anti-bot/IP CI): il job falliva sempre con 'SPARQL request failed after 5 attempts', senza distinguere una vera regressione dal blocco di rete. Rete di sicurezza: testing live in locale prima di ogni release (RELEASING.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 94c7a91 commit 03ab14b

4 files changed

Lines changed: 52 additions & 28 deletions

File tree

.github/workflows/ci.yml

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,11 @@ jobs:
3535
- name: Deterministic tests (no live SPARQL)
3636
run: npm test -- --run --exclude src/tools/tools.test.ts --exclude src/tools/sparql.test.ts
3737

38-
live_integration:
39-
if: github.event_name == 'pull_request'
40-
needs: ci
41-
continue-on-error: true
42-
runs-on: ubuntu-latest
43-
timeout-minutes: 35
44-
steps:
45-
- name: Checkout
46-
uses: actions/checkout@v4
47-
48-
- name: Setup Node.js
49-
uses: actions/setup-node@v4
50-
with:
51-
node-version: "20"
52-
cache: npm
53-
54-
- name: Install dependencies
55-
run: npm ci
56-
57-
- name: Live integration tests (Camera/Senato endpoints)
58-
run: npm test -- --run src/tools/sparql.test.ts src/tools/tools.test.ts
38+
# I test live (src/tools/tools.test.ts, src/tools/sparql.test.ts) interrogano
39+
# gli endpoint LOD dati.camera.it / dati.senato.it, che rifiutano in modo
40+
# sistematico le richieste dai runner GitHub Actions (anti-bot / IP CI): il
41+
# job falliva SEMPRE con "SPARQL request failed after 5 attempts", senza mai
42+
# distinguere una vera regressione dal blocco di rete. Rimosso perché rumore
43+
# a zero segnale. La rete di sicurezza è il testing live in locale prima di
44+
# ogni release (RELEASING.md impone `npm test -- --run`, dove gli endpoint
45+
# rispondono).

LOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## 2026-07-11
44

5+
- **fix `senato-votes --ddl-uri` — legislatura derivata dal DDL (non dal default 19)** (#60, branch `fix/senato-votes-ddl-legislature`). Prima `--ddl-uri` di un DDL di legislatura ≠ 19, senza `--legislature` esplicito, dava un **falso negativo silenzioso** ("Nessuna votazione trovata"): il filtro `osr:legislatura` cablato al default 19 escludeva le sedute del DDL. L'URI Senato `dati.senato.it/ddl/{N}` non codifica la legislatura, ma il DDL espone `osr:legislatura`: ora quando c'è `--ddl-uri` la legislatura effettiva è derivata dal DDL (una query `<ddlUri> osr:legislatura ?leg`) e usata in tutte le query, con fallback all'input. Allinea il comportamento a `votes --bill-code` (Camera) che di default non vincola la legislatura. Ripro: DDL 52988 (S.1811, decreto lockdown DL 19/2020, leg.18) ora torna il voto `18-219-1` anche senza `--legislature`, con `rss_url` corretto su `/18`. +1 test di integrazione. Emerso dal run news-driven `docs/news-agent/2026-07-11_18-11.md`.
56
- **v0.25.0** — release minor. Raccoglie **tutte** le PR mergiate dopo la v0.24.2 (#54#59). Due nuove capacità utente: (1) **`speeches` filtro temporale** `--date-from`/`--date-to` + colonna `date` su entrambe le camere (#54, chiude #37); (2) **geografia di nascita normalizzata** `birth_province`/`birth_country`/`birth_region` su `senators`/`deputies`, regione derivata da SITUAS/ISTAT via `opensituas`, copertura 100% (#59, chiude #24/#23; corretto l'assunto errato "Senato solo città"). Più fix: **`bill-progress --branch C`** ora dà la timeline Camera (#55, chiude #41) e i follow-up di robustezza SPARQL/paginazione (#56/#57/#58). 43 tool invariati, 158/158 test verdi. Dettaglio nelle voci sotto.
67
- **feat geografia di nascita normalizzata — provincia, nazione, regione su entrambe le camere** (#24/#23, branch `feat/birth-geography-province-region`). Corretto un **assunto sbagliato** (era anche nel codice e nel wiki): il Senato **non** espone "solo la città". Ri-verificato con `COUNT` su tutta la leg.19 (254 senatori): `osr:cittaNascita` 254/254, `osr:provinciaNascita` 250/254 (i 4 mancanti sono i nati all'estero), `osr:nazioneNascita` 254/254 (`Italia` o stato estero). `senators list` ora restituisce `birth_province`, `birth_country`, `birth_region`. **Regione derivata** una volta sola in `src/core/province-region.ts` (modulo TS bundle-safe) da SITUAS/ISTAT report 64 via CLI `opensituas`, date-accurate (union snapshot 2010 + 2020 + più recente per le province soppresse/rinominate, + 3 alias per le forme brevi del Senato): copertura 100% (0 province non risolte). **Camera** (`deputies list`): la geografia è solo uno slug nell'URI (`comune_provincia_regione`), non triple — ora decodificata in `birth_city`/`birth_province`/`birth_country`/`birth_region` con disambiguazione dello slug a 2 parti (Valle d'Aosta / Trentino-Alto Adige `comune_regione` vs estero `comune_stato`) e `birth_region` portata alla **stessa** forma canonica del Senato (verificato: "Veneto" identico tra camere). `birth_province`/`birth_country` restano nella forma nativa di ciascuna fonte (nessun canonicalizzatore province cross-camera). Wiki `dati-anagrafici.md`/`index.md` corretti; nota ai gestori Camera (geografia non strutturata) in `docs/note-gestori-lod/`. +10 test unitari (risorsa provincia→regione + parser slug Camera), 158/158 verdi.
78
- **follow-up residuale commenti PR #56/#57 (`bill-progress`)**: migliorato l'hint del ramo Camera su risultato vuoto (ora segnala che i filtri `keyword`/intervallo di date o la paginazione `limit`/`offset` possono escludere tutte le righe), ordinamento timeline Camera reso stabile per paginazione con `?st` come tie-breaker in `ORDER BY` (non proiettato in `SELECT DISTINCT`, che ne altererebbe il dedup), escape robusto dei literal SPARQL per `keyword` (Senato + Camera) con helper dedicato che escapa solo `\ " newline CR` per la grammatica STRING_LITERAL2 (non `JSON.stringify`, che emette `\uXXXX` per i controlli, rifiutato da alcuni parser), e descrizioni `dateFrom/dateTo` chiarite sui due significati (presentazione Senato vs data stato-iter Camera).

src/tools/senato-votes.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,24 @@ export const senatoVotesTool: Tool<typeof inputSchema> = {
138138
? `FILTER(?date <= "${input.dateTo}"^^xsd:date)`
139139
: "";
140140

141+
// --ddl-uri identifica univocamente il provvedimento e quindi la sua
142+
// legislatura. L'URI (dati.senato.it/ddl/{N}) non la codifica, ma la
143+
// proprietà osr:legislatura del DDL sì. Senza risolverla, il filtro di
144+
// legislatura di default (19) esclude in silenzio i DDL di altre
145+
// legislature (falso negativo: "Nessuna votazione trovata" pur essendo
146+
// l'URI già non ambiguo). La deriviamo dal DDL e la usiamo in tutte le
147+
// query; fallback all'input se il DDL non la espone.
148+
let effectiveLeg = input.legislature;
149+
if (input.ddlUri) {
150+
const legRows = flattenBindings(
151+
await snQuery(
152+
`${OSR_PREFIXES}\nSELECT ?leg WHERE { <${input.ddlUri}> osr:legislatura ?leg } LIMIT 1`,
153+
),
154+
);
155+
const resolved = Number(legRows[0]?.leg);
156+
if (Number.isInteger(resolved) && resolved > 0) effectiveLeg = resolved;
157+
}
158+
141159
// Supplemento fiducie per --keyword: le fiducie non hanno osr:oggetto,
142160
// quindi ?ddlTitolo resta unbound e il filtro keyword in SPARQL le esclude
143161
// anche quando il tema cercato è nel titolo del DDL citato per numero nel
@@ -160,7 +178,7 @@ export const senatoVotesTool: Tool<typeof inputSchema> = {
160178
SELECT DISTINCT ?v ?date ?numero ?tipo ?label ?esito
161179
?favorevoli ?contrari ?astenuti ?presenti ?votanti ?maggioranza
162180
WHERE {
163-
?v a osr:Votazione ; osr:legislatura ${input.legislature} ; osr:seduta ?s .
181+
?v a osr:Votazione ; osr:legislatura ${effectiveLeg} ; osr:seduta ?s .
164182
OPTIONAL { ?s osr:dataSeduta ?date }
165183
?v rdfs:label ?label .
166184
FILTER(CONTAINS(LCASE(STR(?label)), "fiducia") &&
@@ -202,7 +220,7 @@ WHERE {
202220
const filter = nums.map((n) => `STR(?f) = "S.${n}"`).join(" || ");
203221
const fq = `${OSR_PREFIXES}
204222
SELECT ?ddl ?f ?titolo WHERE {
205-
?ddl a osr:Ddl ; osr:legislatura ${input.legislature} ; osr:fase ?f .
223+
?ddl a osr:Ddl ; osr:legislatura ${effectiveLeg} ; osr:fase ?f .
206224
OPTIONAL { ?ddl osr:titolo ?titolo }
207225
FILTER(${filter})
208226
}`;
@@ -232,7 +250,7 @@ SELECT ?ddl ?f ?titolo WHERE {
232250
if (input.ddlUri) {
233251
const datesQuery = `${OSR_PREFIXES}
234252
SELECT DISTINCT ?date WHERE {
235-
?v a osr:Votazione ; osr:legislatura ${input.legislature} ; osr:seduta ?s .
253+
?v a osr:Votazione ; osr:legislatura ${effectiveLeg} ; osr:seduta ?s .
236254
?s osr:dataSeduta ?date .
237255
?v osr:oggetto ?o . ?o osr:relativoA <${input.ddlUri}> .
238256
}`;
@@ -258,7 +276,7 @@ SELECT DISTINCT ?date WHERE {
258276
// a --ddl-uri, che richiede la risoluzione via fallback: in quel caso il
259277
// conteggio è la cardinalità del result-set filtrato (calcolata sotto).
260278
if (input.countOnly && !input.ddlUri) {
261-
const countWhere = [`?v a osr:Votazione ; osr:legislatura ${input.legislature} .`];
279+
const countWhere = [`?v a osr:Votazione ; osr:legislatura ${effectiveLeg} .`];
262280
if (ddlTopicPattern) countWhere.push(ddlTopicPattern);
263281
if (needsLabel) countWhere.push(`?v rdfs:label ?label . ${labelFilter}`);
264282
if (input.dateFrom || input.dateTo)
@@ -281,7 +299,7 @@ SELECT DISTINCT ?date WHERE {
281299
?favorevoli ?contrari ?astenuti ?presenti ?votanti ?maggioranza
282300
?ddl ?oggetto
283301
WHERE {
284-
?v a osr:Votazione ; osr:legislatura ${input.legislature} ; osr:seduta ?s .
302+
?v a osr:Votazione ; osr:legislatura ${effectiveLeg} ; osr:seduta ?s .
285303
OPTIONAL { ?s osr:dataSeduta ?date }
286304
${ddlTopicPattern}
287305
${needsLabel ? `?v rdfs:label ?label . ${labelFilter}` : "OPTIONAL { ?v rdfs:label ?label }"}
@@ -354,7 +372,7 @@ WHERE {
354372
const filter = nums.map((n) => `STR(?f) = "S.${n}"`).join(" || ");
355373
const fbQuery = `${OSR_PREFIXES}
356374
SELECT ?ddl ?f WHERE {
357-
?ddl a osr:Ddl ; osr:legislatura ${input.legislature} ; osr:fase ?f .
375+
?ddl a osr:Ddl ; osr:legislatura ${effectiveLeg} ; osr:fase ?f .
358376
FILTER(${filter})
359377
}`;
360378
const byFase = new Map<string, string>();
@@ -478,7 +496,7 @@ SELECT ?ddl ?f WHERE {
478496
const rows = values.map((v) => {
479497
const ddlUris = splitMulti(v.ddl_uri);
480498
const ddlHtmlUrls = ddlUris.map((u) => actHtmlUrl(u)).filter(Boolean);
481-
const rssUrls = ddlUris.map((u) => ddlRssUrl(u, input.legislature)).filter(Boolean);
499+
const rssUrls = ddlUris.map((u) => ddlRssUrl(u, effectiveLeg)).filter(Boolean);
482500
return {
483501
...v,
484502
ddl_count: String(ddlUris.length),

src/tools/tools.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,24 @@ describe("Senato tools", () => {
829829
expect(fiducia!.bill_number).toBe("1944");
830830
}, 30000);
831831

832+
it("senato-votes: --ddl-uri resolves the legislature from the DDL, not the default 19", async () => {
833+
// DDL 52988 = S.1811 (decreto lockdown DL 19/2020), legislatura 18. L'URI
834+
// dati.senato.it/ddl/{N} non codifica la legislatura: senza risolverla dal
835+
// DDL, il filtro di default (19) darebbe un falso negativo silenzioso.
836+
// Nessun --legislature passato: deve comunque trovare il voto di leg.18.
837+
const result = await senatoVotesTool.execute({
838+
legislature: 19,
839+
ddlUri: "http://dati.senato.it/ddl/52988",
840+
limit: 100,
841+
offset: 0,
842+
});
843+
const voto = result.rows.find((r) => r.uri.endsWith("/18-219-1"));
844+
expect(voto).toBeDefined();
845+
expect(voto!.ddl_uri).toBe("http://dati.senato.it/ddl/52988");
846+
// rss_url deve puntare alla legislatura del DDL (18), non al default 19.
847+
expect(voto!.rss_url).toContain("/52988/18");
848+
}, 30000);
849+
832850
it("senato-votes: backfills bill_number from the resolved DDL on generic labels", async () => {
833851
// 19-376-2 (Corte dei Conti): label "Votazione finale" senza numero, ma
834852
// ddl_uri risolto via osr:relativoA → bill_number = fase S.1457.

0 commit comments

Comments
 (0)