{# Pareto + binary-splits panel. Embedded full-page on first render and re-fetched as an HTMX fragment when the user clicks a daily/hourly bar on the cost chart. The container ``#cost-portfolio-panel`` is owned by ``cost_overview.html``; this partial is what gets swapped. #} {% from "_spend_shapes.html" import split_cell, spark_cell %} {% if is_filtered %}
Filtered to {{ filter_label }} Clear ✕
{% endif %}

Where the money goes

{% if pareto.total_session_count %}
{{ pareto.total_cost }}
Total cost{% if not is_filtered %} (all sessions){% endif %}
{{ pareto.pareto_session_count }}
Sessions to 80%
{{ pareto.total_session_count }}
Sessions with cost
{% if cache_loss and cache_loss.count %}
{{ cache_loss.cost }}
Recoverable cache waste ({{ cache_loss.count }} event{{ 's' if cache_loss.count != 1 else '' }}{% if cache_loss.show_pct %} · {{ "%.1f"|format(cache_loss.pct_of_total) }}%{% endif %})
{% endif %} {% if cache_loss and cache_loss.break_count %}
{{ cache_loss.break_cost }}
Breaks > {{ cache_loss.max_ttl_minutes }} min ({{ cache_loss.break_count }} · unrecoverable)
{% endif %}
{% if ttl_verdict and ttl_verdict.n_requests %}
Prompt-cache TTL
{% if ttl_verdict.decisive %}

{{ ttl_verdict.recommendation }} would have been cheaper here — about {{ ttl_verdict.savings }} ({{ "%.1f"|format(ttl_verdict.margin_pct) }}%) over these {{ "{:,}".format(ttl_verdict.n_requests) }} requests.

{% else %}

The two settings land within {{ "%.1f"|format(ttl_verdict.margin_pct) }}% of each other over these {{ "{:,}".format(ttl_verdict.n_requests) }} requests — too close to call. Leave it alone.

{% endif %}

Replayed at 5m: {{ ttl_verdict.cost_5m }} · at 1h: {{ ttl_verdict.cost_1h }}{% if ttl_verdict.ttl_observed %} · currently billed at {{ ttl_verdict.ttl_observed }}{% endif %}. {{ ttl_verdict.n_gaps_recoverable }} gap(s) a longer TTL would rescue, {{ ttl_verdict.n_gaps_unrecoverable }} break(s) past {{ ttl_verdict.max_ttl_minutes }} min that nothing recovers, and {{ ttl_verdict.n_structural }} structural invalidation(s) that cost the same either way. 1h charges 2× input on every incremental cache write, not just the rebuilds it avoids — which is why the waste figure above cannot decide this on its own. Subagents are excluded; they have their own subagentPromptCacheTtl.

{% endif %}

These {{ pareto.pareto_session_count }} sessions represent 80% of {{ pareto.total_cost }} total cost ({{ pareto.pareto_cost }} cumulative).

{% else %}

No cost data{% if is_filtered %} for {{ filter_label }}{% endif %}.

{% endif %}

Pareto table

{% if pareto.rows %}

Sessions ranked by cost. The highlighted row is where cumulative spend crosses 80% — everything above it is the "vital few".

Cost split: cache read cache write output

{% for row in pareto.rows %} {{ split_cell(row.split) }} {{ spark_cell(row.spark) }} {% endfor %}
Started Project Title Cost Cost split Spend shape Cumulative % of total
{{ row.started_at }} {{ row.project }} {{ row.title or row.session_id[:8] }} {{ row.cost }} {{ row.cumulative }} {{ "%.1f"|format(row.cum_pct) }}%
{% else %}

No sessions with cost{% if is_filtered %} during {{ filter_label }}{% endif %}.

{% endif %}

Breakdowns

Each breakdown partitions every session into two buckets and compares total spend. "% of total $" sums to 100% across the pair.

{% set splits = [ ("Subagent presence", subagent_split, "Sessions that used Task/Agent subagents or produced sidechain messages."), ("Huge reads", huge_reads_split, "Sessions where Read-category cache creation is ≥10% of cost and ≥100k tokens."), ("Skill / slash command usage", skill_split, "Sessions that invoked a non-built-in slash command or skill."), ] %} {% for label, split, hint in splits %}

{{ label }}

{{ hint }}

Sessions Total $ Mean $ % of total $
With {{ split["with"].sessions }} {{ split["with"].cost }} {{ split["with"].mean_cost }} {{ "%.1f"|format(split["with"].pct_cost) }}%
Without {{ split["without"].sessions }} {{ split["without"].cost }} {{ split["without"].mean_cost }} {{ "%.1f"|format(split["without"].pct_cost) }}%
{% endfor %}