Skip to content

Date Window Alignment Checks

Temporal misalignment is the single largest driver of accrual leakage, overpayment, and audit exposure in vendor rebate and trade promotion reconciliation. Date window alignment is the sub-problem of proving — deterministically and reproducibly — that every sales transaction, shipment record, and claim line falls inside the contractual promotion period before any money is accrued. It is the temporal predicate layer of the broader claim validation rule engine: the parent framework decides whether a claim is valid, but it cannot do so until this layer establishes a single, unambiguous timeline against which every other rule executes. Without rigorous boundary enforcement, volume-based rebates compound incorrectly, off-promo sales trigger false positives, and finance teams face material restatements during quarterly close.

This page specifies the entity schema for promotion windows, the conditional logic that encodes inclusive and exclusive boundaries, the settlement implications of proration at period edges, the ETL patterns that make alignment idempotent, and the drift-detection and dispute-routing controls that keep the timeline auditable under SOX.

Promotion window boundary timeline A horizontal timeline with an inclusive start boundary and an exclusive end boundary, flanked by symmetric tolerance grace zones. A transaction inside the window is accrued, a transaction in the tolerance grace zone is routed to scoring, and an off-promo transaction is rejected. promo_start (inclusive) promo_end (exclusive) promotion window time [ ) inside window accrued tolerance band routed to scoring off-promo rejected
An inclusive lower bound and exclusive upper bound define the canonical window; the symmetric tolerance_hours grace zones straddle each boundary and divert borderline transactions to scoring rather than auto-rejecting them.

Positioning within the rule engine

Date window alignment runs first in the evaluation order because every downstream rule depends on the timeline it produces. Identity resolution, channel scoping, and quantitative checks all assume that a transaction’s transaction_date has already been normalized and tested against a canonical window. When this layer is skipped or implemented loosely, errors cascade: misaligned dates inflate qualifying volume, which corrupts tier attainment, which over-accrues liability.

Concretely, this layer feeds three sibling subsystems. Volume Threshold Validation must execute strictly after window verification, because a transaction that falls outside the promotion window but is incorrectly aggregated into qualifying volume will systematically overpay tiered rebates. SKU Mapping & Deduplication depends on verified windows so that pre-promo inventory is never collapsed into promo-period sell-through during canonicalization. And Scoring & Confidence Models consume the boundary-zone flags this layer emits, assigning probabilistic weights to transactions that land inside a configured tolerance band rather than auto-rejecting them. The contractual definitions that govern these windows originate upstream in agreement schema design and are scoped by the eligibility rule framework; raw timestamps arrive from the data ingestion normalization pipelines.

Entity topology and schema specification

A promotion window is a versioned entity, not a pair of loose date strings. Every window carries explicit boundary semantics, a timezone anchor, and a configuration hash so that two reconciliation runs over the same inputs produce byte-identical results. The table below specifies the canonical fields.

Field Type Constraint Notes
promo_id str required, immutable Stable contract-line identifier
agreement_version int required, monotonic Increments on any term amendment
promo_start datetime (UTC) required, day-truncated Boundary semantics set by start_inclusive
promo_end datetime (UTC) required, > promo_start Boundary semantics set by end_inclusive
start_inclusive bool default True Inclusive lower bound is the trade norm
end_inclusive bool default False Exclusive upper bound avoids double-count at midnight
recognition_basis enum ship_to | bill_to | invoice Which transaction timestamp the window tests
source_tz str (IANA) required Original contract timezone for audit
tolerance_hours int default 0, >= 0 Symmetric grace band routed to scoring
fiscal_calendar_id str | null optional Links to a fiscal-period dimension table
config_hash str (sha256) computed Deterministic hash of all of the above

Boundary conditions must be explicit in the configuration layer, never implied by code. Most trade agreements specify inclusive start dates and exclusive end dates, but vendor contracts frequently deviate based on ship_to versus bill_to recognition policies — recognition_basis makes that choice data-driven rather than a hardcoded branch. The config_hash is the linchpin of reproducibility: it is recomputed on ingest and stored alongside every accrual so auditors can prove which window definition produced a given payout. Adherence to ISO 8601 date and time formatting at the boundary eliminates cross-border parsing ambiguity and guarantees deterministic comparisons across distributed systems.

Conditional logic and rule integration

The pipeline ingests heterogeneous date formats from POS feeds, EDI 810 invoices, vendor claim portals, and internal ERP systems. Before any business logic executes, strict temporal normalization is mandatory: every timestamp is parsed into UTC, stripped of ambiguous regional formats, and truncated to a consistent day boundary aligned to the contractual fiscal calendar.

python
import polars as pl

