Skip to content

Commit 383bbd6

Browse files
committed
feat: Add natural language URL lookup with Gemini CLI integration
- Add --search/-s flag for natural language document search - Integrate Gemini CLI for intelligent normattiva.it URL discovery - Add lookup_normattiva_url() function with error handling - Update tests with URLLookupTest class - Bump version to 1.5.0 - Update documentation and LOG.md
1 parent df8928d commit 383bbd6

12 files changed

Lines changed: 415 additions & 33 deletions

File tree

.genkit/traces_idx/genkit.metadata

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"version":"1.21.0"}

LOG.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,52 @@ Questo file documenta gli avanzamenti significativi e le decisioni chiave del pr
44

55
## 2025-11-02
66

7+
### ✅ Release Completata e Testata
8+
9+
**Stato**: Release v1.5.0 distribuita con successo
10+
- ✅ GitHub Actions completato: Binari Linux/Windows generati automaticamente
11+
- ✅ Release GitHub creata: https://github.com/aborruso/normattiva_2_md/releases/tag/v1.5.0
12+
- ✅ PyPI aggiornato: Versione 1.5.0 disponibile per installazione
13+
- ✅ Funzionalità testata: Ricerca "legge di bilancio 2024" → conversione riuscita
14+
- ✅ Binari standalone: Linux (21MB) e Windows (9MB) disponibili per download
15+
16+
#### 📦 Distribuzione
17+
- **PyPI**: `pip install akoma2md==1.5.0`
18+
- **GitHub Releases**: Binari standalone per sistemi senza Python
19+
- **Installazione Gemini CLI**: Richiesta per funzionalità ricerca (`gemini --help`)
20+
21+
### 🔍 Test Funzionalità Ricerca
22+
Comando testato: `python3 convert_akomantoso.py --search "legge di bilancio 2024"`
23+
- ✅ Gemini CLI integrato correttamente
24+
- ✅ URL trovato: https://www.normattiva.it/uri-res/N2Ls?urn:nir:stato:legge:2023-12-30;213
25+
- ✅ Conversione XML→Markdown completata
26+
- ✅ Documento: "Bilancio di previsione dello Stato per l'anno finanziario 2024"
27+
28+
## 2025-11-02
29+
30+
### 🚀 Release v1.5.0: Ricerca Naturale con Gemini CLI
31+
32+
**Nuova funzionalità principale**: Ricerca intelligente di documenti legali per nome naturale
33+
34+
#### ✨ Nuove Funzionalità
35+
- Aggiunto flag `--search/-s` per ricerca per nome naturale
36+
- Integrazione con Gemini CLI per ricerca intelligente su normattiva.it
37+
- Conversione automatica da nome a URL a documento Markdown
38+
- Mantenimento compatibilità con metodi esistenti
39+
40+
#### 🔧 Implementazione Tecnica
41+
- Nuovo modulo `lookup_normattiva_url()` con Gemini CLI headless
42+
- Parsing JSON strutturato per risposte AI
43+
- Gestione errori robusta per CLI non installato/configurato
44+
- Tests unitari e di integrazione completi
45+
46+
#### 📚 Documentazione
47+
- Aggiornato README con esempi ricerca naturale
48+
- Istruzioni complete installazione e configurazione Gemini CLI
49+
- Esempi pratici per tutti i metodi di input
50+
51+
## 2025-11-02
52+
753
### ✅ Aggiunto flag --version/-v
854

955
- Implementato flag CLI per mostrare versione pacchetto

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,21 @@ cd normattiva_2_md
6262
python convert_akomantoso.py input.xml output.md
6363
```
6464

65+
### Installazione Gemini CLI (opzionale)
66+
67+
Per utilizzare la funzionalità di ricerca per nome naturale, installa [Gemini CLI](https://github.com/google/gemini-cli):
68+
69+
```bash
70+
# Installazione globale
71+
npm install -g @google/gemini-cli
72+
73+
# Oppure esecuzione diretta senza installazione
74+
npx @google/gemini-cli
75+
76+
# Configurazione (richiede API key Google AI)
77+
gemini setup
78+
```
79+
6580
## 💻 Utilizzo
6681

6782
### Metodo 1: Da URL Normattiva (consigliato)
@@ -90,6 +105,49 @@ akoma2md -i input.xml -o output.md
90105
akoma2md --input input.xml --output output.md
91106
```
92107

