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

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.

Acclimatisation input validation and diagnosis Three inputs feed the acclimatisation state resolver: the time-zone difference, which fails when kept signed; the elapsed time, which fails when measured on local time; and the reference, which fails when a reset is missed. Each input is validated at ingestion, and the resolved state is diffed against a trusted reference computation to localise any misclassification to its input. Time-zone difference fails if kept signed Elapsed time fails on local clock Reference fails if reset missed State resolver Trusted reference state Diff → cause localise the input
Each acclimatisation input carries a signature failure mode; validating them at ingestion and diffing the resolved state against a trusted computation localises a misclassification to the exact input at fault.

Failure modes and troubleshooting

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.

Back to Data Schema Validation Rules.