dp-zp-agent/tools/evaluation/report_maintenance.py

97 lines
3.4 KiB
Python

"""Optional historical report migration; production only reads existing journals."""
import hashlib
import json
import shutil
from pathlib import Path
from tools.rag.common import DATA_DIR
from tools.runtime.report_files import CORE_JSON, run_id
def migration_plan(data_dir: Path = DATA_DIR) -> list[dict]:
moves = []
for path in sorted(data_dir.iterdir()):
if not path.is_file() or path.name.startswith(".") or path.name in CORE_JSON:
continue
if path.suffix == ".json":
target = data_dir / "reports/archive/legacy" / path.name
elif path.suffix == ".log":
target = data_dir / "logs/archive/legacy" / path.name
else:
continue
moves.append(
{
"source": str(path.absolute()),
"destination": str(target.absolute()),
"sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
}
)
return moves
def retention_plan(data_dir: Path = DATA_DIR, *, keep: int = 5) -> list[dict]:
if keep < 1:
raise ValueError("Keep at least one verification")
completed = []
for path in (data_dir / "reports/verification").glob("*.json"):
try:
data = json.loads(path.read_text())
if isinstance(data, dict) and type(data.get("passed")) is bool:
completed.append(path)
except (ValueError, OSError):
continue
moves = []
for path in sorted(completed, key=lambda p: (p.stat().st_mtime_ns, p.name), reverse=True)[
keep:
]:
target = data_dir / "reports/archive/verification" / path.name
moves.append(
{
"source": str(path.absolute()),
"destination": str(target.absolute()),
"sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
}
)
log = data_dir / "logs/verification" / path.with_suffix(".pytest.log").name
if log.exists():
target = data_dir / "logs/archive/verification" / log.name
moves.append(
{
"source": str(log.absolute()),
"destination": str(target.absolute()),
"sha256": hashlib.sha256(log.read_bytes()).hexdigest(),
}
)
return moves
def apply_moves(moves: list[dict], *, data_dir: Path = DATA_DIR) -> Path:
# Validate the entire plan before moving anything. Never replace existing files.
for move in moves:
source, destination = Path(move["source"]), Path(move["destination"])
if (
destination.exists()
or hashlib.sha256(source.read_bytes()).hexdigest() != move["sha256"]
):
raise RuntimeError("Report migration conflict; no files moved")
journal = data_dir / "reports/migrations" / (run_id() + ".json")
journal.parent.mkdir(parents=True, exist_ok=True)
record = {
"method": "Move without rewriting content or deleting historical evidence",
"moves": [],
}
def write_journal():
temporary = journal.with_suffix(".tmp")
temporary.write_text(json.dumps(record, indent=2) + "\n")
temporary.replace(journal)
write_journal()
for move in moves:
destination = Path(move["destination"])
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.move(move["source"], destination)
record["moves"].append(move)
write_journal()
return journal