Validating Parsed Data Against AMM Standards

In modern MRO environments, parsed maintenance log entries must be cross-referenced against OEM Aircraft Maintenance Manual (AMM) specifications before they enter the airworthiness record. This validation layer acts as the final compliance gate in Automated Log Ingestion & Parsing Workflows, ensuring that extracted task references, torque values, and part numbers align with the approved revision baseline. Without strict schema enforcement, downstream systems risk propagating non-conforming maintenance actions, triggering FAA Part 43 or EASA Part 145 audit findings. The validation engine must enforce ATA 100/iSpec 2200 chapter formatting, task card syntax, numerical tolerance bands, and OEM revision control.

Core Validation Logic & AMM Compliance Mapping

The validation engine operates on a strict contract: every parsed record must satisfy structural, numerical, and temporal constraints defined by the OEM. Schema validation is not merely a data-cleaning step; it is a regulatory boundary enforcement mechanism. When torque specifications, fastener grades, or procedural references are extracted from unstructured technician notes, they must be normalized against the exact AMM revision in effect at the time of maintenance.

The following implementation uses Pydantic v2 for declarative schema validation and enforces AMM-specific business rules. It explicitly handles compliance boundaries by rejecting out-of-range torque tolerances, invalid ATA hierarchies, and mismatched revision dates. For comprehensive error routing and fallback strategies, see Schema Validation & Error Handling when integrating this engine into broader data pipelines.

Production-Ready Validation Engine

The code below is fully runnable, uses Pydantic v2 syntax, and implements structured JSON logging for audit traceability. datetime.now(timezone.utc) is used throughout — never datetime.utcnow(), which returns a naive object and is deprecated in Python 3.12+.

import json
import logging
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, ConfigDict, Field, ValidationError, field_validator, model_validator


class ComplianceJSONFormatter(logging.Formatter):
    def format(self, record: logging.LogRecord) -> str:
        log_obj = {
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
            "module": record.module,
            "function": record.funcName,
        }
        if hasattr(record, "compliance_data"):
            log_obj["compliance_data"] = record.compliance_data
        return json.dumps(log_obj)


logger = logging.getLogger("amm_validator")
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
handler.setFormatter(ComplianceJSONFormatter())
logger.addHandler(handler)


class AMMTaskReference(BaseModel):
    model_config = ConfigDict(strict=True, extra="forbid")

    ata_chapter: str = Field(..., pattern=r"^\d{2}-\d{2}-\d{2}$")
    task_id: str = Field(..., pattern=r"^[A-Z0-9]{3,8}$")
    revision_date: datetime
    torque_value_nm: Optional[float] = Field(None, ge=0.0, le=5000.0)
    torque_tolerance_pct: Optional[float] = Field(None, ge=0.0, le=15.0)
    part_numbers: Optional[List[str]] = None

    @field_validator("ata_chapter")
    @classmethod
    def validate_ata_structure(cls, v: str) -> str:
        parts = v.split("-")
        major, sub, unit = int(parts[0]), int(parts[1]), int(parts[2])
        if not (1 <= major <= 99):
            raise ValueError("ATA major chapter must be 01–99 per ATA 100/iSpec 2200")
        if not (0 <= sub <= 99):
            raise ValueError("ATA sub-chapter out of bounds (00–99)")
        if not (0 <= unit <= 99):
            raise ValueError("ATA unit out of bounds (00–99)")
        return v

    @field_validator("revision_date", mode="before")
    @classmethod
    def parse_revision_date(cls, v: Any) -> datetime:
        """Accept ISO 8601 strings and coerce naive datetimes to UTC."""
        if isinstance(v, str):
            dt = datetime.fromisoformat(v.replace("Z", "+00:00"))
        elif isinstance(v, datetime):
            dt = v
        else:
            raise ValueError(f"Cannot parse revision_date from {type(v)}")
        if dt.tzinfo is None:
            dt = dt.replace(tzinfo=timezone.utc)
        return dt.astimezone(timezone.utc)

    @model_validator(mode="after")
    def enforce_torque_compliance(self) -> "AMMTaskReference":
        if self.torque_value_nm is not None and self.torque_tolerance_pct is None:
            raise ValueError(
                "torque_tolerance_pct is required when torque_value_nm is specified "
                "(per AMM fastener tables)"
            )
        return self


