619 lines
26 KiB
Markdown
619 lines
26 KiB
Markdown
# 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 these four required fields, and MAY carry the one
|
||
optional field below:
|
||
|
||
| 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 |
|
||
| `provenance` | string | *Optional.* Only allowed value: `"50ohm-loesungsweg"` |
|
||
|
||
`provenance` records **how the text was produced**, which `source` (a
|
||
citation) does not. Set it to `"50ohm-loesungsweg"` only on entries
|
||
that are genuinely a translation/condensation of a 50ohm.de worked
|
||
solution (`contents/solutions/<ID>.md` in `DARC-e-V/50ohm-contents-dl`).
|
||
The build uses it — and *not* the `source` domain — to decide whether
|
||
to show the CC BY 4.0 derivative-work credit on the card (CC BY
|
||
requires naming the author team and indicating modification). Do **not**
|
||
add it just because an entry cites a 50ohm.de study page; merely citing
|
||
a page is not a derivative work. Any other value, or any other extra
|
||
field, is rejected by `load_explanations()` with
|
||
`unknown fields [...]` / `provenance must be one of [...]`. The loader
|
||
is also strict about types: a JSON `true` will not satisfy the integer
|
||
contract for `revision` or `confidence`. To track other editorial
|
||
metadata, 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
|
||
|
||
```json
|
||
{
|
||
"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`:
|
||
|
||
```sh
|
||
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). Getting the *inside* of
|
||
`$...$` right is its own discipline — see **§4a Math typesetting**
|
||
below. This is the single most error-prone part of this file.
|
||
- **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.
|
||
|
||
### 4a. Math typesetting (MathJax) — get this right
|
||
|
||
Everything between `$...$` is TeX. MathJax renders **bare multi-letter
|
||
runs as a product of italic variables**, so `ohm` shows as *o·h·m*,
|
||
`log10` as *l·o·g·10*, `P_{PEP}` as *P·(P·E·P)*. A whole multi-round
|
||
review on this repo was spent fixing exactly these. The rules below are
|
||
the checklist; wrong → right.
|
||
|
||
**Units** — never leave a unit as bare letters; wrap in `\text{}` (or use
|
||
the symbol) and put a thin space `\,` after the number:
|
||
|
||
| Wrong (in `$...$`) | Right |
|
||
|---|---|
|
||
| `0.01 ohm`, `10 kOhm`, `5 MOhm` | `0.01\,\Omega`, `10\,\text{k}\Omega`, `5\,\text{M}\Omega` |
|
||
| `3 microhenry`, `3 uH` | `3\,\mu\text{H}` |
|
||
| `1 uF`, `100 nF`, `47 pF` | `1\,\mu\text{F}`, `100\,\text{nF}`, `47\,\text{pF}` |
|
||
| `5 mA`, `2 mV`, `1 kHz`, `28 MHz` | `5\,\text{mA}`, `2\,\text{mV}`, `1\,\text{kHz}`, `28\,\text{MHz}` |
|
||
| `12 dB`, `8 dBi`, `46.8 bit/s` | `12\,\text{dB}`, `8\,\text{dBi}`, `46.8\,\text{bit/s}` |
|
||
| `5 mm2`, `2.5 A/mm2` | `5\,\text{mm}^2`, `2.5\,\text{A/mm}^2` |
|
||
| `360 degrees`, `45°` | `360^\circ`, `45^\circ` |
|
||
| `20 A`, `12 V`, `100 W`, `5 m`, `50 s` | `20\,\text{A}`, `12\,\text{V}`, `100\,\text{W}`, `5\,\text{m}`, `50\,\text{s}` |
|
||
|
||
**Operators, functions, Greek, powers:**
|
||
|
||
| Wrong | Right | Why |
|
||
|---|---|---|
|
||
| `log10(x)` | `\log_{10}(x)` | bare `log` is *l·o·g* |
|
||
| `10^(20/10)` | `10^{20/10}` | `^(` superscripts only the `(`; braces group |
|
||
| `lambda`, `pi`, `tau`, `omega`, `rho`, `mu` | `\lambda`, `\pi`, `\tau`, … | bare = italic letters |
|
||
| `sin`, `cos`, `ln` | `\sin`, `\cos`, `\ln` | |
|
||
| `2 x 3`, `R1 || R2` | `2 \cdot 3`, `R_1 \parallel R_2` | `x`/`||` are not operators |
|
||
| `62.5%` | `62.5\%` | **bare `%` is a TeX comment — it silently eats the rest of the math** |
|
||
| `4,200,000` | `4{,}200{,}000` | bare `,` gets TeX punctuation spacing |
|
||
|
||
**Subscripts.** Descriptive multi-letter labels/acronyms are roman; a
|
||
single-letter subscript stays italic:
|
||
|
||
| Wrong | Right |
|
||
|---|---|
|
||
| `P_{EIRP}`, `U_{peak}`, `f_{mod}`, `V_{BE}`, `f_{sum}` | `P_\mathrm{EIRP}`, `U_\mathrm{peak}`, `f_\mathrm{mod}`, `V_\mathrm{BE}`, `f_\mathrm{sum}` |
|
||
| `R1/R2 = R3/R4` | `R_1/R_2 = R_3/R_4` |
|
||
| keep italic: `U_F`, `f_c`, `X_C`, `R_g` | (single letter = variable, leave as-is) |
|
||
|
||
A standalone acronym used as a variable (e.g. `MUF` on a formula's LHS)
|
||
also goes roman: `\mathrm{MUF}`.
|
||
|
||
**Dimensional constants keep their unit.** The field-strength constant is
|
||
`30\,\Omega`, so write `\sqrt{30\,\Omega \cdot P_\mathrm{EIRP}}` and
|
||
`(E\cdot d)^2/(30\,\Omega)`, not a bare `30`. Same idea for the dB-level
|
||
exponent: `10^{g/(10\,\text{dB})}` (the `(...)` groups the denominator —
|
||
`10^{g/10\,\text{dB}}` parses as `(g/10)·dB`).
|
||
|
||
**Unit vs. variable — don't blindly unit-ize a single letter.** `A` is
|
||
amperes in `20 A` but *area* in `N^2 A/l`; `s`/`m` are seconds/metres in
|
||
`50 s` / `5 m` but could be variables elsewhere. The tell: a *free-standing
|
||
number* immediately before the letter (`20 A`) means a unit; an exponent
|
||
base (`N^2 A`) or a symbolic factor (`S \cdot A`) means a variable. When
|
||
unsure, read the sentence.
|
||
|
||
**What stays prose — do NOT force into `$...$`:**
|
||
|
||
- Band/wavelength designations: `5/8-wave`, `20/15/10 m trap dipole`,
|
||
`the 80 m band`.
|
||
- Conceptual word-equations: `1st overtone = 2nd harmonic`,
|
||
`Region 1 = Europe/Africa`.
|
||
- Units mentioned adjectivally in a sentence: "a 50 ohm antenna",
|
||
"the 28 V/m limit", "about 11.7 V/m".
|
||
|
||
But a **worked computation with an `=`** (e.g. `230 V / 20 = 11.5 V`)
|
||
belongs in `$...$`, fully typeset.
|
||
|
||
### Verifying it — the process lessons
|
||
|
||
These review rounds kept finding *peers* of an already-fixed defect.
|
||
Avoid the repeat:
|
||
|
||
1. **Fix the whole record, not a substring.** The same formula usually
|
||
appears in *both* the explanation body *and* the `Hilfsmittel:` note.
|
||
Search the entire entry and fix every occurrence.
|
||
2. **Fix the whole class, file-wide — not just the IDs someone listed.**
|
||
If `P_{EIRP}` is wrong in one card it is wrong in twenty; sweep the
|
||
whole file for the pattern.
|
||
3. **Verify with a positive sweep, not a blacklist.** A list of
|
||
known-bad tokens always misses a new category (it missed `degrees`,
|
||
`bit`, `||`, bare seconds…). Instead: strip `\text{}`/`\mathrm{}` and
|
||
the known macros, then flag *every remaining* ≥2-letter run inside
|
||
`$...$` — each survivor must be a deliberate variable product
|
||
(`LC`, `jX`, `RC`, `di/dt`) or it's a bug.
|
||
4. **Don't claim "0 / exhaustive" unless the check actually covers that
|
||
category.** Report what you checked, not a blanket adjective.
|
||
|
||
A ready-made sweep:
|
||
|
||
```sh
|
||
python3 -c '
|
||
import json, re
|
||
d = json.load(open("explanations.json"))
|
||
span = re.compile(r"\$[^$]*\$")
|
||
def strip(s):
|
||
s = re.sub(r"\\(?:text|mathrm|operatorname)\{[^{}]*\}", " ", s)
|
||
return re.sub(r"\\[A-Za-z]+", " ", s) # drop macros (\sqrt, \pi, \cdot, ...)
|
||
for k, e in d.items():
|
||
ex = e["explanation"] if isinstance(e, dict) else ""
|
||
if ex.count("$") % 2 or ex.count("{") != ex.count("}"):
|
||
print("BALANCE", k)
|
||
for m in span.finditer(ex):
|
||
s = m.group(0)
|
||
if re.search(r"(?<!\\)%", s) or "||" in s or "°" in s \
|
||
or re.search(r"(?<!\\)\blog10\b|10\^\(", s) \
|
||
or re.search(r"_\{[A-Za-z][A-Za-z,]*\}", s) \
|
||
or re.search(r"(?<=\d),(?=\d\d\d)", s):
|
||
print("ISSUE", k, m.group(0)[:60]); break
|
||
for tok in re.findall(r"[A-Za-z]{2,}", strip(s)):
|
||
# whitelist genuine products / kept tokens; investigate the rest
|
||
if tok not in {"LC","RC","RA","UI","CU","PR","jX","fL","fC","di","dt"}:
|
||
print("TOKEN?", k, tok, "|", m.group(0)[:60])
|
||
'
|
||
```
|
||
|
||
Anything it prints is either a real defect or a new legitimate product to
|
||
add to the whitelist — look, don't assume.
|
||
|
||
### 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 (3–4) until someone finds a deeper
|
||
hook.
|
||
|
||
### The Hilfsmittel note
|
||
|
||
Candidates may use the official exam aid (`Hilfsmittel_12062024.pdf`)
|
||
during the exam. Its complete contents — every formula and table, with
|
||
**printed page numbers** — are catalogued in
|
||
[`references/Hilfsmittel.md`](references/Hilfsmittel.md). When a
|
||
question's answer is a lookup or a direct application of something in
|
||
that sheet, append a short note to the end of the `explanation` body:
|
||
|
||
```
|
||
... <u>Hilfsmittel:</u> <pointer>
|
||
```
|
||
|
||
Rules — this is the part that was historically done badly (generic
|
||
boilerplate stamped on everything), so be strict:
|
||
|
||
- **Only cite what is actually in the sheet.** Verify against
|
||
`references/Hilfsmittel.md`. If the fact the question turns on is a
|
||
memory item — diode forward voltages (~0.6 V / ~0.3 V), `tan δ = 1/Q`,
|
||
S-meter step = 6 dB, harmonics = n × fundamental, ppm/percent
|
||
arithmetic, semiconductor behaviour, definitions, antenna
|
||
length/shortening factors — **do not add a Hilfsmittel note at all.**
|
||
- **Name the specific formula(s) or table, and the page.** Not "the
|
||
formula is in the Formelsammlung" but e.g. `P = U²/R (Leistung,
|
||
S.12)` or `the Widerstands-Farbcode table (S.11)`.
|
||
- **For multi-step calculations, give the order:** "first
|
||
`U_eff = Û/√2` (Wechselspannung, S.12), then `P = U_eff²/R`
|
||
(Leistung, S.12)". State plainly when a value comes from outside the
|
||
sheet (e.g. a diode drop) versus from a sheet formula.
|
||
- **For a pure table lookup**, say it is a lookup and cite the table +
|
||
page (e.g. "a table lookup, not a memory item — the band limits …
|
||
are in the Frequenzbereichszuweisung (Anlage 1, Tabellarische
|
||
Übersicht, S. 2–3)").
|
||
- **Page numbering:** the printed page number = PDF page − 2 (the cover
|
||
and the "Hinweis" page are unnumbered). Always cite the printed
|
||
number, as `references/Hilfsmittel.md` does.
|
||
|
||
---
|
||
|
||
## 5. Confidence scale
|
||
|
||
| Score | Meaning |
|
||
|------:|------------------------------------------------------------------------|
|
||
| 10 | Direct quote / arithmetic restatement from a primary legal source. |
|
||
| 8–9 | Derivation from a well-known formula or law text; no ambiguity. |
|
||
| 6–7 | Reasoning sound but condensed; a reviewer might want one more line. |
|
||
| 4–5 | Educated guess based on context; corroborating source is weak. |
|
||
| 1–3 | 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 1–6 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 1–4.
|
||
|
||
### Source ↔ confidence linkage
|
||
|
||
The §5 confidence scale isn't independent of the tier:
|
||
|
||
- Tier 1–2 + sound derivation → 9–10 is appropriate.
|
||
- Tier 3–4 → cap at 8 unless the source quotes a primary citation.
|
||
- Tier 5–6 → 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-1–4 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:
|
||
|
||
```sh
|
||
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:
|
||
|
||
```sh
|
||
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.
|
||
- [ ] **Math typesetting (§4a):** every formula is in `$...$`; units are
|
||
`\text{}`/symbols with a leading `\,`; no bare `log10`, `10^(`,
|
||
Greek words, `%`, `||`, multi-letter `_{...}` subscripts, or raw
|
||
thousands commas; dimensional constants keep their unit. Fix every
|
||
occurrence in **both** the body and the `Hilfsmittel:` note.
|
||
- [ ] The §4a verification sweep prints nothing unexpected (run it after
|
||
any batch of math edits — don't trust a per-ID fix to have caught
|
||
the peers).
|
||
- [ ] `python3 -m unittest test_amateurfunk_anki` passes. (The full deck
|
||
suite is `test_amateurfunk_fetch test_amateurfunk_anki
|
||
test_amateurfunk_shorthand test_amateurfunk_technical` — 95 tests;
|
||
the two-module subset that validates explanation schema is 65.)
|
||
|
||
---
|
||
|
||
## 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.)
|