Spot Scheduling Validation & Rule Engines

Broadcast traffic and advertising scheduling operate at the intersection of binding contractual obligations, strict regulatory mandates, and deterministic playout requirements. At the core of this ecosystem sits the validation pipeline and rule engine: a deterministic architecture that ingests raw ad orders, evaluates them against temporal, technical, and compliance constraints, resolves conflicts, and emits an executable traffic log. This guide establishes the end-to-end workflow, defines the operational taxonomy, and delineates the system boundaries that separate validation from the surrounding traffic stack. It builds on the entity model set out in Broadcast Traffic Architecture & Taxonomy and consumes normalized records produced by Avion & Avstar Ingestion Pipelines. The audience is broadcast traffic managers, media operations engineers, ad tech developers, and Python automation builders who need production-ready, auditable, FCC-compliant scheduling infrastructure. Traffic managers get plain-language framing before each subsystem; developers get deployable code with strict typing and structured logging.

Everything downstream of this engine — rule-based rotation logic, time slot conflict detection, make-good routing for preemptions, and threshold tuning for scheduling accuracy — depends on the taxonomy and contracts defined here. Each is a bounded subsystem with explicit input and output schemas, so validation stays idempotent and scheduling stays deterministic.

Spot scheduling validation system boundary Raw ad orders cross an ingestion and normalization boundary into the deterministic rule engine, which evaluates constraints, resolves conflicts, and generates a versioned traffic log for playout. A reconciliation loop feeds Proof-of-Play back to ingestion. Creative transcoding, financial billing, and master control sit outside the engine boundary and integrate only through contracts. Raw Ad Orders OMS exports XML / JSON APIs manual uploads Ingestion & Normalization EDI / SOAP · Pydantic legacy codes → UTC, typed DETERMINISTIC RULE ENGINE stateless · idempotent on spot_id · replayable Constraint Evaluation hard → soft rules Conflict Resolution priority · SLA Log Generation versioned · hashed isolated from playout — integrates via versioned API contracts only Versioned Traffic Log → playout servers, automation, routers Proof-of-Play reconciliation Outside the boundary — owned elsewhere, reached only through strict contracts Creative Asset Transcoding Financial Reconciliation / Billing Linear Master Control Playout

Figure — The validation engine as a bounded system: raw orders cross an ingestion and normalization edge into the deterministic core (constraint evaluation → conflict resolution → log generation), which emits a versioned traffic log to playout. Proof-of-Play reconciliation closes the loop, while transcoding, billing, and master control stay outside the boundary.

Core Taxonomy & Data Model

A precise vocabulary decouples validation logic from downstream playout and billing systems. Every entity below is a schedulable or evaluable unit with a stable schema; the normalized hierarchy runs spot → avail → order → campaign, and validation operates primarily on the spot and avail levels while honoring order- and campaign-level constraints.

  • Spot / Order Line: A discrete advertising unit carrying immutable metadata — advertiser, campaign ID, duration, daypart, priority tier, and contractual constraints (competitive separation, mandatory placement windows). The canonical field definitions live in Understanding Broadcast Spot Schemas and Metadata.
  • Avail: A time-bound, channel-specific inventory container into which spots are placed. Avail modeling and clearance windows are covered in Avails Mapping Strategies for Linear TV.
  • Traffic Log: The time-indexed schedule artifact (XML, JSON, or proprietary fixed-width CSV) consumed by automation systems, traffic routers, and master control.
  • Validation Rule: A declarative or programmatic constraint applied during scheduling — temporal windows, competitive adjacency, regulatory mandates, and technical codec/format requirements.
  • Constraint Satisfaction Problem (CSP): The mathematical formulation that maps discrete spots to finite time slots without violating hard constraints, solved via backtracking, SAT solvers, or heuristic optimization.
  • Preemption: The forced displacement of scheduled inventory due to breaking news, sports overruns, or Emergency Alert System (EAS) activations.
  • Make-Good: Compensatory inventory automatically routed to fulfill displaced contractual obligations, preserving advertiser SLAs and revenue recognition.
  • Schedule Drift: The temporal delta between the validated schedule and actual playout, measured in seconds or sub-second frames, typically caused by encoder latency, manual overrides, or network jitter.
  • Proof-of-Play (PoP): Post-broadcast reconciliation data used for billing verification, compliance auditing, and regulatory reporting.

