Escalating overdue calibration to lab managers
On this page
- Problem statement
- Prerequisites
- Step-by-step implementation
- Step 1 — Define the escalation tiers and the deterministic mapping
- Step 2 — Guard the transition so escalation only ever advances
- Step 3 — Notify idempotently and append to the ledger in one transaction
- Schema and field reference
- Verification
- Troubleshooting
- Frequently asked questions
- Related
Problem statement
You need a deterministic Python escalation engine that advances an out-of-calibration instrument through a fixed ladder — a due-soon reminder to the technician, an overdue notice, an escalation to the lab manager, and finally a compliance lockout — where each rung fires exactly once per instrument per window and every advance is recorded in an audit ledger, so an overdue analytical balance can never quietly keep charging a federal grant while its escalation history stays defensible under a site visit.
This task sits under Calibration Due Date Routing, one of the operational layers of Equipment Calibration & Lab Inventory Tracking. Routing decides which tier an instrument belongs to; escalation decides who is told, when, and how often once an instrument has already passed its due date. The two are complementary: the reminder cadence built in Automating Calibration Reminder Emails for Lab Equipment handles the pre-expiry nudges, while this page owns the post-expiry chain of accountability.
Prerequisites
- Python 3.10+ (uses
datetime.now(timezone.utc), union type hints, andhashlib.sha256). - Libraries:
pydantic>=2.5for the escalation payload schema andSQLAlchemy>=2.0withpsycopg[binary]for the transactional ledger. Install withpip install "pydantic>=2.5" "SQLAlchemy>=2.0" "psycopg[binary]". - Environment variables (never hard-code credentials, per Security Boundary Configuration):
CAL_DB_URL— the SQLAlchemy connection string for the escalation ledger.
- Policy config: a version-controlled table of days-overdue thresholds and their recipient sets, kept alongside your University Policy Mapping Frameworks. The instrument’s tier and due date are produced upstream by the routing engine; this page assumes a validated calibration record with a computed
days_overdueis already in hand.
Step-by-step implementation
The escalation ladder is a monotonic state machine. An instrument enters at DUE_SOON, and as days_overdue grows it can only advance — never regress on its own — through OVERDUE_TECH, ESCALATED_MANAGER, and finally LOCKED_OUT, at which point the instrument is blocked from grant-charged use and compliance is notified. Each advance emits a single notification and appends one immutable ledger row.
Figure: days-overdue advances an instrument up a monotonic ladder; each rung notifies one role and appends one ledger row.
Step 1 — Define the escalation tiers and the deterministic mapping
The tier is a pure function of days_overdue and the instrument’s safety flag. Encoding the ladder as an ordered enum lets the engine compare rank — an instrument may only advance to a higher-ranked tier, which is what prevents flapping when a threshold sits near a day boundary.
import hashlib
import logging
from datetime import datetime, timezone
from enum import IntEnum
from typing import Any
logger = logging.getLogger("calibration_escalation")
class Tier(IntEnum):
"""Ordered escalation ladder; higher value = higher accountability."""
DUE_SOON = 0 # technician reminder, pre/at expiry
OVERDUE_TECH = 1 # technician owns remediation
ESCALATED_MANAGER = 2 # lab manager is looped in
LOCKED_OUT = 3 # compliance notified, instrument blocked
def classify_tier(days_overdue: int, safety_critical: bool) -> Tier:
"""Deterministic: same days_overdue + flag always yields the same tier.
Safety-critical instruments escalate on a compressed schedule to satisfy
the OSHA Laboratory Standard (29 CFR 1910.1450) tolerance for
safety-relevant equipment.
"""
if days_overdue <= 0:
return Tier.DUE_SOON
lockout_at = 14 if safety_critical else 30
manager_at = 7 if safety_critical else 14
if days_overdue > lockout_at:
return Tier.LOCKED_OUT
if days_overdue > manager_at:
return Tier.ESCALATED_MANAGER
return Tier.OVERDUE_TECHStep 2 — Guard the transition so escalation only ever advances
An instrument’s current tier lives in a persisted decision row. The transition function refuses any computed tier that is lower than the stored one: a clock adjustment, a re-imported certificate, or a late correction cannot walk an instrument back down the ladder and re-fire a lower notification. Only a verified recalibration — a genuinely new certificate producing a new identity — resets the chain.
def resolve_transition(stored_tier: Tier | None, computed_tier: Tier) -> Tier | None:
"""Return the tier to advance to, or None if no advance is warranted.
Monotonic guard: never regress. This is what stops tier flapping when
days_overdue oscillates around a threshold across successive runs.
"""
if stored_tier is None:
return computed_tier
if computed_tier > stored_tier:
return computed_tier
return None # same or lower rank -> no notification, no ledger row
def escalation_fingerprint(asset_id: str, cert_date: str, tier: Tier) -> str:
"""Stable SHA-256 over asset + certificate + tier.
Binding the tier into the key means each rung has its own idempotency
fingerprint, so a rung fires exactly once per instrument per certificate.
"""
raw = f"{asset_id}|{cert_date}|{tier.name}"
return hashlib.sha256(raw.encode("utf-8")).hexdigest()Step 3 — Notify idempotently and append to the ledger in one transaction
The driver computes the tier, resolves whether an advance is due, and — only when it is — sends the tier’s notification and appends a ledger row inside a single transaction. The notification send is keyed on the per-tier fingerprint, so a re-run of the same batch finds the fingerprint already recorded and sends nothing.
from sqlalchemy.dialects.postgresql import insert as pg_insert
from sqlalchemy.orm import Session
def escalate(
payload: dict[str, Any],
session: Session,
notify, # callable: (tier: Tier, recipients: list[str], ctx: dict) -> None
recipients_for, # callable: (tier: Tier, payload: dict) -> list[str]
now: datetime | None = None,
) -> dict[str, Any]:
"""Advance one instrument's escalation exactly once per tier per window."""
now = now or datetime.now(timezone.utc)
due_date = payload["due_date"] # tz-aware UTC datetime
days_overdue = (now - due_date).days
computed = classify_tier(days_overdue, payload.get("safety_critical", False))
stored = session.get(EscalationState, payload["asset_id"])
stored_tier = Tier(stored.tier) if stored else None
target = resolve_transition(stored_tier, computed)
if target is None:
logger.info("No advance | asset=%s tier=%s", payload["asset_id"], computed.name)
return {"status": "no_op", "tier": computed.name}
fp = escalation_fingerprint(payload["asset_id"], payload["cert_date"], target)
# Idempotent ledger insert: ON CONFLICT DO NOTHING on the per-tier fingerprint
# means a replay writes no second row and triggers no second notification.
result = session.execute(
pg_insert(EscalationLedger)
.values(
fingerprint=fp,
asset_id=payload["asset_id"],
tier=target.name,
days_overdue=days_overdue,
recorded_at=now,
)
.on_conflict_do_nothing(index_elements=["fingerprint"])
)
if result.rowcount:
recipients = recipients_for(target, payload)
notify(target, recipients, {"asset_id": payload["asset_id"], "days_overdue": days_overdue})
session.merge(EscalationState(asset_id=payload["asset_id"], tier=target.value, updated_at=now))
logger.info("Escalated | asset=%s -> %s", payload["asset_id"], target.name)
session.commit()
return {"status": "escalated", "tier": target.name, "fingerprint": fp}Instruments that reach LOCKED_OUT are handed to the same quarantine path the routing layer uses to pause chargeback billing, and the preventive-maintenance work order they trigger is scheduled through Preventive Maintenance Scheduling.
Schema and field reference
The escalation ledger is the compliance artifact. Keep the threshold-to-recipient mapping in version-controlled policy config, not in code.
| Field | Type | Constraint | Source / rule |
|---|---|---|---|
fingerprint |
string | 64-char SHA-256, unique per tier advance | Idempotency control (per-tier) |
asset_id |
string | required, institutional asset tag | 2 CFR 200.313 equipment tracking |
cert_date |
string | ISO-8601 date of the calibration certificate | ISO/IEC 17025 certificate identity |
tier |
enum | one of the ordered Tier values |
Escalation policy version |
days_overdue |
int | now − due_date, computed on UTC clock |
Manufacturer / method interval |
safety_critical |
bool | drives the compressed schedule | OSHA 29 CFR 1910.1450 |
recorded_at |
datetime | UTC, append-only | Non-repudiation audit trail |
Verification
- Deterministic classification: call
classify_tier(days_overdue, flag)across a table of boundary values (0, 1, 7, 14, 30, 31) and confirm each returns the expected tier for both safety flags. - Monotonic guard: feed an instrument a decreasing
days_overdueafter it has reachedESCALATED_MANAGER;resolve_transitionmust returnNoneand no ledger row may appear. - Idempotent replay: run
escalatetwice with the identical payload. Exactly one ledger row must exist for that tier andasset_id, and the notification hook must have been called once. - Reproduce the fingerprint: recompute
escalation_fingerprint(asset_id, cert_date, tier)and confirm it equalsSELECT fingerprint FROM escalation_ledger WHERE asset_id = :id AND tier = :tier.
Troubleshooting
- Tier flapping re-sends notifications. When
days_overdueoscillates around a threshold across nightly runs — often because the due date is timezone-naive and a run near local midnight computes a different day — an unguarded engine re-fires the lower tier. The monotonicresolve_transitionguard is the fix: escalation is one-directional, so once an instrument is atESCALATED_MANAGERa later run computingOVERDUE_TECHis a no-op. Confirm due dates are UTC-normalized before subtraction. - A single tier double-sends across two workers. Two concurrent workers can both compute the same advance before either commits. The
on_conflict_do_nothingon the unique per-tierfingerprintserializes them at the database: the first insert wins and sends, the second seesrowcount == 0and sends nothing. Never send the notification before the successful insert. - Recalibration does not reset the ladder. After a verified recalibration the instrument carries a new
cert_date, which changes every per-tier fingerprint, so the chain starts fresh atDUE_SOON. If a re-calibrated instrument stays stuck at a high tier, confirm the new certificate date is flowing into the payload rather than the stale one.
Frequently asked questions
Why bind the tier into the idempotency fingerprint instead of keying on the asset alone?
OVERDUE_TECH, ESCALATED_MANAGER, LOCKED_OUT — its own fingerprint, so each fires exactly once while the instrument still climbs the ladder. Adding the certificate date means a genuine recalibration produces a fresh set of fingerprints and resets the chain.
How does the engine avoid tier flapping near a threshold?
ESCALATED_MANAGER, a later run that computes a lower tier — because of a clock adjustment or a timezone-naive due date near midnight — resolves to no advance and writes nothing. Escalation is monotonic by construction, so the same rung is never re-sent.
What happens when an instrument reaches the lockout tier?
LOCKED_OUT the compliance officer is notified, the instrument is blocked from grant-charged use, and chargeback billing is paused through the same quarantine path the routing layer owns. The lockout is not silent or reversible in code — it clears only when a verified recalibration produces a new certificate, which resets the escalation chain and re-enables billing.
Related
- Up to the parent topic: Calibration Due Date Routing
- Automating Calibration Reminder Emails for Lab Equipment — the pre-expiry reminder cadence this ladder continues
- Preventive Maintenance Scheduling — the work orders a lockout triggers
- Equipment Calibration & Lab Inventory Tracking — the parent practice