Freight Contract Architecture & Rate Mapping

When a freight audit program has no deterministic contract architecture underneath it, every other stage of the pipeline inherits the ambiguity. An EDI 210 invoice arrives referencing a lane and a service date, and the audit engine has to answer a deceptively hard question: which rate was in force when this shipment was tendered? Without versioned tariffs, point-in-time resolution, and an immutable archive of superseded agreements, that question is answered by guesswork — and guesswork at scale means silent overpayment, unrecoverable disputes, and SOX-relevant audit gaps that surface months later during a carrier reconciliation. This page is the architectural backbone for the rest of the freight audit pipeline: it defines how carrier contracts, negotiated rate schedules, fuel indices, and accessorial tariffs are modelled, versioned, stored, and resolved into the auditable rate matrices that downstream ingestion and validation depend on.

The audience here is Python ETL developers, freight auditors, and logistics analysts who own the rate store and the resolution logic that sits between raw carrier submissions and the AP ledger. The data flows in one direction — carrier submission → ingestion → rate resolution → validation → dispute → audit-ready record — and the contract architecture described below is what every other stage reads from. Get this layer right and the rest of the system becomes deterministic; get it wrong and no amount of downstream tuning will recover the lost variance.

Pipeline architecture overview

The contract architecture does not run in isolation. It is the system of record that the ingestion and validation tiers query on every invoice. Carrier submissions enter through Automated Invoice Parsing & EDI/XML Ingestion, where each payload is normalized into a canonical ShipmentInvoice and stamped with a contract_version_id that pins it to an exact tariff snapshot. From there the invoice flows into Rule-Based Rate Validation & Accessorial Auditing, which reads the resolved rate matrix to compute expected charges and classify variance. The contract store sits at the centre of that loop, queried point-in-time on every lookup.

Contract architecture as the central system of record A left-to-right freight-audit pipeline runs across the top: carrier submission, ingest and normalize, rate resolution, rate validation, and audit-ready record. Beneath it sits the versioned contract store, the system of record. Dashed two-way links connect the store to the ingest, resolution, and validation stages, which respectively stamp the contract version, resolve the point-in-time tariff, and read the rate matrix and fuel surcharge. Resolution and transient failures branch down to a message broker that routes them to a quarantine table or a dead-letter queue instead of being dropped. SUBMISSION INGEST RESOLVE VALIDATE SETTLE Carrier submission EDI 210/810 · XML · PDF Ingest & normalize stamp contract_version_id Rate resolution point-in-time Rate validation variance vs tolerance Audit-ready record immutable AP ledger version stamp point-in-time resolve rate matrix + FSC Versioned contract store system of record · queried point-in-time effective dating · version_hash · amendment lineage immutable Parquet / Delta archive resolution / transient failures Message broker · Kafka / RabbitMQ Quarantine no active contract Dead-letter queue transient infra errors
The versioned contract store is the system of record at the centre of the pipeline: ingestion stamps the contract version, resolution reads the point-in-time tariff, and validation reads the rate matrix and fuel surcharge — while unresolved and transient failures branch through the broker to a quarantine table or dead-letter queue rather than being dropped.

The transport topology decouples carrier submission endpoints from the workers that resolve and validate. Invoices land via SFTP drops, AS2 transmissions, or REST webhooks, are normalized, and are published onto a message broker (Kafka or RabbitMQ) as discrete events. Resolution workers consume those events, query the contract store for the active version, and either approve the invoice or emit a dispute payload. The broker gives the pipeline back-pressure handling during carrier volume spikes and a natural quarantine path: any payload that cannot be resolved is routed to a dead-letter topic rather than silently dropped, preserving a complete audit trail for later reconciliation.

The canonical data flow is summarised below; the architecture stages map directly onto the system-of-record responsibilities this page defines.

Stage Owner Reads from contract store Writes
Submission Carrier / EDI VAN Raw EDI 210/810, XML, PDF
Ingestion Ingestion tier contract_version_id lookup Canonical ShipmentInvoice event
Resolution Contract architecture Point-in-time tariff snapshot Resolved RateContext
Validation Validation tier Active rate matrix + accessorial schedule Compliance verdict + variance
Dispute Dispute router Fallback tariff cascade Structured dispute payload
Audit Settlement Full version lineage (hashed) Immutable settlement record