The rule engine reads a normalized subset of the canonical spot schema. The fields below are the ones validation actually evaluates; the ingestion layer guarantees their types before a record reaches the solver.

Field Type Constraint Role in validation
spot_id str (UUIDv5) non-null, namespace-bound, unique Idempotency key; deduplicates placements
avail_id str FK → avail table Binds spot to a legal inventory window
advertiser_id str non-null Drives competitive-separation grouping
product_category str (enum) controlled vocabulary Adjacency / separation constraint input
duration_frames int > 0, ≤ avail length Hard constraint; rejects zero-duration avails
daypart str (enum) must intersect contracted window Temporal placement constraint
priority_tier int 0–9, 0 = highest Conflict-resolution ordering
separation_seconds int ≥ contract minimum Competitive-separation hard constraint
political_flag bool Triggers FCC political file handling
billing_code str alphanumeric, ≥ 4 chars Revenue attribution; reconciliation join key

System boundaries. This engine governs validation logic, rule evaluation, conflict resolution, compliance checking, and drift telemetry. It explicitly excludes creative asset transcoding, financial reconciliation, and linear master control playout — though it defines strict API contracts and data schemas for integration with those domains. Legacy integrations (mainframe exports, fixed-width traffic dumps, proprietary XML dialects) are resolved at the ingestion boundary, never inside the core constraint solver, which keeps the solver deterministic and replayable.

End-to-End Workflow

The scheduling automation pipeline is a linear, state-managed progression with explicit handoff points. Every stage enforces schema validation, cryptographic hashing for audit trails, and timezone-aware temporal normalization to UTC before any comparison is made.

1. Ingestion & schema normalization

Orders arrive via order-management exports, XML/JSON APIs, or manual traffic uploads. The ingestion layer strips vendor-specific noise, normalizes daypart boundaries, and maps legacy field codes to the canonical internal schema. Pydantic models enforce strict typing and reject malformed payloads before they reach the rule engine — the full validator pattern is documented in Schema Validation with Pydantic for Traffic Data. Timezone offsets resolve to UTC with explicit daylight-saving handling to prevent cross-market scheduling collisions.

2. Constraint evaluation & rule execution

Normalized orders enter the constraint solver, where declarative rules compile into an executable evaluation graph. Hard constraints (EAS blackout windows, political-file mandates, FCC competitive separation) evaluate first; soft constraints (optimal daypart weighting, frequency caps) are scored and weighted. The architecture supports pluggable rule sets, which is what lets Building Rule Engines for Spot Rotation extend behavior without touching the core solver. Rule evaluation is stateless and idempotent, allowing safe retries and deterministic replay during audit investigations.

3. Conflict resolution & preemption handling

When multiple orders compete for finite inventory, the engine applies priority tiers, contractual SLAs, and fallback routing. Overlaps are flagged, resolved, or escalated by configurable severity thresholds. For time-critical overlaps, the system runs Detecting Time Slot Conflicts in Traffic Logs to isolate exact collision boundaries and propose displacement candidates. When preemptions occur from live programming or EAS activations, the pipeline triggers Automating Make-Good Routing for Preemptions, rebooking displaced spots into compliant windows without manual traffic intervention.

4. Deterministic log generation & dispatch

Once constraints are satisfied, the engine emits a finalized traffic log. Output formats are strictly versioned and carry embedded checksums, generation timestamps, and rule-evaluation summaries. Logs dispatch to automation controllers, playout servers, and traffic routers over authenticated endpoints. To hold broadcast-grade precision, operators rely on Tuning Thresholds for Scheduling Accuracy, which calibrates buffer tolerances, encoder lead times, and splice-point alignments against historical playout performance.

5. Playout handoff & as-run reconciliation

After transmission, the pipeline ingests Proof-of-Play data, master control logs, and third-party verification feeds. Automated reconciliation compares scheduled versus actual air times and flags deviations beyond acceptable drift margins. Every reconciliation artifact is written to an immutable, append-only ledger to satisfy FCC public-inspection and internal audit requirements. Variances close the loop back to make-good routing or billing adjustment.

