{# Executive Summary — Phase 1a briefing-shell migration. Extends _briefing_shell.html (T0b deliverable). The shell owns DOCTYPE/html/head/body, the render_mode chrome gate, the 880px page frame, the design-system include, the readiness beacons, and the Inter font load. This template fills the named blocks only. Chart story (per design spec § Per-recipe chart story): - distribution → project breakdown bar (stacked by severity) - sections → open issues pie + scan frequency line + table - stats → 4 derived KPI cards (this plan's deviation from the spec table; see plan's Reconciliation note) Chart blocks branch on render_mode (the canonical Option-Y pattern from fs_report/templates/bar_chart.html): - fragment mode → emit server_svgs.get('', '') | safe - HTML/PDF mode → emit namespaced container
+ ECharts {% endblock %} {# briefing_header inherits the shell's default chrome — logo + meta band (recipe name, period, generated_at, record count, folder path, project name). Override here only if executive_summary needs different chrome; the default covers exec_summary's needs today. #} {% block hero %} {# Hero strip = eyebrow + section title + deck. In compound bundles the assembler emits its own SECTION 0N divider with the same title immediately above the fragment, so the entire hero is the chrome to suppress (not just the H1). The wrapping
below carries the suppress-marker so render_fragment(..., suppress_section_title=True) removes the whole block. Single-recipe fragment consumers (forge MCP renders) keep the hero. (B1.4 follow-up: fragment-mode visual parity.) #}
Executive Summary

{{ slide_title | default("Executive Security Summary") }}

{{ description | default("High-level security posture overview with key performance indicators, risk metrics, and project-level breakdowns.") }}

{% endblock %} {% block stats %} {# Derive 4 KPI cards from table_data totals. table_data.rows is a list of per-project dicts with critical/high/medium/low integer columns (post-pivot from the recipe's main transform). Jinja namespace-mutation syntax: {% set ns.attr = ... %} after {% set ns = namespace(attr=...) %}. NOT ns.update({...}) — Jinja namespaces don't expose Python dict methods. Same pattern as fs_report/templates/security_progress.html:199. #} {% set ns = namespace(c=0, h=0, m=0, l=0) %} {% if table_data and table_data.rows %} {% for row in table_data.rows %} {% set ns.c = ns.c + (row.get('critical', 0) or 0) | int %} {% set ns.h = ns.h + (row.get('high', 0) or 0) | int %} {% set ns.m = ns.m + (row.get('medium', 0) or 0) | int %} {% set ns.l = ns.l + (row.get('low', 0) or 0) | int %} {% endfor %} {% endif %} {% set total_findings = ns.c + ns.h + ns.m + ns.l %}
Total Findings
{{ total_findings }}
Critical
{{ ns.c }}
High
{{ ns.h }}
Medium + Low
{{ ns.m + ns.l }}
{% endblock %} {% block distribution %} {% if product_only %} {# Executive Summary aggregates per-finding rows, and a finding belongs to one row, so a dependency used by two products is counted under one of them. The Executive Dashboard's summary mode is the surface that reports each product's full contents. #}
Products only: each row is a product, with the dependency projects it uses rolled into it. A dependency used by more than one product is counted under one of them, so every finding appears exactly once. {% set shared_dependencies = shared_dependencies | default([]) %} {% if shared_dependencies is mapping or shared_dependencies is string or shared_dependencies is not iterable %}{% set shared_dependencies = [] %}{% endif %} {% if shared_dependencies and shared_dependencies | length > 0 %} {# Same disclosure the Executive Dashboard renders, so an operator moving between the two portfolio reports gets the same arithmetic explained. #}
    {% for s in shared_dependencies %}
  • {{ s.name }} (version {{ s.version_id }}){% if s.is_product %} (itself a product){% endif %} — used by {{ s.used_by | join(", ") }}; counted under {{ s.counted_under }}.
  • {% endfor %}
{% endif %}
{% endif %}

{% if product_only %}Security Findings by Product and Severity{% else %}{{ project_breakdown_spec.title if project_breakdown_spec else "Security Findings by Project and Severity" }}{% endif %}

{# The recipe-authored description is project-worded; under --product-only it would sit directly under a Product heading and contradict it. #} {% if product_only %}

Distribution of security findings across products with severity breakdown. Each product carries the dependency projects it owns.

{% elif project_breakdown_spec %}

{{ project_breakdown_spec.description }}

{% endif %} {% if render_mode == 'fragment' and not fragment_scripts_enabled %}
{{ server_svgs.get('project_breakdown', '') | safe }}
{% else %} {# Container only — all five Exec Summary charts are initialized by the consolidated themed script at the end of the sections block (it owns token-driven text colors + re-theme on light/dark toggle, A3). #}
{% endif %}
{% endblock %} {% block sections %}
{# 2×2 chart grid. Row 1: Open Issues by Severity (pie) + Exploit Signals (gauge, C1) Row 2: Security Findings Over Time (line) + Exploits Over Time (line, C2) Each cell branches on render_mode independently; fragment mode emits a per-chart server SVG, HTML/PDF emits an ECharts container. All five containers are initialized by the consolidated themed script below. #}

{{ open_issues_spec.title if open_issues_spec else "Open Issues by Severity" }}

{% if open_issues_spec %}

{{ open_issues_spec.description }}

{% endif %} {% if render_mode == 'fragment' and not fragment_scripts_enabled %}
{{ server_svgs.get('open_issues_distribution', '') | safe }}
{% else %}
{% endif %}

{{ exploit_signals_spec.title if exploit_signals_spec else "Exploit Signals" }}

{% if exploit_signals_spec %}

{{ exploit_signals_spec.description }}

{% endif %} {% if render_mode == 'fragment' and not fragment_scripts_enabled %}
{{ server_svgs.get('exploit_signals', '') | safe }}
{% else %}
{% endif %}

{{ scan_frequency_spec.title if scan_frequency_spec else "Security Findings Over Time" }}

{% if scan_frequency_spec %}

{{ scan_frequency_spec.description }}

{% endif %} {% if render_mode == 'fragment' and not fragment_scripts_enabled %}
{{ server_svgs.get('scan_frequency', '') | safe }}
{% else %}
{% endif %}

{{ exploits_over_time_spec.title if exploits_over_time_spec else "Exploits Over Time" }}

{% if exploits_over_time_spec %}

{{ exploits_over_time_spec.description }}

{% endif %} {% if render_mode == 'fragment' and not fragment_scripts_enabled %}
{{ server_svgs.get('exploits_over_time', '') | safe }}
{% else %}
{% endif %}
{% if render_mode != 'fragment' or fragment_scripts_enabled %} {% endif %} {# Findings by Project table — preserves the legacy pivoted summary. Renders in all modes; table HTML survives fragment extraction. #} {% if table_data and table_data.rows %}

Findings by {{ scope_noun }}

{% for row in table_data.rows %} {% set crit = row.get('critical', 0) | default(0, true) | int %} {% set high = row.get('high', 0) | default(0, true) | int %} {% set med = row.get('medium', 0) | default(0, true) | int %} {% set low = row.get('low', 0) | default(0, true) | int %} {% endfor %}
{{ scope_noun }} Critical High Medium Low Total
{{ row.get('project.name', '') }} {% if crit > 0 %}{{ crit }}{% else %}0{% endif %} {% if high > 0 %}{{ high }}{% else %}0{% endif %} {% if med > 0 %}{{ med }}{% else %}0{% endif %} {{ low }} {{ crit + high + med + low }}
{% endif %}
{% endblock %} {% block methodology %}

This briefing summarizes the security findings across the portfolio for the reporting period. KPIs at the top reflect totals across all findings (open + resolved) aggregated from the main transform. The distribution chart breaks those totals down by {{ scope_noun | lower }} and severity. The open-issues pie counts only unresolved findings (status not in RESOLVED, RESOLVED_WITH_PEDIGREE, NOT_AFFECTED, FALSE_POSITIVE) — different population from the KPIs above. The scan-frequency line tracks the count of findings detected per period.

{% endblock %}