Format and protocol coverage

Carrier rate data and the invoices that reference it arrive in incompatible shapes, and the contract architecture has to normalize all of them into a single rate model before resolution is possible. The ingestion routing logic inspects each payload’s transport envelope and content signature, then dispatches it to a format-specific parser. EDI 210 Motor Carrier Freight Details and Invoices and EDI 810 Invoices are parsed segment-by-segment through EDI 210/810 Processing; carrier-specific XML billing feeds route through XML Freight Bill Ingestion; and scanned or digital contract PDFs and rate sheets are handled by PDF Invoice Parsing with Python before their tariffs can be loaded into the rate store.

EDI 210 parsing in particular requires strict segment mapping so that the resolved invoice carries every field the contract store needs to find a match. The mapping below is the minimum contract for motor-carrier freight invoices.

Segment Element Maps to Notes
B3 Invoice number, SCAC, shipment date invoice_number, carrier_scac, ship_date ship_date drives point-in-time resolution
N1 Shipper / consignee identification origin, destination parties Entity codes SH / CN
N3/N4 Address, city/state/ZIP origin_zip, dest_zip ZIP3 derived for lane keying
L11 Reference identification pro_number, bol_number Used in the idempotency key
L5 Description, freight class freight_class Required for LTL class-based lookup
L0/L1 Weight, quantity, rate, charge billed_weight, billed_rate, billed_charge Compared against resolved contract
L3 Total charges total_billed Reconciled against summed line items

Because freight class, weight breaks, and lane geometry differ by mode, the contract store keys each rate matrix by mode and resolves the matching tariff family. Less-than-truckload agreements expand into class-based rate tables and discount/minimum-charge structures via LTL Rate Sheet Digitization, while full-truckload agreements resolve to lane-flat or mileage-band rates through FTL Base Rate Extraction. The routing logic is mode-aware so that an LTL invoice is never validated against an FTL rate family.

Contract and rate configuration

A resilient rate model begins with a normalized contract schema that decouples carrier identifiers, lane definitions, and pricing tiers. The core data model must support effective dating, geographic zoning, weight and quantity breakpoints, and mode-specific pricing rules. Without deterministic versioning, overlapping contract periods and mid-cycle renegotiations introduce silent audit failures that compound across thousands of monthly invoices.

Production systems implement contract version control by stamping each record with effective_start, effective_end, a version_hash, and an amendment_type such as GENERAL_RATE_INCREASE or LANE_SPECIFIC_ADJUSTMENT. This is what makes the contract_version_id on every ingested invoice meaningful: it enables point-in-time rate resolution during historical invoice reconciliation and prevents temporal bleed, where a newer rate incorrectly overwrites the pricing of a shipment that was tendered weeks earlier.

To support regulatory compliance and retrospective dispute resolution, the architecture integrates immutable rate-sheet archiving as an append-only data-lake layer. Archived rate sheets are stored in partitioned Parquet or Delta Lake format, indexed by carrier_scac, contract_id, and effective_date, so that when an EDI 210 invoice references a rate from a superseded agreement, the engine can reconstruct the exact pricing environment at the time of shipment tender.

The configuration store also holds the moving parts that change more often than base rates: fuel surcharge (FSC) indices and discount tiers. FSC indices are versioned alongside the contract because the formula and the pegged index can change independently of the base tariff; the validation tier reads them through Fuel Surcharge Formula Implementation. Discount tiers are stored as ordered breakpoints so that a single lookup resolves the applicable percentage for a given shipment weight or annual volume commitment.

Config object Keyed by Version scope Consumed by
Base rate matrix carrier_scac, mode, lane, class/weight break version_hash Resolution + validation
FSC index carrier_scac, index name Independent of base Surcharge validation
Discount tier contract_id, breakpoint Tied to contract version Net-rate computation
Accessorial schedule carrier_scac, canonical code Tied to contract version Accessorial validation

The schema below is the authoritative contract record. The model_validator computes a stable version_hash from the fields that define a contract’s pricing identity, so two records that price identically hash identically — which is what lets the archive deduplicate amendments that change metadata but not rates.

