fix(ui): billing-shape-aware quota chip and modal #43

Merged
alee merged 1 commits from fix/quota-ui-billing-shape-clarity into main 2026-09-06 21:26:41 +00:00
5 changed files with 264 additions and 370 deletions

View File

@@ -670,49 +670,22 @@ function renderQuotaModal(quota) {
const displayPct = (quota.metered_fraction_of_plan * 100).toFixed(1);
const calls = Number(quota.metered_calls_30d || 0).toLocaleString();
// ── per-provider balance rows (reusing .bar-row / .bars-scroll) ─────
// ── provider accounts (labeled by billing shape) ────────────────────
const byProvider = quota.by_provider || {};
const providers = Object.keys(byProvider).sort();
let providerRowsHtml = '';
let totalBalance = quota.total_balance_usd;
if (providers.length) {
providerRowsHtml = providers.map(name => {
const p = byProvider[name] || {};
const balance = p.balance_usd;
const burn = p.burn_rate_usd_per_hour ?? null;
const proj = p.projected_hours_remaining ?? null;
const low = !!p.runway_low_warning;
const balanceAt = p.balance_at || null;
const source = p.balance_source || '';
const lineParts = [];
if (balance != null && typeof balance === 'number') {
lineParts.push('$' + balance.toFixed(2));
} else {
lineParts.push('balance unknown');
}
if (burn != null && typeof burn === 'number') {
lineParts.push('~$' + burn.toFixed(2) + '/hr');
}
if (proj != null && typeof proj === 'number') {
lineParts.push('~' + Math.round(proj) + 'h');
}
const suffix = formatBalanceStaleness(balanceAt, source);
const accent = low ? 'var(--tblr-danger)' : (balance == null || burn == null) ? 'var(--tblr-warning)' : 'var(--tblr-success)';
return `<div class="bar-row" title="${escapeHtml(name)}: ${lineParts.join(' · ')}${suffix ? ' · ' + suffix : ''}">
const accent = low ? 'var(--tblr-danger)' : 'var(--tblr-secondary)';
return `<div class="bar-row" title="${formatProviderBalance(name, p)}">
<span class="bar-label" style="color:${accent};font-weight:600">${escapeHtml(name)}</span>
<span class="bar-val">${lineParts.join(' · ')}${suffix ? ' <span style="color:var(--tblr-secondary);font-size:0.68rem">· ' + escapeHtml(suffix) + '</span>' : ''}</span>
<span class="bar-val">${formatProviderBalance(name, p)}</span>
</div>`;
}).join('');
}
let totalRow = '';
if (totalBalance != null && typeof totalBalance === 'number') {
totalRow = `<div class="bar-row" style="border-top:1px solid rgba(255,255,255,.09);margin-top:4px;padding-top:8px">
<span class="bar-label" style="font-weight:700">Total</span>
<span class="bar-val" style="font-weight:700">$${totalBalance.toFixed(2)}</span>
</div>`;
}
el.innerHTML = `
<div class="mb-3">
<div style="font-size:1.8rem;font-weight:700;color:${heroColor}">${displayPct}%</div>
@@ -725,15 +698,14 @@ function renderQuotaModal(quota) {
<span class="quota-label">Reset</span><span class="quota-value">${quota.next_reset_date || 'not configured'}</span>
</div>
${providers.length ? `
<h6 class="card-title mb-2" style="font-size:.78rem;text-transform:uppercase;letter-spacing:.06em;color:var(--tblr-secondary)">Per-provider balance &amp; runway</h6>
<h6 class="card-title mb-2" style="font-size:.78rem;text-transform:uppercase;letter-spacing:.06em;color:var(--tblr-secondary)">Provider accounts</h6>
<div id="quota-provider-rows" class="bars-scroll" style="max-height:260px">
${providerRowsHtml}
${totalRow}
</div>` : ''}
</div>` : '<div class="text-muted small">No provider accounts configured</div>'}
<div class="quota-note">${escapeHtml(quota.note || '')}</div>`;
}
function formatBalanceStaleness(balanceAt, source) {
function formatBalanceStaleness(balanceAt) {
if (!balanceAt) return '';
try {
const then = new Date(balanceAt);
@@ -741,17 +713,57 @@ function formatBalanceStaleness(balanceAt, source) {
if (Number.isNaN(ms) || ms < 0) return '';
const minutes = Math.floor(ms / 60000);
if (minutes < 1) return 'just now';
if (minutes < 60) return `sampled ${minutes}m ago`;
if (minutes < 60) return `${minutes}m old`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `sampled ${hours}h ago`;
if (hours < 24) return `${hours}h old`;
const days = Math.floor(hours / 24);
return `sampled ${days}d ago`;
return `${days}d old`;
} catch (_) {
return '';
}
}
function _quotaChipAccent(low, hasKnownBurn) {
/* Format a provider entry's balance according to its billing shape.
- Negative tiny balances render as "~$0.00 (slight overage)" so we never
show "-$0.00".
- Positive tiny balances render as "<$0.01".
- A genuinely zero balance renders as "$0.00". */
function formatProviderBalance(provider, entry) {
const source = entry && entry.balance_source ? entry.balance_source : '';
const balance = entry && typeof entry.balance_usd === 'number' ? entry.balance_usd : null;
const balanceAt = entry && entry.balance_at ? entry.balance_at : null;
const suffix = formatBalanceStaleness(balanceAt);
const suffixText = suffix ? ` (${suffix})` : '';
if (balance === null) {
return `${escapeHtml(provider)}: account balance not configured`;
}
let amountText;
if (balance === 0) {
amountText = '$0.00';
} else if (Math.abs(balance) < 0.005) {
amountText = balance < 0 ? '~$0.00 (slight overage)' : '<$0.01';
} else {
amountText = '$' + balance.toFixed(2);
}
const sourceText = source === 'polled'
? 'credits'
: source === 'telemetry'
? 'overage allowance'
: source === 'unconfigured'
? 'account balance not configured'
: 'account';
if (source === 'unconfigured') {
return `${escapeHtml(provider)}: ${amountText} · account balance not configured`;
}
return `${escapeHtml(provider)}: ${amountText} ${sourceText}${suffixText}`;
}
function _quotaChipAccent(low) {
if (low) {
return {
'--chip-accent': 'var(--tblr-danger)',
@@ -759,13 +771,6 @@ function _quotaChipAccent(low, hasKnownBurn) {
borderColor: 'rgba(239,68,68,.35)',
};
}
if (!hasKnownBurn) {
return {
'--chip-accent': 'var(--tblr-warning)',
background: 'linear-gradient(135deg, rgba(245,158,11,.12), rgba(59,130,246,.08))',
borderColor: 'rgba(245,158,11,.38)',
};
}
return {
'--chip-accent': 'var(--tblr-success)',
};
@@ -781,58 +786,22 @@ function renderQuotaChip(quota) {
return;
}
chip.hidden = false;
// ── per-provider aggregate display -----------------------------------
const byProvider = quota.by_provider || {};
const providers = Object.keys(byProvider);
const entries = providers.map(name => byProvider[name]).filter(Boolean);
const knownBalances = entries.map(p => p.balance_usd).filter(v => v != null && typeof v === 'number');
const totalBalance = knownBalances.length ? knownBalances.reduce((a, b) => a + b, 0) : null;
if (totalBalance != null) {
const low = entries.some(p => !!p.runway_low_warning);
const hasKnownBurn = entries.some(p => p.burn_rate_usd_per_hour != null);
const worstProjEntry = entries
.filter(p => p.projected_hours_remaining != null)
.sort((a, b) => Number(a.projected_hours_remaining) - Number(b.projected_hours_remaining))[0];
const hours = worstProjEntry ? Math.round(Number(worstProjEntry.projected_hours_remaining)) : null;
const chipStyle = _quotaChipAccent(low, hasKnownBurn);
const balanceStr = '$' + totalBalance.toFixed(2);
text.textContent = hours != null ? balanceStr + ' · ~' + hours + 'h' : balanceStr;
for (const [k, v] of Object.entries(chipStyle)) {
chip.style.setProperty(k, v);
}
if (chipStyle['--chip-accent']) {
text.style.color = chipStyle['--chip-accent'];
}
// Tooltip lists every provider plus its staleness
const tooltipLines = providers.map(name => {
const p = byProvider[name] || {};
const bal = p.balance_usd;
const burn = p.burn_rate_usd_per_hour;
const parts = [escapeHtml(name)];
if (bal != null && typeof bal === 'number') {
parts.push('$' + bal.toFixed(2));
} else {
parts.push('balance unknown');
}
if (burn != null && typeof burn === 'number') parts.push('~$' + burn.toFixed(2) + '/hr');
const proj = p.projected_hours_remaining;
if (proj != null && typeof proj === 'number') parts.push('~' + Math.round(proj) + 'h');
const suffix = formatBalanceStaleness(p.balance_at, p.balance_source);
if (suffix) parts.push(suffix);
return parts.join(' · ');
});
text.setAttribute('title', tooltipLines.join('\n'));
fill.style.width = `${Math.min(quota.metered_fraction_of_plan * 100, 100)}%`;
return;
}
// ── fallback: percentage only ----------------------------------------
// The chip always tells the kWh-subscription story; per-provider balances
// live in the modal where they can be labeled by billing shape.
const pct = Math.min(quota.metered_fraction_of_plan * 100, 100);
const displayPct = (quota.metered_fraction_of_plan * 100).toFixed(1);
fill.style.width = `${pct}%`;
text.textContent = `${(quota.metered_fraction_of_plan * 100).toFixed(1)}%`;
text.textContent = `${displayPct}% · ${quota.plan_kwh} kWh plan`;
text.setAttribute('title', 'Click for provider account detail');
const chipStyle = _quotaChipAccent(pct > 90);
for (const [k, v] of Object.entries(chipStyle)) {
chip.style.setProperty(k, v);
}
if (chipStyle['--chip-accent']) {
text.style.color = chipStyle['--chip-accent'];
}
}
function renderDecisions(decisions) {

View File

@@ -65,81 +65,52 @@ def _fmt_runway(hours: float) -> str:
return f"~{hours / 24:.1f}d"
def _quota_lead_values(rows: dict) -> tuple[Optional[float], Optional[float], Optional[float], bool, Optional[str]]:
providers = sorted(
{label[: -len(" balance_usd")] for label in rows if label.endswith(" balance_usd")}
)
eligible = [
p for p in providers if rows.get(f"{p} balance_source") == "polled"
]
candidates = []
for provider in eligible:
balance_value = rows.get(f"{provider} balance_usd")
balance = _numeric_suffix_value(balance_value)
burn_value = rows.get(f"{provider} burn_rate_usd_per_hour")
burn = _numeric_suffix_value(burn_value)
runway_value = rows.get(f"{provider} projected_hours_remaining")
runway = _numeric_suffix_value(runway_value)
low = bool(rows.get(f"{provider} runway_low_warning"))
note = rows.get(f"{provider} runway_note")
candidates.append((provider, balance, burn, runway, low, note))
if not candidates:
return (None, None, None, False, None)
def _key(item):
_provider, balance, _burn, runway, _low, _note = item
runway_sort = runway if isinstance(runway, (int, float)) else float("inf")
balance_sort = balance if isinstance(balance, (int, float)) else float("inf")
return (runway_sort, balance_sort, item[0])
_, balance, burn, runway, low, note = min(candidates, key=_key)
return (balance, burn, runway, low, note)
def _numeric_suffix_value(value: object) -> Optional[float]:
if value is None:
return None
if isinstance(value, (int, float)):
return float(value)
if isinstance(value, str):
try:
return float(value.split()[0])
except (IndexError, ValueError):
return None
return None
def _format_quota_lead(
balance: Optional[float],
burn: Optional[float],
runway_hours: Optional[float],
low_warning: bool,
) -> str:
"""The quota panel's headline: balance, burn rate, projected runway.
Deliberately 2dp rather than ``_fmt_usd``'s 6 — this is money an operator
acts on, not per-request micro-money.
``burn`` and ``runway_hours`` are legitimately ``None`` when the sample
segment is too short to extrapolate (PR #30's guard against a wild rate
right after a top-up). That is a real answer, not missing data, so it
renders "burn n/a" and the caller shows ``runway_note`` alongside. The
literal "None" must never reach the screen.
"""
def _format_small_balance(balance: Optional[float]) -> str:
if balance is None:
return "balance n/a"
parts = [f"balance ${balance:.2f} left"]
parts.append(f"burn ${burn:.2f}/h" if burn is not None else "burn n/a")
if runway_hours is not None:
runway = f"runway {_fmt_runway(float(runway_hours))}"
# Same markup idiom the legend already uses for `resets`.
parts.append(
f"[rgb(200,80,80)]{runway}[/rgb(200,80,80)]" if low_warning else runway
)
return "n/a"
if balance == 0:
return "$0.00"
if abs(balance) < 0.005:
return "~$0.00 (slight overage)" if balance < 0 else "<$0.01"
return f"${balance:.2f}"
def _format_provider_quota_row(provider: str, rows: dict) -> str:
balance = rows.get(f"{provider} balance_usd")
source = rows.get(f"{provider} balance_source") or ""
balance_at = rows.get(f"{provider} balance_at")
burn = rows.get(f"{provider} burn_rate_usd_per_hour")
runway = rows.get(f"{provider} projected_hours_remaining")
amount = _format_small_balance(balance) if isinstance(balance, (int, float)) else "n/a"
if source == "polled":
label = "credits"
elif source == "telemetry":
label = "overage allowance"
elif source == "unconfigured":
label = "account balance not configured"
else:
label = "account"
parts = [f"{provider}: {amount} {label}"]
if burn is not None and isinstance(burn, (int, float)):
parts.append(f"burn ${burn:.2f}/h")
if runway is not None and isinstance(runway, (int, float)):
parts.append(f"runway {_fmt_runway(float(runway))}")
if balance_at:
parts.append(f"@{balance_at}")
return " · ".join(parts)
def _provider_quota_rows(rows: dict) -> list[str]:
return [
_format_provider_quota_row(provider, rows)
for provider in sorted(
{label[: -len(" balance_usd")] for label in rows if label.endswith(" balance_usd")}
)
]
# Compact bottom keys legend. The 11 BINDINGS are collapsed to these short
# (key, label) pairs — duplicate quit bindings (q/Q/ctrl+c) show once. Rendered
# as a single markup string so Textual's Static wraps rather than truncates.
@@ -603,16 +574,6 @@ class DashboardApp(App):
flex_default=None,
window_start_30d=None,
) -> str:
"""Build the quota legend string, suppressing ``None`` values.
``metered``/``frac``/``calls`` come from ``rows.get(...)`` and may be
``None`` (unmetered plan); render those as ``n/a`` rather than the
Python literal so the legend never contains the substring ``"None"``.
``reset_date`` is the billing-period start (shown as "period since …").
``window_start_30d`` is the rolling 30-day window start (shown as
"window start …").
"""
metered_fmt = "n/a" if metered is None else metered
frac_fmt = "n/a" if frac is None else frac
calls_fmt = "n/a" if calls is None else calls
@@ -625,13 +586,11 @@ class DashboardApp(App):
legend += f" · [rgb(128,128,128)]flex default {flex_default}[/rgb(128,128,128)]"
if reset_date:
legend += (
f" · [rgb(128,128,128)]period since {reset_date}"
f"[/rgb(128,128,128)]"
f" · [rgb(128,128,128)]period since {reset_date}[/rgb(128,128,128)]"
)
if window_start_30d:
legend += (
f" · [rgb(128,128,128)]window start {window_start_30d}"
f"[/rgb(128,128,128)]"
f" · [rgb(128,128,128)]window start {window_start_30d}[/rgb(128,128,128)]"
)
if next_reset_date:
legend += (
@@ -639,6 +598,10 @@ class DashboardApp(App):
)
return legend
def _format_quota_kwh_lead(self, plan, metered, frac) -> str:
percent = round(float(frac) * 100) if frac is not None else 0
return f"{percent}% of {plan} kWh plan ({metered or 0} kWh metered)"
def _render(self, model: dict) -> None:
# Quota panel — guard against being called before compose() mounts
# these widgets (set_interval in on_mount can fire _on_interval too
@@ -655,20 +618,12 @@ class DashboardApp(App):
frac = rows.get("fraction")
calls = rows.get("calls")
# Balance/runway lead. Wired OUTSIDE the plan gate on purpose: the
# credit balance is meaningful whether or not a kWh plan is set, and
# it is the figure that actually stops traffic.
lead = self.query_one("#quota-lead", Static)
note = self.query_one("#quota-note", Static)
if rows:
balance, burn, runway, low, note_text = _quota_lead_values(rows)
lead.update(_format_quota_lead(balance, burn, runway, low))
if balance is not None and burn is None:
note.update(str(note_text or "burn estimate unavailable"))
note.display = True
else:
note.update("")
note.display = False
lead.update(self._format_quota_kwh_lead(plan, metered, frac))
note.update("")
note.display = False
else:
lead.update("")
note.update("")
@@ -682,12 +637,14 @@ class DashboardApp(App):
window_start = rows.get("window_start_30d")
flex_default = model.get("flex_default")
next_reset_date = rows.get("next_reset_date")
legend.update(
legend_lines = [
self._format_quota_legend(
plan, metered, frac, calls, reset_date,
next_reset_date, flex_default, window_start,
)
)
]
legend_lines.extend(_provider_quota_rows(rows))
legend.update("\n".join(legend_lines))
else:
bar.display = False
legend.update("quota not configured")

View File

@@ -56,21 +56,19 @@ def build_model(data: dict) -> dict:
{"label": "reset_date", "value": quota.get("reset_date")},
{"label": "next_reset_date", "value": quota.get("next_reset_date")},
{"label": "window_start_30d", "value": quota.get("window_start_30d")},
{"label": "total_balance_usd", "value": quota.get("total_balance_usd")},
]
for provider, entry in sorted((quota.get("by_provider") or {}).items()):
balance = entry.get("balance_usd")
source = entry.get("balance_source") or ""
balance_at = entry.get("balance_at")
balance_label = f"{provider} balance_usd"
if balance is None:
balance_value = None
elif balance_at:
balance_value = f"{balance} @ {balance_at}"
else:
balance_value = str(balance)
quota_rows.append({"label": balance_label, "value": balance_value})
quota_rows.append(
{"label": f"{provider} balance_source", "value": entry.get("balance_source")}
{"label": f"{provider} balance_usd", "value": balance}
)
quota_rows.append(
{"label": f"{provider} balance_source", "value": source}
)
quota_rows.append(
{"label": f"{provider} balance_at", "value": balance_at}
)
quota_rows.append(
{

View File

@@ -175,13 +175,18 @@ def test_quota_modal_has_not_configured_fallback():
assert "not configured" in html
def test_admin_quota_chip_references_balance_and_runway():
"""The index page references per-provider balance / runway quota fields and the provider rows container."""
def test_admin_quota_chip_references_kwh_plan_and_modal_lists_provider_accounts():
"""The chipshows kWh-plan fraction and the modal has labeled Provider accounts rows."""
index_path = ROOT / "admin" / "frontend" / "index.html"
html = index_path.read_text()
assert "balance_usd" in html and "runway_low_warning" in html
assert "metered_fraction_of_plan" in html and "plan_kwh" in html
assert 'id="quota-provider-rows"' in html
assert "balance_at" in html
assert "Provider accounts" in html
assert "formatProviderBalance" in html
assert "overage allowance" in html
assert "credits" in html
assert "~$0.00 (slight overage)" in html
assert "<$0.01" in html
def test_builtin_profile_cards_offer_duplicate(admin_client):

View File

@@ -239,14 +239,15 @@ def test_build_model_quota_panel_includes_reset_date():
assert by_label["window_start_30d"] == "2026-07-24"
def test_build_model_quota_panel_includes_total_balance_and_provider_rows():
"""The new per-provider quota shape must reach the TUI data model."""
def test_build_model_quota_panel_includes_provider_rows():
"""The per-provider quota shape reaches the TUI data model."""
m = build_model(_fixture())
rows = m["quota"]
by_label = {r["label"]: r["value"] for r in rows}
assert by_label["total_balance_usd"] == 8.50
assert by_label["neuralwatt balance_usd"] == "8.5 @ 2026-08-23T09:58:00+00:00"
assert "total_balance_usd" not in by_label
assert by_label["neuralwatt balance_usd"] == 8.5
assert by_label["neuralwatt balance_source"] == "telemetry"
assert by_label["neuralwatt balance_at"] == "2026-08-23T09:58:00+00:00"
assert by_label["neuralwatt burn_rate_usd_per_hour"] == 1.0
assert by_label["neuralwatt projected_hours_remaining"] == 8.5
assert by_label["neuralwatt runway_low_warning"] is False
@@ -1717,195 +1718,159 @@ def test_placeholder_row_matches_column_arity():
# --------------------------------------------------------------------------
# T4 — quota panel leads with balance and runway.
# T4 — quota panel: kWh-only lead, labeled per-provider account rows.
# --------------------------------------------------------------------------
def test_format_quota_lead_states():
"""All four lead states, including the one this deployment is actually in.
burn/runway are legitimately None when the sample segment is too short
(PR #30). That is deliberate, not missing data, so the None path must
still say something an operator can read.
"""
full = tui._format_quota_lead(8.50, 1.0, 8.5, False)
assert "balance $8.50 left" in full
assert "burn $1.00/h" in full
assert "runway ~8.5h" in full
none_burn = tui._format_quota_lead(8.50, None, None, False)
assert "burn n/a" in none_burn
assert "None" not in none_burn
assert tui._format_quota_lead(None, 1.0, 8.5, False) == "balance n/a"
assert "runway ~20.8d" in tui._format_quota_lead(100.0, 1.0, 500.0, False)
low = tui._format_quota_lead(8.50, 4.0, 1.5, True)
assert "[rgb(200,80,80)]runway ~1.5h" in low
def test_format_small_balance_renders_tiny_and_normal_values():
assert tui._format_small_balance(None) == "n/a"
assert tui._format_small_balance(0.0) == "$0.00"
assert tui._format_small_balance(0.0001) == "<$0.01"
assert tui._format_small_balance(-0.004) == "~$0.00 (slight overage)"
assert tui._format_small_balance(8.50) == "$8.50"
assert tui._format_small_balance(-1.0) == "$-1.00"
def test_quota_lead_values_binds_only_to_polled_providers():
rows = {
"openrouter balance_usd": "50.0 @ 2026-08-23T10:00:00+00:00",
def test_format_provider_quota_row_labels_billing_shape():
polled = {
"openrouter balance_usd": 4.999,
"openrouter balance_source": "polled",
"openrouter burn_rate_usd_per_hour": 5.0,
"openrouter balance_at": "2026-08-23T10:00:00+00:00",
"openrouter burn_rate_usd_per_hour": 0.5,
"openrouter projected_hours_remaining": 10.0,
"openrouter runway_low_warning": False,
"openrouter runway_note": None,
"neuralwatt balance_usd": "-0.004 @ 2026-08-23T10:00:00+00:00",
}
row = tui._format_provider_quota_row("openrouter", polled)
assert "openrouter: $5.00 credits" in row
assert "burn $0.50/h" in row
assert "runway ~10.0h" in row
telemetry = {
"neuralwatt balance_usd": -0.004,
"neuralwatt balance_source": "telemetry",
"neuralwatt burn_rate_usd_per_hour": 1.0,
"neuralwatt projected_hours_remaining": -0.004,
"neuralwatt runway_low_warning": True,
"neuralwatt runway_note": "allowance reads negative",
"neuralwatt balance_at": "2026-08-23T09:58:00+00:00",
}
balance, burn, runway, low, note = tui._quota_lead_values(rows)
assert balance == 50.0
assert burn == 5.0
assert runway == 10.0
assert low is False
assert note is None
row = tui._format_provider_quota_row("neuralwatt", telemetry)
assert "neuralwatt: ~$0.00 (slight overage) overage allowance" in row
def test_quota_lead_values_uses_lowest_runway_among_polled():
rows = {
"openrouter balance_usd": 50.0,
"openrouter balance_source": "polled",
"openrouter burn_rate_usd_per_hour": 5.0,
"openrouter projected_hours_remaining": 10.0,
"openrouter runway_low_warning": False,
"openrouter runway_note": None,
"provider-b balance_usd": 20.0,
"provider-b balance_source": "polled",
"provider-b burn_rate_usd_per_hour": 5.0,
"provider-b projected_hours_remaining": 3.0,
"provider-b runway_low_warning": True,
"provider-b runway_note": "low",
unconfigured = {
"provider-a balance_usd": None,
"provider-a balance_source": "unconfigured",
}
balance, burn, runway, low, note = tui._quota_lead_values(rows)
assert balance == 20.0
assert runway == 3.0
assert low is True
assert note == "low"
row = tui._format_provider_quota_row("provider-a", unconfigured)
assert "provider-a: n/a account balance not configured" in row
def test_quota_lead_values_falls_back_to_lowest_balance_when_no_runway():
def test_provider_quota_rows_returns_one_row_per_provider_sorted():
rows = {
"openrouter balance_usd": 50.0,
"neuralwatt balance_usd": 0.0001,
"neuralwatt balance_source": "telemetry",
"neuralwatt balance_at": None,
"neuralwatt burn_rate_usd_per_hour": None,
"neuralwatt projected_hours_remaining": None,
"neuralwatt runway_low_warning": False,
"neuralwatt runway_note": None,
"openrouter balance_usd": 4.999,
"openrouter balance_source": "polled",
"openrouter balance_at": None,
"openrouter burn_rate_usd_per_hour": None,
"openrouter projected_hours_remaining": None,
"openrouter runway_low_warning": False,
"openrouter runway_note": "recent top-up",
"provider-b balance_usd": 20.0,
"provider-b balance_source": "polled",
"provider-b burn_rate_usd_per_hour": None,
"provider-b projected_hours_remaining": None,
"provider-b runway_low_warning": False,
"provider-b runway_note": None,
}
balance, burn, runway, low, note = tui._quota_lead_values(rows)
assert balance == 20.0
assert burn is None
assert runway is None
def test_quota_lead_values_returns_none_for_all_telemetry_providers():
rows = {
"neuralwatt balance_usd": -0.004,
"neuralwatt balance_source": "telemetry",
"neuralwatt burn_rate_usd_per_hour": 1.0,
"neuralwatt projected_hours_remaining": -0.004,
"neuralwatt runway_low_warning": True,
"neuralwatt runway_note": "negative allowance",
}
assert tui._quota_lead_values(rows) == (None, None, None, False, None)
def test_quota_lead_values_returns_none_for_old_flat_payload():
rows = {
"balance_usd": 8.5,
"burn_rate_usd_per_hour": 1.0,
"projected_hours_remaining": 8.5,
"runway_low_warning": False,
"runway_note": None,
}
assert tui._quota_lead_values(rows) == (None, None, None, False, None)
def test_quota_lead_values_sorted_name_tiebreak():
rows = {
"openrouter balance_usd": 50.0,
"openrouter balance_source": "polled",
"openrouter burn_rate_usd_per_hour": 1.0,
"openrouter projected_hours_remaining": 5.0,
"openrouter runway_low_warning": False,
"openrouter runway_note": None,
"provider-a balance_usd": 50.0,
"provider-a balance_source": "polled",
"provider-a burn_rate_usd_per_hour": 1.0,
"provider-a projected_hours_remaining": 5.0,
"provider-a runway_low_warning": False,
"provider-a runway_note": None,
}
balance, burn, runway, low, note = tui._quota_lead_values(rows)
assert balance == 50.0
assert runway == 5.0
out = tui._provider_quota_rows(rows)
assert len(out) == 2
assert out[0].startswith("neuralwatt:")
assert "overage allowance" in out[0]
assert out[1].startswith("openrouter:")
assert "credits" in out[1]
def test_quota_panel_burn_unavailable_renders_note_verbatim():
"""When burn is None the note renders VERBATIM — never blank, 0, or None.
This is the state the live deployment is in right now, so it is the first
thing an operator sees, not an edge case.
"""
def test_quota_panel_lead_is_kwh_only_and_note_empty():
"""The quota lead is the kWh-plan fraction; the note line stays hidden."""
stub = _StubFetcher()
data = _fixture()
note = "burn estimate unavailable: segment after last balance increase spans only 9 minutes"
for block in (data["quota"], data["coverage"]["quota"]):
block["by_provider"]["neuralwatt"]["balance_source"] = "polled"
block["by_provider"]["neuralwatt"]["balance_usd"] = 7.5
block["by_provider"]["neuralwatt"]["burn_rate_usd_per_hour"] = None
block["by_provider"]["neuralwatt"]["projected_hours_remaining"] = None
block["by_provider"]["neuralwatt"]["runway_low_warning"] = False
block["by_provider"]["neuralwatt"]["runway_note"] = note
stub.payload = data
app = tui.DashboardApp(fetcher=stub)
def _assert(a):
lead_text = str(a.query_one("#quota-lead", Static).content)
note_text = str(a.query_one("#quota-note", Static).content)
assert "balance $7.50 left" in lead_text
assert "burn n/a" in lead_text
assert "20% of 6.25 kWh plan" in lead_text
assert "None" not in lead_text
# The note is passed through unchanged, not summarised.
assert note_text == note
assert note_text.strip() != ""
assert note_text.strip() != "0"
assert note_text.strip() != "None"
_run_app(app, _assert)
def test_quota_panel_note_hidden_when_burn_is_available():
"""With a real burn rate there is nothing to explain, so no note line."""
stub = _StubFetcher()
data = _fixture()
for block in (data["quota"], data["coverage"]["quota"]):
block["by_provider"]["neuralwatt"]["balance_source"] = "polled"
block["by_provider"]["neuralwatt"]["balance_usd"] = 12.0
block["by_provider"]["neuralwatt"]["burn_rate_usd_per_hour"] = 0.5
block["by_provider"]["neuralwatt"]["projected_hours_remaining"] = 24.0
block["by_provider"]["neuralwatt"]["runway_low_warning"] = False
block["by_provider"]["neuralwatt"]["runway_note"] = None
stub.payload = data
app = tui.DashboardApp(fetcher=stub)
def _assert(a):
lead_text = str(a.query_one("#quota-lead", Static).content)
assert "balance $12.00 left" in lead_text
assert "burn $0.50/h" in lead_text
assert "runway ~24.0h" in lead_text
assert a.query_one("#quota-note", Static).display is False
_run_app(app, _assert)
def test_quota_legend_includes_provider_account_rows():
"""The TUI quota legend lists one labeled row per provider after the kWh
summary line."""
stub = _StubFetcher()
data = _fixture()
stub.payload = data
app = tui.DashboardApp(fetcher=stub)
def _assert(a):
legend = str(a.query_one("#quota-legend", Static).content)
assert "neuralwatt:" in legend
assert "overage allowance" in legend
assert "None" not in legend
_run_app(app, _assert)
def test_quota_legend_lists_polled_provider_with_credits_label():
"""A polled provider row shows the credits label, distinct from a
telemetry provider's overage-allowance label."""
stub = _StubFetcher()
data = _fixture()
for block in (data["quota"], data["coverage"]["quota"]):
block["by_provider"]["openrouter"] = {
"balance_usd": 4.999,
"balance_at": "2026-08-23T10:00:00+00:00",
"balance_source": "polled",
"burn_rate_usd_per_hour": None,
"projected_hours_remaining": None,
"runway_low_warning": False,
"runway_note": None,
}
stub.payload = data
app = tui.DashboardApp(fetcher=stub)
def _assert(a):
legend = str(a.query_one("#quota-legend", Static).content)
assert "openrouter:" in legend
assert "credits" in legend
_run_app(app, _assert)
def test_format_small_balance_handles_boundary_and_zero():
"""Exactly ±0.005 renders conventionally (not tiny); zero renders $0.00."""
assert tui._format_small_balance(0.005) == "$0.01"
assert tui._format_small_balance(-0.005) == "$-0.01"
assert tui._format_small_balance(0.0) == "$0.00"
def test_format_provider_quota_row_includes_balance_at_when_present():
rows = {
"openrouter balance_usd": 4.999,
"openrouter balance_source": "polled",
"openrouter balance_at": "2026-08-23T10:00:00+00:00",
"openrouter burn_rate_usd_per_hour": None,
"openrouter projected_hours_remaining": None,
}
row = tui._format_provider_quota_row("openrouter", rows)
assert "openrouter: $5.00 credits" in row
assert "@2026-08-23T10:00:00+00:00" in row
def test_format_provider_quota_row_unknown_source_prefixes_account():
rows = {
"provider-x balance_usd": 3.25,
"provider-x balance_source": "",
"provider-x balance_at": None,
"provider-x burn_rate_usd_per_hour": None,
"provider-x projected_hours_remaining": None,
}
row = tui._format_provider_quota_row("provider-x", rows)
assert "provider-x: $3.25 account" in row