Multi-axis tolerance check for candidate placements A candidate placement enters a multi-axis tolerance check that evaluates three axes in parallel: temporal drift, inventory saturation, and rotation or separation. Their results feed a threshold decision. Placements within thresholds commit to the schedule; breaches route to auto-correction, manual review, or make-good. Candidate Placement Multi-axis Tolerance Check evaluated in parallel Temporal drift schedule vs. playout Δ Inventory saturation avail fill · frequency caps Rotation / separation competitive adjacency Within thresholds? yes Commit to schedule no Auto-correct · Manual review · Make-good

Figure — Candidate placements pass a multi-axis tolerance check (temporal, inventory, rotation); those within thresholds commit, while breaches route to auto-correction, manual review, or make-good.

Architectural Boundaries & Integration Patterns

The engine must integrate with systems it does not own — order management, automation controllers, billing — without letting their coupling leak into the solver. Three patterns keep those boundaries clean.

API contracts over shared database access. The engine never reads another system’s tables directly. Order data enters through a versioned ingestion contract; finalized logs leave through a versioned dispatch contract. Direct DB coupling would make every schema change in a neighboring system a potential source of silent validation corruption. Where a downstream consumer needs live state, it subscribes to events rather than polling internal tables. The database-access hardening that enforces this separation is detailed in Security Boundaries for Traffic Database Access.

Legacy EDI / SOAP bridges at the edge. Older order feeds arrive as EDI documents or SOAP envelopes with proprietary XML dialects. These are translated to the canonical schema by dedicated adapter services that live entirely at the ingestion boundary. The adapter owns all dialect quirks — fixed-width padding, non-UTC timestamps, vendor billing codes normalized per Standardizing Billing Codes Across Traffic Systems — so the solver only ever sees clean, typed records.

Message-broker decoupling. A broker (RabbitMQ or Kafka) sits between ingestion, evaluation, and dispatch. Each stage consumes from a durable queue and publishes to the next, which gives back-pressure handling, at-least-once delivery, and replay. Because rule evaluation is idempotent, at-least-once delivery is safe: a redelivered order produces the same spot_id-keyed result and is deduplicated on commit. Kafka’s partitioning by avail_id also guarantees that all placements competing for one inventory window are evaluated in order on a single consumer, eliminating a whole class of race conditions in conflict resolution.

Python Automation Stack

The reference implementation is a typed, async Python service. Pydantic models enforce the schema contract at the ingestion boundary; asyncio handles I/O-bound fan-out to order feeds and dispatch endpoints; SQLAlchemy over a TimescaleDB hypertable stores drift telemetry and the append-only audit ledger. Every mutating operation is idempotent on spot_id, and every state transition emits a structured log line in the traffic-ops format timestamp | level | module | message so SOC 2 and ISO 27001 audit trails can be reconstructed from logs alone.

The snippet below is the core validation entry point: it validates an incoming order against hard constraints and records the outcome to the audit log. It is deployable as-is against a Pydantic v2 environment.

python
from __future__ import annotations

import logging
from datetime import datetime, timezone
from enum import Enum

from pydantic import BaseModel, Field, field_validator

# Traffic-ops structured logging: timestamp | level | module | message
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
)
logger = logging.getLogger("rule_engine.validate")


class Daypart(str, Enum):
    EARLY_MORNING = "early_morning"
    DAYTIME = "daytime"
    PRIME = "prime"
    LATE_NIGHT = "late_night"


class SpotOrder(BaseModel):
    """Normalized order line the solver evaluates. All fields are pre-typed
    by the ingestion boundary; validators here enforce hard constraints."""

    spot_id: str = Field(..., min_length=1)          # UUIDv5 idempotency key
    avail_id: str
    advertiser_id: str
    product_category: str
    duration_frames: int = Field(..., gt=0)          # zero-duration avails are illegal
    avail_length_frames: int = Field(..., gt=0)
    daypart: Daypart
    priority_tier: int = Field(..., ge=0, le=9)      # 0 = highest priority
    separation_seconds: int = Field(..., ge=0)
    contract_min_separation: int = Field(..., ge=0)
    political_flag: bool = False
    billing_code: str = Field(..., min_length=4)

    @field_validator("billing_code")
    @classmethod
    def billing_code_alnum(cls, v: str) -> str:
        code = v.strip().upper()
        if not code.isalnum():
            raise ValueError("billing_code must be alphanumeric for reconciliation")
        return code


class ValidationResult(BaseModel):
    spot_id: str
    accepted: bool
    violations: list[str] = Field(default_factory=list)
    evaluated_at: str


