Core Architecture Regulatory Mapping

State Filing Deadline Calendars: Computing Deterministic Due Dates Across Jurisdictions

A state filing deadline calendar is the component that converts a statutory rule — “an annual report is due on the anniversary of formation” or “the first day of the fourth month after fiscal year-end” — into an exact, business-day-adjusted, time-zoned date that a scheduler can act on without human interpretation. The hard part is not storing dates; it is computing them deterministically from heterogeneous statutory anchors so the same entity resolves to the same trigger on every run, across fifty jurisdictions that each define “due date” differently.

This guide is part of the Core Architecture & Regulatory Mapping framework. It sits one layer above Entity Taxonomy & Classification: once an entity has been resolved to a single filing obligation, the calendar engine attaches a dated, dispatchable execution window to it. Everything downstream in the Deadline Tracking & Routing Engines layer — priority scoring, batch orchestration, and notification pipelines — consumes the dates this engine produces, so a one-day computation error here silently propagates into late filings and penalty exposure across an entire portfolio.

Statutory and Regulatory Context

Filing deadlines are defined by statute, and the statutes do not agree on an anchor. Delaware franchise tax and the annual report for domestic corporations are due March 1 under Del. Code tit. 8 § 502(a), a fixed calendar date independent of when the entity formed. Delaware LLCs instead owe only an annual tax due June 1 under Del. Code tit. 6 § 18-1107, with no annual report at all. California, by contrast, anchors the Statement of Information to the anniversary month of registration under Cal. Corp. Code § 1502, giving the filer the entire month, while New York uses a biennial cadence keyed to the registration anniversary month. Texas folds the franchise tax and Public Information Report into a single May 15 annual deadline.

Three statutory primitives recur across these rules and must be modeled explicitly: the anchor (fixed date, fiscal-year offset, or formation/registration anniversary), the cadence (annual, biennial, or biennial-by-parity-of-year), and the business-day treatment (whether a due date that lands on a weekend or state holiday rolls forward, rolls back, or stays put). All date arithmetic is expressed in ISO 8601 and resolved against a jurisdiction-specific time zone, because a deadline is a civil date in the filing state, not in UTC. The holiday inputs are drawn from each Secretary of State’s published schedule, cross-referenced against the National Association of Secretaries of State directory.

Architecture and Design Model

The engine is a three-stage pipeline: ingest raw statutory anchors into a typed calendar entry, normalize the anchor into a concrete civil date with business-day and grace-period offsets applied, then dispatch that date as an idempotent, single-intent routing instruction. Each stage commits its state before the next begins, so a failure is always replayable from a durable checkpoint rather than re-derived from scratch.

The central design decisions are deliberate. Temporal normalization is pure and deterministic: given the same entry and the same holiday set, resolve_execution_date always returns the same date — no date.today(), no wall-clock reads inside the resolver. Dispatch is idempotent: a SHA-256 routing key over (entity, jurisdiction, filing_type, year) guarantees that a re-run, a retry, or a duplicate queue delivery cannot produce a second filing. State transitions commit before external I/O, so the system never performs a side effect it cannot account for in the audit log. These three properties — purity, idempotency, commit-before-effect — are what let the downstream priority scoring algorithms and multi-entity batch orchestration treat a calendar entry as a trustworthy fact rather than a guess.

Ingest, normalize, dispatch: the deterministic deadline-calendar pipeline A raw statutory anchor is typed during Ingest, resolved to a business-day-adjusted ISO 8601 civil date during Normalize, and dispatched under an idempotency guard. The dispatch drives a PENDING to DISPATCHED to COMPLETED state machine; a failed dispatch branches to FAILED and into an Escalation Router that categorizes errors, retrying recoverable integration faults with exponential backoff and escalating statutory ambiguities to legal-ops without burning the grace window. ingest → normalize → dispatch — a pure, idempotent statutory-deadline pipeline 1 · Ingest raw anchor → typed calendar entry FIXED_CALENDAR · DE Mar 1 · TX May 15 FISCAL_OFFSET · 4th mo. after FY-end FORMATION_ANNIVERSARY · CA · NY + cadence, grace, IANA time zone 2 · Normalize pure resolve, injected holiday set ISO 8601 civil-date resolve weekend rollover → Monday holiday skip, then + grace period re-roll so grace date never weekends 3 · Dispatch single-intent, commit-before-effect SHA-256 routing key entity · jurisdiction · type · year idempotency guard suppresses replays write DISPATCHED before external I/O Execution state machine PENDING DISPATCHED COMPLETED FAILED exception Escalation Router · error categorization INTEGRATION_TIMEOUT → retry, exp. backoff (×3) IDEMPOTENCY_CONFLICT → suppress duplicate STATUTORY_AMBIGUITY → legal-ops, no retry GRACE_PERIOD_EXHAUSTED → legal-ops, no retry DATA_VALIDATION_FAILURE → manual review

