<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    {% if domain %}<meta name="fs-domain" content="{{ domain }}">{% endif %}
    <title>Triage Prioritization - Finite State Report</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/dompurify@3/dist/purify.min.js"></script>

    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            line-height: 1.6; color: #333; background-color: #f8f9fa;
        }
        .container { margin: 0 auto; padding: 20px; background-color: white; border: 1px solid #dee2e6; min-height: 100%; }
        .header { text-align: center; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 2px solid #e9ecef; }
        .company-logo { max-height: 50px; width: auto; margin: 0 auto; display: block; }
        .header h1 { color: #2c3e50; font-size: 2.5em; margin-bottom: 10px; }
        .header .subtitle { color: #6c757d; font-size: 1.1em; }
        .metadata { background-color: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 30px; font-size: 0.9em; color: #6c757d; }

        /* Summary Cards */
        .summary-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 15px; margin-bottom: 30px; }
        .summary-card {
            padding: 20px; border-radius: 8px; text-align: center;
            color: white; font-weight: bold;
        }
        .summary-card .count { font-size: 2.5em; line-height: 1; }
        .summary-card .label { font-size: 0.9em; margin-top: 5px; opacity: 0.9; }
        .card-critical { background-color: #dc3545; }
        .card-high { background-color: #fd7e14; }
        .card-medium { background-color: #ffc107; color: #333; }
        .card-low { background-color: #28a745; }
        .card-info { background-color: #6c757d; }
        .card-total { background-color: #2c3e50; }

        /* Chart containers */
        .chart-container { background-color: white; border-radius: 8px; padding: 20px; margin-bottom: 30px; border: 1px solid #dee2e6; }
        .chart-wrapper { position: relative; height: 400px; margin: 20px 0; }
        .chart-wrapper-tall { position: relative; height: 500px; margin: 20px 0; }
        .chart-title { color: #2c3e50; font-size: 1.5em; margin-bottom: 10px; }
        .chart-description { color: #6c757d; font-size: 0.95em; margin-bottom: 20px; }

        /* Two-column layout */
        .two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px; }
        @media (max-width: 900px) { .two-col { grid-template-columns: 1fr; } }

        /* Narrative */
        .narrative-section { background-color: white; border-radius: 8px; padding: 20px; margin-bottom: 30px; border: 1px solid #dee2e6; }
        .narrative-section h3 { color: #2c3e50; margin-bottom: 15px; }
        .narrative-section h4 { color: #495057; margin: 20px 0 10px 0; }
        .narrative-section ul { margin-left: 20px; margin-bottom: 15px; }
        .narrative-section li { margin-bottom: 8px; }

        /* Tables */
        .table-container { background-color: white; border-radius: 8px; padding: 20px; margin-bottom: 30px; border: 1px solid #dee2e6; overflow-x: auto; }
        table { width: 100%; border-collapse: collapse; margin-top: 15px; }
        th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid #dee2e6; }
        th { background-color: #f8f9fa; font-weight: 600; color: #495057; position: sticky; top: 0; z-index: 10; }
        td code { word-break: break-all; white-space: pre-wrap; display: inline-block; max-width: 280px; font-size: 0.85em; }
        tr:hover { background-color: #f8f9fa; }

        /* Band badges */
        .band-badge { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 0.8em; font-weight: 600; color: white; }
        .band-CRITICAL { background-color: #dc3545; }
        .band-HIGH { background-color: #fd7e14; }
        .band-MEDIUM { background-color: #ffc107; color: #333; }
        .band-LOW { background-color: #28a745; }
        .band-INFO { background-color: #6c757d; }

        /* Reachability badges (matches CVE Impact styling) */
        .reach-badge { padding: 3px 8px; border-radius: 4px; font-size: 0.75em; font-weight: 600; }
        .reach-REACHABLE { background: #dc3545; color: white; }
        .reach-UNREACHABLE { background: #28a745; color: white; }
        .reach-INCONCLUSIVE { background: #6c757d; color: white; }
        .reach-UNKNOWN { background: #adb5bd; color: #333; }

        /* VEX status badges (set statuses — solid border) */
        .vex-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.7rem; font-weight: 600; white-space: nowrap; }
        .vex-IN_TRIAGE { background: #fff8e1; color: #856404; border: 1px solid #e6a817; }
        .vex-NOT_AFFECTED, .vex-FALSE_POSITIVE { background: #e6ffe6; color: #166534; border: 1px solid #28a745; }
        .vex-EXPLOITABLE { background: #fef2f2; color: #991b1b; border: 1px solid #dc3545; }
        .vex-RESOLVED, .vex-RESOLVED_WITH_PEDIGREE { background: #e6ffe6; color: #166534; border: 1px solid #28a745; }

        /* VEX recommendation badges (dashed border = not yet applied) */
        .vex-rec-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.7rem; font-weight: 600; border: 1.5px dashed; white-space: nowrap; }
        .vex-rec-NOT_AFFECTED { background: #f0fdf4; color: #166534; border-color: #86efac; }
        .vex-rec-IN_TRIAGE { background: #fefce8; color: #854d0e; border-color: #fde047; }

        /* Icons */
        .icon-exploit { color: #6f42c1; }
        .icon-kev { color: #dc3545; }

        /* Entity links */
        a.entity-link { color: #007bff; text-decoration: none; font-weight: 500; }
        a.entity-link:hover { text-decoration: underline; color: #0056b3; }
        a.entity-link-cve { color: #6f42c1; }
        a.entity-link-cve:hover { color: #533399; }
        a.entity-link-component { color: #0d6efd; }
        a.entity-link-component:hover { color: #0a58ca; }
        a.entity-link-folder { color: #198754; }
        a.entity-link-folder:hover { color: #146c43; }

        .footer { text-align: center; margin-top: 40px; padding-top: 20px; border-top: 1px solid #e9ecef; color: #6c757d; font-size: 0.9em; }
        .finite-state-logo { color: #007bff; font-weight: bold; }
        .action-btn.btn-remediation { border-color: #2563eb; color: #2563eb; }
        .action-btn.btn-remediation:hover { background: #eff6ff; }
        .no-data { text-align: center; color: #6c757d; font-style: italic; padding: 40px; }

        .show-more-btn {
            background: none;
            border: 1px solid #dee2e6;
            border-radius: 6px;
            padding: 6px 16px;
            color: #495057;
            cursor: pointer;
            font-size: 0.9em;
        }
        .show-more-btn:hover { background-color: #f8f9fa; }
        .hidden-row { display: none; }

        @media print {
            body { background-color: white; }
            .container { max-width: none; }
            .chart-container, .narrative-section, .table-container { border: 1px solid #dee2e6; }
            .hidden-row { display: table-row; }
            .show-more-row { display: none; }
        }
    </style>
</head>
<body>
    <span id="fsConnectionBadge"></span>
    <div class="container">
        <!-- Header -->
        <div class="header">
            {% if logo_path %}
            <div class="logo-container">
                <img src="{{ logo_path }}" alt="Company Logo" class="company-logo">
            </div>
            {% else %}
            <div class="logo-container">
                <img src="data:image/webp;base64,UklGRogLAABXRUJQVlA4WAoAAAAQAAAA/wAAMAAAQUxQSJsHAAAB8EZt/zql/v9dNDjCYPdgd7cLRnfZsdgg2N0KdmF3d7did3d3dxe1ckut7YzCmNeNeb6e8xzeMe+4ExETgP/zzTe0okPn0ieRlhm5HLcm95m+4CUTejk7ZpUOkKuLQjfWxDt/OGD5Fv7gqVqAE1BsHbmvgoPlNiiFzwOBXFVRvARQ5yy/z8vrSLV6wvShHkBw7CKEmIZ7AEEv+dcAV0ep+jFyaUGg5mlyLgLJ520Az+EZfNTcISq0nDxaDSiwjLwneJbFY1WBwivII1UdHs9hGXzUAnALS+WjX+twHgIZXvIouSgfUOM4uaSAY9PmBT8MdAOaPWJauBtqcRECOQJo9Ywf+rsArZ8wdbC7A6NL4oJ8QIX95PKCADpxuQgeQzN4vzHgHpbBev9WOLn9c8mZ/ADIPesbT9cE4LeJDEMrAVB4Fbm3HLCQDf71VNqxTXNPT8HY3dt2VBR4nWmhVWLrtl1TRXV2bdsdLojYtU12T6hV8J5tmrsDFNyH01W+CQagizDzZiOgJUfArX9RALXPMaUwFrG+GvfQyOt3zyxs6i5qvWiu7MKeMoWnOMnUXjRXdv5oN0GnRXM15xUXNaHkZsE1ko1F8T87adQh+dRJEEryqOAG5RdazaZkLyVuUUm5AYS+ZXwPQOT8Kj3cHcAJllFW5g7FN30FKyl/SWYc/WX6UD7RS3CYkkbRHzIbBOdJ/i56T44Q1fpJ3hMFk9wvuGLDPKtpMt3VvHvnipJn+XWmL9C1s+gh+aQVsIXlVPm+pOYLN8ESG85I6OO4X6anDdGiPTL+Mpb4hISEhPS5SjjfHntTExLiP5HMiE9ISI/Q+hifkJCQkBZik0+KVZQ7evNsGaDOGc4UuLz4sPInj5ZZx4qqhpH8ejry9jcuhHDOd7PZZCH51WQ2fz0oEUZ+qyHR+ZvZbMok+d1kNmc9lckymc1m86f6Mhd1Pnq93tdLDbe5q9Pp9Xr3SJJh7nq9r6fWDDe9Xq/3dbctVaMHO6DIapLT0ZJj4PLqPaoc5ZekH1WwmHVVHCI5CkD13X+IcvkZDPnGk4zMZzD45dPyek1yi4TOz2AoEELyVpFiBkNhJ4kB+Q3WHjJnIG0bz+ZSJlxHsge0rSKgWKI7wwf8zeuTOAMtORaub/4qCLR6QlbHYtZWcYxkGKydRMJ+JJdBvjtJZpbXEgaQvAhZqz8hb3XaTnzoh7pUt55kLxsW5DMYDH75Fb1/747O/MTkvqitgTNM6O0Mt8FJ9VRNJfl5YgHYOpDkCjnXR1ZcYUMjkpds6urq7e3t7Sn1NKR9+/ad66ljbJXcidnpi8lsNn89qsYjRmCZlweoKxgBeI818V4ToHBuVX6pJJm2qoK92pJZd8iPfvZLjY2Li0uZIyXepibzKUlmNPJ7k43E5+3Q8WQlAIZdXIDmfNQAQLE15IFKgCr8lkLrrAl2ukFerfGVnGM/8UYFm9WYKkWS5Jdg3wcvstkVO+gA6Cd95u3ayLuN3OgHoPZpfpvroQyG9R9pPd8uzUn2xGkypWA2iZT6YbFYLFyrJtOABbQOyznRPbscaN+tW7eejVXcEwEuPeMY280ZABrd5KcIHYDAZJZUBxSbkEryRzV7nCNjcyKY5AS77RwZERExsbnUjVJly5UrX1DN55LACCvOgTgbjIFin9RbGk3u0jzBB0BdT8CpezzfhToDO3+WsAdQOorkIDs0JJlyabzLGzI+l73aQN7qDKRVoIOFJFf80wgl1xoA1DzJ+40B+M60MALYzdJYpub3X62wnmSEHQ7T2uQ7muQQewUqOGs3/JZG8rFTdtkf2qVLly7dS6kLZxiAoqvJ2z+5rwKAFlxuVQYr1Bzn4Y4VizSOJ9lBXe0fAo7K95F8p7PT9IaNrX2zFaq8I+9lG82e6gaxLVxHpfN+U9Q6S8ssT1TnUnsUTCX51USSqQXUbSc5tOEfTWpgC8ledtIMkDmnLIrMFKHIbWUb1HVT4pUkCEZpMgzWgR9ZHbW4EtijrAUl+0BZJQv5BEJ/kk/ds4W/zHm5CzLRZFYpEbxPv5Q5ILPRhuky3ZW4XIrzwCAGoVAiLzYCUHozLeVRjauAHYJaCnL0uvtdkNAN0uEk12ptITlYhBsku2n9SvKG1EEZo8g3wN9YSa6S0T9AL3CuFeDfwFMDOUKcBXmM/sbyMqWN/sYCEgajv6Yxv0361DuAro1VK8BvA7m92kQLL9QFynENsE1QQwHgVK3vglWzQnJDvmRIUGh1DZdmbYODfDQqhgaF1tXKGxwU0lCqbkiQZnAe0b9Q77+4ohAAjOBBA4D6F0lGtQegm8MtorWsrMRhbPqQGcM8gco3+GlCTgAdH0/zBtAxinEtgS0sg8EdnRwZuA34wBeBgFP3OEZ1BOAMIOAKLdP0QEAUy8LxzbvgB0/VAvRTs3glAEDJSHJbaaDkVnJDDgcIqHSAXF0EKLWN3FxlQhavGQGfyVm8GgBHucl9/j3SCzBeIxndGUCXWMZ0ggPt0jeZr4MBdH0yxRswXmPmJG841nnmfOPZuoALUGorGVkCjnf5veS6YvCZksUrAXDM/7jDjMXvGN0JDrtzrwR+nugNRz5veFn8vy8AVlA4IMYDAAAQGgCdASoAATEAPkkgjUUioiESyu5UKASEsQQ4BkBmgLwBnkth/5WF+V/YBdQxT/QOuI4n3f8p+g27T8McrbSnnPdAf8D+ifkz2zPzT7AH6f9LvzAfx7/F+qf/mP1d9xnoAf3P0a/9V7GX7h+wx5c/7gfBZ+6H7pe0z//9aUQkk833U3/010TMZyNMJOh7BVPMaj385fqmxA57PseAo3OPAykt9bpJCWjkQopiYBocscHNNUKYIfiMw7n03MLowQrd4fwQ/M41Oz7l12lGBmcFpssA3NB7Dl8QAP7neK3fUwYp5YF+1PcER/+ufcR8oQYI6yXqgZ+T/3zLtzRKtMovH89ZN9GXVqgzBcNHzJeYGOpIi7+2dnE17hzWGcltXA9cOe//TdQCvR4RL1iBIHjF8qNyV2wnQ6OCx+IX++c1e1TBr9EkT2PvApXIL9ciPLLrFBk5WVBOd786zot/vI636pgA4rWfrPPePmbxeee2YaoxxnLiKfOVoIveCdjHuba3z/T9RSrbgBChtZy3o6VRTL9gYHas6vJL1DckHBPdUqpt/6hucimiN+4TAUeGk6Co18dNdz41fb9Xhwe7JVcG4CZ5vuyXOq1hCOX77L3meHa5gQp+8Kh1gBsRC3cHuTsPn4J4Rc/kZ+n/2vThrh//Ydy/oDH6aBMPjhx5xExdz3bIvdTdLp6nCZ278QP+HUSxdmcv3yJD1J86O2WsoYU2KhLU/t35z6VQNZMM2NkWnCAdNyW47zng1wQZYQLHY+5daCiFuhQ/xM3oVPdwEgxgAQk6owVnSSkMxe11YGvOfg/yAgwbUfvX+ggiHIbLrjZgVq+grhS9U3M3HtmKFz/kgwKRM7n8kk8LRYGK/9MYvArjTeoBsBiLk2I6tNAOI1BicVGLb008DuWG9RV0oqasjqcyxMuTd7CCLkWzXD//BS6oW/94CEbS3Pg7qi7eJwCT93iHAPhPuHcWx1dF7z+RLcx6bRqlnrqqinGaW5oYozyKstInug4w3eu9ypoHaLLwJb5D3/iADFjNPd10L2dbh9CFGP+rEjdupInLy2nUngp3wP/Z7iSiaO+SV6MF1J2ZAPsxJtXUFwVlgHcmwyqujWazTJ367dp6zib3FnY/7dHbxbZXzHEV37AnCuCiruTi/zm/9g8tnTfblrGDrDGRAl8ThBs5HWPgY8x0bDxP35ITV6015+SLai8QkDVLevEnQPXMgZadvkfPYUY22Bz1HFZj3iNRhFfL05MpXr1IKdBy07hvaFxL1IAAAAAAAAAAAAAAAAA="
                     alt="Finite State Logo" class="company-logo">
            </div>
            {% endif %}
            <h1>Triage Prioritization Report</h1>
            <div class="subtitle">
                {% if folder_path %}
                Folder: {{ folder_path }}{% if is_single_project and single_project_name %} / Project: {{ single_project_name }}{% endif %} — Risk-Based Vulnerability Triage &amp; Prioritization
                {% elif is_single_project and single_project_name %}
                Project: {{ single_project_name }} — Risk-Based Vulnerability Triage &amp; Prioritization
                {% else %}
                Risk-Based Vulnerability Triage &amp; Prioritization
                {% endif %}
            </div>
        </div>

        <!-- Metadata -->
        <div class="metadata">
            <strong>Report:</strong> {{ recipe_name }} |
            <strong>Generated:</strong> {{ generated_at }} |
            {% if metadata and metadata.start_date and metadata.end_date %}
            <strong>Period:</strong> {{ metadata.start_date }} to {{ metadata.end_date }} |
            {% endif %}
            <strong>Records:</strong> {{ metadata.transformed_count if metadata and metadata.transformed_count else 'N/A' }}
        </div>

        <!-- Summary Cards -->
        {% if portfolio_summary %}
        <div class="summary-cards">
            <div class="summary-card card-critical">
                <div class="count">{{ portfolio_summary.CRITICAL|default(0) }}</div>
                <div class="label">CRITICAL</div>
            </div>
            <div class="summary-card card-high">
                <div class="count">{{ portfolio_summary.HIGH|default(0) }}</div>
                <div class="label">HIGH</div>
            </div>
            <div class="summary-card card-medium">
                <div class="count">{{ portfolio_summary.MEDIUM|default(0) }}</div>
                <div class="label">MEDIUM</div>
            </div>
            <div class="summary-card card-low">
                <div class="count">{{ portfolio_summary.LOW|default(0) }}</div>
                <div class="label">LOW</div>
            </div>
            <div class="summary-card card-info">
                <div class="count">{{ portfolio_summary.INFO|default(0) }}</div>
                <div class="label">INFO</div>
            </div>
            <div class="summary-card card-total">
                <div class="count">{{ portfolio_summary.total|default(0) }}</div>
                <div class="label">TOTAL</div>
            </div>
        </div>
        {% endif %}

        <!-- Row 1: Band Distribution + Gate Funnel (side by side) -->
        <div class="two-col">
            <div class="chart-container">
                <h2 class="chart-title">📊 Priority Band Distribution</h2>
                <p class="chart-description">Distribution of findings across risk priority bands.</p>
                <div class="chart-wrapper">
                    <canvas id="bandDistributionChart"></canvas>
                </div>
            </div>
            <div class="chart-container">
                <h2 class="chart-title">🔀 Gate Classification Funnel</h2>
                <p class="chart-description">How findings flow through Gate 1 (Reachable+Exploit), Gate 2 (Strong Signal), and additive scoring.</p>
                <div class="chart-wrapper">
                    <canvas id="gateFunnelChart"></canvas>
                </div>
            </div>
        </div>

        <!-- Row 2: CVSS vs Band Heatmap -->
        <div class="chart-container">
            <h2 class="chart-title">🔥 CVSS Severity vs Priority Band</h2>
            <p class="chart-description">Each horizontal band represents a priority level — red (CRITICAL) at the top, gray (INFO) at the bottom. CVSS severity runs left-to-right. Bubbles that appear off the diagonal highlight where CVSS alone would misclassify risk — e.g., a CVSS Medium that lands in the CRITICAL band due to reachability and active exploitation.</p>
            <div class="chart-wrapper-tall">
                <canvas id="heatmapChart"></canvas>
            </div>
        </div>

        <!-- Row 3: Top Components + Radar (side by side) -->
        <div class="two-col">
            <div class="chart-container">
                <h2 class="chart-title">🏆 Top 15 Riskiest Components</h2>
                <p class="chart-description">Components with the highest average triage scores, with band breakdown.</p>
                <div class="chart-wrapper-tall">
                    <canvas id="topComponentsChart"></canvas>
                </div>
            </div>
            <div class="chart-container">
                <h2 class="chart-title">🎯 Risk Factor Profiles</h2>
                <p class="chart-description">Radar chart comparing risk factor profiles (Reachability, Exploits, Attack Vector, EPSS, CVSS) across top projects.</p>
                <div class="chart-wrapper-tall">
                    <canvas id="factorRadarChart"></canvas>
                </div>
            </div>
        </div>

        <!-- Row 4: Project Summary Chart -->
        {% if project_summary_df is defined and project_summary_df|length > 0 %}
        <div class="chart-container">
            <h2 class="chart-title">📋 Project Risk Summary</h2>
            <p class="chart-description">Per-project breakdown showing band counts. Projects sorted by CRITICAL count descending.</p>
            <div class="chart-wrapper-tall">
                <canvas id="projectSummaryChart"></canvas>
            </div>
        </div>
        {% endif %}

        <!-- Methodology Section -->
        <div class="narrative-section">
            <h3>Scoring Methodology</h3>
            <p>This report uses a <strong>tiered-gates scoring model</strong> that prioritizes findings based on real-world exploitability rather than theoretical CVSS scores alone.</p>

            {% if scoring_config and scoring_config.gates %}
            {% for gate in scoring_config.gates %}
            <h4>{{ gate.name | replace('_', ' ') | title }} — {{ gate.band }} (score={{ gate.score }})</h4>
            <ul>
                <li>{{ gate.description | e | replace(' AND ', ' <strong>AND</strong> ') | replace(' OR ', ' <strong>OR</strong> ') | safe }}</li>
            </ul>
            {% endfor %}
            {% endif %}

            {% if scoring_config and scoring_config.weights %}
            <h4>Additive Scoring (Remaining Findings)</h4>
            <ul>
                <li><strong>Reachability</strong>: +{{ scoring_config.weights.reachable }} (reachable), {{ scoring_config.weights.unknown }} (inconclusive), {{ scoring_config.weights.unreachable }} (unreachable)</li>
                <li><strong>Exploit/KEV</strong>: +{{ scoring_config.weights.exploit }} (exploit), +{{ scoring_config.weights.kev_only }} (KEV only)</li>
                <li><strong>Attack Vector</strong>: +{{ scoring_config.weights.vector_network }} (NETWORK), +{{ scoring_config.weights.vector_adjacent }} (ADJACENT), +{{ scoring_config.weights.vector_local }} (LOCAL), {{ scoring_config.weights.vector_physical }} (PHYSICAL)</li>
                <li><strong>EPSS</strong>: 0–{{ scoring_config.weights.epss_max }} points (scaled by percentile)</li>
                <li><strong>CVSS</strong>: 0–{{ scoring_config.weights.cvss_max }} points (scaled by score/10)</li>
            </ul>
            <p>Bands: HIGH ≥ {{ scoring_config.weights.band_high_threshold }}, MEDIUM ≥ {{ scoring_config.weights.band_medium_threshold }}, LOW ≥ {{ scoring_config.weights.band_low_threshold }}, INFO &lt; {{ scoring_config.weights.band_low_threshold }}</p>

            {% if scoring_config.weights.vex_resolved %}
            <h4>VEX Status Penalty</h4>
            <ul>
                <li>Findings with status <strong>NOT_AFFECTED</strong>, <strong>RESOLVED</strong>, or <strong>RESOLVED_WITH_PEDIGREE</strong>: <strong>{{ scoring_config.weights.vex_resolved }} points</strong></li>
                <li>Applies to both gated and additive-scored findings</li>
                <li>Allows triaged findings to drop out of critical gates without being removed from the report</li>
            </ul>
            {% endif %}
            {% endif %}

            <h4>Reachability Interpretation</h4>
            <ul>
                <li><span class="reach-badge reach-REACHABLE">REACHABLE</span> (score &gt; 0): Code path is reachable — vulnerability can be triggered</li>
                <li><span class="reach-badge reach-INCONCLUSIVE">INCONCLUSIVE</span> (score = 0): Reachability analysis ran but was inconclusive</li>
                <li><span class="reach-badge reach-UNKNOWN">UNKNOWN</span> (no data): Reachability analysis was never run</li>
                <li><span class="reach-badge reach-UNREACHABLE">UNREACHABLE</span> (score &lt; 0): Code path cannot be triggered</li>
            </ul>
        </div>

        <!-- AI Prompts — Portfolio (--ai-prompts flag) -->
        {% if ai_portfolio_prompt %}
        <div class="narrative-section" style="border-left: 4px solid #17a2b8;">
            <h3>AI Prompts</h3>
            <p style="color: #6c757d;">Copy and paste into any LLM for remediation guidance. No API key required. Project, component, and finding prompts are in the tables below.</p>

            <details style="margin-bottom: 12px;">
                <summary style="cursor:pointer;color:#007bff;font-weight:600;font-size:0.95em;">Portfolio Remediation Prompt</summary>
                <div style="position:relative;margin-top:10px;">
                    <button onclick="copyPrompt(this)" style="position:absolute;top:5px;right:5px;padding:4px 10px;font-size:0.8em;cursor:pointer;border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;">Copy</button>
                    <pre style="background:#f8f9fa;padding:15px;border-radius:6px;font-size:0.85em;white-space:pre-wrap;word-wrap:break-word;max-height:400px;overflow-y:auto;">{{ ai_portfolio_prompt }}</pre>
                </div>
            </details>
        </div>
        {% endif %}

        <!-- AI Remediation Guidance (conditionally rendered) -->
        {% if ai_portfolio_summary and not is_single_project %}
        <div class="narrative-section" style="border-left: 4px solid #007bff;">
            <h3>🤖 AI Portfolio Remediation Summary</h3>
            <div id="ai-portfolio-summary"></div>
            <textarea id="ai-portfolio-summary-raw" style="display:none">{{ ai_portfolio_summary }}</textarea>
        </div>
        {% endif %}

        {% if ai_project_summaries %}
        <div class="narrative-section" style="border-left: 4px solid #28a745;">
            <h3>🤖 AI Project Remediation Guidance</h3>
            {% for project_name, summary in ai_project_summaries.items() %}
            <h4>{{ project_name }}</h4>
            <div id="ai-project-summary-{{ loop.index0 }}"></div>
            <textarea id="ai-project-summary-raw-{{ loop.index0 }}" style="display:none">{{ summary }}</textarea>
            {% endfor %}
        </div>
        {% endif %}

        {% if ai_component_guidance %}
        <div class="table-container" style="border-left: 4px solid #6f42c1;">
            <h2>🤖 AI Component Remediation Guidance</h2>
            <p style="color: #6c757d; margin-bottom: 15px;">Detailed fix guidance for Critical and High priority components. Fix version data derived from NVD API where available.</p>
            <table id="tbl-ai-component">
                <thead>
                    <tr>
                        <th>Component</th>
                        <th>Fix Version</th>
                        <th>Rationale</th>
                        <th>Guidance</th>
                        <th>Workaround</th>
                        <th>Code Search</th>
                        <th>Confidence</th>
                    </tr>
                </thead>
                <tbody>
                    {% for comp_key, guidance in ai_component_guidance.items() %}
                    <tr class="{% if loop.index > 10 %}hidden-row{% endif %}">
                        <td><strong>{{ comp_key }}</strong></td>
                        <td>{{ guidance.fix_version|default('—') }}</td>
                        <td>{{ guidance.rationale|default('—') }}</td>
                        <td>{{ guidance.guidance|default('—') }}</td>
                        <td>{{ guidance.workaround|default('—') }}</td>
                        <td><code>{{ guidance.code_search_hints|default('—') }}</code></td>
                        <td>{{ guidance.confidence|default('—') }}</td>
                    </tr>
                    {% endfor %}
                    {% if ai_component_guidance|length > 10 %}
                    <tr class="show-more-row">
                        <td colspan="7">
                            <button class="show-more-btn" onclick="toggleRows('tbl-ai-component')">
                                Show {{ ai_component_guidance|length - 10 }} more...
                            </button>
                        </td>
                    </tr>
                    {% endif %}
                </tbody>
            </table>
        </div>
        {% endif %}

        {% if ai_finding_guidance %}
        <div class="table-container" style="border-left: 4px solid #e83e8c;">
            <h2>🤖 AI Finding Triage Guidance</h2>
            <p style="color: #6c757d; margin-bottom: 15px;">Per-finding triage and remediation guidance for Critical and High priority findings. Fix version data derived from NVD API where available.</p>
            <table id="tbl-ai-finding">
                <thead>
                    <tr>
                        <th>Finding</th>
                        <th>Priority</th>
                        <th>Action</th>
                        <th>Rationale</th>
                        <th>Fix Version</th>
                        <th>Workaround</th>
                        <th>Code Search</th>
                        <th>Confidence</th>
                    </tr>
                </thead>
                <tbody>
                    {% for finding_id, guidance in ai_finding_guidance.items() %}
                    <tr class="{% if loop.index > 10 %}hidden-row{% endif %}">
                        <td><strong>{{ finding_id }}</strong></td>
                        <td>{{ guidance.priority|default('—') }}</td>
                        <td>{{ guidance.action|default('—') }}</td>
                        <td>{{ guidance.rationale|default('—') }}</td>
                        <td>{{ guidance.fix_version|default('—') }}</td>
                        <td>{{ guidance.workaround|default('—') }}</td>
                        <td><code>{{ guidance.code_search_hints|default('—') }}</code></td>
                        <td>{{ guidance.confidence|default('—') }}</td>
                    </tr>
                    {% endfor %}
                    {% if ai_finding_guidance|length > 10 %}
                    <tr class="show-more-row">
                        <td colspan="8">
                            <button class="show-more-btn" onclick="toggleRows('tbl-ai-finding')">
                                Show {{ ai_finding_guidance|length - 10 }} more...
                            </button>
                        </td>
                    </tr>
                    {% endif %}
                </tbody>
            </table>
        </div>
        {% endif %}

        <!-- Project Summary Table -->
        {% if project_summary_df is defined and project_summary_df|length > 0 %}
        <div class="table-container">
            <h2>Project Risk Summary</h2>
            <table id="tbl-project-summary">
                <thead>
                    <tr>
                        {% if folder_path %}<th>Folder</th>{% endif %}
                        <th>Project</th>
                        <th>CRITICAL</th>
                        <th>HIGH</th>
                        <th>MEDIUM</th>
                        <th>LOW</th>
                        <th>INFO</th>
                        <th>Total</th>
                        <th>Avg Score</th>
                        {% if ai_project_prompts %}<th>AI Prompt</th>{% endif %}
                    </tr>
                </thead>
                <tbody>
                    {% for row in project_summary_df %}
                    <tr class="{% if loop.index > 10 %}hidden-row{% endif %}">
                        {% if folder_path %}<td>{{ row.folder_name if row.folder_name is defined else '' }}</td>{% endif %}
                        <td>
                            {% if domain and row.project_id %}
                            <a href="https://{{ domain }}/projects/{{ row.project_id }}/" target="_blank" class="entity-link" title="View project in Finite State">{{ row.project_name }}</a>
                            {% else %}
                            {{ row.project_name }}
                            {% endif %}
                        </td>
                        <td>{% if row.CRITICAL|default(0) > 0 %}<span class="band-badge band-CRITICAL">{{ row.CRITICAL }}</span>{% else %}0{% endif %}</td>
                        <td>{% if row.HIGH|default(0) > 0 %}<span class="band-badge band-HIGH">{{ row.HIGH }}</span>{% else %}0{% endif %}</td>
                        <td>{% if row.MEDIUM|default(0) > 0 %}<span class="band-badge band-MEDIUM">{{ row.MEDIUM }}</span>{% else %}0{% endif %}</td>
                        <td>{{ row.LOW|default(0) }}</td>
                        <td>{{ row.INFO|default(0) }}</td>
                        <td><strong>{{ row.total_findings|default(0) }}</strong></td>
                        <td>{{ row.avg_score|default(0) }}</td>
                        {% if ai_project_prompts %}
                        <td>
                            {% if row.project_name in ai_project_prompts %}
                            <details>
                                <summary style="cursor:pointer;color:#007bff;font-size:0.85em;">View</summary>
                                <div style="position:relative;margin-top:6px;">
                                    <button onclick="copyPrompt(this)" style="position:absolute;top:2px;right:2px;padding:2px 8px;font-size:0.75em;cursor:pointer;border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;">Copy</button>
                                    <pre style="background:#f8f9fa;padding:10px;border-radius:6px;font-size:0.8em;white-space:pre-wrap;word-wrap:break-word;max-height:300px;overflow-y:auto;max-width:400px;">{{ ai_project_prompts[row.project_name] }}</pre>
                                </div>
                            </details>
                            {% else %}—{% endif %}
                        </td>
                        {% endif %}
                    </tr>
                    {% endfor %}
                    {% if project_summary_df|length > 10 %}
                    <tr class="show-more-row">
                        <td colspan="20">
                            <button class="show-more-btn" onclick="toggleRows('tbl-project-summary')">
                                Show {{ project_summary_df|length - 10 }} more...
                            </button>
                        </td>
                    </tr>
                    {% endif %}
                </tbody>
            </table>
        </div>
        {% endif %}

        <!-- Top Components Table -->
        {% if top_components is defined and top_components|length > 0 %}
        <div class="table-container">
            <h2>Top Riskiest Components</h2>
            <table id="tbl-top-components">
                <thead>
                    <tr>
                        <th>Component</th>
                        <th>Version</th>
                        <th>CRITICAL</th>
                        <th>HIGH</th>
                        <th>MEDIUM</th>
                        <th>LOW</th>
                        <th>Total</th>
                        <th>Avg Score</th>
                        <th>Max Score</th>
                        {% if ai_component_prompts %}<th>AI Prompt</th>{% endif %}
                        <th>Actions</th>
                    </tr>
                </thead>
                <tbody>
                    {% for row in top_components %}
                    <tr class="{% if loop.index > 10 %}hidden-row{% endif %}">
                        <td>{{ row.component_name }}</td>
                        <td>{{ row.component_version }}</td>
                        <td>{% if row.CRITICAL|default(0) > 0 %}<span class="band-badge band-CRITICAL">{{ row.CRITICAL }}</span>{% else %}0{% endif %}</td>
                        <td>{% if row.HIGH|default(0) > 0 %}<span class="band-badge band-HIGH">{{ row.HIGH }}</span>{% else %}0{% endif %}</td>
                        <td>{% if row.MEDIUM|default(0) > 0 %}<span class="band-badge band-MEDIUM">{{ row.MEDIUM }}</span>{% else %}0{% endif %}</td>
                        <td>{{ row.LOW|default(0) }}</td>
                        <td><strong>{{ row.total_findings|default(0) }}</strong></td>
                        <td>{{ row.avg_score }}</td>
                        <td>{{ row.max_score }}</td>
                        {% if ai_component_prompts %}
                        <td>
                            {% set comp_key = row.component_name ~ ':' ~ row.component_version %}
                            {% if comp_key in ai_component_prompts %}
                            <details>
                                <summary style="cursor:pointer;color:#007bff;font-size:0.85em;">View</summary>
                                <div style="position:relative;margin-top:6px;">
                                    <button onclick="copyPrompt(this)" style="position:absolute;top:2px;right:2px;padding:2px 8px;font-size:0.75em;cursor:pointer;border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;">Copy</button>
                                    <pre style="background:#f8f9fa;padding:10px;border-radius:6px;font-size:0.8em;white-space:pre-wrap;word-wrap:break-word;max-height:300px;overflow-y:auto;max-width:400px;">{{ ai_component_prompts[comp_key] }}</pre>
                                </div>
                            </details>
                            {% else %}—{% endif %}
                        </td>
                        {% endif %}
                        <td class="action-cell">
                            {% set c_key = row.component_name ~ ':' ~ row.component_version %}
                            {% set c_guidance = ai_component_guidance.get(c_key, {}) if ai_component_guidance is defined and ai_component_guidance else {} %}
                            {% set c_summary_parts = [] %}
                            {% if c_guidance.get('fix_version') %}{% if c_summary_parts.append('Fix: ' + c_guidance.fix_version) %}{% endif %}{% endif %}
                            {% if c_guidance.get('guidance') %}{% if c_summary_parts.append(c_guidance.guidance) %}{% endif %}{% endif %}
                            {% if c_guidance.get('workaround') %}{% if c_summary_parts.append('Workaround: ' + c_guidance.workaround) %}{% endif %}{% endif %}
                            {% set c_summary = c_summary_parts | join('\n\n') if c_summary_parts else (row.total_findings | string ~ ' findings (' ~ row.CRITICAL | string ~ ' CRITICAL, ' ~ row.HIGH | string ~ ' HIGH) in ' ~ row.component_name ~ ' ' ~ row.component_version) %}
                            {% set c_band = 'CRITICAL' if (row.CRITICAL|default(0)) > 0 else ('HIGH' if (row.HIGH|default(0)) > 0 else ('MEDIUM' if (row.MEDIUM|default(0)) > 0 else 'LOW')) %}
                            <button class="action-btn btn-jira" title="Create Jira ticket for this component"
                                data-action="{{ {'endpoint': 'components', 'title': 'Remediate ' ~ row.component_name ~ ' ' ~ row.component_version, 'summary': c_summary, 'band': c_band, 'findings': row.finding_internal_ids if row.finding_internal_ids is defined else [], 'components': [row.component_id], 'mode': 'SINGLE_COMPONENT', 'projectId': row.project_id, 'projectVersionId': row.project_version_id, 'componentId': row.component_id} | tojson | forceescape }}"
                                onclick="fsOpenJiraModal(Object.assign(JSON.parse(this.dataset.action), {sourceBtn: this}))">&#x1F3AB; Jira</button>
                            <button class="action-btn btn-remediation" title="Generate Remediation Package for this component"
                                data-component="{{ row.component_name }}@{{ row.component_version }}"
                                data-project="{{ row.project_id }}"
                                onclick="fsGenerateRemediation(this, 'component')">&#x1F4E6; Remediation</button>
                        </td>
                    </tr>
                    {% endfor %}
                    {% if top_components|length > 10 %}
                    <tr class="show-more-row">
                        <td colspan="20">
                            <button class="show-more-btn" onclick="toggleRows('tbl-top-components')">
                                Show {{ top_components|length - 10 }} more...
                            </button>
                        </td>
                    </tr>
                    {% endif %}
                </tbody>
            </table>
        </div>
        {% endif %}

        <!-- Findings Detail Table (first 100) -->
        {% if findings_df is defined and findings_df|length > 0 %}
        <div class="table-container">
            <h2>Findings Detail (Top 100 by Priority)</h2>
            <table id="tbl-findings-detail">
                <thead>
                    <tr>
                        <th>Finding ID</th>
                        <th>Severity</th>
                        <th>Band</th>
                        <th>Score</th>
                        <th>Gate</th>
                        <th>Component</th>
                        <th>Project</th>
                        <th>Version</th>
                        <th>Reachability</th>
                        <th>Score</th>
                        <th>Vuln Functions</th>
                        <th>Exploit</th>
                        <th>KEV</th>
                        <th>Vector</th>
                        <th>EPSS %ile</th>
                        <th>CVSS</th>
                        {% if ai_finding_prompts %}<th>AI Prompt</th>{% endif %}
                        <th>AI Status</th>
                        <th>Actions</th>
                    </tr>
                </thead>
                <tbody>
                    {% for row in findings_df[:100] %}
                    <tr class="{% if loop.index > 10 %}hidden-row{% endif %}">
                        <td>
                            {% if domain and row.project_id and row.project_version_id and row.internal_id %}
                            <a href="https://{{ domain }}/projects/{{ row.project_id }}/versions/{{ row.project_version_id }}/findings?findingId={{ row.internal_id }}" target="_blank" class="entity-link entity-link-cve" title="View finding in Finite State">{{ row.finding_id }}</a>
                            {% else %}
                            {{ row.finding_id }}
                            {% endif %}
                        </td>
                        <td>{{ row.severity }}</td>
                        <td><span class="band-badge band-{{ row.priority_band }}">{{ row.priority_band }}</span></td>
                        {% set _additive = (row._pts_reachability|default(0) + row._pts_exploit|default(0) + row._pts_vector|default(0) + row._pts_epss|default(0) + row._pts_cvss|default(0))|round(1) %}
                        {% set _gate_bonus = (row.triage_score - _additive)|round(1) if row.gate_assignment != 'NONE' else 0 %}
                        <td title="Reachability: {{ row._pts_reachability|default(0) }}&#10;Exploit/KEV: {{ row._pts_exploit|default(0) }}&#10;Attack Vector: {{ row._pts_vector|default(0) }}&#10;EPSS: {{ row._pts_epss|default(0)|round(1) }}&#10;CVSS: {{ row._pts_cvss|default(0)|round(1) }}{% if _gate_bonus %}&#10;Gate Bonus ({{ row.gate_assignment }}): {{ _gate_bonus }}{% endif %}" style="cursor:help">{{ row.triage_score }}</td>
                        <td>{{ row.gate_assignment if row.gate_assignment != 'NONE' else '—' }}</td>
                        <td>
                            {% if domain and row.project_id and row.project_version_id and row.component_id %}
                            <a href="https://{{ domain }}/projects/{{ row.project_id }}/versions/{{ row.project_version_id }}/bill-of-materials?view=list&componentId={{ row.component_id }}" target="_blank" class="entity-link entity-link-component" title="View component in Finite State">{{ row.component_name }}{% if row.component_version %} {{ row.component_version }}{% endif %}</a>
                            {% elif domain and row.project_id and row.project_version_id %}
                            <a href="https://{{ domain }}/projects/{{ row.project_id }}/versions/{{ row.project_version_id }}/bill-of-materials?view=list" target="_blank" class="entity-link entity-link-component" title="View component in Finite State">{{ row.component_name }}{% if row.component_version %} {{ row.component_version }}{% endif %}</a>
                            {% else %}
                            {{ row.component_name }}{% if row.component_version %} {{ row.component_version }}{% endif %}
                            {% endif %}
                        </td>
                        <td>
                            {% if domain and row.project_id %}
                            <a href="https://{{ domain }}/projects/{{ row.project_id }}/" target="_blank" class="entity-link" title="View project in Finite State">{{ row.project_name }}</a>
                            {% else %}
                            {{ row.project_name }}
                            {% endif %}
                        </td>
                        <td>
                            {% if domain and row.project_id and row.project_version_id %}
                            <a href="https://{{ domain }}/projects/{{ row.project_id }}/versions/{{ row.project_version_id }}/overview" target="_blank" class="entity-link" title="View version in Finite State">{{ row.version_name if row.version_name else '—' }}</a>
                            {% else %}
                            {{ row.version_name if row.version_name else '—' }}
                            {% endif %}
                        </td>
                        <td>
                            {% set rlabel = row.reachability_label if row.reachability_label is string else 'UNKNOWN' %}<span class="reach-badge reach-{{ rlabel }}">{{ rlabel }}</span>
                        </td>
                        <td>{{ row.reachability_score|int if row.reachability_score else '—' }}</td>
                        <td>{{ row.vuln_functions if row.vuln_functions else '—' }}</td>
                        <td>{% if row.has_exploit %}<span class="icon-exploit">💥 Yes</span>{% else %}—{% endif %}</td>
                        <td>{% if row.in_kev %}<span class="icon-kev">🔒 Yes</span>{% else %}—{% endif %}</td>
                        <td>{{ row.attack_vector }}</td>
                        <td>{{ "%.1f"|format(row.epss_percentile|default(0) * 100) if row.epss_percentile else '—' }}%</td>
                        <td>{{ row.risk }}</td>
                        {% if ai_finding_prompts %}
                        <td>
                            {% if row.finding_id in ai_finding_prompts %}
                            <details>
                                <summary style="cursor:pointer;color:#007bff;font-size:0.85em;">View</summary>
                                <div style="position:relative;margin-top:6px;">
                                    <button onclick="copyPrompt(this)" style="position:absolute;top:2px;right:2px;padding:2px 8px;font-size:0.75em;cursor:pointer;border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;">Copy</button>
                                    <pre style="background:#f8f9fa;padding:10px;border-radius:6px;font-size:0.8em;white-space:pre-wrap;word-wrap:break-word;max-height:300px;overflow-y:auto;max-width:400px;">{{ ai_finding_prompts[row.finding_id] }}</pre>
                                </div>
                            </details>
                            {% else %}—{% endif %}
                        </td>
                        {% endif %}
                        <td>
                            {% if row.status is defined and row.status and row.status not in ('', 'nan', 'None') %}
                                <span class="vex-badge vex-{{ row.status }}">{{ row.status }}</span>
                            {% elif vex_rec_lookup is defined and vex_rec_lookup and row.finding_id in vex_rec_lookup %}
                                {% set vrec = vex_rec_lookup[row.finding_id] %}
                                <span class="vex-rec-badge vex-rec-{{ vrec.recommended_vex_status }}" title="{{ vrec.reason }}">{{ vrec.recommended_vex_status }}</span>
                            {% else %}
                                —
                            {% endif %}
                        </td>
                        <td class="action-cell">
                            {% set f_guidance = ai_finding_guidance.get(row.finding_id, {}) if ai_finding_guidance is defined and ai_finding_guidance else {} %}
                            {% set f_summary_parts = [] %}
                            {% if f_guidance.get('fix_version') %}{% if f_summary_parts.append('Fix: ' + f_guidance.fix_version) %}{% endif %}{% endif %}
                            {% if f_guidance.get('guidance') %}{% if f_summary_parts.append(f_guidance.guidance) %}{% endif %}{% endif %}
                            {% if f_guidance.get('workaround') %}{% if f_summary_parts.append('Workaround: ' + f_guidance.workaround) %}{% endif %}{% endif %}
                            {% set f_reach = row.reachability_label if row.reachability_label is string else 'UNKNOWN' %}
                            {% set f_summary = f_summary_parts | join('\n\n') if f_summary_parts else (row.severity ~ ' vulnerability in ' ~ row.component_name ~ ' ' ~ row.component_version ~ '. Reachability: ' ~ f_reach ~ '.') %}
                            <button class="action-btn btn-jira" title="Create Jira ticket for this finding"
                                data-action="{{ {'endpoint': 'findings', 'title': '[' ~ row.finding_id ~ '] ' ~ row.component_name ~ ' ' ~ row.component_version ~ ' — ' ~ row.severity, 'summary': f_summary, 'band': row.priority_band, 'findings': [row.internal_id], 'components': [row.component_id], 'mode': 'SINGLE_FINDING', 'projectId': row.project_id, 'projectVersionId': row.project_version_id, 'internalId': row.internal_id, 'componentId': row.component_id} | tojson | forceescape }}"
                                onclick="fsOpenJiraModal(Object.assign(JSON.parse(this.dataset.action), {sourceBtn: this}))">&#x1F3AB; Jira</button>
                            {% if row.internal_id and row.project_version_id %}
                            {% set vex_reason = vex_reason_lookup.get(row.finding_id, '') if vex_reason_lookup is defined and vex_reason_lookup else '' %}
                            {% set rec_data = vex_rec_lookup.get(row.finding_id) if vex_rec_lookup is defined and vex_rec_lookup else None %}
                            <button class="action-btn btn-triage" title="Set status to IN_TRIAGE"
                                data-action="{{ {'findingId': row.finding_id, 'internalId': row.internal_id, 'projectVersionId': row.project_version_id, 'reason': vex_reason, 'recommendation': rec_data, 'reachabilityLabel': row.reachability_label if row.reachability_label is string else 'INCONCLUSIVE'} | tojson | forceescape }}"
                                onclick="fsOpenTriageModal(Object.assign(JSON.parse(this.dataset.action), {sourceBtn: this, currentStatus: this.dataset.currentStatus || ''}))">&#x2691; Triage</button>
                            {% endif %}
                            <button class="action-btn btn-remediation" title="Generate Remediation Package for this CVE"
                                data-cve="{{ row.finding_id }}"
                                data-project="{{ row.project_id }}"
                                onclick="fsGenerateRemediation(this, 'cve')">&#x1F4E6; Remediation</button>
                        </td>
                    </tr>
                    {% endfor %}
                    {% if findings_df[:100]|length > 10 %}
                    <tr class="show-more-row">
                        <td colspan="20">
                            <button class="show-more-btn" onclick="toggleRows('tbl-findings-detail')">
                                Show {{ findings_df[:100]|length - 10 }} more...
                            </button>
                        </td>
                    </tr>
                    {% endif %}
                </tbody>
            </table>
        </div>
        {% endif %}

        <div class="footer">
            <p>Generated by <span class="finite-state-logo">Finite State</span> | {{ generated_at }}</p>
            <p style="font-size: 0.8em; margin-top: 4px;">Copyright &copy; 2026 Finite State, Inc.</p>
        </div>
    </div>

    <script>
        function toggleRows(tableId) {
            const table = document.getElementById(tableId);
            if (!table) return;
            const hiddenRows = table.querySelectorAll('.hidden-row');
            const showMoreRow = table.querySelector('.show-more-row');
            hiddenRows.forEach(row => row.classList.toggle('hidden-row'));
            if (showMoreRow) showMoreRow.style.display = 'none';
        }

        function copyPrompt(btn) {
            const pre = btn.parentElement.querySelector('pre');
            navigator.clipboard.writeText(pre.textContent).then(function() {
                btn.textContent = 'Copied!';
                setTimeout(function() { btn.textContent = 'Copy'; }, 2000);
            });
        }

        function fsGenerateRemediation(btn, mode) {
            // Only works when served via localhost (needs /api/run endpoint)
            if (!(window.location.protocol === 'http:' &&
                  (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'))) {
                alert('Remediation Package generation requires fs-report serve mode.');
                return;
            }

            var formData = new FormData();
            formData.append('recipes', 'Remediation Package');
            formData.append('project_filter', btn.dataset.project);
            formData.append('ai_prompts', 'true');
            formData.append('overwrite', 'true');

            if (mode === 'component') {
                formData.append('component_filter', btn.dataset.component);
            } else {
                formData.append('cve_filter', btn.dataset.cve);
            }

            btn.disabled = true;
            var origText = btn.textContent;
            btn.textContent = 'Generating...';

            fetch('/fsapi/session').then(function(r) { return r.json(); }).then(function(session) {
                var headers = {};
                if (session.nonce) headers['X-FS-Session'] = session.nonce;
                return fetch('/api/run', { method: 'POST', body: formData, headers: headers });
            }).then(function(r) {
                if (!r.ok) return r.json().then(function(d) { throw new Error(d.error || 'HTTP ' + r.status); });
                return r.json();
            }).then(function(data) {
                if (!data.run_id) throw new Error('No run_id returned');
                window.open('/run/' + data.run_id, '_blank');
                btn.disabled = false;
                btn.textContent = origText;
            }).catch(function(err) {
                btn.disabled = false;
                btn.textContent = origText;
                alert('Failed to start remediation: ' + err.message);
            });
        }

        document.addEventListener('DOMContentLoaded', function() {
            const BAND_COLORS = {
                'CRITICAL': '#dc3545',
                'HIGH': '#fd7e14',
                'MEDIUM': '#ffc107',
                'LOW': '#28a745',
                'INFO': '#6c757d'
            };
            const BAND_ORDER = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFO'];
            const RADAR_COLORS = ['#2E86AB', '#A23B72', '#F18F01', '#C73E1D', '#228B22'];

            // =============================================
            // 1. Band Distribution Chart (Doughnut)
            // =============================================
            {% if portfolio_summary %}
            (function() {
                const ctx = document.getElementById('bandDistributionChart');
                if (!ctx) return;
                const data = {{ portfolio_summary | tojson }};
                const counts = BAND_ORDER.map(b => data[b] || 0);
                const colors = BAND_ORDER.map(b => BAND_COLORS[b]);

                if (counts.every(c => c === 0)) { ctx.parentElement.innerHTML = '<p class="no-data">No data available</p>'; return; }
                new Chart(ctx.getContext('2d'), {
                    type: 'doughnut',
                    data: {
                        labels: BAND_ORDER,
                        datasets: [{
                            data: counts,
                            backgroundColor: colors,
                            borderWidth: 2,
                            borderColor: '#fff'
                        }]
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            legend: { position: 'right' },
                            datalabels: {
                                color: '#fff',
                                font: { weight: 'bold', size: 14 },
                                formatter: function(value, ctx) {
                                    if (value === 0) return '';
                                    const total = ctx.dataset.data.reduce((a, b) => a + b, 0);
                                    const pct = ((value / total) * 100).toFixed(1);
                                    return value + '\n(' + pct + '%)';
                                }
                            }
                        }
                    },
                    plugins: [ChartDataLabels]
                });
            })();
            {% endif %}

            // =============================================
            // 2. Gate Funnel Chart (Horizontal Bar)
            // =============================================
            {% if gate_funnel %}
            (function() {
                const ctx = document.getElementById('gateFunnelChart');
                if (!ctx) return;
                const funnel = {{ gate_funnel | tojson }};
                const labels = [
                    'Gate 1 (CRITICAL)',
                    'Gate 2 (HIGH)',
                    'Additive → HIGH',
                    'Additive → MEDIUM',
                    'Additive → LOW',
                    'Additive → INFO'
                ];
                const values = [
                    funnel.gate_1_critical,
                    funnel.gate_2_high,
                    funnel.additive_high,
                    funnel.additive_medium,
                    funnel.additive_low,
                    funnel.additive_info
                ];
                const bgColors = [
                    BAND_COLORS.CRITICAL,
                    BAND_COLORS.HIGH,
                    BAND_COLORS.HIGH,
                    BAND_COLORS.MEDIUM,
                    BAND_COLORS.LOW,
                    BAND_COLORS.INFO
                ];

                new Chart(ctx.getContext('2d'), {
                    type: 'bar',
                    data: {
                        labels: labels,
                        datasets: [{
                            data: values,
                            backgroundColor: bgColors,
                            borderWidth: 1,
                            borderColor: bgColors.map(c => c)
                        }]
                    },
                    options: {
                        indexAxis: 'y',
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            legend: { display: false },
                            datalabels: {
                                anchor: 'end',
                                align: 'right',
                                font: { weight: 'bold' },
                                color: '#333',
                                formatter: function(v) { return v > 0 ? v : ''; }
                            }
                        },
                        scales: {
                            x: { title: { display: true, text: 'Number of Findings' } },
                            y: { ticks: { font: { size: 11 } } }
                        }
                    },
                    plugins: [ChartDataLabels]
                });
            })();
            {% endif %}

            // =============================================
            // 3. CVSS vs Band Heatmap (Bubble Chart)
            // =============================================
            {% if cvss_band_matrix %}
            (function() {
                const ctx = document.getElementById('heatmapChart');
                if (!ctx) return;
                const matrix = {{ cvss_band_matrix | tojson }};
                // rows = y-axis (bands, top-to-bottom: CRITICAL, HIGH, MEDIUM, LOW, INFO)
                // cols = x-axis (CVSS severity: CRITICAL, HIGH, MEDIUM, LOW, NONE)
                if (!matrix.data || matrix.data.length === 0) return;

                const maxVal = Math.max(...matrix.data.map(d => d.v)) || 1;
                const points = matrix.data.map(d => ({
                    x: d.x,
                    y: d.y,
                    r: Math.max(10, Math.min(40, (d.v / maxVal) * 40)),
                    v: d.v,
                    severity: d.severity,
                    band: d.band
                }));

                // Helper: hex to rgba
                function hexToRgba(hex, alpha) {
                    const r = parseInt(hex.slice(1,3), 16);
                    const g = parseInt(hex.slice(3,5), 16);
                    const b = parseInt(hex.slice(5,7), 16);
                    return `rgba(${r}, ${g}, ${b}, ${alpha})`;
                }

                // Plugin: draw colored HORIZONTAL band strips behind the chart
                // Each row is a priority band — CRITICAL (red) at top, INFO (gray) at bottom
                const bandStripPlugin = {
                    id: 'bandStrips',
                    beforeDatasetsDraw(chart) {
                        const { ctx, chartArea, scales } = chart;
                        const yScale = scales.y;
                        const numBands = matrix.rows.length;

                        ctx.save();
                        for (let i = 0; i < numBands; i++) {
                            // rows indexed bottom-to-top: [INFO, LOW, MEDIUM, HIGH, CRITICAL]
                            // i=0 → INFO at y=0 (bottom), i=4 → CRITICAL at y=4 (top)
                            const band = matrix.rows[i];
                            const hex = BAND_COLORS[band] || '#6c757d';

                            const top = yScale.getPixelForValue(i + 0.5);
                            const bottom = yScale.getPixelForValue(i - 0.5);

                            // Soft color fill across full chart width
                            ctx.fillStyle = hexToRgba(hex, 0.15);
                            ctx.fillRect(chartArea.left, top, chartArea.right - chartArea.left, bottom - top);

                            // Stronger accent bar on the left edge
                            ctx.fillStyle = hexToRgba(hex, 0.6);
                            ctx.fillRect(chartArea.left, top, 5, bottom - top);
                        }
                        ctx.restore();
                    }
                };

                // Plugin: draw vertical CVSS severity labels as large greyed-out text
                // Each column gets its severity name rendered vertically (letter-stacked)
                const severityColumnLabelsPlugin = {
                    id: 'severityColumnLabels',
                    beforeDatasetsDraw(chart) {
                        const { ctx, chartArea, scales } = chart;
                        const xScale = scales.x;
                        const numCols = matrix.cols.length;
                        // Column width for offset calculation
                        const colWidth = (chartArea.right - chartArea.left) / numCols;

                        ctx.save();
                        ctx.textAlign = 'center';
                        ctx.textBaseline = 'middle';

                        for (let i = 0; i < numCols; i++) {
                            const label = matrix.cols[i];
                            // Offset 20% to the left of column center
                            const centerX = xScale.getPixelForValue(i) - colWidth * 0.2;
                            const colHeight = chartArea.bottom - chartArea.top;
                            const letters = label.split('');
                            // Scale font size to fit — allow larger text
                            const maxFontSize = 24;
                            const fitFontSize = Math.floor(colHeight / (letters.length + 1));
                            const fontSize = Math.min(maxFontSize, fitFontSize);
                            const lineHeight = fontSize * 1.15;
                            const totalTextHeight = letters.length * lineHeight;
                            const startY = chartArea.top + (colHeight - totalTextHeight) / 2;

                            ctx.font = `800 ${fontSize}px -apple-system, BlinkMacSystemFont, sans-serif`;
                            ctx.fillStyle = 'rgba(0, 0, 0, 0.18)';

                            for (let j = 0; j < letters.length; j++) {
                                const y = startY + j * lineHeight + lineHeight / 2;
                                ctx.fillText(letters[j], centerX, y);
                            }
                        }
                        ctx.restore();
                    }
                };

                new Chart(ctx.getContext('2d'), {
                    type: 'bubble',
                    data: {
                        datasets: [{
                            label: 'Finding Count',
                            data: points,
                            backgroundColor: points.map(p => hexToRgba(BAND_COLORS[p.band] || '#6c757d', 0.85)),
                            borderColor: points.map(p => BAND_COLORS[p.band] || '#6c757d'),
                            borderWidth: 2
                        }]
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            legend: { display: false },
                            tooltip: {
                                callbacks: {
                                    label: function(ctx) {
                                        const p = ctx.raw;
                                        return [
                                            `Priority Band: ${p.band}`,
                                            `CVSS Severity: ${p.severity}`,
                                            `Count: ${p.v}`
                                        ];
                                    }
                                }
                            },
                            datalabels: {
                                color: '#000',
                                font: { weight: 'bold', size: 12 },
                                formatter: function(value) { return value.v > 0 ? value.v : ''; },
                                textStrokeColor: '#fff',
                                textStrokeWidth: 3
                            }
                        },
                        scales: {
                            x: {
                                type: 'linear',
                                min: -0.5,
                                max: matrix.cols.length - 0.5,
                                ticks: {
                                    stepSize: 1,
                                    callback: function(value) {
                                        return matrix.cols[value] || '';
                                    },
                                    color: '#000',
                                    font: { weight: 'bold', size: 12 }
                                },
                                title: { display: true, text: 'CVSS Severity', color: '#333', font: { size: 13 } },
                                grid: { display: true, color: 'rgba(0,0,0,0.06)' }
                            },
                            y: {
                                type: 'linear',
                                min: -0.5,
                                max: matrix.rows.length - 0.5,
                                ticks: {
                                    stepSize: 1,
                                    callback: function(value) {
                                        // rows indexed bottom-to-top: [INFO, LOW, MEDIUM, HIGH, CRITICAL]
                                        return matrix.rows[value] || '';
                                    },
                                    color: function(context) {
                                        const band = matrix.rows[context.tick.value];
                                        return BAND_COLORS[band] || '#000';
                                    },
                                    font: { weight: 'bold', size: 13 }
                                },
                                title: { display: true, text: 'Priority Band', color: '#333', font: { size: 13 } },
                                grid: { display: false }
                            }
                        }
                    },
                    plugins: [ChartDataLabels, bandStripPlugin, severityColumnLabelsPlugin]
                });
            })();
            {% endif %}

            // =============================================
            // 4. Top Components Chart (Horizontal Stacked Bar)
            // =============================================
            {% if top_components is defined and top_components|length > 0 %}
            (function() {
                const ctx = document.getElementById('topComponentsChart');
                if (!ctx) return;
                const components = {{ top_components | tojson }};
                const labels = components.map(c => c.component_name + ' ' + c.component_version);
                const datasets = BAND_ORDER.map(band => ({
                    label: band,
                    data: components.map(c => c[band] || 0),
                    backgroundColor: BAND_COLORS[band]
                }));

                new Chart(ctx.getContext('2d'), {
                    type: 'bar',
                    data: { labels: labels, datasets: datasets },
                    options: {
                        indexAxis: 'y',
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            legend: { position: 'top' },
                            datalabels: { display: false }
                        },
                        scales: {
                            x: { stacked: true, title: { display: true, text: 'Finding Count' } },
                            y: { stacked: true, ticks: { font: { size: 10 } } }
                        }
                    }
                });
            })();
            {% endif %}

            // =============================================
            // 5. Factor Radar Chart
            // =============================================
            {% if factor_radar %}
            (function() {
                const ctx = document.getElementById('factorRadarChart');
                if (!ctx) return;
                const radar = {{ factor_radar | tojson }};
                if (!radar.labels || !radar.datasets || radar.datasets.length === 0) return;

                const datasets = radar.datasets.map((ds, i) => ({
                    label: ds.label,
                    data: ds.data,
                    borderColor: RADAR_COLORS[i % RADAR_COLORS.length],
                    backgroundColor: RADAR_COLORS[i % RADAR_COLORS.length] + '33',
                    borderWidth: 2,
                    pointRadius: 4,
                    pointBackgroundColor: RADAR_COLORS[i % RADAR_COLORS.length]
                }));

                new Chart(ctx.getContext('2d'), {
                    type: 'radar',
                    data: { labels: radar.labels, datasets: datasets },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            legend: { position: 'top' },
                            datalabels: { display: false }
                        },
                        scales: {
                            r: {
                                min: 0,
                                max: 100,
                                ticks: { stepSize: 20 },
                                pointLabels: { font: { size: 12 } }
                            }
                        }
                    }
                });
            })();
            {% endif %}

            // =============================================
            // 6. Project Summary Chart (Stacked Bar)
            // =============================================
            {% if project_summary_df is defined and project_summary_df|length > 0 %}
            (function() {
                const ctx = document.getElementById('projectSummaryChart');
                if (!ctx) return;
                const projects = {{ project_summary_df | tojson }};

                // Sort projects by total risk: highest risk (most CRITICAL/HIGH) first
                projects.sort((a, b) => {
                    // Weight CRITICAL most, then HIGH, etc.
                    const riskScore = (p) =>
                        (p['CRITICAL'] || 0) * 10000 +
                        (p['HIGH'] || 0) * 1000 +
                        (p['MEDIUM'] || 0) * 100 +
                        (p['LOW'] || 0) * 10 +
                        (p['INFO'] || 0);
                    return riskScore(b) - riskScore(a);
                });

                // Horizontal bar — projects on y-axis, highest risk at top
                const labels = projects.map(p => p.project_name);
                const datasets = BAND_ORDER.map(band => ({
                    label: band,
                    data: projects.map(p => p[band] || 0),
                    backgroundColor: BAND_COLORS[band]
                }));

                new Chart(ctx.getContext('2d'), {
                    type: 'bar',
                    data: { labels: labels, datasets: datasets },
                    options: {
                        indexAxis: 'y',
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            legend: { position: 'top' },
                            datalabels: { display: false }
                        },
                        scales: {
                            x: { stacked: true, title: { display: true, text: 'Finding Count' } },
                            y: { stacked: true }
                        }
                    }
                });
            })();
            {% endif %}

            // Render AI summary markdown
            if (typeof marked !== 'undefined') {
                var portfolioRaw = document.getElementById('ai-portfolio-summary-raw');
                var portfolioTarget = document.getElementById('ai-portfolio-summary');
                if (portfolioTarget && portfolioRaw && portfolioRaw.value) {
                    portfolioTarget.innerHTML = DOMPurify.sanitize(marked.parse(portfolioRaw.value));
                }
                document.querySelectorAll('[id^="ai-project-summary-raw-"]').forEach(function(ta) {
                    var idx = ta.id.replace('ai-project-summary-raw-', '');
                    var target = document.getElementById('ai-project-summary-' + idx);
                    if (target && ta.value) {
                        target.innerHTML = DOMPurify.sanitize(marked.parse(ta.value));
                    }
                });
            }

        });
    </script>

    {% if ai_component_guidance or ai_finding_guidance %}
    <!-- NVD Terms of Use: Required Attribution -->
    <div style="margin: 30px auto; max-width: 900px; padding: 12px 20px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 6px; font-size: 0.85em; color: #6c757d; text-align: center;">
        This product uses the NVD API but is not endorsed or certified by the NVD.
        Fix version data is derived from NVD CPE match criteria and may be reformatted.
        Verify fix versions against vendor advisories before deploying updates.
    </div>
    {% endif %}

    {% include '_action_buttons.html' %}
</body>
</html>
