<?php
/**
 * OT Executive Brief — Consulting-Quality Report
 * McKinsey/Deloitte-standard PDF for Assistant City Managers
 * Fire Chief Frank McKinley · Cincinnati Fire Department
 */
$shift = getShiftForDate();
$reportDate = date('F j, Y');
$fiscalYear = 'FY 2026';
?>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>

<style>
/* ─── DESIGN TOKENS ─── */
:root {
    --rpt-navy: #0f2b4a;
    --rpt-navy-light: #1e3a5f;
    --rpt-black: #111827;
    --rpt-body: #374151;
    --rpt-gray: #6b7280;
    --rpt-rule: #d1d5db;
    --rpt-light: #e5e7eb;
    --rpt-subtle: #f9fafb;
    --rpt-accent-bg: #f0f4f8;
    --rpt-red: #7f1d1d;
    --rpt-green: #14532d;
    --rpt-amber: #78350f;
    --rpt-font: 'Inter', -apple-system, 'Helvetica Neue', sans-serif;
}

/* ─── BASE ─── */
.rpt { font-family: var(--rpt-font); color: var(--rpt-black); line-height: 1.65; -webkit-font-smoothing: antialiased; }
.rpt h1 { font-size: 22px; font-weight: 700; color: var(--rpt-navy); letter-spacing: -0.025em; margin: 0 0 4px 0; }
.rpt h2 { font-size: 16px; font-weight: 700; color: var(--rpt-navy); border-left: 3px solid var(--rpt-navy); padding-left: 14px; margin: 36px 0 6px 0; letter-spacing: -0.01em; }
.rpt h3 { font-size: 10px; font-weight: 700; color: var(--rpt-gray); text-transform: uppercase; letter-spacing: 0.1em; margin: 24px 0 8px 0; }
.rpt p { font-size: 13px; color: var(--rpt-body); margin: 0 0 10px 0; line-height: 1.7; }
.rpt .subtitle { font-size: 12px; color: var(--rpt-gray); letter-spacing: 0.005em; }
.rpt .num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

/* ─── KPI CARDS ─── */
.kpi-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin: 20px 0 24px; }
.kpi-card { border-top: 2px solid var(--rpt-navy); padding: 20px 16px 16px; background: #fff; text-align: center; }
.kpi-label { font-size: 8.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em; color: var(--rpt-gray); margin-bottom: 6px; }
.kpi-value { font-size: 30px; font-weight: 700; color: var(--rpt-navy); line-height: 1.1; font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.kpi-context { font-size: 9.5px; color: var(--rpt-gray); margin-top: 6px; font-weight: 500; }

/* ─── FINDINGS ─── */
.finding { border-left: 2px solid var(--rpt-navy); padding: 12px 20px; margin: 10px 0; }
.finding strong { display: block; font-size: 13px; font-weight: 700; color: var(--rpt-navy); margin-bottom: 3px; letter-spacing: -0.01em; }
.finding p { font-size: 12px; color: var(--rpt-body); margin: 0; line-height: 1.65; }

/* ─── CATEGORY ROWS ─── */
.cat-row { display: flex; gap: 24px; padding: 20px 24px; margin-bottom: 12px; border-top: 1px solid var(--rpt-rule); background: #fff; break-inside: avoid; align-items: flex-start; }
.cat-row:first-child { border-top: 2px solid var(--rpt-navy); }
.cat-amt { font-size: 22px; font-weight: 700; color: var(--rpt-navy); width: 110px; flex-shrink: 0; letter-spacing: -0.02em; }
.cat-amt .cat-pct { font-size: 11px; font-weight: 600; color: var(--rpt-gray); margin-top: 2px; }
.cat-detail { flex: 1; }
.cat-detail h4 { font-size: 14px; font-weight: 700; color: var(--rpt-navy); margin: 0 0 4px 0; letter-spacing: -0.01em; }
.cat-detail p { font-size: 11.5px; color: var(--rpt-body); line-height: 1.6; margin: 0 0 8px 0; }
.cat-stats { width: 100px; flex-shrink: 0; text-align: right; font-size: 11px; color: var(--rpt-gray); font-weight: 500; }

/* ─── INVESTMENT PANEL ─── */
.inv { background: var(--rpt-subtle); border-top: 1px solid var(--rpt-rule); padding: 14px 18px; margin-top: 12px; }
.inv-title { font-size: 8px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; color: var(--rpt-navy); margin-bottom: 10px; }
.inv-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; text-align: center; }
.inv-grid dt { font-size: 7.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--rpt-gray); }
.inv-grid dd { font-size: 16px; font-weight: 700; margin: 3px 0; letter-spacing: -0.01em; }
.inv-grid .sub { font-size: 8px; color: var(--rpt-gray); font-weight: 500; }
.inv-foot { display: flex; justify-content: center; gap: 24px; margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--rpt-rule); font-size: 10px; color: var(--rpt-body); font-weight: 500; }

