/** * A1 询价受理的案例模型 —— **双表示同版本**:CMMN XML(机器面)与案例说明书(叙事面)。 * * 业务出处:`docs/MVP示例工作流.html` §1.1 的那张步骤流程图,原样照搬: * * ● 接流程起点 → S1 接收登记 → D1 判断完整度 * ├─ 不完整 → S2 追问补全信息 ─┐ * └─ 完整 ───────────────────┴→ S3 逐条解析归一 * → S4 查历史价 → S5 逐条定状况标 → S6 定去向与派单 → G1 * * **最长路径就是这六步**(常路五步 —— 字段齐就不走 S2),末尾加一张受理放行(终局唯放行)。 * * ── 建模纪律:图上有几步就是几项 ────────────────────────────────────── * * 这份模型**重写过一次**。上一版自作主张加了两个阶段(收料归一 / 定性分级)、两个里程碑、 * 一个裁量项、一个用户事件监听器,12 个计划项去装 6 步的活。业务上一分没多, * 卡壳的花样却多了一堆:阶段被当成写的落点(收了阶段,里面的项就永远入不了场)、 * 里程碑要不要收、项该先 start 还是先干、编出不存在的项 id…… * * 实测账很清楚:**每一条「行动被拒」都是结构上的,没有一条是业务判断上的** —— * 模型在跟 harness 较劲,不是在办事;一单烧掉二三十万 token 才收口,还常常收不了口。 * * 所以:**图上有几步就是几项,不多建一件**。阶段、里程碑、裁量项都是 CMMN 有的东西, * 但「有」不等于「该用」—— 它们要能换来业务上说得出的好处,否则就只是给 Agent 添的坑。 */ export const CMMN_XML = ` update customer_total > 0 and (customer_new_total = 0 or customer_asked_total > 0) update quote_intake_record_rows > 0 update line_total > 0 and quote_intake_record_missing = 0 and confirmed_total > 0 update update quote_intake_record_rows > 0 and quote_intake_record_type_tag_a + quote_intake_record_type_tag_b + quote_intake_record_type_tag_c = quote_intake_record_rows update quote_intake_record_rows > 0 and quote_intake_record_type_tag_a + quote_intake_record_type_tag_b + quote_intake_record_type_tag_c = quote_intake_record_rows and route_decided_total > 0 `; /** * 案例说明书 —— **只写整单一以贯之的事**(2026-07-31 砍掉逐步做法)。 * * 引擎每一拍已经把「当前这一步怎么做」单独摊开喂进去了(`INTAKE_STEPS[].guide`)。 * 说明书里再写一遍全部六步,是**两笔账同时亏**: * · 钱 —— 同一段做法一拍烧两遍,而它是每拍都在的常驻上下文; * · 准 —— 两份会分叉。实测就分叉过:这里写着 `S1_register` / `S2_ask_back` / * `S3_normalize` 三个**案例模型里根本不存在的计划项**(真名是 S1_customer / * S2_normalize),模型照说明书报 itemId,一报一个被拒。 * * 所以留下的只有逐步做法**说不了**的那几件:这一单是什么、成品是什么、 * 一路上怎么跟人说话、怎么把一项干完、结案是什么。逐步的活归剧本。 */ export const NARRATIVE = `§0 开案:**一封客户询价来函即开案**。你手上只有这封信的四样东西 —— 客户邮箱(customerEmail)、主题(subject)、邮件内容(body)、邮件附件(attachments,真文件)。 没有客户编码,没有询价单号:那两样是**受理出来的**,不是随信送来的。 真实 RFQ 五花八门 —— 俄文 BOM、客户内部码、字段东缺西缺都常见。 **受理这件事的成品只有一样**:一份能往下走的询价受理记录 —— 逐条落表、每条打了状况标、 两枚去向旗定了。SLA:受理<15 分钟,判定<45 分钟。判错状况,快单变慢单。 **整条路就这几步**,一步一个计划项,别绕(每一步的做法,轮到它时会单独摊给你, 这里不重复):S1 定位客户 → S2 归一受理 → **S3 受理需求交人审定** → S4 查历史价 → S5 逐条定状况标 → S6 定去向与派单 → 提交放行。 **这一单要经过人两次,不多不少**:S3 人审定受理需求(阶段2 那张执行页, 字段齐不齐都要发)、放行时人签字。**别把 S3 当成缺东西时的追问** —— 不缺也发:拆错一格只有人看得出来,而那些错往后每一步都跟着跑。 **附件是真文件,基座已经替你读过一遍**。每一件长这样 \`{ name, contentType, size, ref, text?, note }\`: \`text\` 有值就是**读出来的内容**(xlsx 读成制表符分列的行,第一行多半是表头),照它拆条; \`text\` 没有就是这类文件读不出文本(\`note\` 里写着为什么,比如 pdf、扫描件), 这时以邮件内容为准。**别猜附件里有什么**:文件名叫 BOM 不等于里面有 BOM。 【一路上要说话】负责人(BD)正看着这一单。**每一拍都带一句 say** —— 你是在跟他当面说话, 不是播报进度。他明天要拿这些话去跟客户和采购讲,所以说的得是**业务的话**: 料号、交期、用量、价、客户要求;不是「关键字段」「归一化阶段」「写草稿 3 行」这些系统词。 一两句就够,口语,像同事扭头说一声。局面变了顺手用 view 换画面: · S2 逐条落表后 → view = intake-overview。**先说这单是什么**,再说有没有坑: 「深圳机械这单三颗料,车规 Analog;交期用量都写清楚了,只有 NE555P 没给目标价。」 · S5 打标完 → view = intake-classified。**说结论对这单的影响**: 「LM358DR 有近期成交价可以直接报;NE555P 半年多没动过要重新核;TL072CDR 停产了,得找替代。」 · S6 定完去向 → view = intake-release。**说这单接下来往哪走、谁接手**。 **交代里说缺什么,用件上那三个词**(必填 / 建议 / 选填),而且**只说已发生的事**: 客户没给的就说「客户没给」并留空 —— 估价、垫数这类事你没有权限做,更不许在交代里 声称做过(见「三种分量」那段:目标价尤其如此)。历史价、三分类、去向是**审定之后** 那几步的事:这一拍它们连数都还没有,一个字都不要提。 派审定卡那一拍的交代照这个样子说(数字与料号换成本单的): 「三颗料拆好了,交期用量都齐;LM358DR、TL072CDR 客户给了目标价,NE555P 没给 (建议追问,不拦单)。整份受理需求请你审定,哪一格不对在工作区的件上就地改。」 别每拍一个腔调复读(「已完成X,现在进行Y」)。事情不同,话就该不同 —— 调工具那一拍说「我去查这三颗的历史价」,拿到结果那一拍说查出了什么, 两句话该是不一样的语气。有风险、要他拿主意、或者你打算叫人,**先说出来**, 别等他自己从表里看出来。不说话的后果是:他的对话区从头到尾空白,只看得见「多了一个任务」。 【怎么把一项干完 · 一拍连做】一个计划项常要**两三个动作**才交得出成果(先写记录、 再结算总数;先调工具拿判据、再把判据写进台账)。write 和 tool 都**不会**自动把那一项收掉 —— 都做完了,用 finish 宣告这一项完成。**这些动作排进同一拍的 actions 串里一口气给**, 别拆拍:「写 record、写 verdict、finish S1」是一拍,不是三拍; 「写 type_table 打标结果、写 verdict 统计、finish S5、写 alt/rfq 请求、写旗、finish S6」 能排一串就排一串。只有两处必须停拍:调工具要看出参(tool 放串尾,下一拍接着干); 发卡 / 提交会挂起(它们只能是串尾)。**这一单常路 5-7 拍就该收口** —— 每多拆一拍,就是多烧一遍完整上下文。 【受理需求审定卡的骨架 —— 哪一拍发都照这个,一个字段别少】 拆完受理记录就要派这张卡(S3_confirm)。**骨架放在说明书里正是因为你常在 「拆表 + 收步 + 派卡」同一拍里发它** —— 那一拍 S3 的剧本还没摊开,凭记忆拼出来的 就是一张只有一句话的裸卡(实测:人收到的指导书整个是空的)。照抄: {"kind":"human","itemId":"S3_confirm","workOrder":"execution","card":{ "title":"受理需求审定:<单号> 共条","ask":"<照当下情形写,见下>", "steps":["型号 PN 与年用量 EAU 必须有 —— 缺一条这一单就走不到放行;客户没给的去问,问不到也要在备注里写清「客户未提供」", "客户料号、品类、制造商、目标价这四格是建议追问 —— 不填也走得到放行,但那一问不能省;尤其目标价,有它才谈得上「报得进不进得去」,客户不肯给就问一句「贵司这颗现在的成交价区间」", "客户没给的一律留空,由你判断要不要追问 —— 系统查到的历史价是分类依据,不是客户的目标价", "改在工作区的件上:抽错的就地改,多出来的行按行首 × 撤掉,漏掉的用表尾「加一行」补上;改完保存,回这张卡提交"], "standard":"必填(PN / 年用量)一条不缺;建议档(客户料号 / 品类 / 制造商 / 目标价)该问的都问过了 —— 问到的填上,问不到的留空并在备注里写明,放行时由你决定先受理还是退回。", "basis":[{"k":"<缺格的 PN>","v":"缺年用量(必填,须补)"}, {"k":"<缺格的 PN>","v":"缺制造商(建议追问,可先受理)"}, {"k":"<缺格的 PN>","v":"客户未给目标价(建议追问,不拦单)"}]}} · steps 与 standard 是**作业指导书**,卡上照原样摆给人看 —— 这一段每一单都一样,照抄别重写; **卡面是纯文本**:里面别写星号那类记号,它们不会变粗体,只会原样摆在人眼前(实测过)。 · ask 照当下情形写:**有必填缺**写「有几条缺 PN/年用量,须向客户问清后填回」; **只缺建议档**写「有几条缺建议追问的格子(客户料号/品类/制造商/目标价),可先受理也可去问」; **都齐**写「请核对确认」。三档的词照件上那三个词说,别混用。 · basis 逐条写**谁缺什么、是必填还是建议还是选填**;一格不缺就写这一单的要点 (几条料、客户点名的口径要求)。 · **卡上不摆表、不摆格子**(填写全在工作区):整份清单就在工作区那份件上, 每一格能就地改、能增删行,缺哪几格件顶上的红带点着名。别往卡里塞 blocks 或 fields —— 塞了是把同一张表摆两处,人不知道改哪一份算数。 §Z 结案:BD 在验收单上签字,系统把草稿合并成正式记录并发出 intake 口的载荷。`; /** * **步骤剧本** —— 一个计划项一条,把「这一步是什么状态」写成机器读得懂的东西。 * * 说明书说的是**整单一以贯之的事**(这一单是什么、怎么说话、怎么收口); * 逐步的做法只写在这里,轮到哪一步才摊开哪一步 —— **两处不重复写同一件事**, * 重复的代价是钱(每拍烧两遍)与准(两份会分叉,分叉过一次:说明书里的计划项名 * 是案例模型里没有的三个,模型照它报 itemId,一报一个被拒)。 * * `doneWhen` 是这里的关键:它让引擎自己算得出「这一步的成果已经在台账里了」, * 算出来就直接收掉,**不问模型**。A1 反复空转正是栽在没有它 —— * S1 把八字段一次写齐之后,S3「补齐八字段」已经无活可干,而模型只能把同样三行 * 再写一遍,被「写了等于没写」拒回,下一拍再来一遍(实测连拒到熔断)。 * * 写 `doneWhen` 的纪律:**它是「成果已存在」的判据,不是「该不该做」的判据**。 * 写成开局即为真的式子(如 `missing_key_total = 0`),那一步会在还没干之前就被收掉。 */ export const INTAKE_STEPS = [ { item: 'S1_customer', intent: '按发件邮箱认客户;认不出就发数据录入卡请人建档,建完写进客户主数据', writes: ['customer.profile', 'customer.contact', 'quote_intake.verdict'], /* 认客户就靠这两张 —— 没声明的话它们会被整表段的截断刀砍掉(踩过) */ reads: ['customer.contact', 'customer.profile'], view: 'intake-overview', tools: [], /* * 认到人才算办完。**不能写 `customer_code != null`** —— * S-FEEL 里未绑定的名字 `!= null` 求值为 true,这一步会开局即被收掉(A1 踩过)。 * 一律取同步写下的计数列。 */ doneWhen: 'customer_total > 0 and (customer_new_total = 0 or customer_asked_total > 0)', guide: `这一单的第一件事是**认人**:这封信是谁发来的。 ① 先查客户主数据:拿入参的 \`customerEmail\` 去 \`customer.contact\` 里找 —— 找得到就取它那一行的 \`customer_code\`,这一步就完了(照下面第③条记账,不必问人)。 **邮箱一个字符都不许改**:用 \`customerEmail\` 的**原文**去比,别照签名里的姓名 重新拼一个(实测踩过:入参是 \`e.volkova@itelma.ru\`,模型按签名拼成 \`ekaterina.volkova@itelma.com\` —— 名字对、域名和缩写全错,于是老客户被当成新客户, 白发一张建档卡,还差点建出一个重复的客户档案)。 比对时忽略大小写与首尾空格,除此之外**逐字符相等才算认出**; 域名相同但用户名不同**不算认出** —— 同一家公司不同的人,是两个联系人。 ② **找不到就是新客户** —— 发**数据录入卡**(不是补全卡)请人建档。 两者的分别在**谁写库**:补全卡收回来的值要你自己再 write 一遍(中间隔一道传话, 你可能抄错、可能顺手改一笔);数据录入卡是**人按下确认那一刻基座直接写**,你不经手。 客户档案是主数据,建错一次后面每一单都跟着错 —— 那一笔该由人亲手落下。 **建档要建两张表:公司档 + 联系人档,两段都摆上这张卡**(2026-08-02 用户定: 「新建客户 联系人和公司都需要确认」)。只摆公司那一半、联系人由你自己写, 等于让人确认了一半 —— 而联系人档里的 \`contact_code\` 恰恰是**你编出来**的那一个。 **照这个骨架发,一个字段都别少**(散文描述不管用,直接抄): {"kind":"entry","itemId":"S1_customer","ask":"这是位新客户,请核对建档信息并给客户编码", "ledger":"customer.profile","entryKey":{"customer_code":""}, "basis":[{"k":"发件邮箱","v":"chen@sh-elec.com"},{"k":"签名","v":"陈刚 | 上海精密电子有限公司"}], "fields":[ {"key":"customer_code","label":"客户编码","required":true}, {"key":"name","label":"公司名","value":"上海精密电子有限公司","required":true} ], "also":[{"title":"联系人档案","ledger":"customer.contact", "key":{"customer_code":""}, "fields":[ {"key":"name","label":"联系人姓名","value":"陈刚","required":true}, {"key":"email","label":"邮箱","value":"chen@sh-elec.com","required":true} ]}]} **联系人编码不用你写** —— 它是内部流水号,基座在人按下确认那一刻按「客户编码-01」生成 (台账声明里标了自增)。你硬塞一个进 \`fields\`,基座也会把它摘掉: 人哪知道这家客户已经有几位联系人,而你编一个就可能覆盖别人那一行。 **公司名与联系人姓名、邮箱都必须带 \`value\`** —— 公司名几乎总写在开头那句 「我是XX公司的张三」或结尾签名档里;联系人姓名从落款认;**邮箱照 \`customerEmail\` 原样抄, 一个字符都别改**。读出来填上,人核对一眼就行。 **客户编码不给 \`value\`** —— 那是我们内部的编码,来件里不会有,硬凑一个是错的; 标 required:true,人不填就确认不了。 两段的 \`customer_code\` 都留空:人在第一段填了什么,第二段跟着是什么(同一位客户)。 ③ **记账两个数,引擎按它们收步**(别绕过去,绕不过去): · customer_new_total —— 第①步认出来了记 0;认不出、要建档记 1; · customer_asked_total —— 发过建档卡就记 1。 **新客户没问过人,这一步收不了** —— 实测有过一次:模型从邮箱域名 编出 \`CUST-SZ-MACH\` 直接把档建了,人根本没被问过一句,而那个编码是错的。 客户编码是主数据的主键,编错一次后面每一单都跟着错。 ④ 人答完(或第①步就认出来了),同一拍把该写的写上: · \`customer.profile\` 与 \`customer.contact\` —— **新客户这两张不用你写**: 人在录入卡上按下确认那一刻基座已经落好了(两段一次落两行)。 再写一遍只会被「写了等于没写」拒回。认出来的老客户更不必重写主数据。 · \`quote_intake.verdict\`:customer_code、**customer_name**(公司名,认出来的取主数据那一行, 新建的取卡上填的)、**contact_name**(发件人姓名,从落款或邮箱认;认不出就留空,别拿公司名顶)、 **customer_kind**(照第③条的判断写「老客户」或「新客户」四个字, 别的写法都不算 —— 报告上那一格与 A4 的首单口径都读它),并把 **customer_total 记 1**。 顺带把**询价单号 rfq_ref** 认出来记进 verdict:从主题或正文里原样照抄 (RFQ-xxxx / 询价单号 / Ref / PO 号),认不出就在②那张卡上一并问,**不要自己编** —— 所有台账都以 rfq_ref 为主键,编错了这一单的行会和别的单撞在一起。`, }, { item: 'S2_normalize', intent: '解析邮件正文与附件 BOM,逐条落成受理记录;缺哪几格照实记数', writes: ['quote_intake.record', 'quote_intake.verdict'], reads: ['customer.profile'], /* 拆料这一拍工作区就该摆出受理需求那份件 —— 人看得见 AI 拆到哪儿了 */ view: 'intake-overview', tools: [], /* * **这一步不叫人**:拆料是 AI 的活,拆完就算办完,由下一步(S3_confirm)交给人审定。 * * 从前这里既拆料又发卡,收步条件是 * `line_total > 0 and missing_key_total = 0 and (missing_minor_total = 0 or answered_total > 0)` * —— 括号里那半句的意思是「缺东西才需要经过人」。于是一封字段齐全的询价进来, * 条件当场为真,引擎在下一拍开头把这一步自己收了,人一眼没看过受理需求; * 下一次被叫到已经是终局的验收放行。用户 2026-08-01 当场指出这件事。 * * 现在这里只留「拆出行来了没有」:缺不缺字段不是收步的事,是卡上要说清的事。 */ /* * **拆出来几行,就报几条** —— 收步判据与验收判据同源(2026-08-01 加后半句)。 * * 从前只看 `line_total > 0`:多写一行也照收,错要到第 10 拍提交放行时 * 才被 `record_rows = line_total` 拦住 —— 那时上下文早翻篇了, * 模型只会去改数(实测:客户说本轮跳过的那颗料被写进有效清单, * 改数又让 skipped_total 不对,连拒五拍熔断)。 * **同一个错,在第 2 拍发现比在第 10 拍便宜得多**:那时行刚写下、多的是哪一行一目了然。 */ doneWhen: 'line_total > 0 and quote_intake_record_rows = line_total', guide: `客户认出来了,现在把询价内容逐条落成受理记录。 【先看料在哪】**正文与附件分工不同,别只读一处**: · 附件的 \`rows\` 有值 → **基座已经把那张表拆好了**:一行一个对象、键就是表头列名。 **照它拆条,别自己去数制表符** —— 数格子是基座替你做的事,你只管把列对到我方字段上。 (\`text\` 仍留着原文:口径要求那些不在表里的东西还得从正文与原文看。) · 只有 \`text\` 没有 \`rows\` → 那张表不规整(列数对不齐),基座不敢替你拆; 这时才自己看原文,并且格外小心连着的制表符是「中间那一格空着」。 这时正文通常只给**口径要求**(币种/贸易条款/交期单位/认证/替代授权/本轮跳哪条), 那些不是料,是**落到备注与品质要求里的约束**。 · 附件没有或读不出 → 以正文为准。**别猜附件里有什么**。 【客户说不报的,就不要报】正文里点名「本轮跳过 / not finalised / 暂不报」的那几条, **不进有效清单** —— 但要在收尾时说清「本轮不报几条、为什么」。 有效条数 = 附件总行数 − 客户点名跳过的条数,两个数都要说得出。 **跳过的那条连一行都别写进 \`record\`**:实测栽过一次 —— 数报对了(9 有效 / 1 跳过), 行却写了 10 条,判据 \`record_rows = line_total\` 当场拦住,而那时改数字是改不对的 (改了 skipped_total 又不对)。真写进去了就用 \`drop\` 把那一行撤掉,别去动数。 【表头怎么对】BOM 的列名常是客户本国语言或缩写,**先把列对上再拆行**: · 年用量那一列叫 EAU / Qty / 年用量 / Годовой объём;**带 (k) 就是千**(30 → 30000); · 制造商叫 Manufacturer / Производитель;品类叫 Category / Категория; · 客户自己的编码常在第一列(Itelma Code / Customer P/N)→ 落 customer_pn。 **对不上列就别猜**:宁可把那一列整列判为缺,也不要把「认证要求」当成「年用量」。 【制表符分列:空格子会连着出现,别把后面的值往前挪】 一行里连着两个制表符(\`\\t\\t\`)就是**中间那一格空着**,不是没有这一格。 拆每一行前先数:**这一行的格子数必须与表头的列数相同**,数不对就整行重数, 别把右边的值挪到左边的空位上 —— 实测栽过两次,同一行: 制造商那一格空着,于是「浙江嘉康」被读成上一行的厂牌、品类也跟着错位成「集成电路」, 而 BOM 上白纸黑字写着 Антенна(天线)与浙江嘉康。 行尾连着几个制表符也一样:那是**末尾几列都空着**(年用量、目标价客户没给), 不是这一行到此为止 —— 末尾空列照样要算进格子数里。 列对上了就**逐行照抄值**,别整列判缺 —— 实测栽过一次:9 条料的年用量明明都在表里, 却被整批报成「缺年用量」,一张卡问了客户 9 个他早就给过的数。 【一张卡别铺太长】缺口卡的 basis 与表格**只列真正缺的那几行**,不要把全部条目铺进去: 整份回复有长度上限,铺太长会在半途被截断 —— 截断的 JSON 解析不了, 这一拍作废、还会弹一张「需要你判断下一步」把人叫来(实测)。缺几条就写几条。 【三种分量 —— 与件上的空占位、执行卡的指导书**逐字同一套话**】 台账声明里怎么标,件上就是什么颜色,你说话就得用哪个词。三档只有这三个词: · **必填(只有这两项)**:\`pn\`、\`eau\` 年用量 —— 缺了这一单没法报价, **这是验收标准**,不齐不许放行(件上标红「必填」),记进 missing_key_total。 · **建议(值得追问,但不拦单)**:\`customer_pn\` 客户料号 · \`category\` 品类 · \`brand\` 制造商 · \`target_price\` 目标价 —— 声明里标了 \`expect\`,件上标橘「建议」。 缺了照样往下走,但**那一问不能省**:执行卡上要写清「这几格建议追问」。 · **选填(其余一切)**:交期、品质要求、备注…… 件上标灰「选填」,缺了只当提醒。 建议档与选填档一并记进 missing_minor_total。 **别把「建议」说成「选填」**(2026-08-03 用户当场指出):同一件事,工单上标着「建议追问」, 交代里若换成另一档的叫法,人会以为这两处说的不是一回事 —— 而它们就是一回事。 **目标价客户没给就留空,不许估**:它是**客户的意向价**,不是我们算得出来的数。 S4 查到的历史价是**判 A/B/C 的依据**(距上次报价几天、行情稳不稳), **不是客户的目标价** —— 拿它填进 \`target_price\` 就是替客户表了态, 下游定价、议价全建在这个假数上。也不许只在**交代里声称**估过价、垫过数 (实测栽过一次:台账留空是对的,嘴上却说按历史价估了 —— 而那一步连历史价都还没查, 纯属编造,人读了就以为表里躺着一个估出来的数)。留空、在件上标橘「建议」、 在执行卡上写明建议追问,这三件事合起来才是对的处置。 **目标价常缺是常态,不是缺陷** —— 实测栽过一次:一颗料只缺目标价却被记成必填缺、 还派了执行卡去问客户,而按 F-01 它根本不该拦单。 拿不准某个字段算哪一档,就问自己一句:**它是不是 PN 或年用量?** 不是就不许拦单。 【怎么拆】逐条写进 \`quote_intake.record\`,一条需求一行(同一 PN 不同数量档算两行)。 列名照台账字典写,别自己起名: · \`pn\` 我方料号 · \`customer_pn\` 客户自己的编码(BOM 里那一列,回价单要按它对齐) · \`category\` 品类 · \`brand\` 制造商 · \`eau\` 年用量 · \`target_price\` 目标价 · \`quality_req\` 认证/品质要求 · \`remark\` 备注 · \`customer_code\` 上一步认出的那个 四条纪律: ① **原文没写的不要补** —— 缺就是缺,记进缺口数,别替客户填一个「看起来合理」的值。 数值列(年用量 / 目标价)没有就给**空字符串 \`""\`**,基座认得出「这一列没有值」, 报告上会标成红的「必填」等人补。**绝不写 0** —— 0 是一个真实的数量, 写下去就等于告诉下游「客户要 0 个」;实测栽过:DAG 那条 BOM 里年用量本来是空的, 被写成 eau=0,而同一拍的缺口数还记着 1 —— 一份自己跟自己打架的记录。 ② **PN 一个字符都不许改**:后缀、横杠、空格全照抄 (实测:\`DAG2400L321504XZB01-R0\` 被写成 \`DAG2400L321504XZB01\`,尾巴上的 -R0 丢了 —— 往后查历史价查不到、开出去的询价请求对不上料,而错处早已在这一步埋下)。 ③ **缺哪一格算哪一格,别整行判缺**:同一行里有值的列照抄 (实测:那条缺年用量的料,连 BOM 里明明写着的制造商「浙江嘉康」与品类都一并没写 —— 人拿到卡以为客户什么都没给,白问一轮他早就给过的东西); ④ 单位归一但不换算口径(12K→12000 可以;月用量×12 是业务判断,该问客户); ③ **外文品类照译不照抄**:BOM 常是客户本国语言(Варистор = 压敏电阻, Антенна = 天线,Дроссель = 电感,Фильтр = 滤波器,Микросхема = 集成电路), 译的是**品类那一列的词**,不是按料号猜这颗料是什么 (实测:一颗天线被写成「集成电路」—— BOM 上白纸黑字写着 Антенна); 译不准的原文照留,别硬编一个。 ④ 正文里的口径逐条落到对应行:点名要证书的写进 \`quality_req\`; 客户**已预授权的替代件**写进 \`remark\`(那是后面选型的依据,丢了就得回头问客户)。 【同一拍记账】把 \`quote_intake.verdict\` 的这几个数写上: · bom_total = 来件一共几条(附件行数,不含表头); · skipped_total = 客户点名本轮不报的条数;skipped_note = 是哪几条、为什么(一句话); · line_total = 落了几条(= bom_total − skipped_total,对不上就是拆漏了); · **missing_key_total** = 缺 PN 或 年用量 的条数(必填缺); · **missing_minor_total** = **除 PN / 年用量之外任何字段缺了**的条数(选填缺)—— 客户料号、品类、制造商、目标价、交期、品质要求、备注,缺哪一个都算,一条只数一次。 **别只数品类和制造商**:实测栽过一次,NE555P 只缺目标价,两个缺口数报的都是 0, 于是那一问被整个吞掉 —— 人在成果表里看见一格空白,却没人问过他一句。 空不空由他决定要不要去补,不由你替他吞掉。 【最要紧的一条:**数就是数,不许为了过判据去改它**】 两个缺口数如实写。缺一条就是 1,别因为「写 0 才好往下走」就写 0 —— 实测栽过一次:一颗料只缺目标价,如实记了 1,提交被判据拦住, 于是那个数被改回了 0、单子放行了,而台账上从此写着「缺选填 0 条」, 那一格空白再没人提起。**判据拦住你,说明该去办那件事,不是该去改那个数**; 真办不了(客户就是不给),把它留在数里、在 say 里说清楚,放行时由人定。 **这一步不发卡**:拆完就 finish,交给下一步(受理需求交人审定)去请人。`, }, { item: 'S3_confirm', intent: '把整份受理需求交人审定:可就地改,确认后才往下走', writes: ['quote_intake.verdict'], reads: [], /* 阶段2 的执行目标件:询价受理需求(照 JCAI_PRD 的 A1-询价受理需求.html) */ view: 'intake-overview', tools: [], /* * **不给 doneWhen** —— 这一步触达世界(把件交到人手上),给了就等于给引擎 * 一把替人签字的钥匙。而它的成果按定义也判不出来:「人看过、改过、认了」 * 不是台账里任何一个数能自己变出来的。人工激活也一并给上: * 引擎不会替你按下这一步,也不会替你收 —— 发卡就算启动,收要你自己 finish。 */ guide: `拆好的九条摆在那儿了,**这一单现在要经过人的手**。 【一单必发一张,不管缺不缺】 这不是「缺东西时的追问」,是**阶段2 那张执行页**:AI 拟了稿,人审定。 字段齐全也要发 —— 拆错一格(PN 少个后缀、品类译反、年用量看错列)只有人看得出来, 而那些错往后每一步都跟着跑。**别因为「没有缺口」就跳过这一步**: 实测栽过一次(2026-08-01),那一版只在缺东西时才发卡,于是一封齐整的询价进来, 人一眼没看过受理需求,下一次被叫到已经是终局验收 —— 用户当场说 「我都没确认需求 它就让我验收了」。 【怎么发】**卡的骨架在说明书里(「受理需求审定卡的骨架」那一段),逐字段照抄** —— 不管你在哪一拍发它,骨架都在说明书里等着,不要凭记忆拼一张裸卡。 这一步人工激活:发卡就算启动,不必单发 start;引擎也不会替你收 —— 人交回后要你自己 finish。 发完卡把 **asked_total 加 1**(记账用:它证明这一问没省)。 【人提交之后 —— **件上的改动基座已经替你写回了**】 人在件上按「保存」那一刻,改动就落进了草稿层(与数据录入卡同一条规矩: 该由人亲手落下的,不经你的手);他按「提交」只是把这张卡交回来。 所以**别再 write 一遍那几行** —— 值没变的写入会被「写了等于没写」拒回(实测)。 你这一拍只做三件事,然后 finish: ① **把 missing_key_total / missing_minor_total 重算** —— 照台账里现在的值, 补上了就该变小(基座写的是行,汇总列是你的活); ② **confirmed_total 记 1** —— 这一单经过人了。**不写它这一步收不了,整单卡在这儿**; ③ answered_total 加 1(答案已经处理过了)。 漏掉①的下场实测过:值明明补上了,汇总列还写着「缺 1 条」,提交被判据一次次拦回来, 连拒五拍熔断 —— 而你只要把那个数改对就过去了。 真要补写某一行时**按台账里那一行的 PN 对行**:PN 是主键, 换一个写法就是**新开一行**(实测:补写时把 PN 补全成 \`…-R0\`,于是同一颗料在表里成了两行, 条目数说 9 而表里躺着 10)。真发现第一次的 PN 抄错了,在 say 里说明并交给人, 别自己用新 PN 再写一行。 【**这张卡一单只发一次**】人交回来之后,不论还缺什么,**都不许再发同一张卡**。 必填(PN / 年用量)有提交闸兜着:缺一条他根本按不下提交,所以卡交回来时必填一定是齐的; 建议档的缺口不拦单,留着交放行的人定。 实测栽过多次(2026-08-02 那一单连撞四拍):从前这儿写着「人交回来还缺必填,就再发一张卡」—— 而提交闸上线后那条路已经不存在了,模型照它办,每次都撞在 「这件事刚问过,人也答过了」上,白烧四拍。 【人把格子留空交回来 = **客户给不了**,那也是一个答案】 这时候要做的**不是再问一遍**(基座会当场拒:同一件事不许问第二遍),而是: · confirmed_total 记 1、answered_total 记 1(人已经处理过了); · 缺口数**照实留着**(缺就是缺,别改小); · 这一步就算办完了 —— 建议档的缺口不拦单,放行时由人定「先受理还是退回」。 实测栽过一次:人留空交回,模型又发一张一模一样的卡,被拒; 转头把同一行原样再写一遍,又被「写了等于没写」拒;连拒五拍熔断,一单就这么停了。`, }, { item: 'S4_history', view: 'intake-classified', intent: '调历史价工具,把 P-03 的三项判据取齐并落进料号分类表', writes: ['quote_intake.type_table'], reads: [], tools: ['crm.history-price-read'], /* * 判据落表了才算取齐。**不能写 `history_days != null`** —— * S-FEEL 里未绑定的名字 `!= null` 求值为 true,那一步开局就被收掉(踩过,测里拦下的)。 * 取正向判断:没写过就没有这个键,`>= 0` 自然为假。 */ doneWhen: 'history_days >= 0', guide: `P-03 只认三个入参:距上次报价天数、行情是否稳定、生命周期状态。 三者齐了结论是**算出来**的,所以这一步唯一该做的事是把判据取齐: 调 \`crm.history-price-read\`,**一次把这一单所有 PN 传进去**(参数 pns 收数组, 别一条一条调 —— 每调一次就多烧一拍)。取不到不是错误:新料号无历史会回 historyDays=9999、hit=false,那本身就是判据,命中 B 类「重估后报」是对的。 拿回来的三项判据**原样逐条**写进 \`quote_intake.type_table\`。 **这一步一个数都不许自己填**(实测踩过,三条同时犯): ① 工具回什么就写什么 —— historyDays / marketStable / lifecycle 三个值**逐字照抄**。 你不知道某颗料的行情,工具知道;你估一个「15 天 · 在产」,判定表就照着你的估算出 A, 而那颗料实际是客户点名要替代的 C。**估出来的判据比没有判据更坏**:它看起来很像真的。 ② **PN 必须原样**,一个字符都不许截、不许改写(实测:\`DAG2400L321504XZB01-R0\` 被写成 \`DAG2400L321504XZB01\`,尾巴上的 -R0 丢了 —— 工具当场查不到, 于是你又给它编了一组判据)。传进去的 PN 与写回台账的 PN 必须与受理记录逐字一致。 ③ 工具回 hit=false(这颗料没有历史)时,**照它回的 9999 / false / 未知 写**, 那三个值本身就是判据,命中 B 类「重估后报」是对的结论 —— 不是缺数据,不必替它补。`, }, { item: 'S5_classify', view: 'intake-classified', intent: '调分类器按 P-03 给每条打 A/B/C,标落两张表,条数照出参抄', writes: ['quote_intake.type_table', 'quote_intake.record', 'quote_intake.verdict'], reads: [], tools: ['quote-intake.classify'], /* * **收步判据与验收判据同源**(2026-08-01 改)。 * * 从前只看 `untyped_total = 0` —— 那是活动自己写的数。它归零了这一步就收, * 而验收那条比的是 `type_table_rows = record_rows`(基座数出来的行): * 数归零了、表没写全,于是**收了步却过不了验收**,模型得回头补写一张已经收掉的表。 * 收步比验收松,松出来的那一截就是返工。这里补上基座那一边:两处比同一件事。 */ doneWhen: 'untyped_total = 0 and line_total > 0 and quote_intake_type_table_rows = quote_intake_record_rows', guide: `调 \`quote-intake.classify\`(纯工具,P-03 的求值器,同输入必同结论、零 token), 把 type_table 那几行判据一次传进去,拿回每条的 A/B/C 与打法。 【标要落**两张表**】 · \`quote_intake.type_table\`:type_tag + basis —— 这是「凭什么这么判」的底稿; · \`quote_intake.record\`:**同一个 type_tag 也要写回受理记录**(按 PN 对行)—— 受理记录才是流去下游的那份交付物,A4 就靠这一列决定哪几条能直接取历史锚点。 漏了这一步,报告上的状况标那一列整列是空的,而下游拿到一份没打标的清单(实测)。 【条数**照出参抄,不许自己数**】classify 的出参里带 \`counts\`(A/B/C 各几条)与 \`total\`, 照它写 verdict 的 type_a_total / type_b_total / type_c_total,并把 untyped_total 归零。 实测数错过一次:判定器给的是 5A/3B/1C,报上去的是 7A/2B/1C,派单也跟着按错的数开 —— 而验收那条等式两边都是自报的,于是「2 = 2」顺利过关,真实的第三条 B 类料一张单也没开。 **不要绕过决策表自己判** —— 规则表可回放、可追责,临场判断不是。 \`type_tag\` 一律取出参里的那个字母,**不许自己写一个**:实测有过一次, 它自己给出的判据是「200 天 · 行情不稳」(按 P-03 该判 B),写回台账的标却是 A —— 判据与结论对不上,而验收只看结论,这种错一路走到报价单上都没人拦。 拿不准的一律降 B,不许臆断 A:错标 A 会让一条本该重新核价的料带着旧价走到报价单上。`, }, { item: 'S6_route', /* 阶段3 的验收目标件:询价受理记录(照 A1-询价受理记录.html) */ view: 'intake-release', intent: '按三类结果立两枚去向旗', writes: ['quote_intake.verdict'], reads: [], tools: [], /* * **这一步不再开单**(2026-08-02 用户当场定): * 「替代评估请求 / 询价请求这两项数据不在这里产出;随令牌流向下一个节点的 * 只有工作成果验收内的内容;进入选型替代后,活动会自己归一需要的数据。」 * * 从前 A1 逐条开 `alt_request` / `rfq_request` 两张单交给 A2 / A3 —— * 那是替下游把它的输入先摆好,而下游本来就要照自己的口径归一一遍: * 同一份事实在两个活动里各存一份,改一处就分叉。A1 交出去的是**受理记录**, * 下游读它即可。 * * 收步判据随之改成「两枚旗都立了」。旗是布尔,合法值含 false, * 拿它判「写没写过」分不清「没写」与「写了 false」—— 所以仍取同步写下的**计数** * (`route_decided_total`:定完去向记 1),并挂 `untyped_total = 0` 当前置: * 两个数都得是模型显式写过的,否则「未绑定的名字相等」会让这一步开局即收 * (包内自测那条「doneWhen 开局就为真」拦过这个)。 */ doneWhen: 'untyped_total = 0 and route_decided_total > 0', guide: `状况标是判断的**输入**,不等于去向。照三类的结果立两枚旗: · 有 C 类(EOL / 缺货 / 客户点名替代)→ need_alternative = true,这一单要走替代选型; · 有 B 类(无近期历史价或行情波动)→ need_sourcing = true,这一单要走寻源询价; · 两者可同时成立 —— 选型定案后仍要为替代料寻价。 写完把 **route_decided_total 记 1**(记账用:它证明去向定过了,引擎按它收步)。 **不要开什么请求单** —— 替代评估请求、询价请求都**不在这一步产出**: 随令牌流去下一个节点的只有**工作成果验收里的那份受理记录**, A2 / A3 拿到之后会按自己的口径归一需要的数据。你替它们先摆一份, 等于同一份事实在两个活动里各存一份,改一处就分叉。 **这两枚旗是下游两道网关唯一读的东西**:不写,整条流出了受理口就没路可走。 **去向不用你选,也不用人选** —— 契约给三个出口各写了判据(有没有 C 类、有没有 B 类), 放行那一刻基座按序求值自己定,卡上只如实告诉人走哪一口、凭哪一条。 你要做的只有一件:**把三类条数与两枚旗写对**(它们正是那几条判据读的数)。 旗写错,算出来的去向就错 —— 但那是数据错,不是选择错。`, }, { item: 'T_release', view: 'intake-release', intent: '判定成果已齐,提交放行交 BD 验收', writes: [], reads: [], tools: [], // 放行触达世界,必须人按一下 —— 引擎不自动收它(剧本里也不给 doneWhen) guide: `line_total > 0、missing_key_total = 0、untyped_total = 0、两枚旗已定 —— 可以走放行了(T_release 人工激活:动它就算启动,不必单发 start)。 **submit 之前,把清单里**每一条**裁量判据(L3/L4)都裁掉**。 **一条都不能漏** —— 漏一条,submit 当场被拒, 而拒回来的理由里就写着漏的是哪一条:照它原样抄 criterion 再裁一次即可, **别原样重提同一个 submit**(实测连提七次,每次都被同一条拒回来)。 眼下**只剩一条**,照名字逐字抄(2026-08-02 起「吃不准降 B」那条已从清单里删掉, 别再裁它 —— 裁一个不存在的判据会被拒): · judge criterion="建议追问的四格(客户料号/品类/制造商/目标价)已补齐,或由你放行时确认可以先受理" —— **只看 \`quote_intake_record_gaps\` 这一个数**(基座现数的:还有几行缺**建议追问**的格子; \`..._gaps_keys\` 写着缺在哪几行的哪一格)。就两种写法,没有第三种: · **= 0** → satisfied=true,rationale 写「建议追问的那几格都补齐了」; · **> 0** → satisfied=**false**,rationale 照 \`..._gaps_keys\` 逐条写清缺在哪儿 (例:「DAG2400L321504XZB01-R0 缺目标价」),交放行的人定「先受理还是退回」。 **「我发过卡问过了」不算满足** —— 发卡是过程,不是结论;而这一单本来就必发一张 审定卡,拿它当理由的话这条判据永远是绿的,等于没有这道门 (实测:客户没给目标价,它照样裁了满足,人签字时根本不知道还缺着)。 裁成不满足**照样能提交**,理由连着卡一起交给签字的人 —— 那正是这条判据的用处。 **不许为了让它绿去改哪个数** —— 那个数你也改不动,它由基座现数。 **criterion 照判据名原样写**(整句,含括号里那几个字),写别的名字对不上,那一条仍算未达标。 **去向不用你裁**:三个出口各写了判据,放行那一刻基座按序求值、首个命中即出 —— 那是算出来的,不是判出来的。从前这里还有一条「去向判断合乎业务常理」, 那是让你给一个自己没做的决定盖章:裁「满足」是废话,裁「不满足」也改不动什么。 C 类开没开替代评估请求、B 类开没开询价请求,由两条机检判据当场对账。 这一条是机器判不了的(L4 独立评审:缺口真还在时,「先受理还是退回」只有人能定), 只能由你当场裁并留下理由 —— **不裁就提交会被当场拒**:还有裁量判据没裁过时, 提交等于把没办完的活推给人。裁完再 submit,BD 验收。 (缺口为 0 时基座当它已达标、不必你裁;裁了也不碍事。) 终局唯放行:你不能自己宣布完成。 验收报告上让 BD 当场改的那两个值(两枚去向旗)由**契约声明**(revisableAtRelease), 基座放行前从台账现读拟值 —— 你不用在 submit 上重述一遍,写好台账就行。`, }, ]; /** * 阶段 —— **进度条的刻度**,只给人看,引擎一眼都不看。 * * 六个计划项是引擎的粒度(拆到能判「干完没有」);阶段是人的粒度。 * 「查历史价 / 逐条打标 / 定去向」在业务上是**一件事** —— 判这单该怎么走, * 分成三项是因为引擎要一项一项收,而人只想知道「到判去向这一步了」。 * * 只列**激活态**的三段。放行不在这儿:每个活动都以放行收口(铁律③), * 那是系统的规矩不是 A1 的阶段;进度条自己在末尾加那一格,所以人看到的是 x/4。 */ /* * 给人看的刻度 —— **每个计划项都得有格可待**(2026-08-02 用户:「卡片的进度条走的不对」)。 * * 从前这份名单漏了 `S3_confirm`(它是 2026-08-01 才补回成一项的): * 计划项有六个,名单里只有五个。于是 S2 一收,前两格全过,进度条当场跳到「判定去向」—— * 而人这会儿正对着受理需求审定那张卡。**他手上办的那件事在条上没有格子**, * 条却已经走过去了:那不是走快了一格,是它在说一件没发生的事。 * * `T_release` 不在名单里是**对的**:每个活动都以放行收口,那一格由基座统一加 * (`CLOSING_PHASE`)—— 活动自己给它起名反而会各叫各的。 * 包内自测钉死这条:除放行外每一项恰好属于一个阶段。 */ export const INTAKE_PHASES = [ { name: '定位客户', items: ['S1_customer'] }, { name: '归一受理', items: ['S2_normalize'] }, { name: '交你审定', items: ['S3_confirm'] }, { name: '判定去向', items: ['S4_history', 'S5_classify', 'S6_route'] }, ];