Above/Below-the-Line Mapping
Above/below-the-line mapping is the deterministic routing layer that decides whether every payroll disbursement, deal memo, and vendor invoice lands in a creative-talent cost center or a technical-crew one before it reaches the general ledger. Get that boundary wrong on a single transaction and the error compounds: fringe multipliers are miscalculated, pension and health contributions are booked against the wrong basis, and a completion guarantor reading the weekly cost report finds a variance nobody can explain. Within the broader Core Production Architecture & Taxonomy, this mapping owns compensation-tier resolution — the stage between raw ingestion and the immutable ledger where a transaction is classified as above-the-line (ATL) or below-the-line (BTL) and can never silently drift afterward. This guide specifies that layer as a stateful validation pipeline: normalize at the edge, derive the tier from the account code rather than trusting the input, reject mismatches, and hash every decision into an audit trail a lender can reconstruct line by line.
Prerequisites
This pipeline targets Python 3.11+ (for enum.StrEnum and zoneinfo, both standard library) and assumes the following in your environment:
- Pydantic v2 (
>=2.6) for strict edge validation usingmodel_validateandfield_validator— the classification engine never sees an unvalidated payload. decimal.Decimalfor every monetary field. Floating-point arithmetic is prohibited: a single fractional cent multiplied across thousands of daily line items becomes a variance a guarantor will ask about.zoneinfofor timezone-aware timestamps keyed to IANA identifiers (for exampleAmerica/Los_Angelesfor a studio ledger), never fixed UTC offsets that break across daylight-saving boundaries on long shoots.hashlibfor SHA-256 payload hashing on both the classified and quarantined paths.- A cost-code map — the canonical account-code-to-tier table produced upstream by Cost Code Standardization. This mapping treats codes as immutable identifiers, so the tier boundary stays stable from budget lock through wrap.
Expected inputs are heterogeneous: EP/Showbiz payroll exports, vendor invoices, and fragmented deal memos. The normalization and concurrent-fetch mechanics that deliver those records belong to Cost Ingestion & Data Parsing Workflows; this layer is what every ingested record must resolve against once it arrives.
Architecture: the classification pipeline
The evaluator enforces a strict precedence. A transaction is validated at the edge first; only a structurally valid payload reaches the classifier. The classifier then resolves the tier in a fixed order — ATL guild jurisdiction wins first, then the department-code map, then a conservative BTL default — and every terminal state is sealed with a SHA-256 hash before it syncs to the ledger. Nothing reaches the general ledger by a side door: a payload with malformed department tags or a missing guild identifier is quarantined into a staging queue for manual reconciliation rather than halting the accounting cycle.
The decision flow below captures that precedence: edge validation first, then ATL guild jurisdiction, then the department-code map, falling back to BTL, with every branch converging on an audit hash and ledger sync.
The boundary itself is fundamentally a two-tier taxonomy with a guild-jurisdiction override, and it is worth seeing the two columns side by side — which roles and accounts sit above the line, which sit below, and where the guild map cuts across the department default.
Core implementation
The pattern that enforces the boundary is a validated classification model. Every line item is coerced through a strict, frozen schema at ingestion; the tier is derived from guild jurisdiction and the account-code map rather than accepted from the caller; and any transaction that fails validation is diverted, not committed. The model below spells out the guild abbreviations on first use — Screen Actors Guild-American Federation of Television and Radio Artists (SAG-AFTRA), Directors Guild of America (DGA), and Writers Guild of America (WGA) cover ATL roles, while the International Alliance of Theatrical Stage Employees (IATSE) and the Teamsters cover BTL crew and are resolved through the department-code map rather than guild affiliation alone.
import hashlib
from decimal import Decimal, ROUND_HALF_UP, InvalidOperation
from enum import StrEnum
from typing import Optional
from datetime import datetime
from zoneinfo import ZoneInfo
from pydantic import BaseModel, ConfigDict, Field, field_validator, ValidationError
# Studio ledger timezone: an IANA identifier, so timestamps stay correct
# across daylight-saving transitions on a multi-month shoot.
LEDGER_TZ = ZoneInfo("America/Los_Angeles")
class CostCenterLevel(StrEnum):
ATL = "above_the_line"
BTL = "below_the_line"
# Guilds whose covered roles are paid above the line: principal cast (SAG-AFTRA),
# directors (DGA), and writers (WGA). IATSE and Teamster crew are below the line
# and are resolved through the department-code map, not guild affiliation alone.
ATL_GUILDS = frozenset({"SAG-AFTRA", "DGA", "WGA"})
class TransactionPayload(BaseModel):
model_config = ConfigDict(strict=True, frozen=True, extra="forbid")
transaction_id: str
talent_id: Optional[str] = None
department_code: str
union_guild: Optional[str] = None
gross_amount: Decimal
compensation_type: str
asserted_level: Optional[CostCenterLevel] = None
timestamp: datetime = Field(default_factory=lambda: datetime.now(LEDGER_TZ))
@field_validator("gross_amount")
@classmethod
def validate_amount(cls, v: Decimal) -> Decimal:
if v <= 0:
raise ValueError("Transaction amount must be positive")
return v.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
class TierMismatchError(ValueError):
"""Raised when the caller's asserted tier disagrees with the derived tier."""
class ClassificationRuleEngine:
def __init__(self, cost_center_map: dict[str, CostCenterLevel]):
# Canonical account-code -> tier map, locked at budget lock.
self.cost_center_map = cost_center_map
def evaluate(self, payload: TransactionPayload) -> CostCenterLevel:
# Precedence: ATL guild jurisdiction > department-code mapping > BTL default.
if payload.union_guild in ATL_GUILDS:
derived = CostCenterLevel.ATL
else:
derived = self.cost_center_map.get(payload.department_code, CostCenterLevel.BTL)
# A caller may assert a tier, but the derived tier always wins — and a
# disagreement is a hard error, never a silent override.
if payload.asserted_level is not None and payload.asserted_level != derived:
raise TierMismatchError(
f"asserted {payload.asserted_level} != derived {derived} "
f"for dept {payload.department_code}"
)
return derived
def _audit_hash(*parts: str) -> str:
return hashlib.sha256("|".join(parts).encode("utf-8")).hexdigest()
def process_transaction(raw: dict, engine: ClassificationRuleEngine) -> dict:
try:
payload = TransactionPayload.model_validate(raw)
level = engine.evaluate(payload)
digest = _audit_hash(payload.transaction_id, str(payload.gross_amount), level.value)
return {
"status": "classified",
"cost_center": level.value,
"audit_hash": digest,
"timestamp": payload.timestamp.isoformat(),
}
except (ValidationError, TierMismatchError, InvalidOperation) as exc:
# Quarantine path is also hashed, so a rejected record is auditable too.
quarantine_digest = _audit_hash(
str(raw.get("transaction_id", "UNKNOWN")),
type(exc).__name__,
)
return {
"status": "quarantined",
"reason_code": type(exc).__name__,
"error": str(exc),
"audit_hash": quarantine_digest,
}
Pydantic performs the edge validation, so malformed payloads never reach the classifier. The evaluator enforces the precedence explicitly and — crucially — treats a caller-asserted tier that disagrees with the derived tier as a hard TierMismatchError rather than trusting the input. This is what keeps the mapping honest once the budget is locked: a coordinator cannot re-book a director’s fee below the line by mislabeling the account. Because the tier boundary is derived from the canonical map, keeping that map immutable across parallel units is the related concern covered in Production Schema Design.
The evaluator must resolve hybrid roles — a director who also holds an executive producer credit, or a writer stepping into showrunner duties. Precedence handles this cleanly: ATL guild jurisdiction is checked first, so a DGA-covered director resolves ATL regardless of any secondary department code, and contractual caps applied downstream then supersede departmental defaults.
Guild and contract specifics
The ATL/BTL boundary is not accounting convention — it is the trigger for real contractual obligations, and the applicable collective bargaining agreement (CBA) determines how the classified amount is then loaded with fringes.
- SAG-AFTRA (principal cast, ATL). Classified ATL cast wages become the basis for pension and health contributions and for residuals. The contribution and residual mechanics are covered in Pension & Health Fund Calculations and SAG-AFTRA Residuals Logic; this layer’s job is to guarantee the wage is booked in the ATL cost center that feeds those calculations in the first place.
- DGA (directors and the directorial team, ATL). Beyond salary, a misclassified directorial line item can distort the base used for turnaround and overtime penalty checks. The penalty triggers themselves live in DGA Overtime & Turnaround Rules, which depend on the department being resolved correctly here.
- WGA (writers, ATL). Writing services and script fees route ATL and carry their own residual basis; a writer-producer’s fee must not leak into a BTL production-office code.
- IATSE and the Teamsters (crew, BTL). These roles are resolved by department code, not guild affiliation — an IATSE grip and a Teamster driver both land BTL through the map. Their fringe multipliers (health and welfare caps, vacation and holiday pay) apply to the BTL basis, so a crew wage misrouted to ATL would silently under- or over-state those loads.
A typical rate/fringe table is keyed by (department_code, guild, fringe_type) and stores each multiplier as a Decimal, so the classified tier selects the correct fringe set deterministically. When a required rate table is missing — a common event when a new location’s local rates haven’t been loaded — resolution should degrade through the documented Compliance Fallback Chains rather than guessing. Completion bond lenders require that ATL liabilities stay transparently separated from BTL operational costs and that the mapping stays immutable once the budget is locked, so that a contingency draw is always tracked against the correct compensation tier.
Error handling and quarantine
Rather than halting the accounting cycle on bad data, the pipeline quarantines. Three failure classes route to the staging queue: a schema failure (missing department code, non-positive amount, unexpected field under extra="forbid"), a TierMismatchError (the caller asserted a tier the derived logic rejects), and an InvalidOperation from a malformed Decimal. Each rejected record is serialized with a machine-readable reason_code and a SHA-256 hash so the quarantine path is as auditable as the committed path — a rejected record is never a lost record.
Quarantined transactions carry their original payload forward for manual reconciliation by the production accountant. Who is permitted to view, override, or re-classify a quarantined item is governed by Security & Access Boundaries: compensation data stays compartmentalized, and an emergency reclassification on a weather day runs through a time-bound, multi-approval workflow that logs a cryptographic signature, auto-reverts unless ratified by the line producer and production accountant, and never bypasses the underlying tier-derivation logic. The broader parsing-failure taxonomy — malformed delimiters, BOM headers, dead-letter routing — is handled upstream in Schema Validation & Error Handling.
Verification
Confirming the mapping is correct means checking three artifacts, not just eyeballing a total:
- Ledger entries. Every
classifiedresult should produce exactly one general-ledger posting whose cost center matches the derived tier. Reconcile ATL postings against the approved ATL cap: the sum of ATL-classified actuals must never exceed the locked ATL budget without a logged override. Aguild in ATL_GUILDStransaction must always land ATL regardless of its department code — assert this in a unit test seeded with a director who also carries a producer credit. - Audit log fields. Each record — classified and quarantined — must carry
transaction_id,cost_center(orreason_code),audit_hash, and a timezone-awaretimestamp. Recomputing the SHA-256 over the stored fields must reproduce the recordedaudit_hashbyte for byte; a mismatch means the ledger was mutated after the fact. - Reconciliation report shape. The nightly report should net to zero unclassified transactions:
count(classified) + count(quarantined) == count(ingested). Any drift means records left the pipeline through a path that skipped hashing. Wire this assertion into continuous integration so it re-runs against evolving CBA rate templates and lender reporting formats.
Treating ATL/BTL mapping as a deterministic, compliance-first routing system — validated at the edge, derived rather than asserted, and hashed on every branch — is what gives production accounting teams real-time financial visibility, automated union compliance, and an audit-ready ledger a guarantor can trust. For the canonical validation-framework reference used above, consult the official Pydantic documentation, and the Python enum.StrEnum documentation for the tier-enum pattern.
Related
- Cost Code Standardization — the immutable account-code taxonomy this mapping resolves each transaction against.
- Production Schema Design — how the tier boundary and its history are persisted so the mapping stays stable across parallel units.
- Security & Access Boundaries — who may view, override, or reclassify a compensation-tier record.
- DGA Overtime & Turnaround Rules — the penalty checks that depend on directorial line items being classified correctly here.
- SAG-AFTRA Residuals Logic — the residual basis fed by ATL cast wages routed through this layer.
Up one level: Core Production Architecture & Taxonomy.