Deadline Reminder Cadence Engines

A reminder cadence engine is the component that expands a single computed due date into a graduated sequence of reminders — for example a 90/60/30/14/7/3/1-day ladder — each fired on a business day, tuned to the deadline’s category, and cancelled the moment a responsible human acknowledges it. It decides when to warn, leaving whom to warn next to escalation and whether it worked to reporting.

The engine is the second stage of the Docket Alerting, Escalation & Compliance Reporting pipeline: it reads effective deadlines the rule engine has already rolled off non-working days and produces the timed reminders that channel dispatch delivers. Its output is the input to the Escalation Routing Workflows model — every reminder that fires and goes unacknowledged past its window is what triggers a climb up the responsibility ladder. This page specifies how to structure the ladder, expand it against a business-day calendar, schedule it idempotently, and keep it from degenerating into a wall of noise that trains people to ignore it.

A tiered reminder cadence timeline for a single patent deadline A horizontal time axis runs left to right toward a due date on the right. Seven reminder tiers fire at 90, 60, 30, 14, 7, 3, and 1 business days before the deadline, spaced more tightly as the due date approaches. Each tier is a card labelled with its lead time and notification channel, connected by a stem to a filled badge on the axis: early tiers use email, the mid tiers add in-app, and the final tiers add SMS. The due marker on the right is labelled as a statutory bar. A band below the axis explains that any reminder instant landing on a weekend or office holiday is shifted to the previous working day, and that a human acknowledgment cancels all remaining reminders for the deadline. EARLIER longer runway T-90d email T-30d email + in-app T-7d email + SMS T-1d SMS + page T-60d email T-14d in-app T-3d SMS DUE statutory bar effective date Business-day-aware & acknowledgment-gated Any tier instant landing on a weekend or office holiday shifts to the previous working day — a reminder never fires on a closed day. A human acknowledgment cancels every remaining tier for the deadline; only unacknowledged tiers survive to trigger escalation.

Scope & Boundaries

A cadence engine has one job — turning a due date into a schedule of reminder instants — and it must not quietly acquire others. It does not compute the deadline (that is the rule engine’s job), it does not decide who receives the reminder (that is the responsibility ladder), and it does not deliver anything (that is channel dispatch). Keeping these separate is what makes the cadence auditable: a reviewer can look at the schedule the engine produced and confirm it matches policy without untangling delivery failures or escalation decisions.

The engine reads a single input contract — an effective due date, a deadline category, and an identifier — and emits a set of scheduled reminders. It never mutates the deadline. If the underlying due date changes because the rule engine recomputed it (a corrected office-action mail date, a restored priority claim), the engine regenerates the cadence from scratch and supersedes the old schedule with a recorded reason, rather than editing individual reminders in place. This regeneration-not-mutation rule is what lets the audit trail show the complete history of every schedule a deadline ever had.

Operational Action: Give the cadence engine exactly one input contract and one output contract. If you find it reading assignment tables or calling a mail server, the responsibilities have leaked — pull them back into the escalation and dispatch stages.

Anatomy of a Tiered Cadence

A tier is a single rung of the reminder ladder, defined by three properties: a lead time (how many business days before the due date it fires), an urgency level that governs which channels carry it, and an acknowledgment window (how long the recipient has to confirm before the tier is considered unanswered and eligible for escalation). A cadence is an ordered set of tiers, densest near the deadline. The 90/60/30/14/7/3/1-business-day ladder is a sensible default for high-value statutory deadlines because it front-loads a long, low-urgency runway and then compresses into daily, high-urgency touches in the final week when the cost of inaction is highest.

Lead times are expressed in business days, not calendar days, for a specific reason: a 3-calendar-day reminder that lands on a Friday for a Monday deadline gives the recipient effectively no working time, whereas a 3-business-day reminder guarantees three actual working days of runway. The engine therefore walks the same office calendar the rule engine used for its non-working-day roll under 37 CFR § 1.7(a) and PCT Rule 80.5, so that reminder math and deadline math share one definition of a working day. Two subtleties follow. First, a tier whose lead time exceeds the remaining runway (a deadline computed only 20 days out cannot have a 90-day reminder) is simply dropped, not fired retroactively. Second, a tier instant that itself lands on a weekend or holiday is shifted to the previous working day — earlier, never later — so a reminder is never scheduled for a day the office is closed and never pushed closer to the bar.

Designing the Tier Ladder by Deadline Category

