---
name: fak-tariff-comparison
description: Confronto tariffe FAK (Freight All Kinds) per Cippà Trasporti — parsifica PDF/CSV/XLSX di vari carrier (Evergreen, Cosco, MSC, Maersk), genera confronto, report testuale e dashboard HTML. Sistema a circuito privato con aggiornamento ogni 15 giorni.
category: productivity
---

# FAK Tariff Comparison — Cippà Trasporti

Confronto automatico delle tariffe FAK (Freight All Kinds) tra carrier sulla tratta **Far East → Italia**. Sistema a circuito privato per Kuki (CEO Cippà Trasporti), aggiornato ogni 15 giorni via file PDF/CSV/XLSX.

## Trigger

Usa questa skill quando:

- Kuki invia file di tariffe (PDF, CSV, XLSX) di compagnie di navigazione
- Kuki parla di **Evergreen, Cosco, MSC, Maersk, CMA CGM** o altri carrier
- Kuki chiede **confronto tariffe FAK** o **miglior quotazione** su una rotta
- Kuki chiede di **confrontare carrier** su POL/POD specifici (es. Shanghai → Genova)
- Kuki chiede di **calcolare arbitrary** (Bari, porti outports) o **soprattasse**
- Kuki dice "ogni 15 giorni inserisco nuove tariffe" — attivare il sistema di archiviazione
- Il sistema deve **parsare formati diversi** (PDF con tabelle, CSV semicolon, XLSX con multi-sheet)

## Architettura del Sistema

### Directory base

```
~/.hermes/fak-comparison/
├── archive/                          ← File storici, organizzati per periodo
│   └── YYYYMMDD-YYYYMMDD/           ← Cartella per ogni validità 15gg
│       ├── evergreen.csv             ← Maiuscole/minuscole irrilevante
│       ├── cosco.pdf
│       ├── msc.xlsx
│       └── maersk.pdf
├── compare_fak.py                    ← Script principale di parsificazione e confronto
├── comparison_data.json              ← Database JSON di tutte le tariffe
├── latest_report.txt                 ← Report testuale aggiornato
└── fak_dashboard.html                ← Dashboard HTML statica
```

### Flusso di lavoro

1. **Ricezione file** — Kuki invia i file via Telegram. Salvarli in `archive/PERIODO/`
2. **Esecuzione** — `python3 ~/.hermes/fak-comparison/compare_fak.py`
3. **Output** — `latest_report.txt` + `fak_dashboard.html` aggiornati

### Cron job

Ogni 15 giorni (1° e 16 del mese), il cron `fak-comparison-refresh` riesegue lo script. Lista con `cronjob action=list`. Periodo da cercare col nome `fak-comparison`.

## Parsificazione per Formato

### CSV (Evergreen — formato IT Guideline)

Il CSV Evergreen usa `;` come delimitatore e ha la struttura:

```
POL;POD_NAME;;GENOVA;;LA SPEZIA;;VENEZIA;;...
;;20';40';40H;20';40';40H;20';40';40H;...
CNSHG;SHANGHAI;3800;5400;5400;3800;5400;5400;...
```

- **Riga 0**: header con i POD (ogni 3 colonne: 20', 40', 40H)
- **Riga 1**: sottotitolo container
- **Righe 2+**: dati per POL
- **Righe finali**: note/avvertenze (da saltare — iniziano con numeri e punto es. "1. Rates are...")

**⚠️ Pitfall encoding**: Il file è Non-ISO extended-ASCII. Usare `encoding='utf-8-sig', errors='replace'` nella open.

### PDF (Cosco e altri)

Il PDF Cosco è compresso con FlateDecode. Strategie per ordine di efficacia:

1. **PyMuPDF (fitz) con coordinate** — Migliore per tabelle strutturate. Estrarre la tabella con coordinate rettangolo, leggere cella per cella:
   ```python
   import fitz
   doc = fitz.open(filepath)
   page = doc[0]
   # Disegnare rettangoli sulle celle, estrarre testo per cella
   tabs = page.find_tables()
   for tab in tabs:
       for row in tab.extract():
           # row è una lista di celle
   ```
   **Vantaggio**: preserva la struttura tabellare anche su PDF con layout complessi.

2. **`pdftotext -layout`** — estrae testo con layout preservato. Funziona bene su PDF text-based (non scansionati).

3. **Cercare pattern** — codici porto (5-6 lettere, tipo CNSHG) seguiti da numeri a 3-5 cifre. Fallback se (1) e (2) falliscono.

**⚠️ Pitfall**: I PDF di tabelle tariffarie possono avere intestazioni su righe multiple (es. "GENOVA/LA SPEZIA" come header unico che copre più colonne). PyMuPDF è l'unico metodo che gestisce questo caso.

**⚠️ Gruppi tariffari Cosco**: I POD italiani sono spesso raggruppati in 4 gruppi con stesso prezzo:
- Gruppo 1: GENOVA, LA SPEZIA (tariffa base)
- Gruppo 2: VENEZIA, TRIESTE, KOPER, RIJEKA (+$100 su 20', +$200 su 40')
- Gruppo 3: RAVENNA, ANCONA, NAPOLI (+$50 su 20', +$100 su 40')
- Gruppo 4: LIVORNO, SALERNO (+$150 su 20', +$200 su 40')
- BARI: arbitrary +$250/20' +$500/40' (applicato su GENOVA)
- AUGUSTA: arbitrary +$425/20' +$850/40' (applicato su LA SPEZIA)

### XLSX (MSC, Maersk, altri)

Usare `pandas.read_excel()` con i percorsi python:
```python
import sys
sys.path.append('/home/oem/.local/lib/python3.14/site-packages')
import pandas as pd
df = pd.read_excel(filepath, sheet_name=0)
```

**⚠️ XLSX può contenere tariffe inland (trasporto feeder/rail in Cina) invece di oceaniche FAK.** 
Sempre ispezionare la prima riga e le intestazioni — se vedi "ARB" nel nome file (Arbitrary), o porti inland cinesi (Nanjing, Wuhan, Chongqing), probabilmente non sono tariffe oceaniche da confrontare. Verificare con Kuki prima di scartare.

**⚠️ Multi-sheet**: Usare `pd.ExcelFile(filepath).sheet_names` per elencare tutti i fogli, poi esaminare ciascuno.

## Confronto e Reportistica

### Stessa rotta tra carrier

Caricare tutte le tariffe in `comparison_data.json` con struttura:

```json
{
  "rates": {
    "evergreen": {
      "CNSHG:GENOVA": {"pol": "CNSHG", "pol_name": "SHANGHAI", "pod": "GENOVA", "20": 3800, "40": 5400, "40h": 5400},
      ...
    },
    "cosco": { ... },
    "msc": { "forfettario": { "west_med_20": 3750, "west_med_40": 5500 } }
  },
  "surcharges": { ... }
}
```

### Calcolo arbitrary Bari (Cosco)

Se il POD è BARI su tariffe Cosco, aggiungere:
- 20': +$250
- 40'/40H: +$500

### Tabella comparativa

Mostrare per ogni rotta comune: POL, POD, prezzi per carrier, miglior carrier e differenza. Ordinare per risparmio decrescente.

### Soprattasse da segnalare

- **Evergreen**: ISOCC $89/TEU, EUIS €72/TEU, LSS $20/TEU, THC €205/cntr, ISPS €20/cntr, HWCS $200-400 (Adriatico, >18t)
- **Cosco**: ETS €57/TEU, EFS $150/TEU (porti non-base), arbitrary Bari $250/$500
- **MSC**: GFS VATOS $225/TEU (giugno 2026, incluso nelle tariffe pubblicate)

