vignette/scripts/materialize-persona-seeds.py
2026-06-28 20:12:35 +09:00

108 lines
3.3 KiB
Python

"""Materialize built-in persona seed versions on demand.
Default mode is a dry run: report the repository persona seed manifest without
touching the database. Use --apply when an operator intentionally wants to run
the existing idempotent DB materializer.
"""
from __future__ import annotations
import argparse
import asyncio
import json
import sys
from pathlib import Path
from typing import Any
REPO_ROOT = Path(__file__).resolve().parents[1]
API_ROOT = REPO_ROOT / "apps" / "api"
if str(API_ROOT) not in sys.path:
sys.path.insert(0, str(API_ROOT))
from app.persona_repository import ( # noqa: E402
SEED_VERSION,
built_in_personas,
materialize_seed_personas,
seed_persona_id,
)
from app.db import close_pool, init_pool # noqa: E402
def _manifest_rows() -> list[dict[str, Any]]:
rows: list[dict[str, Any]] = []
for card in built_in_personas():
rows.append(
{
"code": card.code,
"version": SEED_VERSION,
"persona_id": str(seed_persona_id(card.code)),
"display_name": card.display_name,
"difficulty": card.difficulty,
"theory_target": list(card.theory_target),
"source_provenance": card.source_provenance,
"synthetic": bool(card.is_synthetic),
}
)
return rows
def _print_text(*, applied: bool, count: int | None, rows: list[dict[str, Any]]) -> None:
mode = "APPLY" if applied else "DRY-RUN"
print(f"persona seed materializer: {mode}")
print(f"seed_version: {SEED_VERSION}")
print(f"manifest_count: {len(rows)}")
if count is not None:
print(f"materialize_calls: {count}")
for row in rows:
theories = ",".join(row["theory_target"])
print(
f"- {row['code']} v{row['version']} {row['persona_id']} "
f"{row['display_name']} [{theories}]"
)
async def _main_async(args: argparse.Namespace) -> int:
rows = _manifest_rows()
count: int | None = None
if args.apply:
await init_pool()
try:
count = await materialize_seed_personas()
finally:
await close_pool()
payload = {
"applied": bool(args.apply),
"seed_version": SEED_VERSION,
"manifest_count": len(rows),
"materialize_calls": count,
"personas": rows,
}
if args.json:
print(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True))
else:
_print_text(applied=bool(args.apply), count=count, rows=rows)
return 0
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"Report or apply the repository persona seed/version manifest. "
"Dry-run is the default; --apply performs DB writes through the "
"existing idempotent materializer."
)
)
parser.add_argument("--apply", action="store_true", help="write missing seed versions to DB")
parser.add_argument("--json", action="store_true", help="emit machine-readable JSON")
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
args = parse_args(list(argv if argv is not None else sys.argv[1:]))
return asyncio.run(_main_async(args))
if __name__ == "__main__":
raise SystemExit(main())