Not every deadline deserves the same ladder. A non-extendable statutory bar and a maintenance-fee window with a six-month grace period carry very different consequences for a missed reminder, so the cadence is keyed to the deadline category defined in the Core Docketing Architecture & Deadline Types taxonomy. Tuning the ladder per category is what stops routine annuities from generating the same alert volume as an irreversible bar, which is the root cause of most reminder fatigue.

Deadline category Example Suggested tier ladder (business days) Rationale
Statutory, non-extendable PCT 30-month national phase entry 120 / 90 / 60 / 30 / 14 / 7 / 3 / 1 Miss is generally unrecoverable; long runway plus daily final week
Procedural, extendable USPTO office-action response, 37 CFR § 1.136(a) 60 / 30 / 14 / 7 / 3 / 1 Extensions exist but cost fees; still needs a firm final push
Fee, with grace period USPTO maintenance fee (3.5/7.5/11.5 yr) 180 / 90 / 30 / 7 Six-month grace absorbs slips; early runway matters more than daily nags
Discretionary / internal Client-reporting or docket-review target 14 / 3 Low external consequence; keep the ladder short to preserve signal

The entity-tunable dimension matters as much as the category. A firm docketing for a risk-averse corporate client may widen a statutory ladder; a solo practitioner may narrow the fee ladder to reduce noise. The ladder is therefore configuration, not code — declared in a versioned table, citable to policy, and changeable without a redeploy. The concrete YAML form and the expansion function are worked through in Configuring Tiered Reminder Schedules for Patent Deadlines.

Operational Action: Key every cadence to a deadline category and store the ladders in a version-controlled configuration table with an effective_from date. A change to a reminder schedule is a policy change and should be reviewable as a diff, never a hard-coded constant edit.

Step-by-Step Implementation

Each step is independently testable — you can exercise the tier model, the business-day expansion, and the idempotency logic against a fixed calendar before wiring in a live scheduler.

Step 1 — Model tiers as validated configuration

Represent a tier and a cadence policy as Pydantic models so a malformed ladder is rejected at load time rather than producing a broken schedule at runtime. Validation at the boundary is the same discipline the ingestion layer applies to office data.

from __future__ import annotations

from pydantic import BaseModel, Field, field_validator


class ReminderTier(BaseModel):
    """One rung of a reminder ladder."""
    lead_business_days: int = Field(gt=0)
    urgency: str = Field(pattern=r"^(low|normal|high|critical)$")
    ack_window_hours: int = Field(gt=0)          # runway to acknowledge before escalation


class CadencePolicy(BaseModel):
    """A category-keyed ladder. Tiers are normalized to descending lead time."""
    category: str
    tiers: list[ReminderTier]

    @field_validator("tiers")
    @classmethod
    def sort_and_dedupe(cls, tiers: list[ReminderTier]) -> list[ReminderTier]:
        seen: set[int] = set()
        for t in tiers:
            if t.lead_business_days in seen:
                raise ValueError(f"duplicate tier at {t.lead_business_days} days")
            seen.add(t.lead_business_days)
        # Densest-near-deadline order is produced by sorting descending.
        return sorted(tiers, key=lambda t: t.lead_business_days, reverse=True)

Step 2 — Expand a deadline against a business-day calendar

The core operation walks the injected office calendar backward from the due date, counting only working days, then shifts any tier instant that still lands on a closed day to the previous working day. The calendar is injected, not imported, so a holiday-table update never touches this function.

from datetime import date, timedelta
from typing import Protocol


class BusinessCalendar(Protocol):
    def is_working_day(self, d: date) -> bool: ...
    def version(self) -> str: ...


def _minus_business_days(due: date, n: int, cal: BusinessCalendar) -> date:
    cursor, counted = due, 0
    while counted < n:
        cursor -= timedelta(days=1)
        if cal.is_working_day(cursor):
            counted += 1
    return cursor


def expand_cadence(deadline_id: str, due: date, policy: CadencePolicy,
                   cal: BusinessCalendar) -> list[dict[str, str]]:
    """Turn one deadline into concrete, business-day-aware reminder dates."""
    schedule: list[dict[str, str]] = []
    for tier in policy.tiers:
        fire_on = _minus_business_days(due, tier.lead_business_days, cal)
        if fire_on >= due:                       # runway shorter than this tier
            continue
        # A tier instant on a closed day shifts EARLIER, never toward the bar.
        while not cal.is_working_day(fire_on):
            fire_on -= timedelta(days=1)
        schedule.append({
            "deadline_id": deadline_id,
            "lead_business_days": str(tier.lead_business_days),
            "urgency": tier.urgency,
            "fire_date": fire_on.isoformat(),
            "ack_window_hours": str(tier.ack_window_hours),
            "calendar_version": cal.version(),
        })
    return schedule