## Web App Flask (alternativa interattiva)

Vedi `references/flask-webapp.md` per la documentazione completa della web app Flask su porta 5000.

Quando Kuki chiede di creare qualcosa di interattivo e condivisibile con i colleghi, **preferire la web app Flask** rispetto alla dashboard statica — permette richieste live via form nel browser senza dover ricontattare l'agente.

## Ricerca tariffe via web

Alcuni carrier (MSC, CMA CGM) pubblicano FAK rates su container-news.com e sui propri siti. Maersk e altri richiedono contatto sales. Strategia:

1. Cercare su web: `"MSC" "FAK rates" "Far East" "Italy" "20'"`
2. Cercare su `container-news.com` — spesso pubblica riassunti tariffari
3. Per Maersk: `site:maersk.com "Far East" "North Europe" "rate announcement" "June"`
4. **⚠️ NOTA**: le tariffe pubbliche sono spesso senza dettaglio POL→POD, servono come riferimento ma per precisione chiedere file diretto a Kuki

## FAK Read-Only API Server (per agenti tool-limitati)

Per agenti che hanno solo `web` + `clarify` (e nessun accesso file/tools), esiste un server API HTTP sulla porta **8081** che espone i dati FAK in sola lettura.

Vedi `references/fak-readonly-api.md` per endpoint e uso.

**Pattern generale:** quando un agente non può leggere file, esporre i dati via un mini HTTP server JSON su una porta non pubblica e interrogarli via `web_extract`.

### Endpoint

| Endpoint | Descrizione |
|----------|-------------|
| `GET /fak` | Tutti i dati FAK |
| `GET /fak/search?q=SHANGHAI` | Cerca per POL/POD |
| `GET /fak/carrier/evergreen` | Solo un carrier |

### Come lo usa l'agente

```python
result = web_extract(urls=["http://localhost:8081/fak/search?q=SHANGHAI"])
result = web_extract(urls=["http://localhost:8081/fak/carrier/cosco"])
```

### Schema dello script

Il server è `/home/oem/.hermes/fak-api.py` e si avvia con:
```bash
cd /home/oem/.hermes && python3 fak-api.py &
```

## Esporre la dashboard via Tailscale Funnel (preferito)

Tailscale Funnel è più affidabile di Cloudflare TryCloudflare. URL fisso e HTTPS nativo.

**Attivazione singola per servizio:**

```bash
# Disattiva serve se attivo
sudo tailscale serve --https=443 off

# Attiva funnel sulla dashboard statica (porta 8080)
sudo tailscale funnel --bg 8080

# Verifica
sudo tailscale funnel status
```

**Output atteso:**
```
# Funnel on:
#     - https://MACHINE_NAME.TAILNET.ts.net

https://MACHINE_NAME.TAILNET.ts.net (Funnel on)
|-- / proxy http://127.0.0.1:8080
```

**URL stabile:** `https://MACHINE_NAME.TAILNET.ts.net/emma-dashboard.html`

**NOTA**: Tailscale Funnel espone su **internet pubblico** (chiunque, anche senza Tailscale). Per esporre solo dentro il tailnet (solo utenti con Tailscale), usare `tailscale serve` invece di `funnel`.

**Per la web app FAK (porta 5000)**: al momento non è esposta via Funnel. Se serve, attivare un secondo funnel sulla porta 5000 (richiede un secondo nome host o cambiare porta del funnel).

**Se l'URL non funziona**, i motivi più comuni:
1. Il PC usa un firewall aziendale che blocca domini esterni
2. Tailscale non è aggiornato all'ultima versione
3. Il tunnel funnel si è disattivato (verificare con `sudo tailscale funnel status`)

**Workaround per PC aziendali con firewall**: inviare la dashboard come file HTML standalone (vedi `references/standalone-html-inline-data.md`).

### Ticket system — lista multi-ticket nella dashboard

