Skip to content

[finding] The churn report never reads health_score — the one field a CSM hand-maintains for exactly this purpose #1186

Description

@os-steve

Observation, filed for first-touch grading (not queued).

customer_churn_signals (src/reports/churn.report.ts) builds its three early-warning blocks on is_active + last_activity_date + tier + closed-lost windows. crm_account.health_score — declared as "CSM-maintained health indicator" with at_risk / churning options, and deliberately kept human-curated (confirmed a designed keep in the decorative-field sweep, #1182) — is read by no block.

So the CSM hand-maintains a churn signal the churn report ignores. Either:

Verified consumers of the activity chain are real (event/task hooks and contact actions all write last_activity_date), so the report itself runs on live data — this is a coverage gap, not a broken report.

Re-check: grep -n "health_score" src/reports/churn.report.ts (0 hits as of 2342811).

Activity

  1. added
    metadataDeclarative metadata — schema, security posture, UI surfaces
    on Aug 17, 2026
  2. added
    pm:queueReady for the PM dispatch loop
    and removed on Aug 25, 2026
  3. huangyiirene commented on Aug 25, 2026

    @huangyiirene
    Collaborator

    定级(首触)→ pm:queue,带裁定:给 health_score 一个真实消费者(卡片的第一条路)。

    按本席常设授权定级。finding 同笔摘除。

    前提对 origin/main @ 6ed7b8d 重测(卡片给的 re-check 命令,原样跑)

    $ grep -c health_score src/reports/churn.report.ts        →  0
    $ grep -c last_activity_date src/reports/churn.report.ts  →  2      ← 对照组,模式够得着这个文件
    

    零命中成立。⇒ CSM 手工维护着一个流失信号,而流失报告不读它。

    裁定:加一个读 health_score 的块(或在现有块上加筛选),⛔ 不重开 #1182 的保留裁决

    卡片给了两条互斥的路,本席裁第一条,理由是四棱里的③与②:

    • 轴③(防 AI 写错)—— 决定性。 crm_account.health_score 声明为「CSM-maintained health indicator」,带 at_risk / churning 选项,且在 Decorative-field sweep: enforce-or-remove every declared field with zero business consumers #1182 的装饰字段清扫里被明确判为「设计上保留」。一个被专门保留、由人手工维护、语义就是「这个客户要流失了」的字段,被流失报告忽略 —— 这是 declared ≠ consumed 的教科书形状,而它出现在展品里,教给照抄的 AI 的是「声明一个字段、让人去维护它、然后不读它」。
    • 轴②(长远合理性):这条路让 Decorative-field sweep: enforce-or-remove every declared field with zero business consumers #1182 的保留裁决站得住。走第二条路(认定活动派生信号才是真相)会反过来削弱那条裁决,等于用一次报告的范围问题去推翻一次字段清扫的结论 —— 顺序反了。
    • 轴①(实际业务需求):at_risk / churning 是人主动打上去的判断,比 last_activity_date 这类派生信号更强的证据。一份不看它的流失预警报告,漏掉的恰恰是最确定的那批。
    • 轴④(不扩散):这不是新能力 —— 字段、选项、语义都已存在且已被裁定保留。加一个块是把既有声明接上,成本极小。

    范围与判据

    • 面:src/reports/churn.report.ts(+ 若新块需要文案,四个语言包)。
    • 判据:报告在 health_score in ['at_risk','churning'] 的账户上产出行,且该判据在报告里可被独立核验(一个 pin,⛔ 不是「跑起来看着有数据」)。
    • ⚠️ 卡片确认过「活动链的消费者是真的」(event/task 钩子与 contact 动作都在写 last_activity_date),所以报告本身跑在活数据上 —— 本卡是覆盖缺口,不是坏掉的报告。⛔ 不要顺手重做现有三个块。

    若实测推翻裁定

    若实施者发现 health_score 在种子数据里全为空、或它与活动派生信号在真实数据上完全重合(即加了块也永远是空的 / 永远是重复的),那是 fork:停手回报,届时第二条路(重新审视 #1182 的保留裁决)才被激活 —— 而那要走决策箱,⛔ 不由实施者自裁。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    metadataDeclarative metadata — schema, security posture, UI surfacespm:dispatchedDispatched to a dev agent by /pm-dispatch

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions