/** * 活动包清单 —— E04 关系覆盖与关键人经营(案例活动)。 * * 业务出处:`docs/SS01-客户与市场/e04-html/E04-活动流程与工单说明.md`(7 步业务链条, * AI 承担信息汇总/记录/初稿生成,人负责判断与决策,收成 2 张执行工单 + 2 张验收/决策工单)、 * `docs/SS01-客户与市场/Demo-E04-I01-Activity台账关联与设计.html`(L-02 客户主台账 / * L-08 客户联系人台账的读写关系)。 * * 这是本仓库第一个不属于 `quote-*` A1-A7 报价管线的活动:触发方式是月度例行 / 事件驱动 * 而非来件邮件,核心读写对象是共享主数据 `customer.profile`/`customer.contact`。 * * 两处与 A 系列的刻意不同,写在这里免得下一个读者当成疏漏: * ① `customer.profile`/`customer.contact` 已由 A1(quote-intake)声明 `kind:'master'` * 并持有 `customer_code`/`name` 等基本列 —— 这里只**追加**本活动要用的新列 * (`schema-merge.ts` 的声明合并是纯 add-column,不冲突,不需要重复声明已有列)。 * ② 「验收工单1」(经营计划审批)是**非终局**的通过/驳回+AI重做循环 —— * `issueAcceptance` 硬绑定终局放行、一个任务只能发一次,不能拿来做中间验收点, * 所以这里用 `decide` 决策卡实现(S4_plan_approval),真正的终局验收放行是 * 「验收工单2」(T_release,台账更新确认)。 */ import type { ActivityManifest, DmnModel } from '@contracts'; import { activityIdSchema } from '@contracts'; import { COVERAGE_SKILLS } from './skills.ts'; import { CMMN_XML, NARRATIVE, COVERAGE_PHASES, COVERAGE_STEPS } from './case-model.ts'; import { COVERAGE_SCOPE, COVERAGE_PLAN, COVERAGE_RECEIPT, COVERAGE_LEDGER_UPDATE } from './views/report.ts'; const D = 'crm_coverage'; const ACTIVITY_ID = activityIdSchema.parse('crm-relationship-coverage'); /** * 没有判定表 —— 覆盖度与排序是**连续加权算术**(BT 层级权重 × 覆盖率、 * 影响力权重 × 亲密度缺口),不是离散分支,不适合 DMN 命中表,交给两个纯工具算 * (见 `apps/api/src/app/tools/first-party.ts` 的 * `crm-relationship-coverage.recompute-score` / `.rank-key-persons`)。 * `dmn` 在清单里是必填字段,空表即可 —— 静态审不对 DMN 表数量做任何要求。 */ const EMPTY_DMN: DmnModel = { drd: [], tables: [] }; export const manifest: ActivityManifest = { identity: { activityId: ACTIVITY_ID, name: 'E04 · 关系覆盖与关键人经营', packageVersion: '2.0.9', category: '客户关系经营', sdkVersion: '0.1.0-m5', checksum: 'dev-checksum-crm-relationship-coverage', }, /* ── 契约七件 ────────────────────────────────────────────────── */ contract: { activityId: ACTIVITY_ID, version: '1.0.0', input: { /* * 不是来件解析(没有唯一「这封信」),是触发参数。§六 E04 业务说明与 * `Demo-E04-I01-Activity台账关联与设计.html`(L-02/L-08 完整字段表)两份文档核对 * 后按用户明确要求做成**两级下拉 + 一个数组**: * ① `trigger` 必填 —— 月度例行 / 事件驱动,用户明确要求这一级必须是显式的 * 下拉框(不接受"从数组是否点名反推"这种省略,那样选项与数组状态一旦不一致 * 就是一处会撒谎的地方); * ② `eventType` 选填,只在 `trigger:'event'` 时在测试台表单里现身(靠 * `showWhen` 这个测试台通用能力),值就是业务说明里那四种事件; * ③ `customers` 数组,元素两种形状二选一:字符串 = 已有客户的 `customer_code` * (业务说明里这就是 L-02 的 `customer_id`,同一个概念,本仓库沿用既有列名 * 不改叫法);对象 = **仅 `eventType:'new_customer'` 时使用**,这一刻这家 * 客户还没有编码(新客户建档要等人在录入卡上现场给),数组元素就相当于 * "新建一条 L-02 客户主台账记录所包含的字段"。录入卡会把这些字段落进 * `customer.profile`/`customer.contact` —— 那几列(name/industry/region/ * title/email)本版起**都在下方 declarations.ledger 里显式声明** * (授权面是台账声明;从前只在说明书里出现、声明里没有,是一处会撒谎的注释)。 * `items` 不声明单一形状 —— 字符串与对象二选一,声明成 object 会让测试台的 * 类型提示(object[])与首选用法(字符串数组)相悖。 */ data: { type: 'object', required: ['trigger'], properties: { trigger: { type: 'string', enum: ['monthly', 'event'], enumNames: ['月度例行(常规覆盖轮次)', '事件驱动'], title: '触发方式', }, eventType: { type: 'string', enum: ['new_customer', 'key_person_change', 'major_deal', 't04_project'], enumNames: ['新客户建立合作', '关键人离职/调岗', '重大商机启动', '攻坚项目(T04)立项'], title: '事件类型(仅事件驱动时选)', showWhen: { field: 'trigger', equals: 'event' }, }, customers: { type: 'array', title: '本轮涉及的客户 —— 已有客户填客户编码字符串;若事件类型是"新客户建立合作",' + '改填新客户档案对象(公司名等 L-02 字段,不含客户编码——那一格由人在录入卡上现场给)。留空 = 月度例行,AI按台账规则从整个客户库筛选候选', examples: [ ['CU-0142'], [{ name: '青岛新星电子有限公司', custType: '模组厂', industry: '车规电子', region: '山东', contact: { name: '王芳', title: '采购经理', email: 'wang.fang@example.com' } }], ], }, note: { type: 'string', title: '本轮说明(选填,背景信息 —— 帮AI判断覆盖深度与经营侧重)', examples: ['客户 CU-0142 启动新项目,商机金额较大,AM Lead 要求本轮优先覆盖其决策链'], }, }, }, messageAllowed: true, messageHint: 'AM Lead 可在此说明本轮覆盖的特殊要求、优先客户、或需要重点关注的关键人变动', }, output: { /* * 单一必发口,无分支:E04 全程线性(①→②③④→⑤⑥→⑦),没有像 A1 那样 * 「有没有 C 类」这种要按序求值的岔路。E05/E06 在本仓库尚未实现,暂无消费者 —— * 按「契约先立、实现后补」的纪律先声明,不因为没有下游就不发。 */ mandatory: [ { name: 'coverage-map', description: '本轮更新后的关系覆盖地图 —— 供未来 E05互动与拜访 / E06账户计划读取;当前代码库尚未实现下游活动,此口暂无消费者,仅按"载荷=已落地事实"的契约纪律预先声明', payload: { type: 'object', required: ['roundRef', 'coveredCustomerTotal', 'keyPersonCoveredTotal'], properties: { roundRef: { type: 'string', title: '覆盖轮次编号' }, coveredCustomerTotal: { type: 'integer', title: '本轮覆盖客户数' }, keyPersonCoveredTotal: { type: 'integer', title: '本轮覆盖关键人数' }, }, }, }, ], selective: [], }, /* * 四份必交付物,逐一对应四张工单产出的成果。全部 `editableAtRelease: []`—— * 业务说明明文"验收工单只读展示,不可修改"(与 A1 放行时允许改 type_tag 是刻意对比, * 不是遗漏:E04 的验收工单从设计上就没有"就地修订"这一环)。 */ requiredDeliverables: [ { key: 'coverage-scope', label: '覆盖范围客户清单', ledger: `${D}.candidate`, ledgers: [`${D}.candidate`, `${D}.verdict`], editableAtRelease: [], }, { key: 'coverage-plan', label: '关键人经营计划', ledger: `${D}.plan`, editableAtRelease: [] }, { key: 'coverage-receipt', label: '执行回执', ledger: `${D}.receipt`, editableAtRelease: [] }, { key: 'ledger-update', label: '台账更新明细', ledger: `${D}.ledger_update`, ledgers: [`${D}.ledger_update`, `${D}.verdict`], editableAtRelease: [], }, ], /* * 验收判据 —— **机检(L1/L2)只读基座数得出来的数**:`_rows`/`_missing` 基座现数, * 与执行卡的提交闸同一个数同一套算法。L4 三条是纯裁量(有没有依据、数值合不合理、 * 有没有张冠李戴)——机器数不出「这条更新站不站得住」,那是人对结果负责的地方。 */ acceptanceCriteria: [ /* * 两条 L1 的「人过了没有」一半仍靠记账旗(scope_confirmed_total / plan_approved_total, * 只有人回卡之后模型才记 —— 与 A1 的 confirmed_total 同款,规范 §5.3-4 背书的人环凭据); * 但**每条都必须再钉一半基座数**:旗是模型写的,单独成谓词就是「机检只读基座数」 * 第 5 类违反的伪装形 —— 抢跑写旗那单(2026-08-22 活单熔断)正是踩着这半边过的门。 */ { key: 'scope-confirmed', label: '覆盖范围已经 AM Lead 核对确认(执行工单1已交回,清单有行且必填齐)', level: 'L1', predicate: `scope_confirmed_total > 0 and ${D}_candidate_rows > 0 and ${D}_candidate_missing = 0`, }, { key: 'plan-approved', label: '关系经营计划已经 AM Lead 审批通过(验收工单1已过,不处在驳回待改状态)', level: 'L1', predicate: `plan_approved_total > 0 and ${D}_plan_rows > 0 and ${D}_plan_missing = 0`, }, { key: 'plan-complete', label: '经营计划逐条完整:关键人、建议动作、优先级、理由、时间点都有', level: 'L2', predicate: `${D}_plan_rows > 0 and ${D}_plan_missing = 0`, }, { key: 'receipt-matches-plan', label: '每条计划都收到执行回执,且完成状态、结果摘要、关键人变动均已填写', level: 'L2', predicate: `${D}_receipt_rows > 0 and ${D}_receipt_rows = ${D}_plan_rows and ${D}_receipt_missing = 0`, }, { key: 'ledger-update-complete', label: '台账更新明细逐条完整:每条都写明台账、字段、更新前、更新后、依据', level: 'L2', predicate: `${D}_ledger_update_rows > 0 and ${D}_ledger_update_missing = 0`, }, { key: 'no-missed-update', label: "每条「已完成」的执行回执至少有一条对应的台账更新(没有漏更)", level: 'L2', predicate: `${D}_ledger_update_rows >= ${D}_receipt_completion_status_done`, }, { key: 'update-has-evidence', label: '每条台账更新都能在执行回执里找到对应事实依据,不是凭空推算', level: 'L4', }, { key: 'values-reasonable', label: '更新后的数值(覆盖度、亲密度等)变化合理,没有异常跳变', level: 'L4' }, { key: 'no-wrong-update', label: '更新内容准确对应回执事实,没有张冠李戴或误更别的客户/联系人', level: 'L4' }, /* L5 是责任关:那张卡本身就是这道关,卡面上不摆它(基座统一滤) */ { key: 'owner-release', label: '台账更新入库放行:签下去即正式生效并回写主数据', level: 'L5' }, ], revisableAtRelease: [], prohibitions: [ { key: 'no-guess-contact', statement: '决策链节点对应哪位联系人不确定时不得臆断 —— 匹配不上就留空,那就是缺口' }, { key: 'no-skip-evidence', statement: '台账更新明细每条都要有回执依据,不得凭空推算写入主数据' }, { key: 'no-external-write', statement: '放行前不得触达外部系统 —— 触达世界的动作集中在放行那一刻' }, ], authorization: { discretion: [], preauthorizedActions: [], /* * 基线四次(S2/S4通过/S5/T_release)+ 决策卡循环预留两轮(每轮驳回+重提交各算一次 * 人回复,两轮就是 +4)——**基座按 `used >= cap` 判耗尽**(实测栽过:6 次刚好等于 * 6 的上限,「裁决:pass」那次回复本身就是第 6 次,harnessCheck 在 land() 之前 * 就判耗尽,放行卡显示 9/9 达标、验收也点了通过,任务却终态 exhausted、草稿从没 * 合并进正式版)。留够余量,cap 必须严格大于「最后一次拍板」时的实际次数。 */ humanCallBudget: 12, }, resources: { tools: [{ name: 'crm-relationship-coverage.recompute-score' }, { name: 'crm-relationship-coverage.rank-key-persons' }], skills: COVERAGE_SKILLS.map((s) => s.id), models: ['deepseek-v4-flash'], roles: ['AM Lead'], /* 月度关系维护不是 live-inbox 那种时间敏感的单;人环预算同上调宽到 12(见上注) */ budget: { steps: 80, tokens: 500_000, activeMinutes: 90, humanCalls: 12 }, }, }, /* ── 声明 ────────────────────────────────────────────────────── */ declarations: { models: ['deepseek-v4-flash'], tools: [{ name: 'crm-relationship-coverage.recompute-score' }, { name: 'crm-relationship-coverage.rank-key-persons' }], ledger: [ /* * ── 主数据追加列(kind:'master' 显式声明,纯 add-column,与 A1 现有声明并存)── * 只声明本活动要用的新列;`customer_code` 只是回显/关联用途的重复声明, * 平台已由 `customer.profile`/`customer.contact` 的全量结构持有主键。 */ { domain: 'customer', object: 'profile', kind: 'master', label: '客户档案', fields: [ { name: 'customer_code', type: 'text', primaryKey: true, masked: false, label: '客户编码' }, /* * 建档卡会写的那几列(name/industry/region)也要声明 —— **授权面是台账声明**: * 说明书让录入卡落什么,声明里就得有什么(物理列平台基线本就有,声明是权责面)。 * 与 A1 的 name 声明并存(add-column 合并,同名同型不冲突)。 */ { name: 'name', type: 'text', primaryKey: false, masked: false, label: '客户名称' }, { name: 'industry', type: 'text', primaryKey: false, masked: false, label: '所属行业' }, { name: 'region', type: 'text', primaryKey: false, masked: false, label: '区域' }, /* * L-02 文档明列的字段,权威维护者是尚未建成的 T02(客户建档评估) —— 平时 * E04 不写它;唯一的例外是"新客户建立合作"这一刻,E04 代替不存在的 T02 * 履行建档职责(与 A1 quote-intake 的 S1_customer 完全同构),此时由数据 * 录入卡收人现场给的值。不带 tally——存文档给的中文枚举字面值,不再造一套 * ASCII 编码来回译(这一列从不参与 data-class-of 徽标染色,没有 ASCII 约束)。 */ { name: 'cust_type', type: 'text', primaryKey: false, masked: false, expect: true, label: '客户类型' }, { name: 'coverage_score', type: 'integer', primaryKey: false, masked: false, required: true, label: '关系覆盖度' }, /* 结构:{nodes:[{node, btLevel, required, contactCode, covered}]} —— 见 case-model.ts 的 guide 注 */ { name: 'decision_chain', type: 'json', primaryKey: false, masked: false, required: true, label: '决策链地图' }, /* 结构:{entries:[{date, type, contactCode, note}]},append-only(每次整个数组重写) */ { name: 'interaction_timeline', type: 'json', primaryKey: false, masked: false, expect: true, label: '互动时间线' }, ], }, { domain: 'customer', object: 'contact', kind: 'master', label: '客户联系人', fields: [ { name: 'customer_code', type: 'text', primaryKey: true, masked: false, label: '客户编码' }, /* * `auto:true` **必须跟 A1 一致**(2026-08-20 修死):`autoKeys()` * (`apps/api/src/app/routes/desk.ts`)按**当前任务自己所属活动**的声明判断 * 哪几列自增,不是按全仓合并后的物理表结构 —— E04 若只抄字段名不抄这个标记, * E04 名下的任务发数据录入卡建新联系人时,`contact_code` 就悄悄不再自动生成 * (静默退化成留空,不报错)。 */ { name: 'contact_code', type: 'text', primaryKey: true, masked: false, auto: true, label: '联系人编码' }, /* 建档卡的联系人段会写的三列 —— 同上,声明即授权面;email 照平台口径标脱敏 */ { name: 'name', type: 'text', primaryKey: false, masked: false, label: '姓名' }, { name: 'title', type: 'text', primaryKey: false, masked: false, label: '职务' }, { name: 'email', type: 'text', primaryKey: false, masked: true, label: '邮箱' }, { name: 'role', type: 'text', primaryKey: false, masked: false, expect: true, label: '角色' }, { name: 'decision_node', type: 'text', primaryKey: false, masked: false, expect: true, label: '决策链节点' }, { name: 'influence', type: 'text', primaryKey: false, masked: false, required: true, tally: ['high', 'medium', 'low'], label: '影响力评级', }, { name: 'relationship', type: 'integer', primaryKey: false, masked: false, required: true, label: '关系亲密度(1-5)' }, { name: 'comm_pref', type: 'text', primaryKey: false, masked: false, expect: true, label: '沟通偏好' }, { name: 'key_person', type: 'boolean', primaryKey: false, masked: false, required: true, label: '关键人标记' }, /* 我方追加的批注(入职/离职/调岗留痕),不标 expect —— 标了会让 _gaps 永远大于零 */ { name: 'change_log', type: 'text', primaryKey: false, masked: false, label: '变动记录' }, ], }, /* ── E04 自有工作台账(kind:'record',caseKey:'round_ref') ── */ { domain: D, object: 'candidate', kind: 'record', caseKey: 'round_ref', label: '候选覆盖客户清单', fields: [ { name: 'round_ref', type: 'text', primaryKey: true, masked: false, label: '覆盖轮次编号' }, { name: 'customer_code', type: 'text', primaryKey: true, masked: false, required: true, label: '客户编码' }, { name: 'customer_name', type: 'text', primaryKey: false, masked: false, required: true, label: '客户名称' }, /* 本轮工作快照 —— 有 customer.profile.cust_type 就抄它,没有才按台账信号猜(见 guide) */ { name: 'cust_type', type: 'text', primaryKey: false, masked: false, expect: true, label: '客户类型' }, { name: 'bt_level', type: 'text', primaryKey: false, masked: false, expect: true, label: 'BT 层级' }, { name: 'status', type: 'text', primaryKey: false, masked: false, tally: ['pending', 'covered', 'excluded'], label: '状态' }, { name: 'basis', type: 'text', primaryKey: false, masked: false, expect: true, label: '纳入依据' }, { name: 'depth', type: 'text', primaryKey: false, masked: false, expect: true, label: '覆盖深度' }, { name: 'origin', type: 'text', primaryKey: false, masked: false, label: '来源(ai/human-added)' }, ], }, { domain: D, object: 'plan', kind: 'record', caseKey: 'round_ref', label: '关键人经营计划', fields: [ { name: 'round_ref', type: 'text', primaryKey: true, masked: false, label: '覆盖轮次编号' }, { name: 'contact_code', type: 'text', primaryKey: true, masked: false, label: '联系人编码' }, { name: 'customer_code', type: 'text', primaryKey: false, masked: false, required: true, label: '客户编码' }, { name: 'customer_name', type: 'text', primaryKey: false, masked: false, expect: true, label: '客户名称' }, { name: 'contact_name', type: 'text', primaryKey: false, masked: false, expect: true, label: '联系人姓名' }, { name: 'action', type: 'text', primaryKey: false, masked: false, required: true, label: '建议动作' }, { name: 'priority', type: 'text', primaryKey: false, masked: false, required: true, tally: ['high', 'medium', 'low'], label: '优先级', }, { name: 'rationale', type: 'text', primaryKey: false, masked: false, required: true, label: '理由' }, { name: 'influence', type: 'text', primaryKey: false, masked: false, required: true, label: '影响力(写计划那一刻的快照)' }, { name: 'relationship_gap', type: 'integer', primaryKey: false, masked: false, required: true, label: '亲密度缺口' }, { name: 'rank_score', type: 'number', primaryKey: false, masked: false, required: true, label: '排序分(模版不排序,须已按此列降序写入)' }, { name: 'target_date', type: 'date', primaryKey: false, masked: false, required: true, label: '建议动作时间点' }, /* * 没有 status 列(2.0.0 撤):tally 里的 approved 从没人写过 —— 「计划过没过审」 * 由 plan_approved_total 一个数说了算,同一件事实两处各存一份就会分叉。 */ ], }, { /* * 与 `plan` 分表:回执列若挤进 `plan`,`required` 是对象级全局声明 —— * S3/S4 阶段的 `_missing` 会把还没执行的回执列也算成「缺」,doneWhen/L2 判据 * 永远打不开。S3 那一步顺手把 action/target_date 回填进来,供 S5 工单只读展示 * (单张表内混排只读+可编辑列,复用工作区渲染器 mine:域.对象 单地址绑定的既有能力)。 */ domain: D, object: 'receipt', kind: 'record', caseKey: 'round_ref', label: '经营计划执行回执', fields: [ { name: 'round_ref', type: 'text', primaryKey: true, masked: false, label: '覆盖轮次编号' }, { name: 'contact_code', type: 'text', primaryKey: true, masked: false, label: '联系人编码(与计划一一对应)' }, { name: 'customer_code', type: 'text', primaryKey: false, masked: false, expect: true, label: '客户编码' }, /* 目标件的「对象」列长「AvtoKom · 产品 VP」—— 客户名与人名都要在,S3 从计划抄 */ { name: 'customer_name', type: 'text', primaryKey: false, masked: false, label: '客户名称(承继自计划,只读展示)' }, { name: 'contact_name', type: 'text', primaryKey: false, masked: false, expect: true, label: '联系人姓名' }, { name: 'action', type: 'text', primaryKey: false, masked: false, label: '计划动作(承继自计划,只读展示)' }, { name: 'target_date', type: 'date', primaryKey: false, masked: false, label: '建议时间点(承继自计划,只读展示)' }, { name: 'completion_status', type: 'text', primaryKey: false, masked: false, required: true, tally: ['done', 'partial', 'skipped'], label: '完成状态', }, { name: 'result_summary', type: 'text', primaryKey: false, masked: false, required: true, label: '结果摘要' }, { name: 'customer_feedback', type: 'text', primaryKey: false, masked: false, expect: true, label: '客户反馈' }, { name: 'key_person_change', type: 'text', primaryKey: false, masked: false, required: true, tally: ['changed', 'unchanged'], label: '关键人变动', }, ], }, { domain: D, object: 'ledger_update', kind: 'record', caseKey: 'round_ref', label: '台账更新明细', fields: [ { name: 'round_ref', type: 'text', primaryKey: true, masked: false, label: '覆盖轮次编号' }, { name: 'update_seq', type: 'integer', primaryKey: true, masked: false, label: '本轮更新序号' }, { name: 'ledger_label', type: 'text', primaryKey: false, masked: false, required: true, label: '台账(人读名)' }, { name: 'ledger_address', type: 'text', primaryKey: false, masked: false, required: true, label: '台账地址(机器地址)' }, { name: 'customer_code', type: 'text', primaryKey: false, masked: false, required: true, label: '客户编码' }, { name: 'contact_code', type: 'text', primaryKey: false, masked: false, expect: true, label: '联系人编码(空=客户级字段)' }, { name: 'field_label', type: 'text', primaryKey: false, masked: false, required: true, label: '字段(人读列名)' }, { name: 'before_value', type: 'text', primaryKey: false, masked: false, required: true, label: '更新前(无值写"(无)")' }, { name: 'after_value', type: 'text', primaryKey: false, masked: false, required: true, label: '更新后' }, { name: 'evidence_ref', type: 'text', primaryKey: false, masked: false, required: true, label: '依据(引用具体回执事实)' }, ], }, { domain: D, object: 'verdict', kind: 'record', caseKey: 'round_ref', label: '本轮覆盖结论', fields: [ { name: 'round_ref', type: 'text', primaryKey: true, masked: false, label: '覆盖轮次编号' }, /* * 列名 `trigger_label` 不叫 `trigger`(2.0.0 改):任务视图把草稿列摊平到顶层、 * 与起单入参**同名同位** —— 从前这一列就叫 trigger,S1 写下中文标签之前, * 视图上 `data-slot="trigger"` 摆的是入参英文码(monthly/event), * 「报告上不摆系统内英文名」在首帧上破了功。改名后绑定读 trigger_label, * 写下之前那一格真空着(data-blank),空着是诚实的。 */ { name: 'trigger_label', type: 'text', primaryKey: false, masked: false, label: '触发方式(人读标签,非入参英文码)' }, { name: 'event_type', type: 'text', primaryKey: false, masked: false, label: '事件类型(入参原样抄的人读标签,月度例行留空)' }, { name: 'note', type: 'text', primaryKey: false, masked: false, label: '本轮说明' }, { name: 'scope_confirmed_total', type: 'integer', primaryKey: false, masked: false, label: '覆盖范围已确认' }, { name: 'plan_approved_total', type: 'integer', primaryKey: false, masked: false, label: '经营计划已审批通过' }, /* * 没有 receipt_confirmed_total(2.0.2 退役,两轮活单熔断实测):模型自写的 * 「人环走完」旗让它抢跑 —— S5 的门改读基座数(receipt _rows/_missing, * 回执四列全是人在件上填出来的),被叫的那一方无账可记。 */ { name: 'ledger_update_confirmed_total', type: 'integer', primaryKey: false, masked: false, label: '台账更新已收口' }, { name: 'covered_customer_total', type: 'integer', primaryKey: false, masked: false, label: '本轮覆盖客户数' }, { name: 'key_person_covered_total', type: 'integer', primaryKey: false, masked: false, label: '本轮覆盖关键人数' }, { name: 'summary', type: 'text', primaryKey: false, masked: false, label: '本轮覆盖结论' }, ], }, ], listeners: [], harness: { memoryWindow: 24, /* 月度关系维护不是 live-inbox 那种紧迫的单,给够时间让人执行拜访这类线下动作 */ stallAfter: 'PT4H', nudge: [{ after: 'PT2H', action: 'nudge' }], retry: { times: 2, backoff: 'PT30S' }, }, }, /* ── 资产初始版 ──────────────────────────────────────────────── */ assets: { businessModel: { cmmnXml: CMMN_XML, narrative: NARRATIVE, dmn: EMPTY_DMN, steps: COVERAGE_STEPS, phases: COVERAGE_PHASES, }, skills: COVERAGE_SKILLS, /* * 四个局面各一张: * · `coverage-scope` —— S1/S2 摆,候选客户清单,可编辑; * · `coverage-plan` —— S3/S4 摆,经营计划,只读(业务说明明文"人只做审批,不能修改结论"); * · `coverage-receipt` —— S5 摆,执行回执,混排只读(计划动作/时间点)+ 可编辑(回执四列); * · `coverage-ledger-update` —— S6/T_release 摆,台账更新明细,只读,收口后即归档件。 */ viewTemplates: [ { name: 'coverage-scope', html: COVERAGE_SCOPE }, { name: 'coverage-plan', html: COVERAGE_PLAN }, { name: 'coverage-receipt', html: COVERAGE_RECEIPT }, { name: 'coverage-ledger-update', html: COVERAGE_LEDGER_UPDATE }, ], cardSchemas: {}, distillHooks: [ { on: 'terminal', prompt: '归纳本轮关系覆盖:候选客户识别依据、计划审批是否被驳回过(为什么)、执行回执里客户反馈最集中的诉求、覆盖度变化幅度。', }, ], }, /* * ── 样例集 —— 五条形状真不同(常备案例即验收案例)───────────── * * **第一条必须是满载的那条**(2026-08-23 用户:「询价受理起单时左边都有预填好的入参, * E04 也要」):测试台落到一个活动上就自动铺 `samples[0]`(activity-run.tsx 的 * useEffect),所以第一条就是人打开这一屏看见的那份入参。从前第一条是月度例行、 * 只有 `{trigger:'monthly'}` 一个键 —— 表单上除了那个下拉全是空的,看着像没预填。 * 现在第一条是**点名客户的事件驱动**:四个字段全有值,起单钮按下去就能跑。 * * **样例名一律 ≤14 字**:测试台的样例钮超过 14 字就退成「样例 N」(activity-run.tsx), * 那等于没名字 —— 人得逐个点开才知道哪条是哪条。 */ samples: [ { name: '重大商机 · 点名客户', input: { trigger: 'event', eventType: 'major_deal', customers: ['CU-0142'], note: '客户 CU-0142(Itelma)启动新项目 MD14.2,商机金额较大,AM Lead 要求本轮优先覆盖其决策链', }, expect: { ports: ['coverage-map'] }, }, { /* 月度例行的本分就是不点名(客户格留空 = 由 AI 按台账规则筛)—— 但背景说明要给,不然表单半空 */ name: '月度例行 · 全库筛', input: { trigger: 'monthly', note: '八月例行覆盖轮次:优先看覆盖度陈旧、决策链有空白节点的重点客户', }, expect: { ports: ['coverage-map'] }, }, { name: '关键人离职调岗', input: { trigger: 'event', eventType: 'key_person_change', customers: ['CU-0087'], note: '客户 CU-0087 采购负责人 Chen Gang 已离职,需要重新识别关键人并更新决策链', }, expect: { ports: ['coverage-map'] }, }, { name: '新客户建立合作', input: { trigger: 'event', eventType: 'new_customer', customers: [ { name: '青岛新星电子有限公司', custType: '模组厂', industry: '车规电子', region: '山东', profileTags: '价格敏感 · 首次合作', contact: { name: '王芳', title: '采购经理', email: 'wang.fang@example.com' }, }, ], note: 'AM Lead 上周展会新结识的客户,想尽快建档并启动关系覆盖', }, expect: { ports: ['coverage-map'] }, }, { name: '攻坚项目 T04 立项', input: { trigger: 'event', eventType: 't04_project', customers: ['CU-0142'], note: '客户 CU-0142(Itelma)的 MD14.2 项目已升级为 T04 攻坚项目,需要加强决策链覆盖、确保关键节点无缺口', }, expect: { ports: ['coverage-map'] }, }, ], entry: { file: 'index.js', checksum: 'dev-entry-crm-relationship-coverage' }, };