Il sistema ticket ora supporta **lista di ticket** invece di un singolo ticket attivo.

**Struttura dati in `dashboard_data.json`:**
```json
{
  "tickets": {
    "lista": [
      {
        "id": "TKT-001",
        "titolo": "Talent Risk Management",
        "stato": "APERTO",
        "priorità": "alta",
        "assegnato_a": "Emma",
        "completati": 8,
        "deliverables": 4
      },
      {
        "id": "TKT-002",
        "titolo": "Dashboard FAK Web App — URL",
        "stato": "APERTO",
        "priorità": "alta",
        "assegnato_a": "Emma, Superboy"
      }
    ],
    "totali": 2,
    "ultimo_aggiornamento": "2026-06-17 13:48"
  }
}
```

**Funzione `load_tickets()` in `dashboard_refresh.py`:**
- Legge TUTTI i file `.json` dalla cartella `~/.hermes/tickets/`
- Supporta sia formato `{"ticket": {...}}` che flat `{"id": ..., "titolo": ...}`
- Restituisce array nell'ordine inverso (più recenti prima)

**Rendering nella Emma Dashboard HTML:**
- Ogni ticket è un blocco separato da linea sottile
- Mostra badge stato (verde per aperto, giallo per altro) e priorità (rosso alta, giallo media, blu bassa)
- Include ID, titolo e assegnatario
- **NOTA**: la vecchia struttura `ticket_attivo` è DEPRECATA. Sostituire con `lista`.

### Symlink per dashboard HTML — ricreare dopo ogni rigenerazione

Dopo aver rigenerato `fak_dashboard.html` con `gen_dashboard.py`, il symlink per il server HTTP va ricreato:

```bash
ln -sf ~/.hermes/fak-comparison/fak_dashboard.html ~/.hermes/fak_dashboard.html
```

Il server HTTP serve dalla directory `~/.hermes/` — senza symlink, `/fak_dashboard.html` restituisce **HTTP 404**.

**Verifica:**
```bash
curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:8080/fak_dashboard.html
# Deve dare HTTP 200
```

Stessa cosa per `emma-dashboard.html` (ma quello viene modificato in-place, non serve symlink).

### 6. Large HTML generation — evitare troncamento

La funzione `write_file` (o Python `f.write()`) può troncare file molto grandi se generati come stringa Python unica. **Sintomo**: il file HTML generato finisce a 5-17 KB invece dei 100+ KB attesi, con codice incompleto.

**Causa**: usare f-string multi-riga Python per l'intero HTML (la lunghezza eccede i limiti interni di scrittura).

**✅ Soluzione**: costruire l'HTML per concatenazione di stringhe brevi, NON con un unico f-string multilinea:

```python
# ❌ SBAGLIATO — rischia troncamento
html = f\"\"\"
<!DOCTYPE html>
<html>
... 50+ righe di template ...
</html>\"\"\"

# ✅ CORRETTO — concatenare segmenti
html = '<!DOCTYPE html>\\n'
html += '<html lang=\"it\">\\n<head>\\n'
html += '<meta charset=\"UTF-8\">\\n'
# ... aggiungere ogni blocco separatamente ...

with open(output_path, 'w') as f:
    f.write(html)
```

**Verifica**: sempre controllare che il file termini con `</html>`:
```bash
tail -c 20 output.html
```

**JS inline**: se il JS è complesso, usare stringhe corte concatenate con `\\n` e attenzione agli apici dentro i template. Inline JS > 500 caratteri conviene spezzarlo in blocchi logici (es. `html += js_func1 + '\\n' + js_func2`).

Flask web app è installata su porta 5000. Vedi `references/flask-webapp.md`.

## Cloudflare Tunnel — pattern multi-porta

**⚠️ PREFERIRE TAILSCALE FUNNEL** (vedi `references/tailscale-funnel.md`) rispetto a Cloudflare TryCloudflare — URL stabile, nessun timeout, HTTPS nativo.