/* ─── TABLES — Tufte ─── */
.rpt table { width: 100%; border-collapse: collapse; font-size: 10.5px; font-variant-numeric: tabular-nums; margin: 10px 0; }
.rpt thead { border-top: 2px solid var(--rpt-navy); border-bottom: 1px solid var(--rpt-navy); }
.rpt th { font-size: 8px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; color: var(--rpt-navy); padding: 8px 8px 6px; text-align: left; }
.rpt th.r { text-align: right; }
.rpt td { padding: 6px 8px; border-bottom: 1px solid #eef0f2; color: var(--rpt-body); font-weight: 400; }
.rpt td.r { text-align: right; font-variant-numeric: tabular-nums; }
.rpt td.b { font-weight: 600; color: var(--rpt-navy); }
.rpt tbody tr:last-child td { border-bottom: 2px solid var(--rpt-navy); }

/* ─── SIMULATOR ─── */
.sim-row { display: grid; grid-template-columns: 200px 1fr 44px 130px; align-items: center; gap: 12px; padding: 12px 0; border-bottom: 1px solid #eef0f2; }
.sim-row:last-child { border-bottom: none; }
.sim-slider { width: 100%; accent-color: var(--rpt-navy); }

/* ─── CHARTS ─── */
.chart-box { margin: 16px 0; break-inside: avoid; }
.chart-title { font-size: 11px; font-weight: 700; color: var(--rpt-navy); margin-bottom: 2px; letter-spacing: -0.01em; }
.chart-source { font-size: 8.5px; color: var(--rpt-gray); margin-top: 4px; font-style: italic; font-weight: 400; }

/* ─── SMALL CATEGORIES ─── */
.cat-row-sm { display: flex; gap: 16px; padding: 10px 24px; margin-bottom: 0; border-bottom: 1px solid #eef0f2; background: #fff; align-items: center; }
.cat-row-sm:first-child { border-top: 1px solid var(--rpt-rule); }
.cat-row-sm:last-child { border-bottom: 1px solid var(--rpt-rule); }
.cat-row-sm .cat-amt { font-size: 15px; font-weight: 700; width: 90px; }
.cat-row-sm .cat-detail h4 { font-size: 12px; margin-bottom: 1px; }
.cat-row-sm .cat-detail p { font-size: 10.5px; margin: 0; color: var(--rpt-gray); }
.cat-row-sm .cat-stats { font-size: 10px; font-weight: 500; }

/* ─── PRINT STYLES ─── */
@media print {
    *, *::before, *::after { box-shadow: none !important; text-shadow: none !important; }
    .no-print, .sidebar, .header-bar, .voice-fab, nav, [class*="news"], [class*="ticker"], [class*="carousel"],
    .mobile-tab-bar, .toast-container, .voice-fab-response, button { display: none !important; }
    body, html { background: #fff !important; }
    .main-content, .content-area, .page-content { margin: 0 !important; padding: 0 !important; background: #fff !important; max-width: 100% !important; }
    .print-only { display: block !important; }
    .print-only[style*="flex-direction"] { display: flex !important; }
    .print-only.page-break { display: block !important; }
    .screen-only { display: none !important; }
    .page-break { break-before: page; page-break-before: always; }
    * { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }
    .kpi-card, .finding, .inv, .chart-box { break-inside: avoid; }
    .cat-row, .cat-row-sm { break-inside: avoid; }
    /* Keep category + its investment panel together */
    .cat-group { break-inside: avoid; }
    /* Force charts to fit print width */
    [id^="chart"], .chart-box div[id] { max-width: 100% !important; width: 100% !important; overflow: hidden !important; }
    canvas { max-width: 100% !important; }
    /* Constrain grid layouts for print */
    .kpi-row { grid-template-columns: repeat(4, 1fr) !important; gap: 8px !important; }
    .inv-grid { grid-template-columns: repeat(4, 1fr) !important; gap: 6px !important; }
    .cat-row { flex-wrap: nowrap !important; }
    .sim-row { grid-template-columns: 160px 1fr 40px 120px !important; }
    table { table-layout: fixed !important; word-wrap: break-word !important; }
    h2, h3 { break-after: avoid; }
    thead { display: table-header-group; }
    p { orphans: 3; widows: 3; }
    a { color: inherit; text-decoration: none; }
    a[href]::after { content: none; }
    /* Sample day tables */
    .day-card { break-inside: avoid; }
    @page { size: letter; margin: 0.85in; }
}
.print-only { display: none !important; }
.screen-only { display: flex; }
</style>

<div class="rpt">

<!-- ═══ COVER PAGE (print only) ═══ -->
<div class="print-only" style="min-height:85vh; flex-direction:column; justify-content:center; position:relative;">
    <div style="position:absolute; top:0; left:-0.85in; width:calc(100% + 1.7in); height:2px; background:var(--rpt-navy);"></div>
    <div style="text-align:center; margin-bottom:80px;">
        <img src="assets/img/cfd-logo.png" width="80" height="80" style="object-fit:contain; margin-bottom:24px; opacity:0.9;">
        <div style="font-size:11px; font-weight:600; color:var(--rpt-gray); text-transform:uppercase; letter-spacing:0.2em;">Cincinnati Fire Department</div>
    </div>
    <div style="text-align:center;">
        <h1 style="font-size:28px; font-weight:700; color:var(--rpt-navy); letter-spacing:-0.03em; margin-bottom:6px;">Overtime Executive Brief</h1>
        <div style="font-size:14px; color:var(--rpt-gray); font-weight:400; margin-bottom:48px; letter-spacing:0.02em;"><?= $fiscalYear ?> &middot; Performance Analysis</div>
        <div style="width:40px; height:2px; background:var(--rpt-navy); margin:0 auto 48px;"></div>
        <div style="font-size:11px; color:var(--rpt-body); line-height:2.2; font-weight:400;">
            <div style="margin-bottom:2px;"><span style="font-weight:600; color:var(--rpt-gray); text-transform:uppercase; font-size:9px; letter-spacing:0.08em;">Prepared by</span><br>Fire Chief Frank McKinley</div>
            <div style="margin:16px 0 2px;"><span style="font-weight:600; color:var(--rpt-gray); text-transform:uppercase; font-size:9px; letter-spacing:0.08em;">Date</span><br><?= $reportDate ?></div>
            <div style="margin:16px 0 2px;"><span style="font-weight:600; color:var(--rpt-gray); text-transform:uppercase; font-size:9px; letter-spacing:0.08em;">Prepared for</span><br>Office of the City Manager</div>
        </div>
    </div>
    <div style="position:absolute; bottom:40px; left:0; right:0; text-align:center; font-size:9px; font-weight:500; color:var(--rpt-gray); text-transform:uppercase; letter-spacing:0.15em;">Confidential &mdash; Distribution limited to authorized personnel</div>
    <div style="position:absolute; bottom:0; left:-0.85in; width:calc(100% + 1.7in); height:2px; background:var(--rpt-navy);"></div>
</div>
<div class="print-only page-break"></div>

<!-- ═══ SCREEN NAV + PRINT BUTTON ═══ -->
<div class="no-print" style="margin-bottom:16px; display:flex; align-items:center; justify-content:space-between;">
    <a href="dashboard.php?page=ot-intelligence" style="display:inline-flex; align-items:center; gap:6px; text-decoration:none; color:var(--rpt-navy); font-size:13px; font-weight:600;">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>Full OT Intelligence Center</a>
    <button onclick="printReport()" class="no-print" style="display:inline-flex; align-items:center; gap:6px; padding:8px 18px; border:1px solid var(--rpt-navy); border-radius:6px; background:#fff; color:var(--rpt-navy); font-size:13px; font-weight:600; cursor:pointer; font-family:var(--rpt-font);">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
        Print Report</button>
    <script>
    function printReport() {
        // Check if data has finished loading
        var rpt = document.querySelector('.rpt');
        if (!rpt || rpt.getAttribute('data-loaded') !== 'true') {
            alert('Report data is still loading. Please wait a moment and try again.');
            return;
        }
        // Resize all charts for print
        document.querySelectorAll('[id^="chart"]').forEach(function(el) {
            try {
                var inst = echarts.getInstanceByDom(el);
                if (inst) inst.resize({ width: 650 });
            } catch(e) {}
        });
        setTimeout(function() { window.print(); }, 500);
    }
    </script>
</div>

<!-- ═══ SCREEN HEADER ═══ -->
<div class="screen-only" style="align-items:center; gap:16px; margin-bottom:28px; padding-bottom:18px; border-bottom:2px solid var(--rpt-navy);">
    <img src="assets/img/cfd-logo.png" width="48" height="48" style="object-fit:contain; opacity:0.9;">
    <div>
        <h1>Overtime Executive Brief</h1>
        <div class="subtitle" style="margin-top:2px;">Cincinnati Fire Department &middot; <?= $fiscalYear ?> &middot; Fire Chief Frank McKinley</div>
    </div>
    <div style="margin-left:auto; text-align:right;">
        <div style="font-size:8.5px; font-weight:600; color:var(--rpt-gray); text-transform:uppercase; letter-spacing:0.1em;">Min. Staffing</div>
        <div style="font-size:18px; font-weight:700; color:var(--rpt-navy); letter-spacing:-0.02em; margin-top:2px;">199 / 203</div>
    </div>
</div>

<!-- ═══ PRINT HEADER (page 2+) ═══ -->
<div class="print-only" style="display:flex; align-items:center; gap:10px; margin-bottom:18px; padding-bottom:10px; border-bottom:1px solid var(--rpt-rule);">
    <img src="assets/img/cfd-logo.png" width="28" height="28" style="object-fit:contain; opacity:0.85;">
    <div style="font-size:11px; font-weight:700; color:var(--rpt-navy); letter-spacing:-0.01em;">Cincinnati Fire Department</div>
    <div style="margin-left:auto; font-size:9px; color:var(--rpt-gray); font-weight:500;">Overtime Executive Brief &middot; <?= $fiscalYear ?></div>
</div>

<!-- ═══ EXECUTIVE SUMMARY ═══ -->
<h2 style="margin-top:0;">Executive Summary</h2>

<div class="kpi-row" id="kpiRow"></div>

<!-- Daily QA Lookup (screen only) -->
<div id="dailyQA" class="no-print" style="display:none; border:1px solid var(--rpt-rule); border-radius:6px; padding:16px 20px; margin:-8px 0 16px; background:var(--rpt-subtle);">
    <div style="display:flex; align-items:center; gap:12px; margin-bottom:12px;">
        <div style="font-size:11px; font-weight:700; color:var(--rpt-navy);">Verify Daily Staffing</div>
        <input type="date" id="qaDate" style="padding:4px 8px; border:1px solid var(--rpt-rule); border-radius:4px; font-family:var(--rpt-font); font-size:12px; color:var(--rpt-navy);">
        <button onclick="closeDailyQA()" style="margin-left:auto; background:none; border:none; color:var(--rpt-gray); cursor:pointer; font-size:16px;">&times;</button>
    </div>
    <div id="qaResult" style="font-size:12px; color:var(--rpt-body);"></div>
</div>

<p id="narrativePara" style="font-size:14px; line-height:1.7; color:var(--rpt-body);"></p>

<div class="chart-box">
    <div class="chart-title">FY26 Overtime by Category</div>
    <div id="chartWaterfall" style="height:240px;"></div>
    <div class="chart-source">Source: Power BI Internal OT - Main · FY2026 pay periods</div>
</div>

<h3>Key Findings</h3>
<div id="findings"></div>

<!-- ═══ FY26 PAYROLL COSTS (NOT OVERTIME) ═══ -->
<div class="page-break"></div>
<h2>FY2026 Departmental Costs — Payroll</h2>
<p class="subtitle">Direct payroll costs by category. These are not overtime &mdash; these are the straight costs of leave, certifications, injury, and other earning codes from the payroll system.</p>
<div id="payrollCosts" style="height:380px; border-radius:8px; overflow:hidden;"></div>

<!-- ═══ SECTION 2: WHAT'S CAUSING IT ═══ -->
<h2>What&rsquo;s Causing the Overtime</h2>
<p class="subtitle">Seven cost drivers decomposed with root cause, financial impact, and investment analysis.</p>
<div id="catRows"></div>

<h3>Year-Over-Year OT Trend</h3>
<p class="subtitle">Three fiscal years of Internal OT showing accelerating spend.</p>
<div class="chart-box">
    <div id="chartYoY" style="height:200px;"></div>
    <div class="chart-source">Source: Internal OT &ndash; Main &middot; FY2024&ndash;FY2026</div>
</div>

<h3>Seasonal OT Pattern</h3>
<p class="subtitle">Jul&ndash;Dec comparison across 3 fiscal years &mdash; the 6 months where all FYs have comparable data.</p>
<div class="chart-box">
    <div id="chartSeasonal" style="height:260px;"></div>
    <div class="chart-source">Source: Internal OT &ndash; Main &middot; Jul&ndash;Dec comparison across FY2024, FY2025, FY2026</div>
</div>

<h3>Why FY26 Is 42% Higher Than FY25</h3>
<p class="subtitle">Root cause decomposition of the $2.4M year-over-year increase.</p>
<div id="yoyDrivers"></div>

<h3>Above-Grade Vacancy Escalation</h3>
<p class="subtitle">Month-over-month acting rank OT shows vacancies are getting worse, not better.</p>
<div class="chart-box">
    <div id="chartAboveGradeTrend" style="height:220px;"></div>
    <div class="chart-source">Source: Internal OT &ndash; Main &middot; Above Grade records by acting rank</div>
</div>
<div id="agInsight"></div>

<h3>Monthly OT by Category</h3>
<p class="subtitle">Seasonal patterns and structural trends across all OT drivers.</p>
<div class="chart-box">
    <div id="chartMonthlyReason" style="height:260px;"></div>
    <div class="chart-source">Source: Internal OT &ndash; Main &middot; FY2026 (Jun&ndash;Dec 2025)</div>
</div>


<!-- ═══ SECTION 3: WHERE IT'S HAPPENING ═══ -->
<div class="page-break"></div>
<h2>Where It&rsquo;s Happening</h2>
<p class="subtitle">Overtime by unit type, district, and individual apparatus.</p>

<div class="chart-box">
    <div class="chart-title">OT Spend by Unit Type</div>
    <div id="chartUnitType" style="height:220px;"></div>
    <div class="chart-source">Source: Internal OT &ndash; Main &middot; Skill field classification &middot; FY2026</div>
</div>

<div id="unitTypeTable"></div>

<h3>Daily OT Staffing by Unit Type</h3>
<div id="perUnitCards"></div>

<h3>OT by District</h3>
<p class="subtitle">District 3 (West Side) carries 31% of all overtime. Based on Manpower Data with district assignments (Jun 2024&ndash;Feb 2026, 21 months).</p>
<div class="chart-box">
    <div id="chartDistrictBar" style="height:180px;"></div>
    <div class="chart-source">Source: Manpower Data (Telestaff/Cronos) &middot; Jun 2024&ndash;Feb 2026 &middot; 16,107 OT records</div>
</div>
<div id="districtCards"></div>

<h3>Highest-OT Units</h3>
<p class="subtitle">Individual apparatus ranked by total OT hours. Engine 14 (District 1) leads all units.</p>
<div id="topUnitsTable"></div>

<!-- ═══ SECTION 4: WHO'S DRIVING IT ═══ -->
<h2>Who&rsquo;s Driving It</h2>
<p class="subtitle">Individual concentration, workforce risk factors, and payroll context.</p>

<h3>Top 10 OT Earners — FY2026</h3>
<p class="subtitle">Two District Chiefs and two Captains lead all personnel — a vacancy signal at the command level.</p>
<div id="topEarnersTable"></div>

<h3>OT Concentration Risk</h3>
<div id="riskCards"></div>

<h3>Payroll OT Codes — Requires Verification</h3>
<p class="subtitle">Payroll shows multiple OT earning codes. Several have $0 hours logged, suggesting pension/FLSA adjustments rather than additional work. Confirmation needed from Finance before citing as additive costs.</p>
<div id="payrollOT"></div>

<!-- ═══ SECTION 5: WHAT CAN BE DONE ═══ -->
<div class="page-break"></div>
<h2>What Can Be Done</h2>
<p class="subtitle">Near-term efficiencies, recruit class impact, and longer-term promotion ROI.</p>

<h3>Near-Term Efficiencies (Before End of FY26)</h3>
<div id="lowHangingFruit"></div>

<h3>July 2026 Recruit Class Impact</h3>
<p class="subtitle">46 Firefighters deploy across 3 platoons (~15 per shift). Estimated OT reduction: $300K&ndash;$600K annually.</p>
<div id="recruitImpact"></div>

<h2>Staffing & Promotion Impact Simulator</h2>
<p class="subtitle">Adjust recruitment and promotion levels to model the projected impact on overtime spend. Savings estimates are based on above-grade OT and callback data from the Internal OT system. Callback reduction assumes 15% per additional position (capped at 50%). These are directional estimates &mdash; actual results depend on assignment patterns, leave rates, and operational decisions.</p>

<div id="simPresets" style="margin:16px 0 0;">
    <div style="display:flex; gap:8px; flex-wrap:wrap; margin-bottom:8px;">
        <button onclick="applyPreset('roi')" id="presetROI" class="sim-preset active" style="padding:8px 16px; border:1px solid var(--rpt-navy); border-radius:4px; background:var(--rpt-navy); color:#fff; font-size:11px; font-weight:600; font-family:var(--rpt-font); cursor:pointer; letter-spacing:0.02em;">Best ROI</button>
        <button onclick="applyPreset('balanced')" id="presetBal" class="sim-preset" style="padding:8px 16px; border:1px solid var(--rpt-rule); border-radius:4px; background:#fff; color:var(--rpt-navy); font-size:11px; font-weight:600; font-family:var(--rpt-font); cursor:pointer; letter-spacing:0.02em;">Balanced</button>
        <button onclick="applyPreset('full')" id="presetFull" class="sim-preset" style="padding:8px 16px; border:1px solid var(--rpt-rule); border-radius:4px; background:#fff; color:var(--rpt-navy); font-size:11px; font-weight:600; font-family:var(--rpt-font); cursor:pointer; letter-spacing:0.02em;">Full Investment</button>
        <button onclick="applyPreset('custom')" id="presetCustom" class="sim-preset" style="padding:8px 16px; border:1px solid var(--rpt-rule); border-radius:4px; background:#fff; color:var(--rpt-gray); font-size:11px; font-weight:600; font-family:var(--rpt-font); cursor:pointer; letter-spacing:0.02em;">Custom</button>
    </div>
    <div id="presetDetail" style="font-size:11px; color:var(--rpt-body); line-height:1.6; padding:10px 14px; background:var(--rpt-subtle); border-left:2px solid var(--rpt-navy); margin-bottom:4px;">
        <strong id="presetDesc" style="color:var(--rpt-navy); font-size:10px; text-transform:uppercase; letter-spacing:0.06em;"></strong>
        <div id="presetRationale" style="margin-top:3px;"></div>
    </div>
</div>

<div style="border-top:2px solid var(--rpt-navy); padding:24px 0 0; margin:8px 0 16px; background:#fff;">
    <div style="display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin-bottom:20px; padding-bottom:16px; border-bottom:1px solid var(--rpt-rule); text-align:center;">
        <div><div class="kpi-label">Projected Savings</div><div id="simSav" style="font-size:22px; font-weight:700; color:var(--rpt-green); letter-spacing:-0.02em;">$0</div></div>
        <div><div class="kpi-label">Annual Cost</div><div id="simHire" style="font-size:22px; font-weight:700; color:var(--rpt-red); letter-spacing:-0.02em;">$0</div></div>
        <div><div class="kpi-label">Net Impact</div><div id="simNet" style="font-size:22px; font-weight:700; color:var(--rpt-navy); letter-spacing:-0.02em;">$0</div></div>
        <div><div class="kpi-label">ROI</div><div id="simROI" style="font-size:22px; font-weight:700; color:var(--rpt-navy); letter-spacing:-0.02em;">&mdash;</div></div>
    </div>
    <div id="simBody"></div>
</div>

<!-- ═══ SAMPLE DAY ═══ -->
<div class="page-break"></div>
<h2>What Does a Day of Overtime Look Like?</h2>
<p class="subtitle">Real FY26 days showing exactly who worked overtime, in what capacity, and at what cost.</p>
<div id="dayCards"></div>

<!-- ═══ APPENDIX ═══ -->
<div class="page-break"></div>
<h2>Appendix: Supporting Data</h2>

<div style="display:grid; grid-template-columns:1fr 1fr; gap:20px; margin:16px 0;">
    <div class="chart-box">
        <div class="chart-title">Shift Duration</div>
        <div id="chartDur" style="height:220px;"></div>
        <div class="chart-source">Source: Internal OT &ndash; Main &middot; FY2026</div>
    </div>
    <div class="chart-box">
        <div class="chart-title">Leave by Type</div>
        <div id="chartLv" style="height:220px;"></div>
        <div class="chart-source">Source: Payroll &middot; FY2026</div>
    </div>
</div>

<h3>Data Note</h3>
<p style="font-size:10px; color:var(--rpt-gray); line-height:1.75;">
    <strong>Headline ($8.85M)</strong> comes from the live Power BI [OT Spend] measure. <strong>Breakdowns ($8.16M)</strong> come from the Internal OT &ndash; Main table snapshot (pay periods PP01&ndash;PP13, 11,846 records). The $685K gap represents approximately one additional pay period captured by the live measure after this extract. District analysis uses Manpower Data (16,107 OT records, Jun 2024&ndash;Feb 2026) which tracks shift-fill assignments with district codes &mdash; a broader time window than the Internal OT FY26 extract. Internal OT includes 883 records where work occurred before Jul 2025 but processed in FY26 pay periods. Minimum staffing: 199 per shift (203 with peak demand), per Local 48 CBA Article 37.
</p>

<h3>Methodology</h3>
<p style="font-size:10px; color:var(--rpt-gray); line-height:1.75;">
    Overtime records exported from Power BI via DAX query by fiscal year. Leave records from PS_C_PAYCHK_FISCAL payroll table. Staffing patterns from Telestaff/Cronos daily export (654 days). Contract minimum manning derived from Local 48 CBA Article 37 applied to the actual CFD apparatus roster: 27 Engines (&times;4), 12 Ladders (&times;4), 2 Heavy Rescues (&times;4), 13 Medics (&times;2), 4 District Chiefs (&times;1) = 199 personnel per shift. Salary rates from Local 48 CBA with 28% benefits load factor.
</p>

<!-- Print footer -->
<div class="print-only" style="margin-top:48px; padding-top:10px; border-top:1px solid var(--rpt-rule); display:flex; justify-content:space-between; font-size:8px; color:var(--rpt-gray); font-weight:500; letter-spacing:0.02em;">
    <div>Cincinnati Fire Department &middot; 315 West Court Street &middot; Cincinnati, Ohio 45202</div>
    <div style="text-transform:uppercase; letter-spacing:0.1em;">Confidential &middot; <?= $reportDate ?></div>
</div>

<!-- Screen footer -->
<div class="no-print" style="text-align:center; padding:24px 0; font-size:10px; color:var(--rpt-gray); margin-top:24px; border-top:1px solid var(--rpt-rule); font-weight:500;">
    Cincinnati Fire Department &middot; OT Taskforce &middot; <span id="xDate"></span> &middot; <a href="dashboard.php?page=ot-intelligence" style="color:var(--rpt-navy); font-weight:600; text-decoration:none;">Full Analysis &rarr;</a>
</div>

</div><!-- /.rpt -->

<script>
document.addEventListener('DOMContentLoaded', async function() {
    const navy = '#0f2b4a', gray = '#6b7280', red = '#7f1d1d', green = '#14532d', amber = '#78350f', light = '#d1d5db';
    const fmt$ = n => n >= 1e6 ? '$' + (n/1e6).toFixed(2) + 'M' : '$' + Math.round(n).toLocaleString();

    const data = await window.portalAPI('overtime.php?action=executive');
    if (!data?.headline) return;
    const h = data.headline;

    // ─── KPI Cards ───
    document.getElementById('kpiRow').innerHTML = [
        { label: 'FY26 OT Spend', value: fmt$(h.total_ot_spend), ctx: 'Power BI [OT Spend] measure' },
        { label: 'Daily OT Cost', value: fmt$(h.avg_daily_cost), ctx: 'Median: ' + fmt$(h.median_daily_cost) },
        { label: 'OT Callbacks / Day', value: Math.round(h.avg_ops_personnel_per_day), ctx: 'Extra personnel called back' },
        { label: 'Avg On-Duty', value: h.avg_on_duty, ctx: '199 minimum required', clickable: true },
    ].map(k => `<div class="kpi-card" ${k.clickable ? 'onclick="openDailyQA()" style="cursor:pointer;" title="Click to verify by date"' : ''}>
        <div class="kpi-label">${k.label}</div><div class="kpi-value num">${k.value}</div>
        <div class="kpi-context">${k.ctx}${k.clickable ? ' <span style="color:var(--rpt-navy);font-weight:600;">▼ verify</span>' : ''}</div>
    </div>`).join('');

    // ─── Narrative ───
    document.getElementById('narrativePara').innerHTML = `The Cincinnati Fire Department incurred <strong>${fmt$(h.total_ot_spend)}</strong> in overtime during FY2026. An average of <strong>${Math.round(h.avg_ops_personnel_per_day)} personnel are called back on OT</strong> each day at a cost of <strong>${fmt$(h.avg_daily_cost)}/day</strong>. The two largest cost drivers — minimum staffing callbacks and above-grade vacancy fill — are predominantly full 24-hour tours. Above-grade assignments alone account for <strong>${fmt$(h.above_grade_spend)}</strong> (${Math.round(h.above_grade_spend / h.internal_ot_spend * 100)}% of internal OT), with 55% being full 24-hour shifts where personnel act in a higher rank for the entire tour.`;

    document.getElementById('xDate').textContent = data.generated;

    // ─── Daily QA Lookup with Drill-Down ───
    const dailyData = data.daily_onduty || [];
    let manpowerDaily = null; // Loaded on demand
    const qaDateEl = document.getElementById('qaDate');

    async function loadManpowerDaily() {
        if (manpowerDaily) return manpowerDaily;
        try {
            const resp = await fetch('assets/data/ot-manpower-daily.json');
            manpowerDaily = await resp.json();
        } catch(e) { manpowerDaily = {}; }
        return manpowerDaily;
    }

    function renderDrillDown(records, label) {
        if (!records || !records.length) return `<div style="font-size:11px;color:${gray};font-style:italic;padding:8px 0;">No individual records available for this view.</div>`;
        const hasReasons = records.some(r => r.reason);
        return `<div style="margin-top:10px;border-top:1px solid var(--rpt-rule);padding-top:8px;">
            <div style="font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.08em;color:${navy};margin-bottom:6px;">${label} — ${records.length} Personnel</div>
            <table><thead><tr><th>Name</th><th>Rank</th><th>Unit</th><th>District</th><th class="r">Hours</th><th class="r">Pay</th>${hasReasons ? '<th>OT Reason</th>' : ''}</tr></thead>
            <tbody>${records.map(r => {
                const reasonShort = (r.reason||'').replace('OPS- ','').replace('Above Grade- ','AG: ').replace('HRD- ','');
                const dutyTip = r.duties ? ' title="'+r.duties.replace(/"/g,'&quot;')+'"' : '';
                return `<tr><td class="b">${r.name}</td><td>${r.rank}</td><td>${r.company}</td><td>${r.district.replace('District ','D')}</td><td class="r">${r.hours}h</td><td class="r b">${r.pay ? '$'+r.pay.toLocaleString() : '—'}</td>${hasReasons ? '<td style="font-size:9px;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"'+dutyTip+'>'+(r.reason ? '<span style="color:'+navy+';font-weight:600;">'+reasonShort+'</span>' : '<span style="color:'+gray+';font-style:italic;">outside IOT range</span>')+'</td>' : ''}</tr>`;
            }).join('')}</tbody></table>
            ${hasReasons ? '<div style="font-size:9px;color:'+gray+';margin-top:4px;">Hover over OT Reason for full duty description. Reason data available Jul&ndash;Dec 2025 (Internal OT date range).</div>' : ''}
        </div>`;
    }

    if (qaDateEl && dailyData.length) {
        qaDateEl.min = dailyData[dailyData.length - 1].date;
        qaDateEl.max = dailyData[0].date;
        qaDateEl.addEventListener('change', async function() {
            const sel = this.value;
            const day = dailyData.find(d => d.date === sel);
            const qaResult = document.getElementById('qaResult');
            if (!day) {
                qaResult.innerHTML = '<div style="color:'+gray+';font-style:italic;">No data for this date.</div>';
                return;
            }
            const dow = new Date(sel + 'T12:00:00').toLocaleDateString('en-US', { weekday: 'long', month: 'short', day: 'numeric', year: 'numeric' });
            const mp = await loadManpowerDaily();
            const otRecords = mp[sel] || [];

            qaResult.innerHTML = `
                <div style="display:grid; grid-template-columns:repeat(4,1fr); gap:12px; text-align:center; margin-top:8px;">
                    <div style="cursor:default;" title="Aggregate from Manpower daily summary">
                        <div style="font-size:8px;font-weight:700;text-transform:uppercase;letter-spacing:0.1em;color:${gray};">On-Duty</div>
                        <div style="font-size:24px;font-weight:700;color:${navy};">${day.on_duty}</div>
                        <div style="font-size:8px;color:${gray};">aggregate count</div>
                    </div>
                    <div style="cursor:default;">
                        <div style="font-size:8px;font-weight:700;text-transform:uppercase;letter-spacing:0.1em;color:${gray};">Regular</div>
                        <div style="font-size:24px;font-weight:700;color:${navy};">${day.regular}</div>
                        <div style="font-size:8px;color:${gray};">on-duty minus OT</div>
                    </div>
                    <div style="cursor:pointer;border-radius:6px;padding:4px;${otRecords.length?'background:var(--rpt-accent-bg);':''}" onclick="document.getElementById('qaDrill').style.display=document.getElementById('qaDrill').style.display==='none'?'block':'none'">
                        <div style="font-size:8px;font-weight:700;text-transform:uppercase;letter-spacing:0.1em;color:${gray};">OT Callbacks</div>
                        <div style="font-size:24px;font-weight:700;color:${day.ot_callbacks > 25 ? red : navy};">${day.ot_callbacks}</div>
                        <div style="font-size:8px;color:${navy};font-weight:600;">${otRecords.length ? '▼ click to see who' : 'no detail available'}</div>
                    </div>
                    <div style="cursor:pointer;border-radius:6px;padding:4px;${otRecords.length?'background:var(--rpt-accent-bg);':''}" onclick="document.getElementById('qaDrill').style.display=document.getElementById('qaDrill').style.display==='none'?'block':'none'">
                        <div style="font-size:8px;font-weight:700;text-transform:uppercase;letter-spacing:0.1em;color:${gray};">OT Pay</div>
                        <div style="font-size:24px;font-weight:700;color:${navy};">${fmt$(day.ot_pay)}</div>
                        <div style="font-size:8px;color:${navy};font-weight:600;">${otRecords.length ? '▼ click for breakdown' : ''}</div>
                    </div>
                </div>
                <div style="text-align:center;margin-top:6px;font-size:10px;color:${gray};">${dow} &middot; Source: Manpower Data</div>
                <div id="qaDrill" style="display:none;">${renderDrillDown(otRecords, 'OT Personnel on ' + dow)}</div>`;
        });
    }

    window.openDailyQA = function() {
        const el = document.getElementById('dailyQA');
        el.style.display = el.style.display === 'none' ? 'block' : 'none';
        if (el.style.display === 'block') document.getElementById('qaDate').focus();
    };
    window.closeDailyQA = function() {
        document.getElementById('dailyQA').style.display = 'none';
    };

    // ─── Waterfall Chart ───
    const cats = data.categories.filter(c => c.spend > 50000);
    const wf = echarts.init(document.getElementById('chartWaterfall'));
    wf.setOption({
        tooltip: { trigger: 'axis', formatter: p => `<strong>${cats[cats.length-1-p[0].dataIndex].name}</strong><br>${fmt$(cats[cats.length-1-p[0].dataIndex].spend)} (${cats[cats.length-1-p[0].dataIndex].pct}%)` },
        grid: { top: 4, right: 90, bottom: 4, left: 4, containLabel: true },
        xAxis: { type: 'value', show: false },
        yAxis: { type: 'category', data: cats.map(c => c.name.length > 30 ? c.name.substring(0,30)+'…' : c.name).reverse(), axisLabel: { color: navy, fontSize: 10, fontWeight: 600, fontFamily: 'Inter', width: 180, overflow: 'truncate' }, axisLine: { show: false }, axisTick: { show: false } },
        series: [{ type: 'bar', data: cats.map(c => ({ value: Math.round(c.spend), itemStyle: { color: navy, borderRadius: [0,4,4,0] } })).reverse(), barWidth: '55%', label: { show: true, position: 'right', color: gray, fontSize: 10, fontFamily: 'Inter', formatter: p => fmt$(p.value) } }]
    });

    // ─── Key Findings ───
    const findings = [
        `<strong>Minimum staffing and above-grade OT are predominantly full 24-hour tours</strong> — 58% and 55% respectively. The remaining categories (peak demand, training, work-past-shift) are almost entirely partial shifts under 12 hours. Overall, ${h.full_shift_count.toLocaleString()} full tours account for ${fmt$(h.full_shift_spend)} (55% of spend) while ${h.partial_shift_count.toLocaleString()} partial shifts account for ${fmt$(h.partial_shift_spend)}.`,
        `<strong>Above-grade assignments cost ${fmt$(h.above_grade_spend)}</strong> — driven by vacant Lieutenant, FAO, and District Chief positions. ${h.above_grade_count.toLocaleString()} instances of personnel acting in a higher rank at OT differential rates.`,
        `<strong>${Math.round(h.avg_ops_personnel_per_day)} personnel are called back on OT every day</strong> at an average cost of ${fmt$(h.avg_daily_cost)}/day. Average on-duty strength is ${h.avg_on_duty} against a 199-minimum requirement — the ${h.avg_on_duty - 199} positions above minimum are filled almost entirely by OT callbacks.`,
        `<strong>The December 2025 recruit class (46 members) deploys July 2026.</strong> These recruits enter as Firefighters and can reduce FF-rank callbacks (~$636K/year), but cannot fill Lieutenant, FAO, or District Chief vacancies. 76% of Full Tour callback spending ($2.01M) requires specific higher ranks that only promotions address.`,
    ];
    document.getElementById('findings').innerHTML = findings.map(f => `<div class="finding"><p>${f}</p></div>`).join('');

    // ─── Category Rows ───
    const major = cats.filter(c => c.spend >= 300000);
    const minor = cats.filter(c => c.spend > 50000 && c.spend < 300000);

    const majorHtml = major.map(c => {
        const inv = c.investment;
        let invHtml = '';
        if (inv && inv.annual_investment > 0) {
            const netColor = inv.net_annual >= 0 ? green : red;
            const roiColor = inv.roi_y1_pct >= 50 ? green : inv.roi_y1_pct >= 0 ? amber : red;
            invHtml = `<div class="inv">
                <div class="inv-title">${inv.action.includes('Recruit') || inv.action.includes('Firefighter') ? 'Recruitment' : 'Promotion'} Analysis: ${inv.action}</div>
                <div class="inv-grid">
                    <div><dt>Positions</dt><dd style="color:${navy};">${inv.positions}</dd><div class="sub">${inv.action.includes('Recruit') || inv.action.includes('Firefighter') ? 'Recruits' : 'Promotions'}</div></div>
                    <div><dt>Annual Cost</dt><dd style="color:${red};">${fmt$(inv.annual_investment)}</dd><div class="sub">Investment</div></div>
                    <div><dt>OT Avoided</dt><dd style="color:${green};">${fmt$(inv.ot_avoided)}</dd><div class="sub">Savings</div></div>
                    <div><dt>Payback</dt><dd style="color:${navy};">${inv.payback_months} mo</dd><div class="sub">Break-Even</div></div>
                </div>
                <div class="inv-foot">
                    <span>Net: <strong style="color:${netColor};">${inv.net_annual >= 0 ? '+' : ''}${fmt$(inv.net_annual)}</strong></span>
                    <span>ROI: <strong style="color:${roiColor};">${inv.roi_y1_pct}%</strong></span>
                    <span>5-Year: <strong style="color:${green};">${fmt$(inv.savings_5y)}</strong></span>
                </div>
            </div>`;
        } else if (inv?.note) {
            invHtml = `<div style="margin-top:8px; padding:10px 14px; background:var(--rpt-subtle); border-radius:4px; font-size:11px; color:var(--rpt-gray); font-style:italic;">${inv.note}</div>`;
        }
        return `<div class="cat-group"><div class="cat-row">
            <div class="cat-amt">${fmt$(c.spend)}<div class="cat-pct">${c.pct}% of total</div><div style="font-size:10px;color:var(--rpt-gray);margin-top:4px;">${c.count.toLocaleString()} records<br>${c.persons} people</div></div>
            <div class="cat-detail"><h4>${c.name}</h4><p>${c.cause}</p>${invHtml}</div>
            <div class="cat-stats"><div><strong>${c.avg_daily_instances}</strong> /day</div><div style="margin-top:4px;"><strong>${c.hours.toLocaleString()}</strong> hrs</div></div>
        </div></div>`;
    }).join('');

    const minorHtml = minor.length ? `<h3 style="margin-top:16px;">Other Categories</h3>` + minor.map(c => {
        let noteHtml = '';
        if (c.investment?.note) noteHtml = ` <span style="font-size:10px;color:var(--rpt-gray);font-style:italic;">${c.investment.note}</span>`;
        return `<div class="cat-row-sm">
            <div class="cat-amt">${fmt$(c.spend)}<div class="cat-pct">${c.pct}%</div></div>
            <div class="cat-detail" style="flex:1;"><h4>${c.name}</h4><p>${c.cause}${noteHtml}</p></div>
            <div class="cat-stats"><div><strong>${c.avg_daily_instances}</strong> /day</div></div>
        </div>`;
    }).join('') : '';

    document.getElementById('catRows').innerHTML = majorHtml + minorHtml;

    // ─── Simulator ───
    const wi = data.what_if;
    const simBody = document.getElementById('simBody');
    wi.ranks.forEach(r => {
        const verb = r.code === 'FF1' ? 'Recruit' : 'Promote';
        const row = document.createElement('div');
        row.className = 'sim-row';
        row.innerHTML = `
            <div><div style="font-weight:700;font-size:13px;color:var(--rpt-navy);">${verb} ${r.rank}s</div><div style="font-size:10px;color:var(--rpt-gray);">${verb} cost: ${fmt$(r.hire_cost)}/yr</div></div>
            <input type="range" min="0" max="10" value="0" class="sim-slider" id="s_${r.code}" oninput="applyPreset('custom')">
            <div style="font-size:20px;font-weight:800;color:var(--rpt-navy);text-align:center;" id="sv_${r.code}">0</div>
            <div style="font-size:11px;color:var(--rpt-gray);text-align:right;"><span id="sa_${r.code}" style="color:${green};font-weight:600;">$0</span> savings</div>`;
        simBody.appendChild(row);
    });
    // Preset scenarios computed from Power BI above-grade + callback data
    const PRESETS = {
        roi: {
            label: 'Highest ROI — Targets $2.62M in above-grade vacancy OT',
            detail: 'FAO and DC promotions eliminate the most above-grade OT per dollar. Each vacant FAO position costs $524K/yr in acting-up OT; each DC vacancy costs $357K/yr. This scenario fills the most costly vacancies first. No FF recruits — the Dec 2025 class addresses callback staffing.',
            values: { LT: 2, FAO: 3, DC: 2, FF1: 0, CPT: 0 }
        },
        balanced: {
            label: 'Balanced — Promotions + recruits for both vacancy and callback OT',
            detail: 'Addresses both OT drivers: above-grade vacancies ($2.62M) through promotions AND minimum staffing callbacks ($2.96M) through additional recruits. Adds headcount while filling critical supervisory gaps.',
            values: { LT: 3, FAO: 2, DC: 1, FF1: 5, CPT: 0 }
        },
        full: {
            label: 'Full Investment — Maximum OT reduction across all ranks',
            detail: 'Fills all identifiable vacancies and adds 10 recruits beyond the Dec 2025 class. Largest investment but eliminates nearly all addressable above-grade OT and significantly reduces callbacks. Training OT ($867K) and work-past-shift ($217K) remain unaffected.',
            values: { LT: 3, FAO: 3, DC: 3, FF1: 10, CPT: 0 }
        },
        custom: {
            label: 'Adjust sliders manually to model custom scenarios',
            detail: '',
            values: null
        }
    };

    window.applyPreset = function(key) {
        document.querySelectorAll('.sim-preset').forEach(b => {
            b.style.background = '#fff'; b.style.color = key === 'custom' && b.id === 'presetCustom' ? gray : navy;
            b.style.borderColor = light;
        });
        const btn = document.getElementById('preset' + {roi:'ROI',balanced:'Bal',full:'Full',custom:'Custom'}[key]);
        if (btn) { btn.style.background = navy; btn.style.color = '#fff'; btn.style.borderColor = navy; }
        const p = PRESETS[key];
        document.getElementById('presetDesc').textContent = p?.label || '';
        document.getElementById('presetRationale').textContent = p?.detail || '';
        const detailEl = document.getElementById('presetDetail');
        if (detailEl) detailEl.style.display = (p?.detail) ? 'block' : 'none';

        if (key !== 'custom' && p?.values) {
            wi.ranks.forEach(r => {
                const slider = document.getElementById('s_' + r.code);
                if (slider) slider.value = p.values[r.code] || 0;
            });
        }
        doSim();
    };

    window.doSim = function() {
        let sav = 0, hire = 0;
        wi.ranks.forEach(r => {
            const n = +document.getElementById('s_' + r.code).value;
            document.getElementById('sv_' + r.code).textContent = n;
            const ag = Math.min(n * (r.savings_per_hire || 0), r.above_grade_annual);
            const cb = n > 0 ? Math.round(r.callback_annual * Math.min(n * 0.15, 0.5)) : 0;
            document.getElementById('sa_' + r.code).textContent = fmt$(ag + cb);
            sav += ag + cb; hire += n * r.hire_cost;
        });
        const net = sav - hire;
        document.getElementById('simSav').textContent = fmt$(sav);
        document.getElementById('simHire').textContent = fmt$(hire);
        document.getElementById('simNet').textContent = (net >= 0 ? '+' : '') + fmt$(net);
        document.getElementById('simNet').style.color = net >= 0 ? green : red;
        document.getElementById('simROI').textContent = hire > 0 ? Math.round(sav/hire*100) + '%' : '—';
    };

    // Default to Best ROI preset on load
    applyPreset('roi');

    // ─── Sample Days ───
    if (data.sample_days) {
        document.getElementById('dayCards').innerHTML = data.sample_days.slice(0, 2).map(day => {
            const catBadges = day.categories.map(c => `<span style="display:inline-block;padding:2px 8px;margin:2px;border-radius:4px;font-size:9px;font-weight:600;background:var(--rpt-subtle);border:1px solid var(--rpt-light);color:var(--rpt-body);">${c.name}: ${c.count}</span>`).join('');
            const rows = day.records.slice(0, 8).map(r => {
                const reasonShort = (r.reason||'').replace('OPS- ','').replace('Above Grade- ','AG: ').replace('HRD- ','');
                const dutyTip = r.duties ? ' title="'+r.duties.replace(/"/g,'&quot;')+'"' : '';
                const actingBadge = r.acting_rank && r.acting_rank !== r.rank ? `<span style="color:${red};font-size:9px;font-weight:600;">Acting ${r.acting_rank}</span>` : '';
                return `<tr>
                    <td class="b">${r.name || ('Chris ' + r.chris)}</td>
                    <td>${r.rank}</td>
                    <td>${r.company || '—'}</td>
                    <td>${r.district ? r.district.replace('District ','D') : '—'}</td>
                    <td style="font-size:9px;max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"${dutyTip}>${reasonShort || '—'}</td>
                    <td class="r">${r.hours}h</td>
                    <td class="r b">$${Math.round(r.spend).toLocaleString()}</td>
                    <td>${actingBadge || '—'}</td>
                </tr>`;
            }).join('');
            return `<div class="day-card" style="border-top:2px solid var(--rpt-navy); padding:18px 0; margin-bottom:20px;">
                <div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:12px;padding-bottom:10px;border-bottom:1px solid var(--rpt-rule);">
                    <div><div style="font-size:14px;font-weight:700;color:var(--rpt-navy);letter-spacing:-0.01em;">${day.label}</div><div style="font-size:10px;color:var(--rpt-gray);margin-top:2px;font-weight:500;">${day.date} &middot; ${day.persons} personnel &middot; ${day.hours.toLocaleString()} hours</div></div>
                    <div style="font-size:22px;font-weight:700;color:var(--rpt-navy);letter-spacing:-0.02em;">${fmt$(day.spend)}</div>
                </div>
                <div style="margin-bottom:10px;">${catBadges}</div>
                <table><thead><tr><th>Name</th><th>Rank</th><th>Unit</th><th>District</th><th>OT Reason</th><th class="r">Hours</th><th class="r">Cost</th><th>Acting</th></tr></thead>
                <tbody>${rows}</tbody></table>
                ${day.records.length > 8 ? '<div style="text-align:center;font-size:10px;color:var(--rpt-gray);padding-top:4px;">+ '+(day.records.length-8)+' additional records &middot; hover OT Reason for duty description</div>' : '<div style="text-align:center;font-size:9px;color:var(--rpt-gray);padding-top:4px;">Hover OT Reason for duty description</div>'}
            </div>`;
        }).join('');
    }
    // Mark page as fully loaded for print readiness
    document.querySelector('.rpt').setAttribute('data-loaded', 'true');

    // ─── Charts ───
    if (data.shift_durations) {
        const d = data.shift_durations;
        const shades = ['#0f2b4a','#1e5080','#3d7ab5','#7fafd4','#c8dcea'];
        echarts.init(document.getElementById('chartDur')).setOption({
            tooltip: { trigger: 'item', formatter: p => `${p.name}: ${p.value.toLocaleString()} (${p.percent}%)` },
            series: [{ type: 'pie', radius: ['38%', '70%'], center: ['50%','55%'], label: { formatter: '{b}\n{d}%', fontSize: 10, color: navy, fontFamily: 'Inter' },
                data: d.map((x,i) => ({ name: x.bucket, value: x.count, itemStyle: { color: shades[i] } })) }]
        });
    }
    if (data.leave) {
        const l = data.leave.filter(x => x.hours > 1000).slice(0, 6);
        const shades = ['#0f2b4a','#1e5080','#3d7ab5','#7fafd4','#a8c8de','#c8dcea'];
        const cleanName = s => s.replace('Time Used (Uniform)','').replace('Parental Leave Paid','Parental').replace('Sick With Pay','Sick').replace('Comp-','Comp ').trim();
        echarts.init(document.getElementById('chartLv')).setOption({
            tooltip: { trigger: 'axis', formatter: p => `${p[0].name}: ${p[0].value.toLocaleString()} hrs` },
            grid: { top: 4, right: 8, bottom: 4, left: 4, containLabel: true },
            xAxis: { type: 'value', show: false },
            yAxis: { type: 'category', data: l.map(x => cleanName(x.type)).reverse(), axisLabel: { color: navy, fontSize: 9, fontWeight: 600, fontFamily: 'Inter' }, axisLine: { show: false }, axisTick: { show: false } },
            series: [{ type: 'bar', data: l.map((x,i) => ({ value: x.hours, itemStyle: { color: shades[i], borderRadius: [0,3,3,0] } })).reverse(), barWidth: '60%', label: { show: true, position: 'right', color: gray, fontSize: 8.5, fontFamily: 'Inter', formatter: p => Math.round(p.value/1000) + 'K' } }]
        });
    }

    // ─── District & Unit Analysis ───
    if (data.district_analysis) {
        const da = data.district_analysis;
        const ut = da.unit_types;

        // Unit Type bar chart
        const utChart = echarts.init(document.getElementById('chartUnitType'));
        utChart.setOption({
            tooltip: { trigger: 'axis', formatter: p => `<strong>${p[0].name}</strong><br>${fmt$(p[0].value)}` },
            grid: { top: 4, right: 80, bottom: 4, left: 4, containLabel: true },
            xAxis: { type: 'value', show: false },
            yAxis: { type: 'category', data: ut.map(u => u.type).reverse(), axisLabel: { color: navy, fontSize: 10, fontWeight: 600, fontFamily: 'Inter' }, axisLine: { show: false }, axisTick: { show: false } },
            series: [{ type: 'bar', data: ut.map(u => ({ value: u.spend, itemStyle: { color: navy, borderRadius: [0,3,3,0] } })).reverse(), barWidth: '55%', label: { show: true, position: 'right', color: gray, fontSize: 9, fontFamily: 'Inter', formatter: p => fmt$(p.value) + ' (' + ut.slice().reverse()[p.dataIndex].pct + '%)' } }]
        });

        // Unit type table
        document.getElementById('unitTypeTable').innerHTML = `<table>
            <thead><tr><th>Unit Type</th><th class="r">Units</th><th class="r">Positions/Shift</th><th class="r">Total Spend</th><th class="r">OT Personnel/Day</th><th class="r">% Positions on OT</th></tr></thead>
            <tbody>${ut.map(u => `<tr>
                <td class="b">${u.type}</td>
                <td class="r">${u.unit_count || '—'}</td>
                <td class="r">${u.positions_per_shift || '—'}</td>
                <td class="r b">${fmt$(u.spend)}</td>
                <td class="r b">${u.ot_personnel_per_day != null ? u.ot_personnel_per_day : '—'}</td>
                <td class="r"><span style="font-weight:600;color:${(u.pct_positions_ot||0) >= 20 ? red : (u.pct_positions_ot||0) >= 10 ? amber : navy};">${u.pct_positions_ot != null ? u.pct_positions_ot + '%' : '—'}</span></td>
            </tr>`).join('')}</tbody></table>`;

        // YoY Trend chart
        if (data.year_over_year) {
            const yoy = data.year_over_year;
            const yoyChart = echarts.init(document.getElementById('chartYoY'));
            const yoyData = [
                { fy: 'FY2024', spend: yoy.fy24.spend, records: yoy.fy24.records, avg: yoy.fy24.avg_per_record },
                { fy: 'FY2025', spend: yoy.fy25.spend, records: yoy.fy25.records, avg: yoy.fy25.avg_per_record },
                { fy: 'FY2026', spend: yoy.fy26.spend, records: yoy.fy26.records, avg: yoy.fy26.avg_per_record },
            ];
            yoyChart.setOption({
                tooltip: { trigger: 'axis', formatter: params => {
                    const d = yoyData[params[0].dataIndex];
                    return `<strong>${d.fy}</strong><br>Spend: ${fmt$(d.spend)}<br>Records: ${d.records.toLocaleString()}<br>Avg/record: $${d.avg}`;
                }},
                grid: { top: 10, right: 50, bottom: 24, left: 50 },
                xAxis: { type: 'category', data: yoyData.map(d => d.fy), axisLabel: { color: navy, fontSize: 11, fontWeight: 700, fontFamily: 'Inter' } },
                yAxis: { type: 'value', axisLabel: { color: gray, fontSize: 9, formatter: v => '$' + Math.round(v/1e6) + 'M' }, splitLine: { lineStyle: { color: '#eef0f2' } } },
                series: [{
                    type: 'bar', data: yoyData.map((d, i) => ({
                        value: d.spend,
                        itemStyle: { color: i === 2 ? '#7f1d1d' : navy, borderRadius: [4,4,0,0] }
                    })),
                    barWidth: '45%',
                    label: { show: true, position: 'top', color: gray, fontSize: 10, fontWeight: 600, fontFamily: 'Inter',
                        formatter: p => fmt$(p.value) + (p.dataIndex === 2 ? '\n+' + yoy.yoy_spend_change_pct + '% YoY' : '') }
                }]
            });
            window._distCharts = (window._distCharts || []).concat([yoyChart]);
        }

        // YoY Drivers explanation
        if (data.yoy_drivers) {
            const drv = data.yoy_drivers;
            document.getElementById('yoyDrivers').innerHTML = `
                <div style="border-top:2px solid ${navy}; padding:16px 0; margin:12px 0;">
                    <div style="font-size:13px; font-weight:700; color:${navy}; margin-bottom:14px;">FY25 ($5.76M) &rarr; FY26 ($8.16M) = <span style="color:#7f1d1d;">+${fmt$(drv.total_increase)}</span></div>
                    ${drv.factors.map(f => {
                        const deltaStr = f.delta != null ? (f.delta > 0 ? '+' : '') + fmt$(f.delta) : '';
                        const deltaColor = f.delta > 0 ? '#7f1d1d' : (f.delta < 0 ? '#14532d' : navy);
                        return `<div style="display:flex; gap:16px; padding:10px 0; border-bottom:1px solid #eef0f2; align-items:flex-start;">
                            <div style="min-width:70px; text-align:right;">
                                ${deltaStr ? `<div style="font-size:14px; font-weight:700; color:${deltaColor};">${deltaStr}</div>` : ''}
                                ${f.pct_of_increase != null ? `<div style="font-size:8px; color:${gray};">${f.pct_of_increase > 0 ? f.pct_of_increase + '% of increase' : 'offset'}</div>` : '<div style="font-size:8px; color:'+gray+';">structural</div>'}
                            </div>
                            <div style="flex:1;">
                                <div style="font-size:12px; font-weight:700; color:${navy}; margin-bottom:3px;">${f.title}</div>
                                <div style="font-size:10.5px; color:var(--rpt-body); line-height:1.6;">${f.detail}</div>
                                ${f.note ? '<div style="font-size:9px; color:'+gray+'; font-style:italic; margin-top:3px;">'+f.note+'</div>' : ''}
                            </div>
                        </div>`;
                    }).join('')}
                </div>`;
        }

        // Seasonal overlay chart (Jul-Dec only — comparable across all 3 FYs)
        if (data.seasonal_trend) {
            const st = data.seasonal_trend;
            const seasonChart = echarts.init(document.getElementById('chartSeasonal'));
            seasonChart.setOption({
                tooltip: { trigger: 'axis', formatter: params => {
                    let html = '<strong>' + params[0].name + '</strong>';
                    params.forEach(p => { if (p.value) html += '<br>' + p.marker + ' ' + p.seriesName + ': ' + fmt$(p.value); });
                    return html;
                }},
                legend: { data: ['FY2024 ($1.53M)', 'FY2025 ($2.47M)', 'FY2026 ($7.46M)'], bottom: 0, textStyle: { fontSize: 9, fontFamily: 'Inter', color: gray }, itemWidth: 16, itemHeight: 2 },
                grid: { top: 10, right: 10, bottom: 36, left: 50 },
                xAxis: { type: 'category', data: st.months, axisLabel: { color: navy, fontSize: 11, fontWeight: 700 }, axisTick: { show: false } },
                yAxis: { type: 'value', axisLabel: { color: gray, fontSize: 9, formatter: v => '$' + Math.round(v/1000) + 'K' }, splitLine: { lineStyle: { color: '#eef0f2' } } },
                series: [
                    { name: 'FY2024 ($1.53M)', type: 'bar', data: st.fy24, barGap: '10%', barWidth: '22%', itemStyle: { color: '#c8dcea', borderRadius: [3,3,0,0] } },
                    { name: 'FY2025 ($2.47M)', type: 'bar', data: st.fy25, barWidth: '22%', itemStyle: { color: '#3d7ab5', borderRadius: [3,3,0,0] } },
                    { name: 'FY2026 ($7.46M)', type: 'bar', data: st.fy26, barWidth: '22%', itemStyle: { color: '#7f1d1d', borderRadius: [3,3,0,0] } },
                ]
            });
            window._distCharts = (window._distCharts || []).concat([seasonChart]);
        }

        // Monthly reason stacked bar
        const mr = da.monthly_by_reason;
        const reasonCats = ['min_staffing', 'above_grade', 'peak_demand', 'training', 'work_past_shift', 'other'];
        const reasonLabels = ['Min Staffing', 'Above Grade', 'Peak Demand', 'Training', 'Work Past Shift', 'Other'];
        const reasonColors = ['#0f2b4a', '#1e5080', '#3d7ab5', '#7fafd4', '#a8c8de', '#c8dcea'];
        const mrChart = echarts.init(document.getElementById('chartMonthlyReason'));
        mrChart.setOption({
            tooltip: { trigger: 'axis', formatter: params => {
                let html = '<strong>' + params[0].name + '</strong>';
                let total = 0;
                params.forEach(p => { html += '<br>' + p.marker + ' ' + p.seriesName + ': ' + fmt$(p.value); total += p.value; });
                html += '<br><strong>Total: ' + fmt$(total) + '</strong>';
                return html;
            }},
            legend: { data: reasonLabels, bottom: 0, textStyle: { fontSize: 9, fontFamily: 'Inter', color: gray }, itemWidth: 12, itemHeight: 8 },
            grid: { top: 10, right: 10, bottom: 40, left: 50 },
            xAxis: { type: 'category', data: mr.map(m => m.month.substring(5)), axisLabel: { color: gray, fontSize: 9 } },
            yAxis: { type: 'value', axisLabel: { color: gray, fontSize: 9, formatter: v => '$' + Math.round(v/1000) + 'K' }, splitLine: { lineStyle: { color: '#eef0f2' } } },
            series: reasonCats.map((cat, i) => ({
                name: reasonLabels[i], type: 'bar', stack: 'total',
                data: mr.map(m => m[cat]?.spend || 0),
                itemStyle: { color: reasonColors[i] },
                barWidth: '60%'
            }))
        });

        // Above Grade trend line
        const agm = da.above_grade_monthly;
        const agRanks = ['lt', 'fao', 'dc', 'ac'];
        const agLabels = ['Acting LT', 'Acting FAO', 'Acting DC', 'Acting AC'];
        const agColors = ['#0f2b4a', '#1e5080', '#3d7ab5', '#7fafd4'];
        const agChart = echarts.init(document.getElementById('chartAboveGradeTrend'));
        agChart.setOption({
            tooltip: { trigger: 'axis', formatter: params => {
                let html = '<strong>' + params[0].name + '</strong>';
                params.forEach(p => { html += '<br>' + p.marker + ' ' + p.seriesName + ': ' + fmt$(p.value); });
                return html;
            }},
            legend: { data: agLabels, bottom: 0, textStyle: { fontSize: 9, fontFamily: 'Inter', color: gray }, itemWidth: 16, itemHeight: 2 },
            grid: { top: 10, right: 10, bottom: 36, left: 50 },
            xAxis: { type: 'category', data: agm.map(m => m.month.substring(5)), axisLabel: { color: gray, fontSize: 9 } },
            yAxis: { type: 'value', axisLabel: { color: gray, fontSize: 9, formatter: v => '$' + Math.round(v/1000) + 'K' }, splitLine: { lineStyle: { color: '#eef0f2' } } },
            series: agRanks.map((rank, i) => ({
                name: agLabels[i], type: 'line', smooth: true,
                data: agm.map(m => m[rank]?.spend || 0),
                lineStyle: { color: agColors[i], width: 2 },
                itemStyle: { color: agColors[i] },
                areaStyle: { color: agColors[i], opacity: 0.06 },
                symbol: 'circle', symbolSize: 5
            }))
        });

        // Above Grade insight
        const agFirst = agm.slice(0, 3).reduce((s, m) => s + (m.lt?.spend||0) + (m.fao?.spend||0) + (m.dc?.spend||0), 0);
        const agLast = agm.slice(-3).reduce((s, m) => s + (m.lt?.spend||0) + (m.fao?.spend||0) + (m.dc?.spend||0), 0);
        const agPctChange = Math.round((agLast / agFirst - 1) * 100);
        document.getElementById('agInsight').innerHTML = `<div class="finding"><p><strong>Above-grade OT increased ${agPctChange}% from the first to second half of FY26.</strong> Acting-as-LT spend went from ${fmt$(agm[0]?.lt?.spend||0)}/mo to ${fmt$(agm[agm.length-1]?.lt?.spend||0)}/mo. This confirms vacancy positions are not being filled &mdash; the problem is accelerating.</p></div>`;

        // Per-unit daily OT personnel cards
        document.getElementById('perUnitCards').innerHTML = `<div style="display:grid; grid-template-columns:repeat(3,1fr); gap:14px; margin:12px 0;">
            ${ut.filter(u => u.ot_personnel_per_day != null).map(u => {
                const pctColor = (u.pct_positions_ot||0) >= 20 ? red : (u.pct_positions_ot||0) >= 10 ? amber : navy;
                return `<div style="border-top:2px solid ${pctColor}; padding:16px 14px; text-align:center;">
                <div style="font-size:8px; font-weight:700; text-transform:uppercase; letter-spacing:0.1em; color:${gray}; margin-bottom:8px;">${u.type}</div>
                <div style="font-size:28px; font-weight:700; color:${pctColor}; letter-spacing:-0.02em;">${u.pct_positions_ot}%</div>
                <div style="font-size:9px; color:${gray}; margin-top:2px;">of positions filled by OT daily</div>
                <div style="font-size:11px; color:var(--rpt-body); margin-top:10px; font-weight:600;">${u.ot_personnel_per_day} OT personnel / day</div>
                <div style="font-size:9px; color:${gray}; margin-top:2px;">${u.unit_count} units &middot; ${u.positions_per_shift} positions/shift</div>
            </div>`}).join('')}
        </div>`;

        // District bar chart + cards with real data
        if (da.districts_data) {
            const dd = da.districts_data;
            const distNotes = {'District 1': 'Downtown, OTR, Clifton', 'District 2': 'East — Hyde Park, Madisonville', 'District 3': 'West — Price Hill, Westwood', 'District 4': 'North — College Hill, Colerain'};

            // District bar chart
            const distChart = echarts.init(document.getElementById('chartDistrictBar'));
            distChart.setOption({
                tooltip: { trigger: 'axis', formatter: p => `<strong>${p[0].name}</strong><br>${fmt$(p[0].value)} (${dd.find(d=>d.name===p[0].name)?.pct_hours}%)` },
                grid: { top: 4, right: 80, bottom: 4, left: 4, containLabel: true },
                xAxis: { type: 'value', show: false },
                yAxis: { type: 'category', data: dd.map(d => d.name).reverse(), axisLabel: { color: navy, fontSize: 10, fontWeight: 600, fontFamily: 'Inter' }, axisLine: { show: false }, axisTick: { show: false } },
                series: [{ type: 'bar', data: dd.map(d => ({ value: d.spend, itemStyle: { color: d.pct_hours > 28 ? '#7f1d1d' : navy, borderRadius: [0,3,3,0] } })).reverse(), barWidth: '55%', label: { show: true, position: 'right', color: gray, fontSize: 9, fontFamily: 'Inter', formatter: p => fmt$(p.value) + ' (' + dd.slice().reverse()[p.dataIndex].pct_hours + '%)' } }]
            });
            window._distCharts = (window._distCharts || []).concat([distChart]);

            // District cards
            document.getElementById('districtCards').innerHTML = `<div style="display:grid; grid-template-columns:repeat(2,1fr); gap:12px; margin:12px 0;">
                ${dd.map(d => `<div style="border-top:2px solid ${d.pct_hours > 28 ? '#7f1d1d' : navy}; padding:14px 16px;">
                    <div style="display:flex; justify-content:space-between; align-items:baseline;">
                        <div style="font-size:14px; font-weight:700; color:${navy};">${d.name}</div>
                        <div style="font-size:20px; font-weight:700; color:${d.pct_hours > 28 ? '#7f1d1d' : navy};">${fmt$(d.spend)}</div>
                    </div>
                    <div style="font-size:9px; color:${gray}; margin:4px 0;">${distNotes[d.name] || ''} &middot; ${d.pct_hours}% of total OT &middot; ${d.people} personnel</div>
                    <div style="font-size:10px; color:var(--rpt-body); margin-top:8px;"><strong>Top units:</strong> ${d.top_units.slice(0,4).map(u => u.code + ' (' + u.hours.toLocaleString() + ' hrs)').join(', ')}</div>
                </div>`).join('')}
            </div>`;
        }

        // Top units table
        if (da.top_companies) {
            document.getElementById('topUnitsTable').innerHTML = `<table>
                <thead><tr><th>Unit</th><th>Type</th><th>District</th><th class="r">OT Hours</th><th class="r">Avg/Day</th><th class="r">People</th></tr></thead>
                <tbody>${da.top_companies.slice(0,8).map((c,i) => `<tr>
                    <td class="b">${c.code}</td><td>${c.type}</td><td>${c.district}</td>
                    <td class="r">${c.hours.toLocaleString()}</td><td class="r">${c.daily_avg} hrs</td>
                    <td class="r">${c.people}</td>
                </tr>`).join('')}</tbody>
            </table>`;
        }

        window._distCharts = [utChart, mrChart, agChart];
        window.addEventListener('resize', () => { utChart.resize(); mrChart.resize(); agChart.resize(); });
    }

    // ─── Top Earners ───
    if (data.top_earners) {
        document.getElementById('topEarnersTable').innerHTML = `<table>
            <thead><tr><th>#</th><th>Name</th><th>Rank</th><th class="r">OT Spend</th><th class="r">OT Hours</th><th class="r">~24hr Shifts</th></tr></thead>
            <tbody>${data.top_earners.map((e, i) => `<tr>
                <td>${i+1}</td><td class="b">${e.name}</td><td>${e.rank_group}</td>
                <td class="r b" style="color:${e.spend > 75000 ? '#7f1d1d' : e.spend > 50000 ? '#92400e' : navy};">$${e.spend.toLocaleString()}</td>
                <td class="r">${e.hours.toLocaleString()}</td>
                <td class="r b">${e.shifts_equiv}</td>
            </tr>`).join('')}</tbody></table>
            <div style="font-size:10px; color:${gray}; margin-top:6px;">Each "24hr shift" equivalent = 24 hours of OT worked beyond regular duty assignment.</div>`;
    }

    // ─── Payroll Total OT ───
    if (data.payroll_ot_total) {
        const pot = data.payroll_ot_total;
        const plt = data.payroll_leave_total || {};
        document.getElementById('payrollOT').innerHTML = `
            <div style="display:grid; grid-template-columns:1fr 1fr; gap:20px; margin:12px 0;">
                <div style="border-top:2px solid #7f1d1d; padding:16px;">
                    <div style="font-size:8px; font-weight:700; text-transform:uppercase; letter-spacing:0.1em; color:${gray}; margin-bottom:8px;">Total OT Cost (All Payroll Codes)</div>
                    <div style="font-size:28px; font-weight:700; color:#7f1d1d; letter-spacing:-0.02em;">${fmt$(pot.total_payroll_ot)}</div>
                    <div style="margin-top:10px; font-size:10px; color:var(--rpt-body); line-height:1.7;">
                        Fire 96-Hour OT: ${fmt$(pot.fire_96hr_ot)}<br>
                        Qualified OT (city memo): ${fmt$(pot.obbb_qualified_ot)}<br>
                        Time &amp; Half: ${fmt$(pot.time_half_ot)}<br>
                        Reimbursable OT: ${fmt$(pot.reimbursable_ot)}<br>
                        Paramedic Training OT: ${fmt$(pot.paramedic_training_ot)}
                    </div>
                    <div style="font-size:9px; color:${gray}; margin-top:8px; font-style:italic;">vs Internal OT system: ${fmt$(pot.internal_ot_total)} &middot; vs PBI measure: ${fmt$(pot.pbi_measure)}</div>
                </div>
                <div style="border-top:2px solid ${navy}; padding:16px;">
                    <div style="font-size:8px; font-weight:700; text-transform:uppercase; letter-spacing:0.1em; color:${gray}; margin-bottom:8px;">Total Leave Cost (FY26 Payroll)</div>
                    <div style="font-size:28px; font-weight:700; color:${navy}; letter-spacing:-0.02em;">${fmt$(plt.total_leave_cost)}</div>
                    <div style="margin-top:10px; font-size:10px; color:var(--rpt-body); line-height:1.7;">
                        Vacation: ${fmt$(plt.vacation)}<br>
                        Holiday Used: ${fmt$(plt.holiday_used)}<br>
                        Sick With Pay: ${fmt$(plt.sick_with_pay)}<br>
                        Parental Leave: ${fmt$(plt.parental)}<br>
                        Comp Time Taken: ${fmt$(plt.comp_time_taken)}
                    </div>
                    <div style="font-size:9px; color:${gray}; margin-top:8px; font-style:italic;">For every $1 in leave, the department pays $1.00 in OT</div>
                </div>
            </div>
            <div class="finding" style="margin-top:8px;"><p><strong>Payroll shows $11.4M across all OT earning codes</strong> — but $1.94M of that (QOP, AGO, LOU codes) has zero hours logged, suggesting these are pension/FLSA adjustments of the same overtime, not additional work. The verified OT cost remains <strong>$8.16M</strong> (Internal OT) or <strong>$8.85M</strong> (Power BI measure). Finance should confirm whether QOP ($1.82M) represents additional cost or a reclassification.</p></div>`;
    }

    // ─── Payroll Costs (NOT overtime) — Treemap + Detail Cards ───
    if (data.payroll_costs) {
        const pc = data.payroll_costs;
        const catColors = {
            'Vacation': '#1e5080',
            'Sick Leave': '#7f1d1d',
            'Holiday Pay': '#0f2b4a',
            'Certification Pay': '#14532d',
            'Parental Leave': '#3d7ab5',
            'Injury & Light Duty': '#92400e',
            'FMLA': '#7fafd4',
            'Comp Time': '#6b7280',
            'Military Leave': '#a8c8de',
        };

        // Build treemap data with sub-items
        const treemapData = pc.categories.map(cat => ({
            name: cat.name,
            value: cat.total,
            itemStyle: {
                color: catColors[cat.name] || navy,
                borderColor: '#fff',
                borderWidth: 2,
                gapWidth: 2,
            },
            children: cat.items.filter(i => i.cost > 0).map(item => ({
                name: item.desc,
                value: item.cost,
            }))
        }));

        const payChart = echarts.init(document.getElementById('payrollCosts'));
        payChart.setOption({
            tooltip: {
                formatter: function(p) {
                    if (p.treePathInfo && p.treePathInfo.length > 1) {
                        const parent = p.treePathInfo[1].name;
                        return `<div style="font-size:11px;"><strong>${parent}</strong><br>${p.name}<br><strong>${fmt$(p.value)}</strong></div>`;
                    }
                    const pct = (p.value / pc.grand_total * 100).toFixed(1);
                    return `<div style="font-size:11px;"><strong>${p.name}</strong><br>${fmt$(p.value)} (${pct}%)</div>`;
                }
            },
            series: [{
                type: 'treemap',
                roam: false,
                nodeClick: false,
                width: '100%',
                height: '100%',
                breadcrumb: { show: false },
                levels: [
                    {
                        // Category level
                        itemStyle: {
                            borderColor: '#fff',
                            borderWidth: 3,
                            gapWidth: 3,
                        },
                        upperLabel: {
                            show: true,
                            height: 28,
                            color: '#fff',
                            fontSize: 11,
                            fontWeight: 700,
                            fontFamily: 'Inter',
                            formatter: function(p) {
                                const pct = (p.value / pc.grand_total * 100).toFixed(0);
                                return p.name + '  ' + fmt$(p.value) + '  (' + pct + '%)';
                            },
                            backgroundColor: 'inherit',
                            borderRadius: 0,
                            padding: [6, 8],
                        },
                    },
                    {
                        // Sub-item level
                        itemStyle: {
                            borderColor: 'rgba(255,255,255,0.3)',
                            borderWidth: 1,
                            gapWidth: 1,
                        },
                        label: {
                            show: true,
                            fontSize: 9,
                            fontFamily: 'Inter',
                            color: 'rgba(255,255,255,0.9)',
                            formatter: function(p) {
                                if (p.value < pc.grand_total * 0.015) return '';
                                return p.name + '\n' + fmt$(p.value);
                            },
                        },
                    }
                ],
                data: treemapData,
            }],
            graphic: [{
                type: 'text',
                right: 10,
                bottom: 4,
                style: {
                    text: '$' + (pc.grand_total / 1e6).toFixed(2) + 'M TOTAL PAYROLL',
                    fontSize: 10,
                    fontWeight: 700,
                    fill: 'rgba(255,255,255,0.7)',
                    fontFamily: 'Inter',
                }
            }]
        });

        // Detail cards below
        const detailDiv = document.createElement('div');
        detailDiv.style.marginTop = '16px';
        detailDiv.innerHTML = `
            <div style="display:grid; grid-template-columns:repeat(3, 1fr); gap:12px; margin-bottom:12px;">
                ${pc.categories.slice(0, 6).map(cat => {
                    const color = catColors[cat.name] || navy;
                    const pct = (cat.total / pc.grand_total * 100).toFixed(1);
                    return `<div style="border-left:3px solid ${color}; padding:10px 14px; break-inside:avoid;">
                        <div style="display:flex; justify-content:space-between; align-items:baseline;">
                            <div style="font-size:11px; font-weight:700; color:${navy};">${cat.name}</div>
                            <div style="font-size:14px; font-weight:700; color:${color};">${fmt$(cat.total)}</div>
                        </div>
                        <div style="font-size:8px; color:${gray}; margin-top:2px;">${pct}% of total</div>
                        <div style="margin-top:6px;">
                            ${cat.items.slice(0, 3).map(item => `<div style="font-size:9px; color:var(--rpt-body); padding:2px 0; display:flex; justify-content:space-between;">
                                <span>${item.desc}</span>
                                <span style="font-weight:600; color:${navy};">${fmt$(item.cost)}</span>
                            </div>`).join('')}
                            ${cat.items.length > 3 ? `<div style="font-size:8px; color:${gray}; font-style:italic;">+ ${cat.items.length - 3} more</div>` : ''}
                        </div>
                    </div>`;
                }).join('')}
            </div>
            <div style="display:grid; grid-template-columns:repeat(3, 1fr); gap:12px;">
                ${pc.categories.slice(6).map(cat => {
                    const color = catColors[cat.name] || navy;
                    const pct = (cat.total / pc.grand_total * 100).toFixed(1);
                    return `<div style="border-left:3px solid ${color}; padding:10px 14px; break-inside:avoid;">
                        <div style="display:flex; justify-content:space-between; align-items:baseline;">
                            <div style="font-size:11px; font-weight:700; color:${navy};">${cat.name}</div>
                            <div style="font-size:14px; font-weight:700; color:${color};">${fmt$(cat.total)}</div>
                        </div>
                        <div style="font-size:8px; color:${gray}; margin-top:2px;">${pct}% of total</div>
                        <div style="margin-top:6px;">
                            ${cat.items.slice(0, 3).map(item => `<div style="font-size:9px; color:var(--rpt-body); padding:2px 0; display:flex; justify-content:space-between;">
                                <span>${item.desc}</span>
                                <span style="font-weight:600; color:${navy};">${fmt$(item.cost)}</span>
                            </div>`).join('')}
                        </div>
                    </div>`;
                }).join('')}
            </div>
            <div style="text-align:center; font-size:9px; color:${gray}; margin-top:12px;">Source: PS_C_PAYCHK_FISCAL · FY2026 payroll earning codes · All figures are direct costs, not overtime</div>`;
        document.getElementById('payrollCosts').parentNode.insertBefore(detailDiv, document.getElementById('payrollCosts').nextSibling);

        window.addEventListener('resize', () => payChart.resize());
    }

    // ─── Parental / Family Leave (now in payroll costs section) ───
    if (data.parental_leave) {
        const pl = data.parental_leave;
        document.getElementById('parentalLeave').innerHTML = `
            <div style="display:grid; grid-template-columns:repeat(3,1fr); gap:14px; margin:12px 0;">
                <div style="border-top:2px solid ${navy}; padding:14px; text-align:center;">
                    <div style="font-size:8px; font-weight:700; text-transform:uppercase; letter-spacing:0.1em; color:${gray};">Parental Leave Paid</div>
                    <div style="font-size:22px; font-weight:700; color:${navy}; margin:6px 0;">${fmt$(pl.paid_cost)}</div>
                    <div style="font-size:9px; color:${gray};">${pl.paid_records} records &middot; ${pl.paid_hours.toLocaleString()} hrs</div>
                </div>
                <div style="border-top:2px solid ${navy}; padding:14px; text-align:center;">
                    <div style="font-size:8px; font-weight:700; text-transform:uppercase; letter-spacing:0.1em; color:${gray};">FMLA (All Types)</div>
                    <div style="font-size:22px; font-weight:700; color:${navy}; margin:6px 0;">${fmt$(pl.fmla_cost)}</div>
                    <div style="font-size:9px; color:${gray};">${pl.fmla_records} records &middot; ${pl.fmla_hours.toLocaleString()} hrs</div>
                </div>
                <div style="border-top:2px solid ${navy}; padding:14px; text-align:center;">
                    <div style="font-size:8px; font-weight:700; text-transform:uppercase; letter-spacing:0.1em; color:${gray};">Sick Pay Family</div>
                    <div style="font-size:22px; font-weight:700; color:${navy}; margin:6px 0;">${fmt$(pl.sick_family_cost)}</div>
                    <div style="font-size:9px; color:${gray};">${pl.sick_family_records} records &middot; ${pl.sick_family_hours.toLocaleString()} hrs</div>
                </div>
            </div>
            <div style="text-align:center; font-size:12px; font-weight:700; color:${navy}; margin-top:4px;">Total Family-Related Leave: ${fmt$(pl.total_family_cost)}</div>
            <div style="text-align:center; font-size:9px; color:${gray}; margin-top:2px;">Source: PS_C_PAYCHK_FISCAL &middot; FY2026</div>`;
    }

    // ─── Low-Hanging Fruit ───
    if (data.low_hanging_fruit) {
        document.getElementById('lowHangingFruit').innerHTML = data.low_hanging_fruit.map(f => `
            <div style="display:flex; gap:16px; padding:12px 0; border-bottom:1px solid #eef0f2; align-items:flex-start;">
                <div style="min-width:90px;">
                    <div style="font-size:14px; font-weight:700; color:${green};">${f.savings}</div>
                    <div style="font-size:8px; font-weight:600; color:${gray}; text-transform:uppercase;">${f.timeline}</div>
                </div>
                <div>
                    <div style="font-size:12px; font-weight:700; color:${navy}; margin-bottom:3px;">${f.title}</div>
                    <div style="font-size:10.5px; color:var(--rpt-body); line-height:1.6;">${f.detail}</div>
                </div>
            </div>`).join('');
    }

    // FY27 forecast removed — pay periods are processing cycles (not uniform
    // time periods), so annualization is not mathematically valid.
    // To project FY27, we need: (1) Jan-Jun 2026 data from John,
    // (2) confirmation of how many processing cycles = 1 fiscal year.

    // ─── Recruit Class Impact ───
    if (data.recruit_class) {
        const rc = data.recruit_class;
        const cb = rc.callback_breakdown;
        const ranks = cb.full_tour_by_rank;
        const rankOrder = ['FAO', 'LT', 'FF', 'CPT', 'DC'];

        document.getElementById('recruitImpact').innerHTML = `
            <div style="border-top:2px solid ${navy}; padding:16px 0; margin:12px 0;">
                <div style="display:grid; grid-template-columns:repeat(3,1fr); gap:14px; margin-bottom:16px; text-align:center;">
                    <div style="border-top:2px solid ${green}; padding:12px;">
                        <div style="font-size:8px; font-weight:700; text-transform:uppercase; letter-spacing:0.1em; color:${gray};">FF-Rank Callbacks (Addressable)</div>
                        <div style="font-size:22px; font-weight:700; color:${green};">${fmt$(ranks.FF.spend)}</div>
                        <div style="font-size:9px; color:${gray};">${ranks.FF.records} records &middot; can be absorbed by new FFs</div>
                    </div>
                    <div style="border-top:2px solid #7f1d1d; padding:12px;">
                        <div style="font-size:8px; font-weight:700; text-transform:uppercase; letter-spacing:0.1em; color:${gray};">Higher-Rank Callbacks (Not Addressable)</div>
                        <div style="font-size:22px; font-weight:700; color:#7f1d1d;">${fmt$(cb.not_addressable)}</div>
                        <div style="font-size:9px; color:${gray};">Requires FAO/LT/CPT/DC &mdash; not recruits</div>
                    </div>
                    <div style="border-top:2px solid ${navy}; padding:12px;">
                        <div style="font-size:8px; font-weight:700; text-transform:uppercase; letter-spacing:0.1em; color:${gray};">Estimated Annual Reduction</div>
                        <div style="font-size:22px; font-weight:700; color:${navy};">${fmt$(rc.estimated_reduction.conservative)}&ndash;${fmt$(rc.estimated_reduction.optimistic)}</div>
                        <div style="font-size:9px; color:${gray};">Conservative to optimistic range</div>
                    </div>
                </div>
                <div style="font-size:11px; font-weight:700; color:${navy}; margin-bottom:8px;">Full Tour Callback Spend by Rank</div>
                <table><thead><tr><th>Rank</th><th class="r">Callback Spend</th><th class="r">Records</th><th>Can 46 New FFs Replace?</th></tr></thead>
                <tbody>${rankOrder.map(rk => {
                    const r = ranks[rk];
                    const replaceable = rk === 'FF';
                    return '<tr><td class="b">' + rk + '</td><td class="r">' + fmt$(r.spend) + '</td><td class="r">' + r.records + '</td><td>' + (replaceable ? '<span style="color:'+green+';font-weight:600;">YES — direct replacement</span>' : '<span style="color:#7f1d1d;font-weight:600;">NO — requires ' + rk + ' rank</span>') + '</td></tr>';
                }).join('')}
                <tr style="border-top:2px solid ${navy};"><td class="b">TOTAL</td><td class="r b">${fmt$(cb.full_tour_total)}</td><td class="r b">${Object.values(ranks).reduce((s,r)=>s+r.records,0)}</td><td><span style="font-weight:600;">Only ${Math.round(ranks.FF.spend/cb.full_tour_total*100)}% addressable by recruits</span></td></tr>
                </tbody></table>
                <div class="finding" style="margin-top:12px;"><p>${rc.key_insight}</p></div>
            </div>`;
    }

    // ─── Workforce Risk Factors ───
    if (data.workforce_risks) {
        const sevColor = { critical: '#7f1d1d', high: '#92400e', medium: navy };
        document.getElementById('riskCards').innerHTML = data.workforce_risks.map(r => {
            const c = sevColor[r.severity] || navy;
            return `<div style="display:flex; gap:20px; padding:14px 20px; margin-bottom:8px; border-left:3px solid ${c}; break-inside:avoid; align-items:flex-start;">
                <div style="min-width:80px; text-align:center;">
                    <div style="font-size:22px; font-weight:700; color:${c}; letter-spacing:-0.02em;">${r.stat}</div>
                    <div style="font-size:7px; font-weight:700; text-transform:uppercase; letter-spacing:0.1em; color:${c}; margin-top:2px;">${r.severity}</div>
                </div>
                <div style="flex:1;">
                    <div style="font-size:12px; font-weight:700; color:${navy}; margin-bottom:3px;">${r.title}</div>
                    <div style="font-size:10.5px; color:var(--rpt-body); line-height:1.6;">${r.detail}</div>
                </div>
            </div>`;
        }).join('');
    }

    window.addEventListener('resize', () => wf.resize());

    // Resize ALL ECharts for print (Cmd+P and Playwright PDF)
    function resizeAllChartsForPrint() {
        document.querySelectorAll('[id^="chart"]').forEach(el => {
            try {
                const inst = echarts.getInstanceByDom(el);
                if (inst) inst.resize({ width: 650 });
            } catch(e) {}
        });
    }
    function restoreAllCharts() {
        document.querySelectorAll('[id^="chart"]').forEach(el => {
            try {
                const inst = echarts.getInstanceByDom(el);
                if (inst) inst.resize();
            } catch(e) {}
        });
    }
    window.addEventListener('beforeprint', resizeAllChartsForPrint);
    window.addEventListener('afterprint', restoreAllCharts);
});
</script>
