Triage rules
Thresholds come from config/triage.yaml. Rule conditions and reason strings come from apps/api/src/services/triage.ts. There is no parallel catalogue of rules.
Thresholds
Section titled “Thresholds”# Priority is an additive weighted sum of flag severities.# Closed deals (step 7) are excluded from pipeline totals and from triage.## Formula: priority = sum(weight[severity] for each uncleared, unexpired flag)# + judgement_bonus if any judgement flag is present# Higher number ranks higher on the needs-attention list.
reference_date: "2026-08-14"quarter_end: "2026-09-30"
stale_days_by_step: 1: 90 2: 60 3: 45 4: 30 5: 21 6: 14
# MEDDIC evidence added within this many days either side of a step/P2 increase.# 21 covers the Northgate P2 jump (17 days before reference_date) so the# unsupported_jump scenario still fires while the threshold remains config.unsupported_jump_days: 21
close_date: # "more than K times" — two or more later-moves fire the flag max_slips: 1 max_days_past_original: 45
ratio_bands: healthy_min: 15 healthy_max: 60
# RAG is derived ONLY from MEDDIC completeness vs required_at_step in gates.yaml.# Analyst output is not an input.rag: green_max_missing: 0 amber_max_missing: 2
priority_weights: high: 10 medium: 5 low: 1 judgement_bonus: 3
judgement_ttl_days: 14
# P2 = 90 requires at least this many MEDDIC criteria evidenced.high_p2_min_evidenced: 5
suppression: min_sample: 5 wrong_ratio: 0.6
graduation: min_sample: 5 right_ratio: 0.8Rule implementations
Section titled “Rule implementations”The following excerpts are the reason strings the engine actually emits.
No activity for ${ageDays} days. At step ${deal.step} the tolerance is ${staleDays} days (triage.yaml stale_days_by_step).meddic_gap
Section titled “meddic_gap”Required at step ${deal.step} but neither evidenced nor marked unknown: ${missing.join(", ")}.ratio_outlier
Section titled “ratio_outlier”This rep's open-pipeline ratio sits outside the healthy band ${input.triage.ratioBands.healthyMin}–${input.triage.ratioBands.healthyMax}% (triage.yaml ratio_bands).close_date_slipped
Section titled “close_date_slipped”Close date has moved later ${laterMoves.length} time(s) (threshold: more than ${input.triage.closeDate.maxSlips}) and is ${daysPastOriginal} days past the original close date (threshold: ${input.triage.closeDate.maxDaysPastOriginal} days).close_date_past
Section titled “close_date_past”Close date ${deal.closeDate} is in the past and the deal is still open at step ${deal.step}.unsupported_jump
Section titled “unsupported_jump”Step or P2 increased without MEDDIC evidence for ${missing.join(", ")} within ${windowDays} days either side of the change (triage.yaml unsupported_jump_days).