Skip to content

Commit b3175ad

Browse files
aborrusoclaude
andcommitted
feat: batch reads CSV via DuckDB; bump v0.2.0
- DuckDB core dep: batch input via read_csv (delimiter autodetect from header, --decimal for comma, '-' = stdin), cells kept as raw strings - library: export read_csv_file/resolve_column/rows_to_geojson - README badges + IGM service link + default-format docs - verto-explorer skill v1.1 + docs/skill install guide - OpenSpec change batch-duckdb-csv archived Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 54b9041 commit b3175ad

19 files changed

Lines changed: 666 additions & 18 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ tmp.md
2424
# Caches
2525
.pytest_cache/
2626
.ruff_cache/
27+
28+
# Reference material (e.g. third-party manuals, not for redistribution)
29+
ref/

LOG.md

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

3+
## 2026-06-08 — v0.2.0 — batch legge il CSV con DuckDB
4+
5+
- `batch` ora legge il CSV di input via **DuckDB** (dipendenza core aggiunta): autodetect del delimitatore (`,`, `;`, tab, `|`) rilevato dalla **riga di header**, nuova opzione `--decimal` (`.` default, `,` per CSV italiani tipo `12,4924`), supporto **stdin** (`-`) e stdout (default senza `--out`).
6+
- Niente formati spaziali: l'idea iniziale (export GPKG/FGB/GeoParquet/SHP via estensione spatial) è stata abbandonata dopo una sonda empirica — driver `Parquet` assente, FGB riordina le feature, GeoParquet nativo tagga CRS84 invece dell'EPSG. Si usa DuckDB solo per I/O CSV robusto.
7+
- Bug risolto: con `decimal_separator=','` lo sniffer di DuckDB sceglie la virgola come delimitatore su righe tutte-numeriche → rilevo io il delimitatore dall'header escludendo la virgola, poi `read_csv` con `delim` esplicito.
8+
- Regressione risolta (lettura tipizzata riformattava le colonne attributo, es. `19.90``19.9`, `1000,50``1000.5`): ora `read_csv` con `all_varchar=true` tiene ogni cella come stringa grezza; la virgola→punto è normalizzata **solo** sulle colonne x/y dentro `batch`.
9+
- Allineamento libreria↔CLI: esportati `read_csv_file`, `resolve_column`, `rows_to_geojson` in `openverto/__init__.py` così il workflow di `batch` è riproducibile da `import openverto`.
10+
- README: badge (PyPI/GitHub/DeepWiki/MIT/Newsletter) stile opensdmx + link al servizio IGM Verto Online + sezione formato CSV di default.
11+
- Test: +6 offline (delimitatore `;`, celle grezze preservate, header numerico-regression, stdin, colonna mancante, alias). 22 passati, ruff pulito, wheel ok.
12+
- Skill `verto-explorer` aggiornata (v1.1) con la lettura DuckDB; nuova guida d'installazione skill `docs/skill/README.md` (stile opensdmx, `npx skills add ondata/openverto`), linkata dal README.
13+
- Manuale ufficiale IGM scaricato in `ref/` (PDF + markdown via `lit`).
14+
- Gestito con OpenSpec: change `batch-duckdb-csv` (ex `duckdb-spatial-export`, ridimensionata).
15+
- TODO: bump versione + eventuale release (dipendenza core nuova).
16+
317
## 2026-06-08 — v0.1.1
418

519
- Subrelease patch: aggiunta sezione `Examples:` nel docstring di **ogni** sottocomando CLI (`systems`, `convert`, `inspect`, `detect`, `targets`, `roundtrip`, `batch`, `geojson`, `cache`, `doctor`) — almeno un esempio utile per LLM, sul modello di `opensdmx`.

README.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
# openverto
22

