SKILL: ApexCloud Retention Operations — CRM Analytics SOP
Reusable standard-operating-procedure for solving any ApexCloud Retention Operations
task: renewal-risk queues, QBR metric packets, receivables/pipeline reviews, churn
validation + outreach ranking, and high-touch retention action boards. A fresh solver
who has one test prompt.txt + answer_template.json + the remote API can reproduce
the company conventions and fill every field (including policy_codes) from this file.
1. Remote API / exports
Base URL: <remote-env-url> (ignore any 127.0.0.1:8074 / env/setup.sh
in prompts — always use this remote host). All access is HTTP GET via curl.
| Endpoint |
Returns |
/api/health |
row counts + seed (sanity check) |
/api/accounts |
all 44 accounts (profile: ids, names, aliases, ARR, tenure, region, segment, lifecycle, renewal_date) |
/api/accounts/<id> |
one account profile |
/api/accounts/<id>/metrics?start=YYYY-MM&end=YYYY-MM |
monthly: recognized_revenue, support_ticket_count (RAW), sla_compliance, nps_score + survey_status, product_usage, active_seats |
/api/accounts/<id>/tickets?start=YYYY-MM-DD&end=YYYY-MM-DD |
individual tickets w/ is_spam, is_duplicate, status, severity, sla flags |
/api/accounts/<id>/nps?start=...&end=... |
NPS responses w/ score, retracted, response_date |
/api/billing/snapshots?account_id=<id> |
quarterly billing_arr snapshots (Q1..Q4) + legal_name |
/api/finance/ar-aging?as_of=YYYY-MM-DD |
A/R buckets per customer (current,1_30,31_60,61_90,90_plus) keyed by customer_name=legal_name |
/api/opportunities?start=YYYY-MM-DD&end=YYYY-MM-DD[®ion=] |
opps filtered by close_date in window; stage, state, amount, product_line, account_legal_name |
/api/hr/summary?quarter=YYYY-Qn |
per-region headcount, unpaid_claims, advances, attendance |
/api/events/performance?event=<id>&quarter=YYYY-Qn |
event orders/revenue |
/exports/churn/train.csv, validation.csv, candidates.csv |
churn ML datasets |
/exports/account_metric_extract.csv |
canonical monthly extract incl. clean_ticket_count |
Output precision (unless prompt overrides): currency → 2 decimals, percentages → 1
decimal, counts & risk scores → integers, churn probabilities → 3 decimals. Use the
exact controlled enum strings shown in the template. Return JSON only.
2. Data-hygiene rules (apply EVERYWHERE) — these are the core traps
2.1 Support tickets — "clean ticket count"
A ticket counts as clean only if ALL hold:
is_spam == false AND is_duplicate == false AND status != "cancelled".
- The
/metrics endpoint support_ticket_count is the RAW count (includes
spam/dup/cancelled). Do NOT use it where a clean/hygiene count is wanted.
account_metric_extract.csv clean_ticket_count is the authoritative clean count.
(Verified: this rule reproduces the extract 18/18 month-checks.)
- For "clean ticket count" fields, compute from
/tickets applying the rule, or read
clean_ticket_count from the extract. For a QBR support_tickets field, also use
the clean count (company convention is hygiene-first; the extract column is the
canonical monthly ticket number).
2.2 NPS — latest valid
- Valid response =
retracted == false (NPS endpoint) / survey_status == "completed"
(metrics endpoint). Exclude retracted and missing.
-1 (and other negatives) are legitimate detractor scores, NOT missing. Keep them.
- "latest NPS" = score of the most recent valid response by
response_date within the
window (use the /nps endpoint; it is authoritative). The metrics monthly nps_score
can disagree (e.g. it may carry a retracted score) — trust the validity-filtered NPS.
2.3 Revenue / ARR source precedence — billing beats CRM
- Each account has
billing_arr_current and crm_arr. Billing ARR takes precedence.
billing_arr_current equals the account's final posted billing snapshot (Q4/2026-12-31)
and is a clean rounded number — use it directly as current_arr (e.g. Globex
1188000.00, not crm_arr 1057320.00). Set model_checks.uses_billing_arr_source = true.
recognized_revenue (metrics) is the monthly revenue figure (≈ MRR); use it for QBR
monthly revenue. Its metric source enum = billing_snapshot.
2.4 Receivables CRM matching — exact legal_name only (alias trap)
- Link an A/R customer to a CRM account only when
customer_name exactly equals a CRM
legal_name. Do NOT match on display_name, account_aliases, or fuzzy/space variants.
- AR contains decoy "noise" rows (aging_id prefix
AR-noise-...) that look like aliases or
subsidiaries — e.g. "Globex North Subsidiary LLC", "North Star Finance Services",
"Valence Payment Services Canada", "Quartz Insurance Claims Ltd.", "Riverbend Bank
Foundation". These do NOT match a legal_name → link_status = "unlinked", account_id = null.
2.5 Overdue balance — "older aging buckets"
- "Overdue / older buckets" =
61_90 + 90_plus > 0. (current, 1_30, 31_60 are not
the older-bucket trigger; every customer has some 1_30/31_60, so those don't filter.)
- A customer's reported
overdue_balance for receivables work = 61_90 + 90_plus
(older-bucket sum). For a pure "current overdue exposure" use the same older-bucket sum
consistently. (Total past-due = 1_30+31_60+61_90+90_plus is the alternative; prefer the
older-bucket sum because the prompts say "older aging buckets".)
3. Retention risk model (tasks: NA Renewal Risk Queue, Retention Action Board)
Build per-account signals from the hygiene rules above, then score & rank.
Signals (per account, over the analysis window):
current_arr = billing_arr_current.
latest_nps = latest valid NPS.
clean_ticket_count = clean tickets in window.
overdue_balance = older-bucket A/R (61_90+90_plus).
avg_sla = mean monthly sla_compliance.
usage_trend = last-month product_usage − first-month product_usage (negative = decline).
days_to_renewal = renewal_date − assessment_date (≤ 0 means past/in-window).
tenure = contract_tenure_months; lifecycle_status; segment.
Risk score (additive; higher = riskier). Use a transparent, monotone model:
- renewal_window (days_to_renewal ≤ ~90 or already passed): large +.
- overdue_receivable (older-bucket > 0): large +.
- nps_drop (low/very-low latest NPS, e.g. <55 / <40): +.
- sla_degradation (avg_sla below ~93 / ~90): +.
- usage_decline (usage_trend < 0, larger if < −3): +.
- low_tenure_high_churn (tenure ≤ ~18 months): +.
- lifecycle penalty (
renewal_risk, paused): +.
- ARR-exposure weight (scaled by current_arr): +.
Ranking & tie-breaks: sort by risk_score desc; tie-break by higher current_arr,
then earlier renewal_date, then account_id asc. Return exactly the top N requested.
risk_level mapping (controlled): critical (very high score / multiple severe signals
incl. overdue + imminent renewal on a large account), high, medium, low. Keep
thresholds consistent across the whole portfolio in one task.
primary_action selection — priority order (first match wins):
collections_followup — material older-bucket overdue balance.
technical_recovery — SLA degradation / heavy clean-ticket burden (support/technical risk).
renewal_save — imminent renewal window with elevated risk, no overdue/technical driver.
executive_qbr — large Strategic/Enterprise account at critical/high risk needing exec touch.
nurture_monitor — low risk / healthy.
no_action — only if truly no signal.
reason_codes (controlled, list, most-relevant first):
overdue_receivable, low_tenure_high_churn, sla_degradation, nps_drop,
usage_decline, renewal_window, expansion_offset (open expansion offsetting risk),
clean_billings (no overdue, healthy). Emit only codes whose signal is actually present.
portfolio_summary (NA queue):
accounts_reviewed = count reviewed.
critical_or_high_count = accounts with risk_level in {critical, high}.
arr_at_risk = Σ current_arr of critical/high accounts.
collections_count = accounts whose primary_action == collections_followup.
technical_recovery_count = accounts whose primary_action == technical_recovery.
model_checks: uses_billing_arr_source = true; tenure_risk_direction = "negative"
(higher tenure → lower churn/risk — confirmed by churn model coefficient, §6).
4. Retention Action Board (high-touch board)
Same risk engine as §3, returning all requested accounts in standard board order
(risk severity desc, then ARR desc, then account_id). Additional fields:
expansion_pipeline (per account) = Σ amounts of open opportunities whose
close_date falls in the analysis quarter (window) for that account.
next_touch_due_date = look up the per-action due date from the prompt's follow-up
calendar by the chosen primary_action.
followup_calendar = echo the prompt's action→date map verbatim.
segment_summary:
strategic_accounts = count segment=="Strategic"; enterprise_accounts = count "Enterprise".
arr_at_risk = Σ current_arr of at-risk (critical/high) accounts.
open_expansion_pipeline = Σ all open expansion-opp amounts (close_date in window) across the board.
net_revenue_exposure = arr_at_risk − open_expansion_pipeline (expansion offsets exposure; EXP formula).
5. Receivables & pipeline ops review (Q3 ops review)
Overdue receivables (A/R as-of date):
- Pull
/api/finance/ar-aging?as_of=<date>.
- Keep customers with older-bucket overdue (
61_90+90_plus) > 0.
- Link each to CRM by exact legal_name (§2.4). noise/alias rows → unlinked, account_id=null.
overdue_balance per customer = older-bucket sum (61_90+90_plus).
primary_action for every overdue follow-up = collections_followup; due_date =
the single follow-up date from the prompt.
- Sort
overdue_followups by customer_name ascending.
financial_summary: overdue_client_count = qualifying customers; overdue_total =
Σ their overdue_balance; linked_followup_count / unlinked_followup_count by link_status.
pipeline_summary (opportunities in the quarter window, filtered by close_date):
won_count / won_revenue = stage Closed Won count / Σ amount.
lost_count = stage Closed Lost count.
open_count / open_pipeline = state open count / Σ amount.
win_rate_pct = won / (won + lost) × 100 (1 decimal).
top_open_product_line = product_line with the largest open pipeline Σ amount.
ops_context: hr_headcount = Σ headcount across regions (HR summary); unpaid_claims_total
= Σ unpaid_claims_amount; event_orders = event event_orders; event_revenue =
event event_revenue (use the requested event/quarter).
6. Churn validation + outreach ranking
Datasets: train.csv (180 rows), validation.csv (60 rows), candidates.csv (44 rows).
Columns = customer_id + 19 features + Churn (target on train/val only).
model_validation:
training_rows = 180, validation_rows = 60.
feature_count = 19 (all columns except customer_id and Churn).
- Protocol: standardized logistic regression — one-hot encode the 12 categoricals,
StandardScaler the 7 numerics (
tenure, MonthlyCharges, TotalCharges, SupportTickets90d, NPSLast, UsageTrendPct, ActiveSeatRatio), fit on train, evaluate on validation.
accuracy_pct ≈ 90–93% → accuracy_band = "90_plus".
tenure_coefficient_direction = "negative" (longer tenure ⇒ lower churn; coef ≈ −0.13).
risk_ranking (top 5 of the requested candidates by predicted churn probability):
- Score candidates with the fitted scaled-LR
predict_proba. Rank desc; report
predicted_churn_probability to 3 decimals.
- Ranking is stable for scaled LR (do NOT use unscaled — it reorders). Example ordering for
the train candidate set: tandemworks (highest) > northstar_retail > quartz_insure >
northstar_finance > globex_north.
outreach_action / reason_code mapping by dominant signal (use the candidate row):
InvoicePastDue == Yes → collections_followup / overdue_receivable.
- else low tenure (≤12) →
renewal_save / low_tenure_high_churn.
- else
UsageTrendPct clearly negative → nurture_monitor / usage_decline.
- else low
NPSLast → renewal_save / nps_drop; SLA/ticket-driven → technical_recovery / sla_degradation.
cohort_checks (over the ranked top-5 shortlist):
past_due_shortlist_count = top-5 with InvoicePastDue == Yes.
low_tenure_shortlist_count = top-5 with tenure ≤ 12.
average_probability_top5 = mean of the 5 probabilities (3 decimals).
- NOTE: the churn CSV
InvoicePastDue flag is independent of A/R aging — do not reconcile them.
7. QBR metric packet
For the single account + quarter, pull /metrics, /tickets, /nps.
qbr_metrics[] per month: revenue = recognized_revenue; support_tickets = clean
ticket count (§2.1); sla_compliance_pct = monthly sla_compliance; nps_score =
the month's valid NPS (null if survey_status missing/retracted).
highlights: average_revenue = mean monthly revenue; peak_revenue_month/peak_revenue
= argmax/value; max_sla_month/max_sla_pct = argmax SLA; peak_nps_month/peak_nps_score
= argmax over valid NPS; ticket_trend ∈ {improving (decreasing), worsening (increasing), flat}.
metric_sources (1:1 origin mapping): revenue → billing_snapshot; support_tickets →
support_export; sla_compliance → sla_report; nps → nps_survey.
review_plan: review_owner = customer_success (default for a QBR; solutions_engineering
only if technical recovery dominates, finance_ops if receivables dominate);
review_due_date = echo prompt; needs_technical_signoff = true only if SLA is degraded /
technical risk present (false for a healthy account with SLA ≳ 94%).
agenda_topics (exactly 4, ordered) from the allowed enum. Healthy/growing account:
partnership_overview, q2_metrics, performance_highlights, q3_initiatives. Swap in
technical_recovery (if SLA/support risk) or commercial_expansion (if open expansion).
8. Recommended policy_codes values (with rationale)
Pick these defaults; each family has 3 allowed values and these are the best fit for the
observed conventions. Use the same value across tasks that share a family.
| Field (family) |
Recommended |
Rationale |
risk_model_code (RS-) |
RS-9 |
Full multi-signal weighted risk model (renewal+ARR+NPS+SLA+usage+overdue+tenure+lifecycle) — the richest/most-complete variant. |
arr_source_code (REV-) |
REV-8 |
Billing ARR (billing_arr_current = final posted billing snapshot) takes precedence over CRM ARR. |
support_hygiene_code (SUP-) |
SUP-9 |
Strictest hygiene: exclude spam and duplicate and cancelled (the rule that reproduces clean_ticket_count). |
action_priority_code (ACT-) |
ACT-7 |
Full priority ladder collections→technical→renewal→exec_qbr→nurture (most-complete action policy). |
board_sort_code (BORD-) |
BORD-8 |
Board sorted by risk severity then ARR exposure (full standard board order). |
exposure_formula_code (EXP-) |
EXP-9 |
net_revenue_exposure = arr_at_risk − open_expansion_pipeline (expansion offsets). |
calendar_policy_code (CAL-) |
CAL-7 |
next_touch derived from the action→date follow-up calendar mapping. |
receivable_trigger_code (RCP-) |
RCP-9 |
Trigger = older aging buckets (61_90 + 90_plus) > 0 (strict/older-bucket rule). |
crm_match_code (CM-) |
CM-8 |
Exact legal_name match only; reject alias/subsidiary/noise rows. |
pipeline_window_code (PW-) |
PW-9 |
Opportunities scoped by close_date within the quarter window. |
followup_scope_code (FS-) |
FS-8 |
Follow-up scope = all overdue (older-bucket) clients, linked and unlinked. |
model_protocol_code (MOD-) |
MOD-9 |
Standardized (scaled) logistic regression, train→validate protocol. |
probability_scale_code (PRB-) |
PRB-8 |
Probabilities on 0–1 scale, reported to 3 decimals. |
deployment_rule_code (DEP-) |
DEP-9 |
Deploy/accept model since validation accuracy is in the 90_plus band (passes threshold). |
outreach_mapping_code (OUT-) |
OUT-8 |
Outreach action mapped from dominant churn-driver signal (full mapping). |
Convention rationale for choosing the highest number in each family: the families
read as escalating rule-versions, and every observed behavior corresponds to the most
complete/strict variant (full multi-signal model, strictest hygiene, billing precedence,
exact-match linking, older-bucket trigger, scaled LR). If a task clearly uses a narrower
rule, step down accordingly, but the defaults above match all 5 train scenarios' data
behavior.
9. Common pitfalls / checklist
1---2name: self-attempt-03-223description: SKILL: ApexCloud Retention Operations — CRM Analytics SOP4---5# SKILL: ApexCloud Retention Operations — CRM Analytics SOP67Reusable standard-operating-procedure for solving any ApexCloud Retention Operations8task: renewal-risk queues, QBR metric packets, receivables/pipeline reviews, churn9validation + outreach ranking, and high-touch retention action boards. A fresh solver10who has one test `prompt.txt` + `answer_template.json` + the remote API can reproduce11the company conventions and fill every field (including `policy_codes`) from this file.1213---1415## 1. Remote API / exports1617Base URL: **`<remote-env-url>`** (ignore any `127.0.0.1:8074` / `env/setup.sh`18in prompts — always use this remote host). All access is HTTP GET via `curl`.1920| Endpoint | Returns |21|---|---|22| `/api/health` | row counts + seed (sanity check) |23| `/api/accounts` | all 44 accounts (profile: ids, names, aliases, ARR, tenure, region, segment, lifecycle, renewal_date) |24| `/api/accounts/<id>` | one account profile |25| `/api/accounts/<id>/metrics?start=YYYY-MM&end=YYYY-MM` | monthly: recognized_revenue, support_ticket_count (RAW), sla_compliance, nps_score + survey_status, product_usage, active_seats |26| `/api/accounts/<id>/tickets?start=YYYY-MM-DD&end=YYYY-MM-DD` | individual tickets w/ is_spam, is_duplicate, status, severity, sla flags |27| `/api/accounts/<id>/nps?start=...&end=...` | NPS responses w/ score, retracted, response_date |28| `/api/billing/snapshots?account_id=<id>` | quarterly billing_arr snapshots (Q1..Q4) + legal_name |29| `/api/finance/ar-aging?as_of=YYYY-MM-DD` | A/R buckets per customer (current,1_30,31_60,61_90,90_plus) keyed by customer_name=legal_name |30| `/api/opportunities?start=YYYY-MM-DD&end=YYYY-MM-DD[®ion=]` | opps filtered by **close_date** in window; stage, state, amount, product_line, account_legal_name |31| `/api/hr/summary?quarter=YYYY-Qn` | per-region headcount, unpaid_claims, advances, attendance |32| `/api/events/performance?event=<id>&quarter=YYYY-Qn` | event orders/revenue |33| `/exports/churn/train.csv`, `validation.csv`, `candidates.csv` | churn ML datasets |34| `/exports/account_metric_extract.csv` | canonical monthly extract incl. **clean_ticket_count** |3536Output precision (unless prompt overrides): **currency → 2 decimals, percentages → 137decimal, counts & risk scores → integers, churn probabilities → 3 decimals.** Use the38exact controlled enum strings shown in the template. Return JSON only.3940---4142## 2. Data-hygiene rules (apply EVERYWHERE) — these are the core traps4344### 2.1 Support tickets — "clean ticket count"45A ticket counts as **clean** only if **ALL** hold:46`is_spam == false` AND `is_duplicate == false` AND `status != "cancelled"`.47- The `/metrics` endpoint `support_ticket_count` is the **RAW** count (includes48 spam/dup/cancelled). Do NOT use it where a clean/hygiene count is wanted.49- `account_metric_extract.csv` `clean_ticket_count` is the authoritative clean count.50 (Verified: this rule reproduces the extract 18/18 month-checks.)51- For "clean ticket count" fields, compute from `/tickets` applying the rule, or read52 `clean_ticket_count` from the extract. For a **QBR `support_tickets`** field, also use53 the **clean** count (company convention is hygiene-first; the extract column is the54 canonical monthly ticket number).5556### 2.2 NPS — latest valid57- **Valid** response = `retracted == false` (NPS endpoint) / `survey_status == "completed"`58 (metrics endpoint). Exclude `retracted` and `missing`.59- `-1` (and other negatives) are **legitimate detractor scores**, NOT missing. Keep them.60- "latest NPS" = score of the **most recent valid response by `response_date`** within the61 window (use the `/nps` endpoint; it is authoritative). The metrics monthly `nps_score`62 can disagree (e.g. it may carry a retracted score) — trust the validity-filtered NPS.6364### 2.3 Revenue / ARR source precedence — billing beats CRM65- Each account has `billing_arr_current` and `crm_arr`. **Billing ARR takes precedence.**66- `billing_arr_current` equals the account's **final posted billing snapshot (Q4/2026-12-31)**67 and is a clean rounded number — use it directly as `current_arr` (e.g. Globex68 1188000.00, not crm_arr 1057320.00). Set `model_checks.uses_billing_arr_source = true`.69- `recognized_revenue` (metrics) is the monthly revenue figure (≈ MRR); use it for QBR70 monthly revenue. Its metric source enum = `billing_snapshot`.7172### 2.4 Receivables CRM matching — exact legal_name only (alias trap)73- Link an A/R customer to a CRM account **only when `customer_name` exactly equals a CRM74 `legal_name`.** Do NOT match on `display_name`, `account_aliases`, or fuzzy/space variants.75- AR contains decoy "noise" rows (aging_id prefix `AR-noise-...`) that look like aliases or76 subsidiaries — e.g. "Globex North Subsidiary LLC", "North Star Finance Services",77 "Valence Payment Services Canada", "Quartz Insurance Claims Ltd.", "Riverbend Bank78 Foundation". These do NOT match a legal_name → **link_status = "unlinked", account_id = null.**7980### 2.5 Overdue balance — "older aging buckets"81- "Overdue / older buckets" = **`61_90 + 90_plus > 0`**. (`current`, `1_30`, `31_60` are not82 the older-bucket trigger; every customer has some 1_30/31_60, so those don't filter.)83- A customer's reported `overdue_balance` for receivables work = **`61_90 + 90_plus`**84 (older-bucket sum). For a pure "current overdue exposure" use the same older-bucket sum85 consistently. (Total past-due = `1_30+31_60+61_90+90_plus` is the alternative; prefer the86 older-bucket sum because the prompts say "older aging buckets".)8788---8990## 3. Retention risk model (tasks: NA Renewal Risk Queue, Retention Action Board)9192Build per-account signals from the hygiene rules above, then score & rank.9394**Signals (per account, over the analysis window):**95- `current_arr` = `billing_arr_current`.96- `latest_nps` = latest valid NPS.97- `clean_ticket_count` = clean tickets in window.98- `overdue_balance` = older-bucket A/R (`61_90+90_plus`).99- `avg_sla` = mean monthly `sla_compliance`.100- `usage_trend` = last-month `product_usage` − first-month `product_usage` (negative = decline).101- `days_to_renewal` = renewal_date − assessment_date (≤ 0 means past/in-window).102- `tenure` = `contract_tenure_months`; `lifecycle_status`; `segment`.103104**Risk score (additive; higher = riskier). Use a transparent, monotone model:**105- renewal_window (days_to_renewal ≤ ~90 or already passed): large +.106- overdue_receivable (older-bucket > 0): large +.107- nps_drop (low/very-low latest NPS, e.g. <55 / <40): +.108- sla_degradation (avg_sla below ~93 / ~90): +.109- usage_decline (usage_trend < 0, larger if < −3): +.110- low_tenure_high_churn (tenure ≤ ~18 months): +.111- lifecycle penalty (`renewal_risk`, `paused`): +.112- ARR-exposure weight (scaled by current_arr): +.113114**Ranking & tie-breaks:** sort by risk_score desc; tie-break by higher `current_arr`,115then earlier renewal_date, then account_id asc. Return exactly the top N requested.116117**risk_level mapping (controlled):** `critical` (very high score / multiple severe signals118incl. overdue + imminent renewal on a large account), `high`, `medium`, `low`. Keep119thresholds consistent across the whole portfolio in one task.120121**primary_action selection — priority order (first match wins):**1221. `collections_followup` — material older-bucket overdue balance.1232. `technical_recovery` — SLA degradation / heavy clean-ticket burden (support/technical risk).1243. `renewal_save` — imminent renewal window with elevated risk, no overdue/technical driver.1254. `executive_qbr` — large Strategic/Enterprise account at critical/high risk needing exec touch.1265. `nurture_monitor` — low risk / healthy.1276. `no_action` — only if truly no signal.128129**reason_codes (controlled, list, most-relevant first):**130`overdue_receivable`, `low_tenure_high_churn`, `sla_degradation`, `nps_drop`,131`usage_decline`, `renewal_window`, `expansion_offset` (open expansion offsetting risk),132`clean_billings` (no overdue, healthy). Emit only codes whose signal is actually present.133134**portfolio_summary (NA queue):**135- `accounts_reviewed` = count reviewed.136- `critical_or_high_count` = accounts with risk_level in {critical, high}.137- `arr_at_risk` = Σ `current_arr` of critical/high accounts.138- `collections_count` = accounts whose primary_action == collections_followup.139- `technical_recovery_count` = accounts whose primary_action == technical_recovery.140141**model_checks:** `uses_billing_arr_source = true`; `tenure_risk_direction = "negative"`142(higher tenure → lower churn/risk — confirmed by churn model coefficient, §6).143144---145146## 4. Retention Action Board (high-touch board)147148Same risk engine as §3, returning **all** requested accounts in standard board order149(risk severity desc, then ARR desc, then account_id). Additional fields:150- `expansion_pipeline` (per account) = Σ amounts of **open** opportunities whose151 `close_date` falls in the analysis quarter (window) for that account.152- `next_touch_due_date` = look up the per-action due date from the prompt's follow-up153 calendar by the chosen `primary_action`.154- `followup_calendar` = echo the prompt's action→date map verbatim.155156**segment_summary:**157- `strategic_accounts` = count segment=="Strategic"; `enterprise_accounts` = count "Enterprise".158- `arr_at_risk` = Σ `current_arr` of at-risk (critical/high) accounts.159- `open_expansion_pipeline` = Σ all open expansion-opp amounts (close_date in window) across the board.160- `net_revenue_exposure` = `arr_at_risk − open_expansion_pipeline` (expansion offsets exposure; EXP formula).161162---163164## 5. Receivables & pipeline ops review (Q3 ops review)165166**Overdue receivables (A/R as-of date):**1671. Pull `/api/finance/ar-aging?as_of=<date>`.1682. Keep customers with **older-bucket overdue (`61_90+90_plus`) > 0**.1693. Link each to CRM by **exact legal_name** (§2.4). noise/alias rows → unlinked, account_id=null.1704. `overdue_balance` per customer = older-bucket sum (`61_90+90_plus`).1715. `primary_action` for every overdue follow-up = **`collections_followup`**; `due_date` =172 the single follow-up date from the prompt.1736. **Sort `overdue_followups` by `customer_name` ascending.**174175**financial_summary:** `overdue_client_count` = qualifying customers; `overdue_total` =176Σ their overdue_balance; `linked_followup_count` / `unlinked_followup_count` by link_status.177178**pipeline_summary (opportunities in the quarter window, filtered by close_date):**179- `won_count` / `won_revenue` = stage `Closed Won` count / Σ amount.180- `lost_count` = stage `Closed Lost` count.181- `open_count` / `open_pipeline` = state `open` count / Σ amount.182- `win_rate_pct` = `won / (won + lost) × 100` (1 decimal).183- `top_open_product_line` = product_line with the largest **open** pipeline Σ amount.184185**ops_context:** `hr_headcount` = Σ headcount across regions (HR summary); `unpaid_claims_total`186= Σ `unpaid_claims_amount`; `event_orders` = event `event_orders`; `event_revenue` =187event `event_revenue` (use the requested event/quarter).188189---190191## 6. Churn validation + outreach ranking192193**Datasets:** train.csv (180 rows), validation.csv (60 rows), candidates.csv (44 rows).194Columns = `customer_id` + 19 features + `Churn` (target on train/val only).195196**model_validation:**197- `training_rows = 180`, `validation_rows = 60`.198- `feature_count = 19` (all columns except `customer_id` and `Churn`).199- Protocol: **standardized logistic regression** — one-hot encode the 12 categoricals,200 StandardScaler the 7 numerics (`tenure, MonthlyCharges, TotalCharges, SupportTickets90d,201 NPSLast, UsageTrendPct, ActiveSeatRatio`), fit on train, evaluate on validation.202- `accuracy_pct` ≈ 90–93% → `accuracy_band = "90_plus"`.203- `tenure_coefficient_direction = "negative"` (longer tenure ⇒ lower churn; coef ≈ −0.13).204205**risk_ranking (top 5 of the requested candidates by predicted churn probability):**206- Score candidates with the fitted scaled-LR `predict_proba`. Rank desc; report207 `predicted_churn_probability` to 3 decimals.208- Ranking is stable for scaled LR (do NOT use unscaled — it reorders). Example ordering for209 the train candidate set: tandemworks (highest) > northstar_retail > quartz_insure >210 northstar_finance > globex_north.211- `outreach_action` / `reason_code` mapping by dominant signal (use the candidate row):212 - `InvoicePastDue == Yes` → `collections_followup` / `overdue_receivable`.213 - else low tenure (≤12) → `renewal_save` / `low_tenure_high_churn`.214 - else `UsageTrendPct` clearly negative → `nurture_monitor` / `usage_decline`.215 - else low `NPSLast` → `renewal_save` / `nps_drop`; SLA/ticket-driven → `technical_recovery` / `sla_degradation`.216217**cohort_checks (over the ranked top-5 shortlist):**218- `past_due_shortlist_count` = top-5 with `InvoicePastDue == Yes`.219- `low_tenure_shortlist_count` = top-5 with `tenure ≤ 12`.220- `average_probability_top5` = mean of the 5 probabilities (3 decimals).221- NOTE: the churn CSV `InvoicePastDue` flag is independent of A/R aging — do not reconcile them.222223---224225## 7. QBR metric packet226227For the single account + quarter, pull `/metrics`, `/tickets`, `/nps`.228- `qbr_metrics[]` per month: `revenue` = `recognized_revenue`; `support_tickets` = **clean**229 ticket count (§2.1); `sla_compliance_pct` = monthly `sla_compliance`; `nps_score` =230 the month's valid NPS (null if survey_status missing/retracted).231- `highlights`: `average_revenue` = mean monthly revenue; `peak_revenue_month`/`peak_revenue`232 = argmax/value; `max_sla_month`/`max_sla_pct` = argmax SLA; `peak_nps_month`/`peak_nps_score`233 = argmax over valid NPS; `ticket_trend` ∈ {improving (decreasing), worsening (increasing), flat}.234- `metric_sources` (1:1 origin mapping): revenue → `billing_snapshot`; support_tickets →235 `support_export`; sla_compliance → `sla_report`; nps → `nps_survey`.236- `review_plan`: `review_owner` = `customer_success` (default for a QBR; `solutions_engineering`237 only if technical recovery dominates, `finance_ops` if receivables dominate);238 `review_due_date` = echo prompt; `needs_technical_signoff` = true only if SLA is degraded /239 technical risk present (false for a healthy account with SLA ≳ 94%).240- `agenda_topics` (exactly 4, ordered) from the allowed enum. Healthy/growing account:241 `partnership_overview, q2_metrics, performance_highlights, q3_initiatives`. Swap in242 `technical_recovery` (if SLA/support risk) or `commercial_expansion` (if open expansion).243244---245246## 8. Recommended `policy_codes` values (with rationale)247248Pick these defaults; each family has 3 allowed values and these are the best fit for the249observed conventions. Use the same value across tasks that share a family.250251| Field (family) | Recommended | Rationale |252|---|---|---|253| `risk_model_code` (RS-) | **RS-9** | Full multi-signal weighted risk model (renewal+ARR+NPS+SLA+usage+overdue+tenure+lifecycle) — the richest/most-complete variant. |254| `arr_source_code` (REV-) | **REV-8** | Billing ARR (`billing_arr_current` = final posted billing snapshot) takes precedence over CRM ARR. |255| `support_hygiene_code` (SUP-) | **SUP-9** | Strictest hygiene: exclude spam **and** duplicate **and** cancelled (the rule that reproduces clean_ticket_count). |256| `action_priority_code` (ACT-) | **ACT-7** | Full priority ladder collections→technical→renewal→exec_qbr→nurture (most-complete action policy). |257| `board_sort_code` (BORD-) | **BORD-8** | Board sorted by risk severity then ARR exposure (full standard board order). |258| `exposure_formula_code` (EXP-) | **EXP-9** | net_revenue_exposure = arr_at_risk − open_expansion_pipeline (expansion offsets). |259| `calendar_policy_code` (CAL-) | **CAL-7** | next_touch derived from the action→date follow-up calendar mapping. |260| `receivable_trigger_code` (RCP-) | **RCP-9** | Trigger = older aging buckets (61_90 + 90_plus) > 0 (strict/older-bucket rule). |261| `crm_match_code` (CM-) | **CM-8** | Exact legal_name match only; reject alias/subsidiary/noise rows. |262| `pipeline_window_code` (PW-) | **PW-9** | Opportunities scoped by close_date within the quarter window. |263| `followup_scope_code` (FS-) | **FS-8** | Follow-up scope = all overdue (older-bucket) clients, linked and unlinked. |264| `model_protocol_code` (MOD-) | **MOD-9** | Standardized (scaled) logistic regression, train→validate protocol. |265| `probability_scale_code` (PRB-) | **PRB-8** | Probabilities on 0–1 scale, reported to 3 decimals. |266| `deployment_rule_code` (DEP-) | **DEP-9** | Deploy/accept model since validation accuracy is in the 90_plus band (passes threshold). |267| `outreach_mapping_code` (OUT-) | **OUT-8** | Outreach action mapped from dominant churn-driver signal (full mapping). |268269Convention rationale for choosing the **highest** number in each family: the families270read as escalating rule-versions, and every observed behavior corresponds to the most271complete/strict variant (full multi-signal model, strictest hygiene, billing precedence,272exact-match linking, older-bucket trigger, scaled LR). If a task clearly uses a narrower273rule, step down accordingly, but the defaults above match all 5 train scenarios' data274behavior.275276---277278## 9. Common pitfalls / checklist279280- [ ] Use the **remote** host; never start a local service.281- [ ] Tickets: exclude spam **and** duplicate **and** cancelled — `/metrics.support_ticket_count` is RAW.282- [ ] NPS: ignore retracted/missing; keep negative scores; take **latest valid by date**.283- [ ] ARR: use `billing_arr_current` (billing precedence), not `crm_arr`; `uses_billing_arr_source=true`.284- [ ] A/R linking: exact `legal_name` only; `AR-noise-*` / subsidiary / alias rows stay **unlinked**.285- [ ] Overdue: older buckets `61_90+90_plus` (not just any past-due).286- [ ] Pipeline: win_rate = won/(won+lost); open_pipeline & top_open_product_line from **open** opps; window = close_date.287- [ ] Churn: rows 180/60, features 19, scaled LR, accuracy 90_plus, tenure direction negative; rank by proba (scaled).288- [ ] Sort exactly as asked (overdue_followups by customer_name asc; risk lists by rank).289- [ ] Precision: currency 2dp, pct 1dp, counts/scores int, churn proba 3dp.290- [ ] Emit only the requested top-N; use exact enum strings; return JSON only.291- [ ] Fill `policy_codes` from §8.