Step 3 — Schedule idempotently

Re-running the expansion — nightly reconciliation, a service restart, a manual re-sync — must never create a second copy of the same reminder. Derive a deterministic idempotency key from the deadline, the tier, and the fire date so a duplicate collapses onto the existing row instead of inserting a new one.

import hashlib


def reminder_key(deadline_id: str, lead_business_days: int, fire_date: str) -> str:
    """Stable across re-expansions of the same logical reminder."""
    material = f"{deadline_id}|{lead_business_days}|{fire_date}"
    return hashlib.sha256(material.encode("utf-8")).hexdigest()

The scheduler upserts each reminder keyed on reminder_key. If the due date is later recomputed upstream, the fire dates change, the keys change, and the old reminders are marked SUPPRESSED with the reason superseded_by_recompute — a regeneration, not an in-place edit, so the audit trail preserves every schedule the deadline ever carried.

Step 4 — Track acknowledgment and stop the cadence

Acknowledgment is the only signal that stops the ladder. When a responsible human confirms a reminder, every remaining SCHEDULED tier for that deadline is cancelled. Until then, each fired tier whose acknowledgment window lapses becomes eligible for escalation — the handoff into the Escalation Routing Workflows model.

from datetime import datetime, timezone


def on_acknowledged(deadline_id: str, reminders: list[dict[str, str]]) -> list[dict[str, str]]:
    """Cancel all still-pending tiers once the deadline is acknowledged."""
    now = datetime.now(timezone.utc).isoformat()
    for r in reminders:
        if r.get("state") == "SCHEDULED":
            r["state"] = "SUPPRESSED"
            r["reason"] = "acknowledged"
            r["suppressed_utc"] = now
    return reminders

Preventing Reminder Storms

A cadence engine can fail in two opposite directions: too quiet (deadlines lapse) and too loud (people mute the system, and deadlines lapse anyway). The loud failure is subtler and just as dangerous, because a firm that has trained itself to ignore docket email has effectively disabled the alerting layer while still paying for it. Three controls keep volume proportionate.

First, deduplicate across deadlines that share an owner and a day. A paralegal with forty matters maturing in the same week should receive a consolidated digest per urgency level, not forty separate emails — the engine groups low- and normal-urgency reminders by recipient and day, while high- and critical-urgency reminders always send individually. Second, jitter the batch dispatch window. When hundreds of reminders come due at the same nightly run, releasing them in a randomized spread over the dispatch window prevents both a mail-server thundering herd and a recipient inbox flood, reusing the jitter discipline from the ingestion layer’s exponential backoff patterns. Third, suppress superseded tiers immediately. When a deadline is satisfied, withdrawn, or recomputed, its remaining reminders must be cancelled in the same transaction, so a paid maintenance fee never generates a “7 days remaining” alert the following week.

Operational Action: Track a per-recipient reminder-volume metric and alert on it. A sustained rise in low-urgency reminders per person per day is an early sign the ladders need tuning before staff start filtering docket mail to a folder they never open.

Edge Cases & Failure Modes

  • Runway shorter than the ladder. A deadline computed only days before it is due (a late-ingested office action) cannot support a 90-day tier. Drop out-of-range tiers silently, but always fire at least the nearest in-range tier and flag the compressed runway for review — never leave a short-runway deadline with zero reminders.
  • Recomputed due date mid-cadence. If the rule engine revises the effective date, regenerate the whole cadence and supersede the prior schedule with a recorded reason. Editing individual fire dates in place corrupts the audit trail and can leave orphaned reminders keyed to the old date.
  • Tier instant on a long office closure. A tier landing inside a multi-day holiday (an EPO year-end closure, say) shifts backward past the entire block to the previous working day. Because the shift is earlier, it can collide with the adjacent tier; deduplicate so two tiers never fire the same reminder to the same recipient on the same day.
  • Acknowledgment after escalation. A recipient may acknowledge a tier that has already escalated. The acknowledgment still cancels remaining tiers, but the escalation record stays in the ledger — the fact that it had to escalate is itself a diligence signal the reporting layer counts.
  • Clock skew and DST. All fire instants are computed on dates in the office’s calendar and materialized to timezone-aware UTC only at dispatch, so a daylight-saving transition never shifts a reminder onto the wrong day. Never store a naive local datetime.

