{# Spec 157 - Reservations right-sizing page. Renders one ``SlotDemandSnapshot`` row per active config: a hero stat strip (avg / p95 / peak / idle), the demand-curve chart with horizontal reference lines for current + recommended reservation size, and a recommendation card carrying the reasoning block + a copy-paste ALTER RESERVATION (or CREATE RESERVATION) SQL snippet. Empty state shows when no snapshot has been computed yet (typically a fresh install whose first scan hasn't run). Low-data state shows a soft caveat when fewer than 100 jobs went into the snapshot. #} {% extends "layout.html" %} {% block content %}

Reservations

{% if not snapshot %}

No reservation snapshot yet

Run a scan to compute the slot demand curve for your workload. The recommendation card will appear here once at least one scan has completed.

Go to scan
{% else %} {% from "components/_stat_card.html" import stat_card %} {# Hero stat strip. Slot stats are unitless effective slots; the monthly delta is positive for reduce/consider (savings), zero for no_change, negative for upgrade (additional cost). #}
{{ stat_card("Avg slots used", "%.1f" | format(snapshot.avg_slots), tooltip="Average effective slot count across every 1-minute bucket of the scan window (zero-demand minutes included).") }} {{ stat_card("P95 slots used", "%.0f" | format(snapshot.p95_slots), tooltip="95th percentile demand - the 'burst floor'. The recommender uses this as the headroom anchor.") }} {{ stat_card("Peak slots used", "%.0f" | format(snapshot.peak_slots), tooltip="Maximum effective slot count in any 1-minute bucket of the scan window.") }} {{ stat_card("Idle fraction", ("%.0f%%" | format(snapshot.idle_fraction * 100)), accent="amber", tooltip="Fraction of 1-minute buckets with zero effective slot usage. High values suggest a bursty workload that could be split between a smaller reservation and on-demand overflow.") }}
{% if snapshot.low_data %}

Low-data caveat: Only {{ snapshot.low_data_job_count }} jobs in the scan window contributed to this snapshot. The recommender soft-warns below 100 jobs; rerun with a longer window or wait for more activity before resizing.

{% endif %} {# Demand chart - effective slots over time with horizontal reference lines for current (red) + recommended (green) reservation size when known. #}
{% from "components/_section_title.html" import section_title %}
{{ section_title( "Slot demand over the scan window", description="Per-minute effective slot usage. Red = current reservation (when discoverable). Green = recommended target." ) }}
{# Recommendation card. One per snapshot in v1 (multi-reservation is a follow-up spec). Carries the reasoning + caveats + the copy-paste SQL. Outer style varies by recommendation kind so the card reads "savings" vs "additional cost" at a glance. #} {% set kind = snapshot.recommendation_kind %} {% set accent_emerald = kind in ['reduce', 'consider_reservation', 'split'] %} {% set accent_rose = kind == 'upgrade' %} {% set accent_slate = kind == 'no_change' %}
{% if kind == 'reduce' %}

Reduce reservation {% if snapshot.primary_reservation_size %} from {{ snapshot.primary_reservation_size }} {% endif %} to {{ snapshot.recommendation_target_slots }} slots

{% elif kind == 'upgrade' %}

Upgrade reservation {% if snapshot.primary_reservation_size %} from {{ snapshot.primary_reservation_size }} {% endif %} to {{ snapshot.recommendation_target_slots }} slots

{% elif kind == 'consider_reservation' %}

Consider a {{ snapshot.recommendation_target_slots }}-slot Standard reservation

{% elif kind == 'split' %}

Split: keep a {{ snapshot.recommendation_target_slots }}-slot reservation, route bursts on-demand

{% else %}

No change recommended

{% endif %} {% if snapshot.primary_reservation_id %}

{{ snapshot.primary_reservation_id }}

{% else %}

Billing posture: on-demand

{% endif %}
{% if snapshot.monthly_delta_usd is not none and not accent_slate %}

{% if snapshot.monthly_delta_usd >= 0 %}Monthly saving{% else %}Additional monthly cost{% endif %}

${{ '%.0f' | format(snapshot.monthly_delta_usd | abs) }}

{% if snapshot.annual_delta_usd is not none %}

~${{ '%.0f' | format(snapshot.annual_delta_usd | abs) }}/yr

{% endif %}
{% endif %}
{# Reasoning block. Same shape as the materialization cards on /opportunities so the page reads consistently. #} {% if snapshot.reasoning %}
{% if snapshot.reasoning.current_state %}

Current state

{{ snapshot.reasoning.current_state }}

{% endif %} {% if snapshot.reasoning.observation %}

Observation

{{ snapshot.reasoning.observation }}

{% endif %} {% if snapshot.reasoning.recommendation %}

Recommendation

{{ snapshot.reasoning.recommendation }}

{% endif %} {% if snapshot.reasoning.expected_benefit %}

Expected benefit

{{ snapshot.reasoning.expected_benefit }}

{% endif %}
{% if snapshot.caveats %}
Show caveats ({{ snapshot.caveats | length }})
    {% for c in snapshot.caveats %}
  • {{ c }}
  • {% endfor %}
{% endif %} {% endif %} {# Apply SQL + rollback - only shown when the recommender produced one (no_change leaves both null). #} {% if snapshot.apply_sql %}

Apply

{{ snapshot.apply_sql }}
{% endif %} {% if snapshot.rollback_sql %}

Rollback

{{ snapshot.rollback_sql }}
{% endif %} {# Reservation-size unknown fallback. When the IAM grant isn't present, the recommender doesn't know the "Z" in "Reduce from Z to N"; the user can type it here and the page re-renders the savings calc client-side using local storage. #} {% if not snapshot.primary_reservation_size and snapshot.primary_reservation_id %}

Your reservation size couldn't be discovered (the optional bigquery.reservations.get permission isn't granted). Type the current slot count to see the savings figure - the value is stored only in your browser.

{% endif %}
{# Scan-window meta - lets the user verify they're looking at the right snapshot when reservations change over time. #}

Scan window: {{ snapshot.scan_window_start.strftime('%Y-%m-%d %H:%M UTC') }} → {{ snapshot.scan_window_end.strftime('%Y-%m-%d %H:%M UTC') }}

{# ECharts demand chart + copy-by-id helper. #} {# User-supplied reservation size fallback. Computes savings client-side using the SAME constants as the recommender. #} {% endif %}
{% endblock %}