diff --git a/admin/frontend/index.html b/admin/frontend/index.html
index d5f6b5b..d1e6500 100644
--- a/admin/frontend/index.html
+++ b/admin/frontend/index.html
@@ -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 `
+ const accent = low ? 'var(--tblr-danger)' : 'var(--tblr-secondary)';
+ return `
${escapeHtml(name)}
- ${lineParts.join(' · ')}${suffix ? ' · ' + escapeHtml(suffix) + '' : ''}
+ ${formatProviderBalance(name, p)}
`;
}).join('');
}
- let totalRow = '';
- if (totalBalance != null && typeof totalBalance === 'number') {
- totalRow = `
- Total
- $${totalBalance.toFixed(2)}
-
`;
- }
-
el.innerHTML = `
${displayPct}%
@@ -725,15 +698,14 @@ function renderQuotaModal(quota) {
Reset${quota.next_reset_date || 'not configured'}
${providers.length ? `
-
Per-provider balance & runway
+
Provider accounts
${providerRowsHtml}
- ${totalRow}
-
` : ''}
+
` : 'No provider accounts configured
'}
${escapeHtml(quota.note || '')}
`;
}
-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) {
diff --git a/src/tui.py b/src/tui.py
index 6338fdd..f906e1c 100644
--- a/src/tui.py
+++ b/src/tui.py
@@ -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")
diff --git a/src/tui_model.py b/src/tui_model.py
index e682fe4..2d7a076 100644
--- a/src/tui_model.py
+++ b/src/tui_model.py
@@ -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(
{
diff --git a/tests/test_admin_frontend.py b/tests/test_admin_frontend.py
index f78609a..cce40c8 100644
--- a/tests/test_admin_frontend.py
+++ b/tests/test_admin_frontend.py
@@ -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):
diff --git a/tests/test_tui.py b/tests/test_tui.py
index 609a4f5..7ac6b77 100644
--- a/tests/test_tui.py
+++ b/tests/test_tui.py
@@ -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