feat: project current account rate multipliers

This commit is contained in:
2026-08-05 03:07:21 +08:00
parent 51802d07cf
commit 492e6c882f
5 changed files with 46 additions and 12 deletions
+4 -4
View File
@@ -69,10 +69,10 @@ refresh。组件失败时服务端保留对应 last-good 数据,并在 `compon
Workspace 当前提供以下有界 projection
- `accounts`:账号摘要、quota window、当日用量和 provider/group/status 字段。
- `accounts`:账号摘要、quota window、当日用量和 provider/group/status 字段;如能按当前 server6 upstream key-group binding 关联,还包含 `account_rate_multiplier` 原始有效倍率和 `account_rate_multiplier_cny` 展示倍率
- `status`channel monitor 摘要。
- `sources`Pricing Monitor 已有的脱敏 source 余额与健康状态。
- `traffic.requests`:仅 server6 的最新有界请求样本。
- `traffic.requests`:仅 server6 的最新有界请求样本;每行带对应 account 的当前 `account_rate_multiplier` / `account_rate_multiplier_cny`,无法稳定关联时为 `null`
- `traffic.errors`server6/server4 的有界逐条错误事件,保留节点、时间、key/account、结构化错误类别和状态链路,不做错误分组或 per-row count。
- `traffic.keys`:仅 server6 按 `Asia/Shanghai` 自然日完整聚合的 key 使用量;事件样本上限不会截断其 request/token/cost totalspayload 同时携带统计日期和时区。
@@ -120,8 +120,8 @@ Accounts 只在 canonical 名称 `{family}-quota-{source}` 或
provider、URL、display name 或模糊文本推断;source 为 error/stale 时保留名称和状态,
但不伪造 CNY 数值。
Requests 展示 server6 的最新有界明细:key、account、model、token bucket、actual cost、first-token latency、
duration 和 decode throughput。Errors 展示 server6/server4 的逐条 instance、事件时间、status path、key、
Requests 展示 server6 的最新有界明细:key、account、当前 account multiplier、model、token bucket、actual cost、first-token latency、
duration 和 decode throughput。该 multiplier 来自当前 account binding,不代表请求发生时的历史计费倍率。Errors 展示 server6/server4 的逐条 instance、事件时间、status path、key、
account、model、phase/type/owner,不显示聚合次数。Key usage 和对应 Traffic Account usage 是 server6
完整当天 rollupErrors 与 Requests 仍是事件样本。所有 workspace Traffic 都使用服务器声明的 `Asia/Shanghai` 自然日,不依赖客户端本地日期或滚动
24 小时窗口;每个 Traffic projection 显式携带日期、时区和 `[00:00, 次日 00:00)` 边界。
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "shusub2"
version = "0.3.4"
version = "0.3.5"
description = "Aggregated operations TUI for Sub2API"
readme = "README.md"
requires-python = ">=3.11"
+34 -6
View File
@@ -27,7 +27,7 @@ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
APP_NAME = "shusub2"
FALLBACK_VERSION = "0.3.4"
FALLBACK_VERSION = "0.3.5"
DEFAULT_WORKSPACE_URL = "https://price.tailbeb9ad.ts.net/api/ui-data?view=workspace"
DEFAULT_WORKSPACE_URL_CONFIG_FILE = "~/.config/shusub2/workspace-url"
DEFAULT_API_URL = "http://127.0.0.1:18318/api/tui/accounts"
@@ -392,6 +392,18 @@ def format_cny(value: Any) -> str:
return "-" if amount == "-" else f"¥{amount}"
def format_multiplier(value: Any) -> str:
amount = optional_number(value)
if amount is None:
return "-"
return f"{amount:.8g}x"
def account_rate_value(item: dict[str, Any]) -> float | None:
display = optional_number(item.get("account_rate_multiplier_cny"))
return display if display is not None else optional_number(item.get("account_rate_multiplier"))
def format_count(value: Any) -> str:
number = as_int(value)
if abs(number) >= 1_000_000:
@@ -1172,6 +1184,9 @@ def normalize_log_rows(payload: dict[str, Any], filter_text: str = "") -> list[d
"node": node,
"key": key_name,
"account": account_name,
"account_rate_multiplier": optional_number(item.get("account_rate_multiplier")),
"account_rate_multiplier_cny": optional_number(item.get("account_rate_multiplier_cny")),
"account_multiplier": account_rate_value(item),
"user": user_name,
"model": model,
"upstream_model": upstream_model,
@@ -1225,7 +1240,7 @@ def log_detail_line(row: dict[str, Any]) -> str:
if row["effort"] != "-":
model = f"{model} ({row['effort']})"
detail = (
f"{row['time']} | {row['node']} | key {row['key']} | account {row['account']} | user {row['user']} | {model} | {row['type']} | "
f"{row['time']} | {row['node']} | key {row['key']} | account {row['account']} | multiplier {format_multiplier(row['account_multiplier'])} | user {row['user']} | {model} | {row['type']} | "
f"tokens in {format_count(row['input_tokens'])} out {format_count(row['output_tokens'])} "
f"cache_w {format_count(row['cache_creation_tokens'])} cache_r {format_count(row['cache_read_tokens'])} | "
f"cost {format_cost(row['cost'])} (actual {format_cost(row['actual_cost'])}) | "
@@ -1240,12 +1255,13 @@ def log_detail_line(row: dict[str, Any]) -> str:
def print_logs_once(payload: dict[str, Any], filter_text: str = "") -> None:
rows = normalize_log_rows(payload, filter_text)
print(logs_summary_line(payload, len(rows)))
print("node key account model effort type input output cache tokens cost first duration tok/s time age")
print("node key account multiplier model effort type input output cache tokens cost first duration tok/s time age")
for row in rows:
print(
f"{row['node'][:8]:<9} "
f"{row['key'][:20]:<21} "
f"{row['account'][:20]:<21} "
f"{format_multiplier(row['account_multiplier']):<11} "
f"{row['model'][:24]:<25} "
f"{row['effort']:<7} "
f"{row['type']:<7} "
@@ -1736,6 +1752,9 @@ def normalize_account_rows(
"pricing_balance": pricing_source.get("balance") if pricing_source else None,
"pricing_unit": str(pricing_source.get("unit") or "-") if pricing_source else "-",
"pricing_updated": str(pricing_source.get("updated") or "-") if pricing_source else "-",
"account_rate_multiplier": optional_number(account.get("account_rate_multiplier")),
"account_rate_multiplier_cny": optional_number(account.get("account_rate_multiplier_cny")),
"account_multiplier": account_rate_value(account),
"provider": provider_label(account),
"kind": str(account.get("kind") or "unknown"),
"kind_label": kind_label(account.get("kind")),
@@ -1922,13 +1941,14 @@ def print_once(
print(f"sources: {pricing_error}")
elif pricing_payload:
print(pricing_summary(pricing_payload))
print("name provider group source src cny src state daily today tokens req kind 5h 7d reset status availability")
print("name provider group source multiplier src cny src state daily today tokens req kind 5h 7d reset status availability")
for row in normalize_account_rows(payload, filter_text, pricing_payload):
print(
f"{row['name'][:30]:<30} "
f"{row['provider']:<9} "
f"{row['routing_group']:<6} "
f"{row['pricing_source']:<16} "
f"{format_multiplier(row['account_multiplier']):<11} "
f"{format_cny(row['pricing_cny']):<13} "
f"{row['pricing_state']:<12} "
f"{row['daily_quota_cell']:<10} "
@@ -2078,7 +2098,7 @@ def run_textual(
self.app.sub_title = "Dashboard"
self.configure_table(
self.query_one("#accounts", DataTable),
("ACCOUNT", "Group", "Source", "Src CNY", "Src state", "Today", "Daily", "5h", "7d", "Avail"),
("ACCOUNT", "Group", "Source", "Multiplier", "Src CNY", "Src state", "Today", "Daily", "5h", "7d", "Avail"),
)
self.configure_table(
self.query_one("#keys", DataTable),
@@ -2090,6 +2110,7 @@ def run_textual(
"Node",
"REQUEST KEY",
"Account",
"Multiplier",
"Model",
"First",
"Duration",
@@ -2241,6 +2262,7 @@ def run_textual(
row["name"],
row["routing_group"],
row["pricing_source"],
format_multiplier(row["account_multiplier"]),
format_cny(row["pricing_cny"]),
row["pricing_state"],
format_cost(row["today_cost_usd"]),
@@ -2285,6 +2307,7 @@ def run_textual(
row["node"],
Text(row["key"], style=color) if color else row["key"],
row["account"],
format_multiplier(row["account_multiplier"]),
row["model"],
format_seconds(row["first_token_ms"]),
format_seconds(row["duration_ms"]),
@@ -2418,6 +2441,7 @@ def run_textual(
if table_id == "accounts":
detail = (
f"{row['name']} | {row['provider']} | group {row['routing_group']} | {row['kind_label']} | "
f"multiplier {format_multiplier(row['account_multiplier'])} | "
f"daily {row['daily_quota_cell']} | "
f"5h {row['five_hour']} | "
f"7d {row['weekly']} | "
@@ -2499,7 +2523,7 @@ def run_textual(
table = self.query_one("#accounts", DataTable)
table.cursor_type = "row"
table.zebra_stripes = True
table.add_columns("Name", "Provider", "Group", "Source", "Src CNY", "Src state", "Daily", "Today", "Tokens", "Req", "Kind", "5h", "7d", "Reset", "Status", "Availability")
table.add_columns("Name", "Provider", "Group", "Source", "Multiplier", "Src CNY", "Src state", "Daily", "Today", "Tokens", "Req", "Kind", "5h", "7d", "Reset", "Status", "Availability")
keys_table = self.query_one("#keys", DataTable)
keys_table.cursor_type = "row"
keys_table.zebra_stripes = True
@@ -2597,6 +2621,7 @@ def run_textual(
row["provider"],
row["routing_group"],
row["pricing_source"],
format_multiplier(row["account_multiplier"]),
format_cny(row["pricing_cny"]),
row["pricing_state"],
row["daily_quota_cell"],
@@ -2627,6 +2652,7 @@ def run_textual(
raw = row.get("raw") if isinstance(row.get("raw"), dict) else {}
detail = (
f"{row['name']} | {row['provider']} | group {row['routing_group']} | {row['kind_label']} | {row['account_type']} | "
f"multiplier {format_multiplier(row['account_multiplier'])} | "
f"daily {row['daily_quota_cell']} ({format_percent(row['daily_quota_used_percent'])} used, "
f"{row['daily_quota_remaining'] if row['daily_quota_remaining'] is not None else '-'} left) | "
f"5h {row['five_hour']} | 7d {row['weekly']} | "
@@ -2791,6 +2817,7 @@ def run_textual(
"Node",
"Key",
"Account",
"Multiplier",
"Model",
"Effort",
"Type",
@@ -2865,6 +2892,7 @@ def run_textual(
row["node"],
Text(row["key"], style=color) if color else row["key"],
row["account"],
format_multiplier(row["account_multiplier"]),
row["model"],
row["effort"],
row["type"],
+6
View File
@@ -67,6 +67,8 @@ def workspace_payload_fixture() -> dict[str, object]:
"provider": "openai",
"kind": "quota_limited",
"usable": True,
"account_rate_multiplier": 0.06,
"account_rate_multiplier_cny": 0.003,
}
],
},
@@ -105,6 +107,8 @@ def workspace_payload_fixture() -> dict[str, object]:
"api_key_name": "wmy",
"account_id": "9",
"account_name": "oai-quota-code-plan",
"account_rate_multiplier": 0.06,
"account_rate_multiplier_cny": 0.003,
"model": "gpt-5.5",
"input_tokens": 100,
"output_tokens": 50,
@@ -803,7 +807,9 @@ class WorkspaceTests(unittest.TestCase):
normalized_accounts = mod.normalize_account_rows(accounts, pricing_payload=pricing)
normalized_logs = mod.normalize_log_rows(logs)
self.assertEqual(normalized_accounts[0]["id"], 9007199254740993)
self.assertEqual(normalized_accounts[0]["account_multiplier"], 0.003)
self.assertEqual(normalized_logs[0]["id"], 9007199254740997)
self.assertEqual(normalized_logs[0]["account_multiplier"], 0.003)
self.assertEqual(normalized_logs[0]["cost"], 0.25)
self.assertEqual(mod.as_int("9007199254740999"), 9007199254740999)
self.assertEqual(len(keys["items"]), 1)
Generated
+1 -1
View File
@@ -85,7 +85,7 @@ wheels = [
[[package]]
name = "shusub2"
version = "0.3.4"
version = "0.3.5"
source = { editable = "." }
dependencies = [
{ name = "textual" },