Files
Amateurfunk-Anki/EXPLANATIONS.md
T

18 KiB
Raw Blame History

Explanations — agent-authored answer rationales

This document is the working contract for any AI agent (or human) asked to add or improve explanations for questions in the BNetzA amateur-radio catalog. The decks built by amateurfunk_anki.py optionally append an English explanation block to the back of each card; the text of those explanations lives in explanations.json at the repo root and is edited by hand (or by an agent following this file).

The build is non-blocking on this data: a missing file or missing entry just produces a card without an explanation block. Adding an entry is purely additive — no regenerate ceremony beyond make anki.


1. File location and shape

  • Path: explanations.json at the repo root. Tracked in git.
  • Encoding: UTF-8, no BOM, two-space indent for readability.
  • Top level: a JSON object keyed by question number exactly as it appears in the catalog (e.g. "NA101", "BA205", "EH410"). Keys are case-sensitive.
  • Sort order: keep keys in alphabetical order. Diffs stay clean and merge conflicts get easier; the build does not care.

Per-entry schema

Every entry MUST have exactly these four fields:

Field Type Constraint
revision integer >= 1. Starts at 1, bumps on improvement
explanation string Non-empty. English. Correct & helpful, WHY-focused
source string Non-empty. URL or citation like AFuV §16(2)
confidence integer 1..10 inclusive. See scale in §5

Extra keys are rejected by load_explanations() — the build fails with unknown fields [...] listing them. The loader is similarly strict about types: a JSON true will not satisfy the integer contract for revision or confidence. If you need to track editorial metadata that isn't shown on the card, propose a schema change rather than smuggling fields in.

Top-level keys (the question numbers) must also match the catalog exactly. An entry keyed on a number that no live question carries (typo, stale ID after a catalog revision) fails the build with explanation keys not present in the catalog: .... Fix the key, or remove the entry.

Minimal example

{
  "NA101": {
    "revision": 1,
    "explanation": "100 m weighs 210 g, so 55 g is 55/210 of 100 m ≈ 26.2 m. Mass scales linearly with length for the same wire.",
    "source": "https://50ohm.de/lernen/wissen/elektrotechnik-mathematik",
    "confidence": 8
  }
}

The build appends this on the back card as a styled block headed Explanation, with the source rendered as a clickable link when it looks like an HTTP(S) URL.


2. How the explanation reaches the card

amateurfunk_anki.py does the wiring; the agent does not need to modify Python code to ship a new explanation. End-to-end:

  1. load_explanations(./explanations.json) parses the file and validates each entry against the schema above. A malformed entry is a hard build error.
  2. While rendering a card, render_question() looks up the question's number in the dict. On a hit, render_explanation() produces an HTML block: header "Explanation", the body (with inline $...$ LaTeX rewritten to MathJax \(...\), same as the question text), and a "Source: ..." line.
  3. The block lands inside .af-back at the very end, styled by the .af-explanation* CSS rules — serif body, sans-serif metadata, separated from the answer by a top border. When the entry's confidence is below 7, a small "low confidence" badge appears next to the Explanation header — a hint to the learner that the reasoning may be incomplete or weakly sourced. revision is never shown on the card.
  4. The note GUID is keyed on category.slug:number, NOT on field content, so adding/changing an explanation does not create duplicate cards on re-import — Anki updates the existing note in place.

Run make anki (or python3 amateurfunk_anki.py) after editing the file. The CLI summary line will show ... N with explanations per deck.


3. Locating a question to explain

Questions live in the JSON catalog under the per-edition data directory:

data/<slug>/fragenkatalog<edition>.json

To find one by number, e.g. EH410:

python3 -c '
import json, glob
for path in glob.glob("data/*/fragenkatalog*.json"):
    cat = json.load(open(path))
    def walk(node, prefix=()):
        for q in node.get("questions", []) or []:
            if q["number"] == "EH410":
                print(json.dumps(q, ensure_ascii=False, indent=2))
                print(" / ".join(prefix))
        for s in node.get("sections", []) or []:
            walk(s, prefix + (s.get("title", "?"),))
    walk(cat)
'

You get the question stem, the four answers (answer_a is the correct one upstream; the deck shuffles before display), the class digit ("1" = N, "2" = E, "3" = A), and the section path. That path tells you the topic context — useful when choosing a source citation.


4. Writing the explanation

Style

  • Language: English. The questions and answers stay in German on the card; the explanation is the one English element. Don't switch.
  • Correct and helpful first; length follows the topic. There is no sentence cap. Use as much room as the question genuinely needs — a one-line arithmetic restatement for a simple lookup, a full worked derivation or a multi-step rule walkthrough where that is what makes the answer click. Don't pad: every sentence should earn its place by adding correctness, context, or a usable hook. The card already shows the question, the correct answer text, and the breadcrumb — the explanation adds the missing why and the understanding needed to get there, not a restatement of those.
  • Lead with the reasoning. Don't open with "The correct answer is X because..." — the card already declares the correct answer one line above. Go straight to the principle, the formula, the rule of thumb.
  • Math. Use inline $...$; it's rewritten to MathJax on the card, same as for question text. Don't use display math $$...$$ (the deck has no MathJax support for those).
  • Inline emphasis. <u>...</u> is the only HTML you should type by hand. Everything else gets HTML-escaped.
  • No spoilers about other answers. If a distractor is a common trap, naming the trap is fine; quoting the distractor's text is noise.

