# 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//`). 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.json` filename inside the ZIP. The downloader must not hard-code the current name.