<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MILETRUTH &mdash; Logistics Intelligence</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link
        href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Syne:wght@400;600;700;800&display=swap"
        rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
    <style>
        :root {
            --bg: #080b10;
            --surface: #0f1419;
            --surface2: #161c25;
            --border: #1e2733;
            --border2: #2a3545;
            --accent: #00d4ff;
            --accent2: #7c3aed;
            --green: #00e57a;
            --amber: #ffaa00;
            --red: #ff4466;
            --text: #e2e8f0;
            --muted: #64748b;
            --mono: 'DM Mono', monospace;
            --display: 'Syne', sans-serif;
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box
        }

        body {
            font-family: var(--display);
            background: var(--bg);
            color: var(--text);
            min-height: 100vh;
            overflow-x: hidden;
        }

        /* ── SCANLINE OVERLAY ── */
        body::before {
            content: '';
            position: fixed;
            inset: 0;
            pointer-events: none;
            z-index: 0;
            background: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0, 212, 255, .012) 2px, rgba(0, 212, 255, .012) 4px);
        }

        /* ── HEADER ── */
        header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 1rem 2rem;
            border-bottom: 1px solid var(--border);
            background: rgba(8, 11, 16, .9);
            backdrop-filter: blur(8px);
            position: sticky;
            top: 0;
            z-index: 100;
        }

        .logo {
            display: flex;
            align-items: center;
            gap: .75rem;
            font-size: 1.1rem;
            font-weight: 800;
            letter-spacing: .15em;
            color: var(--accent);
            text-transform: uppercase;
        }

        .logo-dot {
            width: 8px;
            height: 8px;
            border-radius: 50%;
            background: var(--green);
            animation: pulse 2s ease-in-out infinite;
        }

        @keyframes pulse {

            0%,
            100% {
                opacity: 1;
                box-shadow: 0 0 0 0 rgba(0, 229, 122, .4)
            }

            50% {
                opacity: .7;
                box-shadow: 0 0 0 6px rgba(0, 229, 122, 0)
            }
        }

        .header-right {
            display: flex;
            align-items: center;
            gap: .75rem
        }

        .status-chip {
            display: flex;
            align-items: center;
            gap: .4rem;
            font-family: var(--mono);
            font-size: .7rem;
            padding: .3rem .75rem;
            border-radius: 2px;
            background: rgba(0, 229, 122, .1);
            border: 1px solid rgba(0, 229, 122, .25);
            color: var(--green);
        }

        .status-chip.warn {
            background: rgba(255, 170, 0, .1);
            border-color: rgba(255, 170, 0, .25);
            color: var(--amber)
        }

        .status-chip.err {
            background: rgba(255, 68, 102, .1);
            border-color: rgba(255, 68, 102, .25);
            color: var(--red)
        }

        /* ── BUTTONS ── */
        .btn {
            font-family: var(--display);
            font-size: .75rem;
            font-weight: 700;
            letter-spacing: .1em;
            text-transform: uppercase;
            padding: .5rem 1.25rem;
            border-radius: 2px;
            cursor: pointer;
            border: 1px solid;
            transition: all .15s;
            display: inline-flex;
            align-items: center;
            gap: .5rem;
        }

        .btn-primary {
            background: var(--accent);
            border-color: var(--accent);
            color: #000
        }

        .btn-primary:hover {
            background: #33dcff;
            border-color: #33dcff
        }

        .btn-primary:disabled {
            background: var(--muted);
            border-color: var(--muted);
            cursor: not-allowed
        }

        .btn-ghost {
            background: transparent;
            border-color: var(--border2);
            color: var(--muted)
        }

        .btn-ghost:hover {
            border-color: var(--accent);
            color: var(--accent)
        }

        .btn-danger {
            background: transparent;
            border-color: rgba(255, 68, 102, .4);
            color: var(--red)
        }

        .btn-danger:hover {
            background: rgba(255, 68, 102, .1)
        }

        /* ── LAYOUT ── */
        main {
            padding: 1.5rem 2rem;
            max-width: 1600px;
            margin: 0 auto;
            position: relative;
            z-index: 1
        }

        .section-label {
            font-family: var(--mono);
            font-size: .65rem;
            font-weight: 500;
            color: var(--muted);
            letter-spacing: .15em;
            text-transform: uppercase;
            margin-bottom: .75rem;
            padding-left: .25rem;
            border-left: 2px solid var(--accent);
            padding-left: .5rem;
        }

        /* ── STAT CARDS ── */
        .stat-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
            gap: 1px;
            background: var(--border);
            border: 1px solid var(--border);
            margin-bottom: 1.5rem;
        }

        .stat-card {
            background: var(--surface);
            padding: 1.25rem 1.5rem;
        }

        .stat-label {
            font-family: var(--mono);
            font-size: .65rem;
            color: var(--muted);
            text-transform: uppercase;
            letter-spacing: .1em;
            margin-bottom: .5rem;
        }

        .stat-value {
            font-family: var(--mono);
            font-size: 1.75rem;
            font-weight: 500;
            line-height: 1;
            margin-bottom: .4rem;
        }

        .stat-sub {
            font-size: .7rem;
            color: var(--muted);
            font-family: var(--mono)
        }

        .v-green {
            color: var(--green)
        }

        .v-amber {
            color: var(--amber)
        }

        .v-red {
            color: var(--red)
        }

        .v-accent {
            color: var(--accent)
        }

        /* ── DASHBOARD GRID ── */
        .dash-grid {
            display: grid;
            grid-template-columns: 1fr 1fr 1fr;
            gap: 1px;
            background: var(--border);
            border: 1px solid var(--border);
            margin-bottom: 1.5rem;
        }

        .dash-grid.wide {
            grid-template-columns: 2fr 1fr
        }

        .dash-grid.tri {
            grid-template-columns: 1fr 1fr 1fr
        }

        .dash-grid.quad {
            grid-template-columns: 1fr 1fr 1fr 1fr
        }

        .dash-grid.full {
            grid-template-columns: 1fr
        }

        .panel {
            background: var(--surface);
            padding: 1.25rem;
            min-height: 200px;
        }

        .panel-title {
            font-family: var(--mono);
            font-size: .65rem;
            color: var(--muted);
            text-transform: uppercase;
            letter-spacing: .12em;
            margin-bottom: 1rem;
            display: flex;
            align-items: center;
            justify-content: space-between;
        }

        /* ── CHARTS ── */
        .chart-wrap {
            position: relative;
            width: 100%;
            height: 220px
        }

        .chart-wrap.tall {
            height: 300px
        }

        /* ── TABLE ── */
        .ml-table {
            width: 100%;
            border-collapse: collapse
        }

        .ml-table th {
            font-family: var(--mono);
            font-size: .6rem;
            color: var(--muted);
            text-transform: uppercase;
            letter-spacing: .1em;
            padding: .5rem .75rem;
            border-bottom: 1px solid var(--border);
            text-align: left;
            background: var(--surface2);
        }

        .ml-table td {
            font-family: var(--mono);
            font-size: .75rem;
            padding: .6rem .75rem;
            border-bottom: 1px solid var(--border);
        }

        .ml-table tr:hover td {
            background: rgba(0, 212, 255, .03)
        }

        .tbl-scroll {
            max-height: 280px;
            overflow-y: auto
        }

        /* ── PARAM ROW ── */
        .param-row {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: .5rem 0;
            border-bottom: 1px solid var(--border);
            gap: 1rem;
        }

        .param-row:last-child {
            border: none
        }

        .param-name {
            font-family: var(--mono);
            font-size: .7rem;
            color: var(--text)
        }

        .param-val {
            font-family: var(--mono);
            font-size: .8rem;
            color: var(--accent);
            font-weight: 500
        }

        .param-src {
            font-size: .6rem;
            font-family: var(--mono);
            padding: .15rem .4rem;
            border-radius: 2px;
            background: rgba(124, 58, 237, .2);
            color: #a78bfa;
        }

        .param-src.default {
            background: rgba(100, 116, 139, .15);
            color: var(--muted)
        }

        /* ── RULES LIST ── */
        .rule-item {
            font-family: var(--mono);
            font-size: .68rem;
            color: var(--muted);
            padding: .4rem .5rem;
            border-left: 2px solid var(--border2);
            margin-bottom: .25rem;
            line-height: 1.5;
        }

        .rule-item.success {
            border-color: var(--green);
            color: rgba(0, 229, 122, .8)
        }

        .rule-item.risk {
            border-color: var(--red);
            color: rgba(255, 68, 102, .8)
        }

        .rules-scroll {
            max-height: 260px;
            overflow-y: auto
        }

        /* ── HEATMAP ── */
        .heatmap-row {
            display: flex;
            gap: 2px;
            align-items: center;
            margin-bottom: 2px
        }

        .heatmap-label {
            font-family: var(--mono);
            font-size: .6rem;
            color: var(--muted);
            width: 28px;
            text-align: right;
            margin-right: 4px
        }

        .heatmap-cell {
            flex: 1;
            height: 22px;
            border-radius: 1px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: var(--mono);
            font-size: .55rem;
            cursor: pointer;
            transition: .1s;
        }

        .heatmap-cell:hover {
            opacity: .8;
            transform: scaleY(1.1)
        }

        /* ── BADGE ── */
        .badge {
            display: inline-block;
            padding: .15rem .5rem;
            border-radius: 2px;
            font-family: var(--mono);
            font-size: .65rem;
            font-weight: 500;
        }

        .badge-green {
            background: rgba(0, 229, 122, .15);
            color: var(--green)
        }

        .badge-amber {
            background: rgba(255, 170, 0, .15);
            color: var(--amber)
        }

        .badge-red {
            background: rgba(255, 68, 102, .15);
            color: var(--red)
        }

        .badge-blue {
            background: rgba(0, 212, 255, .12);
            color: var(--accent)
        }

        .badge-purple {
            background: rgba(124, 58, 237, .15);
            color: #a78bfa
        }

        /* ── PROGRESS BAR ── */
        .progress-wrap {
            display: flex;
            align-items: center;
            gap: .75rem
        }

        .progress-bar {
            flex: 1;
            height: 4px;
            background: var(--border);
            border-radius: 2px;
            overflow: hidden;
        }

        .progress-fill {
            height: 100%;
            border-radius: 2px;
            transition: width .6s
        }

        /* ── TRUST METER ── */
        .trust-meter {
            display: flex;
            gap: 3px;
            align-items: center
        }

        .trust-pip {
            width: 16px;
            height: 16px;
            border-radius: 2px;
            background: var(--border)
        }

        .trust-pip.on {
            background: var(--green)
        }

        .trust-pip.on.warn {
            background: var(--amber)
        }

        .trust-pip.on.bad {
            background: var(--red)
        }

        /* ── TOAST ── */
        #toast {
            position: fixed;
            bottom: 1.5rem;
            right: 1.5rem;
            padding: .75rem 1.5rem;
            border-radius: 2px;
            font-family: var(--mono);
            font-size: .75rem;
            font-weight: 500;
            background: var(--green);
            color: #000;
            transform: translateY(200%);
            transition: transform .3s;
            z-index: 999;
        }

        #toast.show {
            transform: translateY(0)
        }

        #toast.err {
            background: var(--red);
            color: #fff
        }

        /* ── SPINNER ── */
        .spinner {
            width: 14px;
            height: 14px;
            border: 2px solid rgba(0, 0, 0, .3);
            border-radius: 50%;
            border-top-color: #000;
            animation: spin .8s linear infinite;
            display: none;
        }

        @keyframes spin {
            to {
                transform: rotate(360deg)
            }
        }

        /* ── DIVIDER ── */
        .divider {
            border: none;
            border-top: 1px solid var(--border);
            margin: 1.5rem 0
        }

        /* ── SCROLLBAR ── */
        ::-webkit-scrollbar {
            width: 4px;
            height: 4px
        }

        ::-webkit-scrollbar-track {
            background: transparent
        }

        ::-webkit-scrollbar-thumb {
            background: var(--border2);
            border-radius: 2px
        }

        /* ── RESPONSIVE ── */
        @media(max-width:1100px) {
            .dash-grid {
                grid-template-columns: 1fr 1fr !important
            }

            main {
                padding: 1rem
            }
        }

        @media(max-width:720px) {
            .dash-grid {
                grid-template-columns: 1fr !important
            }

            .stat-grid {
                grid-template-columns: 1fr 1fr
            }

            header {
                padding: .75rem 1rem
            }
        }
    </style>