What "WHY-focused" means in practice

Less useful More useful
"The correct answer is 26.2 m." "55 g is 55/210 of 100 m by mass scaling."
"Because regulations require it." "AFuV §16(2): only class A may operate ≤10 m."
"It's how the formula works." "Q is reactance over resistance, so Q rises as R falls."

If the only honest explanation is "memorize the table" (e.g. a band-plan lookup), say that plainly and cite the band plan in source — confidence stays low (34) until someone finds a deeper hook.


5. Confidence scale

Score Meaning
10 Direct quote / arithmetic restatement from a primary legal source.
89 Derivation from a well-known formula or law text; no ambiguity.
67 Reasoning sound but condensed; a reviewer might want one more line.
45 Educated guess based on context; corroborating source is weak.
13 Best-effort placeholder. Flag for re-explanation.

When in doubt, score lower. The query "explain everything below confidence 7" is a real workflow — a too-generous score hides work that should be redone.

The score is also capped by the source tier — see §6 "Source ↔ confidence linkage". A tier-8 (general web) source caps confidence at 5 regardless of how persuasive the prose is.

Card surface. Confidence below 7 also surfaces a "low confidence" badge on the card itself (see §2). This is deliberate: the learner sees that the explanation is provisional rather than trusting it as definitive. Scoring 7 turns the badge off, so do not nudge a 6 to a 7 just to clear the badge — fix the explanation or the source first.


6. Sources

Use the strictest tier that actually answers the question. Drop down a tier only when the higher one doesn't cover the topic — never because a lower-tier source is easier to find. The exam tests knowledge of German law and BNetzA-curated material; a primary-law question backed by a random tutorial site is a worse explanation than no explanation at all.

The full priority order, top (most preferred) to bottom:

  1. Primary German law.

    • AFuG (Amateurfunkgesetz), AFuV (Amateurfunkverordnung), BEMFV, TKG when applicable, plus frequency-allocation ordinances (vfg / VVnömL).
    • Canonical URL pattern: https://www.gesetze-im-internet.de/<gesetz>/__<§>.html. Cite as AFuV §16(2) style. Use the URL form when possible so the card link is clickable.
    • If a question's answer is fixed by German law, no lower tier is acceptable as the sole source.
  2. EU / international regulation binding on Germany.

    • CEPT recommendations (T/R 61-01, T/R 61-02), ITU Radio Regulations, ECC decisions, EU EMC / RED directives.
    • Use docdb.cept.org/... or the ITU document portal as the canonical URL.
    • Reach for this tier when the question references CEPT licensing, foreign operation, or harmonised band plans that AFuV doesn't restate.
  3. BNetzA official publications.

    • The question catalog's own README.txt, BNetzA "Vfg" announcements, the German band plan as published by BNetzA, official explanatory notes.
    • Useful when primary law is too terse to convey the why on its own — these are the regulator's own gloss on the law.
    • Do not cite the question catalog PDF/ZIP itself as an explanation source. It proves what the official answer is, but it usually does not explain why that answer is right. Use a law, regulation, BNetzA notice, DARC/50ohm study page, standard, or other source that actually supports the explanation.
  4. German amateur-radio organisations.

    • DARC publications and band plan (darc.de), Runder Tisch Amateurfunk (RTA) papers, 50ohm.de (the community study site linked from README.md).
    • Exam-aligned by community convention; well-suited to the WHY when law text alone isn't pedagogically clear.
  5. International amateur-radio organisations.

    • IARU Region 1 documents (Germany is in R1) and DARC's IARU R1 liaison material; ARRL publications when they explain the underlying physics or an internationally harmonised band plan.
    • ARRL is not a source for German licensing rules — don't use it that way.
  6. Standards bodies and primary technical references.

    • NIST, IEEE, IEC, ITU-R recommendations, ETSI standards (when not already binding via tier 2).
    • Appropriate for purely technical questions — units, defining equations, definitions — where no amateur-radio-specific source applies.
  7. Established engineering references.

    • University lecture notes hosted by the university, recognised EE textbook publishers, well-known authors' personal sites.
    • Use only when tiers 16 don't cover the topic.
  8. General web technical sites — last resort.

    • Wikipedia, tutorial sites (allaboutcircuits.com, amateur-radio-wiki.net, biopac.com, etc.), random manufacturer pages, blog posts.
    • Treat citations at this tier as temporary. Score confidence ≤ 5 and consider the entry a §8.3 candidate for re-sourcing the moment a higher-tier source is identified. Never use a tier-8 source as the sole citation for a question that has a clear answer in tiers 14.

