Split technical questions into per-class decks

This commit is contained in:
2026-05-22 15:05:46 +02:00
parent 92d9797b60
commit 0d635a8587
5 changed files with 167 additions and 55 deletions
+45 -21
View File
@@ -491,28 +491,43 @@ output artifacts.
```
anki/
amateurfunk-technische-kenntnisse.apkg (~2.5 MB, 1374 cards)
amateurfunk-betriebliche-kenntnisse.apkg (~130 KB, 172 cards)
amateurfunk-kenntnisse-von-vorschriften.apkg (~75 KB, 204 cards)
amateurfunk-technische-kenntnisse-n.apkg (195 cards)
amateurfunk-technische-kenntnisse-e.apkg (463 cards)
amateurfunk-technische-kenntnisse-a.apkg (716 cards)
amateurfunk-betriebliche-kenntnisse.apkg (172 cards)
amateurfunk-kenntnisse-von-vorschriften.apkg (204 cards)
```
One `.apkg` per top-level Prüfungsteil. The card counts match the
study pools from §3 axis 1: Technische = 1374 (cumulative N+E+A);
Betriebliche = 172 (shared across all candidates); Vorschriften = 204
(also shared). License class is conveyed via tags
(`klasse-N` / `klasse-E` / `klasse-A`), not separate packages —
Anki users browse decks by topic and filter by class.
Five `.apkg` files. Betriebliche and Vorschriften are shared across
every candidate (class-1-only in the data per §3 axis 2) and stay as
one deck each. Technische is fanned out per license class using a
**strict equality split** on the question's `class` field — class-1
questions land in the N deck only, class-2 in E only, class-3 in A
only. The card counts therefore equal the new-at-this-class slices
from §3, not the cumulative study pools: a candidate studying for
class E imports Technische-N + Technische-E + Betriebliche +
Vorschriften.
The Technische deck names use Anki's `::` hierarchy separator
(`Amateurfunk::Technische Kenntnisse::N`) so the three decks render
as children of a shared parent in Anki's deck browser. The
`klasse-N` / `klasse-E` / `klasse-A` tag is still emitted on every
note — redundant within each Technische deck but useful in Betr/Vor
for in-Anki filtering, and harmless besides.
### Steps
1. **Load** the latest catalog: follow `manifest-latest.json` to a
per-edition directory, read the catalog JSON, the per-edition
`manifest.json`, and index the `svgs/` folder.
2. **Categorize** the question tree into three `Category` objects,
one per top-level Prüfungsteil. Each category carries the flat
list of every question that lives anywhere under it, along with
each question's path through the section tree (used for the card
breadcrumb and the path tags).
2. **Categorize** the question tree into five `Category` objects.
Betriebliche and Vorschriften get one each. Technische is
additionally split into three sub-categories (one per license
class) via a strict equality match on the question's `class`
field. Each category carries the flat list of every question that
lives anywhere under it, along with each question's path through
the section tree (used for the card breadcrumb and the path
tags).
3. **For each category, render every question** as an Anki note:
- Compute a stable per-question seed from `--seed` + question
number; shuffle the A/B/C/D choices using it. The displayed
@@ -594,13 +609,19 @@ byte-identical sha256 on each `.apkg`. Verified during review.
- **Slug for filenames.** `slugify()` maps German umlauts and ß
to ASCII digraphs before NFKD-normalizing the rest, then keeps
only `[a-z0-9-]`. Yields readable, sortable filenames
(`amateurfunk-technische-kenntnisse.apkg`).
(`amateurfunk-technische-kenntnisse-n.apkg`). The three
Technische decks append the class letter (`-n`, `-e`, `-a`)
to the shared base slug; Betriebliche and Vorschriften use the
base slug as-is.
- **Tags.** Each note carries `klasse-N|E|A` plus
`pfad-<slugified-section>` for every section level below the
top-level Prüfungsteil. Per-question number tags are
deliberately *not* emitted — they would create ~1750 singletons
in Anki's tag tree, and the number is already in the dedicated
`Number` field for search.
top-level Prüfungsteil. The `klasse-*` tag is redundant within
the three Technische decks (every card already shares one class)
but is still emitted there for uniformity, and remains the
primary filter axis inside the Betriebliche/Vorschriften decks.
Per-question number tags are deliberately *not* emitted — they
would create ~1750 singletons in Anki's tag tree, and the number
is already in the dedicated `Number` field for search.
- **Breadcrumb consistency.** The visible card breadcrumb and the
stored `Path` field both go through one `display_path()`
helper, so they never drift. The boilerplate
@@ -618,8 +639,11 @@ Network-free, fixture-driven. Real Anki is NOT required to
verify the output — tests open the `.apkg` ZIP directly, parse
the embedded SQLite, and check structural invariants.
- Top-level structure: one `.apkg` per Prüfungsteil; expected
filename slugs.
- Top-level structure: five `.apkg` files (Betriebliche +
Vorschriften + Technische×{N,E,A}); expected filename slugs.
- Technische class partition: each Technische deck contains
exactly the questions whose `class` field matches that letter,
and no others.
- Note count and field shape per category.
- Tag mapping: a class-1 question carries `klasse-N`, no
`nummer-*` tags.