This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-MVP/activities/crm-relationship-coverage/src/case-model.ts
T
xcsandClaude Opus 5 0aa2fc11b0 资料文件真产出来了;E04/I01 补到验收线,顺手填了几个「墙拦住却不给路」的坑
**资料文件全链**(用户:审核资料这一步要真产出 word/excel/md/html,前端能渲、点资料名能开)
接手时这一步一个文件都产不出来 —— S3 剧本白纸黑字写着「file_ref 本 MVP 阶段可留空」。
文件是台账那一行的投影,不另存一份:
· 声明 `file:{titleColumn,bodyColumn,formatColumn}`,它同时是授权面(没声明就点不开)
· 现造真字节 —— docx/xlsx 是自己打的 OOXML 包(zip-write:store+CRC32,零依赖)
· 读回来渲 —— 解的就是造出来那份字节,渲得出来才证明文件是真的
· 件上 `data-file-of` → 工作区二级界面(Word 纸面/Excel 网格/渲好的 md/沙箱 html)+ 下载原件
放行落地前把每一份冻进 `sys.task_asset_file`(迁移 0029):归档件点开的必须是签字那一刻
那一份,而资料落在主数据上、试跑单不落正式表 —— 不存这一份,收工后一个都点不开。
工作成果里「产出的资料」自成一摞。Markdown 富文本渲染顺带销账(仍零依赖)。

**Harness**
· 画面按「这一拍办完之后」在办的那一步挑 —— 从前拿拍首的局面决定拍尾的事:
  收掉 S4、发出 S5 的卡,而屏上还摆着 S4 的只读件,人无处可填(墙要配路第四次现形)
· 验收「重做」真能退回重做,且打回后台账没动就不许再提交(软硬两段,不锁死单子)
· 立监听的授权面是清单声明;发卡的授权面是调人预算,不是计划项
· 读工具同参重调有兜底;空转写身后还有行动就不废串;finish 的 doneWhen 按写完后的草稿算

**界面**
· 翻件与翻页各归各位(从前二选一,多卡多页时页翻不动);没得翻写「—」不写 0/0
· 工作成果详情能翻份;表宽超出能横滑(E04/I01 抄版式时把 overflow-x 抄丢了)
· 查看面灰底滚过一屏变白 —— 两条独立的路都治了(沙箱内不同色 / 弹性子项不长个)
· 测试台「取不到」不再当成「还活着」

**活动**
E04 关系覆盖、I01 品牌内容各自补到验收线:判据全读基座数、判据指向的东西摆上卡面、
样例满载且名字 ≤14 字;提交前要裁的判据搬进说明书(模型会压拍,压过之后只有说明书还在)。

验收:e2e i01 4/4、e04 3/3 全部 released,资料在办中渲得出、放行后存了档点得开;
门禁 typecheck/lint/depcruise/1628 测全绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEJX8Z7CHejvwmDQyPQuBX
2026-08-24 10:26:45 +08:00