Source ↔ confidence linkage

The §5 confidence scale isn't independent of the tier:

  • Tier 12 + sound derivation → 910 is appropriate.
  • Tier 34 → cap at 8 unless the source quotes a primary citation.
  • Tier 56 → cap at 7.
  • Tier 7 → cap at 6.
  • Tier 8 (sole source) → cap at 5. Flag for re-sourcing.

This isn't a hard limit the validator enforces — it's editorial discipline. If you find yourself wanting confidence 9 with a tier-7 source, you almost always either (a) need to find the tier-14 source that backs the same claim, or (b) are about to overstate certainty.

Form

If the source is a URL, store it as one — _source_html() makes plain http:// / https:// strings clickable. Mixed text+URL sources are stored verbatim and rendered as escaped text (no auto link), so prefer one or the other. A short citation like AFuV §16(2), gesetze-im-internet.de/afuv_2005/__16.html is acceptable but loses click-through. When possible, narrow the source to a single canonical URL.


7. Revisions

revision is editorial bookkeeping; it never appears on the card.

  • A brand-new entry starts at revision: 1.
  • Bump when you materially improve the explanation, broaden the source, or fix an error. A typo fix is not a revision bump.
  • Don't lower the revision. If a previous revision was wrong, fix it forward and explain the fix in the commit message.
  • A revision bump usually pairs with a confidence bump (or, more rarely, with a deliberate confidence reset to indicate the reviewer is less sure than the prior author was).

8. Workflows

These are the common ways the file gets edited. The agent should read the request and pick one.

8.1 "Explain question X"

  1. Look up X in the catalog (§3) — read the stem, the correct answer (answer_a upstream), and the section path.
  2. Identify why the correct answer is correct. Reach for a primary source before paraphrasing 50ohm.de.
  3. Draft the body per §4, pick a source per §6, score honestly per §5.
  4. Open explanations.json, insert the new entry in alphabetical key order, save.
  5. Run python3 -m unittest test_amateurfunk_anki — the schema validator runs as part of the build path used by tests, so a typo'd entry fails fast.
  6. Rebuild decks (make anki) and spot-check the new entry on the back of the card.

8.2 "Explain every question that doesn't have an entry yet"

This is bulk work — handle it in chunks, not as one giant batch.

  1. Compute the unexplained set:

    python3 -c '
    import json, glob
    catalog = next(iter(glob.glob("data/*/fragenkatalog*.json")))
    exp = json.load(open("explanations.json"))
    def walk(n):
        for q in n.get("questions", []) or []: yield q["number"]
        for s in n.get("sections", []) or []: yield from walk(s)
    missing = sorted(set(walk(json.load(open(catalog)))) - exp.keys())
    print(len(missing), "questions unexplained")
    print("\n".join(missing[:20]))
    '
    
  2. Process in groups of ~10 within one topic. Same section path often shares one source citation, so context stays warm and confidence scores stay consistent.

  3. After each group, save and run the tests. Don't accumulate hundreds of unsaved entries.

8.3 "Improve all entries with confidence below N"

  1. List low-confidence entries:

    python3 -c '
    import json
    exp = json.load(open("explanations.json"))
    for k, v in sorted(exp.items()):
        if v["confidence"] < 7: print(k, v["confidence"], "—", v["source"])
    '
    
  2. For each, look up the question (§3), seek a stronger source (§6), rewrite if the new source changes the framing.

  3. Bump revision and update confidence to reflect the new evidence. Do not just bump confidence without changing anything else — that's how bad explanations entrench themselves.

8.4 "This explanation is wrong"

  1. Read the current entry. Confirm the error against the catalog stem and the cited source.
  2. Rewrite the body, replace the source if needed, bump revision, and re-score confidence from scratch (don't carry over the prior score).
  3. Commit message should name the question number and summarize what was wrong — future agents need to know what kind of mistake to look for elsewhere.

9. Validation checklist

Before saving, confirm:

  • Key is the exact catalog number (case-sensitive).
  • All four fields present, correct types, in range.
  • explanation is English, correct, helpful, WHY-focused — as long as the topic needs, no longer.
  • source is non-empty and as primary as possible.
  • revision is correct (1 for new, bumped for improvement).
  • confidence honestly reflects how well the source supports the explanation.
  • Keys remain in alphabetical order in the file.
  • python3 -m unittest test_amateurfunk_anki passes.

10. What's out of scope here

  • Translating questions into English. The cards are bilingual by design: German question + English explanation. Don't paraphrase the German.
  • Editing the catalog itself. data/ is a build artifact — refreshed by amateurfunk_fetch.py from BNetzA. Mistakes in the upstream questions are upstream's to fix.
  • Removing explanations. If an entry is genuinely useless, fix it forward (§8.4). Don't delete keys — that loses the audit trail. (The schema doesn't currently encode "retracted", so if you ever need it, propose a schema change.)