openrndt 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,315 @@
1
+ Metadata-Version: 2.3
2
+ Name: openrndt
3
+ Version: 1.0.0
4
+ Summary: CLI Python per il Repertorio Nazionale dei Dati Territoriali (RNDT) — pensata per essere orchestrata da un'AI
5
+ Keywords: rndt,geodati,inspire,metadata,iso19115,iso19139,open-data,italy
6
+ Author: Andrea Borruso
7
+ Author-email: Andrea Borruso <aborruso@gmail.com>
8
+ License: MIT
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Scientific/Engineering :: GIS
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Dist: httpx>=0.28.1
19
+ Requires-Dist: typer>=0.24.1
20
+ Requires-Dist: rich>=14.3.3
21
+ Requires-Dist: tenacity>=9.1.4
22
+ Requires-Python: >=3.12
23
+ Project-URL: Issues, https://github.com/ondata/openrndt/issues
24
+ Project-URL: RNDT Portal, https://geodati.gov.it/geoportale/
25
+ Project-URL: RNDT REST API, https://geodati.gov.it/geoportale/eng/strumenti-en/rest-api
26
+ Project-URL: Repository, https://github.com/ondata/openrndt
27
+ Description-Content-Type: text/markdown
28
+
29
+ # openrndt
30
+
31
+ CLI Python e libreria per accedere al **Repertorio Nazionale dei Dati Territoriali (RNDT)** —
32
+ pensata per essere orchestrata da un'AI.
33
+
34
+ > Stato: v1.0 — read-only.
35
+
36
+ ## Cos'è il RNDT
37
+
38
+ Il [Repertorio Nazionale dei Dati Territoriali](https://geodati.gov.it/geoportale/) è
39
+ il catalogo ufficiale italiano dei metadati geografici (ISO 19115/19139). Espone REST
40
+ API per cercare e scaricare i metadati.
41
+
42
+ ## Installazione
43
+
44
+ ### Da PyPI
45
+
46
+ ```bash
47
+ uv tool install openrndt
48
+ # oppure, senza installazione persistente:
49
+ uvx openrndt --help
50
+ ```
51
+
52
+ > Non ancora pubblicato su PyPI (roadmap verso la v1.0). Nel frattempo installa da locale.
53
+
54
+ ### Da locale
55
+
56
+ ```bash
57
+ git clone https://github.com/ondata/openrndt.git
58
+ cd openrndt
59
+
60
+ # CLI globale: venv isolato, eseguibile in PATH
61
+ uv tool install .
62
+
63
+ # Aggiornamento dopo modifiche al codice
64
+ uv tool install --reinstall .
65
+
66
+ # Disinstallazione
67
+ uv tool uninstall openrndt
68
+ ```
69
+
70
+ ### Per sviluppo (modifiche con ricarica immediata)
71
+
72
+ ```bash
73
+ git clone https://github.com/ondata/openrndt.git
74
+ cd openrndt
75
+ uv sync
76
+ uv run openrndt --help
77
+ ```
78
+
79
+ ## Uso
80
+
81
+ ```bash
82
+ # Ricerca testuale
83
+ openrndt search --q "catasto" --num 5
84
+
85
+ # Filtro per bounding box (Piemonte sud)
86
+ openrndt search --q "cartografia" --bbox 7,44,8,45 --num 10
87
+
88
+ # Per categoria tematica ISO 19115
89
+ openrndt search --data-category planningCadastre --num 5
90
+
91
+ # Singolo metadato
92
+ openrndt get age:D_E973_MARSAGLIA
93
+
94
+ # XML ISO 19139 grezzo
95
+ openrndt get age:D_E973_MARSAGLIA --xml > meta.xml
96
+
97
+ # Codelist disponibili (no rete)
98
+ openrndt discover
99
+ ```
100
+
101
+ Il timeout HTTP per singolo tentativo è configurabile con `--timeout` (default 30s);
102
+ con i retry su timeout/5xx (3 tentativi) il caso peggiore è ~3x questo valore:
103
+
104
+ ```bash
105
+ openrndt --timeout 5 search --q "catasto" --num 5
106
+ ```
107
+
108
+ Tutti i comandi accettano `--format json` (default), `--format table`, `--format csv`.
109
+ Per `search` c'è anche `--format compact`: una riga NDJSON per record con i soli
110
+ campi ad alto segnale (`id`, `title`, `org`, `type`, `category`, `updated`,
111
+ `resources`), pensata per agenti AI e pipe a basso consumo di token.
112
+
113
+ ```bash
114
+ openrndt --format compact search --q "catasto" --num 3
115
+ # {"id":"…","title":"…","org":"Comune di Torino","type":"service","category":null,"updated":"…","resources":["WMS"]}
116
+ ```
117
+
118
+ ## Esempi di conversazione con un'AI
119
+
120
+ openrndt è pensato per essere **pilotato da un assistente AI**. Non devi imparare la
121
+ sintassi: descrivi a parole quello che ti serve e l'AI compone i comandi, legge i
122
+ risultati e ti restituisce ciò che conta — di solito **un URL WMS/WFS da incollare in
123
+ QGIS** o un dato da scaricare. Tutti gli esempi qui sotto usano risposte reali del
124
+ catalogo RNDT.
125
+
126
+ > Per provarli serve un assistente (es. Claude) a cui è stata installata la skill
127
+ > `skills/rndt-explorer/` o che può eseguire `openrndt` da terminale.
128
+
129
+ ### «Mi serve l'uso del suolo dell'Emilia-Romagna da caricare in QGIS»
130
+
131
+ > **Tu:** Sto lavorando su un progetto QGIS in Emilia-Romagna. Mi trovi un servizio
132
+ > con l'uso del suolo regionale, così lo aggiungo come layer?
133
+
134
+ L'AI cerca i servizi pubblicati sul tema:
135
+
136
+ ```bash
137
+ openrndt search --q "uso del suolo WMS" --num 20
138
+ ```
139
+
140
+ > **AI:** Trovato. La **Regione Emilia-Romagna** pubblica un WMS *Uso del Suolo* che
141
+ > copre più annate (2020, 2017, 2014, 2008, 2003, 1994, 1976) più l'uso storico dal
142
+ > 1853. In QGIS aggiungi un nuovo layer **WMS/WMTS** con questo URL:
143
+ >
144
+ > ```
145
+ > http://servizigis.regione.emilia-romagna.it/wms/uso_del_suolo?request=GetCapabilities&service=WMS
146
+ > ```
147
+ >
148
+ > Scegli poi l'annata che ti interessa fra i layer offerti dal servizio.
149
+
150
+ ### «Voglio il catasto di un comune piemontese»
151
+
152
+ > **Tu:** Mi serve il catasto urbano per un'analisi in Piemonte.
153
+
154
+ ```bash
155
+ openrndt search --q "catasto" --num 10
156
+ ```
157
+
158
+ > **AI:** C'è il **Catasto Urbano 1:1500** come geo-servizio WMS della Regione
159
+ > Piemonte. URL per QGIS:
160
+ >
161
+ > ```
162
+ > https://geomap.reteunitaria.piemonte.it/ws/siccms/coto-01/wmsg01/wms_sicc17bis_catasto_urbano?service=WMS&version=1.1.1&request=getCapabilities
163
+ > ```
164
+
165
+ ### «Cerco ortofoto da usare come base»
166
+
167
+ > **Tu:** Mi servono delle ortofoto come sfondo per la mia mappa. Cosa c'è disponibile?
168
+
169
+ ```bash
170
+ openrndt search --q "ortofoto" --num 50
171
+ ```
172
+
173
+ > **AI:** Diverse amministrazioni espongono ortofoto via WMS, pronte da usare come
174
+ > base in QGIS. Per esempio:
175
+ >
176
+ > - **Regione Sardegna** — `https://webgis.regione.sardegna.it/geoserverraster/ows?service=wms&version=1.3.0&request=GetCapabilities`
177
+ > - **Provincia di Lodi** — `http://sdi.provincia.lodi.it/arcgis/services/DBTL/dbtl_ortofoto_digitali/MapServer/WMSServer?request=GetCapabilities&service=WMS`
178
+ > - **Regione Piemonte** — `https://geomap.reteunitaria.piemonte.it/mapproxy/service?service=WMS&version=1.3.0&request=getCapabilities`
179
+
180
+ ### «Il reticolo idrografico, ma vettoriale (per editing)»
181
+
182
+ > **Tu:** Non voglio un'immagine: mi serve il reticolo idrografico come vettoriale,
183
+ > così posso interrogarlo e modificarlo.
184
+
185
+ ```bash
186
+ openrndt search --q "reticolo idrografico" --num 50
187
+ ```
188
+
189
+ > **AI:** Per i vettoriali ti serve un **WFS** (in QGIS: *Aggiungi layer WFS*). Alcuni
190
+ > servizi disponibili:
191
+ >
192
+ > - **ISPRA** — `http://sdi.isprambiente.it/geoserver/hy/wfs?service=wfs&version=2.0.0&request=GetCapabilities`
193
+ > - **ARPA Veneto** — `http://gaia.arpa.veneto.it/geoserver/ows?service=WFS&version=1.0.0&request=GetCapabilities`
194
+ > - **Regione Basilicata** — `http://rsdi.regione.basilicata.it/rbgeoserver2016/dbgt_0404/nd_idr/wfs?request=GetCapabilities&service=WFS&version=2.0.0`
195
+
196
+ ### «Tutto quello che pubblica un certo ente»
197
+
198
+ > **Tu:** Quanti e quali dataset ha pubblicato la Regione Lombardia?
199
+
200
+ ```bash
201
+ openrndt search --q 'apiso_OrganizationName_txt:"Regione Lombardia"' \
202
+ --sort "apiso_Modified_dt:desc" --num 10
203
+ ```
204
+
205
+ > **AI:** La **Regione Lombardia** ha **430 dataset** nel RNDT. Ecco i 10 aggiornati
206
+ > più di recente… (l'AI elenca titoli e date). Posso filtrarli per tema — uso del
207
+ > suolo, idrografia, trasporti — o mostrarti solo quelli con un servizio WMS/WFS.
208
+
209
+ ### «Cosa copre la mia area di studio»
210
+
211
+ > **Tu:** Sto studiando l'area attorno a Bologna. Quali dataset la coprono?
212
+
213
+ ```bash
214
+ openrndt search --q "edificato" --bbox "11.2,44.4,11.5,44.6" --num 10
215
+ ```
216
+
217
+ > **AI:** Nel riquadro che copre l'area bolognese ci sono **40 dataset** che la
218
+ > intersecano: il Database Topografico Regionale (edifici, unità volumetriche, falde)
219
+ > e anche cartografie storiche (Ducato di Modena 1821, territorio ferrarese 1814)
220
+ > disponibili come WMS. Nota: il filtro è per *sovrapposizione* al riquadro, quindi
221
+ > include dataset a copertura regionale che toccano l'area.
222
+
223
+ ### «Solo dati con licenza aperta»
224
+
225
+ > **Tu:** Mi servono dati sulle frane, ma solo open data che posso riusare liberamente.
226
+
227
+ ```bash
228
+ openrndt search --q "frane AND isOpendata:*" --num 10
229
+ ```
230
+
231
+ > **AI:** Ci sono **259 dataset open data** sulle frane — dall'inventario IFFI della
232
+ > Regione Toscana ai rilievi con drone dell'emergenza alluvione 2023 in
233
+ > Emilia-Romagna. Te li elenco con la licenza di ciascuno?
234
+
235
+ ## Uso come libreria Python
236
+
237
+ ```python
238
+ from openrndt import search, get_item, get_item_xml, ItemNotFoundError
239
+
240
+ # Ricerca
241
+ results = search(q="catasto", num=5)
242
+ for r in results["results"]:
243
+ print(r["id"], r["title"])
244
+
245
+ # Filtro per categoria e bbox
246
+ results = search(data_category="planningCadastre", bbox="7,44,8,45", num=10)
247
+
248
+ # Dettaglio singolo metadato
249
+ item = get_item("age:D_E973_MARSAGLIA")
250
+ print(item["_source"]["title"])
251
+
252
+ # XML ISO 19139
253
+ xml = get_item_xml("age:D_E973_MARSAGLIA")
254
+
255
+ # Gestione ID inesistente
256
+ try:
257
+ item = get_item("id_inesistente")
258
+ except ItemNotFoundError:
259
+ print("metadato non trovato")
260
+ ```
261
+
262
+ Le funzioni propagano le eccezioni `httpx`: `httpx.HTTPStatusError` per le
263
+ risposte 4xx/5xx e `httpx.ConnectError` / `httpx.TimeoutException` per i
264
+ problemi di rete. I retry interni coprono i timeout e i 5xx (3 tentativi),
265
+ mentre gli errori di connessione/DNS (`ConnectError`) vengono propagati subito.
266
+ Tutte derivano da `httpx.HTTPError`, comodo per catturarle insieme:
267
+
268
+ ```python
269
+ import httpx
270
+ from openrndt import search
271
+
272
+ try:
273
+ results = search(q="catasto")
274
+ except httpx.HTTPError as exc:
275
+ print(f"richiesta fallita: {exc}")
276
+ ```
277
+
278
+ Il base URL è configurabile via variabile d'ambiente o parametro:
279
+
280
+ ```python
281
+ from openrndt.config import set_base_url
282
+ set_base_url("https://mio-mirror.example.com/RNDT")
283
+ ```
284
+
285
+ ## Per agenti AI
286
+
287
+ L'utente primario di questa CLI è un agente che legge `stdout` e compone i comandi
288
+ passo passo. Da qui i principi di design (sul modello di
289
+ [opensdmx](https://github.com/aborruso/opensdmx)):
290
+
291
+ - **Output strutturato, mai oggetti Python.** Default JSON su `stdout`; `--format
292
+ table` per la lettura umana, `--format csv` per i risultati tabellari, `--format
293
+ compact` (NDJSON, una riga per record) per scremare molti risultati a basso costo.
294
+ - **In modalità JSON, `stdout` contiene solo JSON.** Errori e avvisi vanno su
295
+ `stderr`: si può fare pipe diretta in `jq`.
296
+ - **Errori leggibili e self-contained: mai stack trace.** Un errore di rete o HTTP
297
+ produce un messaggio comprensibile su `stderr` ed exit code `1`, non un traceback.
298
+ - **Exit code chiari.** `0` successo, `1` errore (rete, HTTP, ID inesistente),
299
+ `2` parametri non validi.
300
+ - **Niente formati ambigui.** `get <id> --format csv` (dettaglio non tabellare)
301
+ fallisce con un messaggio esplicito invece di restituire output vuoto.
302
+
303
+ Il progetto include inoltre una skill Claude Code in `skills/rndt-explorer/` che guida
304
+ un agente attraverso le 4 fasi: scoperta delle codelist, ricerca con filtri progressivi,
305
+ lettura del dettaglio, download delle risorse collegate (WMS/WFS/download).
306
+
307
+ ## Riferimenti
308
+
309
+ - Pagina ufficiale REST API: <https://geodati.gov.it/geoportale/eng/strumenti-en/rest-api>
310
+ - Spec completa (sito di test Esri Geoportal Server, non produzione): <https://gpt.geocloud.com/geoportal3/api/gpt_api.json>
311
+ - Documentazione raccolta nella cartella [`ref/`](./ref).
312
+
313
+ ## Licenza
314
+
315
+ MIT.
@@ -0,0 +1,287 @@
1
+ # openrndt
2
+
3
+ CLI Python e libreria per accedere al **Repertorio Nazionale dei Dati Territoriali (RNDT)** —
4
+ pensata per essere orchestrata da un'AI.
5
+
6
+ > Stato: v1.0 — read-only.
7
+
8
+ ## Cos'è il RNDT
9
+
10
+ Il [Repertorio Nazionale dei Dati Territoriali](https://geodati.gov.it/geoportale/) è
11
+ il catalogo ufficiale italiano dei metadati geografici (ISO 19115/19139). Espone REST
12
+ API per cercare e scaricare i metadati.
13
+
14
+ ## Installazione
15
+
16
+ ### Da PyPI
17
+
18
+ ```bash
19
+ uv tool install openrndt
20
+ # oppure, senza installazione persistente:
21
+ uvx openrndt --help
22
+ ```
23
+
24
+ > Non ancora pubblicato su PyPI (roadmap verso la v1.0). Nel frattempo installa da locale.
25
+
26
+ ### Da locale
27
+
28
+ ```bash
29
+ git clone https://github.com/ondata/openrndt.git
30
+ cd openrndt
31
+
32
+ # CLI globale: venv isolato, eseguibile in PATH
33
+ uv tool install .
34
+
35
+ # Aggiornamento dopo modifiche al codice
36
+ uv tool install --reinstall .
37
+
38
+ # Disinstallazione
39
+ uv tool uninstall openrndt
40
+ ```
41
+
42
+ ### Per sviluppo (modifiche con ricarica immediata)
43
+
44
+ ```bash
45
+ git clone https://github.com/ondata/openrndt.git
46
+ cd openrndt
47
+ uv sync
48
+ uv run openrndt --help
49
+ ```
50
+
51
+ ## Uso
52
+
53
+ ```bash
54
+ # Ricerca testuale
55
+ openrndt search --q "catasto" --num 5
56
+
57
+ # Filtro per bounding box (Piemonte sud)
58
+ openrndt search --q "cartografia" --bbox 7,44,8,45 --num 10
59
+
60
+ # Per categoria tematica ISO 19115
61
+ openrndt search --data-category planningCadastre --num 5
62
+
63
+ # Singolo metadato
64
+ openrndt get age:D_E973_MARSAGLIA
65
+
66
+ # XML ISO 19139 grezzo
67
+ openrndt get age:D_E973_MARSAGLIA --xml > meta.xml
68
+
69
+ # Codelist disponibili (no rete)
70
+ openrndt discover
71
+ ```
72
+
73
+ Il timeout HTTP per singolo tentativo è configurabile con `--timeout` (default 30s);
74
+ con i retry su timeout/5xx (3 tentativi) il caso peggiore è ~3x questo valore:
75
+
76
+ ```bash
77
+ openrndt --timeout 5 search --q "catasto" --num 5
78
+ ```
79
+
80
+ Tutti i comandi accettano `--format json` (default), `--format table`, `--format csv`.
81
+ Per `search` c'è anche `--format compact`: una riga NDJSON per record con i soli
82
+ campi ad alto segnale (`id`, `title`, `org`, `type`, `category`, `updated`,
83
+ `resources`), pensata per agenti AI e pipe a basso consumo di token.
84
+
85
+ ```bash
86
+ openrndt --format compact search --q "catasto" --num 3
87
+ # {"id":"…","title":"…","org":"Comune di Torino","type":"service","category":null,"updated":"…","resources":["WMS"]}
88
+ ```
89
+
90
+ ## Esempi di conversazione con un'AI
91
+
92
+ openrndt è pensato per essere **pilotato da un assistente AI**. Non devi imparare la
93
+ sintassi: descrivi a parole quello che ti serve e l'AI compone i comandi, legge i
94
+ risultati e ti restituisce ciò che conta — di solito **un URL WMS/WFS da incollare in
95
+ QGIS** o un dato da scaricare. Tutti gli esempi qui sotto usano risposte reali del
96
+ catalogo RNDT.
97
+
98
+ > Per provarli serve un assistente (es. Claude) a cui è stata installata la skill
99
+ > `skills/rndt-explorer/` o che può eseguire `openrndt` da terminale.
100
+
101
+ ### «Mi serve l'uso del suolo dell'Emilia-Romagna da caricare in QGIS»
102
+
103
+ > **Tu:** Sto lavorando su un progetto QGIS in Emilia-Romagna. Mi trovi un servizio
104
+ > con l'uso del suolo regionale, così lo aggiungo come layer?
105
+
106
+ L'AI cerca i servizi pubblicati sul tema:
107
+
108
+ ```bash
109
+ openrndt search --q "uso del suolo WMS" --num 20
110
+ ```
111
+
112
+ > **AI:** Trovato. La **Regione Emilia-Romagna** pubblica un WMS *Uso del Suolo* che
113
+ > copre più annate (2020, 2017, 2014, 2008, 2003, 1994, 1976) più l'uso storico dal
114
+ > 1853. In QGIS aggiungi un nuovo layer **WMS/WMTS** con questo URL:
115
+ >
116
+ > ```
117
+ > http://servizigis.regione.emilia-romagna.it/wms/uso_del_suolo?request=GetCapabilities&service=WMS
118
+ > ```
119
+ >
120
+ > Scegli poi l'annata che ti interessa fra i layer offerti dal servizio.
121
+
122
+ ### «Voglio il catasto di un comune piemontese»
123
+
124
+ > **Tu:** Mi serve il catasto urbano per un'analisi in Piemonte.
125
+
126
+ ```bash
127
+ openrndt search --q "catasto" --num 10
128
+ ```
129
+
130
+ > **AI:** C'è il **Catasto Urbano 1:1500** come geo-servizio WMS della Regione
131
+ > Piemonte. URL per QGIS:
132
+ >
133
+ > ```
134
+ > https://geomap.reteunitaria.piemonte.it/ws/siccms/coto-01/wmsg01/wms_sicc17bis_catasto_urbano?service=WMS&version=1.1.1&request=getCapabilities
135
+ > ```
136
+
137
+ ### «Cerco ortofoto da usare come base»
138
+
139
+ > **Tu:** Mi servono delle ortofoto come sfondo per la mia mappa. Cosa c'è disponibile?
140
+
141
+ ```bash
142
+ openrndt search --q "ortofoto" --num 50
143
+ ```
144
+
145
+ > **AI:** Diverse amministrazioni espongono ortofoto via WMS, pronte da usare come
146
+ > base in QGIS. Per esempio:
147
+ >
148
+ > - **Regione Sardegna** — `https://webgis.regione.sardegna.it/geoserverraster/ows?service=wms&version=1.3.0&request=GetCapabilities`
149
+ > - **Provincia di Lodi** — `http://sdi.provincia.lodi.it/arcgis/services/DBTL/dbtl_ortofoto_digitali/MapServer/WMSServer?request=GetCapabilities&service=WMS`
150
+ > - **Regione Piemonte** — `https://geomap.reteunitaria.piemonte.it/mapproxy/service?service=WMS&version=1.3.0&request=getCapabilities`
151
+
152
+ ### «Il reticolo idrografico, ma vettoriale (per editing)»
153
+
154
+ > **Tu:** Non voglio un'immagine: mi serve il reticolo idrografico come vettoriale,
155
+ > così posso interrogarlo e modificarlo.
156
+
157
+ ```bash
158
+ openrndt search --q "reticolo idrografico" --num 50
159
+ ```
160
+
161
+ > **AI:** Per i vettoriali ti serve un **WFS** (in QGIS: *Aggiungi layer WFS*). Alcuni
162
+ > servizi disponibili:
163
+ >
164
+ > - **ISPRA** — `http://sdi.isprambiente.it/geoserver/hy/wfs?service=wfs&version=2.0.0&request=GetCapabilities`
165
+ > - **ARPA Veneto** — `http://gaia.arpa.veneto.it/geoserver/ows?service=WFS&version=1.0.0&request=GetCapabilities`
166
+ > - **Regione Basilicata** — `http://rsdi.regione.basilicata.it/rbgeoserver2016/dbgt_0404/nd_idr/wfs?request=GetCapabilities&service=WFS&version=2.0.0`
167
+
168
+ ### «Tutto quello che pubblica un certo ente»
169
+
170
+ > **Tu:** Quanti e quali dataset ha pubblicato la Regione Lombardia?
171
+
172
+ ```bash
173
+ openrndt search --q 'apiso_OrganizationName_txt:"Regione Lombardia"' \
174
+ --sort "apiso_Modified_dt:desc" --num 10
175
+ ```
176
+
177
+ > **AI:** La **Regione Lombardia** ha **430 dataset** nel RNDT. Ecco i 10 aggiornati
178
+ > più di recente… (l'AI elenca titoli e date). Posso filtrarli per tema — uso del
179
+ > suolo, idrografia, trasporti — o mostrarti solo quelli con un servizio WMS/WFS.
180
+
181
+ ### «Cosa copre la mia area di studio»
182
+
183
+ > **Tu:** Sto studiando l'area attorno a Bologna. Quali dataset la coprono?
184
+
185
+ ```bash
186
+ openrndt search --q "edificato" --bbox "11.2,44.4,11.5,44.6" --num 10
187
+ ```
188
+
189
+ > **AI:** Nel riquadro che copre l'area bolognese ci sono **40 dataset** che la
190
+ > intersecano: il Database Topografico Regionale (edifici, unità volumetriche, falde)
191
+ > e anche cartografie storiche (Ducato di Modena 1821, territorio ferrarese 1814)
192
+ > disponibili come WMS. Nota: il filtro è per *sovrapposizione* al riquadro, quindi
193
+ > include dataset a copertura regionale che toccano l'area.
194
+
195
+ ### «Solo dati con licenza aperta»
196
+
197
+ > **Tu:** Mi servono dati sulle frane, ma solo open data che posso riusare liberamente.
198
+
199
+ ```bash
200
+ openrndt search --q "frane AND isOpendata:*" --num 10
201
+ ```
202
+
203
+ > **AI:** Ci sono **259 dataset open data** sulle frane — dall'inventario IFFI della
204
+ > Regione Toscana ai rilievi con drone dell'emergenza alluvione 2023 in
205
+ > Emilia-Romagna. Te li elenco con la licenza di ciascuno?
206
+
207
+ ## Uso come libreria Python
208
+
209
+ ```python
210
+ from openrndt import search, get_item, get_item_xml, ItemNotFoundError
211
+
212
+ # Ricerca
213
+ results = search(q="catasto", num=5)
214
+ for r in results["results"]:
215
+ print(r["id"], r["title"])
216
+
217
+ # Filtro per categoria e bbox
218
+ results = search(data_category="planningCadastre", bbox="7,44,8,45", num=10)
219
+
220
+ # Dettaglio singolo metadato
221
+ item = get_item("age:D_E973_MARSAGLIA")
222
+ print(item["_source"]["title"])
223
+
224
+ # XML ISO 19139
225
+ xml = get_item_xml("age:D_E973_MARSAGLIA")
226
+
227
+ # Gestione ID inesistente
228
+ try:
229
+ item = get_item("id_inesistente")
230
+ except ItemNotFoundError:
231
+ print("metadato non trovato")
232
+ ```
233
+
234
+ Le funzioni propagano le eccezioni `httpx`: `httpx.HTTPStatusError` per le
235
+ risposte 4xx/5xx e `httpx.ConnectError` / `httpx.TimeoutException` per i
236
+ problemi di rete. I retry interni coprono i timeout e i 5xx (3 tentativi),
237
+ mentre gli errori di connessione/DNS (`ConnectError`) vengono propagati subito.
238
+ Tutte derivano da `httpx.HTTPError`, comodo per catturarle insieme:
239
+
240
+ ```python
241
+ import httpx
242
+ from openrndt import search
243
+
244
+ try:
245
+ results = search(q="catasto")
246
+ except httpx.HTTPError as exc:
247
+ print(f"richiesta fallita: {exc}")
248
+ ```
249
+
250
+ Il base URL è configurabile via variabile d'ambiente o parametro:
251
+
252
+ ```python
253
+ from openrndt.config import set_base_url
254
+ set_base_url("https://mio-mirror.example.com/RNDT")
255
+ ```
256
+
257
+ ## Per agenti AI
258
+
259
+ L'utente primario di questa CLI è un agente che legge `stdout` e compone i comandi
260
+ passo passo. Da qui i principi di design (sul modello di
261
+ [opensdmx](https://github.com/aborruso/opensdmx)):
262
+
263
+ - **Output strutturato, mai oggetti Python.** Default JSON su `stdout`; `--format
264
+ table` per la lettura umana, `--format csv` per i risultati tabellari, `--format
265
+ compact` (NDJSON, una riga per record) per scremare molti risultati a basso costo.
266
+ - **In modalità JSON, `stdout` contiene solo JSON.** Errori e avvisi vanno su
267
+ `stderr`: si può fare pipe diretta in `jq`.
268
+ - **Errori leggibili e self-contained: mai stack trace.** Un errore di rete o HTTP
269
+ produce un messaggio comprensibile su `stderr` ed exit code `1`, non un traceback.
270
+ - **Exit code chiari.** `0` successo, `1` errore (rete, HTTP, ID inesistente),
271
+ `2` parametri non validi.
272
+ - **Niente formati ambigui.** `get <id> --format csv` (dettaglio non tabellare)
273
+ fallisce con un messaggio esplicito invece di restituire output vuoto.
274
+
275
+ Il progetto include inoltre una skill Claude Code in `skills/rndt-explorer/` che guida
276
+ un agente attraverso le 4 fasi: scoperta delle codelist, ricerca con filtri progressivi,
277
+ lettura del dettaglio, download delle risorse collegate (WMS/WFS/download).
278
+
279
+ ## Riferimenti
280
+
281
+ - Pagina ufficiale REST API: <https://geodati.gov.it/geoportale/eng/strumenti-en/rest-api>
282
+ - Spec completa (sito di test Esri Geoportal Server, non produzione): <https://gpt.geocloud.com/geoportal3/api/gpt_api.json>
283
+ - Documentazione raccolta nella cartella [`ref/`](./ref).
284
+
285
+ ## Licenza
286
+
287
+ MIT.
@@ -0,0 +1,61 @@
1
+ [project]
2
+ name = "openrndt"
3
+ version = "1.0.0"
4
+ description = "CLI Python per il Repertorio Nazionale dei Dati Territoriali (RNDT) — pensata per essere orchestrata da un'AI"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Andrea Borruso", email = "aborruso@gmail.com" }
8
+ ]
9
+ license = { text = "MIT" }
10
+ requires-python = ">=3.12"
11
+ keywords = ["rndt", "geodati", "inspire", "metadata", "iso19115", "iso19139", "open-data", "italy"]
12
+ classifiers = [
13
+ "Development Status :: 5 - Production/Stable",
14
+ "Intended Audience :: Science/Research",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Topic :: Scientific/Engineering :: GIS",
21
+ "Topic :: Software Development :: Libraries :: Python Modules",
22
+ ]
23
+ dependencies = [
24
+ "httpx>=0.28.1",
25
+ "typer>=0.24.1",
26
+ "rich>=14.3.3",
27
+ "tenacity>=9.1.4",
28
+ ]
29
+
30
+ [project.scripts]
31
+ openrndt = "openrndt:main"
32
+
33
+ [project.urls]
34
+ Repository = "https://github.com/ondata/openrndt"
35
+ Issues = "https://github.com/ondata/openrndt/issues"
36
+ "RNDT Portal" = "https://geodati.gov.it/geoportale/"
37
+ "RNDT REST API" = "https://geodati.gov.it/geoportale/eng/strumenti-en/rest-api"
38
+
39
+ [dependency-groups]
40
+ dev = [
41
+ "mypy>=2.3.0",
42
+ "pytest>=8",
43
+ "pytest-cov>=7.1.0",
44
+ "responses>=0.25",
45
+ "respx>=0.21",
46
+ "ruff>=0.11",
47
+ ]
48
+
49
+ [tool.pytest.ini_options]
50
+ testpaths = ["tests"]
51
+
52
+ [tool.ruff.lint]
53
+ ignore = ["E402"]
54
+
55
+ [tool.mypy]
56
+ python_version = "3.12"
57
+ strict = true
58
+
59
+ [build-system]
60
+ requires = ["uv_build>=0.9.7,<0.10.0"]
61
+ build-backend = "uv_build"
@@ -0,0 +1,8 @@
1
+ """openrndt — CLI Python per il Repertorio Nazionale dei Dati Territoriali."""
2
+
3
+ from openrndt.cli import main
4
+ from openrndt.item import ItemNotFoundError, get_item, get_item_html, get_item_xml
5
+ from openrndt.search import search
6
+
7
+ __version__ = "1.0.0"
8
+ __all__ = ["main", "search", "get_item", "get_item_xml", "get_item_html", "ItemNotFoundError", "__version__"]