#+TITLE: Build request — apkg → org-drill converter (inverse of flashcard-to-anki.py) From: home session, 2026-06-21. Craig wants this built (backlogged, not urgent). Standalone build request — the earlier anki-title-fix-proposal only mentioned it in passing; this is the real ask. * Why The flashcard pipeline is one-directional (org-drill → apkg). Decks authored or curated on the phone, and orphaned apkgs whose .org source was never saved, can't get back into the org source-of-truth. Concrete case: deepsat-fundamentals.apkg — a 100-card DeepSat subset generated once with --deck "DeepSat Fundamentals" — has no .org source anywhere on ratio, velox, or in work git history. The converter recovers it and makes phone → org round-tripping possible. * What — contract (inverse of flashcard-to-anki.py) Input: an Anki =.apkg= (a zip containing collection.anki2 / .anki21 sqlite, plus a media blob). Output: an org-drill =.org= file in the house canonical shape that flashcard-stats.py / flashcard-to-anki.py already agree on. Mapping (mirror flashcard-to-anki.py's parse/build): - Deck name (from the apkg) → =#+TITLE:=. - Each note → =** :drill:= with the Back as the body. - Card tag → top-level =* Section= grouping (inverse of section_to_tag; cards sharing a tag collect under one section; the slug won't round-trip to the exact original section title, so this is best-effort — emit the tag as the section heading and let a human retitle). - Back HTML → org: convert =
= back to newlines; unescape =&/</>=; strip the =
= the card template adds (the Back field itself shouldn't contain it, but guard anyway). - Generate a fresh =:ID:= UUID per card in a =:PROPERTIES:= drawer so the output is immediately org-drill-valid and round-trips back through flashcard-to-anki.py. (Note: GUIDs in flashcard-to-anki.py are derived from the front text, not the :ID:, so a regenerated apkg still matches existing phone cards by front — call that out in the docstring.) Edge cases to cover in tests (Normal/Boundary/Error): - Multiple decks in one apkg (emit one file per deck, or error asking for a deck filter — pick one and document it). - Notes with multiple fields / non-basic note types (the pipeline only models Front/Back — skip or warn on others, don't silently drop). - HTML entities, embedded =
=, and any =Source:= footer surviving round-trip. - Empty back; media references (flag, since org side has no media path). - collection.anki2 vs .anki21 schema differences. * Where it lives Rulesets-owned, beside the flashcard-* family (=claude-templates/.ai/scripts/=): suggest =anki-to-flashcard.py= (or =apkg-to-orgdrill.py= — your naming call). Add tests under =scripts/tests/=. A new file can't be built downstream — home/.ai/scripts/ is wiped to match the template by the startup =--delete= rsync — so this has to be built in the rulesets canonical. PEP 723 uv-run script like its sibling; genanki isn't needed for reading (stdlib =zipfile= + =sqlite3= suffice), so it has no runtime deps. * Acceptance Round-trip test: take a known org-drill source, run it through flashcard-to-anki.py, run the result back through this converter, and assert the cards (front/back/section) match the original (modulo regenerated :ID:s and best-effort section titles). Plus: run it on the real deepsat-fundamentals.apkg and hand the recovered .org back so its source can be filed (work project).