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-TMP-REPO/activities/quote-pricing/src/case-model.ts
T
ChaiHaoandClaude Opus 5 ade0fa5a94 A2-A7 全部补齐到验收线;顺手把几处「门拦住却不给路」的坑填了
活动:A2 选型 / A3 寻源 / A4 定价 / A6 议价 / A7 归档五件各补成真活动包 ——
契约宽进(input.required 空,归一在活动内)、台账必填声明(含行级 requiredWhen)、
入场哨兵与 L1/L2 判据一律读基座数、两份件(在办可编辑 + 办结静态快照)、
三案例常备、包内自测。e2e 各三案例 released、行动被拒 0、发口正确、提交闸每单真拦。

基座(共享面,改一处影响所有活动,故每处都带回归):
· 重发判断走在站上场之前 —— 人答完那步常已收掉,readyToWork 先抛「不能再往它上面挂活」,
  于是「重发已答过的卡 = 无事发生」这条唯一活路在最需要时够不着,A1 基线当场熔断;
· 决策卡补幂等兜底(与执行工单同一把尺):头两次无事发生,第三次起才算被拒;
· 输出没成形先就地重问一次,原话进工作日志、不再摆上卡面;
· 工具调用读数 tool_<名>_calls —— 「机检只读基座数」这条纪律第一次覆盖到工具;
· 执行卡 steps/standard 补进出参 schema(从前能到卡面全靠厂商 JSON 模式宽松);
· 件的就地更新改逐节点 morph,保存先把新值镜像进只读那份 —— 不闪、不等回声。

A1(基线,全程守住):措辞三档对齐(必填/建议/选填)、禁止拿历史价当客户目标价、
卡骨架从步骤剧本搬进说明书(压成一拍连做时剧本没摊开,会发出没有指导书的裸卡)。

契约两处**新增可选字段**(非破坏):FieldDeclaration.requiredWhen、工单 finalHtml;
api-report 同批更新。docs/adr 与 docs/archive 的 13 份文件在工作树里不见了,
本次予以恢复而非提交删除。

门禁:typecheck / lint / depcruise / test(1366)全绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 12:28:31 +08:00

