mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-10-15 12:14:54 +00:00
fix(ui): per-key 链复用全局 AUTO 画布;统计周期显示实际窗口
两件事。 一、per-key AUTO 链的 UI 之前自己写了一套表格,只有 model + tier 两列, 比全局 AUTO 编辑器弱:没有拖拽排序、没有每槽位的 quota/period/hours。 这是重复实现且必然漂移。改为复用同一套泳道画布: - 抽出 buildSortIndex / buildLanes / lanesToRules / renderSortEditor, 全局编辑器与 per-key 编辑器共用。 - sortState.scope 决定保存目标(null=全局,key=该密钥),persistAuto 按此分支;空 lane 列表即"恢复继承",与后端空 PUT 等价。 - 画布查找走 sortCanvasEl(),两个 id 都能解析,否则拖拽在弹窗里静默失效。 - keyAutoClose 必须清 scope,否则关掉弹窗后全局编辑器会存到这个 key。 - 删掉旧的 keyAutoRow/keyAutoAddRow/keyAutoClear。 判据改为断言"复用"而非断言具体实现,并新增两条: - 关闭弹窗不清 scope(全局编辑器会存错目标) - per-key 编辑器不再挂载共享画布 二、"周视图总量大于月视图"不是 bug:日历周期并非嵌套。"本周"从周一开始, 所以月初会回溯到上月末几天,本周总量合法地大于本月。实测今天 2026-10-03 (周六):周窗口 09-28→10-03(6 天,9.2B tokens),月窗口 10-01→10-03 (3 天,4.0B tokens)。 真正的缺陷是看不见这一点。统计页新增"统计区间:YYYY-MM-DD → YYYY-MM-DD (Nd)",让周 > 月 自解释。判据钉住这个事实,并做变异验证:把周窗口钳制到 月内(一个看起来很自然的"修复")会被两条判据同时抓住。 CDP 实测:周视图显示"2026-09-28 → 2026-10-03 (6d)",月视图显示 "2026-10-01 → 2026-10-03 (3d)";per-key 编辑器渲染出 36 条泳道 / 226 个 块,与全局编辑器同款。 Co-Authored-By: ModelRouter <noreply@modelrouter.dev>
This commit is contained in:
@ -319,3 +319,85 @@ func TestPeriodFromQuery(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestPeriodWeekMayStartBeforeMonth pins a fact that looks like a bug and
|
||||
// gets reported as one: the calendar periods are NOT nested.
|
||||
//
|
||||
// On 2026-10-03 (a Saturday) the ISO week runs Mon 2026-09-28 .. Sat
|
||||
// 10-03, while the month window is 10-01 .. 10-03. The week therefore reaches
|
||||
// back into the previous month and its total is legitimately LARGER than the
|
||||
// month's. Anyone reading only the totals will conclude the aggregator is
|
||||
// broken; it is not, and this test exists so a future "fix" that makes the
|
||||
// windows nested (e.g. clamping the week to the month) is caught as the
|
||||
// regression it would be.
|
||||
func TestPeriodWeekMayStartBeforeMonth(t *testing.T) {
|
||||
// Saturday 2026-10-03, UTC.
|
||||
now := time.Date(2026, 10, 3, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
weekFrom := periodStart(PeriodWeek, now)
|
||||
monthFrom := periodStart(PeriodMonth, now)
|
||||
|
||||
weekFromD := time.UnixMilli(weekFrom).UTC()
|
||||
monthFromD := time.UnixMilli(monthFrom).UTC()
|
||||
|
||||
if got := weekFromD.Format("2006-01-02"); got != "2026-09-28" {
|
||||
t.Fatalf("week window start = %s, want 2026-09-28 (the Monday)", got)
|
||||
}
|
||||
if got := monthFromD.Format("2006-01-02"); got != "2026-10-01" {
|
||||
t.Fatalf("month window start = %s, want 2026-10-01", got)
|
||||
}
|
||||
if !weekFromD.Before(monthFromD) {
|
||||
t.Fatal("this test only means anything when the week starts before the month")
|
||||
}
|
||||
// The week must reach strictly further back than the month window does,
|
||||
// otherwise the nesting intuition would hold and the UI note would be noise.
|
||||
if back := monthFromD.Sub(weekFromD).Hours() / 24; back <= 0 {
|
||||
t.Fatalf("week should start at least a day before the month window, got %v days back", back)
|
||||
}
|
||||
}
|
||||
|
||||
// The same overlap must be visible in the aggregate numbers, not just in the
|
||||
// window bounds: a week total larger than a month total is the exact symptom
|
||||
// an operator reported.
|
||||
func TestPeriodWeekTotalCanExceedMonthTotal(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
audit := filepath.Join(dir, "audit.jsonl")
|
||||
|
||||
// Two records before the month window (inside this ISO week) and one
|
||||
// inside October.
|
||||
pre := time.Date(2026, 9, 29, 10, 0, 0, 0, time.UTC).UnixMilli()
|
||||
in1 := time.Date(2026, 10, 1, 10, 0, 0, 0, time.UTC).UnixMilli()
|
||||
in2 := time.Date(2026, 10, 3, 10, 0, 0, 0, time.UTC).UnixMilli()
|
||||
|
||||
auditPeriod(t, audit,
|
||||
Req{Time: pre, OK: true, Prompt: 100, Compl: 10},
|
||||
Req{Time: pre + 1000, OK: true, Prompt: 100, Compl: 10},
|
||||
Req{Time: in1, OK: true, Prompt: 50, Compl: 5},
|
||||
Req{Time: in2, OK: true, Prompt: 50, Compl: 5},
|
||||
)
|
||||
|
||||
s := periodStats(t, audit)
|
||||
now := time.Date(2026, 10, 3, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
week := s.PeriodSnapshot(PeriodWeek, "", now)
|
||||
month := s.PeriodSnapshot(PeriodMonth, "", now)
|
||||
|
||||
if week.Total.Reqs != 4 {
|
||||
t.Fatalf("week reqs = %d, want 4 (both September records fall in this ISO week)", week.Total.Reqs)
|
||||
}
|
||||
if month.Total.Reqs != 2 {
|
||||
t.Fatalf("month reqs = %d, want 2", month.Total.Reqs)
|
||||
}
|
||||
if week.Total.Tokens <= month.Total.Tokens {
|
||||
t.Fatalf("week tokens (%d) must exceed month tokens (%d): the week reaches into September",
|
||||
week.Total.Tokens, month.Total.Tokens)
|
||||
}
|
||||
// Day must be a strict subset of both, which is the only nesting that holds.
|
||||
day := s.PeriodSnapshot(PeriodDay, "", now)
|
||||
if day.Total.Reqs != 1 {
|
||||
t.Fatalf("day reqs = %d, want 1", day.Total.Reqs)
|
||||
}
|
||||
if day.Total.Tokens >= month.Total.Tokens || day.Total.Tokens >= week.Total.Tokens {
|
||||
t.Fatal("today must be contained in both the week and the month totals")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user