Split technical questions into per-class decks
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user