Files
mcm-26-b/scripts/archive_replay_logs.py
T
2026-09-13 08:52:23 +08:00

93 lines
3.7 KiB
Python

"""把评测机一局的日志归档到 ``examples/replay/``,供无 referee-data 时复现插图。
``referee-data/`` 与 ``output/`` 都在 .gitignore 里:本机清掉评测机数据后,
``docs/figures/*.png`` 就无法复现。本脚本把"作图用到的那几局"抽出来,
命名成 ``<case_code>.<module>.jsonl`` 存进 ``examples/replay/``(入库、几万行以内),
``scripts/make_figures.py`` 在没有评测机数据时自动改读这里。
uv run python scripts/archive_replay_logs.py # 归档作图默认选中的两局
uv run python scripts/archive_replay_logs.py BBM9-HZG2-77N5-NKKC # 指定案例
uv run python scripts/archive_replay_logs.py --list # 只看有哪些局
归档是**内容等价**的:只做 JSON 重新格式化(逐行 compact 输出),不改字段,
并在文件头写入一条 ``session_start``(评测机日志本就自带),保持解析兼容。
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SESSIONS_DIR = ROOT / 'referee-data' / 'sessions'
ARCHIVE_DIR = ROOT / 'examples' / 'replay'
sys.path.insert(0, str(Path(__file__).resolve().parent))
from make_figures import discover_runs, pick_run # noqa: E402
def archive(run: dict, out_dir: Path) -> Path:
"""把一局日志写成归档 JSONL:逐行原样紧凑输出(字段不变,只重新格式化)。"""
out_dir.mkdir(parents=True, exist_ok=True)
target = out_dir / f"{run['case_code']}.{run['module']}.jsonl"
lines: list[str] = []
for line in run["source"].read_text(encoding='utf-8').splitlines():
line = line.strip()
if not line:
continue
try:
doc = json.loads(line)
except json.JSONDecodeError:
continue
lines.append(json.dumps(doc, ensure_ascii=False, separators=(',', ':')))
target.write_text('\n'.join(lines) + '\n', encoding='utf-8')
return target
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument('cases', nargs='*', help='案例编码;缺省=作图默认选中的那几局')
parser.add_argument('--list', action='store_true', help='列出可选案例后退出')
parser.add_argument('--out', default=str(ARCHIVE_DIR))
args = parser.parse_args()
runs = discover_runs()
if not runs:
print('没有找到可归档的日志:referee-data/sessions/*/log.jsonl 与 '
'examples/replay/*.jsonl 都为空。')
return 1
if args.list:
for run in runs:
clears = sum(1 for a in run["actions"] if a["path"] == "/clear")
print(f"{run['case_code']} {run['module']:<12} 真值 {len(run['truth']):2d} "
f"动作 {len(run['actions']):4d} 清除 {clears:3d}")
return 0
picked = []
if args.cases:
wanted = set(args.cases)
picked = [r for r in runs if r["case_code"] in wanted]
missing = wanted - {r["case_code"] for r in picked}
if missing:
print(f'未找到案例:{sorted(missing)}')
return 1
else:
for module, truth in (('q3', 10), ('q4', 12)):
run = pick_run(runs, module=module, truth=truth)
if run and run not in picked:
picked.append(run)
for run in picked:
path = archive(run, Path(args.out))
print(f"[archive] {run['case_code']:<20} {run['module']:<12} "
f"真值 {len(run['truth']):2d} 动作 {len(run['actions']):4d} → "
f"{path.relative_to(ROOT)}")
return 0
if __name__ == '__main__':
raise SystemExit(main())