ccus-cli 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -411,8 +411,11 @@ function buildSevenDayCurveFromEvents(events) {
411
411
  }
412
412
  /**
413
413
  * 同一 personKey 的 7d 累计曲线:把该人**所有 bundle**(非仅 winner)的 rawEvents 计算成事件、
414
- * 取非 null `sevenDayUsagePct`、按 timestamp 升序合并、对完全相同 timestamp 去重,再做读数去毛刺,
415
- * 得到一条账号级曲线。
414
+ * 取非 null `sevenDayUsagePct`、按 timestamp 升序合并、对完全相同 timestamp 去重,再做读数去毛刺。
415
+ *
416
+ * Claude 与 Codex 是两个不同额度池的独立曲线(读数水平常常相差悬殊),按 source 各自建一条,
417
+ * 算累计时分别做分段峰谷和再相加。混成一条会让低位源频繁触发假 reset、把高位源的上升段反复重算,
418
+ * 导致累计严重虚高(实测 jizhiqiang 混算 221% vs 分源相加 44%)。
416
419
  *
417
420
  * 走全样本而非 winner,是因为累计指标是同一条共享额度曲线的密集采样:只取 winner 会漏掉非 winner
418
421
  * 机器的样本(曲线稀疏、累计偏小),分机各自累计再相加又会翻倍。结果按 bundles 数组缓存复用。
@@ -423,27 +426,31 @@ function buildPersonSevenDayCurve(bundles) {
423
426
  if (cached) {
424
427
  return cached;
425
428
  }
426
- const collected = new Map();
429
+ const claudeByPerson = new Map();
430
+ const codexByPerson = new Map();
427
431
  for (const { bundle } of bundles) {
428
432
  const personKey = bundlePersonKey(bundle);
429
- // 含 Claude + Codex 两源读数:codex 7d 读数不再过滤,并入同一条曲线算累计
430
433
  for (const record of bundle.rawEvents.filter((r) => isPersistedStatuslineEvent(r))) {
431
434
  const event = (0, payload_1.computeStatuslineEvent)(record);
432
435
  if (event.sevenDayUsagePct === null) {
433
436
  continue;
434
437
  }
435
- const list = collected.get(personKey);
438
+ const target = (0, payload_1.isCodexSourceEvent)(event) ? codexByPerson : claudeByPerson;
439
+ const list = target.get(personKey);
436
440
  if (list) {
437
441
  list.push(event);
438
442
  }
439
443
  else {
440
- collected.set(personKey, [event]);
444
+ target.set(personKey, [event]);
441
445
  }
442
446
  }
443
447
  }
444
448
  const curves = new Map();
445
- for (const [personKey, events] of collected.entries()) {
446
- curves.set(personKey, buildSevenDayCurveFromEvents(events));
449
+ for (const personKey of new Set([...claudeByPerson.keys(), ...codexByPerson.keys()])) {
450
+ curves.set(personKey, {
451
+ claude: buildSevenDayCurveFromEvents(claudeByPerson.get(personKey) ?? []),
452
+ codex: buildSevenDayCurveFromEvents(codexByPerson.get(personKey) ?? []),
453
+ });
447
454
  }
448
455
  personSevenDayCurveCache.set(bundles, curves);
449
456
  return curves;
@@ -456,6 +463,18 @@ function sliceCurveByDate(curve, date) {
456
463
  function sliceCurveByWeek(curve, week) {
457
464
  return curve.filter((event) => weekKey(new Date(event.timestamp)) === week);
458
465
  }
466
+ /**
467
+ * 分源 7d 累计:Claude 与 Codex 各自按区间切片做分段峰谷和,再用 `addNullable` 相加。
468
+ * 两源是不同额度池的独立曲线,混算会互相触发假 reset 而虚高;分源算后相加,与 CSV 里 token /
469
+ * 消息数「Claude+Codex 合计」口径一致。该 personKey 无任何曲线、或两源都无有效样本时返回 null。
470
+ */
471
+ function computeCumulativeSevenDayBySource(curves, personKey, slicer, rangeKey) {
472
+ const bySource = curves.get(personKey);
473
+ if (!bySource) {
474
+ return null;
475
+ }
476
+ return addNullable(computeCumulativeSevenDay(slicer(bySource.claude, rangeKey)), computeCumulativeSevenDay(slicer(bySource.codex, rangeKey)));
477
+ }
459
478
  /** 从一组事件按真实时间戳重算 5h / 7d 的 peak(max)与 latest(时间戳最新非空)。 */