388 lines
27 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.
/**
* A4 成本定价的案例模型 —— **双表示同版本**:CMMN XML(机器面)与案例说明书(叙事面)。
*
* 业务出处:`docs/MVP示例工作流.html` §1.2(S1 取价基准 → S2 逐条定价 → S3 底价自检),
* 按 A3 同一条形状建模(2026-08-03 重写,@3.0.0):
*
* 上游(A3 的目标报价清单 / A2 的询价受理清单 / A1 直达的受理记录)进来 → 归一成
* **报价草案**底表,逐条按 P-01 利润模型算出底价线与拟报单价、过 P-02 底价门禁 →
* **一张执行单**交 BD 核价(拟报单价可校可改,无基准的那几行由他定价)→
* **只有压在拦截档上的那几条才叫人拍板**(决策卡)→ 放行,整份报价草案交定价审批。
*
* 上一版(@2.0.0)三处不合现行口径,这一版整个置换:
* ① 入参必填 `rfqRef + customerCode + records` —— 而 A3 发的是 `{rfqRef, rows}`,
* 门口拦的恰是主路(现在宽进到底,归一在活动内做);
* ② 台账一个 `required` 都没有 —— 执行卡的闸没牙,人不填也能交卷;
* ③ 入场哨兵 `s_priced` 读活动自报的 `draft_total`(「机检只读基座数」第 5 类违反)——
* 模型不记这个账,门就永不开,而它一步也没做错。
*/
export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL">
<case id="pricing">
<!--
案卷项的 id **就是任务视图里的键名**。上游载荷(rows / records)与本活动台账列
都摊平到任务视图顶层,照它们命名;quote_pricing_* 那几个是**基座读数**
(行数 / 缺必填 / 按值现数),由基座现算,模型碰不着。
-->
<caseFileModel>
<caseFileItem id="rows" name="上游清单行(寻源/选型转来,流内开案即有)"/>
<caseFileItem id="records" name="上游受理记录(受理直达时有)"/>
<caseFileItem id="line_total" name="草案条目数(自报)"/>
<caseFileItem id="priced_total" name="已算出拟报价的条数"/>
<caseFileItem id="confirmed_total" name="BD 已核价条数(人环走完的凭据)"/>
<caseFileItem id="route_decided_total" name="已定盘"/>
<caseFileItem id="quote_pricing_draft_rows" name="草案行数(基座现数)"/>
<caseFileItem id="quote_pricing_draft_missing" name="缺拟报单价的行数(基座现数)"/>
<caseFileItem id="quote_pricing_draft_disposition_block" name="压在拦截档的条数(基座现数)"/>
</caseFileModel>
<casePlanModel id="plan" name="成本定价" autoComplete="true">
<!--
① 算完了 —— 交 BD 核价那一步的入场前提。
行数两边对上(自报 = 基座现数),且算价那一轮走完(priced_total 是算完之后
显式写下的数;「大于等于 0」对「写过的 0」为真、对「没写过」为假)。
-->
<sentry id="s_priced">
<onPart id="op_pr" caseFileItemRef="priced_total"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_pr"><condition>line_total &gt; 0 and quote_pricing_draft_rows = line_total and priced_total &gt;= 0</condition></ifPart>
</sentry>
<!--
② 人定完价了 —— 底价自检那一步的入场前提。
confirmed_total 只在人交回执行单之后才产生,它就是「经过人了」的凭据。
-->
<sentry id="s_confirmed">
<onPart id="op_cf" caseFileItemRef="confirmed_total"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_cf"><condition>confirmed_total &gt; 0</condition></ifPart>
</sentry>
<!--
③ **超限才叫人** —— 这个活动的要害。
「压在拦截档上的条数」由基座按 disposition 的取值域现数(不是活动自报),
且要等 BD 核过价(confirmed_total 只在人交回执行单之后才产生)——
他自己回调掉的那几条不必再惊动第二个人。
-->
<sentry id="s_over">
<onPart id="op_ov" caseFileItemRef="quote_pricing_draft_disposition_block"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_ov"><condition>quote_pricing_draft_disposition_block &gt; 0 and confirmed_total &gt; 0</condition></ifPart>
</sentry>
<!--
── 放行门的入场条件:**认「办完了没有」,且读基座数**────────────────
· 每行都有拟报单价(quote_pricing_draft_missing 由基座按 required 现数,
与执行卡的提交闸、验收 L1 同一个数同一套算法);
· 门禁处置逐条落定且守恒(三档相加 = 行数,两边都是基座按值现数);
· **没有还压在拦截档上的条目**(要么回调、要么经人拍板改记附理由放行);
· 定盘过了(route_decided_total 是定盘那一步显式写下的记账数)。
-->
<sentry id="s_ready">
<onPart id="op_rd" caseFileItemRef="route_decided_total"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_rd"><condition>route_decided_total &gt; 0 and quote_pricing_draft_rows &gt; 0 and quote_pricing_draft_missing = 0 and quote_pricing_draft_disposition_block = 0 and quote_pricing_draft_disposition_pass + quote_pricing_draft_disposition_note + quote_pricing_draft_disposition_block = quote_pricing_draft_rows</condition></ifPart>
</sentry>
<!-- ① 归一草案 + 取基准 + 逐条算价过门禁(能算的不问人) -->
<humanTask id="S1_price" name="归一报价草案:取成本基准、按利润模型算底价线与建议价">
<itemControl><repetitionRule id="r_s1"/></itemControl>
</humanTask>
<!--
② 交 BD 核价 —— **一张执行单**(拟报价可校可改,无基准的行由他定价)。
manualActivation + 不给 doneWhen(发卡触达真人,引擎不替人按)。
-->
<humanTask id="S2_review" name="交BD定价:工作单上逐条定价,收回后过底价门禁">
<entryCriterion id="ec_s2" sentryRef="s_priced"/>
<itemControl><manualActivationRule id="m_s2"/></itemControl>
</humanTask>
<!--
③ 底价自检 —— SOP 里的 S3,一件独立的事:按人定的价算毛利、偏离度、门禁处置。
它不触达世界,所以给 doneWhen(三档处置的条数加起来等于行数就算判完了),
引擎自己收 —— 从前这一段藏在「交人核价」的收尾里,模型写完 confirmed_total
就把那一项收了,三列处置一格没写,放行门当场判「处置没落定」,而拒回只会说
「数不对/行不对」—— 两扇打不开的门(实测 8 次拒回熔断)。
-->
<humanTask id="S3_gate" name="底价自检:按定下的价算毛利与偏离度,逐条过底价门禁">
<entryCriterion id="ec_s3" sentryRef="s_confirmed"/>
<itemControl><repetitionRule id="r_s3"/></itemControl>
</humanTask>
<!-- ④ 超限拍板:触达客户利益的那一下,必须人工激活;只在拦截档还有条目时入场 -->
<humanTask id="T_approve" name="超限拍板:压在拦截档的条目要人定怎么办">
<entryCriterion id="ec_ap" sentryRef="s_over"/>
<itemControl><manualActivationRule id="m_ap"/><repetitionRule id="r_ap"/></itemControl>
</humanTask>
<!-- 草案放行:终局唯放行 —— 触达世界,必须人工激活 -->
<humanTask id="T_release" name="报价草案验收放行">
<entryCriterion id="ec_rel" sentryRef="s_ready"/>
<itemControl><manualActivationRule id="m_rl"/></itemControl>
</humanTask>
</casePlanModel>
</case>
</definitions>`;
/**
* 案例说明书 —— **只写整单一以贯之的事**(逐步做法归剧本,两处不重复写同一件事)。
*/
export const NARRATIVE = `§0 开案:**一批料要定出各自的拟报单价**。来路三种,形状都收(宽进):
· **寻源转来**:任务数据里有 \`rows\` 数组 —— 目标报价清单的落地行,每行带底价(base_price)
与价源;这是最全的一种来路;
· **受理/选型直达**:有 \`records\` 或 \`rows\` 而行上没有底价 —— 只有料号与年用量;
· **单飞**:只有一段需求文字,列了几颗料要报价。
**成品只有一样**:一份**报价草案** —— 整份行都在、**每一行都有拟报单价**,
而且每个价都说得出来路:基准从哪来、毛利档是哪一档、偏离底价线多少、门禁怎么判。
人签的不是一个数字,是**这个数字的来路**。它是定价审批的直接输入。
**能算的不问人,该人签的不代签**:P-01(利润模型)给毛利档、P-02(底价门禁)给处置,
两支判定表说了算,你不临场拍价;而**拟报单价那一列是 BD 定的,你一格都不写** ——
你给的是**建议价**,他确认或改成自己的数。你的活是把判据取齐、把结论落进台账。
**四步走**:归一取基准算建议价 → 交 BD 定价 → 底价自检(按他定的价算毛利、偏离度、
门禁处置)→ 定盘放行。
**这一单要经过人一到两次**:BD 在定价工作单上逐条定价(那张单绑着整份草案,
拟报单价缺一行按不下提交 —— 基座的闸看着,他填的格子直接落账);
**只有还压在拦截档上的条目才另外叫人拍板**(一张决策卡)。每条都叫人,人就成了橡皮图章;
一条都不叫,底价门禁就是摆设。最后 BD 放行签字。
【一路上要说话】负责人正看着这一单。**每一拍都带一句 say** —— 说业务的话:
这批料算下来什么价、哪几条压线、要不要找人拍板;不说系统词。
到了要找人的那一拍,先说清这一轮办完了什么、为什么现在需要他。
【怎么把一项干完 · 一拍连做】write 和 tool 都不会自动收步 —— 做完用 finish 宣告。
动作排进同一拍的 actions 串一口气给;只有调工具(出参下一拍才看得见)与
发卡 / 提交(挂起)必须停拍。**这一单常路 7-9 拍就该收口**。
§Z 结案:BD 在验收单上签字,系统把草稿合并成正式草案并发出 draft 口的载荷 ——
整份报价草案交给定价审批。`;
/**
* 步骤剧本 —— 一个计划项一条;`doneWhen` 是「成果已存在」的判据,不是「该不该做」。
*
* 三处不给 `doneWhen`:`S2_review`(发卡触达真人)、`T_approve`(要人拍板)、
* `T_release`(签下去触达世界)—— 给了等于给引擎一把替人签字的钥匙。
*/
export const PRICING_STEPS = [
{
item: 'S1_price',
intent: '整份归一成报价草案:取成本基准 → 按利润模型算出底价线与建议价,逐条落表',
writes: ['quote_pricing.draft', 'quote_pricing.verdict'],
/* 上游载荷数组不声明就会被整表段的截断刀砍掉(砍掉的恰是要归一的行) */
reads: ['rows', 'records'],
view: 'pricing-worksheet',
tools: ['crm.history-price-read'],
/*
* 归一 + 算价两轮都走完才算办完:行数两边对上(一边是基座现数),
* 且 priced_total 写过(全是无基准的单子这个数就是 0;S-FEEL 未绑定的名字
* 参与比较为假,所以「没写过」进不来)。
*/
doneWhen: 'line_total > 0 and quote_pricing_draft_rows = line_total and priced_total >= 0',
guide: `第一件事:把这一单的**报价草案**落出来,并把能算的价都算出来。
【料从哪儿来】
· 寻源转来(\`rows\` 里的行带 base_price / price_basis):照抄 —— 底价就是成本基准,
一个数字都别改;带 quote_line_total 这类总数的是结论行,不是料,别落进草案。
· 受理/选型直达(\`records\` 或没有底价的 \`rows\`):料号、品类、制造商、年用量、
客户目标价、类别(type_tag)照抄,**底价一栏留空**。
· 单飞:照正文归一,列了几颗落几行;没说类别的按 B。
**按 pn 归并,一颗料一行**;pn 一个字符不许改(它是主键,改一个字就是新开一行)。
【第一拍:只做两件事 —— 落草案行 + 串尾发一次查价】
① 写 \`quote_pricing.draft\`,一颗料一行(rows 收数组):
rfq_ref(入参有就原样抄;没有从主题认;认不出才自己造一个,**造出来的必须以 \`PRC-\` 开头**
(\`PRC-<主题或第一颗料的型号>\`)。两条硬规矩:
**① 整单只有一个单号,不是一颗料一个** —— 它是这一单的案号,草案所有行、verdict 那一行
用的都得是同一个串(实测栽过:两颗料造出两个号,一单裂成两单);
**② 只从本单来件取材** —— 台账里躺着的 RFQ-xxxx 是别人的单,借它的号等于把两单的行
混进同一个案卷(实测栽过一轮))、pn、customer_pn、category、
brand、eau、target_price(客户目标价)、lead_time、remark(客户约束/急料,一个字别丢)、
type_tag(照上游现标;单飞按 B)、base_price(上游给了就抄,没有留空)。
suggested_price / margin_band / floor_price **这一拍全部留空** —— 基准还没取齐。
**quoted_price / margin / deviation / disposition 这四列从头到尾都不是你写的**(见下)。
② 串尾调 \`crm.history-price-read\`:pns 收数组,把**底价还空着的那几颗料**一次传完
(上游已经给了底价的不必查)。
**这一拍不写 \`quote_pricing.verdict\`,一列都不写** —— 尤其 priced_total:
它是「算价走完了」的凭据,提前写等于把闸提前打开,引擎会在下一拍开头当场收掉这一步,
**算价从此不会发生**(A3 实测栽过:嘴上说预填了,台账里没有)。
【**这一列你一格都不写**:quoted_price】
拟报单价是 **BD 在工作单上定的那个数** —— 人签的就是它。你给的是**建议价**
(suggested_price),他确认或改成自己的数;margin / deviation / disposition 三列
要等他定完价才算得出来,这一步也别写。
这不是客气话:从前只有一列价、剧本写着「算不出的留空」,实测跑单时两颗库里查不到的料
照样被编出了价(一颗抄客户目标价、一颗凭空写了个数),而交代里还说着「价空着等你定」。
**一条要你分辨「这行算得出/算不出」的规矩会走岔;「这一列不写」没有分支可走岔。**
【第二拍:算建议价 + 记账 + 收步,一拍连做】
① 补基准:**只补上一拍 base_price 空着的那几行** —— 工具出参里 hit=true 的写
base_price = lastPrice、price_basis 写 **HIST**;hit=false 的**别编价**,base_price 留空,
它就是「无基准」那一档。
**上游已经给了 base_price 的行:工具回什么都不采用,那一格一个数字都别改** ——
上游给的是本单问回来的货源底价,工具给的是库内历史价;这一单的成本是前者
(实测栽过:两行上游底价被历史价盖掉,毛利从 20% 变成 115%,数字全对而结论是假的)。
② 逐条算**建议价**,规矩写死,照做即可(**这些是算术,不是判断**):
· 毛利档 margin_band 按利润模型 P-01 取(客户等级 × 类别):
战略客户 + A 类(大批量长单)→ 8%~10%;普通客户 + A 类 → 12%~15%;
B 类 → 15%~20%;C 类 → 20%~50%;拿不准按 12%~15%。写成「12%~15%」这样的文本。
· **底价线** floor_price = base_price ×(1 + 档下限)。保留四位小数。
· **建议价** suggested_price:
- 客户没给目标价(target_price 空)→ 取 base_price ×(1 + 档上限);
- 客户目标价 ≥ 底价线 → 就取客户目标价(接得住,毛利还在档内);
- 客户目标价 < 底价线 → 仍取客户目标价(贴着客户报),这一条到门禁那儿会亮灯;
- **base_price 空(无基准)→ margin_band / floor_price / suggested_price 三列一起留空**。
不要拿一个不相干的价去凑一个数出来 —— 那比没有建议更危险,它看起来像有依据。
· **「没有」要给空串 \`""\`,不许写 0** —— 0 是一个数,底价线写 0 看起来像「成本为零」,
偏离度写 0 看起来像「正好压在底价线上」。空串基座会当成「这一列不写」。
③ 这时才写 \`quote_pricing.verdict\`(一次写全):rfq_ref、customer_code / customer_name
(顶层有就抄)、customer_grade(来件认得出就写,认不出写「普通客户」)、
**line_total** = 草案行数、**basis_total** = 有成本基准的条数、
**priced_total** = 这一拍算出建议价的条数(0 也要写)。
④ finish 这一项。`,
},
{
item: 'S2_review',
intent: '一张执行单交 BD:工作单上逐条定价(拟报单价归他);收回后过底价门禁并定盘',
writes: ['quote_pricing.draft', 'quote_pricing.verdict'],
reads: ['quote_pricing.draft'],
view: 'pricing-worksheet',
tools: [],
/* 不给 doneWhen —— 发卡触达真人(manualActivation),引擎不替人按 */
guide: `建议价算完了,**该 BD 出场了** —— 这是本活动唯一的一张执行单,
**拟报单价是他定的,不是你定的**。工作区摆的是**定价工作单**:逐颗料一张定价档案卡
(成本基准、毛利档、客户目标价、底价线、系统建议价),利润模型与底价门禁两把尺挂在上面,
填回区长在同一屏 —— 他在「拟报单价」那一列逐行填(照建议价确认,或改成自己的数)。
这一步标了人工激活:发卡就算启动,不必单发 start;引擎也不会替你收 —— 人交回后要你自己 finish。
【照这个骨架发,别少字段】(卡面是纯文本,steps/standard 里**别写星号记号**):
{"kind":"human","itemId":"S2_review","workOrder":"execution","card":{
"title":"报价定价:<单号> 共 <N> 条待你定价",
"ask":"系统已按成本基准与利润模型算出每一颗的建议价,请在工作单的「拟报单价」一列逐行定价:认可建议价就照抄,不认可就改成你的数;库内没有成本基准的那几行没有建议价,由你定。",
"steps":["拟报单价每一行都要有 —— 缺一行这张单按不下提交;它是这一单最后签下去的那个数,系统不替你填",
"建议价是从成本基准按利润模型的毛利档推出来的:照抄就是认可这个毛利,改高改低都可以,改了在备注里写一句缘由",
"低于底价线的价会被底价门禁标出来:两成以内附理由即可放行,超过两成的要另外找人拍板 —— 你在这里往上调一点,就不必再惊动一个人",
"库内查不到成本的料没有建议价:按你的经验定价,并在备注写明依据;实在定不了的按行首 × 把这一条撤出本轮"],
"standard":"每一行都有拟报单价,而且每个价都说得出来路(基准 / 毛利档 / 让价理由)—— 缺一行这张单按不下提交(系统的闸看着)。",
"basis":[{"k":"<PN>","v":"<类别 · 成本基准 · 毛利档 · 建议价>"}]}}
· \`basis\` 逐条列本单的料:PN + 基准与建议价。几颗写几条,别铺全表。
· **卡上不摆表、不摆格子**(执行卡零输入):整份草案就在工作单那份件上,
拟报单价与备注两列都能就地改、行能撤 —— **别往卡里塞 \`blocks\` 或 \`fields\`**。
· **这张卡带提交闸**:拟报单价(必填列)还空着,他按不下提交 —— 缺几格、缺在哪行,
闸上与件上的红带都写着(与放行门 L1 同一个数同一套算法)。
发完卡这一拍就结束了(发卡挂起)。
【人交回之后 —— 他填的价基座已经替你写回台账了】这一步你只做两件事,然后 finish:
① 写 verdict:**confirmed_total** = 他定过价的条数(照草案行数记);
他在件上撤掉了行的话,**line_total 跟着改成撤后的实数**。
② finish 这一项。
**别在这一拍算门禁** —— 底价自检是下一步的活(它有自己的一段做法),这里算了也白算。
草案行你一列都不用重写:他填的值基座已经落进台账了,重写会被「写了等于没写」拒回。
【这张卡一单只发一次】人交回来之后不许再发同一张卡 —— 必填有提交闸兜着,
卡回来时拟报单价一定是齐的(或那几行已被他撤掉)。`,
},
{
item: 'S3_gate',
intent: '底价自检:按人定下的价逐条算毛利、偏离度与门禁处置,判完即定盘',
writes: ['quote_pricing.draft', 'quote_pricing.verdict'],
reads: ['quote_pricing.draft'],
view: 'pricing-worksheet',
tools: [],
/*
* **收步判据与验收判据同源**:三档处置的条数加起来等于行数(两边都是基座现数)——
* 判完了才算办完。`_rows > 0` 那一半不能省:开案时行数是 0,而 0+0+0 = 0
* 会让这一步开局即收(未绑定名字的坑之外,还有「全零相等」这一坑)。
*/
doneWhen:
'quote_pricing_draft_rows > 0 and quote_pricing_draft_disposition_pass + quote_pricing_draft_disposition_note + quote_pricing_draft_disposition_block = quote_pricing_draft_rows',
guide: `人把价定完了,**现在才轮到算门禁** —— 这一步是纯算术,一拍连做:
① **逐行按他定的 quoted_price 算三列**(**quoted_price 你一格都不写,只写这三列**):
· margin =(quoted_price − base_price)/ base_price,四位小数;
· **偏离度** deviation =(quoted_price − floor_price)/ floor_price,四位小数;
· **门禁处置** disposition 按底价门禁 P-02 判,只许写这三个大写码之一,一个字母不许差:
- deviation ≥ 0 → **PASS**(在底价线之上,直接放行)
- −0.20 ≤ deviation < 0 → **NOTE**(低于底价线两成以内,附理由放行)
- deviation < −0.20 → **BLOCK**(低于两成以上,压在拦截档,要人拍板)
· **base_price 空(无基准)的行**:margin / deviation 给空串 \`""\`(**不许写 0** ——
0 看起来像「正好压在底价线上」,而这一行压根没有底价线)、disposition 记 **NOTE**,
price_note 里保留他写的定价依据(他没写就补一句「库内无成本基准,由 BD 定价」)。
**一行都不能漏**:三档处置的条数加起来必须等于 quote_pricing_draft_rows(基座数的行数)。
现在三档都是 0,就是一行都还没判 —— 这一步没办完,放行门开不了。
② 看这一拍写完之后的**拦截档条数**(disposition = BLOCK 的行):
· 一条都没有 → 同一拍把 verdict 的 **route_decided_total 记 1**(定盘)、
summary 写一句人话(几条直接放行、几条附理由),然后 finish 这一项;
· 还有 → **别写 route_decided_total**,只 finish 这一项 —— 下一拍会有一步专门叫人拍板。`,
},
{
item: 'T_approve',
intent: '压在拦截档的条目要人拍板 —— 发一张决策卡,照他选的那条路把草案改掉',
writes: ['quote_pricing.draft', 'quote_pricing.verdict'],
reads: ['quote_pricing.draft'],
view: 'pricing-roster',
tools: [],
/* 人工激活 + 要人拍板 —— 不给 doneWhen,引擎不许替人收 */
guide: `**这一单只发一张决策卡。动手之前先分清你在哪一半:**
· 轨迹里**已经有**「decision 卡回传」/「人选了:xxx」—— 那就跳到下面【人点完之后】那一段,
照他选的那条路改草案、写 route_decided_total、finish。**一个 decide 都不许再发**
(实测栽过:人点完了,而这一步又从头念了一遍「发一张决策卡」,同一张卡连发四次,
调人预算烧光,单子 exhausted —— 他点过的那一下就白点了);
· 轨迹里**还没有** —— 才轮到下面【发一张决策卡】那一段。
这一步只在还有条目压在拦截档(基座现数 quote_pricing_draft_disposition_block > 0)时入场;
它触达客户利益,标了人工激活:发卡就算启动,不必单发 start。
工作区这时摆的是**报价草案清单**,整份草案与门禁分布都在上面,他看得见全局再拍板。
【发一张决策卡,照这个骨架】(选项少于两个基座拒发):
{"kind":"decide","itemId":"T_approve","ask":"<N> 条低于底价线两成以上,按哪条路办?",
"options":[
{"value":"floor","label":"回调到底价线报","consequence":"守住毛利下限,客户可能不接受这个价","recommended":true},
{"value":"as-is","label":"照现价破线报,附理由","consequence":"接住客户目标价拿单,毛利跌破下限,理由随草案交审批"},
{"value":"drop","label":"这几条本轮不报","consequence":"撤出本单草案,客户拿到的是一份缺项的报价"}],
"basis":[{"k":"<PN>","v":"成本基准 <X> · 底价线 <Y> · 拟报 <Z> · 偏离 <W>"}]}
· basis 逐条摆压在拦截档的那几颗料:基准、底价线、拟报价、偏离度 —— 裸卡基座拒发。
· 发完这一拍就结束了(发卡挂起)。
【人点完之后】轨迹上会出现「decision 卡回传 —— 人选了:xxx」。**照那条路把草案改掉,
别再发第二张卡**,一拍连做:
· 选 floor:**这是全活动唯一一次你可以写 quoted_price** —— 那个数不是你定的,
是他在卡上点下的(就是该行的底价线),你只是替他落账:
那几行 quoted_price 改成本行的 floor_price,margin 记该行毛利档的下限、deviation 记 0、
disposition 改 **PASS**,price_note 写「按底价线回调,由负责人拍板」;
· 选 as-is:价不动,disposition 改 **NOTE**,price_note 写明破线的理由与拍板人;
· 选 drop:用 \`drop\` 把那几行撤出本单草稿(主键照 \`quote_pricing_draft_keys\` 名单**原样粘**),
并把 verdict 的 line_total 改成撤后的实数。
然后写 verdict:**route_decided_total 记 1**、summary 补一句拍板结果,finish 这一项。
拦截档清零之后,放行门才开得了。`,
},
{
item: 'T_release',
intent: '草案已定盘,提交放行交 BD 验收',
writes: [],
reads: [],
view: 'pricing-roster',
tools: [],
/* 放行触达世界,必须人按一下 —— 引擎不自动收它(剧本里也不给 doneWhen) */
guide: `每行有价、门禁处置守恒、拦截档清零、定盘已记 —— 可以走放行了
(T_release 人工激活:动它就算启动,不必单发 start)。先把裁量判据裁掉,然后 submit,同一拍连做:
· judge criterion="报价的来路说得清:基准价来源、毛利档与让价理由逐条可溯,BD 对报价负责" ——
看草案里每一行的基准、毛利档与备注:说得出来路就 satisfied=true;
有行说不出的(比如无基准由人定的价没写缘由),satisfied=**false**,rationale 逐行写清,
交签字的人定。**裁成不满足照样能提交** —— 理由连着卡一起交给他。
criterion 照判据名**整句原样抄**,写别的名字对不上。
终局唯放行:你不能自己宣布完成。`,
},
];
/**
* 阶段 —— 进度条的刻度,只给人看。除放行外每个计划项恰属一个阶段;
* 放行那一格由基座统一加,人看到的是 x/4。
*/
export const PRICING_PHASES = [
{ name: '取基准算建议价', items: ['S1_price'] },
{ name: '交BD定价', items: ['S2_review'] },
{ name: '底价自检', items: ['S3_gate'] },
{ name: '超限拍板', items: ['T_approve'] },
];