aps-agent/server/state/packs.py

87 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================================
# 数据包(moduleId: state-packs, 可重生 ✅)
# 把「一套完整世界数据」封装为可重放的 JSON 包:
# - 演示厂 = server/data/packs/demo.json(由脚本从种子生成)
# - 行业种子 / 客户现场数据都可做成包,data.reset 即「重放数据包」
# 日期重基:包内记录 baseDate(生成日),加载时把所有 YYYY-MM-DD
# 字面日期整体平移到「今天」,保证任意运行日行为一致(黄金测试稳定)。
# ============================================================
from __future__ import annotations
import json
import os
import re
from datetime import datetime, timedelta
from typing import Any
PACKS_DIR = os.path.join("server", "data", "packs")
_DATE_RE = re.compile(r"^(\d{4}-\d{2}-\d{2})")
def export_pack(world: dict[str, Any], path: str, *, name: str = "", description: str = "") -> None:
"""把世界数据导出为数据包(含日期基准,供加载时重基)。"""
pack = {
"packVersion": 1,
"name": name or os.path.splitext(os.path.basename(path))[0],
"description": description,
"baseDate": datetime.now().strftime("%Y-%m-%d"),
"world": world,
}
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
json.dump(pack, f, ensure_ascii=False, indent=1)
def _shift_dates(obj: Any, delta_days: int) -> Any:
"""递归平移所有 YYYY-MM-DD 开头的字符串日期。"""
if delta_days == 0:
return obj
if isinstance(obj, dict):
return {k: _shift_dates(v, delta_days) for k, v in obj.items()}
if isinstance(obj, list):
return [_shift_dates(v, delta_days) for v in obj]
if isinstance(obj, str):
m = _DATE_RE.match(obj)
if m:
try:
d = datetime.strptime(m.group(1), "%Y-%m-%d") + timedelta(days=delta_days)
return d.strftime("%Y-%m-%d") + obj[10:]
except ValueError:
return obj
return obj
def load_pack(path: str) -> dict[str, Any]:
"""读取数据包并把日期重基到今天,返回 world dict。"""
with open(path, "r", encoding="utf-8") as f:
pack = json.load(f)
world = pack.get("world") or {}
base = pack.get("baseDate")
if base:
try:
delta = (datetime.now().date() - datetime.strptime(base, "%Y-%m-%d").date()).days
world = _shift_dates(world, delta)
except ValueError:
pass
return world
def list_packs() -> list[dict[str, Any]]:
"""列出可用数据包(名称/描述/路径)。"""
out: list[dict[str, Any]] = []
if not os.path.isdir(PACKS_DIR):
return out
for fn in sorted(os.listdir(PACKS_DIR)):
if not fn.endswith(".json"):
continue
path = os.path.join(PACKS_DIR, fn)
try:
with open(path, "r", encoding="utf-8") as f:
head = json.load(f)
out.append({"file": fn, "name": head.get("name") or fn,
"description": head.get("description") or "",
"baseDate": head.get("baseDate") or "", "path": path})
except (json.JSONDecodeError, OSError):
continue
return out