# Regole di parsing della topologia

> Come Gestione Sinottici deriva il grafo della rete ottica di un armadio dai dati Excel.

## Sorgente dati

L'unica fonte è il campo **`Foglio / Prestazione`** delle righe `T` (Totale Foglio) dell'Excel "Estrazione totale" (formato XME/CIRCET). Le righe `M` (Misura) figlie del foglio servono solo per calcolare etichette cavo / lunghezza tratta (best-effort).

I campi `Da Rete`, `A Rete`, `Desc. Da Rete`, `Desc. A Rete` sono SEMPRE 0/null nel formato attuale dell'estrazione: NON utilizzabili per ricavare gli edge.

## Pattern riconosciuti

Il valore `Foglio / Prestazione` di una riga T ha sempre forma `<armadio>/<elemento>` (es. `57400D_02/G57:61`). Lo strip del prefisso `<armadio>/` lascia l'elemento, parsato in:

| Pattern dell'elemento | Tipo | Esempio | Parent | Note |
|---|---|---|---|---|
| `G<a>:<b>` | giunto | `G57:61` | armadio (root) | Contiene PTE in range [a..b] |
| `PTE_<n>` | pte | `PTE_4` | giunto contenente n | n deve essere in 1 solo giunto (errore se 0 o 2+) |
| `PTE_<a>:<b>` | pte_group | `PTE_20:22` | giunto contenente | Pte multipli fisicamente vicini |
| `AO` | ao | `AO` | armadio (root) | "Asse Ottico" / "Armadio Ottico" |
| `G<x>:<y>-G<z>:<w>` (tollerante a punti/spazi) | spillato | `G.19:39-G. 19:24` | — | NON un nodo: è un EDGE diretto tra due giunti già esistenti |
| Altro | unknown | qualsiasi | — | Errore segnalato |

## Costruzione del grafo (`buildTopology`)

1. **Crea il nodo armadio** (root, parent=NULL).
2. **Parse di ogni riga T** del comune con `desc_armadio = <armadio>` corrente:
   - Se elemento valido: push del nodo
   - Se spillato: tieni da parte come edge separato
   - Se unknown / no_prefix: errore
3. **Assegnazione parent** per ogni PTE / PTE_group:
   - Trova giunto il cui range `[from..to]` contiene `n` (o si interseca con `[a..b]`)
   - **0 giunti**: orphan → parent = armadio + warning
   - **1 giunto**: ok
   - **2+ giunti**: error overlap → assegnazione deterministica (range più stretto vince) + segnalazione
4. **AO**: parent = armadio
5. **Tree edges**: generati come parent→child per ogni nodo non root
6. **Spillato edges**: lookup dei due giunti referenziati (con tolleranza al formato del label). Se entrambi trovati → edge kind='spillato'. Se uno o entrambi mancano → error spillato_not_found.
7. **Cable label / length** per ogni tree edge: aggregazione delle righe M figlie del foglio T (link via `id_fogli`). Whitelist di `cod_prestazione` (510605, 521000, 779381, ecc.). Best effort, lascia null se non determinabile.

## Categorie di errore

Persistite in tabella `import_errors` (visibili a Salvatore in `/comune/<slug>/sinottici/errori`):

| categoria | severity | quando |
|---|---|---|
| `no_prefix` | error | T-row con `Foglio / Prestazione` non inizia per `<armadio>/` |
| `pattern_unknown` | error | Elemento non corrispondente ad alcun pattern noto |
| `overlap` | error | PTE rientra in 2+ giunti (range giunti sovrapposti = errore data entry) |
| `orphan` | warning | PTE non rientra in nessun giunto (forse il giunto non esiste ancora) |
| `spillato_not_found` | error | Edge spillato cita un giunto che non è stato importato |

## Cosa NON è ricavabile dall'Excel (e va aggiunto manualmente)

- **Tipo specifico di PTE** (Colonnina, Muro Esterno, Muro Interno, Interno Edificio, Sotterraneo, Business, Passed): nessun campo Excel lo dice. Salvatore lo imposta manualmente cliccando un nodo nel sinottico → dropdown salva su tabella `node_metadata`. La scelta **persiste tra reimport** (chiave: comune_id + armadio + label).
- **Stati colorati** (Collaudato/Giuntato/Posato/...): non implementati per scelta esplicita di Salvatore.

## Esempio reale (armadio `57400D_02`)

```
57400D_02 (armadio, root)
├── G1:4        → contiene PTE_1, PTE_2, PTE_3, PTE_4
├── G5:9        → PTE_5, PTE_7, PTE_8, PTE_9 (PTE_6 manca, non lavorato)
├── G10:18      → PTE_16
├── G19:39      → PTE_19, PTE_23, PTE_24, PTE_20:22
├── G40:47      → PTE_42, PTE_45, PTE_46, PTE_47, PTE_40:41, PTE_43:44
├── G49:55      → PTE_49–55
├── G56:59      → PTE_56, PTE_58, PTE_59
├── G57:61      → PTE_57, PTE_60, PTE_61            ⚠ overlap con G56:59 su 57-59
├── G62:70      → PTE_62–67, PTE_70, PTE_62:63
├── G71:79      → PTE_73, PTE_74, PTE_75
└── AO

Edge spillato:
  G19:39  ←spillato→  G19:24  ⚠ G19:24 NON esiste (atteso; data entry tipo, da correggere)
```

Stato errori per `57400D_02` al 2026-05-02: 6 errori (3 overlap su PTE_57/58/59, 2 no_prefix per `57400D_7902` e `ARLO 02 CENTRALE PRIMARIA`, 1 spillato_not_found).

## Riferimento implementativo

- Parser: `src/lib/server/topology/parser.ts` (regex)
- Builder: `src/lib/server/topology/builder.ts` (assemblaggio grafo + errori)
- Persistenza: `src/lib/server/repositories/{nodes,edges,nodeMetadata,importErrors}.ts`
- Wiring nell'import: `src/lib/server/importService.ts` (chiamato dopo `rowsRepo.insertBatch`)
- Test: `src/lib/server/topology/parser.test.ts`, `builder.test.ts` + smoke nell'`importService.test.ts`
