Include scheduled operations usage in weekly reports

This commit is contained in:
wuyang
2026-11-03 22:22:29 +08:00
parent ad86a1661b
commit 830d5b722a
3 changed files with 68 additions and 2 deletions
+47 -1
View File
@@ -117,6 +117,50 @@ def codex_usage(session_root, thread_id, start, end):
'measurement_note': '会话日志计数含重复处理的上下文和缓存;不是唯一文字数、周额度百分比或API账单。推理token包含于输出计数,不重复相加。'}
def scheduled_codex_usage(directory, start, end):
"""Read bounded cycle summaries separately from the interactive thread."""
if not directory or not Path(directory).is_dir():
return {'available': False, 'reason': '定时运营汇总目录未配置或不可用'}
totals = {k: 0 for k in ('input_tokens', 'cached_input_tokens', 'output_tokens', 'reasoning_output_tokens')}
runs = failed = 0
for path in Path(directory).glob('*.summary.json'):
try:
record = json.loads(path.read_text())
stamp = record.get('started_at')
when = dt.datetime.fromisoformat(stamp.replace('Z', '+00:00')) if stamp else dt.datetime.strptime(record['id'][:16], '%Y%m%dT%H%M%SZ').replace(tzinfo=dt.timezone.utc)
if when.tzinfo is None or not start <= when < end:
continue
runs += 1
failed += record.get('status') != 'complete'
for usage in record.get('usage', []):
for k in totals:
totals[k] += max(0, int(usage.get(k, 0)))
except (ValueError, OSError, KeyError, TypeError, AttributeError):
continue
return {'available': True, 'runs': runs, 'failed_runs': failed, **totals,
'measurement_note': '来自独立定时会话的运行汇总,与交互会话分开统计;失败或中断可能没有完整用量。'}
def harness_run_usage(path, start, end):
if not Path(path).exists():
return {'available': False, 'reason': '后续 Harness 调用账本尚未创建'}
result = {'available': True, 'runs': 0, 'input_tokens': 0, 'output_tokens': 0, 'cash_settlement_usd': None}
for line in Path(path).read_text().splitlines():
try:
row = json.loads(line)
when = dt.datetime.fromisoformat(row['at_utc'].replace('Z', '+00:00'))
if when.tzinfo is None or not start <= when < end:
continue
result['runs'] += 1
for usage in row.get('usage') or []:
result['input_tokens'] += max(0, int(usage.get('inputTokens', 0)))
result['output_tokens'] += max(0, int(usage.get('outputTokens', 0)))
except (ValueError, KeyError, TypeError, AttributeError):
continue
result['measurement_note'] = '只统计自动账本创建后的调用;实际结算未知,不与历史快照重复计费。'
return result
def build_report(data_dir, snapshot=False, now=None):
now = now or dt.datetime.now(TZ)
now = now.astimezone(TZ)
@@ -184,8 +228,10 @@ def build_report(data_dir, snapshot=False, now=None):
'project_video_views': 0 if not published else None,
'project_game_sessions': None,
'codex': usage,
'scheduled_codex': scheduled_codex_usage(collector.get('ops_cycle_summaries_dir'), begin_stamp, end_stamp),
'deepseek': api or {'status': '未采集'},
'measurement_notes': ['只统计运营账本登记稿件,不计入账户其他历史内容。', '当前会话包含PawPlay、运营基础设施和虚拟人设研究,token仅能按该共享会话汇总,未精确归因到单个方向。', '游戏离线运行且未接入统计,不能推导活跃、留存或转化率。', '已发布稿件存在时,播放数据需接入稿件快照采集,当前不填造数值。']
'harness_new_runs': harness_run_usage(data_dir / 'harness-runs.jsonl', begin_stamp, end_stamp),
'measurement_notes': ['只统计运营账本登记稿件,不计入账户其他历史内容。', '当前会话包含PawPlay、运营基础设施和虚拟人设研究,token仅能按该共享会话汇总,未精确归因到单个方向。定时任务作为独立会话分别统计。', '游戏离线运行且未接入统计,不能推导活跃、留存或转化率。', '已发布稿件存在时,播放数据需接入稿件快照采集,当前不填造数值。']
},
'costs': {
'cash_usd': round(sum(e['amount'] for e in costs), 2) if config.get('cash_tracking_complete') else None,