class AMMValidationEngine:
    def __init__(self, approved_revisions: Dict[str, datetime]):
        self.approved_revisions = approved_revisions

    def validate_entry(self, parsed_data: Dict[str, Any]) -> Dict[str, Any]:
        try:
            task_ref = AMMTaskReference(**parsed_data)
            revision_status = self._check_revision_baseline(task_ref)
            self._log_compliance_event("VALID", task_ref, revision_status)
            return {
                "status": "VALID",
                "data": task_ref.model_dump(mode="json"),
                "revision_status": revision_status,
            }
        except ValidationError as e:
            self._log_compliance_event("INVALID", parsed_data, str(e))
            return {"status": "INVALID", "errors": e.errors(), "raw_input": parsed_data}

    def _check_revision_baseline(self, task: AMMTaskReference) -> str:
        if task.task_id not in self.approved_revisions:
            return "UNAPPROVED_REVISION"
        baseline = self.approved_revisions[task.task_id]
        # Ensure comparison is between timezone-aware datetimes
        if baseline.tzinfo is None:
            baseline = baseline.replace(tzinfo=timezone.utc)
        if task.revision_date < baseline:
            return "OUTDATED_REVISION"
        return "COMPLIANT"

    def _log_compliance_event(
        self, status: str, data: Any, detail: Any
    ) -> None:
        log_record = logging.LogRecord(
            name="amm_validator",
            level=logging.INFO,
            pathname="",
            lineno=0,
            msg=f"Validation {status}: {detail}",
            args=(),
            exc_info=None,
        )
        log_record.compliance_data = {
            "status": status,
            "detail": str(detail),
            "data": data if isinstance(data, dict) else data.model_dump(mode="json"),
        }
        logger.handle(log_record)


if __name__ == "__main__":
    approved_revisions = {
        "ENG-204": datetime(2023, 11, 15, tzinfo=timezone.utc),
        "LND-112": datetime(2024, 2, 10, tzinfo=timezone.utc),
    }
    engine = AMMValidationEngine(approved_revisions)

    # Valid entry
    valid_entry = {
        "ata_chapter": "72-30-10",
        "task_id": "ENG-204",
        "revision_date": "2024-01-20T00:00:00Z",
        "torque_value_nm": 45.5,
        "torque_tolerance_pct": 10.0,
        "part_numbers": ["PN-8842A", "PN-9910B"],
    }
    print("Result:", json.dumps(engine.validate_entry(valid_entry), indent=2, default=str))

    # Invalid: torque_value_nm present but torque_tolerance_pct absent
    invalid_entry = {
        "ata_chapter": "72-30-10",
        "task_id": "ENG-204",
        "revision_date": "2022-05-01T00:00:00Z",
        "torque_value_nm": 45.5,
    }
    print("Result:", json.dumps(engine.validate_entry(invalid_entry), indent=2, default=str))

Compliance Boundaries & Structured Audit Logging

Aviation maintenance records are legal documents. The validation engine above enforces three critical compliance boundaries:

  1. ATA 100/iSpec 2200 hierarchy — major chapters (01–99) and sub-chapters are strictly bounded. Invalid chapter routing prevents misclassification of maintenance actions across aircraft systems.
  2. Torque tolerance enforcement — AMM fastener tables mandate explicit tolerance bands when torque values are recorded. The @model_validator rejects partial torque data before it reaches the logbook, aligning with OEM procedural integrity requirements.
  3. Revision baseline control — maintenance actions must reference an AMM revision that is current relative to the OEM-approved baseline. The engine flags OUTDATED_REVISION or UNAPPROVED_REVISION states, preventing technicians from inadvertently executing superseded procedures.

Structured JSON logging is embedded directly into the validation pipeline. Each log record carries a compliance_data payload containing the validation status, raw input, and schema errors. This design satisfies audit requirements under 14 CFR Part 43 and EASA Part 145 by providing immutable, machine-readable evidence of compliance checks. For deeper integration guidance, consult the official Pydantic v2 documentation on custom validators and error serialization.

Integration with Parts Traceability Pipelines

Validated AMM references serve as the primary key for parts-traceability workflows. Once an entry passes validation, the task_id and part_numbers fields are routed to inventory reconciliation systems, ensuring installed components match the OEM-approved bill of materials. Fleet managers can leverage this deterministic validation layer to automate airworthiness release generation, reduce manual QA/QC overhead, and maintain continuous compliance across multi-site MRO operations.

By embedding strict schema enforcement at the ingestion boundary, organizations eliminate silent data corruption, standardize technician reporting, and maintain a defensible audit trail for regulatory inspections.