#!/usr/bin/env python3 """Snapshot a book's character-library records to a JSON file for later comparison. python3 scripts/backup_characters.py "" characters_auto.json python3 scripts/backup_characters.py "" characters_manual.json --base-url http://127.0.0.1:7890 Matches records the same way the app does: exact `book` field OR `book` present in the comma-separated `tags` field (case-insensitive) — this mirrors `_characters_for_title` in routes/reader.py / clGetAllByTagOrBook in characters-library.js, so the snapshot contains exactly what the UI would show for that book. Run this once right after "Define Characters" (step 5) for each version you want to compare — e.g. once for the automatic pass, once for the manually corrected KONTROLLE version — before either one gets overwritten by a later run. compare_characters.py then diffs two such snapshots. """ import argparse import json import sys import requests def characters_for_title(all_chars: list[dict], title: str) -> list[dict]: key = title.strip().lower() out = [] for rec in all_chars: book = str(rec.get("book") or "").strip().lower() tags = [t.strip().lower() for t in str(rec.get("tags") or "").split(",")] if book == key or key in tags: out.append(rec) return out def main() -> None: ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("book", help="Book title as it appears in the character's `book` field / tags") ap.add_argument("outfile", help="Path to write the JSON snapshot to") ap.add_argument("--base-url", default="http://127.0.0.1:7890") args = ap.parse_args() resp = requests.get(f"{args.base_url}/api/characters", timeout=60) resp.raise_for_status() all_chars = resp.json() if isinstance(all_chars, dict): all_chars = all_chars.get("characters") or all_chars.get("items") or [] matched = characters_for_title(all_chars, args.book) if not matched: print(f"No characters found for book {args.book!r}. Known books: " f"{sorted({str(c.get('book') or '') for c in all_chars} - {''})}", file=sys.stderr) sys.exit(1) matched.sort(key=lambda r: str(r.get("name") or "").lower()) with open(args.outfile, "w", encoding="utf-8") as f: json.dump(matched, f, ensure_ascii=False, indent=2) print(f"Wrote {len(matched)} characters for {args.book!r} -> {args.outfile}") if __name__ == "__main__": main()