718 lines
52 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* E04 关系覆盖与关键人经营的案例模型 —— **双表示同版本**:CMMN XML(机器面)与
* 案例说明书(叙事面)。
*
* 业务出处:`docs/SS01-客户与市场/e04-html/E04-活动流程与工单说明.md`(7 步业务链条,
* 收成 2 张执行工单 + 2 张验收工单)与 `docs/SS01-客户与市场/Demo-E04-I01-Activity台账
* 关联与设计.html`(L-02/L-08 台账读写关系)。
*
* 7 个业务步收成 8 个案例模型项,非逐一对应:
* ① 启动与覆盖范围确认 → S1_identify_candidates(AI 起草)+ S2_scope_confirm(人核对);
* ②③④ 读现状→缺口分析→制定计划 → S3_plan(一项,AI)+ S4_plan_approval(决策卡循环);
* ⑤⑥ 执行经营动作→更新关系台账 → S5_execute_receipt(人执行填回执)+
* S6_writeback(AI 写回主数据)+ S6_ledger_update(AI 留更新明细与结论 ——
* 与写回拆成两项是 2.0.4 的结构治:一串挤四张表,串尾最费字数的明细表
* 被截成空数组挨拒,实测两轮都栽在同一处;拆开后每项的剧本只摊自己那几张表);
* ⑦ 复盘与完结 → T_release(终局验收放行)。
*
* 这是本仓库第一个用 `decide` 实现「非终局通过/驳回+AI重做」循环的活动
* (S4_plan_approval)——`issueAcceptance` 硬绑定终局放行,一个任务只能发一次,
* 不能拿来做②③④这个中间验收点;`decide` 才是唯一能循环的机制,细则见 S4 的 guide。
*/
export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL">
<case id="crm-coverage">
<!--
案卷项的 id 就是任务视图里的键名。入参顶层是 trigger/eventType/customers/note 四个
字段(不进这份 caseFileModel——没有谓词引用它们,S1 的步骤 reads 里单独声明);
crm_coverage_* 与 customer_profile_* / customer_contact_* 那几个是基座读数
(行数 / 缺必填,由基座现算,模型碰不着)。
-->
<caseFileModel>
<caseFileItem id="crm_coverage_candidate_rows" name="候选客户行数(基座现数)"/>
<caseFileItem id="crm_coverage_candidate_missing" name="候选清单缺必填的行数(基座现数)"/>
<caseFileItem id="scope_confirmed_total" name="覆盖范围已核对确认(人环走完的凭据)"/>
<caseFileItem id="crm_coverage_plan_rows" name="经营计划行数(基座现数)"/>
<caseFileItem id="crm_coverage_plan_missing" name="经营计划缺必填的行数(基座现数)"/>
<caseFileItem id="plan_approved_total" name="经营计划已审批通过(仅通过时置一)"/>
<caseFileItem id="crm_coverage_receipt_rows" name="执行回执行数(基座现数)"/>
<caseFileItem id="crm_coverage_receipt_missing" name="执行回执缺必填的行数(基座现数)"/>
<caseFileItem id="crm_coverage_ledger_update_rows" name="台账更新明细行数(基座现数)"/>
<caseFileItem id="crm_coverage_ledger_update_missing" name="台账更新明细缺必填的行数(基座现数)"/>
<caseFileItem id="customer_profile_rows" name="本单写下的客户档案行数(基座现数)"/>
<caseFileItem id="customer_profile_missing" name="客户档案缺必填的行数(基座现数)"/>
<caseFileItem id="customer_contact_rows" name="本单写下的客户联系人行数(基座现数)"/>
<caseFileItem id="customer_contact_missing" name="客户联系人缺必填的行数(基座现数)"/>
<caseFileItem id="ledger_update_confirmed_total" name="台账更新已收口(S6 写完时置一)"/>
</caseFileModel>
<casePlanModel id="plan" name="关系覆盖与关键人经营" autoComplete="true">
<!-- ① 候选客户已起草 —— 人核对那一步的入场前提 -->
<sentry id="s_candidates_drafted">
<onPart id="op_cd" caseFileItemRef="crm_coverage_candidate_rows"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_cd"><condition>crm_coverage_candidate_rows &gt; 0</condition></ifPart>
</sentry>
<!-- ② 覆盖范围已核对 —— 制定经营计划那一步的入场前提 -->
<sentry id="s_scope_confirmed">
<onPart id="op_sc" caseFileItemRef="scope_confirmed_total"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_sc"><condition>scope_confirmed_total &gt; 0</condition></ifPart>
</sentry>
<!-- ③ 经营计划已起草且齐 —— 审批那一步的入场前提 -->
<sentry id="s_plan_drafted">
<onPart id="op_pd" caseFileItemRef="crm_coverage_plan_rows"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_pd"><condition>crm_coverage_plan_rows &gt; 0 and crm_coverage_plan_missing = 0</condition></ifPart>
</sentry>
<!-- ④ 经营计划已审批通过 —— 交人执行那一步的入场前提 -->
<sentry id="s_plan_approved">
<onPart id="op_pa" caseFileItemRef="plan_approved_total"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_pa"><condition>plan_approved_total &gt; 0</condition></ifPart>
</sentry>
<!--
⑤ 执行回执已交回且齐 —— 收口写台账那一步的入场前提。
**不再有 receipt_confirmed_total 记账旗**(2.0.2 退役,活单熔断两轮实测逼的):
那面旗是模型写的,S4 通过后它顺手把旗写上就坚信 S5 已办完,连续五拍
[空转写旗 + finish 被 worldTouched 拒] 熔断 —— 「该不该往下走」不能由
被叫的那一方自己记账。回执四列全是**人在件上填出来的**,_rows/_missing
是基座数出来的 —— 人填齐那一刻门自己开,模型在 S5 只剩发卡与收步两件事,
没有可抢跑的旗。
-->
<sentry id="s_receipt_confirmed">
<onPart id="op_rc" caseFileItemRef="crm_coverage_receipt_missing"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_rc"><condition>crm_coverage_receipt_rows &gt; 0 and crm_coverage_receipt_missing = 0</condition></ifPart>
</sentry>
<!--
⑥(中缝)主数据已写回 —— 留更新明细那一步的入场前提。
收口从一项拆成两项(2.0.4,治「一串挤四张表、串尾被截成空数组」的实测账):
写回主数据(S6_writeback)与留明细(S6_ledger_update)各一项,剧本各只摊自己
要写的表,一串自然变小。四个数全是基座现数:重算分/档案都写齐了,门才开。
-->
<sentry id="s_masters_written">
<onPart id="op_mw" caseFileItemRef="customer_profile_rows"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_mw"><condition>customer_profile_rows &gt; 0 and customer_profile_missing = 0 and customer_contact_rows &gt; 0 and customer_contact_missing = 0</condition></ifPart>
</sentry>
<!--
⑥ 台账已收口 —— 放行门的入场前提。
认「办完了没有」,且四处都是基座现数:台账更新明细齐(有行、不缺必填)、
主数据(客户档案/联系人)写下的那几行不缺必填、且 S6 自己显式记了账
(ledger_update_confirmed_total 是写完之后才产生的数,不是「写过一笔就算数」)。
-->
<sentry id="s_ledger_updated">
<onPart id="op_lu" caseFileItemRef="ledger_update_confirmed_total"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_lu"><condition>ledger_update_confirmed_total &gt; 0 and crm_coverage_ledger_update_rows &gt; 0 and crm_coverage_ledger_update_missing = 0 and customer_profile_missing = 0 and customer_contact_missing = 0</condition></ifPart>
</sentry>
<!-- ① 识别候选客户,铸造 round_ref(能算的不问人) -->
<humanTask id="S1_identify_candidates" name="识别候选覆盖客户"/>
<!-- ① 交人核对候选清单(增删改)—— 触达世界前的人环,不给 doneWhen -->
<humanTask id="S2_scope_confirm" name="覆盖范围确认:核对候选清单,增删改">
<entryCriterion id="ec_s2" sentryRef="s_candidates_drafted"/>
<itemControl><manualActivationRule id="m_s2"/></itemControl>
</humanTask>
<!-- ②③④ 读现状 → 缺口分析 → 制定经营计划(一项,不触达世界) -->
<humanTask id="S3_plan" name="读现状、缺口分析、制定关系经营计划">
<entryCriterion id="ec_s3" sentryRef="s_scope_confirmed"/>
</humanTask>
<!-- ②③④ 经营计划审批 —— 决策卡循环,通过或驳回+AI重做,不给 doneWhen -->
<humanTask id="S4_plan_approval" name="经营计划审批:通过或驳回(驳回则AI修订重提交)">
<entryCriterion id="ec_s4" sentryRef="s_plan_drafted"/>
<itemControl><manualActivationRule id="m_s4"/></itemControl>
</humanTask>
<!-- ⑤⑥(前半)交人执行经营动作并填回执 —— 触达世界,不给 doneWhen -->
<humanTask id="S5_execute_receipt" name="执行经营动作并填回执">
<entryCriterion id="ec_s5" sentryRef="s_plan_approved"/>
<itemControl><manualActivationRule id="m_s5"/></itemControl>
</humanTask>
<!-- ⑥(后半A)写回主数据:重算覆盖度、更新档案与候选状态(不触达世界,给 doneWhen) -->
<humanTask id="S6_writeback" name="写回主数据:重算覆盖度并更新档案">
<entryCriterion id="ec_s6a" sentryRef="s_receipt_confirmed"/>
</humanTask>
<!-- ⑥(后半B)留痕:逐条台账更新明细 + 本轮结论(不触达世界,给 doneWhen) -->
<humanTask id="S6_ledger_update" name="更新关系台账:留更新明细与结论">
<entryCriterion id="ec_s6" sentryRef="s_masters_written"/>
</humanTask>
<!-- ⑦ 复盘与完结:终局唯放行,必须人工激活 -->
<humanTask id="T_release" name="台账更新确认放行">
<entryCriterion id="ec_rel" sentryRef="s_ledger_updated"/>
<itemControl><manualActivationRule id="m_rl"/></itemControl>
</humanTask>
</casePlanModel>
</case>
</definitions>`;
/**
* 案例说明书 —— **只写整单一以贯之的事**(逐步做法归剧本,两处不重复写同一件事)。
*/
export const NARRATIVE = `§0 开案:**这一轮要经营哪些客户的关系,经营完把结果写回台账**。
入参四个顶层字段:
· \`trigger\`——**必有**,\`"monthly"\`(月度例行)或 \`"event"\`(事件驱动),翻成人读标签
写进 \`crm_coverage.verdict.trigger_label\`(报告上不摆系统内英文码),不用你自己判该归哪一类。
· \`eventType\`——仅 \`trigger:"event"\` 时可能有,四种之一:\`"new_customer"\`(新客户建立
合作)/ \`"key_person_change"\`(关键人离职/调岗)/ \`"major_deal"\`(重大商机启动)/
\`"t04_project"\`(攻坚项目 T04 立项)。同样翻成人读标签写进
\`crm_coverage.verdict.event_type\`(对照表见下方 S1 的 guide),\`trigger:"monthly"\`
时这一列留空。
· \`customers\`——本轮涉及的客户,**元素形状分两种**:①字符串,已有客户的 \`customer_code\`
(就是 L-02 客户主台账的 \`customer_id\`,同一个东西);②对象,**仅
\`eventType:"new_customer"\` 时出现**——这一刻这家客户还没有编码,对象里装的是
"新建一条 L-02 客户档案要用的字段"(公司名等),编码要等人在数据录入卡上现场给
(细节见 S1 的 guide,这是本轮唯一会用到的第二种卡)。**留空 = 月度例行,你自己
按台账规则从整个客户库识别本轮候选**。
· \`note\`——本轮背景说明(选填),帮你判断覆盖深度与经营侧重。
**成品是三样东西,缺一不可**:①一份**核对过的候选覆盖清单**;②一份**经审批通过的
关系经营计划**(逐个关键人的建议动作与优先级);③**写回 L-02/L-08 主数据的台账更新**
(覆盖度重算、决策链更新、联系人档案更新),每条更新都能在执行回执里找到依据。
**AI 承担信息汇总、记录与初稿生成,人负责判断与决策**:识别候选、缺口分析、排序计划、
收口写台账,都是你的活;核对候选清单、审批计划、到场执行、签字放行,四道都要人过。
**能算的不问人**:关键人是否覆盖(决策链节点比对)、覆盖度、排序,都调工具算,
你不临场拍;调 \`crm-relationship-coverage.recompute-score\` 和
\`crm-relationship-coverage.rank-key-persons\`,一批一次,别一条条调。
**六步走**:识别候选 → 人核对范围 → 制定计划 → 人审批(可能来回改一轮)→
人执行填回执 → 收口写台账 → 人签字放行。
【本单可能用到的另一种卡 —— 新客户建档,长这样,只在 S1_identify_candidates 这一项里
发生,\`customers\` 里出现对象形状的元素才发】新客户这一刻没有编码,**不能自己编一个**
(客户编码是主数据的主键,编错一次后面每一单都跟着错——这条纪律与 A1 quote-intake
建新客户档案那一步完全一样,照抄它已经验证过的机制):
{"kind":"entry","itemId":"S1_identify_candidates","ask":"这是位新客户,请核对建档信息并给客户编码",
"ledger":"customer.profile","key":{"customer_code":""},
"fields":[
{"key":"customer_code","label":"客户编码","required":true},
{"key":"name","label":"公司名","value":"青岛新星电子有限公司","required":true},
{"key":"cust_type","label":"客户类型","value":"模组厂"},
{"key":"industry","label":"行业","value":"车规电子"},
{"key":"region","label":"区域","value":"山东"}
],
"also":[{"title":"联系人档案","ledger":"customer.contact","key":{"customer_code":""},
"autoNote":"联系人编码由系统按顺序生成,不用你填",
"fields":[
{"key":"name","label":"联系人姓名","value":"王芳","required":true},
{"key":"title","label":"职位","value":"采购经理"},
{"key":"email","label":"邮箱","value":"wang.fang@example.com"}
]}]}
**只有 \`customers\` 里那个对象已经给出的字段才带 \`value\`**,没给的字段整格别写(界面自己
留白待填,别拿假值凑数);\`customer_code\` 从不给 \`value\`,标 \`required:true\`,人不填就
确认不了;对象里没给 \`contact\` 就不带 \`also\` 段(不是每次都已经知道具体联系人)。
**联系人编码不用你写**——自增列,基座在确认那一刻生成,写进 \`fields\` 也会被摘掉。
**人按下确认那一刻基座已经把这两张表直接落进草稿层**——你不经手,同一拍或下一拍都不许
再 \`write\` 一遍 \`customer.profile\`/\`customer.contact\`(写了会被"写了等于没写"拒回)。
一次只发一张,这张的回复到了、这家客户的编码定下来了,才轮到 \`customers\` 里下一个
对象形状的元素(如果还有的话)。
【这一单唯一的岔路口 —— 经营计划审批,长这样,无论哪一拍都照这个发,别自己另编一张】
计划审批不是一次性的:人可能驳回、要求你按他的意见改。**这一步只在 S4_plan_approval
这一项里发生**,发决策卡之前先在轨迹里找有没有已经答过、还没处理的 \`[card-reply]\`:
· **\`value\` 是 \`"approve"\`**——写 \`plan_approved_total=1\`,finish 这一项,别再发卡;
· **\`value\` 是 \`"reject"\` 或以 \`"custom:"\` 开头**——都算驳回:\`"reject"\` 是点了固定
按钮、没写具体意见,**不许再发卡追问理由**(那两个按钮点出来的回复永远没有 note,
再问一遍是把人指向一扇打不开的门);以 \`"custom:"\` 开头的,\`note\` 就是他写的具体
意见,按这条意见改。两种都要对 \`crm_coverage.plan\` 做出至少一处实质性修订(同 PK
覆盖写,不开新计划项、不重新进入 sentry),然后**换一种问法**再发一张 decide 卡
(措辞不换会被系统当成同一张卡静默吞掉);
· **没有**——才发第一张 decide 卡。
{"kind":"decide","itemId":"S4_plan_approval","allowCustom":true,
"ask":"经营计划已算出,共 <N> 条,覆盖 <M> 家客户的关键人,请审批。",
"options":[{"value":"approve","label":"通过","recommended":true},
{"value":"reject","label":"驳回(不写具体意见)"}]}
三个 \`value\` 只有这三种形态:\`"approve"\`、\`"reject"\`、\`"custom:"\` 开头(人自己写的话)——
**别自己造第四种**。
【一路上要说话】负责人正看着这一单。**每一拍带一句 say** —— 说业务的话:
识别到几家候选、计划排了几个关键人、执行回执收了几条、覆盖度改了多少;不说系统词。
到了要找人的那一拍,先说清这一轮办完了什么、为什么现在需要他。
【怎么把一项干完 · 一拍连做】write 和 tool 都不会自动收步 —— 做完用 finish 宣告。
动作排进同一拍的 actions 串一口气给;只有发卡 / 提交(挂起)必须停拍。
**这一单常路 8-11 拍就该收口**(比 A1 多两道人环:候选核对 + 计划审批 + 执行回执 + 放行,
共四次;审批若被驳回会多出一到两拍)。
§Z 结案:AM Lead 在验收单上签字,系统把草稿合并进 L-02/L-08 正式版并发射
\`coverage-map\` 口 —— 这一轮的覆盖地图供下游读取(E05/E06 尚未实现,暂无消费者)。`;
/**
* 步骤剧本 —— 一个计划项一条;`doneWhen` 是「成果已存在」的判据,不是「该不该做」。
*
* 四处不给 `doneWhen`:`S2_scope_confirm`(交人核对触达世界)、`S4_plan_approval`
* (要人拍板,且这一项内部可能循环)、`S5_execute_receipt`(拜访/交流等 AI 做不到的线下动作)、
* `T_release`(签下去触达世界)—— 给了等于给引擎一把替人签字的钥匙。
*/
export const COVERAGE_STEPS = [
{
item: 'S1_identify_candidates',
intent: '按台账规则识别本轮候选覆盖客户,铸造本轮案号',
writes: ['crm_coverage.candidate', 'crm_coverage.verdict', 'customer.profile', 'customer.contact'],
/* 入参顶层字段不声明就会被整表段截断刀砍掉 —— 事件驱动那几条路全靠这四个字段;
case:openedAt 是铸 round_ref 的料(引擎注入的开案时刻,2026-08-22 起真的在作用域里) */
reads: ['trigger', 'eventType', 'customers', 'note', 'case:openedAt', 'customer.profile', 'customer.contact'],
view: 'coverage-scope',
tools: [],
/*
* **`_rows > 0` 独木难支,`_missing = 0` 也只顾得上半条命**(2026-08-22 两轮实测
* 逼出来的:事件驱动、customers 数组两个客户点名的入参)。
* 第一轮:模型写成的那一行 customer_name 是空的——`_missing = 0` 补上就治好了。
* 第二轮:模型干脆**没写第二颗客户那一行**(不是写了个空值,是那一行压根不存在)——
* `_missing` 只查「已经存在的行缺不缺字段」,查不出「该有的行还没生出来」;
* `_rows > 0` 更是从一开始就跟"两颗"这个数没关系,一行也算数。
* 两轮都在 S1/S2/S3/S4 四道全部放行,一路悄悄漏到 S5 提交闸才第一次被拦下。
* 治法只能是**把「该有几行」也摆进判据**:customers 数组点了几个名,
* 候选行就该有几条。
*
* **`count(customers)` 在入参没给这个键时 = 1,不是 0**(sfeel 的 count 对未绑定名
* flatten 出 `[undefined]`,长度 1 —— 包内 steps 测试用真 @sfeel 钉死了这三种形状):
* 月度例行(键不存在)走的是 `rows >= 1` 这一半,与前面 `rows > 0` 恰好同义,
* 所以行为对;但别把 `count(...) = 0` 那半句当成「留空时不约束」的开关 ——
* 它只在显式传了 `customers: []` 时才为真。
*/
doneWhen:
'crm_coverage_candidate_rows > 0 and crm_coverage_candidate_missing = 0 ' +
'and (count(customers) = 0 or crm_coverage_candidate_rows >= count(customers))',
guide: `第一件事:把这一轮**候选覆盖客户**列出来。**分两段**——先把每一位客户的编码
定下来,全部定下来了才写候选清单(候选清单不许分批写,写一半会被引擎当成这一步已经办完)。
**customers 里点了几个名,候选清单就该有几行**——事件驱动且点名的那几条路,
遗漏一颗客户等于让它从这一单里凭空消失,后面所有步骤都不会再补回来;
写完候选清单,自己数一遍行数是不是等于 customers 里点名的客户数,对不上就是漏写了。
【先铸造本轮案号 round_ref】格式固定:\`'COV-' + case:openedAt 按 YYYYMMDDHHmmss 格式化\`
(\`case:openedAt\` 就在你的作用域里,形如 "2026-08-22 08:47:58" —— 去掉分隔符即得;
你不必也不许自己编)。**不许从 \`crm_coverage.candidate\` 等正式表里已有的别单案号里挑**
—— 那是别的轮次的号,借了就把两轮的数据搅进同一个主键(A2 借号那一课的同款)。
**整单只有一个 round_ref**,写
\`crm_coverage.candidate\` / \`.plan\` / \`.receipt\` / \`.ledger_update\` / \`.verdict\`
时都用这同一个串 —— 造出两个号就是把一单裂成两单。
【第一段:把 customers 里每一项都定成一个确切的客户编码】
· \`customers\` 留空 —— **月度例行**,不必定编码,直接跳到"没点名"那条候选识别路径。
· 元素是字符串 —— 已经是编码,直接用,不必确认。
· 元素是对象(只在 \`eventType:"new_customer"\` 时出现)——**这是新客户,还没有编码,
发一张数据录入卡请人现场建档**,骨架照 NARRATIVE 那段"新客户建档"原样抄
(\`"kind":"entry"\`,\`ledger:"customer.profile"\`,\`key:{"customer_code":""}\`)。
一次只发一张,发完这一拍就结束(挂起);**人按下确认那一刻基座已经把公司档
(和给了的话,联系人档)落进草稿层**——不许你再 \`write\` 一遍这两张表。回复到了,
从轨迹里取人刚给的 \`customer_code\`,继续处理 \`customers\` 里下一项(还有的话)。
【第二段:全部编码定下来了,才做候选识别与写盘,一拍连做】
· 有确切编码的(月度例行没有这一条)—— **这一轮是事件驱动**,以这些编码为骨架,
逐个从 \`customer.profile\` / \`customer.contact\` 里取画像与联系人现状;
**候选清单只收这几个编码,一个不多、一个不少**——事件驱动点了名的,是这一轮
唯一该覆盖的客户;别顺手把台账里其他覆盖度低、决策链有空白的客户也一并纳入
(那是月度例行的活,这一轮不归它管)。已实测炸过:入参只点了 1 家,模型却在
交代里说"识别到 2 家候选客户",多出来的那一家台账里从没写过——人在核对卡上
看见的候选数量,得和候选清单里躺着的行数完全对得上,不能一个在嘴上一个在表里。
· \`customers\` 从一开始就留空的 —— **这一轮是月度例行**,按台账规则识别:优先选
覆盖度低(\`coverage_score\` 低或缺)、决策链有空白节点、或近期有商机/关键人变动
信号的客户。识别不出候选也要如实写 \`crm_coverage.verdict.summary\`,别硬凑几行充数。
【\`decision_chain\` 是什么形状,没有时怎么办】它是一个 JSON 结构:
\`{nodes:[{node, btLevel:"bt-01"|"bt-02"|"bt-03", required:boolean, contactCode, covered}]}\`。
这家客户若从没跑过 E04(\`customer.profile.decision_chain\` 空的),这一步不必初始化它 ——
留给 S3 那一步在做缺口分析时按标准三节点(bt-01 采购/供应链、bt-02 技术、bt-03 高层)
起草,这一步只管把候选客户与联系人现状读出来、把清单列好。
【写 \`crm_coverage.candidate\`,一颗客户一行】(rows 收数组)
round_ref、customer_code(**必填**,上一段定下来的那个)、customer_name(**必填**)、
cust_type(选填,\`customer.profile.cust_type\` 有值就直接抄它——新客户刚在录入卡上
给过;没有才按 \`grade\`/\`profile_tags\` 猜,如"Tier1 OEM"/"模组厂"/"方案商"/"贸易商"/
"终端",猜不出留空)、bt_level(选填,ASCII \`bt-01\`/\`bt-02\`/\`bt-03\` 之一,按这家
客户目前哪个层级的联系人最缺覆盖来定)、status(选填,ASCII \`pending\`,本步一律先写
这个值)、basis(选填,一句纳入依据,如"覆盖度已 40 天未更新"或"客户点名,商机联动"或
"新客户建档,首轮覆盖")、depth(选填,ASCII \`light\`/\`standard\`/\`deep\` 之一,按 basis
的紧迫程度定)、origin(固定写 \`ai\`)。
【同一拍写 \`crm_coverage.verdict\`(一行)】round_ref、trigger_label(**翻成人读标签写,
不写入参那个英文码**——\`"monthly"\`→"月度例行"、\`"event"\`→"事件驱动";报告上不摆系统内
英文名)、event_type(\`trigger:"event"\` 时把入参 \`eventType\` 也翻成人读标签写进来——
\`new_customer\`→"新客户建立合作"、\`key_person_change\`→"关键人离职/调岗"、
\`major_deal\`→"重大商机启动"、\`t04_project\`→"攻坚项目(T04)立项";\`trigger:"monthly"\`
时这一列留空)、note(入参 \`note\` 原样抄,没有留空)、summary 一句人话(本轮识别到几家
候选、依据是什么)。写完 finish 这一项。`,
},
{
item: 'S2_scope_confirm',
intent: '交人核对候选清单:增删改,确认本轮真正要覆盖谁',
writes: ['crm_coverage.verdict'],
reads: ['crm_coverage.candidate'],
view: 'coverage-scope',
tools: [],
/* 不给 doneWhen —— 交人核对触达世界(manualActivation),引擎不替人收 */
guide: `候选清单起草好了,**该 AM Lead 核对了** —— 这是本活动第一张执行单,零输入。
工作区摆的是候选清单的报告(编辑闸开着就能增删改),他在那份件上核对:AI 没识别到但
他了解有价值的客户,他会加一行;AI 纳入了但实际不该覆盖的,他会删掉;识别错的信息,
他会就地改。
这一步标了人工激活:发卡就算启动,不必单发 start;人交回后你自己 finish。
【照这个骨架发,别少字段】(卡面是纯文本,steps/standard 里别写星号记号,别绑 \`blocks\`
或 \`fields\` —— 整份清单在工作区那份件上,执行卡零输入)
{"kind":"human","itemId":"S2_scope_confirm","workOrder":"execution","card":{
"title":"覆盖范围确认:本轮共 <N> 家候选客户",
"ask":"AI 已按台账规则识别出 <N> 家候选客户,请核对确认。",
"steps":["工作区的件上核对候选清单:客户编码与客户名称是必填,缺一条这一单走不到经营计划那一步",
"加一行 = 额外纳入(AI 没识别但你了解有价值的客户)",
"删一行 = 排除(AI 纳入了但实际应排除的客户)",
"改单元格 = 修正 AI 识别的内容 —— 客户类型、BT 层级、纳入依据都能改",
"改完保存,回这张卡提交"],
"standard":"候选清单里每一行客户编码、客户名称都齐;该纳入的都纳入、该排除的都排除了。",
"basis":[{"k":"触发口径","v":"<照 crm_coverage.verdict 刚写的 trigger_label/event_type 说,如
「月度例行」或「事件驱动:重大商机启动」>"},
{"k":"AI 识别","v":"<N> 家候选客户"}]}}
发完这一拍就结束了(发卡挂起)。
【人交回之后】这一步你只做两件事,然后 finish:
① 写 \`crm_coverage.verdict.scope_confirmed_total = 1\`;他在件上撤掉了行或加了行的话,
下一步的缺口分析、计划制定按当下 \`crm_coverage.candidate\` 的实际行数走,不必你重写。
② finish 这一项。**别在这一拍做缺口分析** —— 那是下一步的活,这里做了也白做。
【这张卡一单只发一次】人交回来之后不许再发同一张卡 —— 缺必填有提交闸兜着,
卡回来时客户编码/客户名称一定是齐的(或那几行已被他撤掉)。`,
},
{
item: 'S3_plan',
intent: '读覆盖现状、缺口分析、制定关系经营计划',
writes: ['crm_coverage.plan', 'crm_coverage.receipt', 'crm_coverage.candidate'],
reads: ['crm_coverage.candidate', 'customer.profile', 'customer.contact'],
view: 'coverage-plan',
tools: ['crm-relationship-coverage.recompute-score', 'crm-relationship-coverage.rank-key-persons'],
doneWhen: 'crm_coverage_plan_rows > 0 and crm_coverage_plan_missing = 0',
guide: `候选范围定了,**现在读现状、找缺口、排计划** —— 固定三拍,一拍一件事,别在同一拍里
把「调工具」和「写台账」串在一起(**工具调用后同拍其余动作作废,结果要下一拍才看得见**;
串了也是白串,只会多烧一拍)。这一步只需要调工具**各一次**,拿到结果后不要再调第二次。
**这一步只管 \`crm_coverage.candidate\` 里躺着的那几家,一家都不能多**——\`reads\` 里
还带着 \`customer.profile\`/\`customer.contact\` 整表,那是给你查这几家候选客户各自的
决策链与联系人现状用的**参考资料**,不是"顺手也把看见的其他客户一起排上计划"的许可。
已实测炸过:候选清单里明明只有 1 家,写出来的计划却多了一家台账里压根不存在于候选清单
的客户(那家客户是**从 customer.profile/customer.contact 整表里"看见"的**,和这一轮
候选范围毫无关系)——动手前先把候选清单的 \`customer_code\` 抄一遍,写计划时逐条核对
是不是都在这张抄出来的清单里,不在的一律不许写。
【第一拍:缺口分析,只调工具,别的什么都不写】把本轮全部候选客户(读
\`crm_coverage.candidate\`)一次性打包,调一次
\`crm-relationship-coverage.recompute-score\`,入参:
\`\`\`
{"customers":[
{"customerCode":"CU-0142",
"decisionNodes":[
{"node":"bt-01","btLevel":"bt-01","required":true},
{"node":"bt-02","btLevel":"bt-02","required":true},
{"node":"bt-03","btLevel":"bt-03","required":true}],
"contacts":[
{"contactCode":"CU-0142-01","decisionNode":"bt-01","keyPerson":false,"influence":"","relationship":0}]}
]}
\`\`\`
· \`decisionNodes\`:这家客户若已有 \`customer.profile.decision_chain\`,原样照抄它的节点;
没有(新客户/首次跑 E04)就照抄上面这三行(**node 与 btLevel 写同一个值**,
\`bt-01\`/\`bt-02\`/\`bt-03\`,一个字符不许错、不许留空 —— node 留空会让出参里那个节点
变成 \`null\`,缺口分析就废了一条)。
· \`contacts\`:这家客户名下**所有**已知联系人(读 \`customer.contact\`),逐人填
\`contactCode\`/\`influence\`/\`relationship\`/\`keyPerson\`;\`decisionNode\` 按人现有的
\`customer.contact.decision_node\` 填,没有就按职位/角色估一个最贴近的层级(如「采购部」
估 bt-01),都判不出就填 \`"bt-01"\` 兜底 —— **这一格不许留空**,留空这个人就永远配不上
任何节点、白算了这一趟。空缺字段(如 \`influence\` 还没定过)照现有值填,是空字符串
或 0 都行,工具认得。
多家候选客户就都塞进同一个 \`customers\` 数组里,**一次调完,不要一家调一次**。
调完这一拍就结束(工具出参要下一拍才看得见)。
【第二拍:排序,只调工具,别的什么都不写】把上一拍 \`results\` 里出现过的**所有联系人**
(不止缺口节点上的那几位)一次性打包,调一次 \`crm-relationship-coverage.rank-key-persons\`,
入参 \`{"contacts":[{"contactCode":"...","influence":"...","relationship":0}, ...]}\`——
**批量调,不要一人调一次**。调完这一拍就结束。
【第三拍:写计划,一拍连做】拿着前两拍的工具出参,**一次都不要再调工具**,直接写:
① 逐家候选客户看第一拍的 \`gapNodes\`:**gapNodes 里的每一个缺口节点,都要在计划里
至少配一个关键人经营动作**——就算这个节点上目前一个联系人都没有覆盖到(\`coveredNodes\`
是空的、\`coverageScore\` 是 0),那正是这一步该管的事:挑这家客户名下**已知的、
离这个节点最近的联系人**(按 \`decisionNode\` 匹配,匹配不上就挑影响力最高的那位)
写一条「去覆盖这个节点」的动作。**\`gapNodes\` 全员缺口是正常结果,不是异常,
不需要回去重调工具确认。**
② **写 \`crm_coverage.plan\`,按第二拍的 rank_score 降序、一个关键人一行**(rows 收数组;
模版不排序,你必须已经按序写)。round_ref、contact_code(**必填**)、customer_code
(**必填**)、customer_name(选填)、contact_name(选填)、action(**必填**,具体到能
执行的建议动作,如"下月中拜访技术总监,带 XX 认证报告"而不是笼统的"拜访")、priority
(**必填**,ASCII \`high\`/\`medium\`/\`low\`,照第二拍算出的档位抄)、rationale(**必填**,
说清为什么是这个人这个动作,引影响力与亲密度差值,或"该节点目前空白,此人是最接近的
候选")、influence(**必填**,写计划这一刻的快照,照抄联系人当前的 influence,没有就写
\`"low"\`)、relationship_gap(**必填**,第二拍算出的数)、rank_score(**必填**,第二拍
算出的数)、target_date(**必填**,具体日期,没有时间点的建议不算数)。
③ **同一拍,给每条计划配一行 \`crm_coverage.receipt\` 骨架**(这一步只写骨架,完成状态那几列
留空,S5 那一步才由人填):round_ref、contact_code(与 plan 同一个,**必填**)、
customer_code(选填,照抄)、customer_name(选填,照抄 —— 执行回执工单的「对象」列长
"客户名 · 关键人",两个名字都从这里来)、contact_name(选填,照抄)、action(选填,
照抄 plan 的 action,供执行回执工单只读展示)、target_date(选填,照抄)。
completion_status / result_summary / customer_feedback / key_person_change
**这一步一格都不写**。
④ 若某候选客户的 \`cust_type\`/\`bt_level\` 在 S1 那步没判出来,顺手补写
\`crm_coverage.candidate\` 的这两列(同 PK 覆盖写)。
写完这一拍就结束(不必你 finish)——\`crm_coverage_plan_rows > 0 and
crm_coverage_plan_missing = 0\` 一旦为真,引擎自己收这一步。`,
},
{
item: 'S4_plan_approval',
intent: '经营计划审批:通过就往下走,驳回则按意见修订后换一种问法再提交',
writes: ['crm_coverage.plan', 'crm_coverage.receipt', 'crm_coverage.verdict'],
reads: ['crm_coverage.plan'],
view: 'coverage-plan',
tools: [],
/* 决策卡循环 + 要人拍板 —— 不给 doneWhen,引擎不许替人收 */
guide: `**动手之前先在轨迹里找一行 \`[card-reply]\` 带这一项的答复:**
· **没找到** —— 这是第一次,跳到下面【发一张决策卡】那一段;
· **\`value\` 是 \`"approve"\`** —— 写 \`crm_coverage.verdict.plan_approved_total = 1\`,
finish 这一项,**不许再发卡**;
· **\`value\` 是 \`"reject"\` 或以 \`"custom:"\` 开头** —— 都算驳回,跳到下面【驳回之后】
那一段,修订计划再重新发卡。
【这一步办完了没有,看一个数】\`plan_approved_total\`:它写成 1 就是这一步办完了,
引擎不会再把这一项交给你(它归零 doneWhen 帮不上忙,只有你自己 finish)。
【发一张决策卡,照这个骨架】(选项少于两个基座拒发;\`allowCustom\` 别漏 —— 少了它,人只有
两个固定按钮可点,连意见都没地方写;\`ask\` 每一轮都要按下面的规则换措辞)
{"kind":"decide","itemId":"S4_plan_approval",
"allowCustom":true,
"ask":"经营计划已算出,共 <N> 条,覆盖 <M> 家客户的关键人,请审批。",
"options":[
{"value":"approve","label":"通过","recommended":true,"consequence":"计划进入执行阶段,交人去落实"},
{"value":"reject","label":"驳回(不写具体意见)","consequence":"AI按自己的判断复核一遍计划,重新提交审批"}],
"basis":[{"k":"覆盖客户","v":"<M> / <本轮候选总数> 家已各有至少一条计划"},
{"k":"排序依据","v":"影响力 × 亲密度缺口"},
{"k":"想说明理由","v":"不点上面两个按钮,直接在下方输入框写你的意见再提交"}]}
发完这一拍就结束了(发卡挂起)。
【驳回之后 —— 不管有没有具体意见,都只处理这一轮,不许再追问一遍】
① \`value\` 以 \`"custom:"\` 开头 —— \`note\` 就是他写的具体意见,**按这条意见**修订
\`crm_coverage.plan\`(同 PK 覆盖写对应的行,不开新计划项、不重新进入 sentry)。
② \`value\` 是 \`"reject"\`(点了固定按钮,没写具体意见)—— **不许再发卡追问理由**
(那两个按钮点出来的回复永远没有 note,再问一遍是把人指向一扇打不开的门,
会被同一句话拒回,连问出真话的机会都没有)。改成你自己复核:排序有没有明显不合理、
有没有遗漏更紧要的缺口节点、理由那一句站不站得住,尽量改得更有说服力;复核后**至少要有
一处实质性变化**(改排序、换动作、补理由,不能原样重发)。
③ **改了 \`action\` 这一列的话,\`crm_coverage.receipt\` 同一行的 \`action\` 也要跟着改**
(同 PK——\`round_ref\`+\`contact_code\`——覆盖写)。receipt.action 是 S3 那一拍从
plan.action **抄**过来的展示副本(执行回执工单直接读它、只读展示),这里只改了
plan 不改 receipt,S5 那一步人看到的还是驳回前的旧动作——已经实测过:审批来回改了
三稿,receipt 里摆的还是第一稿,推进到执行回执时对不上人刚通过的那一版。
④ **重新发卡的 \`ask\` 必须换一种说法**,带上修订轮次与改了什么,例如
"已复核并修订(第2稿:调整了 CU-0142 的优先级,补充了理由)请再次审批"——
措辞不换,系统会把它当成同一张卡:前两次静默吞掉(人看不到新卡),第三次直接拒。
**每一轮的 \`ask\` 都要与上一轮明显不同**,不止改个数字。
⑤ 再走一遍【发一张决策卡】那一段(用新措辞,\`allowCustom\` 仍然要给),然后这一拍结束
(发卡挂起)。
**这一步只在这一项里循环,不许把驳回意见带去改别的项**(S1/S2/S3 写下的东西不动)。`,
},
{
item: 'S5_execute_receipt',
intent: '交人执行经营动作,回填执行回执',
writes: [],
reads: ['crm_coverage.receipt'],
view: 'coverage-receipt',
tools: [],
/*
* 拜访 / 技术交流等线下动作 AI 做不到 —— 不给 doneWhen。
* **这一步没有任何记账旗**(2.0.2 退役 receipt_confirmed_total,两轮活单熔断实测):
* 旗是模型写的,S4 一过它顺手写旗就坚信 S5 已办完,连续五拍
* [写旗 + finish 被 worldTouched 拒] 熔断。回执四列全是人在件上填的,
* 收口那道门读的是基座数(`_rows`/`_missing`)—— 模型无账可记,也就无处抢跑。
*/
guide: `计划审批通过了,**该 AM 出场了** —— 这是本活动第二张执行单,零输入。
触达客户的那一下由人做:拜访、技术交流、价值传递这些线下动作 AI 做不到,
工作区摆的是执行回执工单(逐个关键人一行,计划动作只读展示,回执四列可填)。
这一步标了人工激活:发卡就算启动,不必单发 start。**这一步不写任何台账** ——
回执四列由人在件上填、由基座直接落草稿层,你只有两个动作:发卡,和人交回后 finish。
**这一步「人被问过没有」不用你判断 —— 【当前这一步】标题下面那行引擎事实写得明明白白**
(「这一单里已经真的给它发过卡…人已经被问过了」或「这一单里从没给它发过卡」),照它分两条路:
· 那行说**「从没给它发过卡」** —— 你唯一合法的动作就是照下面骨架发卡,发完这一拍结束(挂起)。
**别去轨迹里数 \`[card-reply]\` 自己判断**:S2 的执行单、S4 的决策卡都会在轨迹里留
\`[card-reply]\`,它们不是这一步的 —— 实测两轮连拒都是把 S4 审批的答复当成了 S5 的回执,
凭空宣布「执行回执已交回」。引擎那行事实比你对轨迹的读法准,以它为准。
· 那行说**「人已经被问过了」** —— 人交回后直接 finish 这一项,一个字都不用写
(回执数据人已经在件上填好、基座已落草稿层;**别在这一拍收口写台账** ——
重算覆盖度、写回主数据是下一步的活)。
【照这个骨架发,别少字段】(执行卡零输入,别绑 \`blocks\` 或 \`fields\`)
{"kind":"human","itemId":"S5_execute_receipt","workOrder":"execution","card":{
"title":"经营计划执行回执:共 <N> 项动作",
"ask":"经营计划已审批通过,请执行并回填回执。",
"steps":["每项动作执行后,在工作区的件上逐项回填:完成状态、结果摘要为必填,客户反馈选填",
"关键人变动如实回答「是/否」;若填「是」,请在结果摘要里说清具体变动是什么",
"确实没有变动的,「关键人变动」这一格要写「无变动」,不能留空",
"改完保存,回这张卡提交"],
"standard":"每一项动作的完成状态与结果摘要都填了;关键人变动逐项如实标注,不遗漏。",
"basis":[{"k":"本轮动作数","v":"<N> 项"}]}}
发完这一拍就结束了(发卡挂起)。
【这张卡一单只发一次】人交回来之后不许再发同一张卡 —— 缺必填有提交闸兜着。`,
},
{
item: 'S6_writeback',
intent: '写回主数据:按回执重算覆盖度,更新联系人/客户档案与候选状态',
writes: ['customer.profile', 'customer.contact', 'crm_coverage.candidate'],
reads: ['crm_coverage.receipt', 'crm_coverage.plan', 'customer.profile', 'customer.contact'],
view: 'coverage-ledger-update',
tools: ['crm-relationship-coverage.recompute-score'],
/*
* 四个数全是基座现数:重算分与档案都写齐了才算办完。
* 收口从一项拆成两项(2.0.4 结构治):从前「三拍纪律」写得再明白,模型照样把
* 四张表挤进一串,串尾最费字数的 ledger_update 被截成空数组挨拒(两轮实测同一处)。
* 拆开之后每项的剧本只摊自己那几张表 —— 串想长也长不起来。
*/
doneWhen:
'customer_profile_rows > 0 and customer_profile_missing = 0 and customer_contact_rows > 0 and customer_contact_missing = 0',
guide: `回执都收齐了,**现在写回主数据** —— 固定两拍,别把「调工具」和「写台账」串进
同一拍(工具调用后同拍其余动作作废,结果要下一拍才看得见)。
**这一步不碰 \`crm_coverage.ledger_update\`** —— 留更新明细是下一步(S6_ledger_update)
的活,这一步只管把主数据写对。
【第一拍:重算,只调工具,别的什么都不写】逐个联系人按回执推导新的 relationship/influence
(草稿,先想清楚,还不落库):\`completion_status\` 为 \`done\` 且关系明显推进的,
\`relationship\` 上调(不超过 5);\`partial\`/\`skipped\` 的原样不动或视回执具体内容微调。
**调不出结论就别改** —— 没有回执支持的调整不许写。把本轮涉及的候选客户**一次性打包**,
调一次 \`crm-relationship-coverage.recompute-score\`:
\`decisionNodes\` 照抄 \`customer.profile.decision_chain\` 现有的节点(\`node\`/\`btLevel\`/
\`required\` 三样都要,一个字符不许错);\`contacts\` 是这家客户名下**所有**已知联系人,
**逐人五个字段都要给全**——\`contactCode\`/\`decisionNode\`(按 \`customer.contact
.decision_node\` 填)/\`keyPerson\`(这轮判断的关键人标记)/\`relationship\`(推导后的新值)/
\`influence\`(推导后的新值)。
\`\`\`
{"customers":[{"customerCode":"CU-0142",
"decisionNodes":[{"node":"bt-01","btLevel":"bt-01","required":true}, ...],
"contacts":[{"contactCode":"CU-0142-01","decisionNode":"bt-01","keyPerson":true,
"relationship":4,"influence":"high"}]}]}
\`\`\`
**\`keyPerson\` 这一格漏了,工具会把这个人当成"不存在",这个节点当场判空**——已经实测炸过:
同一批入参只差这一个字段,coverageScore 从 17/33 变成 0,跟台账里明明白白写着的
"bt-01 已覆盖"对不上;模型因此怀疑结果、来回改了又调、调了又改,一单纯为这一步烧了 26 次
工具调用、烧穿了 500k token 预算,\`crm_coverage.ledger_update\` 一行没写成就 exhausted 了。
拿到 \`results\`(逐家 coverageScore/coveredNodes)——**一次调完,不要一家调一次,更不要见
结果与预期不符就反复重调**:它是纯工具,同样的输入永远同样的结论,算出来的覆盖度比预想低,
先检查自己传的 \`contacts\` 是不是漏了字段,而不是重调一遍赌下次会不一样(不会)。
调完这一拍就结束。
【第二拍:只写这两张,别的表这一拍不碰】
· **写 \`customer.contact\`**(对应联系人行,同 PK 覆盖写):**碰过的每一行,
\`influence\` / \`relationship\` / \`key_person\` 三列一列都不许缺**(基座数缺必填
只看本单写下的行 —— 你写了这一行却漏了其中一列,\`customer_contact_missing\`
就大于零,放行门永远打不开):\`influence\` 写推导后的评级(没变化就照现值抄一遍)、
\`relationship\` 写推导后的新值(没变化照现值抄)、\`key_person\` 照这轮判断写;
**本单新建档的联系人**(录入卡只落了姓名/职务/邮箱)这三列由你首评:
\`influence\` 按职务估(总监/VP 级 high、经理级 medium、其余 low)、
\`relationship\` 按实际接触定(还没接触过写 1)、\`key_person\` 如实判断;
\`role\`/\`decision_node\`/\`comm_pref\` 若这轮有新信息一并补;
\`change_log\` 追加一句本轮变动(离职/调岗从回执的 \`key_person_change\` 抓,
没有变动不必写这一列)。
· **写 \`customer.profile\`**(对应客户行,同 PK 覆盖写):\`coverage_score\` = 工具
算出的新分数(**必填**);\`decision_chain\` = 更新后的节点覆盖状态(**必填**,
整个数组重写,不是增量合并);\`interaction_timeline\` 追加本轮的互动条目(选填,
没有新增互动的客户不必写这一列)。
· **把办完的候选行状态置 \`covered\`**(写 \`crm_coverage.candidate\`,同 PK 覆盖写,
只带 status 一列):回执里有 \`done\` 动作的客户置 ASCII \`covered\`;
全部 \`skipped\` 的留 \`pending\` 如实 —— 清单上那枚状态徽是给人看的,
一轮跑完还全写着「待覆盖」就是界面在撒谎。
写完这三张,这一步就办完了 —— 不必你 finish,\`doneWhen\` 会在主数据写齐那一刻自动收步,
留更新明细归下一步(S6_ledger_update),这一拍到此为止。`,
},
{
item: 'S6_ledger_update',
intent: '留痕收口:逐条台账更新明细 + 本轮覆盖结论',
writes: ['crm_coverage.ledger_update', 'crm_coverage.verdict'],
reads: ['crm_coverage.receipt', 'customer.profile', 'customer.contact'],
view: 'coverage-ledger-update',
tools: [],
doneWhen:
'crm_coverage_ledger_update_rows > 0 and crm_coverage_ledger_update_missing = 0 and customer_profile_missing = 0 and customer_contact_missing = 0',
guide: `主数据已在上一步写回,**这一步只写两张表、一拍连做**:先写更新明细
(它最费篇幅,放在串首),再写结论。**上一步那三张表(contact/profile/candidate)
这一拍不许再写一遍** —— 再写一遍原值只会被当空转拒掉。
· **写 \`crm_coverage.ledger_update\`,每条主数据更新对应一行**(rows 收数组,
**数组不能是空的** —— 第二拍写的每一处真实变化,这里都要有一行对应):
round_ref、update_seq(**必填**,本轮内从 1 递增)、ledger_label(**必填**,人读名,
如"客户主台账·关系覆盖度")、ledger_address(**必填**,机器地址,如
"customer.profile.coverage_score")、customer_code(**必填**)、contact_code
(选填,客户级字段留空)、field_label(**必填**,人读列名)、before_value(**必填**,
无值写字面"(无)")、after_value(**必填**)、evidence_ref(**必填**,引用具体回执
事实,如"回执 contact_code=CU-0142-01:已完成拜访,亲密度由3提升至4")。
**没有真实变化的字段不必开一行** —— 明细表只记「真改了什么」。
每一处真实变化的依据都在回执与上一步写下的新值里,对照着写,别凭记忆。
· **写 \`crm_coverage.verdict\`**(同一拍接着写):ledger_update_confirmed_total = 1、
covered_customer_total = 这一轮覆盖度提升或已达标的客户数、
key_person_covered_total = 这一轮标记为 key_person 的联系人数、summary
补一句人话(这一轮覆盖了几家客户、覆盖度平均涨了多少)。
写完 finish 这一项 —— 或者不必你 finish,\`doneWhen\` 会在必填列都齐的那一刻自动收步。`,
},
{
item: 'T_release',
intent: '台账已收口,提交放行交 AM Lead 验收;被打回就按意见改完再交',
/* 被打回重做时要改的就是这三张 —— 不声明就写不进去(写的授权面是台账声明) */
writes: ['crm_coverage.plan', 'crm_coverage.ledger_update', 'crm_coverage.verdict'],
reads: ['crm_coverage.plan', 'crm_coverage.ledger_update', 'crm_coverage.receipt'],
view: 'coverage-ledger-update',
tools: [],
/* 放行触达世界,必须人按一下 —— 引擎不自动收它 */
guide: `台账更新明细齐了、主数据缺项也补齐了,**可以走放行了**
(T_release 人工激活:动它就算启动,不必单发 start)。
【**先看一眼轨迹里有没有「验收被打回重做」**】有的话,这一拍**不是**再提交一遍 ——
人已经看过这份成果并把它退回来了,原样递上去他看见的还是刚被他打回的那一份
(基座那道闸也会当场拒:「打回之后台账一个字没改」)。照他的意见改**台账本身**,
改完这一拍再 judge + submit:
· 他说**计划的理由空/不实** → 改 \`crm_coverage.plan\` 的 \`rationale\`(同 PK 覆盖写),
把影响力评级、亲密度差值、决策链缺的是哪个节点写进去,一条一条落实;
动作不具体的连 \`action\`/\`target_date\` 一起改。
· 他说**台账更新没依据 / 数不对** → 改 \`crm_coverage.ledger_update\` 的
\`evidence_ref\`(点名是哪条回执支持它)或 \`before_value\`/\`after_value\`。
· 他说**结论没说清** → 改 \`crm_coverage.verdict.summary\`。
· 他**没写具体意见**(只点了重做)→ 自己把成果复核一遍,挑最站不住的那处改实
(理由最空的那条计划、依据最含糊的那条更新),**至少改对一处**;
实在判断不出他要什么,发一张零输入执行工单问清楚,别原样重提。
**改的是值,不是数**:同一行覆盖写,别新开行、别把行数改了 —— 那会把收步判据弄假。
【没被打回(或已按意见改过了)】先把裁量判据裁掉,然后 submit,同一拍连做:
· judge criterion="每条台账更新都能在执行回执里找到对应事实依据,不是凭空推算" ——
逐条看 \`crm_coverage.ledger_update\` 的 \`evidence_ref\`:说得出具体哪条回执支持这条
更新就 satisfied=true;有一条说不清来路的,satisfied=**false**,rationale 逐条写清,
交签字的人定。
· judge criterion="更新后的数值(覆盖度、亲密度等)变化合理,没有异常跳变" ——
看每条数值型更新的 before/after 幅度是否与回执的完成程度相称。
· judge criterion="更新内容准确对应回执事实,没有张冠李戴或误更别的客户/联系人" ——
核对 customer_code/contact_code 与回执主体是否一致。
**裁成不满足照样能提交** —— 理由连着卡一起交给签字的人。criterion 照判据名整句原样抄,
写别的名字对不上。
这一单没有分支去向 —— 全程线性,只发一口 \`coverage-map\`,不必你选出口。
终局唯放行:你不能自己宣布完成。`,
},
];
/**
* 阶段 —— 进度条的刻度,只给人看。除放行外每个计划项恰属一个阶段;
* 放行那一格由基座统一加,人看到的是 x/4。
*/
export const COVERAGE_PHASES = [
{ name: '确认覆盖范围', items: ['S1_identify_candidates', 'S2_scope_confirm'] },
{ name: '制定与审批计划', items: ['S3_plan', 'S4_plan_approval'] },
{ name: '执行与更新台账', items: ['S5_execute_receipt', 'S6_writeback', 'S6_ledger_update'] },
];