Prerequisites and Dependencies

Dependency Minimum version Role in the calendar engine
Python 3.10+ zoneinfo, match statements, `X
zoneinfo (stdlib) 3.9+ IANA time zones for per-jurisdiction civil-date resolution
Pydantic v2 Validates ingested statutory anchors before normalization
holidays 0.40+ Seeds federal and per-state holiday sets for rollover logic
Celery or ARQ 5.x / 0.25+ Schedules retries with exponential backoff on dispatch failure
PostgreSQL 14+ Transactional state store for routing keys (READ COMMITTED)

Infrastructure assumptions: a distributed lock (Redis or a database advisory lock) backs the dispatcher in production — the in-process threading.Lock below is illustrative only — and the holiday datasets are version-controlled so an audit can reconstruct which calendar produced a given date.

Step-by-Step Implementation

Phase 1 — Model the statutory anchor

Every calendar entry begins as a typed, frozen record. The anchor type is explicit so the normalizer can branch on it deterministically rather than inferring intent from loosely-typed fields. This model is the temporal counterpart of the typed payloads defined in Compliance Metadata Schemas.

from __future__ import annotations

import logging
from dataclasses import dataclass
from datetime import date
from enum import Enum, auto

logger = logging.getLogger(__name__)


class FilingType(Enum):
    ANNUAL_REPORT = auto()
    FRANCHISE_TAX = auto()
    BENEFICIAL_OWNERSHIP = auto()


class AnchorKind(Enum):
    FIXED_CALENDAR = auto()       # e.g. Delaware corp report: March 1
    FISCAL_OFFSET = auto()        # e.g. first day of 4th month after FY-end
    FORMATION_ANNIVERSARY = auto()  # e.g. California Statement of Information


class Cadence(Enum):
    ANNUAL = auto()
    BIENNIAL = auto()             # e.g. New York DOS


@dataclass(frozen=True)
class StatutoryCalendarEntry:
    jurisdiction_code: str        # ISO 3166-2, e.g. "US-DE"
    entity_classification: str
    filing_type: FilingType
    anchor_kind: AnchorKind
    base_due_date: date           # resolved nominal date for the target year
    cadence: Cadence = Cadence.ANNUAL
    grace_period_days: int = 0
    weekend_rollover: bool = True
    holiday_exclusion: bool = True
    timezone: str = "America/New_York"

Phase 2 — Normalize the anchor into a civil date

The resolver applies the statutory offsets in a fixed order — weekend rollover, holiday skip, then grace period (with a second rollover so a grace date never lands on a weekend). It is pure: the holiday set is injected, never read from a global, so the function is fully testable and reproducible.

from __future__ import annotations

import logging
from datetime import date, timedelta
from typing import Protocol
from zoneinfo import ZoneInfo

logger = logging.getLogger(__name__)


class BusinessDayCalculator(Protocol):
    def adjust_to_business_day(self, target: date, jurisdiction: str) -> date: ...


class TemporalNormalizer:
    """Deterministic statutory-deadline resolver. No wall-clock reads."""

    def __init__(self, holiday_set: set[date]) -> None:
        self._holidays = holiday_set

    def resolve_execution_date(self, entry: StatutoryCalendarEntry) -> date:
        execution_date = entry.base_due_date

        if entry.weekend_rollover:
            execution_date = self._roll_weekend(execution_date)
        if entry.holiday_exclusion:
            execution_date = self._skip_holidays(execution_date)
        if entry.grace_period_days > 0:
            execution_date += timedelta(days=entry.grace_period_days)
            if entry.weekend_rollover:
                execution_date = self._roll_weekend(execution_date)

        # Civil date is anchored to the filing state's zone, not UTC.
        tz = ZoneInfo(entry.timezone)
        logger.info(
            "deadline.resolved",
            extra={
                "jurisdiction": entry.jurisdiction_code,
                "filing_type": entry.filing_type.name,
                "base_due_date": entry.base_due_date.isoformat(),
                "execution_date": execution_date.isoformat(),
                "timezone": str(tz),
            },
        )
        return execution_date

    @staticmethod
    def _roll_weekend(d: date) -> date:
        if d.weekday() >= 5:  # Saturday=5, Sunday=6 -> roll to Monday
            return d + timedelta(days=7 - d.weekday())
        return d

    def _skip_holidays(self, d: date) -> date:
        while d in self._holidays:
            d += timedelta(days=1)
            d = self._roll_weekend(d)
        return d

Phase 3 — Dispatch with an idempotency guard

Dispatch transitions a routing instruction through an explicit state machine. The routing key is computed over persisted, deterministic fields only, so a replay produces the identical key and is suppressed. The state is written to DISPATCHED before the filing pipeline runs, which is the commit-before-effect rule that keeps the audit log honest.

from __future__ import annotations

import hashlib
import logging
import threading
from dataclasses import dataclass, field
from datetime import date
from enum import Enum

logger = logging.getLogger(__name__)


class ExecutionState(Enum):
    PENDING = "PENDING"
    DISPATCHED = "DISPATCHED"
    COMPLETED = "COMPLETED"
    FAILED = "FAILED"


@dataclass
class RoutingInstruction:
    entity_id: str
    jurisdiction: str
    filing_type: FilingType
    execution_date: date
    target_year: int
    routing_key: str = field(default="")

    def __post_init__(self) -> None:
        if not self.routing_key:
            payload = (
                f"{self.entity_id}:{self.jurisdiction}:"
                f"{self.filing_type.name}:{self.target_year}"
            )
            self.routing_key = hashlib.sha256(payload.encode()).hexdigest()


class IdempotentDispatcher:
    """Single-intent dispatch with commit-before-effect state transitions."""

    def __init__(self, state_store: dict[str, ExecutionState]) -> None:
        self._state_store = state_store
        self._lock = threading.Lock()  # Production: Redis / advisory lock

    def dispatch(self, instruction: RoutingInstruction) -> bool:
        key = instruction.routing_key
        with self._lock:
            current = self._state_store.get(key, ExecutionState.PENDING)
            if current in (ExecutionState.DISPATCHED, ExecutionState.COMPLETED):
                logger.warning("dispatch.suppressed", extra={"routing_key": key})
                return False
            self._state_store[key] = ExecutionState.DISPATCHED  # commit first

        try:
            self._execute_filing_pipeline(instruction)
            self._state_store[key] = ExecutionState.COMPLETED
            logger.info("dispatch.completed", extra={"routing_key": key})
            return True
        except Exception:
            self._state_store[key] = ExecutionState.FAILED
            logger.exception("dispatch.failed", extra={"routing_key": key})
            raise

    def _execute_filing_pipeline(self, instruction: RoutingInstruction) -> None:
        # Hand off to the portal-submission layer; the dated, single-intent
        # instruction is now an immutable fact downstream consumers can trust.
        ...

Phase 4 — Categorize failures and route fallbacks

Regulatory environments are volatile, so a dispatch failure must be classified before it is retried. Recoverable integration faults retry with exponential backoff; non-recoverable statutory ambiguities route straight to legal-ops without burning the grace window. This taxonomy is the same error model the registered agent assignment logic uses when it escalates a routing failure to a regional team.

from __future__ import annotations

import enum
import logging
from dataclasses import dataclass

logger = logging.getLogger(__name__)


class ComplianceErrorCategory(enum.Enum):
    INTEGRATION_TIMEOUT = "integration_timeout"
    IDEMPOTENCY_CONFLICT = "idempotency_conflict"
    STATUTORY_AMBIGUITY = "statutory_ambiguity"
    GRACE_PERIOD_EXHAUSTED = "grace_period_exhausted"
    DATA_VALIDATION_FAILURE = "data_validation_failure"


@dataclass(frozen=True)
class ComplianceError(Exception):
    category: ComplianceErrorCategory
    entity_id: str
    jurisdiction: str
    message: str
    retryable: bool = False

    def __str__(self) -> str:
        return f"[{self.category.value}] {self.jurisdiction}/{self.entity_id}: {self.message}"


class EscalationRouter:
    MAX_RETRIES = 3
    BACKOFF_BASE = 2.0

    def handle_failure(self, error: ComplianceError, attempt: int) -> None:
        if not error.retryable:
            logger.critical("escalation.legal_ops", extra={"error": str(error)})
            return  # freeze entity routing, page on-call, attach audit log
        if attempt >= self.MAX_RETRIES:
            logger.warning("escalation.manual_review", extra={"error": str(error)})
            return  # pause automation, open ticket with statutory reference
        delay = self.BACKOFF_BASE ** attempt
        logger.info(
            "escalation.retry_scheduled",
            extra={"entity_id": error.entity_id, "delay_s": delay, "attempt": attempt + 1},
        )

Edge Cases and Jurisdiction-Specific Gotchas

The anchor, cadence, and business-day treatment differ enough across states that hard-coding any single rule guarantees wrong dates somewhere. Model each jurisdiction as data:

Jurisdiction Anchor Cadence Business-day treatment Gotcha
Delaware (corp) Fixed: March 1 Annual No rollover — statute fixes the date Franchise tax and report share the March 1 deadline; large-filer estimated payments fall earlier on June 1
Delaware (LLC) Fixed: June 1 Annual No rollover LLC owes a flat tax and no annual report — do not emit a report obligation
California Registration anniversary month Annual (stock) / biennial (LLC) Filing window is the whole month; late = entire month elapses Stock corporations file annually; LLCs file biennially — cadence depends on entity type
New York Registration anniversary month Biennial Window is the anniversary month Biennial parity must be derived from the registration year, not the current year
Texas Fixed: May 15 Annual Rolls forward to next business day Combined franchise tax + Public Information Report; a “no tax due” entity still owes the report
Florida Fixed: May 1 Annual No rollover; steep penalty after the date A flat $400 late fee applies the day after the deadline with no proportional grace

Two cross-cutting traps: DST drift corrupts dates when arithmetic is done on naive datetimes — always resolve civil dates with zoneinfo and never add timedelta(hours=...) across a DST boundary. Anniversary anchors for Feb-29 formations must clamp to Feb 28 in non-leap years; an unclamped date(year, 2, 29) raises ValueError and crashes the whole batch.

Verification and Testing

Determinism is the property worth testing hardest: feed the resolver a frozen holiday set and assert idempotence across repeated calls, then assert the documented jurisdiction-specific outcomes.

import unittest
from datetime import date


class TestTemporalNormalizer(unittest.TestCase):
    def setUp(self) -> None:
        # 2026-07-03 is the observed federal Independence Day holiday.
        self.normalizer = TemporalNormalizer(holiday_set={date(2026, 7, 3)})

    def test_resolution_is_idempotent(self) -> None:
        entry = StatutoryCalendarEntry(
            jurisdiction_code="US-TX",
            entity_classification="domestic_llc",
            filing_type=FilingType.FRANCHISE_TAX,
            anchor_kind=AnchorKind.FIXED_CALENDAR,
            base_due_date=date(2026, 5, 15),
        )
        first = self.normalizer.resolve_execution_date(entry)
        second = self.normalizer.resolve_execution_date(entry)
        self.assertEqual(first, second)

    def test_saturday_deadline_rolls_to_monday(self) -> None:
        entry = StatutoryCalendarEntry(
            jurisdiction_code="US-TX",
            entity_classification="domestic_corp",
            filing_type=FilingType.ANNUAL_REPORT,
            anchor_kind=AnchorKind.FIXED_CALENDAR,
            base_due_date=date(2026, 5, 16),  # a Saturday
        )
        self.assertEqual(self.normalizer.resolve_execution_date(entry), date(2026, 5, 18))

    def test_holiday_then_weekend_skips_both(self) -> None:
        entry = StatutoryCalendarEntry(
            jurisdiction_code="US-DE",
            entity_classification="domestic_corp",
            filing_type=FilingType.FRANCHISE_TAX,
            anchor_kind=AnchorKind.FIXED_CALENDAR,
            base_due_date=date(2026, 7, 3),  # holiday -> skip -> lands Sat -> Monday
        )
        self.assertEqual(self.normalizer.resolve_execution_date(entry), date(2026, 7, 6))

For dispatch, assert that a duplicate routing key is suppressed and that a thrown exception leaves the key in FAILED rather than DISPATCHED, so the retry path can pick it up cleanly.

Troubleshooting

Delaware LLCs are being assigned a March 1 annual report deadline

The anchor table is applying the domestic-corporation rule to LLCs. Delaware LLCs owe only the flat tax due June 1 and file no annual report. Branch the calendar entry on entity type during ingestion so an LLC never receives a FilingType.ANNUAL_REPORT obligation — this is a classification boundary that belongs in Entity Taxonomy & Classification, upstream of date computation.

The same entity resolves to a different date on a re-run

The resolver is reading a wall clock or an unsorted/mutable holiday source. resolve_execution_date must be pure: inject the holiday set, never call date.today() inside it, and version the holiday dataset so the same input always reproduces the same output under audit.

A deadline computed an hour off, then missed by a day during March or November

Date arithmetic is being done on naive datetimes across a daylight-saving boundary. Resolve civil dates with zoneinfo and operate on date objects, not datetime + timedelta(hours=...); a filing deadline is a civil date in the filing state, and adding hours across a DST transition shifts it.

The batch crashes every leap year on February anniversaries

An anniversary anchor is constructing date(year, 2, 29) in a non-leap year, which raises ValueError. Clamp February anniversaries to the 28th in non-leap years before passing the date to the normalizer.

Two identical filings were submitted to the same portal

The dispatcher’s idempotency guard is keyed on a non-deterministic field or the lock is process-local under horizontal scaling. Compute the SHA-256 routing key over persisted fields only and back the guard with a distributed lock so a duplicate queue delivery across workers is suppressed.

Operational Checklist