Skip to content

๐Ÿ‡ฒ๐Ÿ‡ป mv-caa โ€‹

CountryMaldives
Registration prefix8Q-
Data sourcehttps://www.caa.gov.mv/operations/registration-of-aircraft-and-mortgages
FormatPDF (opaque hash filename, discovered via index page)
Run frequencyWeekly (Tuesday, 15:10 UTC)
Depends on Mictronics for ICAO hexYes โ€” this runner does not publish ICAO hex addresses; 8Q- registrations are resolved via RediSearch against Mictronics records (idx:aircraft:mictronics). Must run after the mictronics runner.

How it works โ€‹

The CAA index page is scraped for the first link ending in .pdf (its filename is an opaque hash rather than a predictable pattern, so any PDF href found is taken). Each page of the PDF is parsed with pdfplumber's extract_table(); only rows whose registration column starts with 8Q- and whose status column reads exactly "Valid" are kept. The combined Manufacturer+Designation column has embedded newlines collapsed to single spaces. The Owner Name+Address column is multi-line: the first line is taken as the owner name, the last line as the country, and any lines in between as the street address (joined as a list). Serial number is also whitespace-collapsed like the Manufacturer+Designation column, since pdfplumber can represent a wrapped cell's text with an embedded newline rather than a space. 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.

Columns โ€‹

Source columnImportedNotes
N/S (sequence)โŒParsed but not stored
Certificate#โŒParsed but not stored
Registrationโœ…8Q-prefix; used as the Mictronics lookup key
Manufacturer+Designationโœ…โ†’ aircraft.model; embedded newlines collapsed to spaces
MTOWโŒParsed but not stored
Serial Numberโœ…โ†’ aircraft.serial_number; embedded newlines collapsed to a single space
Year Builtโœ…โ†’ aircraft.manufactured_date (YYYY-01-01), only if a 4-digit year
Owner Name+Addressโœ…Multi-line: first line โ†’ registrant.names[0], last line โ†’ registrant.country, middle lines โ†’ registrant.street (list)
Legal OwnerโŒParsed but not stored
Registration BasisโŒParsed but not stored
Basis of Registration, Other SpecificsโŒPresent in source; not read by this runner
MortgageโŒParsed but not stored
IDERAโŒPresent in source; not read by this runner
Original Issue DateโŒParsed but not stored
Last Revision DateโŒParsed but not stored
StatusโŒUsed as a filter โ€” only rows with status "Valid" are kept; the status value itself is not stored

See specs/data-dictionary.yaml (mv-caa 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 05A097 | python3 -m json.tool --sort-keys --no-ensure-ascii
json
{
    "aircraft": {
        "manufactured_date": "1977-01-01",
        "manufacturer": "CESSNA",
        "manufacturer_model": "CESSNA 152",
        "model": "Textron Aviation Inc., 152",
        "serial_number": "152-80719",
        "type_designator": "C152",
        "wake_turbulence_category": "Light"
    },
    "icao_hex": "05A097",
    "military": false,
    "registrant": {
        "country": "Republic of Maldives",
        "names": [
            "Zenith Aviation Academy Pvt. Ltd."
        ],
        "street": [
            "Gan International Airport",
            "S. Gan, 19020"
        ]
    },
    "registration": "8Q-MFB",
    "source": "mv-caa"
}
bash
docker run --rm --network host redis:latest redis-cli EVAL "$(cat ./shared/lua/merge_aircraft.lua)" 0 05A0AF | python3 -m json.tool --sort-keys --no-ensure-ascii
json
{
    "aircraft": {
        "manufactured_date": "2010-01-01",
        "manufacturer": "AIRBUS",
        "manufacturer_model": "AIRBUS A-330-200",
        "model": "Airbus, Airbus A330-203",
        "serial_number": "1161",
        "type_designator": "A332",
        "wake_turbulence_category": "Heavy"
    },
    "icao_hex": "05A0AF",
    "military": false,
    "registrant": {
        "country": "Republic of Maldives",
        "names": [
            "Island Aviation Services Limited"
        ],
        "street": [
            "M. Dar Al-Eiman Building, Majeedhee Magu",
            "Maleโ€™ 20345"
        ]
    },
    "registration": "8Q-IAB",
    "source": "mv-caa"
}

Configuration โ€‹

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

ParameterRequiredDefaultNotes
redis.hostโœ…โ€”Redis connection host
redis.portโŒ6379
mqtt.hostโŒโ€”Omit the whole mqtt block to skip completion-stats publishing entirely
mqtt.portโŒ1883
mqtt.usernameโŒโ€”Optional MQTT auth; omit for an anonymous broker
mqtt.passwordโŒโ€”
redis_ttl_daysโŒ14TTL applied to each aircraft:registry:{icao_hex} key written by this runner

MQTT โ€‹

Published once, at the end of a run, to SkyFollower/runner/mv-caa/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_mv_caa_{name}/config for each of the three stats above.