93 lines
3.7 KiB
Python
93 lines
3.7 KiB
Python
"""把评测机一局的日志归档到 ``examples/replay/``,供无 referee-data 时复现插图。
|
|
|
|
``referee-data/`` 与 ``output/`` 都在 .gitignore 里:本机清掉评测机数据后,
|
|
``docs/figures/*.png`` 就无法复现。本脚本把"作图用到的那几局"抽出来,
|
|
命名成 ``<case_code>.<module>.jsonl`` 存进 ``examples/replay/``(入库、几万行以内),
|
|
``scripts/build_figure_data.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 build_figure_data 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())
|