• contact@verticalserve.com
Home / Engineering / Post 101
Engineering Blog · Post #101

Sticky Pins and Dormancy Gaps: How InsightUW Tracks the First Bind With a New Broker AND Re-Engages Dormant Brokers Without Forking the Milestone Log

From "broker management asks every Friday at the team meeting 'who bound something with a new broker this week?' and the production team manually tags policies in a spreadsheet, while no one notices that Acme Brokerage hasn't bound new business in seven months because there's no calendar reminder for relationships that quietly went dormant" to "the bind transaction stamps a first bind milestone for production fan-out + a re engagement milestone when a broker bound new business after a 90+ day gap; days-since-last-bind is computed at read time so chips never go stale; the broker activity dashboard ranks dormant brokers by days-since-last-new-deal so re-engagement work has a queue" — through one event log table reused for two cap purposes, one running-counter cache, and the deliberate decision that first-ever is event-shaped while last-bind elapsed time is state-shaped.


The Problem

Two related but distinct broker-relationship signals need surfaces:

  • First bind with a new broker. A relationship just went live. Production team wants to know — they sponsored the appetite onboarding, the sales pipeline conversion, sometimes commission terms. Compliance wants the documented "first bound" record. The UW wants a chip on their bound bind request that says "🎉 first bind with this broker."

  • Dormant broker re-engagement. A broker bound 14 deals last year, then went silent for 7 months, then suddenly bound a new program last Tuesday. That's not "first bind" (they have 14 prior); it's also not noise (they were dormant). It's a re-engagement, with different operational implications — production wants to know they're back, but probably also wants to know why they went silent.

The signals overlap structurally (broker-level event triggered by a bind) but have different lifecycle semantics:

  • First-ever is once forever — a broker becomes "first bound" exactly once in the relationship. Even if they go dormant for years and come back, they don't become "first bind" again.
  • Re-engagement is possibly recurring — a broker can dormancy-cycle multiple times in their lifetime; each new-business bind that crosses the dormancy threshold is its own event.

Plus the UI needs both:
- An event log queue ("recent first-bind events") for production team review with acknowledge workflow.
- A running counter dashboard (last_bind_at, days_since_last_bind, days_since_last_new_business_bind) for broker-level monitoring.

The usual fixes don't fix:

  • Event log only. Then "days since last bind for Acme" requires a max() over the event log every read. Slow at scale.
  • Counter only. Then "what was the production-fanout audit story for the May 12 bind?" has no answer.
  • One event log with all milestone types collapsed. Then "first_bind" shape (fan out to broker management, ack workflow) and "re_engagement" shape (fan out to broker management, plus attach the dormancy gap, plus show prior date in notes) bleed into one row.

The root cause: first-ever and last-bind-elapsed are different shapes that share a trigger.

The InsightUW Approach