Quando si espongono più servizi (es. dashboard statica su 8080 e web app su 5000), usare tunnel SEPARATI con porte metrics DIVERSE:

```bash
# Servizio 1: dashboard statica (porta 8080, metrics 54321)
/tmp/cloudflared tunnel --url http://localhost:8080 --no-autoupdate --metrics 0.0.0.0:54321

# Servizio 2: web app Flask (porta 5000, metrics 54322)
/tmp/cloudflared tunnel --url http://localhost:5000 --no-autoupdate --metrics 0.0.0.0:54322
```

Per ottenere l'URL di ciascuno:
```bash
curl -s http://localhost:METRICS_PORT/quicktunnel | python3 -c "import sys,json; print(json.load(sys.stdin).get('hostname',''))"
```

## Python Pitfalls (dallo sviluppo)

### encoding='utf-8-sig' non basta

File con byte non UTF-8 (codepage 1252 o simili):
```python
# ✅ Usare errors='replace'
with open(path, 'r', encoding='utf-8-sig', errors='replace') as f:
```

### list.append() NON accetta keyword arguments

```python
# ❌ SBAGLIATO
lines.append("text", end="")

# ✅ CORRETTO — concatenare prima, poi append
line = f"{'POL':<25} {'Tipo':<6}"
lines.append(line)
```

### Type checking su strutture dati miste

I surcharges possono essere dict, list, o valori scalari:
```python
if isinstance(info, dict):
    amount = info.get("amount", "")
elif isinstance(info, list):
    # lista di voci
else:
    # scalare
```

### pdftotext

Disponibile sulla macchina. Non sempre produce tabelle pulite — serve fallback a pattern matching.

### Kuki's communication preference

Quando Kuki chiede **una valutazione specifica** (es. "dammi la migliore valutazione da Shanghai a Genova per 40'HC"), vuole:
- **Risposta diretta e concisa** — il numero, il carrier migliore, la differenza. Non riesporre tutto il report.
- **Tabella compatta** con solo la rotta richiesta, non l'intero confronto.
- Se ci sono soprattasse da considerare (es. arbitrary Bari), menzionarle brevemente, non in elenco completo.
- Usa questo formato: nome carrier + prezzo + risparmio vs competitor. Fine.

Esempio ✅:
> **Cosco a $3,000 per 40'HC.** Risparmio: **$3,200** (52% vs Evergreen a $6,200 con add 45HC).

Non riesporre l'intera tabella delle soprattasse o il report completo — quello è già nella dashboard.

### Dashboard integration (Emma Dashboard)

Il sistema FAK si integra con la dashboard principale di Emma (`emma-dashboard.html`) in due modi:

**1. Dati nel JSON principale (`dashboard_data.json`):**

Lo script `~/.hermes/scripts/dashboard_refresh.py` include una funzione `load_fak_data()` che legge `comparison_data.json` e popola il campo `fak_comparison` nel JSON della dashboard.

**2. Widget nella Emma Dashboard HTML:**

Nell'HTML `emma-dashboard.html`, aggiungere una card con id `fak-count` e `fak-content` che mostra carrier, numero rotte, e link a `/fak_dashboard.html`.

**3. Generazione dashboard FAK dettagliata:**

Usare `~/.hermes/fak-comparison/gen_dashboard.py` — questo script genera la dashboard FAK interattiva. È separato da `compare_fak.py` perché richiede una logica JS complessa.

**4. Elementi chiave della dashboard FAK:**

- **Banner richiesta** (dorato, in alto) — input per POL, POD, tipo container, con datalist autocomplete. Bottone "Trova" che calcola subito il carrier migliore.
- **Risultato richiesta** — sezione che appare sotto il banner con il vincitore e il risparmio.
- **Statistiche** — numero rotte totali e per carrier.
- **Tab per carrier** — filtra per singolo carrier o tutti.
- **Tabella confronto** — tutte le rotte con prezzi affiancati, miglior carrier evidenziato in verde.
- **Soprattasse** — sezione collassabile in fondo.

