三面拆分⑭:宽进收敛 —— 起单不卡缺项,进活动受理归一,信息不全派补全单
用户定的逻辑:「调用活动,输入数据可以不定或者为空;进入活动后受理归一,
信息不全派补全单。」把收敛点从门口挪进活动 —— 三道门口闸全撤:
· 触发装配:缺必填**不再入死信**(AssemblyResult 的 missing 改 partial,带上已装配的);
装到多少带多少照样起单。死信只留给真错误(勾了确认没指定人这类配置错误)。
· process.start:撤掉调用接口缺项闸 —— 从前缺一个字段整条流都起不来,
现实是客户来信本来就常缺客户码,拦在门外等于要求外界替系统把信息备齐。
· task.start(替代旧 AM 12.1「缺项不得启动」):缺必填的任务落成「等人」,
缺的字段做成**补全卡**(elicitation,标签取 schema 的 title)发到负责人的
任务对话流,并武装 human 监听(IntakeDesk 端口;不占活动的人卡预算)。
答复只收契约声明过的键,合并进 data;齐了或答「没法填」才转 running 开跑;
答一半就就缺的部分再派一张卡。等人期间谱一步不走、不扣步数。
· 测试台预检改成**信息**不是闸:灯灭不拦起单,文案直说
「抽不出的键由受理补全单找人收齐」。
5 条受理归一单测 + 全链实测:投一封只写「想问下工控板价格」的信(零键)→
流照起 → A1 落负责人工位、等人、补全卡在对话流 → 答卡(customerCode/rfqRef)→
任务醒、合并、谱跑 3 步到验收。修一处接线错误:监听 deadline 要 ISO-8601
时长(P2D)不是时刻,传错整个补全监听静默没装上。
trigger/task/process 三份 CONTRACT.md 与产品设计.md 同步(宽进纪律成文)。
五闸绿;契约快照随本提交入库。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -9,8 +9,9 @@
|
||||
* 视觉语言分两个世界:**机器说 mono**(读数、灯、编号、TRACE),
|
||||
* **那封信说无衬线** —— 它是外界唯一伸进这台仪器的东西。
|
||||
* 戏眼是左栏的「抽键预检」读数屏:通道的正则**实时**跑在正打的字上
|
||||
* (与服务端同一套规则,契约原文回传,不另编一套),
|
||||
* 该抽的键都点了灯,起单钮才上膛 —— 从前要投出去才知道「抽不出客户码」,现在打字就知道。
|
||||
* (与服务端同一套规则,契约原文回传,不另编一套)。
|
||||
* 预检是**信息**,不是闸(宽进,2026-07-30):抽不全照样起 —— 缺的键进活动后
|
||||
* 由受理归一派补全单收齐;灯只告诉你「哪些会当场带上、哪些要有人补」。
|
||||
*/
|
||||
import { useMemo, useState } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
@@ -177,12 +178,13 @@ export function SimulateRun({ nav }: { nav: Nav }): ReactNode {
|
||||
);
|
||||
const required = requiredOf(pkg);
|
||||
const ruleKeys = new Set((channel?.extract ?? []).map((r) => r.key));
|
||||
/** 必填但既不是信自带、又没有任何抽键规则 —— 投出去必死,预检就得说 */
|
||||
/** 必填但既不是信自带、又没有任何抽键规则 —— 这键只能靠受理补全单找人收 */
|
||||
const unruled = required.filter((k) => !MAIL_BUILTIN.has(k) && !ruleKeys.has(k));
|
||||
const mustCatch = required.filter((k) => ruleKeys.has(k));
|
||||
const armed =
|
||||
pkg !== undefined && channel !== undefined && unruled.length === 0 && mustCatch.every((k) => caught.has(k));
|
||||
// 宽进:预检不再决定上不上膛 —— 抽不全照样起,缺的进活动后补全单收齐
|
||||
const keysReady = mustCatch.every((k) => caught.has(k)) && unruled.length === 0;
|
||||
const fireable = pkg !== undefined && channel !== undefined && mail.from.trim() !== '' && mail.subject.trim() !== '';
|
||||
const armed = fireable;
|
||||
|
||||
const run = useMutation({
|
||||
mutationFn: (input: { packageId: string; mail: MailDraft; assignments: Record<string, string> }) =>
|
||||
@@ -302,13 +304,13 @@ export function SimulateRun({ nav }: { nav: Nav }): ReactNode {
|
||||
{k}
|
||||
<em>必填</em>
|
||||
</span>
|
||||
<span className="v">无抽键规则</span>
|
||||
<span className="v">无规则 · 靠补全</span>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<p className="tb-well-t">与服务端同一套正则 —— 灯全亮,这封信才装配得出调用入参。</p>
|
||||
<p className="tb-well-t">与服务端同一套正则。灯灭不拦起单 —— 抽不出的键由受理补全单找人收齐。</p>
|
||||
</aside>
|
||||
|
||||
{/* ── 右:① 一封信 ② 派给谁 + 起单条 ── */}
|
||||
@@ -383,12 +385,10 @@ export function SimulateRun({ nav }: { nav: Nav }): ReactNode {
|
||||
<span className="say">
|
||||
{error !== undefined ? (
|
||||
<b className="bad">{error}</b>
|
||||
) : armed ? (
|
||||
'装配齐了 —— 起单会先销毁上一单,任务真实落到被指派人的工作台'
|
||||
) : unruled.length > 0 ? (
|
||||
`必填的 ${unruled.join('、')} 没有抽键规则 —— 投出去会被退回`
|
||||
) : keysReady ? (
|
||||
'键全抽得出 —— 起单会先销毁上一单,任务真实落到被指派人的工作台'
|
||||
) : (
|
||||
'预检还有灯没亮 —— 主题或正文里要写出能被抽中的键'
|
||||
`有键抽不出(${mustCatch.filter((k) => !caught.has(k)).concat(unruled).join('、')})—— 照样能起,受理人会收到补全单`
|
||||
)}
|
||||
</span>
|
||||
<button className={'tb-go' + (armed ? ' armed' : '')} disabled={!fireable || run.isPending} onClick={fire}>
|
||||
|
||||
Reference in New Issue
Block a user