/** * 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 = ` update crm_coverage_candidate_rows > 0 update scope_confirmed_total > 0 update crm_coverage_plan_rows > 0 and crm_coverage_plan_missing = 0 update plan_approved_total > 0 update crm_coverage_receipt_rows > 0 and crm_coverage_receipt_missing = 0 update customer_profile_rows > 0 and customer_profile_missing = 0 and customer_contact_rows > 0 and customer_contact_missing = 0 update ledger_update_confirmed_total > 0 and crm_coverage_ledger_update_rows > 0 and crm_coverage_ledger_update_missing = 0 and customer_profile_missing = 0 and customer_contact_missing = 0 `; /** * 案例说明书 —— **只写整单一以贯之的事**(逐步做法归剧本,两处不重复写同一件事)。 */ 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":"经营计划已算出,共 条,覆盖 家客户的关键人,请审批。", "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":"覆盖范围确认:本轮共 家候选客户", "ask":"AI 已按台账规则识别出 家候选客户,请核对确认。", "steps":["工作区的件上核对候选清单:客户编码与客户名称是必填,缺一条这一单走不到经营计划那一步", "加一行 = 额外纳入(AI 没识别但你了解有价值的客户)", "删一行 = 排除(AI 纳入了但实际应排除的客户)", "改单元格 = 修正 AI 识别的内容 —— 客户类型、BT 层级、纳入依据都能改", "改完保存,回这张卡提交"], "standard":"候选清单里每一行客户编码、客户名称都齐;该纳入的都纳入、该排除的都排除了。", "basis":[{"k":"触发口径","v":"<照 crm_coverage.verdict 刚写的 trigger_label/event_type 说,如 「月度例行」或「事件驱动:重大商机启动」>"}, {"k":"AI 识别","v":" 家候选客户"}]}} 发完这一拍就结束了(发卡挂起)。 【人交回之后】这一步你只做两件事,然后 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":"经营计划已算出,共 条,覆盖 家客户的关键人,请审批。", "options":[ {"value":"approve","label":"通过","recommended":true,"consequence":"计划进入执行阶段,交人去落实"}, {"value":"reject","label":"驳回(不写具体意见)","consequence":"AI按自己的判断复核一遍计划,重新提交审批"}], "basis":[{"k":"覆盖客户","v":" / <本轮候选总数> 家已各有至少一条计划"}, {"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":"经营计划执行回执:共 项动作", "ask":"经营计划已审批通过,请执行并回填回执。", "steps":["每项动作执行后,在工作区的件上逐项回填:完成状态、结果摘要为必填,客户反馈选填", "关键人变动如实回答「是/否」;若填「是」,请在结果摘要里说清具体变动是什么", "确实没有变动的,「关键人变动」这一格要写「无变动」,不能留空", "改完保存,回这张卡提交"], "standard":"每一项动作的完成状态与结果摘要都填了;关键人变动逐项如实标注,不遗漏。", "basis":[{"k":"本轮动作数","v":" 项"}]}} 发完这一拍就结束了(发卡挂起)。 【这张卡一单只发一次】人交回来之后不许再发同一张卡 —— 缺必填有提交闸兜着。`, }, { 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'] }, ];