def validate_order(order: SpotOrder) -> ValidationResult:
    """Evaluate hard constraints for a single order. Deterministic and
    idempotent: same input -> same result, keyed on spot_id."""
    violations: list[str] = []

    # Hard constraint: spot must fit inside its avail window.
    if order.duration_frames > order.avail_length_frames:
        violations.append("duration_exceeds_avail")

    # Hard constraint: competitive separation must meet the contract floor.
    if order.separation_seconds < order.contract_min_separation:
        violations.append("separation_below_contract")

    # Hard constraint: political inventory must carry a resolvable billing code.
    if order.political_flag and not order.billing_code:
        violations.append("political_spot_missing_billing_code")

    accepted = not violations
    level = logging.INFO if accepted else logging.WARNING
    logger.log(
        level,
        "spot_id=%s avail_id=%s accepted=%s violations=%s",
        order.spot_id, order.avail_id, accepted, ",".join(violations) or "none",
    )
    return ValidationResult(
        spot_id=order.spot_id,
        accepted=accepted,
        violations=violations,
        evaluated_at=datetime.now(timezone.utc).isoformat(),
    )

A representative accepted-path log line reads 2026-07-03T14:22:07+00:00 | INFO | rule_engine.validate | spot_id=6ba7… avail_id=A-0912 accepted=True violations=none. Because the function is pure over its input, the same order replayed during an audit produces a byte-identical result, which is the property SOC 2 evidence collection depends on.

Compliance & Regulatory Constraints

Compliance is not a bolt-on; several FCC and technical mandates are hard constraints evaluated in the same pass as inventory rules.

FCC political file. Any order with political_flag set is subject to lowest-unit-charge rules and public-inspection-file disclosure. The engine refuses to commit a political spot whose billing code cannot be resolved to a candidate, sponsor, and rate, because a missing disclosure is a regulatory violation, not a soft warning. Political records are written to the immutable ledger with their full evaluation trace.

SCTE-104 / SCTE-35 signaling. Finalized logs must carry splice-point metadata that the automation system translates into SCTE-104 messages upstream of the encoder, which in turn inserts SCTE-35 cue messages into the transport stream. The engine validates that every commercial break boundary has a well-formed splice descriptor and that avail durations align to frame-accurate splice points; misaligned splices are the most common cause of clipped or overlapping spots at air.

EAS halt protocol. An Emergency Alert System activation is an unconditional preemption. The engine treats an active EAS window as a hard blackout: no placement may commit inside it, and any already-scheduled spot intersecting the window is displaced and handed to make-good routing. This rule outranks every contractual guarantee, including political mandates.

Audit-log immutability. Every accepted placement, every rejection, and every reconciliation variance is appended to a write-once ledger with a content hash chaining each entry to its predecessor. Records are never updated in place; a correction is a new entry that references the original. This is what makes the schedule defensible under both FCC inspection and financial audit.

Failure Modes & Resilience

A scheduling engine that fails unsafely can put non-compliant or double-booked inventory to air. The design assumes failure and contains it.

  • Circuit breakers. When a downstream dependency (billing lookup, SCTE validator, dispatch endpoint) exceeds a failure threshold, a circuit breaker opens and the pipeline stops committing rather than emitting unvalidated logs. The breaker transitions to half-open after a recovery timeout, admits probe traffic, and closes on first success. The concrete state-machine implementation is walked through in Tuning Thresholds for Scheduling Accuracy.
  • Make-good fallback. If conflict resolution cannot place a contracted spot in its primary window, it does not silently drop the obligation — it routes the spot to make-good handling, which finds the next compliant window that preserves the advertiser SLA.
  • Network-partition handling. Because stages communicate through a durable broker with at-least-once delivery, a partition between ingestion and evaluation delays throughput but never loses orders; queued work drains on recovery, and idempotency prevents the redelivered backlog from producing duplicates.
  • Duplicate-placement prevention. The spot_id UUIDv5 key is the deduplication guarantee across the whole pipeline. A commit is a conditional insert keyed on spot_id; a replayed or redelivered order collides on the key and is rejected as an already-committed no-op, so the same spot can never air twice from a retry.

Taken together, these boundaries, contracts, and failure containments give a deterministic, auditable foundation for modern broadcast traffic operations: strict schema enforcement at the edge, constraint evaluation isolated from playout, and compliance telemetry embedded in every stage.