# models/contract_schema.py
from pydantic import BaseModel, Field, field_validator, model_validator
from datetime import date
from typing import Optional, Dict, Any
from enum import Enum
import hashlib
import json


class ContractStatus(str, Enum):
    ACTIVE = "ACTIVE"
    EXPIRED = "EXPIRED"
    SUSPENDED = "SUSPENDED"


class AmendmentType(str, Enum):
    GRI = "GENERAL_RATE_INCREASE"
    LANE_ADJ = "LANE_SPECIFIC_ADJUSTMENT"
    ACCESSORIAL_UPDATE = "ACCESSORIAL_UPDATE"


class RateContract(BaseModel):
    contract_id: str
    carrier_scac: str
    mode: str  # LTL, FTL, INTERMODAL
    effective_start: date
    effective_end: Optional[date] = None
    status: ContractStatus = ContractStatus.ACTIVE
    rate_matrix: Dict[str, Any] = Field(default_factory=dict)
    amendment_type: Optional[AmendmentType] = None
    version_hash: str = ""

    @field_validator("effective_end")
    @classmethod
    def validate_date_range(cls, v: Optional[date], info) -> Optional[date]:
        # Reject zero-width or inverted effective windows — these silently
        # break point-in-time resolution by matching nothing or everything.
        if v and v <= info.data.get("effective_start"):
            raise ValueError("effective_end must be strictly after effective_start")
        return v

    @model_validator(mode="before")
    @classmethod
    def compute_version_hash(cls, data: Any) -> Any:
        # Hash only the fields that define pricing identity so that a pure
        # metadata amendment does not produce a spurious new version.
        if isinstance(data, dict):
            payload = json.dumps({
                "contract_id": data.get("contract_id"),
                "carrier_scac": data.get("carrier_scac"),
                "effective_start": str(data.get("effective_start")),
                "rate_matrix_keys": sorted(data.get("rate_matrix", {}).keys()),
            }, sort_keys=True)
            data["version_hash"] = hashlib.sha256(payload.encode()).hexdigest()
        return data

Core ETL implementation

The primary loop the contract architecture exposes to the rest of the pipeline is resolve, then validate. Resolution selects the single tariff record that was in force at the shipment date; validation compares the billed charge against the contracted charge within tolerance. The two are deliberately separated so that a resolution failure (no active contract) and a validation failure (a real overcharge) route to different dispute paths.

Point-in-time resolution is the operation most likely to be implemented incorrectly. The common production bug is to select the latest contract for a carrier rather than the one active at ship_date, which silently misprices every historical or back-dated invoice. The engine below filters to records whose effective window contains the shipment date, then takes the most recent qualifying effective_start so that mid-cycle amendments win over the original agreement.

# validation/rate_engine.py
import logging
from datetime import date
from decimal import Decimal
from typing import Tuple

logger = logging.getLogger(__name__)


class RateValidationEngine:
    def __init__(self, tolerance_pct: float = 0.015):
        # Tolerance absorbs rounding drift between carrier billing systems
        # and the internal ERP; it is NOT a license to ignore real variance.
        self.tolerance = Decimal(str(tolerance_pct))

    def resolve_point_in_time(self, rate_matrix: dict, shipment_date: date) -> dict:
        """Return the single tariff record active on shipment_date."""
        active = []
        for _lane_id, rate in rate_matrix.items():
            if rate["effective_start"] <= shipment_date:
                end = rate.get("effective_end")
                if end is None or end >= shipment_date:
                    active.append(rate)
        if not active:
            # Resolution failure — distinct from a pricing dispute. Route to
            # the fallback cascade, not the overcharge queue.
            raise ValueError(f"No active contract for shipment date {shipment_date}")
        # Most recent effective_start wins so amendments override the original.
        return max(active, key=lambda r: r["effective_start"])

    def validate_linehaul(
        self, contracted_rate: Decimal, billed_rate: Decimal
    ) -> Tuple[bool, Decimal]:
        if contracted_rate == 0:
            return False, Decimal("0")
        variance = abs(billed_rate - contracted_rate) / contracted_rate
        is_compliant = variance <= self.tolerance
        if not is_compliant:
            logger.warning(
                "Rate variance %s exceeds tolerance. Contracted: %s, Billed: %s",
                f"{variance:.4%}", contracted_rate, billed_rate,
            )
        return is_compliant, variance