def normalize_promo_dates(df: pl.DataFrame) -> pl.DataFrame:
    """
    Standardize promotion windows and transaction dates to UTC midnight
    boundaries before downstream eligibility evaluation.

    Requires promo_start, promo_end, and transaction_date columns as
    strings in "%Y-%m-%d %H:%M:%S" format (already UTC or timezone-naive
    inputs that should be treated as UTC).
    """
    fmt = "%Y-%m-%d %H:%M:%S"
    return df.with_columns([
        pl.col(c)
            .str.to_datetime(fmt, strict=True)
            .dt.replace_time_zone("UTC")
            .dt.truncate("1d")
        for c in ("promo_start", "promo_end", "transaction_date")
    ])

Boundary inclusion is the most error-prone predicate in the entire rule engine. The window test must honour start_inclusive and end_inclusive independently — a naive start <= txn <= end collapses both bounds to inclusive and double-counts any transaction settled exactly at the end-of-window midnight. The eligibility predicate is best expressed as an explicit comparison driven by the schema flags rather than a single BETWEEN:

python
def in_window(txn, win) -> bool:
    lo_ok = txn >= win.promo_start if win.start_inclusive else txn > win.promo_start
    hi_ok = txn <= win.promo_end if win.end_inclusive else txn < win.promo_end
    return lo_ok and hi_ok

Channel and product scopes layer on top of the temporal predicate, but the temporal predicate is always evaluated first and short-circuits the rest of the chain when it fails. This ordering is what lets the engine treat the date window as a primary key for caching: a transaction that fails the window test never reaches volume or SKU logic, so those subsystems never see off-promo noise.

Financial settlement layer

Temporal boundaries directly shape money. When a contract amendment lands mid-cycle — a new agreement_version with a shifted promo_end — volume attribution must be split at the exact amendment timestamp, and the rebate owed on each side computed against the version that was in force at the moment of the transaction. All such arithmetic uses decimal.Decimal; floating-point accumulation across thousands of line items introduces drift that fails audit reconciliation to the cent.

python
from decimal import Decimal, ROUND_HALF_UP

def prorated_accrual(units: int, rate: Decimal, days_in_window: int,
                     eligible_days: int) -> Decimal:
    """Accrue only the fraction of the window a transaction is eligible for."""
    fraction = Decimal(eligible_days) / Decimal(days_in_window)
    gross = Decimal(units) * rate * fraction
    return gross.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)

Proration at period edges is where recognition basis bites hardest. A shipment recognized on ship_to may fall inside the window while its invoice — recognized on bill_to — falls outside it. The settlement layer resolves this by accruing against the timestamp named in recognition_basis, and never silently substituting one for the other. Currency is normalized to a single settlement currency before accrual, using the contractual FX rate effective on the recognition date, again as Decimal. Tier boundary math itself lives in Volume Threshold Validation; this layer’s settlement responsibility is solely to deliver the correct eligible quantity and the correct effective rate for the window that actually applied.

ETL implementation patterns

Date window alignment is implemented as an idempotent transform: re-running it over the same inputs and the same config_hash must upsert identical rows. Pydantic v2 models validate windows on ingest, enforce the boundary and ordering constraints declaratively, and compute the deterministic hash.

python
from datetime import datetime
from decimal import Decimal
from enum import Enum
from hashlib import sha256
from pydantic import BaseModel, Field, model_validator, computed_field


class RecognitionBasis(str, Enum):
    ship_to = "ship_to"
    bill_to = "bill_to"
    invoice = "invoice"


class PromoWindow(BaseModel):
    promo_id: str
    agreement_version: int = Field(ge=1)
    promo_start: datetime
    promo_end: datetime
    start_inclusive: bool = True
    end_inclusive: bool = False
    recognition_basis: RecognitionBasis = RecognitionBasis.invoice
    source_tz: str
    tolerance_hours: int = Field(default=0, ge=0)
    fiscal_calendar_id: str | None = None

    @model_validator(mode="after")
    def _ordered(self) -> "PromoWindow":
        if self.promo_end <= self.promo_start:
            raise ValueError("promo_end must be strictly after promo_start")
        if self.promo_start.tzinfo is None or self.promo_end.tzinfo is None:
            raise ValueError("window boundaries must be timezone-aware (UTC)")
        return self

    @computed_field
    @property
    def config_hash(self) -> str:
        payload = "|".join(str(v) for v in (
            self.promo_id, self.agreement_version,
            self.promo_start.isoformat(), self.promo_end.isoformat(),
            self.start_inclusive, self.end_inclusive,
            self.recognition_basis.value, self.tolerance_hours,
            self.fiscal_calendar_id,
        ))
        return sha256(payload.encode()).hexdigest()

The upsert key is (promo_id, agreement_version, config_hash). Because the hash folds in every boundary-affecting field, a re-ingested window with unchanged terms maps to the same row and the operation is a no-op; a window whose terms changed produces a new hash and a new immutable record, preserving the prior version for replay. Schema evolution follows the same discipline as agreement schema design: new optional fields default safely, and any field that affects accrual is versioned rather than mutated in place. The step-by-step join logic that matches normalized transactions to these windows is covered in aligning promotion start/end dates with sales data.