3+
[![PyPI version](https://img.shields.io/pypi/v/openverto)](https://pypi.org/project/openverto/)
4+
[![GitHub](https://img.shields.io/badge/github-ondata%2Fopenverto-blue?logo=github)](https://github.com/ondata/openverto)
5+
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/ondata/openverto)
6+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7+
[![Newsletter](https://img.shields.io/badge/newsletter-ondata-FF6719?logo=substack)](https://ondata.substack.com/)
8+
39
> Tool sperimentale — aiutaci a testarlo [aprendo issue](https://github.com/ondata/openverto/issues) o inviando feedback.
410
5-
CLI e libreria Python per il servizio ufficiale **IGM Verto Online**: trasforma coordinate tra tutti i sistemi di riferimento italiani (Roma40, ED50, IGM95, ETRS89, RDN2008) **senza installare le griglie NTv2**, appoggiandosi al servizio autorevole dell'Istituto Geografico Militare.
11+
CLI e libreria Python per il servizio ufficiale **[IGM Verto Online](https://igmi.esercito.difesa.it/servizi/verto-online/)**: trasforma coordinate tra tutti i sistemi di riferimento italiani (Roma40, ED50, IGM95, ETRS89, RDN2008) **senza installare le griglie NTv2**, appoggiandosi al servizio autorevole dell'Istituto Geografico Militare.
612

713
Pensato per essere **orchestrato da agenti AI**: output `--json`/`--jsonl`/`--csv`, non interattivo, pipeable, read-only. L'intelligenza sugli EPSG (`inspect`, `detect`, `targets`) è completamente **offline**.
814

15+
> **Meglio con un'AI.** openverto funziona benissimo da solo, ma **dà il meglio guidato da un agente AI**. Per un'esperienza interattiva — identificazione del sistema di origine, scelta di un target valido, conversione e verifica della copertura — abbinalo alla Agent Skill [`verto-explorer`](skills/verto-explorer/SKILL.md) inclusa nel repo. Vedi la [**guida d'installazione**](docs/skill/README.md) per i passi.
16+
917
## Installazione
1018

1119
Come strumento CLI (consigliato):
@@ -35,10 +43,26 @@ openverto inspect 3003
3543
# converti un CSV di punti Gauss-Boaga (colonne est/nord) in RDN2008/TM32
3644
openverto batch catasto.csv --from 3003 --to 6707 --e-col est --n-col nord --out out.csv
3745

46+
# CSV all'italiana (delimitatore ; e virgola decimale), da stdin verso stdout
47+
cat comuni.csv | openverto batch - --from 4265 --to 6706 --decimal , --e-col lon --n-col lat
48+
3849
# riproietta le geometrie di un GeoJSON
3950
openverto geojson aree.geojson --from 4230 --to 6706 --out out.geojson
4051
```
4152

53+
### Lettura del CSV (comando `batch`)
54+
55+
Il CSV di input è letto con **DuckDB**: il **delimitatore** (`,`, `;`, tab, `|`) è
56+
rilevato automaticamente, non va dichiarato.
57+
58+
- **Formato di default, nessuna opzione richiesta**: CSV con prima riga di
59+
intestazione, separatore decimale **punto** (`12.4924`). Il delimitatore può
60+
essere virgola o punto e virgola: viene riconosciuto da solo.
61+
- **CSV all'italiana**: per coordinate con la **virgola decimale** (`12,4924`)
62+
aggiungi `--decimal ,`.
63+
- **stdin/stdout**: usa `-` come file per leggere da stdin; ometti `--out` per
64+
scrivere su stdout.
65+
4266
## Formati di output
4367

4468
Globale, con `-o/--output`:
@@ -58,7 +82,7 @@ openverto -o csv convert ... # CSV
5882
|---|---|
5983
| `systems` | Elenco dei sistemi di riferimento supportati (EPSG + descrizione) |
6084
| `convert` | Converte una o più coordinate (`e n`, o `e,n` da stdin) |
61-
| `batch` | Converte un CSV in CSV o GeoJSON (auto-chunk a 32000, `--skip-invalid`) |
85+
| `batch` | Converte un CSV (letto con DuckDB: delimitatore auto, `--decimal`, `-`=stdin) in CSV o GeoJSON (auto-chunk a 32000, `--skip-invalid`) |
6286
| `geojson` | Riproietta le geometrie di un file GeoJSON |
6387
| `inspect` | Famiglia datum, ordine assi, unità, fuso, false easting di un EPSG |
6488
| `detect` | Indovina il sistema di origine di una coordinata dalla sua magnitudine |
@@ -91,6 +115,11 @@ ov.inspect(3003)
91115
ov.targets(3003)
92116
ov.detect(2300000, 4640000) # {"kind": "projected", "candidate_epsg": [3004], ...}
93117

118+
# lettura CSV robusta come la CLI (delimitatore auto; '-' = stdin)
119+
# le celle restano stringhe grezze: normalizza la virgola solo sulle colonne x/y
120+
header, records = ov.read_csv_file("comuni.csv", decimal=",")
121+
e_idx = ov.resolve_column(header, "lon", ov.geo.E_ALIASES)
122+
94123
# salta le coordinate fuori griglia isolandole per bisezione
95124
results, skipped = ov.convert_skipping(coords, 3003, 6707)
96125

@@ -111,7 +140,7 @@ Nessuna. Il servizio è libero e gratuito; i campi `utente`/`chiave` richiesti d
111140

112141
## Crediti
113142

114-
Servizio dati: [IGM Verto Online](https://igmi.esercito.difesa.it/), Istituto Geografico Militare.
143+
Servizio dati: [IGM Verto Online](https://igmi.esercito.difesa.it/servizi/verto-online/), Istituto Geografico Militare.
115144

116145
## Licenza
117146

docs/future-ideas.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Future ideas
2+
3+
## Export spaziale via DuckDB (input CSV/JSON/Parquet generico + output GIS con CRS)
4+
5+
### Idea
6+
7+
Dare in pasto a openverto un file tabellare **generico** (CSV, JSON, Parquet) in cui
8+
l'utente dichiara qual è la colonna X e quale la Y, far convertire le coordinate
9+
da Verto, e produrre in output un **file di punti in formato spaziale vero**
10+
(GeoPackage, FlatGeobuf, Shapefile, GeoParquet) con il **CRS corretto già scritto
11+
nel file**.
12+
13+
Il modulo che fa il lavoro di lettura/scrittura è **DuckDB** (con estensione
14+
`spatial`).
15+
16+
### Punto chiave (la tesi di tutto il design)
17+
18+
**DuckDB fa SOLO I/O. Non riproietta nulla.** La conversione di coordinate resta
19+
esclusivamente di Verto. DuckDB:
20+
21+
1. legge l'input (autodetect di delimitatori/tipi su CSV, parsing JSON anche
22+
annidato, lettura Parquet);
23+
2. scrive l'output spaziale **etichettando** il CRS di destinazione sulle
24+
coordinate **già convertite da Verto** — NON trasformandole.
25+
26+
Concretamente l'export è qualcosa come:
27+
28+
```sql
29+
COPY punti TO 'out.gpkg'
30+
WITH (FORMAT GDAL, DRIVER 'GPKG', SRS 'EPSG:6706');
31+
```
32+
33+
dove `SRS` è solo un'**etichetta**: i valori X/Y nella tabella sono quelli
34+
restituiti da Verto.
35+
36+
**Perché NON usare `ST_Transform` di DuckDB.** L'estensione `spatial` espone
37+
`ST_Transform`/PROJ, e un futuro implementatore sarà tentato di usarlo
38+
scavalcando Verto. Sarebbe un errore che svuota di senso il progetto: Verto usa
39+
le **griglie ufficiali IGM**, che PROJ-senza-griglie non replica. Lo scarto è
40+
documentato in `LOG.md` e `docs/evaluation.md` (0.1–2.8 m). Quel divario è
41+
l'intera ragione per cui questa feature passa da Verto.
42+
43+
### Posizionamento rispetto a `batch`/`geojson` esistenti
44+
45+
Non è un duplicato di `batch`. È un'**estensione**:
46+
47+
- `batch` oggi: legge CSV (colonne E/N), converte, output `table/json/jsonl/csv`
48+
o GeoJSON costruito a mano.
49+
- nuovo: input più ampio (CSV "sporco", JSON, Parquet via DuckDB) e soprattutto
50+
**output GIS reale** (GPKG/FGB/SHP/GeoParquet) con CRS embedded — cosa che oggi
51+
openverto non sa fare.
52+
53+
### Architettura (il pezzo non banale sta in mezzo)
54+
55+
Non è una singola pipeline SQL. È:
56+
57+
```
58+
DuckDB read → estrai X/Y → Verto convert (riusa chunking/bisezione di batch)
59+
→ re-join delle coord convertite preservando TUTTI gli attributi
60+
e l'ordine delle righe → DuckDB COPY TO (formato spaziale)
61+
```
62+
63+
Il **re-join / preservazione attributi** in mezzo è la parte ingegneristica vera.
64+
`batch` + `rows_to_geojson` già fanno una parte di questo lavoro e sono il punto
65+
di partenza.
66+
67+
### Decisioni (2026-06-08)
68+
69+
- **Dipendenza**: DuckDB entra come **dipendenza core** (scelta dell'autore).
70+
Nota: il core passa da 4 dipendenze pure-Python a includere un binario pesante;
71+
install più pesante per tutti, ma feature sempre disponibile. Da rivalutare se
72+
l'impatto sull'install/wheel risulta eccessivo.
73+
- **Comando**: **nuovo comando dedicato** (`export`/`spatial`), non estensione di
74+
`batch`. Tiene `batch` semplice e separa le responsabilità.
75+
- **Output**: GPKG, FlatGeobuf, GeoParquet, Shapefile (tutti e quattro al primo
76+
giro). GPKG come default naturale.
77+
- **Input**: **solo CSV** al primo giro (lettura robusta via DuckDB: autodetect
78+
delimitatori/tipi). JSON e Parquet rimandati a iterazioni successive.
79+
80+
### Domande ancora aperte
81+
82+
- Nome esatto del comando: `export` vs `spatial` vs altro.
83+
- Shapefile: gestione limiti noti (nomi campo ≤10 char, tipi) — troncamento
84+
automatico con warning?
85+
- Default driver dedotto dall'estensione del file di output (`.gpkg`→GPKG, ecc.)?

docs/skill/README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Install the verto-explorer skill
2+
3+
The `verto-explorer` skill enables guided, interactive coordinate conversion
4+
between Italian reference systems directly in your AI coding agent, using the
5+
**[openverto](https://github.com/ondata/openverto)** CLI under the hood.
6+
7+
Skills can be installed in many ways — refer to your agent's documentation for the
8+
available options. Here we use [skills](https://github.com/vercel-labs/skills), a
9+
convenient tool that installs a skill in a single step across multiple agents
10+
(Claude Code, OpenCode, GitHub Copilot, Codex, and more) in a unified way.
11+
12+
## Prerequisites
13+
14+
- The **openverto** CLI (`uv tool install openverto`) available on your PATH.
15+
- Node.js (v18+) — required only for the `npx skills` method below. Install it via
16+
your system's package manager or from [nodejs.org](https://nodejs.org).
17+
18+
## Installation
19+
20+
Run:
21+
22+
```bash
23+
npx skills add ondata/openverto --skill verto-explorer
24+
```
25+
26+
### Step 1 — Select agents
27+
28+
The installer fetches the skill from the repository and asks which agents to
29+
install it for. Several universal agents (including Claude Code) are enabled by
30+
default. If your agent is missing from the default list, scroll down to
31+
**Additional agents** to find and select it.
32+
33+
### Step 2 — Choose installation scope (Global recommended)
34+
35+
Choose between installing for the current project or globally (home directory,
36+
available across all projects). **Global** makes the skill available in every
37+
project.
38+
39+
### Step 3 — Symlink (Recommended)
40+
41+
Choose how the skill file is installed. We recommend **Symlink**: instead of
42+
copying the file, a symbolic link points to the original source, so any update to
43+
the skill is reflected immediately everywhere, with no need to reinstall.
44+
45+
### Step 4 — Confirm
46+
47+
Review and confirm with **Yes** to proceed.
48+
49+
## Alternative: manual installation
50+
51+
If you prefer not to use `npx skills`, you can download the skill folder directly
52+
and add it to your agent without Node.js. The skill is located at
53+
[`skills/verto-explorer/`](../../skills/verto-explorer/) in this repository —
54+
refer to your agent's documentation for how to register a local skill folder.
55+
56+
## Update
57+
58+
To update the skill to the latest version:
59+
60+
```bash
61+
npx skills update verto-explorer
62+
```
63+
64+
## Usage
65+
66+
Once installed, use `/verto-explorer` in the selected agents to start a guided
67+
coordinate-conversion session using openverto.
68+
69+
To explore the skill's full capabilities before or after installing, see the
70+
[skill definition](../../skills/verto-explorer/SKILL.md).
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-06-08
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
## Context
2+
3+
Il comando `batch` (`src/openverto/cli.py`) legge il CSV via
4+
`read_csv_file` in `geo.py`, che usa `csv.reader` e tratta ogni cella come
5+
stringa. Le colonne X/Y vengono poi convertite con `float(rec[idx].strip())`.
6+
Questo fallisce su due casi molto comuni nei dati italiani: delimitatore `;` e
7+
separatore decimale virgola (`12,4924`).
8+
9+
DuckDB è ora una dipendenza del progetto e il suo `read_csv_auto` ha uno sniffer
10+
robusto che riconosce delimitatore e tipi, con un parametro esplicito per il
11+
separatore decimale.
12+
13+
## Goals / Non-Goals
14+
15+
**Goals:**
16+
17+
- `batch` legge il CSV via DuckDB con autodetect del delimitatore.
18+
- Opzione per dichiarare il separatore decimale (default `.`, tipico alternativo
19+
`,`).
20+
- Le colonne X/Y diventano numeriche già in lettura (niente `float()` su stringhe
21+
con virgola).
22+
23+
**Non-Goals:**
24+
25+
- Nessun formato di output spaziale (GPKG/FGB/SHP/GeoParquet): abbandonati.
26+
- Nessun comando nuovo: si potenzia `batch`.
27+
- Nessun cambiamento all'output di `batch` (`e_out`/`n_out`, csv/geojson).
28+
- Nessun input non-CSV (JSON/Parquet) per ora.
29+
30+
## Decisions
31+
32+
**1. Lettura via `read_csv_auto`, restituendo `(header, records)` come oggi.**
33+
Il nuovo helper interroga DuckDB e produce la stessa struttura
34+
`(list[str], list[list[str]])` che `batch` già consuma, così a valle non cambia
35+
nulla (risoluzione colonne, skip, output). *Alternativa scartata*: riscrivere
36+
l'intera pipeline di `batch` in SQL — sovradimensionato e rischioso.
37+
38+
**2. Separatore decimale come opzione esplicita, non autodetect.**
39+
DuckDB non indovina in modo affidabile la virgola decimale (ambigua col
40+
delimitatore). Si espone `--decimal` (default `.`) passato a `read_csv_auto`
41+
come `decimal_separator`. *Alternativa scartata*: euristica lato Python —
42+
fragile.
43+
44+
**3. DuckDB sniffer al posto di `csv.Sniffer`.**
45+
Scelta dell'autore: usare DuckDB che è già una dipendenza e ha uno sniffer
46+
migliore. *Alternativa scartata*: `csv.Sniffer` + `str.replace(',', '.')`
47+
eviterebbe la dipendenza ma è meno robusto e contro la richiesta esplicita.
48+
49+
**4. Le celle restano restituite come stringhe verso `batch`.**
50+
Per non toccare l'output, il record passa a `batch` come stringhe; le sole
51+
colonne che contano numericamente (X/Y) sono garantite parse-abili perché
52+
DuckDB le ha già normalizzate (punto decimale) in lettura.
53+
54+
## Risks / Trade-offs
55+
56+
- **[Tipi vs stringhe] DuckDB tipizza, `batch` vuole stringhe** → si converte il
57+
risultato DuckDB in stringhe preservando il valore; per i numeri si usa una
58+
rappresentazione con punto decimale, così `float()` a valle non fallisce.
59+
- **[Peso dipendenza] DuckDB binario ~20 MB solo per leggere CSV** → accettato
60+
esplicitamente dall'autore; lo sniffer robusto e l'uso già presente di DuckDB
61+
giustificano la scelta.
62+
- **[NULL/celle vuote] DuckDB rende NULL le celle vuote** → mappare i NULL a
63+
stringa vuota per non alterare l'output rispetto a oggi.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
## Why
2+
3+
Il comando `batch` legge oggi il CSV con un `csv.reader` semplice: niente
4+
autodetect del delimitatore e nessuna gestione del separatore decimale. I CSV
5+
italiani/PA usano spessissimo `;` come delimitatore e la virgola come separatore
6+
decimale (es. `12,4924`), che oggi finiscono interpretati come testo e fanno
7+
fallire la conversione. Usare DuckDB per la lettura risolve entrambi i casi con
8+
uno sniffer robusto.
9+
10+
## What Changes
11+
12+
- `batch` legge il CSV tramite DuckDB (`read_csv_auto`) invece del `csv.reader`.
13+
- **Autodetect del delimitatore**: `,`, `;`, tab riconosciuti senza che l'utente
14+
li dichiari.
15+
- Nuova opzione per indicare il **separatore decimale** (es. virgola), così le
16+
colonne X/Y con `12,4924` vengono lette come numeri.
17+
- DuckDB entra come **dipendenza core**.
18+
- Nessun comando nuovo. Nessun formato spaziale. L'output di `batch`
19+
(`e_out`/`n_out`, csv/geojson) resta invariato.
20+
21+
## Capabilities
22+
23+
### New Capabilities
24+
25+
- `batch-csv-reading`: lettura robusta del CSV di input del comando `batch` via
26+
DuckDB, con autodetect del delimitatore e separatore decimale configurabile,
27+
preservando l'attuale risoluzione delle colonne e l'output.
28+
29+
### Modified Capabilities
30+
31+
<!-- Nessuna: non esistono spec in openspec/specs/. Il comportamento di output di batch non cambia. -->
32+
33+
## Impact
34+
35+
- **Dipendenze**: aggiunta di `duckdb` alle dipendenze core in `pyproject.toml`.
36+
- **Codice**: `src/openverto/geo.py` (`read_csv_file`) o un nuovo helper di
37+
lettura passa a DuckDB; il comando `batch` in `src/openverto/cli.py` ottiene
38+
l'opzione separatore decimale. La logica di conversione/skip resta invariata.
39+
- **Test**: nuovi test offline su delimitatore non standard e separatore
40+
decimale virgola.

0 commit comments

Comments
 (0)