Skip to content

Commit 7da8335

Browse files
authored
fix camera keyword html entities (#64)
1 parent 507350a commit 7da8335

5 files changed

Lines changed: 85 additions & 3 deletions

File tree

LOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# LOG
22

3+
## 2026-07-13
4+
5+
- **fix keyword Camera con entità HTML**`bills --keyword` e `bill-progress --uri/--branch C --keyword` ora cercano anche la variante HTML-escaped della keyword (`criminalità``criminalità`). Il LOD Camera salva alcuni `rdfs:label`/`dc:title` con entità HTML ma la CLI decodifica l'output: prima una ricerca sul testo visibile (`--keyword "criminalità"`) non trovava atti il cui titolo mostrato contiene `criminalità organizzata`; funzionavano solo workaround come `criminalit` o `criminalità`. Aggiunto helper condiviso `htmlEntityKeywordVariants`, test di regressione su `ac19_2696`, build/tsc/test completi verdi (161/161).
6+
37
## 2026-07-12
48

59
- **v0.25.1** — release patch. Un solo fix funzionale dopo la v0.25.0: **`senato-votes --ddl-uri` ora deriva la legislatura dal DDL** invece di applicare il default 19 come filtro rigido (#60, PR #61). Prima, passando solo `--ddl-uri` di un provvedimento di legislatura ≠ 19, il comando dava un **falso negativo silenzioso** ("Nessuna votazione trovata"): un giornalista che cercava un voto Senato storico (es. il decreto lockdown DL 19/2020, S.1811, leg.18) non trovava nulla e poteva concludere erroneamente che il dato mancasse. Ora la legislatura effettiva è letta da `osr:legislatura` del DDL (l'URI `dati.senato.it/ddl/{N}` non la codifica) e usata in tutte le query — allineando il comportamento a `votes --bill-code` (Camera), che non vincola la legislatura. Aggiunti alla scoperta anche description e esempio `--help` con un DDL storico, e nota nella skill. Emerso dal run news-driven `docs/news-agent/2026-07-11_18-11.md` (era l'unico gap reale con impatto; gli altri due rilievi erano assenze source-side già documentate nel wiki, non gap CLI). Contestualmente rimosso dalla CI il job `live_integration` (sempre rosso dai runner GitHub: gli endpoint LOD bloccano gli IP CI — zero segnale; il testing live resta affidabile in locale). 43 tool invariati, 159/159 test verdi in locale.

src/core/html-entity-variants.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const HTML_ENTITY_BY_CHAR: Record<string, string> = {
2+
'"': "&quot;",
3+
"'": "&apos;",
4+
"&": "&amp;",
5+
"<": "&lt;",
6+
">": "&gt;",
7+
"\u2019": "&rsquo;",
8+
"\u2018": "&lsquo;",
9+
"\u201d": "&rdquo;",
10+
"\u201c": "&ldquo;",
11+
"\u2013": "&ndash;",
12+
"\u2014": "&mdash;",
13+
"\u2026": "&hellip;",
14+
"\u20ac": "&euro;",
15+
"\u00ab": "&laquo;",
16+
"\u00bb": "&raquo;",
17+
"\u00e0": "&agrave;",
18+
"\u00e8": "&egrave;",
19+
"\u00e9": "&eacute;",
20+
"\u00ec": "&igrave;",
21+
"\u00f2": "&ograve;",
22+
"\u00f9": "&ugrave;",
23+
};
24+
25+
const HTML_ENTITY_CHARS = /["'&<>\u2018\u2019\u201c\u201d\u2013\u2014\u2026\u20ac\u00ab\u00bb\u00e0\u00e8\u00e9\u00ec\u00f2\u00f9]/g;
26+
27+
/**
28+
* Camera LOD often stores title/label literals with HTML entities
29+
* (e.g. `criminalit&agrave;`) while CLI users search the decoded text
30+
* (`criminalità`). Return both forms so SPARQL filters can match raw data.
31+
*/
32+
export function htmlEntityKeywordVariants(keyword: string): string[] {
33+
const htmlEncoded = keyword.replace(
34+
HTML_ENTITY_CHARS,
35+
(char) => HTML_ENTITY_BY_CHAR[char] ?? char,
36+
);
37+
return [...new Set([keyword, htmlEncoded])];
38+
}

src/tools/bill-progress.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { snQuery, cdQuery } from "../core/client.js";
33
import { OSR_PREFIXES, OCD_PREFIXES } from "../core/prefixes.js";
44
import { flattenBindings } from "../core/flatten.js";
55
import { decodeHtml } from "../core/decode-html.js";
6+
import { htmlEntityKeywordVariants } from "../core/html-entity-variants.js";
67
import { ddlRssUrl } from "../core/html-url.js";
78
import { currentLegislature } from "../core/current-legislature.js";
89
import type { Tool } from "./types.js";
@@ -162,7 +163,9 @@ export const billProgressTool: Tool<typeof inputSchema> = {
162163
}
163164
if (input.keyword) {
164165
filters.push(
165-
`FILTER(CONTAINS(LCASE(STR(?titolo)), LCASE(${sparqlStringLiteral(input.keyword)})))`,
166+
`FILTER(${htmlEntityKeywordVariants(input.keyword)
167+
.map((variant) => `CONTAINS(LCASE(STR(?titolo)), LCASE(${sparqlStringLiteral(variant)}))`)
168+
.join(" || ")})`,
166169
);
167170
}
168171
if (input.number) {
@@ -254,7 +257,9 @@ async function cameraIterTimeline(
254257
const filters: string[] = [];
255258
if (opts.keyword) {
256259
filters.push(
257-
`FILTER(BOUND(?titolo) && CONTAINS(LCASE(STR(?titolo)), LCASE(${sparqlStringLiteral(opts.keyword)})))`,
260+
`FILTER(BOUND(?titolo) && (${htmlEntityKeywordVariants(opts.keyword)
261+
.map((variant) => `CONTAINS(LCASE(STR(?titolo)), LCASE(${sparqlStringLiteral(variant)}))`)
262+
.join(" || ")}))`,
258263
);
259264
}
260265
if (opts.dateFrom) {

src/tools/bills.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { cdQuery } from "../core/client.js";
33
import { flattenBindings } from "../core/flatten.js";
44
import { OCD_PREFIXES } from "../core/prefixes.js";
55
import { decodeHtml } from "../core/decode-html.js";
6+
import { htmlEntityKeywordVariants } from "../core/html-entity-variants.js";
67
import { actHtmlUrl } from "../core/html-url.js";
78
import type { Tool } from "./types.js";
89

@@ -85,7 +86,12 @@ export const billsTool: Tool<typeof inputSchema> = {
8586
: "";
8687
const keywordFilter =
8788
input.keyword !== undefined
88-
? `FILTER(CONTAINS(LCASE(STR(?label)), LCASE("${sparqlEscape(input.keyword)}")) || CONTAINS(LCASE(STR(?title)), LCASE("${sparqlEscape(input.keyword)}")))`
89+
? `FILTER(${htmlEntityKeywordVariants(input.keyword)
90+
.map(
91+
(variant) =>
92+
`(CONTAINS(LCASE(STR(?label)), LCASE("${sparqlEscape(variant)}")) || CONTAINS(LCASE(STR(?title)), LCASE("${sparqlEscape(variant)}")))`,
93+
)
94+
.join(" || ")})`
8995
: "";
9096
const typeFilter =
9197
input.type !== undefined

src/tools/tools.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,20 @@ describe("Camera tools", () => {
113113
expect(result.rows[0]).toHaveProperty("type");
114114
}, 30000);
115115

116+
it("bills: keyword matches decoded HTML entities in Camera titles", async () => {
117+
const result = await billsTool.execute({
118+
legislature: 19,
119+
keyword: "criminalità",
120+
limit: 10,
121+
offset: 0,
122+
});
123+
expect(
124+
result.rows.some(
125+
(r) => r.uri === "http://dati.camera.it/ocd/attocamera.rdf/ac19_2696",
126+
),
127+
).toBe(true);
128+
}, 30000);
129+
116130
it("votes: returns votes for legislature 19", async () => {
117131
const result = await votesTool.execute({ legislature: 19, limit: 3, offset: 0 });
118132
expect(result.rows.length).toBe(3);
@@ -554,6 +568,21 @@ describe("Senato tools", () => {
554568
expect(first.phase).toBe("C.302");
555569
}, 30000);
556570

571+
it("bill-progress: Camera keyword matches decoded HTML entities in atto titles", async () => {
572+
const result = await billProgressTool.execute({
573+
uri: "http://dati.camera.it/ocd/attocamera.rdf/ac19_2696",
574+
keyword: "criminalità",
575+
limit: 10,
576+
offset: 0,
577+
});
578+
expect(result.rows.length).toBeGreaterThan(0);
579+
expect(
580+
result.rows.every(
581+
(r) => r.ddl_uri === "http://dati.camera.it/ocd/attocamera.rdf/ac19_2696",
582+
),
583+
).toBe(true);
584+
}, 30000);
585+
557586
it("bill-progress: --number + --branch C returns the Camera iter timeline (#41)", async () => {
558587
// dl Covid Camera 2020: --number 2617 --branch C --legislature 18 deve dare
559588
// la timeline dell'atto Camera (più stati datati), non il record di rimando

0 commit comments

Comments
 (0)