+
    #2jE                        R t ^ RIt^ RIt^ RIHt ]P                  P                  R4      t]P                  P                  ]R4      t]P                  P                  ]R4      t	R t
R tR t]R	8X  d   ]
! 4       t]! ]4       R# R# )
uY   Genera dashboard FAK avanzata — multi-carrier, richieste integrate, filtri intelligentiN)datetimez~/.hermes/fak-comparisonzcomparison_data.jsonzfak_dashboard.htmlc                      \         P                  P                  \        4      '       d9   \	        \        4      ;_uu_ 4       p \
        P                  ! V 4      uuR R R 4       # R/ R/ R/ /#   + '       g   i     L; i)Nrates
surchargesmetadata)ospathexists	DATA_FILEopenjsonload)fs    1/home/oem/.hermes/fak-comparison/gen_dashboard.py	load_datar   
   sM    	ww~~i  )__99Q< _Rr:r:: _s   A**A:	c                 0   \        V P                  R / 4      P                  4       4      p/ pV F  pV R ,          P                  V/ 4      p\        V\        4      '       g   K4  VP                  4        F  w  rVWR9  d;   RVP                  RR4      RVP                  RR4      RVP                  RR4      /W%&   W2V,          9  d   / W%,          V&   RVP                  R4      RVP                  R4      RVP                  R4      /W%,          V&   K  	  K  	  W!3# )r   pol pol_namepod204040h)listgetkeys
