From 465650957be4d7cdc85e09f5a9020b9af4582669 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 14 May 2026 14:56:58 +0200 Subject: [PATCH] fix(pipeline-refactor): purge stale 9-stage name references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit of every '*_sent' / '*_signed' / 'in_communication' / 'details_sent' / 'deposit_10pct' / 'completed' literal under src/ caught four genuinely broken sites that migration 0062 collapsed away but the runtime code never followed through on: 1. alert-rules.ts: `interest.stale` matched 'details_sent' / 'in_communication' / 'eoi_sent' — none of which exist post-migration. The alert never fired. Updated to the new mid-funnel canon (enquiry / qualified / nurturing). 2. berth-recommender.service.ts: TWO copies of the same stage-rank CASE (one for active history, one for fallthrough scoring) referenced the full legacy 8-stage ladder. Every WHEN missed → MAX(...) returned 0 → tier-ladder + heat-score logic collapsed silently. Rebuilt both against the 7-stage canon mirroring getHotDeals. 3. interests.service.ts: clearInterestOutcome reopen default was the dead 'in_communication'. Switched to 'qualified' (closest analog; rep can still override via data.reopenStage). Pre-fix, any reopened deal fell through safeStage() to 'enquiry'. 4. report-generators.ts: revenue-PDF "total completed" filter intersected pipeline_stage='completed' AND outcome='won'. The stage filter is redundant today (setInterestOutcome always writes 'completed' for terminal outcomes) and is brittle to the upcoming sentinel-stage cleanup. Dropped the stage filter — outcome='won' is the canonical money-changed-hands signal. Follow-up flagged: setInterestOutcome still writes pipeline_stage = 'completed' as a sentinel, which is non-canonical under the new 7-stage type (PIPELINE_STAGES doesn't include 'completed'). Migration 0062's intent is `outcome` carries terminal state forward; pipeline_stage stays in-canon. Cleaning up requires sweeping every consumer of pipeline_stage='completed' as a terminal marker — separate commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib/services/alert-rules.ts | 15 ++++++++-- src/lib/services/berth-recommender.service.ts | 30 +++++++++---------- src/lib/services/interests.service.ts | 7 ++++- src/lib/services/report-generators.ts | 17 +++++------ 4 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/lib/services/alert-rules.ts b/src/lib/services/alert-rules.ts index a054d9ba..2b093754 100644 --- a/src/lib/services/alert-rules.ts +++ b/src/lib/services/alert-rules.ts @@ -74,9 +74,18 @@ async function reservationNoAgreement(portId: string): Promise // Pipeline stuck in mid-funnel stages with no contact for 14+ days. async function interestStale(portId: string): Promise { - // Mid-funnel stages where silence is a problem. EOI/deposit/contract stages - // have their own dedicated alerts (eoi.unsigned_long, deposit_overdue, etc.). - const STALE_STAGES = ['details_sent', 'in_communication', 'eoi_sent']; + // Mid-funnel stages where silence is a problem. EOI / reservation / + // deposit / contract stages have their own dedicated alerts + // (eoi.unsigned_long, reservation.no_agreement, deposit_overdue, etc.), + // so this alert sits before signing kicks in. + // + // 2026-05-14 pipeline-refactor sweep: the prior values + // ('details_sent', 'in_communication', 'eoi_sent') were collapsed by + // migration 0062 into the 7-stage canon (enquiry / qualified / + // nurturing / eoi / ...). Until this fix landed, this alert never + // fired because no row in the new schema carried the dead stage + // strings. + const STALE_STAGES = ['enquiry', 'qualified', 'nurturing']; const rows = await db .select({ id: interests.id, diff --git a/src/lib/services/berth-recommender.service.ts b/src/lib/services/berth-recommender.service.ts index b894b03b..90f6148d 100644 --- a/src/lib/services/berth-recommender.service.ts +++ b/src/lib/services/berth-recommender.service.ts @@ -482,14 +482,13 @@ export async function recommendBerths(args: RecommendBerthsArgs): Promise