Once linehaul is resolved, the validation tier layers on accessorial and fuel checks. Carrier-specific accessorial codes are mapped to a canonical taxonomy through Accessorial Charge Taxonomy Mapping so that “detention”, “DET”, and “DETENT” all resolve to one schedule entry; charges that fail the schedule check are forwarded to Accessorial Charge Scoring for weighted penalty assignment. Lane geometry is confirmed against the contracted origin/destination pairs by Lane Matching Algorithms, and billed weight and zone are reconciled by Weight & Zone Cross-Validation before the variance verdict is finalised.

Idempotency and deduplication

Carrier feeds redeliver. The same invoice will arrive twice over AS2 after a transmission retry, appear again in a nightly portal export, and sometimes be re-sent by the carrier with a corrected total. If the pipeline processes each arrival blindly, a single shipment can be approved and paid more than once. Idempotency is therefore a first-class property of the contract architecture, not an afterthought.

The deduplication key is derived deterministically from the fields that uniquely identify a freight movement — never from an auto-increment id or a receipt timestamp. A stable composite of carrier, invoice number, and PRO survives retransmission and re-export unchanged.

# pipeline/idempotency.py
import hashlib
from typing import Optional


def ingestion_key(carrier_scac: str, invoice_number: str, pro_number: str) -> str:
    """Deterministic key — identical for every redelivery of one shipment."""
    raw = f"{carrier_scac.strip().upper()}|{invoice_number.strip()}|{pro_number.strip()}"
    return hashlib.sha256(raw.encode()).hexdigest()


def claim_or_skip(store, key: str, payload_hash: str) -> Optional[str]:
    """
    Atomically claim an ingestion key.
    Returns the disposition: 'new', 'duplicate', or 'amended'.
    """
    existing = store.get(key)
    if existing is None:
        store.put(key, payload_hash)        # first sighting
        return "new"
    if existing == payload_hash:
        return "duplicate"                  # byte-identical redelivery, skip
    store.put(key, payload_hash)
    return "amended"                        # same shipment, changed charges

The disposition matters: a byte-identical duplicate is skipped silently, but an amended redelivery (same key, different payload hash) is a real correction that must re-enter resolution. A common mistake is to treat any repeated key as a duplicate, which causes corrected invoices to be dropped and the original — possibly wrong — charge to stand. The retry safety guarantee is that processing the same payload any number of times yields exactly one settlement record.

Observability and error handling

A freight audit pipeline that cannot explain why an invoice was approved, disputed, or quarantined is unauditable, which defeats its purpose. Every resolution decision emits a structured log line carrying the invoice id, the resolved contract_version_id, the computed variance, and the disposition. Logs are structured (JSON) so that they are queryable, because “grep the worker output” does not scale to a SOX audit request six months later.

Three routing destinations cover every outcome. Invoices that resolve and validate flow to settlement. Invoices that fail resolution — no active contract, malformed mandatory segment — go to a quarantine table with the raw payload preserved for reprocessing once the missing contract is digitized. Invoices that exhaust retries on transient infrastructure errors go to a dead-letter queue with the failure context attached.

# pipeline/observability.py
import logging

logger = logging.getLogger("freight_audit")


def emit_decision(invoice_id: str, version_id: str, variance, disposition: str) -> None:
    logger.info(
        "audit_decision",
        extra={
            "invoice_id": invoice_id,
            "contract_version_id": version_id,
            "variance_pct": f"{variance:.4%}" if variance is not None else None,
            "disposition": disposition,  # approved | disputed | quarantined
        },
    )


def quarantine(store, invoice_id: str, raw_payload: bytes, reason: str) -> None:
    # Preserve the raw bytes so the invoice can be replayed unchanged once
    # the missing contract or fix lands. Never discard the original payload.
    store.write_quarantine(invoice_id=invoice_id, payload=raw_payload, reason=reason)
    logger.warning("quarantined", extra={"invoice_id": invoice_id, "reason": reason})

Alert thresholds are derived from the variance and quarantine rates, not from raw error counts. A quarantine rate above a configured ceiling for a single carrier usually means a contract lapsed or an amendment was never digitized — the alert points procurement at the specific carrier_scac. Threshold derivation and alert routing are owned by Threshold Tuning & Alerting, which the contract architecture feeds with its decision stream.