isinstancedictitems)datacarriers
all_routescarrierrouteskeyrates   &      r   gather_all_routesr&      s   DHHWb)..01HJg""7B/&$''IC$#($((5*<j$((S]^`Jachjnjrjrsxy{j|"}
o-+-
((,dhhtndDHHTNTY[_[c[cdi[j'kJOG$ (	      c           	      &   \        V 4      w  r\        P                  ! 4       P                  R 4      p\        P
                  ! V4      p\        P
                  ! V4      p\        P
                  ! V P                  R/ 4      4      pRV RV RV RV R2	# )z%d/%m/%Y %H:%Mr   u  <!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>FAK Confronto — Cippà Trasporti</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; background:#0d1117; color:#c9d1d9; }

/* HEADER */
.header { background:linear-gradient(135deg,#161b22,#0d1117); border-bottom:1px solid #30363d; padding:16px 24px; }
.header h1 { color:#58a6ff; font-size:22px; }
.header h1 span { color:#8b949e; font-weight:400; font-size:13px; }
.header .sub { color:#8b949e; font-size:12px; margin-top:4px; }

/* SEZIONE RICHIESTA PREZZO */
.quote-section { background:linear-gradient(135deg,#1a2332,#0d1117); border:2px solid #d29922; border-radius:12px; margin:16px 24px; padding:20px; }
.quote-section .title { color:#d29922; font-size:18px; font-weight:700; margin-bottom:14px; }
.quote-section .title small { color:#8b949e; font-weight:400; font-size:12px; }
.quote-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(160px,1fr)); gap:10px; align-items:end; }
.quote-field { }
.quote-field label { display:block; color:#8b949e; font-size:11px; margin-bottom:3px; text-transform:uppercase; }
.quote-field input,.quote-field select { width:100%; background:#0d1117; border:1px solid #30363d; color:#c9d1d9; padding:8px 10px; border-radius:6px; font-size:14px; outline:none; }
.quote-field input:focus,.quote-field select:focus { border-color:#d29922; }
.quote-btn { background:#d29922; color:#0d1117; font-weight:700; border:none; padding:8px 20px; border-radius:6px; cursor:pointer; font-size:14px; height:38px; }
.quote-btn:hover { background:#e3b341; }
.quote-btn-secondary { background:#21262d; color:#c9d1d9; border:1px solid #30363d; padding:8px 16px; border-radius:6px; cursor:pointer; font-size:13px; height:38px; }
.quote-btn-secondary:hover { border-color:#8b949e; }

/* RISULTATO RICHIESTA */
.result-panel { background:#161b22; border:1px solid #30363d; border-radius:10px; margin:0 24px 14px; padding:18px 22px; display:none; }
.result-panel.show { display:block; }
.result-panel .row { display:flex; flex-wrap:wrap; gap:16px; align-items:center; }
.result-panel .winner { font-size:24px; font-weight:700; color:#3fb950; flex:1; min-width:200px; }
.result-panel .winner small { font-size:14px; color:#8b949e; }
.result-panel .prices { text-align:right; }
.result-panel .prices .item { display:inline-block; margin:0 8px; padding:4px 10px; border-radius:6px; font-size:13px; }
.result-panel .prices .item.best { background:#23863622; color:#3fb950; font-weight:700; }
.result-panel .prices .item.other { background:#21262d; color:#8b949e; }
.result-panel .saving { color:#d29922; font-weight:600; font-size:15px; margin-top:6px; }

/* STATS + FILTRI */
.container { padding:0 24px 16px; }
.stats { display:flex; gap:10px; flex-wrap:wrap; margin-bottom:12px; }
.stat-card { background:#161b22; border:1px solid #30363d; border-radius:8px; padding:10px 14px; flex:1; min-width:80px; text-align:center; }
.stat-card .num { font-size:20px; font-weight:700; color:#58a6ff; }
.stat-card .lbl { font-size:10px; color:#8b949e; text-transform:uppercase; }

.toolbar { display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-bottom:12px; }
.toolbar input,.toolbar select { background:#21262d; border:1px solid #30363d; color:#c9d1d9; padding:5px 10px; border-radius:6px; font-size:12px; outline:none; }
.toolbar input:focus,.toolbar select:focus { border-color:#58a6ff; }
.toolbar label { color:#8b949e; font-size:11px; }
.toolbar .btn { background:#238636; color:#fff; border:none; padding:5px 12px; border-radius:6px; cursor:pointer; font-size:12px; }
.toolbar .btn:hover { background:#2ea043; }
.toolbar .btn-outline { background:transparent; border:1px solid #30363d; color:#c9d1d9; }
.toolbar .btn-outline:hover { border-color:#58a6ff; color:#58a6ff; }
.toolbar .count { color:#8b949e; font-size:12px; flex:1; text-align:right; }

/* CARRIER TOGGLE PILLS */
.pills { display:flex; gap:4px; flex-wrap:wrap; margin-bottom:10px; }
.pill { background:#21262d; border:1px solid #30363d; color:#8b949e; padding:3px 12px; border-radius:16px; cursor:pointer; font-size:11px; user-select:none; }
.pill.active { background:#1f6feb33; border-color:#58a6ff; color:#58a6ff; }

/* TABELLA */
.tw { overflow-x:auto; }
table { width:100%; border-collapse:collapse; font-size:12px; }
th { background:#1c2333; color:#58a6ff; padding:6px 8px; text-align:left; border-bottom:2px solid #30363d; position:sticky; top:0; z-index:10; white-space:nowrap; }
td { padding:4px 8px; border-bottom:1px solid #21262d; }
tr:hover { background:#1c233322; }
.best-price { color:#3fb950; font-weight:700; }
.best-badge { display:inline-block; background:#23863622; color:#3fb950; padding:2px 6px; border-radius:4px; font-size:11px; font-weight:600; }
.empty { text-align:center; padding:30px; color:#484f58; }

/* SOPRATTASSE */
.detail { background:#161b22; border:1px solid #30363d; border-radius:8px; padding:14px; margin-top:14px; }
.detail h3 { color:#f0883e; font-size:15px; margin-bottom:8px; }
.sc-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(260px,1fr)); gap:8px; }
.sc-card { background:#21262d; border-radius:6px; padding:10px; }
.sc-card h4 { color:#58a6ff; font-size:12px; margin-bottom:4px; }
.sc-card .v { font-size:11px; }
.sc-card .n { color:#8b949e; font-size:10px; font-style:italic; }

.footer { text-align:center; color:#484f58; font-size:10px; border-top:1px solid #21262d; padding:10px; margin-top:14px; }

@media (max-width:700px) { .quote-grid { grid-template-columns:1fr 1fr; } }
</style>
</head>
<body>

<div class="header">
    <h1>🚢 FAK Confronto Tariffe <span>Cippà Trasporti</span></h1>
    <div class="sub">Far East → Italia · u	  </div>
</div>

<!-- SEZIONE RICHIESTA PREZZO -->
<div class="quote-section">
    <div class="title">🏆 Richiedi il miglior prezzo <small>su tutti i carrier disponibili</small></div>
    <div class="quote-grid">
        <div class="quote-field">
            <label>POL (Porto di partenza)</label>
            <input type="text" id="rq-pol" list="pol-list" placeholder="es. Shanghai, Ningbo...">
            <datalist id="pol-list"></datalist>
        </div>
        <div class="quote-field">
            <label>POD (Porto di destinazione)</label>
            <input type="text" id="rq-pod" list="pod-list" placeholder="es. Genova, Bari...">
            <datalist id="pod-list"></datalist>
        </div>
        <div class="quote-field">
            <label>Tipo container</label>
            <select id="rq-type">
                <option value="20">20' Standard</option>
                <option value="40" selected>40' Standard</option>
                <option value="40h">40' High Cube</option>
            </select>
        </div>
        <div style="display:flex;gap:6px;">
            <button class="quote-btn" onclick="findBestPrice()">🔍 Trova prezzo</button>
            <button class="quote-btn-secondary" onclick="clearQuote()">✕</button>
        </div>
    </div>
</div>

<!-- RISULTATO -->
<div class="result-panel" id="result-panel">
    <div id="result-content"></div>
</div>

<div class="container">

<div class="stats" id="stats-bar"></div>

<div class="pills" id="pills-bar"></div>

<div class="toolbar">
    <label>POL</label>
    <input type="text" id="f-pol" placeholder="es. Shanghai" oninput="filterRoutes()">
    <label>POD</label>
    <input type="text" id="f-pod" placeholder="es. Genova" oninput="filterRoutes()">
    <label>Tipo</label>
    <select id="f-type" onchange="filterRoutes()">
        <option value="20">20'</option>
        <option value="40" selected>40'</option>
        <option value="40h">40HC</option>
    </select>
    <button class="btn btn-outline" onclick="resetFilters()">✕ Reset</button>
    <span class="count" id="count-label"></span>
</div>

<div class="tw">
<table id="rtable">
    <thead><tr id="thead"></tr></thead>
    <tbody id="tbody"></tbody>
</table>
</div>

<div class="detail">
    <h3>💰 Soprattasse per Carrier</h3>
    <div class="sc-grid" id="sc-grid"></div>
</div>

</div>

<div class="footer">
    FAK Comparison System v3.0 · Multi-Carrier · Circuito Privato Cippà Trasporti · 🦊 Emma
</div>

<script>
const ROUTES = z;
const CARRIERS = z;
const SURCHARGES = u  ;
let activePills = new Set(CARRIERS);

function populateLists() {
    const ps = new Set(), ds = new Set();
    Object.values(ROUTES).forEach(r => { if (r.pol_name) ps.add(r.pol_name); if (r.pol) ps.add(r.pol); if (r.pod) ds.add(r.pod); });
    document.getElementById('pol-list').innerHTML = [...ps].sort().map(p => `<option value="${p}">`).join('');
    document.getElementById('pod-list').innerHTML = [...ds].sort().map(p => `<option value="${p}">`).join('');
}

function fmt(v) { return v!=null ? '$'+Number(v).toLocaleString() : '—'; }

// Trova miglior prezzo
function findBestPrice() {
    const pol = document.getElementById('rq-pol').value.trim().toUpperCase();
    const pod = document.getElementById('rq-pod').value.trim().toUpperCase();
    const ctype = document.getElementById('rq-type').value;
    if (!pol||!pod) { alert('Inserisci POL e POD'); return; }
    
    let matches = [];
    Object.entries(ROUTES).forEach(([k,r]) => {
        const pm = (r.pol_name||'').toUpperCase().includes(pol)||(r.pol||'').toUpperCase().includes(pol);
        const dm = (r.pod||'').toUpperCase().includes(pod);
        if (pm&&dm) CARRIERS.forEach(c => { const v=r[c]?r[c][ctype]:null; if(v) matches.push({carrier:c,price:v,pol:r.pol_name||r.pol,pod:r.pod}); });
    });
    
    const panel = document.getElementById('result-panel');
    const content = document.getElementById('result-content');
    
    if (!matches.length) {
        content.innerHTML = '<div style="color:#f85149;font-weight:600;">❌ Nessuna tariffa per questa rotta. Prova altri termini.</div>';
        panel.classList.add('show'); return;
    }
    
    const best = matches.reduce((a,b)=>a.price<b.price?a:b);
    const worst = matches.reduce((a,b)=>a.price>b.price?a:b);
    const saving = worst.price - best.price;
    
    let html = `<div class="row"><div class="winner">🏆 ${best.carrier.toUpperCase()} — ${fmt(best.price)}<br><small>${best.pol} → ${best.pod} · ${ctype}'</small></div><div class="prices">`;
    matches.sort((a,b)=>a.price-b.price).forEach(p => {
        const cls = p.price===best.price?'best':'other';
        html += `<span class="item ${cls}">${p.carrier.toUpperCase()} ${fmt(p.price)}${p.price===best.price?' ✅':''}</span>`;
    });
    if (saving>0) html += `<div class="saving">💰 Risparmio scegliendo ${best.carrier.toUpperCase()}: ${fmt(saving)}</div>`;
    html += '</div></div>';
    content.innerHTML = html;
    panel.classList.add('show');
    panel.scrollIntoView({behavior:'smooth',block:'center'});
}

function clearQuote() {
    document.getElementById('rq-pol').value=''; document.getElementById('rq-pod').value='';
    document.getElementById('result-panel').classList.remove('show');
}

// Stats
function renderStats() {
    const total = Object.keys(ROUTES).length;
    const cnt = {}; CARRIERS.forEach(c=>cnt[c]=0);
    Object.values(ROUTES).forEach(r => CARRIERS.forEach(c=>{if(r[c])cnt[c]++;}));
    let h = `<div class="stat-card"><div class="num">${total}</div><div class="lbl">Rotte totali</div></div>`;
    CARRIERS.forEach(c => h+=`<div class="stat-card"><div class="num" style="color:#3fb950">${cnt[c]||0}</div><div class="lbl">${c.toUpperCase()}</div></div>`);
    document.getElementById('stats-bar').innerHTML = h;
}

// Pills
function renderPills() {
    let h = `<span class="pill ${activePills.size===CARRIERS.length?'active':''}" onclick="toggleAllPills()">📊 Tutti</span>`;
    CARRIERS.forEach(c => h+=`<span class="pill ${activePills.has(c)?'active':''}" onclick="togglePill('${c}')">${c.toUpperCase()}</span>`);
    document.getElementById('pills-bar').innerHTML = h;
}

function togglePill(c) {
    if (activePills.has(c)) activePills.delete(c); else activePills.add(c);
    if (activePills.size===0) activePills = new Set(CARRIERS);
    renderPills(); filterRoutes();
}

function toggleAllPills() {
    activePills = activePills.size===CARRIERS.length ? new Set() : new Set(CARRIERS);
    if (activePills.size===0) activePills = new Set(CARRIERS);
    renderPills(); filterRoutes();
}

function resetFilters() {
    document.getElementById('f-pol').value=''; document.getElementById('f-pod').value='';
    filterRoutes();
}

function filterRoutes() {
    const pol = document.getElementById('f-pol').value.toLowerCase().trim();
    const pod = document.getElementById('f-pod').value.toLowerCase().trim();
    const ctype = document.getElementById('f-type').value;
    const target = [...activePills];
    
    let entries = Object.entries(ROUTES).filter(([k,r]) => {
        if (pol && !(r.pol_name||r.pol||'').toLowerCase().includes(pol)) return false;
        if (pod && !(r.pod||'').toLowerCase().includes(pod)) return false;
        return target.some(c => r[c] && r[c][ctype]);
    });
    
    entries.sort((a,b)=>((a[1].pol_name||a[1].pol)||'').localeCompare((b[1].pol_name||b[1].pol)||'')||((a[1].pod||'')).localeCompare((b[1].pod||'')));
    
    document.getElementById('count-label').textContent = entries.length+' rotte';
    
    let hh = '<th>POL → POD</th>';
    target.forEach(c => hh += `<th>${c.toUpperCase()} ${ctype}'</th>`);
    hh += '<th>🏆 Migliore</th><th>💰 Risparmio</th>';
    document.getElementById('thead').innerHTML = hh;
    
    let body = '';
    if (!entries.length) {
        body = '<tr><td colspan="'+(target.length+3)+'" class="empty">Nessuna rotta trovata</td></tr>';
    } else {
        entries.forEach(([k,r]) => {
            const pn = r.pol_name||r.pol||'?', dn = r.pod||'?';
            body += `<tr><td><strong>${pn}</strong> → ${dn}</td>`;
            let prices = [];
            target.forEach(c => { const v=r[c]?r[c][ctype]:null; if(v) prices.push({c,v}); body+=`<td>${fmt(v)}</td>`; });
            if (prices.length>1) {
                const b=prices.reduce((a,b)=>a.v<b.v?a:b), w=prices.reduce((a,b)=>a.v>b.v?a:b);
                body+=`<td><span class="best-badge">✅ ${b.c.toUpperCase()} ${fmt(b.v)}</span></td><td class="best-price">${fmt(w.v-b.v)}</td>`;
            } else if (prices.length===1) {
                body+=`<td><span class="best-badge">${prices[0].c.toUpperCase()} ${fmt(prices[0].v)}</span></td><td>—</td>`;
            } else { body+='<td>—</td><td>—</td>'; }
            body += '</tr>';
        });
    }
    document.getElementById('tbody').innerHTML = body;
}

// Surcharges
function renderSurcharges() {
    let h = '';
    Object.entries(SURCHARGES).forEach(([carrier,sc]) => {
        h+=`<div class="sc-card"><h4>${carrier.toUpperCase()}</h4>`;
        Object.entries(sc).forEach(([name,info]) => {
            if (typeof info==='object'&&!Array.isArray(info)) {
                const a=info.amount||'', n=info.note||'', u=info.unit||'', c=info.currency||'';
                h+=`<div class="v">• <strong>${name.toUpperCase()}</strong>: ${a}${c?' '+c:''}${u?'/'+u:''}</div>`;
                if (n) h+=`<div class="n">${n}</div>`;
            } else if (Array.isArray(info)) {
                h+=`<div class="v">• ${name.toUpperCase()}: ${info.join(', ')}</div>`;
            } else { h+=`<div class="v">• ${name.toUpperCase()}: ${info}</div>`; }
        });
        h+='</div>';
    });
    document.getElementById('sc-grid').innerHTML = h;
}

populateLists(); renderStats(); renderPills(); filterRoutes(); renderSurcharges();
</script>
</body>
</html>)r&   r   nowstrftimer   dumpsr   r   OUTPUT_FILEwritehtmlprint)r   r!   r    r)   routes_jsoncarriers_jsonsurcharges_jsonr   s   &       r   
build_htmlr3      s    ,T2J
,,.
!
!"2
3C**Z(KJJx(Mjj,!;<O\-x .1E M2Z }  !#$ Y%WD Dr'   __main__)__doc__r   r   r   r   
expanduserBASEjoinr
   r,   r   r&   r3   __name__r    r'   r   <module>r;      s|    _  	ww45GGLL56	ggll4!56; O5b
 z;Dt r'   