Escalating Missed Notice Windows to Manual Review
A tiered reminder ladder does its job right up until the moment it fails: the owner is on leave, the manager’s inbox rule swallowed the escalation, and the notice window for a renewal option quietly crosses zero with nobody having acknowledged it. The Critical Date Alerting layer is built to make that rare, but “rare” is not “never,” and when it happens the machine must not simply go quiet. This page covers the one narrow thing that happens after the last reminder tier has fired and gone unanswered: converting a breached or unacknowledged critical date into a concrete, owned, time-boxed review task and handing it to a human triage queue with enough context to act. It is the terminal rung — not another reminder, but the point where automation admits it cannot resolve the date and routes it to a person.
The distinction from the alerting ladder matters. The ladder decides who to tell and how loudly while there is still runway; this page begins where that runway is gone. A missed option deadline may already carry legal exposure, so the handoff cannot be a bare “check on lease L-4471” line in a spreadsheet. It has to arrive as a structured work item that says exactly which canonical field lapsed, which amendment set the deadline, how that deadline was computed, why the ladder failed to get an acknowledgement, and how long the reviewer has before the situation gets worse.
Architectural context
This escalation is deliberately the same seam the platform already uses for machine-unresolvable work. When a low-confidence extraction cannot be trusted, it diverts through Fallback Routing Logic into a manual-review queue; a breached critical date lands in that same queue by the same contract, so operations triages parsing failures and missed deadlines in one place rather than two. The review task itself reads exclusively from already-validated data: the due date, owner, and manager come from the watched CriticalDate, whose values trace back to the canonical lease data models, and any derived deadline (a rent-review date on a CPI cycle, a notice window measured back from an option date) carries a pointer to the escalation formula mapping rule that produced it. This layer never recomputes those values; it packages them, stamps provenance, assigns an SLA, and enqueues — idempotently, so one missed date is exactly one review item no matter how many times the daily sweep re-runs.
Trigger conditions, severity, and SLA
Not every escalation is equally urgent, and the triage queue is useless if a lapsed insurance certificate and a soft near-breach on a distant rent review arrive with identical priority. The mapping below is the contract the builder encodes: each trigger produces a severity, an assignee, and a service-level target that the queue enforces. Assignees are roles, not people, so the schedule survives staff changes.
| Trigger condition | Severity | Assignee & SLA | Action taken |
|---|---|---|---|
| Option / renewal deadline passed unacknowledged | Critical | Senior asset manager · 4h | File task, flag legal-watch, freeze auto-actions on the lease |
| Insurance or estoppel deadline passed | Critical | Compliance owner · 4h | File task, attach certificate provenance, notify risk |
| Final tier (T-7) unacknowledged past ack SLA, deadline imminent | High | Portfolio manager · 1 business day | File task, re-ping owner in parallel |
| Notice window closed with no acknowledgement | High | Asset manager · 1 business day | File task, record whether option was preserved |
| Near-breach: within final tier, ack SLA elapsed, deadline still future | Medium | Owner + manager · 2 business days | File task, keep ladder live as backstop |
| Suspected false positive (stale sweep / timezone) | Low | Ops triage · 3 business days | File task, verify computed deadline before acting |
Severity keys off how much runway remains and what is at stake if it is gone: a passed option deadline is Critical because the tenant’s right may already be forfeited, while a near-breach with the deadline still a fortnight out is Medium because a human can still catch it. The SLA is not decoration — it is the second clock. The first clock (the deadline) may have already expired; the SLA governs how fast a person must now look at the wreckage.
Recommended implementation
The builder consumes the exact CriticalDate and AlertState the alerting evaluator already maintains — this layer adds no new schema for the date itself. It produces a ReviewTask whose primary property is a deterministic idempotency key, so enqueuing the same breach twice is a no-op. Provenance is a first-class nested model, not a free-text note, because a reviewer acting under a 4-hour SLA cannot afford to go spelunking through source documents to reconstruct why the alert exists.
from __future__ import annotations
from datetime import date, datetime, timedelta, timezone
from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field, field_validator
class BreachType(str, Enum):
DEADLINE_PASSED = "deadline_passed" # days_left < 0, unacknowledged
FINAL_TIER_UNACKED = "final_tier_unacked" # nearest tier fired, ack SLA elapsed
class Severity(str, Enum):
CRITICAL = "critical"
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
# (date_type, breach_type) -> (severity, assignee_role, sla_hours)
SEVERITY_MATRIX: dict[tuple[str, BreachType], tuple[Severity, str, int]] = {
("option_exercise", BreachType.DEADLINE_PASSED): (Severity.CRITICAL, "senior_asset_manager", 4),
("notice_window", BreachType.DEADLINE_PASSED): (Severity.HIGH, "asset_manager", 24),
("insurance_expiry", BreachType.DEADLINE_PASSED): (Severity.CRITICAL, "compliance_owner", 4),
("estoppel_deadline", BreachType.DEADLINE_PASSED): (Severity.CRITICAL, "compliance_owner", 4),
("rent_review", BreachType.DEADLINE_PASSED): (Severity.HIGH, "portfolio_manager", 24),
}
_DEFAULT_RULE = (Severity.MEDIUM, "portfolio_manager", 48) # near-breach / final-tier unacked
class Provenance(BaseModel):
"""Everything a reviewer needs to act without opening the source lease."""
canonical_fields: list[str] = Field(..., min_length=1) # which fields lapsed
source_amendment_id: Optional[str] = None # amendment that set the deadline
computed_deadline: date # the deadline as the platform saw it
formula_rule_id: Optional[str] = None # escalation-formula rule, if derived
last_tier_fired: Optional[int] = None # nearest reminder that went out
last_notified_on: Optional[date] = None
breach_reason: str = Field(..., min_length=3) # why the ladder failed to resolve it
class ReviewTask(BaseModel):
idempotency_key: str = Field(..., min_length=8)
lease_id: str
date_type: str
breach_type: BreachType
severity: Severity
assignee_role: str
sla_due_at: datetime
provenance: Provenance
status: str = "open"
@field_validator("sla_due_at")
@classmethod
def _timezone_aware(cls, v: datetime) -> datetime:
if v.tzinfo is None:
raise ValueError("sla_due_at must be timezone-aware (store UTC)")
return v
def build_review_task(cd, state, today: date, now: Optional[datetime] = None) -> ReviewTask:
"""Convert a breached/unacknowledged CriticalDate into a review-queue item.
Reads only already-validated fields off the CriticalDate and AlertState the
alerting evaluator maintains; classifies the breach; stamps provenance; and
derives a deterministic key so re-running the daily sweep never duplicates it.
"""
now = now or datetime.now(timezone.utc)
days_left = (cd.due_date - today).days
breach_type = (
BreachType.DEADLINE_PASSED if days_left < 0 else BreachType.FINAL_TIER_UNACKED
)
severity, assignee, sla_hours = SEVERITY_MATRIX.get(
(cd.date_type.value, breach_type), _DEFAULT_RULE
)
reason = (
f"deadline {cd.due_date.isoformat()} passed unacknowledged"
if breach_type is BreachType.DEADLINE_PASSED
else f"final tier T-{state.last_tier_fired} unacknowledged past {cd.ack_sla_days}d SLA"
)
provenance = Provenance(
canonical_fields=[f"{cd.date_type.value}.due_date"],
source_amendment_id=getattr(cd, "source_amendment_id", None),
computed_deadline=cd.due_date,
formula_rule_id=getattr(cd, "formula_rule_id", None),
last_tier_fired=state.last_tier_fired,
last_notified_on=state.last_fired_on,
breach_reason=reason,
)
# One missed date == one review item. The key excludes `today` on purpose:
# every sweep after the breach resolves to the same key, so the queue dedupes.
idempotency_key = f"review:{cd.lease_id}:{cd.date_type.value}:{breach_type.value}"
return ReviewTask(
idempotency_key=idempotency_key,
lease_id=cd.lease_id,
date_type=cd.date_type.value,
breach_type=breach_type,
severity=severity,
assignee_role=assignee,
sla_due_at=now + timedelta(hours=sla_hours),
provenance=provenance,
)
def enqueue_review_task(task: ReviewTask, queue) -> bool:
"""Insert-if-absent on the idempotency key. Returns True only on first write."""
if queue.exists(task.idempotency_key): # unique constraint in production
return False
queue.insert(task.idempotency_key, task.model_dump(mode="json"))
return True
The load-bearing choice is that idempotency_key deliberately omits today. The breach is a property of the date, not of the run that noticed it, so the twentieth sweep after a deadline passes produces the same key as the first — and enqueue_review_task discards it. That is the whole guarantee: one missed date is one review item, forever, regardless of how long it sits open. Closing the loop is the reviewer’s action: they either correct and re-arm the underlying schedule (reset the alert state’s tier markers so the ladder tracks a new deadline) or mark the date genuinely lapsed, and either resolution flips the task to resolved so it leaves the active queue while staying in the audit trail.
Edge cases specific to commercial leases
- The sweep never ran. A breach detected on a Monday might actually have occurred the previous Wednesday because the scheduled job was down. The deadline is real, but the
sla_due_atcomputed fromnowis generous relative to how long the date has actually been lapsed. Enrich provenance withdays_since_deadlineand let the queue escalate severity when that gap is large, rather than trusting the detection timestamp as the breach timestamp. - An amendment retroactively moved the deadline. A late-arriving amendment shifts a notice window such that a date the platform thought was breached was never actually due, or vice versa. Because the deadline is re-derived through escalation formula mapping, a review task built against the stale deadline must be reconciled: on re-derivation, close the old task with reason
superseded_by_amendmentand let the ladder re-arm against the corrected date rather than leaving a task that references a deadline that no longer exists. - Duplicate review items for one date. Two workers run the sweep concurrently and both classify the same breach. The deterministic key plus an insert-if-absent write means only the first commits; the second is a silent no-op. Never key the task on a UUID or the run timestamp, or the queue accumulates one task per sweep for the same missed date.
- False positive from a timezone boundary. A notice window that expires at end-of-day in the lease’s jurisdiction can look breached when
todayis computed in UTC just after midnight. Classify these as Low severity into an ops-triage lane and require the reviewer to confirm the computed deadline against the jurisdiction date before any irreversible action — never auto-forfeit an option on a UTC off-by-one. - Acknowledged after breach. An owner acknowledges the date after it has already been escalated to a review task. Acknowledgement stops the reminder ladder, but it does not silently delete the review task, because a lapsed option may need a human to confirm whether the right was actually preserved. Instead, annotate the open task with the late acknowledgement so the reviewer closes it with full context rather than the system erasing evidence of the miss.
When to escalate further
The review queue is the end of the automation path, but some missed dates carry exposure beyond an operations reviewer’s remit. When a passed option or notice window plausibly forfeits a tenant’s right, or an estoppel deadline lapses in a way that could bind the landlord to an inaccurate statement, the task should route onward to legal with the audit trail intact — and that trail is precisely why provenance is structured, not free-text. Escalating into legal or estoppel-exposure territory means the record is about to be read by people who were not in the pipeline, so the who-saw-what history matters: enforce the access rules in security and access boundaries so the review task, its provenance, and the immutable log of every notification the ladder sent are visible to the right roles and tamper-evident. A reviewer resolving a Critical option miss should be able to prove, from that trail alone, that the platform fired every tier on schedule and that the failure was a human non-response — not a silent system fault.
For the mechanics of re-arming a schedule after resolution, and how recurring option deadlines are seeded in the first place, see the calendar guide on mapping option deadlines to recurring reminders; for how the shared queue itself is configured and drained, see implementing fallback routing for missing lease metadata fields.
Frequently asked questions
How is this different from the last reminder tier in the alerting ladder? The final reminder tier still assumes a human will respond in time; this layer begins once that assumption has failed. A reminder is a nudge toward an owner who can still act before the deadline. A review task is an admission that the deadline is gone or effectively unreachable, so it is packaged with provenance, a severity, and an SLA and handed to a triage queue — it is a work item to be worked, not another notification to be ignored.
How do I guarantee one missed date produces exactly one review item?
Derive the task’s idempotency key from stable properties of the date — lease id, date type, and breach type — and deliberately exclude the run timestamp and today. Enqueue with an insert-if-absent write backed by a unique constraint. Every subsequent sweep recomputes the identical key and the write is discarded, so retries, overlapping workers, and weeks of the task sitting open never create a duplicate.
What context does a reviewer actually need in the task? Enough to act without opening the source lease under time pressure: which canonical field lapsed, the amendment that set the deadline, the computed deadline plus the escalation-formula rule that derived it, which reminder tiers actually fired and when, and a plain-language breach reason. That structured provenance is what turns a 4-hour SLA into a solvable problem rather than a research project.
What happens if an amendment changes the deadline after a review task was filed?
Treat the amendment as an event that re-derives the date. If the corrected deadline invalidates the breach, close the existing task with a superseded_by_amendment reason and re-arm the alert schedule against the new deadline. Never leave a task pointing at a deadline that no longer exists, and never edit the deadline in place while keeping the old task open.
Should an acknowledgement that arrives after the breach auto-close the review task? No. A late acknowledgement stops the reminder ladder but should only annotate the open review task, not delete it. A lapsed option or notice window may still need a human to confirm whether anything was actually lost, so the task stays open with the late acknowledgement recorded, and a reviewer closes it deliberately.
Related
- Critical Date Alerting — the active escalation ladder whose final, unanswered rung feeds this terminal handoff.
- Mapping Option Deadlines to Recurring Reminders — how option and notice deadlines are seeded and re-armed after a review resolves.
- Fallback Routing Logic — the shared manual-review seam a breached date lands in, the same one low-confidence extractions use.
- Implementing Fallback Routing for Missing Lease Metadata Fields — field-level configuration and draining of the review queue this layer writes into.
- Security & Access Boundaries — the audit-trail and role rules that govern a review task once it carries legal exposure.