Composite Urgency Scoring: Why a P2 Referral on a Quote Expiring in 6 Hours Outranks a P1 With No Deadline
From "I sort my queue by oldest first and hope for the best" to "the score is 92, three signals are firing, and the manager knows exactly why this one came to the top" — through a transparent additive scorer that recomputes every read so deadlines never go stale.
The Problem
Every manager opening their referral queue at 8am makes the same triage call: which one first? Static priority (P1/P2/P3) is set when the rule fires — useful, but blind to time. A P1 referral set last week with no deadline pressure looks identical, in a queue sorted by priority + age, to a P1 set this morning on a quote that expires in four hours.
The half-fixes don't work:
- Sort by quote expiry date. Misses everything where the deadline is the submission effective date or the quote hasn't been issued yet.
- Manually flag "urgent" rows. Manager spends ten minutes triaging instead of deciding.
- Add an "is_urgent" boolean. Stale the moment a deadline ticks past.
- Static priority + age sort. Doesn't see deadline at all. The quote-expiring-tomorrow problem is invisible until the broker calls.
Result: managers manually scan the whole queue every morning and rely on memory to spot what's about to bite them.
The InsightUW Approach
InsightUW computes a composite urgency score server-side on every queue read. Score is a function of now (deadlines tick), so storing it would go stale immediately — the queue endpoint recomputes per row, batch-loads quotes/submissions, and sorts by score descending. Each row carries the full breakdown so the manager can hover and see why it landed where it did.
The Weight Table
Constants live at the top of uw_referral_rule_service.py. Tuning is one line.
| Signal | Weight |
|---|---|
| Priority P1 | +50 |
| Priority P2 | +20 |
| Priority P3 | 0 |
| Quote effective_date overdue | +100 |
| Quote effective_date ≤ 24h | +80 |
| Quote effective_date ≤ 48h | +50 |
| Quote effective_date ≤ 72h | +30 |
| Quote effective_date ≤ 7d | +10 |
| Quote expiry_date ≤ 24h | +30 |
| Quote expiry_date ≤ 48h | +15 |
| Age ≥ 72h | +30 |
| Age ≥ 48h | +15 |
| Age ≥ 24h | +5 |
| Blocking quote-send | +40 |
| Submission is rush | +25 |
| Premium ≥ $1M | +10 |
| Premium ≥ $500k | +5 |
Bucket thresholds for the UI badge: red ≥ 80, amber ≥ 40, neutral else. The numbers are explicit on the row and the manager hovers to see the components — priority_P2: +20, effective_date_le_24h: +80, blocking_quote_send: +40 = 140.
Why It's Not Stored
A score that looks at "hours until quote effective" goes stale every minute. Persisting it means a periodic recompute job, drift between the stored value and reality, and a class of bug where the queue claims one ranking and the underlying data says another. Recomputing on read is single-digit milliseconds per row at the queue sizes a manager sees (a few hundred max) — the simpler design wins.
Worked Example: Tuesday 8:14 AM, Jennifer's Queue
Jennifer Walsh opens /uw/referrals/queue. She's looking at six items. Here's what the scorer produces:
| # | Submission | Static | What's firing | Score | Order |
|---|---|---|---|---|---|
| 1 | SUB-2026-DO-0317 (Stronghold) | P1 | priority +50, blocking +40, eff ≤24h +80, expiry ≤24h +30, age ≥24h +5 | 205 | 1st |
| 2 | SUB-2026-PR-0144 (Coastal Mfg) | P2 | priority +20, eff overdue +100, blocking +40, age ≥48h +15 | 175 | 2nd |
| 3 | SUB-2026-CY-0421 (DataShield) | P1 | priority +50, blocking +40, eff ≤72h +30, age ≥24h +5 | 125 | 3rd |
| 4 | SUB-2026-PR-0138 (Acme Mills) | P2 | priority +20, eff ≤48h +50, blocking +40, premium ≥$1M +10, age ≥24h +5 | 125 | 4th |
| 5 | SUB-2026-DO-0312 (Apex Holdings) | P1 | priority +50, blocking +40, eff ≤7d +10, age ≥48h +15 | 115 | 5th |
| 6 | SUB-2026-PR-0149 (Riverside) | P3 | priority 0, blocking +40, age ≥24h +5 | 45 | 6th |
The intuitive triage problem solves itself. Item #2 — a P2 — outranks three P1s because its quote effective date is already overdue (+100). The static priority called it routine; the deadline says it's the most urgent thing in the queue.
Jennifer hovers row #2: tooltip shows priority_P2: +20, effective_date_overdue: +100, blocking_quote_send: +40, age_ge_48h: +15. Total 175. She knows exactly why it's #2 instead of #5.
She clicks the Overdue KPI card at the top. The query auto-applies is_overdue=true, the table filters to one row, the broker gets a call back in 90 seconds.
Deadline Filters and the KPI Strip
Capability #9 added three filters and four KPI cards — clickable as quick-picks.
Filters:
- expires within hours — closer of quote.effective_date / quote.expiry_date within N hours.
- is overdue — quote effective date already passed.
- min urgency score — numeric threshold.
KPI cards (clickable):
- Overdue — count of rows where deadline has passed.
- Expiring ≤24h — applies expires_within_hours=24 on click.
- Expiring ≤48h — same with 48.
- Top urgency score — max score across the queue. If it's ≥80, you have a screamer.
Quick-pick chips appear in the filter bar: ≤24h · ≤48h · ≤72h · Overdue · Any.
What Falls Out of the Sort
The capability #8 daily digest reads list queue directly — so the morning summary email lists items in urgency order without writing a separate ranker. The body picks up the new deadline counts: "5 in queue · 2 overdue · 1 expiring ≤24h · top score 205."
The capability #4 exceptions dashboard ignores the live score (those analytics span months — deadline doesn't matter once the referral is decided), but the same compute urgency powers the dashboard's top urgency score KPI for the current period.
What This Means for Underwriters
- Score is explainable. Every component is a named signal with a fixed weight. A manager who's tuned in can hover, read the breakdown, and decide whether to override the order.
- Deadline is dominant — but not the only signal. A P1 referral with no deadline still ranks above a P3 with no deadline. The order doesn't suddenly invert when no quote dates are populated.
- Tuning is one line. All weights are constants at the top of
uw_referral_rule_service.py. Client wants effective_date weighted more, age less? Edit. Restart. Done. No DB migration, no admin UI to build. - No stale scores. Score computes on read. A referral that gets escalated mid-morning and re-routed to chief_uw appears in their queue at the right rank as soon as they refresh.
- The KPI strip is also a filter. Cards are clickable — "Overdue: 2" is also the button that filters the table to those two rows. Triage in one click.
- Queue sort is server-driven. The frontend doesn't reorder; the backend ships rows already sorted. Adding a new signal (e.g. NAICS-restricted-class boost) is a backend-only change.
What's Next
Next: The Quote-Readiness Gate — Why InsightUW Refuses to Send a Quote With Open Referrals
Want to see why your manager queue should sort by deadline-aware urgency rather than priority + age? Request a demo.