77 lines
3.6 KiB
Markdown
77 lines
3.6 KiB
Markdown
# Amateurfunk — BNetzA Question Catalog Downloader
|
|
|
|
A small Python tool that downloads the latest German amateur-radio exam
|
|
question catalog ("Fragenkatalog") published by the Bundesnetzagentur
|
|
(BNetzA) and extracts the structured JSON + SVG image assets for further
|
|
use (study apps, flashcards, training tools, etc.).
|
|
|
|
The full source-discovery notes, JSON schema, and design decisions live
|
|
in `DESIGN.md`. This file is a short orientation for anyone (human or
|
|
agent) opening the project.
|
|
|
|
## What the catalog is
|
|
|
|
- Official German amateur-radio exam questions for classes **N, E, A**
|
|
(German license tiers).
|
|
- Published by the Bundesnetzagentur under the **DL-DE→BY-2.0** open
|
|
data license (free reuse, attribution required).
|
|
- Distributed as a single ZIP containing one JSON file with the full
|
|
question tree, plus a `svgs/` folder with figures referenced by
|
|
individual questions.
|
|
- Current edition at time of writing: **3. Auflage, März 2024**
|
|
(issued 2024-03-20, valid from 2024-06-24, ~1750 questions).
|
|
|
|
## Canonical source
|
|
|
|
- ZIP (machine-readable): `https://www.bundesnetzagentur.de/SharedDocs/Downloads/DE/Sachgebiete/Telekommunikation/Unternehmen_Institutionen/Frequenzen/Amateurfunk/Fragenkatalog/PruefungsfragenZIP.zip?__blob=publicationFile`
|
|
- Landing page (short link): `https://www.bnetza.de/amateurfunk-fragenkatalog`
|
|
- PDF (human-readable, not used by this tool): same path with
|
|
`Pruefungsfragen.pdf` instead of `PruefungsfragenZIP.zip`.
|
|
|
|
The ZIP URL is stable across editions — BNetzA replaces the file
|
|
in-place. The `Last-Modified` HTTP header is reliable for change
|
|
detection. The filename inside the ZIP (`fragenkatalog3b.json`) encodes
|
|
the edition (`3b` = 3rd edition, revision b) and will change on new
|
|
editions, so we discover it from the archive rather than hard-coding.
|
|
|
|
## Scope of the tool (initial)
|
|
|
|
1. Download the ZIP from the canonical URL.
|
|
2. Verify it is a valid ZIP and contains the expected JSON + SVG files.
|
|
3. Extract to a target directory (default: `./data/<edition>/`).
|
|
4. Emit a small `manifest.json` next to the data: source URL, fetched-at
|
|
timestamp, `Last-Modified` from the server, JSON edition metadata,
|
|
sha256 of the ZIP.
|
|
5. Be idempotent — re-running without an upstream change is a no-op.
|
|
The skip key is the HTTP `Last-Modified` header recorded on the
|
|
previous manifest; the ZIP is deleted by default after extraction,
|
|
so the recorded sha256 is provenance, not a re-verification target.
|
|
See `DESIGN.md` for the full idempotency contract.
|
|
|
|
Out of scope for v1 (kept for later): rendering LaTeX/SVG, building a
|
|
study app, multi-edition diffing, mirroring the PDF.
|
|
|
|
## Repo conventions
|
|
|
|
- Python 3.11+, standard library only where reasonable (`urllib`,
|
|
`zipfile`, `hashlib`, `json`, `pathlib`). Add deps only if they pay
|
|
for themselves — none expected for v1.
|
|
- Single-purpose script: `amateurfunk_fetch.py` (or a tiny package if
|
|
it grows). No framework, no CLI library beyond `argparse`.
|
|
- Downloaded data is treated as a build artifact: kept under `data/`
|
|
and gitignored.
|
|
- License attribution string (required by DL-DE→BY-2.0) is preserved
|
|
verbatim from the upstream `README.txt` whenever we redistribute the
|
|
data.
|
|
|
|
## Working on this repo
|
|
|
|
- Start from `DESIGN.md` — it has the JSON schema, the question/answer
|
|
conventions (answer A is always correct, B/C/D are distractors), the
|
|
LaTeX-in-questions caveat, and the SVG naming convention.
|
|
- Do not invent new download URLs; the ones in `DESIGN.md` were
|
|
verified against the live BNetzA site.
|
|
- When BNetzA publishes a new edition, expect a new
|
|
`fragenkatalog<N><rev>.json` filename inside the ZIP. The downloader
|
|
must not hard-code the current name.
|