Skip to content

🇨🇭 ch-bazl

CountrySwitzerland
Registration prefixHB-
Data sourcehttps://app02.bazl.admin.ch/web/bazl-backend/lfr/csv
FormatJSON API (POST, fixed endpoint) returning a UTF-16 BE, semicolon-delimited CSV body (~3,100 records)
Run frequencyWeekly (Wednesday, 13:10 UTC)
Depends on Mictronics for ICAO hexNo — the FOCA/BAZL register publishes the Mode S hex address directly (Aircraft Address HEX column).

How it works

A single POST request to the fixed FOCA/BAZL backend endpoint (page_result_limit: 10000, current_page_number: 1, filtered to aircraftStatus: ["Registered"]) returns the entire register as one UTF-16 encoded, semicolon-delimited CSV — no authentication or page discovery needed. Rows are additionally filtered to Status == "Registered" and to those with a valid 6-hex-digit Aircraft Address HEX. Note that the CSV column headers all carry a leading space (e.g. " Registration", " Aircraft Type") — this is preserved verbatim in the source and must be matched exactly when reading row.get(...). The Main Owner field is a single unstructured address string (Name[, Canton]?, Street, PostalCode City, Switzerland) that is best-effort parsed by popping known trailing/canton tokens off a comma-split list, since there's no per-field structure to rely on. Every written record explicitly sets military: false — this register is exclusively civil, and the explicit value ensures a stale military: true flag (from Mictronics or a prior record on a reused hex) is corrected on re-registration.

Whenever a record has an aircraft.type_designator, aircraft:type:{type_designator} is looked up in Redis (populated by the mictronics runner) and, if found, its manufacturer_model and wake_turbulence_category are each set directly on this record (independently — a type entry with only one of the two still sets that one) — unconditionally, regardless of whether Mictronics also has values for the same hex. This runner's own type_designator is sourced directly from FOCA/BAZL and is authoritative; merge_aircraft.lua's "registry wins over mictronics" precedence rule guarantees these values take priority at read time either way. The lookup is not a hard dependency — a missing reference table entry, or the table not existing yet, leaves the record exactly as it would have been without this step.

Columns

Source columnImportedNotes
LfrIDPresent in source; not read by this runner
RegistrationHB-prefix → registration
StatusUsed to filter to Registered rows only; value itself is not stored
Date of RegistrationPresent in source; not read by this runner
Date of DeregistrationPresent in source; not read by this runner
Manufactureraircraft.manufacturer
Aicraft Model (sic, source typo)aircraft.model
ICAO Aircraft Typeaircraft.type_designator; also used to look up aircraft:type:{type_designator} in Redis, setting aircraft.manufacturer_model and aircraft.wake_turbulence_category when found
Marketing DesignationPresent in source; not read by this runner
Aircraft TypeDecoded via a type map (e.g. Homebuilt AirplaneAirplane) → aircraft.type
Certification BasisPresent in source; not read by this runner
Airworthiness CategoryPresent in source; not read by this runner
Legal BasisPresent in source; not read by this runner
TCDSPresent in source; not read by this runner
ELAPresent in source; not read by this runner
Aircraft Address DECPresent in source; not read by this runner
Aircraft Address HEXicao_hex
Aircraft Address OCTPresent in source; not read by this runner
Aircraft Address BINPresent in source; not read by this runner
ELT CodePresent in source; not read by this runner
Year of Manufacture4-digit year → aircraft.manufactured_date (YYYY-01-01)
Serial Numberaircraft.serial_number
BRSPresent in source; not read by this runner
MOPSCSummed with Minimum Crew → aircraft.seats
Minimum CrewSummed with MOPSC → aircraft.seats
MTOMPresent in source; not read by this runner
Engine manufactureraircraft.powerplant.manufacturer
EngineFirst comma-separated value → aircraft.powerplant.model
Engine CategoryFirst comma-separated value decoded via engine map → aircraft.powerplant.type
Propeller manufacturerPresent in source; not read by this runner
PropellerPresent in source; not read by this runner
Noise StandardPresent in source; not read by this runner
Noise LevelPresent in source; not read by this runner
Noise ClassPresent in source; not read by this runner
Main OwnerBest-effort parsed into registrant.names/street/city/postal_code; registrant.country is hardcoded CH
Main OperatorPresent in source; not read by this runner
Part OwnersPresent in source; not read by this runner
Part OperatorsPresent in source; not read by this runner
Billing AddressPresent in source; not read by this runner