Drift detection and validation

Even a correct window definition degrades when upstream feeds shift. Drift detection monitors the distribution of transaction timestamps relative to each window and raises a quarantine ticket when the pattern deviates from the historical baseline. A sudden spike of transactions clustered in the final hour before promo_end, or a rise in records landing inside the tolerance band, signals a settlement-timing change at the retailer that must be investigated before it pollutes accruals.

Date-window evaluation state machine A record moves from INGESTED to NORMALIZED to a WINDOW-TESTED decision, which branches three ways: pass routes to ACCRUED, a tolerance-band match routes to SCORING, and detected drift routes to QUARANTINED. INGESTED NORMALIZED WINDOW TESTED ACCRUED SCORING QUARANTINED pass tolerance band drift detected
Every record is normalized to UTC before the window predicate runs; the decision node emits exactly one of three terminal states, keeping off-promo noise out of downstream volume and SKU logic.

Concretely, the validation stage emits three signals per batch: the share of transactions outside the window, the share inside the tolerance band, and the count of records whose source_tz could not be resolved to UTC. Each is compared against a rolling baseline; a breach routes the batch to a quarantine table with an explicit mismatch code (TZ_DRIFT, FISCAL_MISMATCH, POS_LATE_SETTLEMENT) rather than failing the whole pipeline. Records in the tolerance band are not auto-rejected — they are handed to Scoring & Confidence Models, which weigh historical vendor behaviour and source reliability to decide whether the boundary-zone transaction is likely genuine.

Fallback and dispute routing

Not all temporal discrepancies resolve programmatically. When a window conflicts with master agreement terms, or a feed contains irreconcilable timestamp gaps, the pipeline triggers fallback routing rather than blocking the close. The default policy is conservative: ambiguous transactions are held at a zero accrual pending resolution, never accrued optimistically. Fallback chains prioritize contractual override documents, historical precedent, and vendor-approved exception logs, and they inherit the same escalation contract as the broader fallback routing logic used across the reconciliation platform.

Unresolved mismatches route to a structured exception queue where vendor managers and trade finance analysts collaborate to adjust accruals or request corrected EDI submissions. Every override — a granted grace period, a manual window extension, a recognition-basis correction — is written to an immutable audit log with the operator identity, the prior and new config_hash, and a reason code. This decoupling of hard failures from soft exceptions keeps the accrual cycle moving while preserving a defensible SOX trail. Fallback logic must never silently approve a misaligned claim; it either resolves the record against a documented authority or quarantines it.

Security and access boundaries

Window definitions are financial control data and are protected accordingly. Contract timestamps and recognition policies are tagged for role-based access: trade finance analysts and vendor managers may propose window edits, but only a controller role may approve a new agreement_version, and the approval is enforced as a four-eyes check in the configuration store. Edits flow through a configuration-as-code path — versioned in Git with pull-request review — so that no boundary changes by direct database mutation.

Field-level controls protect the most sensitive attributes. Vendor-specific tolerance terms and contractual FX rates are encrypted at rest, and the secrets used to sign the immutable audit log are rotated on a fixed schedule. Every read and write of a window record is logged with actor, timestamp, and config_hash, giving auditors a complete lineage from a posted accrual back to the exact, signed window definition that produced it.

Frequently asked questions

What happens when a contract amendment shifts the end date mid-cycle?

The amendment creates a new agreement_version with a fresh config_hash; the prior version is retained immutably. Transactions are accrued against whichever version was in force at their recognition timestamp, and any units straddling the amendment boundary are prorated with decimal.Decimal arithmetic so liability is split exactly at the amendment moment.

Should promotion windows use inclusive or exclusive end boundaries?

The trade norm is inclusive start, exclusive end (start_inclusive=True, end_inclusive=False). An exclusive upper bound prevents a transaction settled exactly at end-of-window midnight from being counted in two adjacent periods. Both flags are stored on the window so the choice is auditable rather than implied by code.

How are transactions that fall just outside the window handled?

Records within the symmetric tolerance_hours grace band are not auto-rejected. They are flagged and routed to scoring and confidence models, which assign a probability based on source reliability and vendor history. Records beyond the tolerance band are held at zero accrual and sent to the exception queue.

Why normalize everything to UTC instead of the contract's local timezone?

UTC gives a single deterministic timeline for joins across feeds that originate in different zones. The original contract timezone is preserved in source_tz for audit, but all boundary comparisons execute in UTC after day-truncation, eliminating off-by-one errors from regional or daylight-saving offsets.

Up one level: Claim Validation & Rule Engine Configuration.