Performance and scaling

Freight audit volume is bursty: month-end and carrier batch drops can deliver hundreds of thousands of invoices in a window. The resolution and validation work is I/O-bound (contract-store lookups) with a CPU-bound core (Decimal arithmetic over rate matrices), so the scaling model is a bounded-concurrency worker pool that caps in-flight lookups while keeping the CPU saturated. Large carrier drops are chunked into micro-batches through Async Batch Processing Workflows rather than loaded into memory whole.

# pipeline/concurrency.py
import asyncio
from typing import Awaitable, Callable, List


async def run_pool(
    invoices: List[dict],
    handler: Callable[[dict], Awaitable[dict]],
    max_concurrency: int = 32,
) -> List[dict]:
    """Bounded fan-out: cap concurrent contract-store lookups."""
    sem = asyncio.Semaphore(max_concurrency)

    async def _guarded(inv: dict) -> dict:
        async with sem:               # back-pressure against the rate store
            return await handler(inv)

    # return_exceptions keeps one poison invoice from killing the batch.
    return await asyncio.gather(*(_guarded(i) for i in invoices), return_exceptions=True)

The tuning guidance below is a starting point; profile against the real contract store before committing. Resolution caching helps disproportionately because most invoices in a batch hit the same handful of active contracts.

Knob Typical range Effect Watch for
max_concurrency 16–64 Caps in-flight store lookups Lock contention on the rate store above ~64
Micro-batch size 250–1,000 Memory vs. scheduling overhead OOM on wide rate matrices at large sizes
Resolution cache TTL 5–15 min Reuses active-contract lookups Stale tariff after a mid-window amendment
Worker count 1 per core CPU saturation for Decimal math GIL contention — prefer processes for CPU-heavy modes

As a rough benchmark, a single worker resolving and validating cached-tariff LTL invoices sustains the low thousands per second; uncached resolution against a cold Parquet archive is one to two orders of magnitude slower, which is why the active-contract cache is load-bearing rather than optional.

Failure modes and troubleshooting

Most production incidents in this layer trace back to a small set of recurring causes. The table maps the symptom an operator sees to the root cause and the resolution path.

Symptom Root cause Resolution
Every invoice for a carrier disputes at once Contract lapsed; effective_end passed with no renewal Digitize renewal; replay quarantine; alert procurement on the SCAC
Historical invoices mispriced after a re-run Resolution selected latest contract, not point-in-time Use resolve_point_in_time; key by ship_date, not now()
Duplicate settlements for one PRO Idempotency key built from a non-stable field Rebuild key from SCAC + invoice + PRO; treat repeat keys via claim_or_skip
Resolution finds two active contracts Overlapping effective windows from a bad amendment Enforce strictly-after window validator; take max effective_start
Fuel surcharge variance on every line FSC index version drifted from base contract Re-pin FSC index version; validate via the surcharge module
LTL invoice validated against FTL rates Mode not carried into routing Make routing mode-aware; assert mode on the resolved record

When an invoice quarantines, the first diagnostic is always to re-resolve it in isolation against the archive at its exact ship_date — that immediately separates a genuine missing-contract case from a segment-drift parsing problem upstream in ingestion.

Rate-mapping modules

The contract architecture is implemented across four focused modules. Each digitizes or validates one slice of the tariff and feeds the resolved rate matrix this page describes.

  • LTL Rate Sheet Digitization — Converts class-based PDF and Excel rate sheets into version-controlled, lookup-ready tables keyed by ZIP3, freight class, and weight break.
  • FTL Base Rate Extraction — Resolves full-truckload lane-flat and mileage-band rates with equipment-type modifiers from negotiated FTL agreements.
  • Accessorial Charge Taxonomy Mapping — Normalizes carrier-specific accessorial codes to a canonical taxonomy and validates them against the contracted schedule.
  • Fuel Surcharge Formula Implementation — Implements DOE-indexed and contract-pegged FSC formulas, versioned independently of the base tariff.

Up: Freight Bill Auditing & Carrier Rate Contract Automation — the freight audit pipeline home.