**5. Symlink per accesso HTTP:**

La dashboard FAK dettagliata è in `~/.hermes/fak-comparison/fak_dashboard.html`. Il server HTTP serve da `~/.hermes/`. Creare un symlink:

```bash
ln -sf ~/.hermes/fak-comparison/fak_dashboard.html ~/.hermes/fak_dashboard.html
```

Poi accessibile via:
- LAN: `http://10.205.63.252:8080/fak_dashboard.html`
- Cloudflare: `https://<random>.trycloudflare.com/fak_dashboard.html`

**⚠️ Dopo ogni rigenerazione, il symlink va ricreato se il file source cambia percorso.**

### Cloudflared TryCloudflare — problemi di affidabilità

**TryCloudflare è inaffidabile** — i tunnel account-less non hanno garanzie di uptime e possono fallire con timeout. Sintomi:

- `context deadline exceeded (Client.Timeout exceeded while awaiting headers)` — impossibile connettersi
- L'URL cambia ogni riavvio del processo
- Il tunnel può cadere senza preavviso

**Workaround**:
1. Riavviare con porta metrics DIVERSA dall'ultima usata: `/tmp/cloudflared tunnel --url http://localhost:PORT --no-autoupdate --metrics 0.0.0.0:54323`
2. Se fallisce al primo tentativo, riprovare subito
3. **Soluzione permanente**: dominio Cloudflare fisso (es. dashboard.cippatrasporti.com)

### Ticket creation workflow

Quando l'utente chiede di creare un ticket per un problema infrastrutturale:

1. Identificare il problema e la priorità
2. Creare ticket file JSON in `~/.hermes/tickets/` con: id, titolo, priorità, stato, categoria, assegnato a, descrizione
3. Mostrare riepilogo all'utente
4. **Opzionale**: aggiornare il ticket via nuovo file o modificando il JSON esistente

### Web search for carrier rates (when files aren't available)

Alcuni carrier pubblicano comunicati tariffari su container-news.com e propri siti. Strategia:

- **MSC/CMA CGM**: cercare su container-news.com
- **Maersk**: `site:maersk.com "rate announcement" "Far East"`
- **Risultato**: tariffe forfettarie (senza dettaglio POL→POD). Utili come riferimento, non per confronto preciso.

### Consegna a Kuki

Il report si trova in `~/.hermes/fak-comparison/latest_report.txt`. La dashboard FAK dettagliata in `~/.hermes/fak-comparison/fak_dashboard.html` (accessibile via `fak_dashboard.html` sul server HTTP). Per accesso remoto, usare Cloudflare Tunnel.

Per mostrare su Telegram: usare `read_file()` su `latest_report.txt` e inviare il contenuto (se breve) o un riassunto.

### Stile di risposta per richieste specifiche

Quando Kuki chiede **una valutazione su una rotta specifica** (es. "dammi la migliore valutazione da Shanghai a Genova per 40'HC"):

- **Risposta diretta e concisa**: carrier migliore, prezzo, risparmio vs competitor. Punto.
- **Niente report completo**: la dashboard e il report file servono per approfondire, non da riportare in chat.
- **Soprattasse**: menzionare solo quelle rilevanti per quella specifica rotta (es. arbitrary Bari), non l'elenco completo.
- **Formato**: `[CARRIER] a $[PREZZO] — Risparmio: $[X] ([Y]% vs [COMPETITOR])`

Esempio:
> **Cosco a $3,000 per 40'HC.** Risparmio: **$3,200** (52% vs Evergreen a $6,200 con add 45HC).

Se chiede un confronto tra più carrier, usare tabella compatta di 3-4 righe massimo. Se chiede dettaglio soprattasse, allora entrare nel merito.
