/** * @module algo.coldThreshold * * Cold-Detection Algorithm * ======================== * * Determines whether an opportunity has stalled in a status long enough * to be considered "cold". When an opportunity goes cold it is * automatically moved to InternalReview, a system-generated activity is * logged, and it is flagged for the internal review report. * * ## Thresholds (defaults) * * | Status | Stall Threshold | * |-----------------|-----------------| * | QuoteSent | 14 days | * | ConfirmedQuote | 30 days | * * Only these two statuses are eligible for cold detection. All other * statuses return `cold: false`. * * ## How "last activity date" is determined * * The algorithm uses `lastActivityDate` — the most recent of: * - the latest activity's `dateStart` * - the opportunity's `cwLastUpdated` * * The caller is responsible for resolving this value before calling * `checkColdStatus`. */ import type { OpportunityController } from "../../controllers/OpportunityController"; // --------------------------------------------------------------------------- // Config // --------------------------------------------------------------------------- /** Stall thresholds in milliseconds, keyed by CW status ID. */ export const COLD_THRESHOLDS: Record = { /** QuoteSent — CW status ID 43, "03. Quote Sent" */ 43: { days: 14, ms: 14 * 24 * 60 * 60 * 1000 }, /** ConfirmedQuote — CW status ID 57, "04. Confirmed Quote" */ 57: { days: 30, ms: 30 * 24 * 60 * 60 * 1000 }, }; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export interface ColdCheckInput { /** Current CW status ID of the opportunity. */ statusCwId: number | null; /** * The most recent meaningful date to measure staleness from. * Typically the latest of the last activity dateStart or cwLastUpdated. */ lastActivityDate: Date | null; /** Override for "now" — useful for testing. Defaults to `new Date()`. */ now?: Date; } export interface ColdCheckResult { /** Whether the opportunity is considered cold. */ cold: boolean; /** * Which threshold triggered the cold flag. * `null` when `cold` is `false`. */ triggeredBy: { statusCwId: number; statusName: string; thresholdDays: number; staleDays: number; } | null; } // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- const STATUS_NAMES: Record = { 43: "QuoteSent", 57: "ConfirmedQuote", }; // --------------------------------------------------------------------------- // Core // --------------------------------------------------------------------------- /** * Evaluate whether an opportunity has exceeded its cold-stall threshold. * * @returns A `ColdCheckResult` indicating cold status and trigger metadata. */ export function checkColdStatus(_input: ColdCheckInput): ColdCheckResult { // Bypassed — always returns not-cold until cold-stall feature is ready return { cold: false, triggeredBy: null }; }