feat: E1002 六色版改版为 AI 用量看板+Claude 用量新增 Fable 7d 行与重置时刻
- 版面重排:主体左 Claude Usage(5h/7d/Fable 7d 三行),右 ChatGPT Usage(目前 mock,标「示例数据」);每行为一张用量卡:标签+F24 百分比+pace/全宽进度条/「重置 HH:MM」+「已过 NN%」 - 移除上一版的橘喵今日实时与 30 天趋势图及 trend totals 合计(业务数据留给 400×300 小屏),绿色暂不再使用 - data 用量结构新增 scoped(按模型限定额度)、reset(重置时刻文本)、updated(快照落盘时刻),新增 _usage_gpt mock 数据源,接入真实数据时保持结构替换即可 - usage_local 读取 statusline 新增的 seven_day_fable 与 updated_at;400×300 小屏 render 跳过 scoped 行 - 同步更新 CLAUDE.md 分支说明 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
106
data.py
106
data.py
@@ -163,23 +163,26 @@ def _mao(now):
|
||||
except Exception as e:
|
||||
print(f"[警告] 今日经营拉取失败:{e}")
|
||||
cols = [_placeholder_col(k) for k in ("单量", "流水", "毛利")]
|
||||
# 近30天趋势(按日期缓存,每天只拉一次);合计每次现算,不进缓存(缓存结构保持不变)
|
||||
trend = dict(_trend(now))
|
||||
trend["totals"] = _trend_totals(trend)
|
||||
return {"upd": upd, "cols": cols, "trend": trend}
|
||||
# 近30天趋势(按日期缓存,每天只拉一次);宽版另用其最后 7 天做「近7天」数据表
|
||||
trend = _trend(now)
|
||||
return {"upd": upd, "cols": cols, "trend": trend, "week": _recent_days(trend, 7)}
|
||||
|
||||
|
||||
def _trend_totals(trend):
|
||||
"""近30天三指标合计 → [(名,文本)],供宽版(E1002)趋势图标题行展示;无数据返回空列表。
|
||||
单量按千分位计数,流水/毛利按金额紧凑格式(≥1万 显示 X.XX万)。"""
|
||||
out = []
|
||||
for s in trend.get("series") or []:
|
||||
data = s.get("data") or []
|
||||
if not data:
|
||||
def _recent_days(trend, n):
|
||||
"""趋势的最后 n 天 → 表格数据 {dates:[MM/DD…], rows:[(名, [文本…], 合计文本)]},供宽版「橘喵·近7天」表使用。
|
||||
recent-days 不含今日,故表头日期到昨天为止。单量按千分位、流水/毛利按紧凑金额格式;无数据返回空 dict。"""
|
||||
dates, series = trend.get("dates") or [], trend.get("series") or []
|
||||
if not dates or not series:
|
||||
return {}
|
||||
dates = dates[-n:]
|
||||
rows = []
|
||||
for s in series:
|
||||
data = (s.get("data") or [])[-n:]
|
||||
if len(data) != len(dates):
|
||||
continue
|
||||
total = sum(data)
|
||||
out.append((s["k"], _fmt_count(total) if s["k"] == "单量" else _fmt_money(total)))
|
||||
return out
|
||||
fmt = _fmt_count if s["k"] == "单量" else _fmt_money
|
||||
rows.append((s["k"], [fmt(v) for v in data], fmt(sum(data))))
|
||||
return {"dates": dates, "rows": rows}
|
||||
|
||||
|
||||
# ---------- Claude Code 用量 ----------
|
||||
@@ -206,35 +209,75 @@ def _window_time_pct(resets_at, window, now):
|
||||
return int(round((window - remaining) / window * 100))
|
||||
|
||||
|
||||
def _usage(now):
|
||||
"""Claude Code 用量(5h / 7d)——只读本地 statusline 生产的快照(见 usage_local.py),**不发任何请求**。
|
||||
def _fmt_reset(resets_at, window):
|
||||
"""重置时刻 → 文本:≤1 天的窗口只给 HH:MM,更长的窗口带 MM/DD。解析失败返回 None。"""
|
||||
ts = _parse_reset_ts(resets_at)
|
||||
if ts is None:
|
||||
return None
|
||||
dt = datetime.fromtimestamp(ts)
|
||||
return dt.strftime("%H:%M") if window <= 86400 else dt.strftime("%m/%d %H:%M")
|
||||
|
||||
utilization 来自 statusline 从 Claude Code stdin 落盘的权威实时值;
|
||||
|
||||
# Claude 用量窗口:(快照键,行标签,窗口长度秒,scoped)。scoped=True 为「按模型限定」的额度
|
||||
# (Fable 7d,来自 /api/oauth/usage 的 limits[],由 statusline 写入快照 seven_day_fable),
|
||||
# 400x300 三色小屏版面放不下第三行,只在 800x480 宽版展示。
|
||||
USAGE_WINDOWS = (
|
||||
("five_hour", "5h", FIVE_HOUR, False),
|
||||
("seven_day", "7d", SEVEN_DAY, False),
|
||||
("seven_day_fable", "Fable 7d", SEVEN_DAY, True),
|
||||
)
|
||||
|
||||
|
||||
def _usage(now):
|
||||
"""Claude Code 用量(5h / 7d / Fable 7d)——只读本地 statusline 生产的快照(见 usage_local.py),**不发任何请求**。
|
||||
|
||||
utilization 来自 statusline 从 Claude Code stdin 落盘的权威实时值(Fable 行来自其转存的 API limits);
|
||||
time_pct(时间已流逝%) 与 pace 每次按当前时间**现算**(resets_at 为绝对时间)。
|
||||
文件缺失/损坏 → 回退占位 pct=None(renderer 显示 "--")。
|
||||
结构契约(renderer 依赖):{title, warn, bars:[{k, pct(0~100 或 None), time_pct(0~100)?}]};
|
||||
pct ≥ warn 时该条进度条与百分比转红;pace = pct − time_pct(>0 超前↑ / <0 节余↓)。
|
||||
文件缺失/损坏 → 回退占位 pct=None(renderer 显示 "--");快照里没有某窗口(如旧版 statusline 无 Fable)同样 "--"。
|
||||
结构契约(renderer 依赖):{title, warn, caution, updated?, bars:[{k, pct(0~100 或 None), time_pct?, reset?, scoped}]};
|
||||
pct ≥ warn 时该条进度条与百分比转红;pace = pct − time_pct(>0 超前↑ / <0 节余↓);
|
||||
reset 为重置时刻文本;updated 为快照落盘时刻 HH:MM(活跃使用时持续刷新,空闲时停在最后一次)。
|
||||
"""
|
||||
wins = {}
|
||||
raw = {}
|
||||
try:
|
||||
raw = usage_local.get_usage()
|
||||
for key, label, win in (("five_hour", "5h", FIVE_HOUR), ("seven_day", "7d", SEVEN_DAY)):
|
||||
seg = raw.get(key) or {}
|
||||
wins[label] = {"pct": int(round(float(seg.get("utilization", 0)))),
|
||||
"resets_at": seg.get("resets_at"), "win": win}
|
||||
except Exception as e:
|
||||
print(f"[警告] Claude 用量读取失败:{e}")
|
||||
|
||||
bars = []
|
||||
for label in ("5h", "7d"):
|
||||
w = wins.get(label) or {}
|
||||
pct = w.get("pct")
|
||||
bar = {"k": label, "pct": pct}
|
||||
tp = _window_time_pct(w.get("resets_at"), w.get("win"), now)
|
||||
for key, label, win, scoped in USAGE_WINDOWS:
|
||||
seg = raw.get(key) or {}
|
||||
util = seg.get("utilization")
|
||||
pct = int(round(float(util))) if util is not None else None
|
||||
bar = {"k": label, "pct": pct, "scoped": scoped}
|
||||
tp = _window_time_pct(seg.get("resets_at"), win, now)
|
||||
if pct is not None and tp is not None:
|
||||
bar["time_pct"] = tp
|
||||
rs = _fmt_reset(seg.get("resets_at"), win)
|
||||
if rs:
|
||||
bar["reset"] = rs
|
||||
bars.append(bar)
|
||||
return {"title": "Claude Usage", "warn": config.USAGE_WARN, "caution": config.USAGE_CAUTION, "bars": bars}
|
||||
out = {"title": "Claude Usage", "warn": config.USAGE_WARN, "caution": config.USAGE_CAUTION, "bars": bars}
|
||||
upd = _parse_reset_ts(raw.get("updated_at"))
|
||||
if upd:
|
||||
out["updated"] = datetime.fromtimestamp(upd).strftime("%H:%M")
|
||||
return out
|
||||
|
||||
|
||||
def _usage_gpt(now):
|
||||
"""ChatGPT 用量——**目前为 mock 占位**(mock=True,renderer 在标题右侧标「示例数据」)。
|
||||
结构与 _usage 完全一致,后续接入真实数据源时只需替换本函数、保持返回结构不变。
|
||||
mock 取值刻意覆盖多种配色档:5h 42%(黑)、7d 71%(黄「注意」)、Codex 7d 55%(黑,scoped 与左侧 Fable 行对称)。"""
|
||||
bars = []
|
||||
for label, pct, win, remain, scoped in (("5h", 42, FIVE_HOUR, 2 * 3600, False),
|
||||
("7d", 71, SEVEN_DAY, 3 * 86400, False),
|
||||
("Codex 7d", 55, SEVEN_DAY, 3 * 86400, True)):
|
||||
reset_ts = now.timestamp() + remain
|
||||
bars.append({"k": label, "pct": pct, "scoped": scoped,
|
||||
"time_pct": _window_time_pct(reset_ts, win, now),
|
||||
"reset": _fmt_reset(reset_ts, win)})
|
||||
return {"title": "ChatGPT Usage", "warn": config.USAGE_WARN, "caution": config.USAGE_CAUTION,
|
||||
"bars": bars, "mock": True}
|
||||
|
||||
|
||||
def get_dashboard_data(now=None):
|
||||
@@ -249,4 +292,5 @@ def get_dashboard_data(now=None):
|
||||
"weather": _weather(now),
|
||||
"mao": _mao(now),
|
||||
"usage": _usage(now),
|
||||
"usage_gpt": _usage_gpt(now),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user