See specs/data-dictionary.yaml (ch-bazl entry) for full column semantics and cross-source schema notes.

Example Output

Read back the merged record for a given ICAO hex (combines this runner's data with Mictronics and any other sources that have written to the same key):

bash
docker run --rm --network host redis:latest redis-cli EVAL "$(cat ./shared/lua/merge_aircraft.lua)" 0 4B012D | python3 -m json.tool --sort-keys --no-ensure-ascii
json
{
    "aircraft": {
        "manufactured_date": "1944-01-01",
        "manufacturer": "PIPER AIRCRAFT CORPORATION",
        "manufacturer_model": "PIPER J-3 Cub",
        "model": "J3C-65/L-4.",
        "powerplant": {
            "manufacturer": "ROLLS-ROYCE MOTORS LTD.",
            "model": "RR O-200-A",
            "type": "Piston"
        },
        "seats": 2,
        "serial_number": "12026",
        "type": "Airplane",
        "type_designator": "J3",
        "wake_turbulence_category": "Light"
    },
    "icao_hex": "4B012D",
    "military": false,
    "registrant": {
        "city": "Wilihof",
        "country": "CH",
        "names": [
            "Kaufmann, Patrick"
        ],
        "postal_code": "6236",
        "street": [
            "Dorfstrasse 22"
        ]
    },
    "registration": "HB-ALP",
    "source": "ch-bazl"
}
bash
docker run --rm --network host redis:latest redis-cli EVAL "$(cat ./shared/lua/merge_aircraft.lua)" 0 4B0280 | python3 -m json.tool --sort-keys --no-ensure-ascii
json
{
    "aircraft": {
        "manufactured_date": "2021-01-01",
        "manufacturer": "AIRBUS S.A.S.",
        "manufacturer_model": "AIRBUS A-320neo",
        "model": "A320-251N",
        "powerplant": {
            "manufacturer": "SAFRAN AIRCRAFT ENGINES",
            "model": "LEAP-1A26",
            "type": "Turbo-jet"
        },
        "seats": 197,
        "serial_number": "10186",
        "type": "Airplane",
        "type_designator": "A20N",
        "wake_turbulence_category": "Medium"
    },
    "icao_hex": "4B0280",
    "military": false,
    "registrant": {
        "country": "CH",
        "names": [
            "easyJet Airline Company Limited, London Luton Airport LU2 9PF Luton"
        ],
        "street": [
            "Bedfordshire"
        ]
    },
    "registration": "HB-AYQ",
    "source": "ch-bazl"
}

Configuration

Reads settings.json (mounted at /app/settings.json):

ParameterRequiredDefaultNotes
redis.hostRedis connection host
redis.port6379
mqtt.hostOmit the whole mqtt block to skip completion-stats publishing entirely
mqtt.port1883
mqtt.usernameOptional MQTT auth; omit for an anonymous broker
mqtt.password
redis_ttl_days14TTL applied to each aircraft:registry:{icao_hex} key written by this runner

MQTT

Published once, at the end of a run, to SkyFollower/runner/ch-bazl/statistic/{name} (all retained):

Topic suffixValueFormat
records_importede.g. 271Integer as string
last_run_ate.g. 2026-07-07T14:32:01.123456+00:00ISO 8601 UTC
last_run_statussuccess or failureString

Home Assistant autodiscovery configs are also published (retained) to homeassistant/sensor/SkyFollower_runner_ch_bazl_{name}/config for each of the three stats above.