460
479
  function recomputeUsage(events) {
461
480
  const newestFirst = [...events].sort((left, right) => new Date(right.timestamp).getTime() - new Date(left.timestamp).getTime());
@@ -526,8 +545,8 @@ function buildAggregatedDailyRows(bundles) {
526
545
  const codexUsage = recomputeUsage(codexEvents);
527
546
  // 额度回退:rawEvents 缺时从 daySummary 取(Claude+Codex 合并,见 fallbackWeeklyUsage)
528
547
  const fallback = fallbackWeeklyUsage(reps.map((r) => r.day));
529
- // 累计指标走全样本合并曲线,不走单机的 recomputeUsage,避免漏掉另一台机器的样本。
530
- const sevenDayCumulativeUsagePct = computeCumulativeSevenDay(sliceCurveByDate(curves.get(personKey) ?? [], date));
548
+ // 累计指标走全样本分源曲线(Claude + Codex 各自切片累计后相加),不走单机的 recomputeUsage,避免漏掉另一台机器的样本。
549
+ const sevenDayCumulativeUsagePct = computeCumulativeSevenDayBySource(curves, personKey, sliceCurveByDate, date);
531
550
  rows.push({
532
551
  personKey,
533
552
  date,
@@ -621,8 +640,8 @@ function buildAggregatedWeeklyRows(bundles) {
621
640
  const usage = recomputeUsage(claudeEvents);
622
641
  const codexUsage = recomputeUsage(codexEvents);
623
642
  const fallback = fallbackWeeklyUsage(acc.days);
624
- // 整周累计:在整周合并曲线上一次性求正增量,跨天边界增量被计入,故 weekly ≥ Σ daily。
625
- const sevenDayCumulativeUsagePct = computeCumulativeSevenDay(sliceCurveByWeek(curves.get(acc.personKey) ?? [], acc.week));
643
+ // 整周累计:Claude + Codex 各自在整周子曲线上做分段峰谷和后相加,跨天边界增量被计入,故 weekly ≥ Σ daily。
644
+ const sevenDayCumulativeUsagePct = computeCumulativeSevenDayBySource(curves, acc.personKey, sliceCurveByWeek, acc.week);
626
645
  rows.push({
627
646
  personKey: acc.personKey,
628
647
  week: acc.week,
@@ -34,8 +34,8 @@ function summarizeEvents(events) {
34
34
  const codexByDesc = events.filter(payload_1.isCodexSourceEvent).sort(byDesc);
35
35
  const latestUsagePct = (0, aggregate_1.addNullable)(claudeByDesc.find((event) => event.usagePct !== null)?.usagePct ?? null, codexByDesc.find((event) => event.usagePct !== null)?.usagePct ?? null);
36
36
  const latestSevenDayUsagePct = (0, aggregate_1.addNullable)(claudeByDesc.find((event) => event.sevenDayUsagePct !== null)?.sevenDayUsagePct ?? null, codexByDesc.find((event) => event.sevenDayUsagePct !== null)?.sevenDayUsagePct ?? null);
37
- // 7d 分区叠加累计:与 aggregate 同一套「去毛刺 + 分段峰谷和」,口径与团队看板一致。
38
- const sevenDayCumulativeUsagePct = (0, aggregate_1.computeCumulativeSevenDay)((0, aggregate_1.buildSevenDayCurveFromEvents)(events));
37
+ // 7d 分源累计:Claude + Codex 各自「去毛刺 + 分段峰谷和」后相加,与 aggregate 同口径(混算会虚高)。
38
+ const sevenDayCumulativeUsagePct = (0, aggregate_1.addNullable)((0, aggregate_1.computeCumulativeSevenDay)((0, aggregate_1.buildSevenDayCurveFromEvents)(events.filter((event) => !(0, payload_1.isCodexSourceEvent)(event)))), (0, aggregate_1.computeCumulativeSevenDay)((0, aggregate_1.buildSevenDayCurveFromEvents)(events.filter(payload_1.isCodexSourceEvent))));
39
39
  return {
40
40
  fiveHourLatestUsagePct: latestUsagePct,
41
41
  fiveHourPeakUsagePct: usages.length > 0 ? (0, time_1.roundNumber)(Math.max(...usages), 1) : null,
@@ -79,8 +79,9 @@ function bucketizeEvents(events, start, end, bucketMinutes = 5) {
79
79
  }
80
80
  // 7d 累计曲线(去毛刺 + 分段峰谷和的逐点版本)映射到时间桶:曲线已按时间升序,
81
81
  // 同桶内后写覆盖前写,最终每个桶留下落在其区间内最后一个累计点的值(单调,等于桶内最大累计)。
82
+ // 只画 Claude 源:折线图是 Claude 趋势看板(与同图 5h/7d 两条线同源),Codex 累计已并入顶部 summary 数值。
82
83
  const cumulativeByBucket = new Map();
83
- for (const point of (0, aggregate_1.computeCumulativeSevenDayCurve)((0, aggregate_1.buildSevenDayCurveFromEvents)(events))) {
84
+ for (const point of (0, aggregate_1.computeCumulativeSevenDayCurve)((0, aggregate_1.buildSevenDayCurveFromEvents)(events.filter((event) => !(0, payload_1.isCodexSourceEvent)(event))))) {
84
85
  const ts = new Date(point.timestamp).getTime();
85
86
  if (ts < start.getTime() || ts > end.getTime()) {
86
87
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccus-cli",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Claude Code statusline usage logger and dashboard CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {