"""Import StudyDaily's re-exported APKG into a fresh official Anki collection and compare every card with the source snapshot taken in Anki. STUDYDAILY_PROOF_OUT=build/migration-proof \ fvm flutter test test/exchange/anki_migration_proof_test.dart python test/fixtures/apkg/migration-proof/verify_roundtrip.py build/migration-proof """ import json import os import re import shutil import sys from anki.collection import Collection, ImportAnkiPackageOptions, ImportAnkiPackageRequest HERE = os.path.dirname(os.path.abspath(__file__)) OUT = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else "build/migration-proof") WORK = os.path.join(OUT, "anki-verify") SOURCE = json.load(open(os.path.join(HERE, "anki-26.9.3-source-snapshot.json"))) REEXPORT = os.path.join(OUT, "studydaily-reexport.apkg") sys.path.insert(0, HERE) from make_source import snapshot_collection # noqa: E402 FIELDS = ["deck", "type", "queue", "due", "ivl", "reps", "lapses", "flags", "stability", "difficulty", "tags"] def strip_media(value): return re.sub(r'(src="|\[sound:)[^"\]]+', r"\1", value) def main(): shutil.rmtree(WORK, ignore_errors=True) os.makedirs(WORK) col = Collection(os.path.join(WORK, "collection.anki2")) col.set_config("fsrs", True) col.import_anki_package( ImportAnkiPackageRequest( package_path=REEXPORT, options=ImportAnkiPackageOptions(with_scheduling=True, with_deck_configs=True), ) ) after = snapshot_collection(col, "StudyDaily migration test") media_dir = col.media.dir() media_files = sorted(os.listdir(media_dir)) revlog_total = col.db.scalar("select count() from revlog") col.close() source = {(c["guid"], c["ord"]): c for c in SOURCE["cards"]} result = {(c["guid"], c["ord"]): c for c in after["cards"]} diffs = [] for key, src in source.items(): got = result.get(key) if got is None: diffs.append({"card": key, "missing": True}) continue for field in FIELDS: a, b = src[field], got[field] same = abs(a - b) < 0.01 if isinstance(a, float) and isinstance(b, float) else a == b if not same: diffs.append({"card": "/".join(map(str, key)), "field": field, "anki": a, "roundtrip": b}) if [strip_media(f) for f in src["fields"]] != [strip_media(f) for f in got["fields"]]: diffs.append({"card": "/".join(map(str, key)), "field": "fields", "anki": src["fields"], "roundtrip": got["fields"]}) report = { "source": SOURCE["summary"], "roundtrip": after["summary"], "revlog_in_collection": revlog_total, "media_files": media_files, "card_differences": diffs, "fields_compared": FIELDS + ["fields (media names normalised)"], } with open(os.path.join(OUT, "roundtrip-report.json"), "w") as f: json.dump(report, f, indent=1, ensure_ascii=False, default=str) print(json.dumps({k: report[k] for k in ("source", "roundtrip", "revlog_in_collection", "media_files")}, indent=1, default=str)) print("differences:", len(diffs)) for d in diffs[:40]: print(d) if __name__ == "__main__": main()