Verification & Regression Testing

Treat the ladder and the expansion function as code and pin their behavior against a fixed calendar, because a subtle off-by-one in the business-day walk silently moves every reminder.

import pytest
from datetime import date


class _FixedCalendar:
    """Weekdays are working days; a fixed holiday set for tests."""
    _holidays = {date(2026, 12, 25), date(2026, 1, 1)}

    def is_working_day(self, d: date) -> bool:
        return d.weekday() < 5 and d not in self._holidays

    def version(self) -> str:
        return "test-cal-v1"


def test_business_day_walk_skips_weekend() -> None:
    cal = _FixedCalendar()
    # 3 business days before Wed 2026-07-15 is Fri 2026-07-10 (skips the weekend).
    assert _minus_business_days(date(2026, 7, 15), 3, cal) == date(2026, 7, 10)


def test_out_of_range_tier_is_dropped() -> None:
    cal = _FixedCalendar()
    policy = CadencePolicy(category="statutory", tiers=[
        ReminderTier(lead_business_days=90, urgency="low", ack_window_hours=48),
        ReminderTier(lead_business_days=3, urgency="high", ack_window_hours=8),
    ])
    # Only ~10 business days of runway: the 90-day tier must be dropped, 3-day kept.
    schedule = expand_cadence("D-1", date(2026, 7, 15), policy, cal)
    leads = {r["lead_business_days"] for r in schedule}
    assert leads == {"3"}


def test_reminder_key_is_stable() -> None:
    a = reminder_key("D-1", 30, "2026-06-03")
    b = reminder_key("D-1", 30, "2026-06-03")
    assert a == b  # re-expansion collapses onto one row, no duplicate reminder

A corpus of known deadlines with hand-verified fire dates — including a month-boundary case and a holiday-block case — is the strongest guard against a change that quietly shifts the whole ladder by a day. Run it in CI before promoting any change to the expansion logic or the default ladders.

Operational Action Summary

Operational Action: Express every lead time in business days and share one office-calendar definition with the rule engine, so reminder math and deadline math never disagree about what a working day is.

Operational Action: Enforce a deterministic reminder key at the scheduler and regenerate-then-supersede on any upstream recompute, so re-runs and revised dates never duplicate or orphan a reminder.

Operational Action: Consolidate low-urgency reminders per recipient, jitter batch dispatch, and always send high- and critical-urgency reminders individually — proportionate volume is what keeps staff reading docket alerts at all.

Frequently Asked Questions

Why express reminder lead times in business days instead of calendar days?
Because calendar-day lead times give inconsistent working runway. A three-calendar-day reminder before a Monday deadline lands on a Friday and offers no working time, while a three-business-day reminder guarantees three actual working days. Expressing lead times in business days and walking the same office calendar the rule engine uses for its non-working-day roll keeps reminder math and deadline math consistent, so a tier never fires on a day the office is closed and never sits closer to the bar than intended.
What happens when a deadline is computed with less runway than the ladder needs?
Tiers whose lead time exceeds the remaining runway are dropped rather than fired retroactively, but the engine always schedules at least the nearest in-range tier and flags the compressed runway for review. A late-ingested office action might only support the 7, 3, and 1-day tiers; those still fire, and the short runway is surfaced so a human can decide whether an extension or expedited action is needed. A short-runway deadline must never end up with zero reminders.
How do you keep a large portfolio from drowning people in reminders?
Key ladders to deadline category so routine fee windows do not generate statutory-bar volume, consolidate low- and normal-urgency reminders into a per-recipient daily digest, and jitter the batch dispatch window so same-day deadlines do not synchronize into a flood. High- and critical-urgency reminders always send individually. The failure mode to avoid is training staff to filter docket mail into a folder they never open, which silently disables the whole alerting layer.
Does acknowledging one reminder cancel the rest of the ladder?
Yes. A human acknowledgment is the only signal that stops the cadence: every remaining scheduled tier for that deadline is cancelled and marked suppressed with the reason "acknowledged." If the deadline had already escalated before acknowledgment, the acknowledgment still stops future tiers, but the escalation record stays in the ledger because the fact that it had to escalate is itself a diligence signal the reporting layer counts.

↑ Back to Docket Alerting, Escalation & Compliance Reporting