Diagnosing Acclimatization-State Misclassification
The exact task this guide solves is finding out why a crew member’s acclimatisation state came out wrong, when the state machine itself is correct but the ingested data feeding it is not. Acclimatisation misclassification is one of the most consequential ingestion bugs, because the state selects which maximum-FDP column applies — a crew wrongly marked acclimatised to local time can be scheduled a longer duty than they are physiologically fit for. The acclimatisation state machine is deterministic, so a wrong state is almost always a data-quality failure upstream. This page builds the validation checks that catch those failures at ingestion. It belongs to the data schema validation rules section and protects the flight data ingestion boundary.
Prerequisites
- Python 3.11+ with
zoneinfoand typed models. - The acclimatisation inputs — reference offset, local offset, and reference-since instant per duty.
- The expected state — a trusted reference computation to diff against.
- UTC-normalised instants so elapsed time is measured correctly.
The four inputs, and how each goes wrong
The state resolves from just three numbers — the time-zone difference, the elapsed time since the reference report, and the acclimatisation reference itself — so a wrong state traces to a defect in one of them. Zone metadata: if a station’s IANA zone is missing or wrong, the local offset is wrong and the difference lands in the wrong table row. Reference reset: if the pipeline fails to reset the reference when a crew acclimatises to a new local time, every later difference is measured from a stale base. Signed difference: if the difference is kept signed instead of absolute, an eastbound crew reads a negative value that never matches a row. Elapsed clock: if elapsed time is measured on local rather than UTC time, the band is off. Diagnosis is a matter of validating each input at the boundary.
Step 1 — Validate zone metadata at ingestion
A duty whose station has no resolvable zone cannot produce a correct offset, so the validator rejects it rather than guessing. Guessing a zone is how a crew silently acquires the wrong acclimatisation state.
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
def validate_zone(station: str, station_zone: str) -> None:
try:
ZoneInfo(station_zone)
except (ZoneInfoNotFoundError, ValueError) as exc:
raise ValueError(f"unresolved zone {station_zone!r} for station {station!r}") from exc
Verify: a station tagged America/New_York validates; one tagged America/Nowhere raises at ingestion, so the bad record never reaches the state machine.
Step 2 — Detect a missing reference reset
When a crew acclimatises to local time, that local time must become the new reference. A pipeline bug that forgets the reset leaves the reference pinned to the original base, so the difference keeps growing across theatres. The check compares the recorded reference against what the state history implies.
def check_reference_reset(prev_state: str, recorded_ref_offset: float,
local_offset: float) -> str | None:
"""After acclimatising to local (state D), the reference offset must equal local."""
if prev_state == "D" and recorded_ref_offset != local_offset:
return (f"stale reference: state D but reference offset {recorded_ref_offset} "
f"≠ local {local_offset} — reset was missed")
return None
Verify: a crew recorded as state D but still carrying the original base offset triggers the stale-reference message; one whose reference offset matches the local offset passes.
Step 3 — Diff the resolved state against the reference computation
The definitive diagnosis re-runs the trusted state resolver on the ingested inputs and compares it to the state the roster recorded. A mismatch localises the defect to the inputs, and reporting all three inputs alongside the two states makes the cause visible.
def diagnose(recorded_state: str, tz_diff_hours: float, elapsed_hours: float,
expected_fn) -> dict:
expected = expected_fn(abs(tz_diff_hours), elapsed_hours).value
return {
"recorded_state": recorded_state,
"expected_state": expected,
"match": recorded_state == expected,
"abs_tz_diff": abs(tz_diff_hours),
"signed_tz_diff": tz_diff_hours,
"elapsed_hours": elapsed_hours,
# A negative signed diff with a correct abs value points at the signing bug.
"likely_cause": "signed_tz_diff" if tz_diff_hours < 0 else None,
}
Verify: feeding a signed −8-hour difference where the roster recorded the wrong state returns match=False with likely_cause="signed_tz_diff", pinpointing the input defect rather than blaming the state machine.
Failure modes and troubleshooting
- Guessed zone metadata. Defaulting an unresolved station to UTC or system-local corrupts the offset. Remediation: reject unresolved zones at ingestion; never guess.
- Missed reference reset. Failing to reset the reference on reaching state D measures every later difference from a stale base. Remediation: assert the reference offset equals the local offset whenever the state is D.
- Signed time-zone difference. A signed difference sends an eastbound crew to a non-existent row. Remediation: take the absolute value before the lookup, and flag negative signed inputs.
- Elapsed time on local clock. Measuring elapsed time on the shifting local clock mis-bands it. Remediation: compute elapsed hours from UTC instants only.
- No reference computation to diff against. Without a trusted state to compare, a misclassification is invisible. Remediation: re-run the deterministic resolver on the ingested inputs and diff.
Frequently Asked Questions
If the state machine is deterministic, why does the state come out wrong?
Because a deterministic function returns a wrong answer for wrong inputs. The state machine is correct; the defect is almost always in the ingested inputs — a bad zone, a stale reference, a signed difference — which is why diagnosis focuses on validating those inputs, not the resolver.
How do I know which input is at fault?
Re-run the trusted resolver on the ingested inputs and diff against the recorded state. Reporting the absolute and signed differences and the elapsed time alongside the two states usually makes the cause obvious — a negative signed difference, for instance, points straight at the signing bug.
Why is a wrong acclimatisation state so serious?
Because the state selects the maximum-FDP column. A crew wrongly marked acclimatised to local time can be assigned a longer duty than their true circadian state safely allows, turning a data-quality bug into a fatigue-risk exposure.
Should a misclassified record be rejected or corrected?
Rejected at the boundary if an input is invalid — an unresolved zone, a naive timestamp — so it never reaches the state machine. If the inputs are valid but the recorded state disagrees with the resolver, the resolver’s value is authoritative and the record is corrected and re-derived.
Related
- Data Schema Validation Rules — the parent section and validation boundary.
- Modeling EASA Acclimatisation State Transitions — the state machine these inputs feed.
- Handling UTC and DST Edge Cases in Flight Time Math — why elapsed time must be measured in UTC.
- Flight Data Ingestion & System Sync — the boundary this validation protects.
Back to Data Schema Validation Rules.