Broker Milestone is the event log (cap #4, reused by cap #5 for re-engagement). Broker Activity State is the 1:1 running counter cache (cap #5). Both update from the same cap #3 bind hook, in-transaction, with idempotency.

graph TD subgraph Bind["Cap #3 _bind commits Policy"] Policy["Policy.broker id = X<br/>Policy.lob = property"] end subgraph Cap4["Cap #4 detect and flag milestones"] Prior["prior count = Count(Policy<br/>Where broker id=X AND guid != current<br/>AND policy status IN active/expired/cancelled/non renewed)"] First["prior count == 0?<br/>→ emit milestone type='first bind'<br/>+ set Policy.is first bind with broker=true"] LOB["else: prior lob == 0?<br/>→ emit milestone type='first bind lob'<br/>(quieter signal)"] Fanout["Fan out to Broker Team Recipient<br/>(LOB-scoped) + UW + manager"] end subgraph Cap5State["Cap #5 update state on bind"] Quote Type["Read quote type via<br/>policy.source quote guid → Quote.quote type"] Is Nb["is new business = (quote type == 'new business')"] State["Upsert Broker Activity State<br/>broker id=X<br/>last bind at = now<br/>last bound policy guid = current<br/>total bind count += 1<br/>premium ytd += if current year"] NB["if is new business:<br/>last new business bind at = now<br/>new business bind count += 1"] end subgraph Reengagement["Re-engagement detection (cap #5)"] GAP["if is new business AND prior last new business bind at exists:<br/>gap days = (now - prior).days"] Threshold["if gap days >= Broker Activity Config.dormancy days (default 90):<br/>emit Broker Milestone<br/>milestone type='re engagement'<br/>notes='Re-engagement after &lt;N&gt;-day dormancy gap'"] Fanout2["Fan out via cap #4's recipient list"] end subgraph Reads["Read paths"] Feed["broker-milestones-feed<br/>(cap #4 + #5 events together,<br/>filter by type)"] Dash["broker-activity-dashboard<br/>(cap #5 state with computed days since)"] Chip["bind-request-detail chips<br/>'🎉 First bind' / '🔄 Re-engagement'<br/>'Last bind: 14d ago'"] end Policy --> Prior Prior --> First Prior --> LOB First --> Fanout LOB --> Fanout Policy --> Quote Type Quote Type --> Is Nb Is Nb --> State Is Nb --> NB State --> GAP GAP --> Threshold Threshold --> Fanout2 Fanout --> Feed Fanout2 --> Feed State --> Dash NB --> Dash Feed --> Chip Dash --> Chip

Two milestone types, one table — append-only

Broker Milestone.milestone_type carries:
- first bind — broker had zero prior bound policies; primary signal; full team fan-out
- first bind lob — broker established but new to this LOB; quieter signal; only managers/broker_managers fan out
- re engagement — added by cap #5; new-business bind closed a dormancy gap

The table is append-only. Once created, rows are mutated only in the acknowledgment fields (acknowledged at, acknowledged by, acknowledgment notes). Production team's "ack" closes the loop without losing the original event.

Idempotency: cap #4 detection short-circuits when (policy_guid, milestone_type) already exists. Re-running on the same policy is a no-op.

is first bind with broker — fast-read flag on Policy

The milestone row is the audit detail. Policy.is_first_bind_with_broker = True is the fast-read flag — UI loads the policy and shows the chip without joining Broker Milestone. Set by cap #4 detection; never unset (once first-ever, always first-ever).

For "first in this LOB" the chip reads from the milestone row directly (no flag column on Policy because LOB-scoped milestones can fire many times for the same broker over time).

Cap #5 running counter cache

Broker Activity State is 1:1 per broker. Updated in-transaction from cap #3 bind after cap #4. Reads quote type from policy.source_quote_guid → Quote.quote_type to distinguish new-business from renewals; renewals satisfy last bind at but not last new business bind at.

Days-since values aren't stored — they're computed at read time:

So chips never go stale. The state row carries the timestamp; the interval is always current.

Re-engagement — the third milestone type

In update state on bind:

The order matters: read the prior last new business bind at, compute gap, decide if re-engagement fires, then update. Otherwise the gap is always 0.

The re-engagement event uses the same Broker Milestone table (one less table), the same recipient registry (cap #4's Broker Team Recipient), the same milestones-feed UI (cap #4's). The only differences are milestone_type='re_engagement' and a different rendered label ("🔄 Re-engagement after dormancy") in the feed.

Configurable thresholds per org

Broker Activity Config is single-row org config:

Days-since chip color reads from these thresholds at render time:
- days_since <= warn_days → green/info
- <= alert_days → amber/warning
- > alert_days → red/alert
- null (never) → neutral

Admin tunes via /uw/admin/broker-activity-config.

Recompute path for legacy data

When clients onboard with prior policy history, the Broker Activity State rows don't exist yet. recompute walks every broker's Policy ordered by effective_date asc and reconstructs state from scratch:

Idempotent. Safe to run anytime. Useful one-shot for backfill; clients can also schedule it for housekeeping.

Broker A: Acme Brokerage — first bind ever

Acme just signed an agency agreement; their first quote got authorized. Cap #3 bind commits Policy P-2026-A8F4C2 for broker_id=acme.

Cap #4 detection:
- prior_count = 0 (no prior policies for Acme).
- Emit Broker Milestone(milestone_type='first_bind') with broker_name='Acme Brokerage', policy_number='P-2026-A8F4C2', bound_at=now, bound_by_uw='sarah', team_notified_at=now.
- Set Policy.is_first_bind_with_broker=true.
- Resolve recipients via Broker Team Recipient (active rows; no LOB scope or LOB matches 'property'): production manager, broker management VP, plus UW Sarah, plus Sarah's manager. 4 recipients.
- Fan out 4 Notification rows.

Cap #5 update_state:
- No existing Broker Activity State for Acme → create row.
- last_bind_at=now, last_new_business_bind_at=now (new business), counts = 1.
- No prior last new business bind at → no re-engagement check fires.

broker-milestones-feed shows:
bind-request-detail chip: 🎉 First bind with broker — Acme Brokerage.

Broker B: Pinnacle Insurance — first in this LOB

Pinnacle has 6 prior bound policies (all GL). Today they bind their first Property — P-2026-PR-9982.

Cap #4 detection:
- prior_count = 6 (≠ 0) → first_bind does NOT fire.
- prior_lob = COUNT(Policy where broker_id=pinnacle AND lob='property') = 0 → emit Broker Milestone(milestone_type='first_bind_lob') with lob='property'.
- Recipient resolution narrows: only recipient_role IN ('manager', 'broker_manager') for first bind lob (quieter signal). 2 recipients (no producer / informational subscribers).

Cap #5 update_state:
- Existing Broker Activity State for Pinnacle. Update last bind at, last new business bind at, increment counts.
- Prior last new business bind at was 18 days ago → gap = 18 < dormancy_days (90) → no re-engagement.

bind-request-detail chip: First in this LOB — Pinnacle Insurance.

Broker C: Sterling — re-engagement after 142-day dormancy

Sterling has 23 prior bound policies. Last new-business bind was 142 days ago (Q4 last year); they renewed two old programs in Q1 but no new business. Today they bind P-2026-DO-7104, a new D&O program.

Cap #4 detection:
- prior_count = 23 → first_bind does NOT fire.
- prior lob for D&O = 4 → first_bind_lob does NOT fire.

Cap #5 update_state:
- Read source quote → quote_type='new_business' → is_new_business = true.
- Prior last new business bind at = 142 days ago.
- Gap = 142 days. dormancy_days = 90. Gap ≥ threshold → fire re-engagement.
- Insert Broker Milestone(milestone_type='re_engagement') with broker_name='Sterling Brokers', policy_number='P-2026-DO-7104', notes="Re-engagement after 142-day dormancy gap. Previous new-business bind: 2025-12-04T16:32Z.".
- Fan out via cap #4's Broker Team Recipient registry (full fan-out, not the quieter first bind lob filter).
- Update state: last_new_business_bind_at=now, increment new_business_bind_count.

broker-milestones-feed shows:
Production VP picks up the phone — calls Sterling's principal to ask "what got you back, what would keep you here." Acknowledgment notes capture the conversation. Decision evolution is documented.

Broker activity dashboard — Sterling appears in Dormant

Two days before the re-engagement bind, the broker-activity-dashboard's "Dormant" tab (filter: days_since_last_new_business_bind >= dormancy_days) showed Sterling at the top:

After today's bind, Sterling drops out of the Dormant tab and reappears in Recent Activity ("Sterling — last bind 0d ago"). Production team can use the dormant-tab queue for proactive outreach before brokers churn — the daily list of "haven't bound new business in 90+ days" is a re-engagement work surface.

What This Means for Underwriters

  1. Two milestone types in one table. first bind (cap #4 primary) and re engagement (cap #5) share Broker Milestone, the same recipient registry, the same feed UI. Cap #5 is event-shape via cap #4's table; cap #5 is also state-shape via Broker Activity State.

  2. is first bind with broker is a fast-read flag. UI loads Policy and shows the chip without joining the milestone log.

  3. Days-since computed at read time. Broker Activity State stores timestamps; chips never go stale. No daily refresh needed.

  4. Renewals satisfy last bind at but not last new business bind at. A broker who renewed yesterday but hasn't won a new deal in 6 months is a re-engagement candidate when they next bind new business.

  5. Re-engagement is recurring. Unlike first bind which fires once forever, a broker can dormancy-cycle multiple times — each new-business bind closing a 90+ day gap is its own re_engagement milestone.

  6. Per-org configurable thresholds. dormancy_days / warn_days / alert_days drive both the re-engagement firing AND the chip color coding. Single-row config; admin edits.

  7. Recompute is idempotent. Backfill state for legacy policy history by walking Policy ordered by effective_date. Safe to re-run anytime.

  8. Quieter recipient set for first bind lob. Cross-LOB cross-sells fire to managers + broker_managers only; full team fan-out is reserved for true first-ever binds and re-engagements.

  9. Broker-activity dashboard's Dormant tab is a queue. Production team works the list of brokers who haven't bound new business in N+ days — proactive outreach instead of reactive reminders.

  10. Acknowledgment closes the loop. Append-only milestones get an acknowledgment with notes; the production team's "we noticed, we acted" record is on the row, not in someone's email.

What's Next

The Bind & Issue module is complete. Next: blogs on integrations beyond Bind, or a deep-dive on one of the smaller-but-quirky caps (the layer-aware fac placement state machine, the broker-authorization regex+veto pattern, the cap #8 dispatcher registration into the Quote-cap-#14 worker).


Want to see how one event-log table + one running-counter cache cover both first-bind milestones and dormancy re-engagement signals without forking your broker-relationship workflow? Request a demo.

See InsightUW run on your data

A 45-minute working session with a real broker email and your LOBs.

Request a demo