</head>

<body>

    <header>
        <div class="logo">
            <div class="logo-dot"></div>
            MILETRUTH DASHBOARD|
        </div>
        <div class="header-right">
            <div id="sysStatus" class="status-chip">
                <span>&bull;</span> CONNECTING
            </div>
            <span id="lastRefresh" style="font-family:var(--mono);font-size:.65rem;color:var(--muted)"></span>
            <button class="btn btn-ghost" onclick="fullRefresh()" title="Refresh">&#8634; REFRESH</button>
            <button class="btn btn-danger" onclick="confirmReset()">RESET DEFAULTS</button>
            <button class="btn btn-primary" id="btnTrain" onclick="triggerTrain()">
                <span class="spinner" id="trainSpinner"></span>
                <span id="trainLabel">TRIGGER RETRAIN</span>
            </button>
        </div>
    </header>

    <main>

        <!-- TOP STAT STRIP -->
        <div class="stat-grid" id="statStrip">
            <div class="stat-card">
                <div class="stat-label">Training Records</div>
                <div class="stat-value v-accent" id="sRecords">&mdash;</div>
                <div class="stat-sub" id="sReady">&mdash;</div>
            </div>
            <div class="stat-card">
                <div class="stat-label">Avg Quality (last 50)</div>
                <div class="stat-value" id="sQuality">&mdash;</div>
                <div class="stat-sub" id="sQualSub">&mdash;</div>
            </div>
            <div class="stat-card">
                <div class="stat-label">Model Trust</div>
                <div class="stat-value" id="sTrust">&mdash;</div>
                <div class="stat-sub" id="sTrustSub">&mdash;</div>
            </div>
            <div class="stat-card">
                <div class="stat-label">R² Score</div>
                <div class="stat-value" id="sR2">&mdash;</div>
                <div class="stat-sub">Prediction accuracy</div>
            </div>
            <div class="stat-card">
                <div class="stat-label">MAE</div>
                <div class="stat-value" id="sMae">&mdash;</div>
                <div class="stat-sub">Avg prediction error (pts)</div>
            </div>
            <div class="stat-card">
                <div class="stat-label">ML-Tuned Params</div>
                <div class="stat-value v-accent" id="sMLParams">&mdash;</div>
                <div class="stat-sub" id="sParamsSub">&mdash;</div>
            </div>
            <div class="stat-card">
                <div class="stat-label">Avg Latency</div>
                <div class="stat-value" id="sLatency">&mdash;</div>
                <div class="stat-sub">Assignment call (ms)</div>
            </div>
        </div>

        <!-- ROW 1: Quality trend + Param table -->
        <div class="section-label">Performance & Configuration</div>
        <div class="dash-grid wide" style="margin-bottom:1.5rem">
            <div class="panel">
                <div class="panel-title">
                    Quality Trend &mdash; last 50 calls
                    <span class="badge badge-blue" id="trendBadge">LIVE</span>
                </div>
                <div class="chart-wrap tall"><canvas id="qualityChart"></canvas></div>
            </div>
            <div class="panel">
                <div class="panel-title">Active Hyperparameters</div>
                <div class="tbl-scroll" id="paramsContainer" style="max-height:310px">
                    <div
                        style="color:var(--muted);font-family:var(--mono);font-size:.7rem;text-align:center;padding:2rem">
                        Loading params...</div>
                </div>
            </div>
        </div>

        <!-- ROW 2: Feature importance + Strategy + Behavior tree -->
        <div class="section-label">Model Internals</div>
        <div class="dash-grid tri" style="margin-bottom:1.5rem">
            <div class="panel">
                <div class="panel-title">Feature Importance</div>
                <div class="chart-wrap"><canvas id="importanceChart"></canvas></div>
            </div>
            <div class="panel">
                <div class="panel-title">Multi-Objective Pareto</div>
                <div class="chart-wrap" style="height:120px"><canvas id="paretoChart"></canvas></div>
            </div>
            <div class="panel">
                <div class="panel-title">
                    ID3 Behavior Rules
                    <span class="badge badge-green" id="rulesCount">0 rules</span>
                </div>
                <div class="rules-scroll" id="rulesContainer">
                    <div
                        style="color:var(--muted);font-family:var(--mono);font-size:.7rem;text-align:center;padding:2rem">
                        Waiting for trained tree...</div>
                </div>
            </div>
        </div>

        <!-- ROW 3: Hourly heatmap + Quality histogram + Strategy compare -->
        <div class="section-label">Operational Analytics</div>
        <div class="dash-grid tri" style="margin-bottom:1.5rem">
            <div class="panel">
                <div class="panel-title">Quality Heatmap &mdash; by Hour</div>
                <div id="heatmapContainer" style="margin-top:.5rem"></div>
            </div>
            <div class="panel">
                <div class="panel-title">Quality Distribution</div>
                <div class="chart-wrap"><canvas id="histogramChart"></canvas></div>
            </div>
        </div>

        <!-- ROW 4: Zone stats + Top trials + Model health -->
        <div class="section-label">Zone Intelligence & Audit</div>
        <div class="dash-grid tri" style="margin-bottom:1.5rem">
            <div class="panel">
                <div class="panel-title">Zone Performance</div>
                <div class="tbl-scroll">
                    <table class="ml-table" id="zoneTable">
                        <thead>
                            <tr>
                                <th>Zone</th>
                                <th>Calls</th>
                                <th>Avg Q</th>
                                <th>Avg km</th>
                            </tr>
                        </thead>
                        <tbody id="zoneTableBody">
                            <tr>
                                <td colspan="4" style="color:var(--muted);text-align:center;padding:1.5rem">Loading...
                                </td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>
            <div class="panel">
                <div class="panel-title">Top Optuna Trials</div>
                <div class="tbl-scroll">
                    <table class="ml-table" id="trialsTable">
                        <thead>
                            <tr>
                                <th>#</th>
                                <th>Predicted Q</th>
                                <th>Key Params</th>
                            </tr>
                        </thead>
                        <tbody id="trialsTableBody">
                            <tr>
                                <td colspan="3" style="color:var(--muted);text-align:center;padding:1.5rem">Run retrain
                                    to see trials</td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>
            <div class="panel">
                <div class="panel-title">Model Health Audit</div>
                <div id="modelHealthContainer">
                    <div
                        style="color:var(--muted);font-family:var(--mono);font-size:.7rem;text-align:center;padding:2rem">
                        Train the model to see audit data</div>
                </div>
            </div>
        </div>

        <!-- ROW 5: Unassigned + Latency series -->
        <div class="section-label">Assignment Quality Drill-Down</div>
        <div class="dash-grid" style="grid-template-columns:1fr 1fr;margin-bottom:1.5rem">
            <div class="panel">
                <div class="panel-title">Unassigned Orders &mdash; Recent Calls</div>
                <div class="chart-wrap"><canvas id="unassignedChart"></canvas></div>
            </div>
            <div class="panel">
                <div class="panel-title">Assignment Latency (ms)</div>
                <div class="chart-wrap"><canvas id="latencyChart"></canvas></div>
            </div>
        </div>

        <!-- EXPORT ROW -->
        <div style="display:flex;gap:.75rem;justify-content:flex-end;margin-bottom:1.5rem">
            <button class="btn btn-ghost" onclick="downloadCSV()">&darr; EXPORT CSV</button>
            <button class="btn btn-ghost" onclick="window.open('/api/v1/ml/status','_blank')">&#8599; RAW STATUS
                JSON</button>
        </div>

    </main>

    <div id="toast"></div>

    <script>
        const API = '/api/v1/ml';
        let charts = {};

        const PARAM_DESC = {
            max_pickup_distance_km: 'Max distance a rider can be from kitchen for initial pickup',
            max_kitchen_distance_km: 'Max travel distance to reach the kitchen',
            max_orders_per_rider: 'Hard cap on orders per trip (prevents overload)',
            ideal_load: 'Target orders-per-rider the optimizer aims for',
            workload_balance_threshold: 'How strictly to enforce equal workload across riders',
            workload_penalty_weight: 'Cost multiplier for load imbalance in the optimizer',
            distance_penalty_weight: 'Cost multiplier for travel distance in the optimizer',
            cluster_radius_km: 'Radius used to group nearby orders into clusters',
            search_time_limit_seconds: 'Max wall-clock time allowed per optimization run',
            road_factor: 'Straight-line to road-distance multiplier (1.3 = 30% longer)',
        };

        // ─────────────────────────── INIT ───────────────────────────
        async function init() {
            await Promise.all([loadStatus(), loadConfig()]);
            document.getElementById('lastRefresh').textContent =
                new Date().toLocaleTimeString();
        }

        async function fullRefresh() { await init(); toast('Refreshed'); }

        // Auto-refresh every 30s
        setInterval(async () => {
            await loadStatus();
            document.getElementById('lastRefresh').textContent = new Date().toLocaleTimeString();
        }, 30000);

        // ─────────────────────────── STATUS ───────────────────────────
        async function loadStatus() {
            try {
                const res = await fetch(`${API}/status`);
                const data = await res.json();
                if (data.status !== 'ok') return;

                updateSysStatus(data);
                updateStatStrip(data);
                renderQualityChart(data.quality_trend || {});
                renderUnassignedChart(data.quality_trend || {});
                renderLatencyChart(data.quality_trend || {});
                renderModelHealth(data.model || {});
                renderBehaviorRules(data.behavior || {});
                renderHeatmap(data.hourly_stats || []);
                renderHistogram(data.quality_histogram || []);
                renderZoneTable(data.zone_stats || []);
                renderTrialsTable(data.model?.top_trials || []);
                renderImportanceChart(data.model?.feature_importance || {});
                renderParetoChart(data.model?.pareto_frontier_size || 0);

            } catch (e) {
                console.error('loadStatus error:', e);
                document.getElementById('sysStatus').className = 'status-chip err';
                document.getElementById('sysStatus').innerHTML = '<span>&bull;</span> API ERROR';
            }
        }

        function updateSysStatus(data) {
            const chip = document.getElementById('sysStatus');
            if (data.ready_to_train) {
                chip.className = 'status-chip';
                chip.innerHTML = '<span>&bull;</span> READY';
            } else {
                chip.className = 'status-chip warn';
                chip.innerHTML = `<span>&bull;</span> COLLECTING (${data.db_records}/${30})`;
            }
        }

        function updateStatStrip(data) {
            const trend = data.quality_trend || {};
            const model = data.model || {};
            const val = data.model?.validation || {};
            const baseline = data.model?.baseline || {};

            set('sRecords', data.db_records ?? '&mdash;');
            set('sReady', data.ready_to_train ? '&check; READY TO TRAIN' : `${30 - (data.db_records || 0)} more needed`);
            el('sReady').style.color = data.ready_to_train ? 'var(--green)' : 'var(--amber)';

            const q = trend.avg_quality;
            set('sQuality', q != null ? q.toFixed(1) + '%' : '&mdash;');
            el('sQuality').className = 'stat-value ' + qualClass(q);
            set('sQualSub', q != null ? `min ${trend.min_quality} / max ${trend.max_quality}` : '&mdash;');

            const ts = model.trust_score || val.trust_score;
            set('sTrust', model.model_trained ? `${ts || 0}/5` : '&mdash;');
            set('sTrustSub', val.trust_level || 'not trained');

            const r2 = val.r2_score;
            set('sR2', r2 != null ? (r2 * 100).toFixed(1) + '%' : '&mdash;');
            el('sR2').className = 'stat-value ' + (r2 > .75 ? 'v-green' : r2 > .5 ? 'v-amber' : 'v-red');

            set('sMae', val.mae != null ? val.mae.toFixed(2) : '&mdash;');
            el('sMae').className = 'stat-value ' + (val.mae < 5 ? 'v-green' : val.mae < 10 ? 'v-amber' : 'v-red');

            const latArr = trend.latency_series || [];
            const avgLat = latArr.length ? (latArr.reduce((a, b) => a + b, 0) / latArr.length).toFixed(0) : '&mdash;';
            set('sLatency', avgLat !== '&mdash;' ? avgLat + 'ms' : '&mdash;');
        }

        // ─────────────────────────── CONFIG ───────────────────────────
        async function loadConfig() {
            try {
                const res = await fetch(`${API}/config`);
                const data = await res.json();
                if (data.status !== 'ok') return;

                set('sMLParams', data.ml_tuned_count ?? '&mdash;');
                set('sParamsSub', `of ${data.total_params} total params`);

                const container = el('paramsContainer');
                container.innerHTML = '';
                const sorted = Object.entries(data.hyperparameters || {})
                    .sort((a, b) => a[0].localeCompare(b[0]));
                for (const [key, info] of sorted) {
                    const div = document.createElement('div');
                    div.className = 'param-row';
                    const isML = info.source === 'ml_tuned' || info.source === 'ml_hypertuner';
                    div.innerHTML = `
        <div>
          <div class="param-name">${key}</div>
          <div class="stat-sub" style="font-size:.6rem;margin-top:.15rem">${PARAM_DESC[key] || ''}</div>
        </div>
        <div style="display:flex;align-items:center;gap:.5rem">
          <span class="param-val">${typeof info.value === 'number' ? info.value.toFixed(2) : info.value}</span>
          <span class="param-src ${isML ? '' : 'default'}">${isML ? 'ML' : 'DEFAULT'}</span>
        </div>
      `;
                    container.appendChild(div);
                }
            } catch (e) {
                console.error('loadConfig:', e);
            }
        }

        // ─────────────────────────── CHARTS ───────────────────────────
        const CHART_DEFAULTS = {
            plugins: { legend: { display: false } },
            animation: { duration: 400 },
            scales: {
                x: { grid: { color: 'rgba(30,39,51,.8)', drawBorder: false }, ticks: { color: '#64748b', font: { family: 'DM Mono', size: 10 } } },
                y: { grid: { color: 'rgba(30,39,51,.8)', drawBorder: false }, ticks: { color: '#64748b', font: { family: 'DM Mono', size: 10 } } },
            }
        };

        function mkChart(id, cfg) {
            if (charts[id]) { charts[id].destroy(); }
            charts[id] = new Chart(document.getElementById(id).getContext('2d'), cfg);
            return charts[id];
        }

        function updateChart(id, labels, datasets) {
            if (!charts[id]) return;
            charts[id].data.labels = labels;
            charts[id].data.datasets = datasets;
            charts[id].update('none');
        }

        function renderQualityChart(trend) {
            const history = trend.history || [];
            const labels = history.map((_, i) => `#${i + 1}`);
            const cfg = {
                type: 'line',
                data: {
                    labels, datasets: [{
                        label: 'Quality', data: history,
                        borderColor: '#00d4ff', backgroundColor: 'rgba(0,212,255,.08)',
                        borderWidth: 2, tension: .4, fill: true,
                        pointRadius: 2, pointHoverRadius: 5, pointBackgroundColor: '#00d4ff'
                    }, {
                        label: 'Threshold', data: history.map(() => 70),
                        borderColor: 'rgba(255,170,0,.4)', borderWidth: 1,
                        borderDash: [4, 4], pointRadius: 0, fill: false
                    }]
                },
                options: {
                    ...CHART_DEFAULTS, responsive: true, maintainAspectRatio: false,
                    plugins: {
                        legend: { display: false }, tooltip: {
                            mode: 'index', intersect: false,
                            callbacks: { label: ctx => `${ctx.dataset.label}: ${ctx.raw?.toFixed(1)}` }
                        }
                    },
                    scales: {
                        x: { ...CHART_DEFAULTS.scales.x },
                        y: { ...CHART_DEFAULTS.scales.y, min: 0, max: 100 }
                    }
                }
            };
            if (charts['qualityChart']) {
                charts['qualityChart'].data.labels = labels;
                charts['qualityChart'].data.datasets[0].data = history;
                charts['qualityChart'].update('none');
            } else {
                mkChart('qualityChart', cfg);
            }
        }

        function renderUnassignedChart(trend) {
            const data = trend.unassigned_series || [];
            const labels = data.map((_, i) => `#${i + 1}`);
            const cfg = {
                type: 'bar',
                data: {
                    labels, datasets: [{
                        label: 'Unassigned', data,
                        backgroundColor: data.map(v => v === 0 ? 'rgba(0,229,122,.5)' : 'rgba(255,68,102,.5)'),
                        borderRadius: 2, borderSkipped: false
                    }]
                },
                options: {
                    ...CHART_DEFAULTS, responsive: true, maintainAspectRatio: false,
                    plugins: { legend: { display: false } },
                    scales: { x: CHART_DEFAULTS.scales.x, y: { ...CHART_DEFAULTS.scales.y, min: 0 } }
                }
            };
            if (charts['unassignedChart']) {
                charts['unassignedChart'].data.labels = labels;
                charts['unassignedChart'].data.datasets[0].data = data;
                charts['unassignedChart'].data.datasets[0].backgroundColor = data.map(v => v === 0 ? 'rgba(0,229,122,.5)' : 'rgba(255,68,102,.5)');
                charts['unassignedChart'].update('none');
            } else { mkChart('unassignedChart', cfg); }
        }

        function renderLatencyChart(trend) {
            const data = trend.latency_series || [];
            const labels = data.map((_, i) => `#${i + 1}`);
            const avg = data.length ? data.reduce((a, b) => a + b, 0) / data.length : 0;
            const cfg = {
                type: 'line',
                data: {
                    labels, datasets: [{
                        label: 'Latency (ms)', data,
                        borderColor: '#7c3aed', backgroundColor: 'rgba(124,58,237,.08)',
                        borderWidth: 2, tension: .3, fill: true, pointRadius: 0
                    }, {
                        label: 'Avg', data: data.map(() => avg),
                        borderColor: 'rgba(124,58,237,.4)', borderWidth: 1,
                        borderDash: [4, 4], pointRadius: 0, fill: false
                    }]
                },
                options: {
                    ...CHART_DEFAULTS, responsive: true, maintainAspectRatio: false,
                    plugins: { legend: { display: false } }
                }
            };
            if (charts['latencyChart']) {
                charts['latencyChart'].data.labels = labels;
                charts['latencyChart'].data.datasets[0].data = data;
                charts['latencyChart'].update('none');
            } else { mkChart('latencyChart', cfg); }
        }

        function renderImportanceChart(importance) {
            const entries = Object.entries(importance).sort((a, b) => b[1] - a[1]).slice(0, 12);
            const labels = entries.map(e => e[0]);
            const values = entries.map(e => e[1]);
            const colors = values.map((v, i) => `hsl(${180 + i * 15},80%,${65 - i * 2}%)`);
            const cfg = {
                type: 'bar',
                data: {
                    labels, datasets: [{
                        label: 'Impact %', data: values,
                        backgroundColor: colors, borderRadius: 2, borderSkipped: false
                    }]
                },
                options: {
                    indexAxis: 'y', ...CHART_DEFAULTS, responsive: true, maintainAspectRatio: false,
                    plugins: { legend: { display: false } },
                    scales: { x: { ...CHART_DEFAULTS.scales.x, max: 100 }, y: CHART_DEFAULTS.scales.y }
                }
            };
            if (charts['importanceChart']) {
                charts['importanceChart'].data.labels = labels;
                charts['importanceChart'].data.datasets[0].data = values;
                charts['importanceChart'].update('none');
            } else { mkChart('importanceChart', cfg); }
        }

        function renderHistogram(hist) {
            const labels = hist.map(h => h.range);
            const data = hist.map(h => h.count);
            const cfg = {
                type: 'bar',
                data: {
                    labels, datasets: [{
                        label: 'Count', data,
                        backgroundColor: data.map((_, i) => `rgba(0,212,255,${.2 + i * 0.08})`),
                        borderRadius: 2, borderSkipped: false
                    }]
                },
                options: {
                    ...CHART_DEFAULTS, responsive: true, maintainAspectRatio: false,
                    plugins: { legend: { display: false } }
                }
            };
            if (charts['histogramChart']) {
                charts['histogramChart'].data.labels = labels;
                charts['histogramChart'].data.datasets[0].data = data;
                charts['histogramChart'].update('none');
            } else { mkChart('histogramChart', cfg); }
        }

        function renderParetoChart(size) {
            // Scatter placeholder showing pareto count
            const dummy = Array.from({ length: size || 5 }, (_, i) => ({ x: 20 + i * 15 + Math.random() * 10, y: 50 + Math.random() * 30 }));
            const cfg = {
                type: 'scatter',
                data: {
                    datasets: [{
                        label: 'Pareto trials', data: dummy,
                        backgroundColor: 'rgba(0,212,255,.6)', pointRadius: 4
                    }]
                },
                options: {
                    ...CHART_DEFAULTS, responsive: true, maintainAspectRatio: false,
                    plugins: { legend: { display: false } },
                    scales: {
                        x: { ...CHART_DEFAULTS.scales.x, title: { display: true, text: 'Latency', color: '#64748b', font: { size: 9, family: 'DM Mono' } } },
                        y: { ...CHART_DEFAULTS.scales.y, title: { display: true, text: 'Quality', color: '#64748b', font: { size: 9, family: 'DM Mono' } } }
                    }
                }
            };
            if (charts['paretoChart']) {
                charts['paretoChart'].data.datasets[0].data = dummy;
                charts['paretoChart'].update('none');
            } else { mkChart('paretoChart', cfg); }
        }

        // ─────────────────────────── HEATMAP ───────────────────────────
        function renderHeatmap(hourly) {
            const container = el('heatmapContainer');
            if (!hourly.length) {
                container.innerHTML = '<div style="color:var(--muted);font-family:var(--mono);font-size:.7rem;text-align:center;padding:1.5rem">No hourly data yet</div>';
                return;
            }
            const maxQ = Math.max(...hourly.map(h => h.avg_quality || 0), 1);
            const hours = Array.from({ length: 24 }, (_, i) => i);
            const byHour = Object.fromEntries(hourly.map(h => [h.hour, h]));

            container.innerHTML = '';
            const row = document.createElement('div');
            row.style.cssText = 'display:flex;flex-wrap:wrap;gap:3px';
            for (const h of hours) {
                const d = byHour[h];
                const q = d?.avg_quality || 0;
                const ratio = q / maxQ;
                const cell = document.createElement('div');
                cell.style.cssText = `
      width:calc(${100 / 24}% - 3px);height:36px;border-radius:2px;
      background:${heatColor(ratio)};cursor:pointer;
      display:flex;flex-direction:column;align-items:center;justify-content:center;
    `;
                cell.innerHTML = `
      <div style="font-family:var(--mono);font-size:.55rem;color:rgba(255,255,255,.5)">${h}h</div>
      <div style="font-family:var(--mono);font-size:.6rem;color:#fff;font-weight:500">${q ? q.toFixed(0) : '&mdash;'}</div>
    `;
                cell.title = `Hour ${h}: Q=${q.toFixed(1)}, calls=${d?.call_count || 0}`;
                row.appendChild(cell);
            }
            container.appendChild(row);
        }

        function heatColor(ratio) {
            if (ratio > .8) return `rgba(0,229,122,${.2 + ratio * .5})`;
            if (ratio > .6) return `rgba(0,212,255,${.2 + ratio * .4})`;
            if (ratio > .4) return `rgba(255,170,0,${.2 + ratio * .4})`;
            return `rgba(255,68,102,${.15 + ratio * .3})`;
        }

        // ─────────────────────────── TABLES ───────────────────────────
        function renderZoneTable(rows) {
            const tbody = el('zoneTableBody');
            if (!rows.length) { tbody.innerHTML = '<tr><td colspan="4" style="color:var(--muted);text-align:center;padding:1.5rem">No zone data yet</td></tr>'; return; }
            tbody.innerHTML = rows.map(r => `
    <tr>
      <td style="color:var(--accent)">${r.zone_id}</td>
      <td>${r.call_count}</td>
      <td><span class="${r.avg_quality > 75 ? 'badge-green' : r.avg_quality > 50 ? 'badge-amber' : 'badge-red'} badge">${r.avg_quality}%</span></td>
      <td>${r.avg_distance_km.toFixed(1)} km</td>
    </tr>`).join('');
        }

        function renderTrialsTable(trials) {
            const tbody = el('trialsTableBody');
            if (!trials.length) { tbody.innerHTML = '<tr><td colspan="3" style="color:var(--muted);text-align:center;padding:1.5rem">Run retrain to see trials</td></tr>'; return; }
            tbody.innerHTML = trials.map((t, i) => {
                const keyParam = Object.entries(t.params || {})
                    .sort((a, b) => Math.abs(b[1]) - Math.abs(a[1]))[0];
                return `<tr>
      <td style="color:var(--muted)">#${i + 1}</td>
      <td><span class="badge badge-green">${(t.score || 0).toFixed(1)}%</span></td>
      <td style="color:var(--muted);font-size:.65rem">${keyParam ? `${keyParam[0]}=${typeof keyParam[1] === 'number' ? keyParam[1].toFixed(2) : keyParam[1]}` : '&mdash;'}</td>
    </tr>`;
            }).join('');
        }

        // ─────────────────────────── MODEL HEALTH ───────────────────────────
        function renderModelHealth(model) {
            const v = model.validation || {};
            const b = model.baseline || {};
            const container = el('modelHealthContainer');

            if (!model.model_trained) {
                container.innerHTML = '<div style="color:var(--muted);font-family:var(--mono);font-size:.7rem;text-align:center;padding:2rem">Train the model to see audit data</div>';
                return;
            }

            const improvement = model.validation?.r2_score != null
                ? ((95 - b.avg_quality) || 0).toFixed(1) : '&mdash;';

            container.innerHTML = `
    <div style="display:flex;flex-direction:column;gap:.75rem">
      <div>
        <div class="stat-label">R² Score</div>
        <div style="display:flex;align-items:center;gap:.75rem;margin-top:.25rem">
          <span style="font-family:var(--mono);font-size:1.2rem;color:${v.r2_score > .75 ? 'var(--green)' : v.r2_score > .5 ? 'var(--amber)' : 'var(--red)'}">${v.r2_score != null ? (v.r2_score * 100).toFixed(1) + '%' : '&mdash;'}</span>
          <div class="progress-bar" style="flex:1"><div class="progress-fill" style="width:${(v.r2_score || 0) * 100}%;background:${v.r2_score > .75 ? 'var(--green)' : v.r2_score > .5 ? 'var(--amber)' : 'var(--red)'}"></div></div>
        </div>
      </div>
      <div>
        <div class="stat-label">Mean Absolute Error</div>
        <div style="font-family:var(--mono);font-size:1rem;color:var(--amber);margin-top:.15rem">${v.mae != null ? v.mae.toFixed(2) + ' pts' : '&mdash;'}</div>
      </div>
      <div>
        <div class="stat-label">Trust Level</div>
        <div style="margin-top:.25rem">
          <div class="trust-meter">${renderTrustPips(v.trust_score || 0)}</div>
          <div style="font-family:var(--mono);font-size:.65rem;color:var(--muted);margin-top:.2rem">${v.trust_level || '&mdash;'}</div>
        </div>
      </div>
      <div>
        <div class="stat-label">Training Size</div>
        <div style="font-family:var(--mono);font-size:1rem;color:var(--accent);margin-top:.15rem">${model.training_rows || 0} rows</div>
      </div>
      <div>
        <div class="stat-label">Baseline → ML Uplift</div>
        <div style="font-family:var(--mono);font-size:.8rem;color:var(--muted);margin-top:.15rem">
          Baseline avg: <span style="color:var(--amber)">${b.avg_quality || 0}%</span>
          &nbsp;→&nbsp;
          Potential: <span style="color:var(--green)">+${improvement}%</span>
        </div>
      </div>
      <div>
        <div class="stat-label">Segment Models</div>
        <div style="display:flex;gap:.5rem;margin-top:.25rem">
          <span class="badge ${model.peak_model_trained ? 'badge-green' : 'badge-red'}">PEAK ${model.peak_model_trained ? '&check;' : '&times;'}</span>
          <span class="badge ${model.offpeak_model_trained ? 'badge-green' : 'badge-red'}">OFF-PEAK ${model.offpeak_model_trained ? '&check;' : '&times;'}</span>
        </div>
      </div>
      <div>
        <div class="stat-label">Last Trained</div>
        <div style="font-family:var(--mono);font-size:.65rem;color:var(--muted);margin-top:.15rem">${model.trained_at ? new Date(model.trained_at).toLocaleString() : '&mdash;'}</div>
      </div>
    </div>
  `;
        }

        function renderTrustPips(score) {
            return Array.from({ length: 5 }, (_, i) => {
                const cls = i < score ? (score >= 4 ? 'on' : score >= 3 ? 'on warn' : 'on bad') : '';
                return `<div class="trust-pip ${cls}"></div>`;
            }).join('');
        }

        // ─────────────────────────── BEHAVIOR RULES ───────────────────────────
        function renderBehaviorRules(behavior) {
            const rules = behavior.rules || [];
            const container = el('rulesContainer');
            set('rulesCount', `${rules.length} rules`);

            if (!rules.length) {
                container.innerHTML = '<div style="color:var(--muted);font-family:var(--mono);font-size:.7rem;text-align:center;padding:2rem">Waiting for trained tree...</div>';
                return;
            }

            container.innerHTML = rules.map(rule => {
                const isSuccess = rule.includes('SUCCESS');
                return `<div class="rule-item ${isSuccess ? 'success' : 'risk'}">${rule}</div>`;
            }).join('');
        }

        // ─────────────────────────── ACTIONS ───────────────────────────
        async function triggerTrain() {
            const btn = el('btnTrain');
            const spinner = el('trainSpinner');
            const label = el('trainLabel');

            btn.disabled = true;
            spinner.style.display = 'block';
            label.textContent = 'TUNING...';

            try {
                const res = await fetch(`${API}/train`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ n_trials: 150, multi_objective: false })
                });
                const data = await res.json();

                if (data.status === 'ok') {
                    toast(`Tuned! Predicted quality: ${data.best_predicted_quality}%`);
                } else if (data.status === 'model_not_ready') {
                    toast(`Not ready: ${data.message}`, true);
                } else if (data.status === 'insufficient_data') {
                    toast(`Need more data: ${data.message}`, true);
                } else {
                    toast(data.message || 'Tuning failed', true);
                }
            } catch (e) {
                toast('API error &mdash; check backend', true);
            } finally {
                btn.disabled = false;
                spinner.style.display = 'none';
                label.textContent = 'TRIGGER RETRAIN';
                await fullRefresh();
            }
        }

        function confirmReset() {
            if (!confirm('Reset ALL ML-tuned parameters to factory defaults?')) return;
            fetch(`${API}/reset`, { method: 'POST' })
                .then(() => { toast('Reset to defaults'); fullRefresh(); })
                .catch(() => toast('Reset failed', true));
        }

        async function downloadCSV() {
            try {
                const res = await fetch(`${API}/export`);
                const text = await res.text();
                const blob = new Blob([text], { type: 'text/csv' });
                const a = document.createElement('a');
                a.href = URL.createObjectURL(blob);
                a.download = `ml_export_${Date.now()}.csv`;
                a.click();
                toast('CSV downloaded');
            } catch (e) {
                toast('Export failed', true);
            }
        }

        // ─────────────────────────── UTILS ───────────────────────────
        function el(id) { return document.getElementById(id); }
        function set(id, val) { const e = el(id); if (e) e.innerHTML = val; }
        function qualClass(q) {
            if (q == null) return '';
            if (q >= 80) return 'v-green';
            if (q >= 60) return 'v-amber';
            return 'v-red';
        }
        function toast(msg, err = false) {
            const t = el('toast');
            t.textContent = msg;
            t.className = err ? 'show err' : 'show';
            setTimeout(() => t.className = '', 4000);
        }

        // ─────────────────────────── BOOT ───────────────────────────
        init();
    </script>
</body>

</html>