{% if render_mode != 'fragment' %} {% if domain %}{% endif %} Scan Analysis - Finite State Report {# ECharts library — replaces Chart.js v4.4.1 + chartjs-adapter-date-fns from the pre-migration template. ECharts handles category-axis date strings natively; no adapter needed. #} {% include "_design_system.html" %} {% include "_console_shell.html" %} {% include "_echarts_ready.html" %} {% endif %} {# Recipe-local styles — render in BOTH standalone and fragment modes so fragment_extractor (_STYLE_RE) collects them, scopes under .fs-section-scan-analysis, and re-emits at the top of the fragment. #} {% if render_mode != 'fragment' %} {% endif %} {% import "_console_macros.html" as fs with context %} {# Defensive defaults — keep the template renderable when the upstream transform short-circuited (e.g. empty data path). Mirrors license_report.html:80-87 + B.1 cve_component_evidence.html. #} {% set table_data = table_data | default({"rows": [], "row_count": 0}) %} {% if not table_data is mapping %}{% set table_data = {"rows": [], "row_count": 0} %}{% endif %} {% set sa_rows = table_data.get('rows', []) or [] %} {% set raw_data = raw_data | default([]) %} {% if raw_data is mapping %}{% set raw_data = [] %}{% endif %} {# Identify the summary row by its `period` marker (set by scan_analysis.py:373) rather than positional inference. If the last row isn't the summary marker, treat all rows as day rows and the summary as empty — defensive against transform reorder. #} {% if (sa_rows | length) > 0 and sa_rows[-1].get('period') == 'Overall Summary' %} {% set summary_row = sa_rows[-1] %} {% set day_rows = sa_rows[:-1] %} {% else %} {% set summary_row = {} %} {% set day_rows = sa_rows %} {% endif %} {% if render_mode != 'fragment' %} {{ fs.topbar( crumbs=["Finite State", "Scan Analysis"], meta=[ {"label": "Domain", "value": domain or "—"}, {"label": "Period", "value": (start_date or "—") ~ " → " ~ (end_date or "—")}, {"label": "Generated", "value": generated_at or "—"}, ], controls=[], ) }} {% endif %}

Scan Analysis

Operational Insights and Performance Metrics

{% if sa_rows %} {# 7 KPI cells driven by the summary row. #} {% set total_started = summary_row.get('total_scans_started', 0) | int %} {% set failed = summary_row.get('failed_scans', 0) | int %} {% set succeeded = total_started - failed %} {# Use the transform's authoritative success_rate (completed / (completed + failed)) so the KPI matches the Daily Scan Metrics table's per-row Success Rate column. The previous (total_started - failed) / total_started formula counted queued/in-progress scans as successes and disagreed with the table cell. #} {% set success_pct = summary_row.get('success_rate', 0) | float %} {% set queue = summary_row.get('recently_queued', 0) | int %}
{{ fs.kpi_cell("Total Scans", total_started, delta="scans started") }} {{ fs.kpi_cell( "Projects", summary_row.get('unique_projects', 0), delta=((summary_row.get('new_projects', 0) | string) ~ " new / " ~ (summary_row.get('existing_projects', 0) | string) ~ " existing"), ) }} {{ fs.kpi_cell("Versions", summary_row.get('unique_versions', 0), delta="artifacts processed") }} {{ fs.kpi_cell( "Success Rate", "%.1f"|format(success_pct) ~ "%", delta=(succeeded | string) ~ " succeeded", dot=("low" if success_pct >= 90 else ("medium" if success_pct >= 70 else "critical")), ) }} {{ fs.kpi_cell( "Avg Duration", "%.0f"|format(summary_row.get('median_duration_minutes', 0) | float), delta="minutes (median)", ) }} {{ fs.kpi_cell( "Queue Size", queue, delta="scans waiting", dot=("medium" if queue > 0 else None), ) }} {{ fs.kpi_cell( "Failed Scans", failed, delta="scans failed", dot=("critical" if failed > 0 else None), ) }}
{# Charts row 1: throughput (full-width line). #}
{{ fs.panel_head("Scan throughput over time", meta="started vs completed per day") }} {% if render_mode == 'fragment' and not fragment_scripts_enabled %}
Chart unavailable in fragment view
{% else %}
{% endif %}
{# Charts row 2: duration + success rate side-by-side. #}
{{ fs.panel_head("Average scan duration", meta="minutes per day") }} {% if render_mode == 'fragment' and not fragment_scripts_enabled %}
Chart unavailable in fragment view
{% else %}
{% endif %}
{{ fs.panel_head("Daily success rate", meta="percent succeeded") }} {% if render_mode == 'fragment' and not fragment_scripts_enabled %}
Chart unavailable in fragment view
{% else %}
{% endif %}
{# Charts row 3: scan-type distribution + failure-type distribution side-by-side. #}
{{ fs.panel_head("Scan type distribution", meta="counts across all days") }} {% if render_mode == 'fragment' and not fragment_scripts_enabled %}
Chart unavailable in fragment view
{% else %}
{% endif %}
{{ fs.panel_head("Scan failures over time", meta="stacked failures + overall rate") }} {% if render_mode == 'fragment' and not fragment_scripts_enabled %}
Chart unavailable in fragment view
{% else %}
{% endif %}
{# Daily Scan Metrics table. #}
{{ fs.panel_head("Daily scan metrics", meta="per-day operational counts") }}
{% for row in sa_rows %} {% set sr = row.get('success_rate', 0) | float %} {% endfor %}
Date Started Projects New Existing Versions Completed Failed In Progress Success Rate Avg Duration (min) Scan Counts by Type
SCA SAST CONFIG SOURCE SCA REACHABILITY IMPORT
{{ row.get('date', '') }} {{ row.get('total_scans_started', 0) }} {{ row.get('unique_projects', 0) }} {{ row.get('new_projects', 0) }} {{ row.get('existing_projects', 0) }} {{ row.get('unique_versions', 0) }} {{ row.get('total_completed_scans', 0) }} {{ row.get('failed_scans', 0) }} {{ row.get('still_active_scans', 0) }} {{ "%.1f"|format(sr) }}% {% set ad = row.get('avg_duration_minutes', 0) | float %} {% if ad > 0 %}{{ "%.0f"|format(ad) }}{% else %}-{% endif %} {{ row.get('sca_scans', 0) }} {{ row.get('sast_scans', 0) }} {{ row.get('config_scans', 0) }} {{ row.get('source_sca_scans', 0) }} {{ row.get('vulnerability_analysis_scans', 0) }} {{ row.get('sbom_import_scans', 0) }}
{% if raw_data and (raw_data | length) > 0 %}
{{ fs.panel_head("Individual scan details", meta=(raw_data | length | string) ~ " scan" ~ ("s" if (raw_data | length) != 1 else "")) }}

Complete list of all scans for detailed analysis and custom reporting. This data is also available in the CSV and XLSX downloads.

{% if folder_path %}{% endif %} {% for row in raw_data %} {% set status_lc = (row.get('status', 'unknown') | string | lower) %} {% set type_lc = (row.get('type', 'unknown') | string | lower) %} {% if folder_path %}{% endif %} {% endfor %}
Scan ID Date Started Date Completed Status TypeFolderProject Version Duration (min) Error Message
{{ row.get('id', 'N/A') }} {{ row.get('scan_date', 'N/A') }} {% set cd = row.get('completion_date', 'N/A') %} {% if cd in ('NaT', '-', None, '') %}-{% else %}{{ cd }}{% endif %} {{ row.get('status', 'N/A') }} {{ row.get('type', 'N/A') }}{{ row.get('folder_name', '') }}{{ row.get('project_name', 'N/A') }} {% set vn = row.get('version_name', '') %} {% if vn %}{{ vn }}{% else %}-{% endif %} {% set dm = row.get('duration_minutes', 0) | float %} {% if dm > 0 %}{{ "%.1f"|format(dm) }}{% else %}-{% endif %} {% set em = row.get('errorMessage', '') %} {% if em and em != '-' %}{{ em }}{% else %}-{% endif %}
{% endif %} {% else %}

No scan data available for the selected time period.

{% endif %}
{% if render_mode != 'fragment' %} {{ fs.status_bar(items=[ "Recipe: Scan Analysis", "Days: " ~ ((day_rows | length) | string), "Scans: " ~ ((summary_row.get('total_scans_started', 0)) | string), generated_at | default(''), ], version='fs-report ' ~ (fs_report_version | default('dev'))) }} {% endif %} {% if render_mode != 'fragment' or fragment_scripts_enabled %} {% include '_action_buttons.html' %} {% endif %}