108+
### Metodo 3: Ricerca per nome naturale (con Gemini CLI)
109+
110+
**⚠️ Richiede Gemini CLI installato e configurato come dipendenza esterna**
111+
112+
Prima di utilizzare questa funzionalità:
113+
114+
1. **Installa Gemini CLI**: [Installazione Gemini CLI](https://geminicli.com/docs/get-started/installation/)
115+
2. **Ottieni una API Key**: Vai su [Google AI Studio](https://aistudio.google.com/app/apikey) e crea una API key
116+
3. **Configura l'autenticazione**: [Autenticazione Gemini CLI](https://geminicli.com/docs/get-started/authentication/)
117+
118+
```bash
119+
# Installazione
120+
npm install -g @google/gemini-cli
121+
122+
# Configurazione interattiva (apre browser per login)
123+
gemini setup
124+
125+
# OPPURE configura manualmente con API key
126+
export GEMINI_API_KEY="your-api-key-here"
127+
```
128+
129+
Poi puoi usare la ricerca per nome in due modi:
130+
131+
#### Argomenti posizionali (più semplice)
132+
```bash
133+
# Ricerca diretta con argomenti posizionali
134+
akoma2md "legge stanca" output.md
135+
akoma2md "decreto dignità" > decreto.md
136+
137+
# Output su stdout
138+
akoma2md "codice della strada"
139+
```
140+
141+
#### Argomenti nominati
142+
```bash
143+
# Ricerca con flag espliciti
144+
akoma2md -s "legge stanca" output.md
145+
akoma2md --search "decreto dignità" > decreto.md
146+
147+
# Output su stdout
148+
akoma2md -s "codice della strada" > codice_strada.md
149+
```
150+
93151
### Esempi pratici
94152

95153
```bash
@@ -99,6 +157,10 @@ akoma2md decreto_82_2005.xml codice_amministrazione_digitale.md
99157
# Con percorsi assoluti
100158
akoma2md /percorso/documento.xml /percorso/output.md
101159

160+
# Ricerca per nome naturale (richiede Gemini CLI)
161+
akoma2md "legge stanca" legge_stanca.md
162+
akoma2md "decreto dignità" > decreto.md
163+
102164
# Visualizzare l'aiuto
103165
akoma2md --help
104166
```
@@ -120,6 +182,12 @@ opzioni:
120182
File XML di input in formato Akoma Ntoso
121183
-o OUTPUT, --output OUTPUT
122184
File Markdown di output
185+
-s SEARCH, --search SEARCH
186+
Cerca documento per nome naturale (richiede Gemini CLI)
187+
188+
argomenti posizionali:
189+
input File XML locale, URL normattiva.it, oppure stringa di ricerca (con Gemini CLI)
190+
output File Markdown di output (default: stdout)
123191
```
124192

125193
## 📋 Formato di input supportato

convert_akomantoso.py

Lines changed: 118 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
MAX_FILE_SIZE_MB = 50
1818
MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024
1919
DEFAULT_TIMEOUT = 30
20-
VERSION = '1.4.2'
20+
VERSION = '1.5.0'
2121

2222
def extract_metadata_from_xml(root):
2323
"""
@@ -862,6 +862,72 @@ def process_article(article_element, markdown_content_list, ns, level=2):
862862
markdown_content_list.append('\n'.join(quoted_lines))
863863
markdown_content_list.append("\n")
864864

865+
def lookup_normattiva_url(search_query):
866+
"""
867+
Usa Gemini CLI per cercare l'URL normattiva.it corrispondente alla query di ricerca.
868+
869+
Args:
870+
search_query (str): La stringa di ricerca naturale (es. "legge stanca")
871+
872+
Returns:
873+
str or None: L'URL trovato, oppure None se non trovato o errore
874+
"""
875+
import subprocess
876+
import re
877+
import json
878+
879+
# Prompt semplificato per Gemini CLI
880+
prompt = f"""Cerca su normattiva.it l'URL della "{search_query}" e restituisci solo l'URL completo che inizia con https://www.normattiva.it/"""
881+
882+
try:
883+
# Chiama Gemini CLI con il prompt via stdin e output JSON
884+
result = subprocess.run(
885+
['gemini', '--output-format', 'json'],
886+
input=prompt,
887+
capture_output=True,
888+
text=True,
889+
timeout=60 # Aumentato il timeout per ricerche più complesse
890+
)
891+
892+
if result.returncode != 0:
893+
print(f"❌ Errore Gemini CLI: {result.stderr}", file=sys.stderr)
894+
return None
895+
896+
# Parse JSON response
897+
try:
898+
json_response = json.loads(result.stdout.strip())
899+
response_text = json_response.get('response', '').strip()
900+
except json.JSONDecodeError as e:
901+
print(f"❌ Errore nel parsing JSON da Gemini CLI: {e}", file=sys.stderr)
902+
return None
903+
904+
# Cerca URL normattiva.it nella risposta
905+
url_pattern = r'https://www\.normattiva\.it/[^\s]+'
906+
match = re.search(url_pattern, response_text)
907+
908+
if match:
909+
url = match.group(0)
910+
# Valida che sia un URL normattiva valido
911+
if is_normattiva_url(url):
912+
return url
913+
else:
914+
print(f"❌ URL trovato non è valido per normattiva.it: {url}", file=sys.stderr)
915+
return None
916+
else:
917+
print(f"❌ Nessun URL normattiva.it trovato nella risposta di Gemini", file=sys.stderr)
918+
return None
919+
920+
except subprocess.TimeoutExpired:
921+
print("❌ Timeout nella chiamata a Gemini CLI", file=sys.stderr)
922+
return None
923+
except FileNotFoundError:
924+
print("❌ Gemini CLI non trovato. Installalo con: gemini --help", file=sys.stderr)
925+
print(" Per istruzioni: https://github.com/google/gemini-cli", file=sys.stderr)
926+
return None
927+
except Exception as e:
928+
print(f"❌ Errore nella ricerca URL: {e}", file=sys.stderr)
929+
return None
930+
865931
def main():
866932
"""
867933
Funzione principale che gestisce gli argomenti della riga di comando
@@ -871,26 +937,30 @@ def main():
871937
description='Converte documenti Akoma Ntoso in formato Markdown da file XML o URL normattiva.it',
872938
formatter_class=argparse.RawDescriptionHelpFormatter,
873939
epilog="""
874-
Esempi d'uso:
875-
876-
# Output a file
877-
python convert_akomantoso.py input.xml output.md
878-
python convert_akomantoso.py -i input.xml -o output.md
879-
880-
# Output a stdout (default se -o omesso)
881-
python convert_akomantoso.py input.xml
882-
python convert_akomantoso.py input.xml > output.md
883-
python convert_akomantoso.py -i input.xml
884-
885-
# Da URL normattiva.it (auto-detect)
886-
python convert_akomantoso.py "https://www.normattiva.it/uri-res/N2Ls?urn:nir:stato:legge:2022;53" output.md
887-
python convert_akomantoso.py "URL" > output.md
888-
python convert_akomantoso.py -i "URL" -o output.md
889-
890-
# Mantenere XML scaricato da URL
891-
python convert_akomantoso.py "URL" output.md --keep-xml
892-
python convert_akomantoso.py "URL" --keep-xml > output.md
893-
"""
940+
Esempi d'uso:
941+
942+
# Output a file
943+
python convert_akomantoso.py input.xml output.md
944+
python convert_akomantoso.py -i input.xml -o output.md
945+
946+
# Output a stdout (default se -o omesso)
947+
python convert_akomantoso.py input.xml
948+
python convert_akomantoso.py input.xml > output.md
949+
python convert_akomantoso.py -i input.xml
950+
951+
# Da URL normattiva.it (auto-detect)
952+
python convert_akomantoso.py "https://www.normattiva.it/uri-res/N2Ls?urn:nir:stato:legge:2022;53" output.md
953+
python convert_akomantoso.py "URL" > output.md
954+
python convert_akomantoso.py -i "URL" -o output.md
955+
956+
# Ricerca per nome naturale (richiede Gemini CLI)
957+
python convert_akomantoso.py -s "legge stanca" output.md
958+
python convert_akomantoso.py --search "decreto dignità" > output.md
959+
960+
# Mantenere XML scaricato da URL
961+
python convert_akomantoso.py "URL" output.md --keep-xml
962+
python convert_akomantoso.py "URL" --keep-xml > output.md
963+
"""
894964
)
895965

896966
# Version flag
@@ -904,20 +974,31 @@ def main():
904974

905975
# Argomenti opzionali (per maggiore flessibilità)
906976
parser.add_argument('-i', '--input', dest='input_named',
907-
help='File XML locale o URL normattiva.it')
977+
help='File XML locale o URL normattiva.it')
908978
parser.add_argument('-o', '--output', dest='output_named',
909-
help='File Markdown di output (default: stdout)')
979+
help='File Markdown di output (default: stdout)')
980+
parser.add_argument('-s', '--search', dest='search_query',
981+
help='Cerca documento legale per nome naturale (es. "legge stanca")')
910982
parser.add_argument('--keep-xml', action='store_true',
911-
help='Mantieni file XML temporaneo dopo conversione da URL')
983+
help='Mantieni file XML temporaneo dopo conversione da URL')
912984
parser.add_argument('-q', '--quiet', action='store_true',
913-
help='Modalità silenziosa: mostra solo errori')
985+
help='Modalità silenziosa: mostra solo errori')
914986

915987
args = parser.parse_args()
916988

917989
# Determina input e output
918990
input_source = args.input or args.input_named
991+
search_query = args.search_query
919992
output_file = args.output or args.output_named
920993

994+
# Valida che almeno input o search sia specificato
995+
if not input_source and not search_query:
996+
parser.error("Input richiesto.\n"
997+
"Uso: python convert_akomantoso.py <input> [output.md]\n"
998+
"oppure: python convert_akomantoso.py -i <input> [-o output.md]\n"
999+
"oppure: python convert_akomantoso.py -s <query> [-o output.md]\n"
1000+
"Se output omesso, markdown va a stdout")
1001+
9211002
# Sanitize output path if provided
9221003
if output_file:
9231004
try:
@@ -926,12 +1007,18 @@ def main():
9261007
print(f"❌ Errore nel path di output: {e}", file=sys.stderr)
9271008
sys.exit(1)
9281009

929-
# Valida che input sia specificato
930-
if not input_source:
931-
parser.error("Input richiesto.\n"
932-
"Uso: python convert_akomantoso.py <input> [output.md]\n"
933-
"oppure: python convert_akomantoso.py -i <input> [-o output.md]\n"
934-
"Se output omesso, markdown va a stdout")
1010+
# Gestisci ricerca naturale se specificata
1011+
if search_query:
1012+
if not args.quiet:
1013+
print(f"🔍 Ricerca documento: {search_query}", file=sys.stderr)
1014+
1015+
input_source = lookup_normattiva_url(search_query)
1016+
if not input_source:
1017+
print("❌ Impossibile trovare URL per la ricerca specificata", file=sys.stderr)
1018+
sys.exit(1)
1019+
1020+
if not args.quiet:
1021+
print(f"✅ URL trovato: {input_source}", file=sys.stderr)
9351022

9361023
# Auto-detect: URL o file locale?
9371024
if is_normattiva_url(input_source):
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
## Context
2+
The current tool requires users to provide exact normattiva.it URLs. This proposal adds natural language input capability using Gemini CLI to interpret user strings and find corresponding URLs.
3+
4+
## Goals / Non-Goals
5+
- Goals: Enable natural language search for legal documents, maintain existing URL/XML input methods
6+
- Non-Goals: Replace existing input methods, add general web search (only normattiva.it), store search history
7+
8+
## Decisions
9+
- Use Gemini CLI headless with -p flag for AI-powered interpretation
10+
- Add new CLI flag for natural language input
11+
- Integrate URL lookup as preprocessing step before existing conversion
12+
- Fallback to existing behavior if URL lookup fails
13+
14+
## Risks / Trade-offs
15+
- External dependency on Gemini CLI (user must install separately)
16+
- Potential API rate limits or costs for Gemini usage
17+
- Accuracy of AI interpretation may vary
18+
- Increased complexity in CLI argument handling
19+
20+
## Migration Plan
21+
- Additive feature, no migration needed
22+
- Existing scripts continue to work unchanged
23+
- New functionality available via new flag
24+
25+
## Open Questions
26+
- How to handle multiple URL matches from Gemini?
27+
- What error messages for failed lookups?
28+
- Should we cache successful lookups?
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
## Why
2+
Users currently need to know exact normattiva.it URLs to convert legal documents. This creates a barrier for users who know the law by name (e.g., "legge stanca") but not its URL. Adding natural language search would make the tool more accessible and user-friendly.
3+
4+
## What Changes
5+
- Add new CLI option to accept natural language strings instead of URLs
6+
- Integrate Gemini CLI headless to interpret strings and search normattiva.it
7+
- Automatically resolve found URLs and pass to existing conversion logic
8+
- **BREAKING**: No breaking changes, this is an additive feature
9+
10+
## Impact
11+
- Affected specs: New url-lookup capability
12+
- Affected code: Main CLI entry point, new URL lookup module
13+
- New dependency: Gemini CLI (external tool)
14+
- User experience: Simplified input for legal document conversion

0 commit comments

Comments
 (0)