Archived
feat(w05): 差异评估活动包骨架与清单 —— 六张主数据追加声明、三张自持单据、两张 DMN、五条样例
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FJcX8A6TWp4GRs41TYryEh
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
/** 占位:真正的案例模型见 Task 4(整份替换本文件)。这里只放一份 schema 过得去的最小件。 */
|
||||
export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL">
|
||||
<case id="part-substitution-assessment">
|
||||
<caseFileModel/>
|
||||
<casePlanModel id="plan" name="差异评估" autoComplete="true">
|
||||
<humanTask id="S0_bind" name="占位"/>
|
||||
</casePlanModel>
|
||||
</case>
|
||||
</definitions>`;
|
||||
export const NARRATIVE = '(占位:案例说明书见 Task 4)';
|
||||
export const ASSESSMENT_STEPS: Array<{ item: string; intent: string; guide: string; writes: string[]; reads: string[]; tools: string[]; view?: string; doneWhen?: string }> = [];
|
||||
export const ASSESSMENT_PHASES: Array<{ name: string; items: string[] }> = [];
|
||||
@@ -0,0 +1,8 @@
|
||||
/** 清单与案例模型共用的常量 —— 单独成文件,免得 manifest ↔ case-model 循环 import 撞 TDZ。 */
|
||||
export const ACTIVITY_ID_TEXT = 'part-substitution-assessment';
|
||||
/** 活动自持单据的域名 */
|
||||
export const D = 'sub_eval';
|
||||
/** GOV 差异规则版本 —— 随包冻结,随每条结论落账(rule_version) */
|
||||
export const RULE_VERSION = 'R-2026.07';
|
||||
export const TOOL_COMPARE = 'part-substitution-assessment.compare-parameters';
|
||||
export const TOOL_RECOMMEND = 'part-substitution-assessment.recommend';
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* ACT-WORK-05 · 差异评估 —— 活动包代码入口(ADR-004:默认导出 defineActivity 的返回值)。
|
||||
*
|
||||
* 这个活动没有 `run`。案例活动的推进由 Loop 驱动,行为全部写在清单里:
|
||||
* 案例模型 + 说明书 + 步骤剧本 + 三条技能 + 台账声明 + 五张 HTML 报告模版。
|
||||
* 这个文件之所以还存在,是因为清单的 `entry` 指着它 ——
|
||||
* 它是**被静态审扫描的产物文本**(查 import 与全局引用,证明活动包零环境能力)。
|
||||
*/
|
||||
import { defineActivity } from '@sdk';
|
||||
|
||||
export default defineActivity({ name: 'part-substitution-assessment' });
|
||||
@@ -0,0 +1,558 @@
|
||||
/**
|
||||
* 活动包清单 —— ACT-WORK-05 差异评估(案例活动)。
|
||||
*
|
||||
* 业务出处:`docs/SS02-ACT-WORK-05-html/`(活动流程与工单说明 / 一键启动入参说明 /
|
||||
* 《SS02-SS03-SS04_Demo台账字段细化与统一方案》/ 四份目标件 / 全流程分支图)。
|
||||
* 骨架照 E04 / I01 已验证的样子:双级入参 → 活动自持单据 + 共享主数据 → decide 循环做中间审核 →
|
||||
* 终局放行。三处与前两个活动刻意不同,写在这里免得下一个读者当成疏漏:
|
||||
* ① **主数据是六张(P-08/P-10/P-01/P-11 主+子/P-06)**,物理表由平台基线
|
||||
* (`apps/api/src/app/seed-mvp-data.ts`)物化,这里只**追加**本活动要用的列与标记
|
||||
* (required / tally / refersTo / requiredWhen 是活动的授权面;声明合并是纯 add-column)。
|
||||
* ② **数据工单**(PN 不在库时让人从零建整条主数据)= 零输入执行工单 + 工作区的件
|
||||
* (`mine:parts.profile` / `mine:parts.document` 可加行),确认落草稿层、放行并入正式表
|
||||
* (守铁律②;文档里「确认即入库」在本平台落成「无审核环节、放行并入」)。一张表一张单。
|
||||
* ③ 逐项四态与推荐等级**由两枚纯工具算**(规则同时以 D01/D02 两张 DMN 随包冻结当文档),
|
||||
* 模型只负责对照表、证据与影响说明 —— 硬约束 MISMATCH 不可能被措辞改判。
|
||||
*/
|
||||
import type { ActivityManifest, DmnModel } from '@contracts';
|
||||
import { activityIdSchema } from '@contracts';
|
||||
import { ASSESSMENT_SKILLS } from './skills.ts';
|
||||
import { CMMN_XML, NARRATIVE, ASSESSMENT_PHASES, ASSESSMENT_STEPS } from './case-model.ts';
|
||||
import { P08_MASTER_CREATE, P10_DOCUMENT_CREATE, EVAL_INPUT_LIST, DIFFERENCE_REVIEW, LANDING_CONFIRM } from './views/report.ts';
|
||||
import { ACTIVITY_ID_TEXT, D, TOOL_COMPARE, TOOL_RECOMMEND } from './constants.ts';
|
||||
|
||||
export { D, RULE_VERSION, TOOL_COMPARE, TOOL_RECOMMEND } from './constants.ts';
|
||||
const ACTIVITY_ID = activityIdSchema.parse(ACTIVITY_ID_TEXT);
|
||||
|
||||
/**
|
||||
* 两张判定表 —— **文档形态**(求值器在 `apps/api/src/app/tools/first-party.ts`,与这里同一份规则):
|
||||
* D01 逐参数四态、D02 推荐等级。都是 FIRST:规则从上往下第一条命中即出结论,顺序就是优先级。
|
||||
*/
|
||||
export const D01_DMN: DmnModel['tables'][number] = {
|
||||
id: 'd01-difference-status',
|
||||
label: '差异四态 D01(R-2026.07)',
|
||||
hitPolicy: 'FIRST',
|
||||
inputs: [
|
||||
{ name: 'compare_result', expression: 'compare_result' },
|
||||
{ name: 'constraint_type', expression: 'constraint_type' },
|
||||
{ name: 'evidence_present', expression: 'evidence_present' },
|
||||
],
|
||||
outputs: [{ name: 'difference_status' }, { name: 'risk_level' }],
|
||||
rules: [
|
||||
{ id: 'd01-u', when: ['"unknown"', '-', '-'], then: ['unknown', 'medium'], annotation: '一侧未披露 → UNKNOWN,挂验证任务' },
|
||||
{ id: 'd01-m0', when: ['"equal","covers"', '"hard"', 'false'], then: ['conditional', 'medium'], annotation: '硬约束缺证据不判 MATCH' },
|
||||
{ id: 'd01-m', when: ['"equal","covers"', '-', '-'], then: ['match', 'low'], annotation: '一致或候选范围盖住原范围' },
|
||||
{ id: 'd01-c', when: ['"configurable","partial"', '-', '-'], then: ['conditional', 'medium'], annotation: '可调配成 / 部分重叠 → 条件' },
|
||||
{ id: 'd01-x', when: ['"narrower","differs"', '"hard"', '-'], then: ['mismatch', 'high'], annotation: '硬约束不一致' },
|
||||
{ id: 'd01-s', when: ['"narrower","differs"', '"soft"', '-'], then: ['conditional', 'medium'], annotation: '软约束不一致 → 评估影响' },
|
||||
],
|
||||
};
|
||||
|
||||
export const D02_DMN: DmnModel['tables'][number] = {
|
||||
id: 'd02-recommendation',
|
||||
label: '推荐等级 D02(R-2026.07)',
|
||||
hitPolicy: 'FIRST',
|
||||
inputs: [
|
||||
{ name: 'hard_mismatch_total', expression: 'hard_mismatch_total' },
|
||||
{ name: 'unknown_total', expression: 'unknown_total' },
|
||||
{ name: 'configurable_total', expression: 'configurable_total' },
|
||||
{ name: 'conditional_total', expression: 'conditional_total' },
|
||||
],
|
||||
outputs: [{ name: 'recommendation_level' }],
|
||||
rules: [
|
||||
{ id: 'd02-n', when: ['> 0', '-', '-', '-'], then: ['not_recommended'], annotation: '硬约束失败直接不推荐' },
|
||||
{ id: 'd02-u', when: ['0', '> 0', '-', '-'], then: ['conditional'], annotation: '有 UNKNOWN → 等验证闭环' },
|
||||
{ id: 'd02-a', when: ['0', '0', '> 0', '-'], then: ['alternate'], annotation: '只靠改外围配得成 → 备选' },
|
||||
{ id: 'd02-c', when: ['0', '0', '0', '> 0'], then: ['conditional'], annotation: '其余条件项 → 条件推荐' },
|
||||
{ id: 'd02-r', when: ['0', '0', '0', '0'], then: ['recommended'], annotation: '全 MATCH → 推荐' },
|
||||
],
|
||||
};
|
||||
|
||||
export const ASSESSMENT_DMN: DmnModel = {
|
||||
drd: [
|
||||
{ decision: 'd01-difference-status', requires: [], inputs: ['compare_result', 'constraint_type', 'evidence_present'] },
|
||||
{ decision: 'd02-recommendation', requires: [], inputs: ['hard_mismatch_total', 'unknown_total', 'configurable_total', 'conditional_total'] },
|
||||
],
|
||||
tables: [D01_DMN, D02_DMN],
|
||||
};
|
||||
|
||||
/** 字段声明的短写(与 E04 同一套写法:显式 primaryKey / masked,其余按需) */
|
||||
type F = { name: string; type: 'text' | 'number' | 'integer' | 'boolean' | 'date' | 'timestamp' | 'json'; label: string; primaryKey?: boolean; required?: boolean; expect?: boolean; tally?: string[]; auto?: boolean; distinct?: boolean; refersTo?: string; requiredWhen?: { column: string; value: string } };
|
||||
const col = (f: F) => ({ primaryKey: false, masked: false, ...f });
|
||||
|
||||
export const manifest: ActivityManifest = {
|
||||
identity: {
|
||||
activityId: ACTIVITY_ID,
|
||||
name: 'ACT-WORK-05 · 差异评估',
|
||||
packageVersion: '1.0.0',
|
||||
category: '需求分析与方案匹配',
|
||||
sdkVersion: '0.1.0-m5',
|
||||
checksum: 'dev-checksum-part-substitution-assessment',
|
||||
},
|
||||
|
||||
/* ── 契约七件 ────────────────────────────────────────────────── */
|
||||
contract: {
|
||||
activityId: ACTIVITY_ID,
|
||||
version: '1.0.0',
|
||||
input: {
|
||||
/*
|
||||
* 一键启动只收 4 项(文档《一键启动入参说明》极简原则):客户 / 客户需求 / 原 PN / 候选 PN 数组。
|
||||
* 应用场景、BOM 位、替代类型、规则版本、负责人期限**不是入参** —— 启动后在执行工单 01 里补。
|
||||
* datasheet 选填文件:传了 AI 在数据工单件上预填,没传人全填(文档 0c:同为合法路径)。
|
||||
* `customerName` 只在客户不在库(分支 0a)时用:S0 发数据录入卡建档,名字先摆上去让人核。
|
||||
*/
|
||||
data: {
|
||||
type: 'object',
|
||||
required: ['customerCode', 'requestDescription', 'originalInternalPn', 'candidateInternalPns'],
|
||||
properties: {
|
||||
customerCode: { type: 'string', title: '客户编码(L-02 客户主台账)', examples: ['CU-0142'] },
|
||||
customerName: { type: 'string', title: '客户名称(仅客户不在库时填,启动后先建档)', examples: ['深圳恒星电源有限公司'] },
|
||||
requestDescription: {
|
||||
type: 'string',
|
||||
title: '客户需求(原始描述 —— 据此新建 P-01 需求主记录 · 版本 V1)',
|
||||
examples: ['工业电源项目在用 LM2596S-5.0(TI,12V→5V),原厂 PCN 转 NRND,客户要求验证替代料'],
|
||||
},
|
||||
originalInternalPn: { type: 'string', title: '原内部料号(P-08)', examples: ['LM2596S-5.0'] },
|
||||
candidateInternalPns: {
|
||||
type: 'array',
|
||||
title: '候选内部料号(数组,≥1;不在库的启动后经数据工单创建)',
|
||||
items: { type: 'string' },
|
||||
examples: [['XL2596S-5.0', 'LM2596S-ADJ']],
|
||||
},
|
||||
datasheets: {
|
||||
type: 'array',
|
||||
format: 'file',
|
||||
title: '候选 datasheet(选填;传了 AI 在数据工单里预填,没传人全填)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', title: '文件名' },
|
||||
contentType: { type: 'string', title: '类型' },
|
||||
size: { type: 'integer', title: '字节数' },
|
||||
ref: { type: 'string', title: '存档号' },
|
||||
text: { type: 'string', title: '读出来的文本' },
|
||||
note: { type: 'string', title: '读取说明' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
messageAllowed: true,
|
||||
messageHint: 'FAE 可在此说明客户的应用边界、验证条件或本轮评估的特殊要求',
|
||||
},
|
||||
output: {
|
||||
/* 单一必发口:本活动终点是 ACT-GATE-03 评审放行;下游 ACT-WORK-08 尚未实现,按「契约先立」声明 */
|
||||
mandatory: [
|
||||
{
|
||||
name: 'assessment-released',
|
||||
description: '差异评估结论已落账(P-11 替代关系主记录 + 差异明细、P-01 回写)—— 供 ACT-WORK-08 证据归集消费;当前无消费者,载荷 = 已落地事实',
|
||||
payload: {
|
||||
type: 'object',
|
||||
required: ['evalRef', 'requestId', 'relationTotal'],
|
||||
properties: {
|
||||
evalRef: { type: 'string', title: '评估案号' },
|
||||
requestId: { type: 'string', title: '需求编号(P-01)' },
|
||||
relationTotal: { type: 'integer', title: '落账的替代关系条数' },
|
||||
recommendedTotal: { type: 'integer', title: '推荐' },
|
||||
conditionalTotal: { type: 'integer', title: '条件推荐' },
|
||||
alternateTotal: { type: 'integer', title: '备选' },
|
||||
notRecommendedTotal: { type: 'integer', title: '不推荐' },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
selective: [],
|
||||
},
|
||||
requiredDeliverables: [
|
||||
{ key: 'eval-input', label: '评估输入清单', ledger: `${D}.input_gap`, ledgers: [`${D}.input_gap`, `${D}.pn_check`, `${D}.verdict`], editableAtRelease: [] },
|
||||
{ key: 'difference-items', label: '差异评估结论(P-11 差异明细)', ledger: 'substitution.difference_item', ledgers: ['substitution.difference_item', 'substitution.relation'], editableAtRelease: [] },
|
||||
{ key: 'landing', label: '拟落账内容(P-11 主记录 + P-01 回写)', ledger: 'substitution.relation', ledgers: ['substitution.relation', 'demand.request'], editableAtRelease: [] },
|
||||
],
|
||||
/*
|
||||
* 验收判据 —— L1/L2 只读基座读数;L4 三条是文档验收工单 01/02 里留给人的裁量;
|
||||
* 「推荐等级与明细一致」「硬约束不判 MATCH」机器已由工具守住,这里仍留 L4 让签字的人过目。
|
||||
*/
|
||||
acceptanceCriteria: [
|
||||
{
|
||||
key: 'input-confirmed',
|
||||
label: '评估输入已经 PW 补齐确认:执行工单 01 已交回,缺口行的补充内容都有,补不了的留了 UNKNOWN',
|
||||
level: 'L1',
|
||||
predicate: `input_confirmed_total > 0 and ${D}_input_gap_rows > 0 and ${D}_input_gap_missing = 0`,
|
||||
},
|
||||
{
|
||||
key: 'review-approved',
|
||||
label: '差异评估结论已经 FAE/RI 审核通过:验收工单 01 已过,不处在驳回待改状态',
|
||||
level: 'L1',
|
||||
predicate: 'review_approved_total > 0 and substitution_difference_item_rows > 0 and substitution_difference_item_missing = 0',
|
||||
},
|
||||
{
|
||||
key: 'every-candidate-assessed',
|
||||
label: '每个未放弃的候选都有一条替代关系记录(N 候选 = N 条 P-11,并行独立落账)',
|
||||
level: 'L2',
|
||||
predicate: `substitution_relation_rows > 0 and substitution_relation_rows + ${D}_pn_check_exists_status_abandoned + 1 = ${D}_pn_check_rows`,
|
||||
},
|
||||
{
|
||||
key: 'every-item-has-status',
|
||||
label: '关键参数差异均有明确状态:每条明细都是 MATCH / MISMATCH / UNKNOWN / CONDITIONAL 之一',
|
||||
level: 'L2',
|
||||
predicate:
|
||||
'substitution_difference_item_rows > 0 and substitution_difference_item_difference_status_match + substitution_difference_item_difference_status_mismatch + ' +
|
||||
'substitution_difference_item_difference_status_unknown + substitution_difference_item_difference_status_conditional = substitution_difference_item_rows',
|
||||
},
|
||||
{
|
||||
key: 'unknown-has-disposition',
|
||||
label: 'UNKNOWN 项均有去向:每个 UNKNOWN 都挂了验证任务或补来源动作,影响说明与证据引用逐条齐全',
|
||||
level: 'L2',
|
||||
predicate: 'substitution_difference_item_missing = 0',
|
||||
},
|
||||
{
|
||||
key: 'judged-by-rule',
|
||||
label: '四态与推荐等级由规则算出:逐参数比对工具与推荐定级工具都真调过',
|
||||
level: 'L2',
|
||||
predicate: `tool_part_substitution_assessment_compare_parameters_calls > 0 and tool_part_substitution_assessment_recommend_calls > 0`,
|
||||
},
|
||||
{
|
||||
key: 'analysis-status-confirmed',
|
||||
label: '分析状态与客户批准状态分离:每条 P-11 只写分析层 analysis_status = FAE 确认,规则版本已锁定随结论落账',
|
||||
level: 'L2',
|
||||
predicate: 'substitution_relation_analysis_status_fae_confirmed = substitution_relation_rows and substitution_relation_missing = 0',
|
||||
},
|
||||
{
|
||||
key: 'p01-writeback-complete',
|
||||
label: '回写 P-01 字段完整:风险等级、验证要求、输出链接、下一步动作、当前阶段五项齐全',
|
||||
level: 'L2',
|
||||
predicate: 'demand_request_rows > 0 and demand_request_missing = 0',
|
||||
},
|
||||
{ key: 'evidence-credible', label: '证据链可信:每条结论都能追溯到 P-08 / P-10 / P-06 的具体记录,或本单数据工单的创建记录(人工录入)', level: 'L4' },
|
||||
{ key: 'hard-not-auto-match', label: '硬约束项未被自动判 MATCH:封装 / 工艺 / 电气边界等硬约束的 MATCH 都有证据支持', level: 'L4' },
|
||||
{ key: 'recommendation-consistent', label: '推荐等级与差异明细一致:存在 UNKNOWN / CONDITIONAL 的候选没有给「推荐」', level: 'L4' },
|
||||
/* L5 是责任关:那张卡本身就是这道关,卡面上不摆它(基座统一滤) */
|
||||
{ key: 'gate03-release', label: 'ACT-GATE-03 评审放行:签下去 P-11 落账、P-01 回写,结论供下游消费', level: 'L5' },
|
||||
],
|
||||
revisableAtRelease: [],
|
||||
prohibitions: [
|
||||
{ key: 'no-fabricated-parameter', statement: 'UNKNOWN 是一等公民:查不到的参数值必须显式写「未披露」并记去向,不得伪造、不得用估计值或空串冒充' },
|
||||
{ key: 'no-override-hard-mismatch', statement: '硬约束 MISMATCH 不得被任何措辞改判 MATCH;要改只能补证据后重比' },
|
||||
{ key: 'no-customer-approval-write', statement: '客户批准状态由独立审批流维护,本活动不写 customer_approval_status(没有这一列的写权)' },
|
||||
{ key: 'no-commercial-judgement', statement: '本活动只答技术可替代性:不做数量、价格、交期、采购判断' },
|
||||
{ key: 'no-rebind', statement: '启动绑定的 4 项(客户 / 需求 / 原 PN / 候选数组)工单内不可改;绑错只能退回触发方重新启动' },
|
||||
{ key: 'no-external-write', statement: '放行前不得触达外部系统 —— 触达世界的动作集中在放行那一刻' },
|
||||
],
|
||||
authorization: {
|
||||
discretion: [],
|
||||
preauthorizedActions: [],
|
||||
/*
|
||||
* 基线:数据工单 ≤2 + 候选放弃/创建决策 ≤2 + 新客户录入卡 1 + 执行单 01 + 审核决策 1 + 放行 1 = 8;
|
||||
* 审核循环预留两轮(+4)= 12。cap 必须严格大于最后一次拍板时的实际次数(E04 实测栽过),留到 16。
|
||||
*/
|
||||
humanCallBudget: 16,
|
||||
},
|
||||
resources: {
|
||||
tools: [{ name: TOOL_COMPARE }, { name: TOOL_RECOMMEND }],
|
||||
skills: ASSESSMENT_SKILLS.map((s) => s.id),
|
||||
models: ['deepseek-v4-flash'],
|
||||
roles: ['FAE/RI'],
|
||||
budget: { steps: 90, tokens: 600_000, activeMinutes: 120, humanCalls: 16 },
|
||||
},
|
||||
},
|
||||
|
||||
/* ── 声明 ────────────────────────────────────────────────────── */
|
||||
declarations: {
|
||||
models: ['deepseek-v4-flash'],
|
||||
tools: [{ name: TOOL_COMPARE }, { name: TOOL_RECOMMEND }],
|
||||
ledger: [
|
||||
/* L-02 客户主台账:只在分支 0a(客户不在库)由数据录入卡建一行 —— 录入卡写的列都得声明 */
|
||||
{
|
||||
domain: 'customer',
|
||||
object: 'profile',
|
||||
kind: 'master',
|
||||
label: '客户档案',
|
||||
fields: [
|
||||
col({ name: 'customer_code', type: 'text', label: '客户编码', primaryKey: true }),
|
||||
col({ name: 'name', type: 'text', label: '客户名称', required: true }),
|
||||
col({ name: 'industry', type: 'text', label: '所属行业', expect: true }),
|
||||
col({ name: 'region', type: 'text', label: '区域', expect: true }),
|
||||
],
|
||||
},
|
||||
/* P-08 芯片主数据:数据工单建整条;参数集是它的一个字段(一张表,不建子表) */
|
||||
{
|
||||
domain: 'parts',
|
||||
object: 'profile',
|
||||
kind: 'master',
|
||||
label: '芯片主数据(P-08)',
|
||||
fields: [
|
||||
col({ name: 'internal_pn', type: 'text', label: '内部料号', primaryKey: true }),
|
||||
col({ name: 'brand_name', type: 'text', label: '品牌名称', required: true }),
|
||||
col({ name: 'manufacturer_name', type: 'text', label: '制造商名称', expect: true }),
|
||||
col({ name: 'specification_description', type: 'text', label: '规格描述', required: true }),
|
||||
col({ name: 'normalized_parameter_set', type: 'text', label: '标准化参数集(一行一参数:参数名 | 值 | 单位 | hard/soft | 说明)', required: true }),
|
||||
col({ name: 'package', type: 'text', label: '封装', required: true }),
|
||||
col({ name: 'process_technology', type: 'text', label: '工艺', expect: true }),
|
||||
col({ name: 'lifecycle_status', type: 'text', label: '生命周期状态', required: true, tally: ['active', 'nrnd', 'eol', 'discontinued', 'risk'] }),
|
||||
col({ name: 'eol_pcn_status', type: 'text', label: 'EOL/PCN 状态', expect: true }),
|
||||
col({ name: 'datasheet_ref', type: 'text', label: 'Datasheet 引用', expect: true }),
|
||||
col({ name: 'datasheet_version', type: 'text', label: 'Datasheet 版本', expect: true }),
|
||||
col({ name: 'application_scenarios', type: 'text', label: '适配应用场景', expect: true }),
|
||||
],
|
||||
},
|
||||
/* P-10 原厂技术资料库:数据工单建;评估时只读做证据回链 */
|
||||
{
|
||||
domain: 'parts',
|
||||
object: 'document',
|
||||
kind: 'master',
|
||||
label: '原厂技术资料库(P-10)',
|
||||
fields: [
|
||||
col({ name: 'internal_pn', type: 'text', label: '关联内部料号', primaryKey: true }),
|
||||
col({ name: 'doc_seq', type: 'text', label: '资料序号(同一料号下 01/02…)', primaryKey: true }),
|
||||
col({ name: 'document_type', type: 'text', label: '资料类型', required: true, tally: ['datasheet', 'app_note', 'reference_design', 'white_paper', 'sample_doc', 'commitment_letter', 'other'] }),
|
||||
col({ name: 'brand_name', type: 'text', label: '品牌名称', expect: true }),
|
||||
col({ name: 'document_title', type: 'text', label: '资料标题', required: true }),
|
||||
col({ name: 'document_version', type: 'text', label: '资料版本', required: true }),
|
||||
col({ name: 'document_ref', type: 'text', label: '资料文件链接', required: true }),
|
||||
col({ name: 'citation_locator', type: 'text', label: '证据定位(页码/章节/表格)', expect: true }),
|
||||
col({ name: 'document_status', type: 'text', label: '资料状态', required: true, tally: ['valid', 'expired', 'pending', 'void'] }),
|
||||
],
|
||||
},
|
||||
/* P-01 需求主记录:S0 新建 V1,放行时回写五项(current_stage = gate03 那一刻五项必填) */
|
||||
{
|
||||
domain: 'demand',
|
||||
object: 'request',
|
||||
kind: 'master',
|
||||
label: '客户需求主记录(P-01)',
|
||||
fields: [
|
||||
col({ name: 'request_id', type: 'text', label: '需求编号', primaryKey: true }),
|
||||
col({ name: 'customer_code', type: 'text', label: '客户编码', required: true, refersTo: 'customer.profile.customer_code' }),
|
||||
col({ name: 'requirement_version', type: 'text', label: '需求版本', required: true }),
|
||||
col({ name: 'request_type', type: 'text', label: '需求类型', required: true }),
|
||||
col({ name: 'request_description', type: 'text', label: '需求描述', required: true }),
|
||||
col({ name: 'input_snapshot_ref', type: 'text', label: '输入快照', required: true }),
|
||||
col({ name: 'original_internal_pn', type: 'text', label: '原内部料号', required: true }),
|
||||
col({ name: 'bom_position', type: 'text', label: 'BOM 位', expect: true }),
|
||||
col({ name: 'application_scenario', type: 'text', label: '应用场景', expect: true }),
|
||||
col({ name: 'technical_constraints', type: 'text', label: '技术约束' }),
|
||||
col({ name: 'risk_level', type: 'text', label: '风险等级', tally: ['low', 'medium', 'high', 'critical'], requiredWhen: { column: 'current_stage', value: 'gate03' } }),
|
||||
col({ name: 'verification_requirement', type: 'text', label: '验证/复测/确认要求', requiredWhen: { column: 'current_stage', value: 'gate03' } }),
|
||||
col({ name: 'request_status', type: 'text', label: '需求状态', required: true, tally: ['pending', 'responded', 'won', 'abandoned', 'closed'] }),
|
||||
col({ name: 'current_activity', type: 'text', label: '当前 Activity', requiredWhen: { column: 'current_stage', value: 'gate03' } }),
|
||||
col({ name: 'current_stage', type: 'text', label: '当前阶段' }),
|
||||
col({ name: 'next_action', type: 'text', label: '下一步动作', requiredWhen: { column: 'current_stage', value: 'gate03' } }),
|
||||
col({ name: 'output_ref', type: 'text', label: '标准输出链接', requiredWhen: { column: 'current_stage', value: 'gate03' } }),
|
||||
],
|
||||
},
|
||||
/* P-11 替代关系主记录:一候选一条;两枚 PN 都是引用墙(查无此料当场拒 —— 存在性检查的硬门) */
|
||||
{
|
||||
domain: 'substitution',
|
||||
object: 'relation',
|
||||
kind: 'master',
|
||||
label: '替代关系主记录(P-11)',
|
||||
fields: [
|
||||
col({ name: 'substitution_relation_id', type: 'text', label: '替代关系ID', primaryKey: true }),
|
||||
col({ name: 'eval_ref', type: 'text', label: '评估案号', required: true }),
|
||||
col({ name: 'request_id', type: 'text', label: '关联需求编号', required: true }),
|
||||
col({ name: 'bom_position', type: 'text', label: '关联 BOM 位', expect: true }),
|
||||
col({ name: 'original_internal_pn', type: 'text', label: '原内部料号', required: true, refersTo: 'parts.profile.internal_pn' }),
|
||||
col({ name: 'candidate_internal_pn', type: 'text', label: '候选内部料号', required: true, refersTo: 'parts.profile.internal_pn' }),
|
||||
col({ name: 'substitution_type', type: 'text', label: '替代类型', required: true, tally: ['pin_to_pin', 'functional', 'parametric', 'other'] }),
|
||||
col({ name: 'recommendation_level', type: 'text', label: '推荐等级', required: true, tally: ['recommended', 'conditional', 'alternate', 'not_recommended'] }),
|
||||
col({ name: 'recommendation_reason', type: 'text', label: '推荐理由', required: true }),
|
||||
col({ name: 'difference_summary', type: 'text', label: '差异摘要', required: true }),
|
||||
col({ name: 'risk_level', type: 'text', label: '替代风险等级', required: true, tally: ['low', 'medium', 'high', 'critical'] }),
|
||||
col({ name: 'verification_requirement', type: 'text', label: '验证要求', requiredWhen: { column: 'recommendation_level', value: 'conditional' } }),
|
||||
col({ name: 'validation_id', type: 'text', label: '关联验证ID' }),
|
||||
col({ name: 'analysis_status', type: 'text', label: '分析状态', required: true, tally: ['ai_draft', 'fae_confirmed', 'pending_customer', 'customer_approved', 'rejected', 'void'] }),
|
||||
col({ name: 'analysis_report_ref', type: 'text', label: '替代分析报告', expect: true }),
|
||||
col({ name: 'confidence_score', type: 'number', label: '证据可信度', expect: true }),
|
||||
col({ name: 'rule_version', type: 'text', label: '差异规则版本', required: true }),
|
||||
],
|
||||
},
|
||||
/* P-11 差异明细子表:一条参数一条记录;relation id 上 distinct 让基座数「几条关系有明细」 */
|
||||
{
|
||||
domain: 'substitution',
|
||||
object: 'difference_item',
|
||||
kind: 'master',
|
||||
label: '差异明细(P-11 子表)',
|
||||
fields: [
|
||||
col({ name: 'substitution_relation_id', type: 'text', label: '替代关系ID', primaryKey: true, distinct: true, refersTo: 'substitution.relation.substitution_relation_id' }),
|
||||
col({ name: 'difference_item_id', type: 'text', label: '差异明细ID', primaryKey: true }),
|
||||
col({ name: 'parameter_name', type: 'text', label: '标准参数名', required: true }),
|
||||
col({ name: 'original_value', type: 'text', label: '原物料参数值', required: true }),
|
||||
col({ name: 'candidate_value', type: 'text', label: '候选物料参数值', required: true }),
|
||||
col({ name: 'parameter_unit', type: 'text', label: '参数单位', expect: true }),
|
||||
col({ name: 'constraint_type', type: 'text', label: '约束类型', required: true, tally: ['hard', 'soft'] }),
|
||||
col({ name: 'compare_result', type: 'text', label: '比对结果(工具出参)', expect: true }),
|
||||
col({ name: 'difference_status', type: 'text', label: '差异状态', required: true, tally: ['match', 'mismatch', 'unknown', 'conditional'] }),
|
||||
col({ name: 'impact_statement', type: 'text', label: '影响说明(MATCH 写「无」)', required: true }),
|
||||
col({ name: 'unknown_disposition', type: 'text', label: 'UNKNOWN 去向(验证任务号 / 补来源动作)', requiredWhen: { column: 'difference_status', value: 'unknown' } }),
|
||||
col({ name: 'risk_level', type: 'text', label: '风险等级', required: true, tally: ['low', 'medium', 'high'] }),
|
||||
col({ name: 'evidence_ref', type: 'text', label: '差异证据引用', required: true }),
|
||||
col({ name: 'confidence', type: 'number', label: 'AI 置信度', expect: true }),
|
||||
col({ name: 'rule_version', type: 'text', label: '差异规则版本', required: true }),
|
||||
],
|
||||
},
|
||||
/* P-06 验证记录:事实类 —— 本活动只写「验证要求」(状态 not_started),不经数据工单建 */
|
||||
{
|
||||
domain: 'validation',
|
||||
object: 'record',
|
||||
kind: 'master',
|
||||
label: '芯片验证记录(P-06)',
|
||||
fields: [
|
||||
col({ name: 'validation_id', type: 'text', label: '验证ID', primaryKey: true }),
|
||||
col({ name: 'request_id', type: 'text', label: '关联需求编号', required: true }),
|
||||
col({ name: 'validation_internal_pn', type: 'text', label: '验证内部料号', required: true, refersTo: 'parts.profile.internal_pn' }),
|
||||
col({ name: 'validation_type', type: 'text', label: '验证类型', required: true, tally: ['pin_to_pin', 'functional', 'reliability', 'certification', 'retest'] }),
|
||||
col({ name: 'validation_status', type: 'text', label: '验证状态', required: true, tally: ['not_started', 'in_progress', 'passed', 'failed', 'conditional', 'pending_evidence'] }),
|
||||
col({ name: 'test_condition', type: 'text', label: '测试条件', expect: true }),
|
||||
col({ name: 'next_validation_action', type: 'text', label: '后续验证动作', required: true }),
|
||||
col({ name: 'evidence_ref', type: 'text', label: '验证证据引用', expect: true }),
|
||||
],
|
||||
},
|
||||
/* ── 活动自持单据(record,caseKey eval_ref)── */
|
||||
{
|
||||
domain: D,
|
||||
object: 'pn_check',
|
||||
kind: 'record',
|
||||
caseKey: 'eval_ref',
|
||||
label: '料号存在性核对',
|
||||
fields: [
|
||||
col({ name: 'eval_ref', type: 'text', label: '评估案号', primaryKey: true }),
|
||||
col({ name: 'internal_pn', type: 'text', label: '内部料号', primaryKey: true }),
|
||||
col({ name: 'role', type: 'text', label: '角色', required: true, tally: ['original', 'candidate'] }),
|
||||
col({ name: 'exists_status', type: 'text', label: '在库状态', required: true, tally: ['found', 'absent', 'created', 'abandoned'] }),
|
||||
col({ name: 'abandon_reason', type: 'text', label: '放弃原因', requiredWhen: { column: 'exists_status', value: 'abandoned' } }),
|
||||
col({ name: 'note', type: 'text', label: '备注' }),
|
||||
],
|
||||
},
|
||||
{
|
||||
domain: D,
|
||||
object: 'input_gap',
|
||||
kind: 'record',
|
||||
caseKey: 'eval_ref',
|
||||
label: '评估输入清单',
|
||||
fields: [
|
||||
col({ name: 'eval_ref', type: 'text', label: '评估案号', primaryKey: true }),
|
||||
col({ name: 'item_key', type: 'text', label: '输入项键', primaryKey: true }),
|
||||
col({ name: 'input_item', type: 'text', label: '输入项', required: true }),
|
||||
col({ name: 'source_ledger', type: 'text', label: '来源台账', expect: true }),
|
||||
col({ name: 'status', type: 'text', label: '状态', required: true, tally: ['gap', 'default', 'ok'] }),
|
||||
col({ name: 'detail', type: 'text', label: '绑定值 / 缺口说明', expect: true }),
|
||||
col({ name: 'supplement', type: 'text', label: '补充内容', requiredWhen: { column: 'status', value: 'gap' } }),
|
||||
col({ name: 'origin', type: 'text', label: '来源(ai/human-added)' }),
|
||||
],
|
||||
},
|
||||
{
|
||||
domain: D,
|
||||
object: 'verdict',
|
||||
kind: 'record',
|
||||
caseKey: 'eval_ref',
|
||||
label: '本次评估结论',
|
||||
fields: [
|
||||
col({ name: 'eval_ref', type: 'text', label: '评估案号', primaryKey: true }),
|
||||
col({ name: 'request_id', type: 'text', label: '需求编号' }),
|
||||
col({ name: 'rule_version', type: 'text', label: '规则版本' }),
|
||||
col({ name: 'original_internal_pn', type: 'text', label: '原内部料号' }),
|
||||
col({ name: 'candidate_total', type: 'integer', label: '候选数' }),
|
||||
col({ name: 'input_confirmed_total', type: 'integer', label: '评估输入已确认' }),
|
||||
col({ name: 'review_approved_total', type: 'integer', label: '差异结论已审核通过' }),
|
||||
col({ name: 'landing_prepared_total', type: 'integer', label: '拟落账已备好' }),
|
||||
col({ name: 'relation_total', type: 'integer', label: '替代关系条数' }),
|
||||
col({ name: 'recommended_total', type: 'integer', label: '推荐' }),
|
||||
col({ name: 'conditional_total', type: 'integer', label: '条件推荐' }),
|
||||
col({ name: 'alternate_total', type: 'integer', label: '备选' }),
|
||||
col({ name: 'not_recommended_total', type: 'integer', label: '不推荐' }),
|
||||
col({ name: 'summary', type: 'text', label: '本次评估结论' }),
|
||||
],
|
||||
},
|
||||
],
|
||||
listeners: [],
|
||||
harness: {
|
||||
memoryWindow: 24,
|
||||
stallAfter: 'PT4H',
|
||||
nudge: [{ after: 'PT2H', action: 'nudge' }],
|
||||
retry: { times: 2, backoff: 'PT30S' },
|
||||
},
|
||||
},
|
||||
|
||||
/* ── 资产初始版 ──────────────────────────────────────────────── */
|
||||
assets: {
|
||||
businessModel: { cmmnXml: CMMN_XML, narrative: NARRATIVE, dmn: ASSESSMENT_DMN, steps: ASSESSMENT_STEPS, phases: ASSESSMENT_PHASES },
|
||||
skills: ASSESSMENT_SKILLS,
|
||||
/*
|
||||
* 五个局面各一张:
|
||||
* · `p08-master-create` —— S0d 摆,数据工单(P-08),件上可加行、可编辑;
|
||||
* · `p10-document-create` —— S0d 摆,数据工单(P-10),同上;
|
||||
* · `eval-input-list` —— S1 摆,评估输入清单:绑定区只读 + 缺口表可编辑;
|
||||
* · `difference-review` —— S2/S3/S4 摆,差异明细 + 各候选推荐等级,只读;
|
||||
* · `landing-confirm` —— S5/T_release 摆,拟写 P-11 / 拟回写 P-01,只读;放行后即归档件。
|
||||
*/
|
||||
viewTemplates: [
|
||||
{ name: 'p08-master-create', html: P08_MASTER_CREATE },
|
||||
{ name: 'p10-document-create', html: P10_DOCUMENT_CREATE },
|
||||
{ name: 'eval-input-list', html: EVAL_INPUT_LIST },
|
||||
{ name: 'difference-review', html: DIFFERENCE_REVIEW },
|
||||
{ name: 'landing-confirm', html: LANDING_CONFIRM },
|
||||
],
|
||||
cardSchemas: {},
|
||||
distillHooks: [{ on: 'terminal', prompt: '归纳本次差异评估:哪些候选、各自推荐等级与关键依据、UNKNOWN 挂了什么验证任务、审核是否被驳回过(为什么)、数据工单建了哪些主数据。' }],
|
||||
},
|
||||
|
||||
/*
|
||||
* ── 样例集:五条形状真不同(常备案例即验收案例)──
|
||||
* 第一条满载且走「候选缺库 → 数据工单」那条最长的路(测试台落地即预填的是它);样例名 ≤14 字。
|
||||
* 库里只播了 LM2596S-5.0 / LM2596S-ADJ 两颗(见 seed-mvp-data.ts),XL2596S-5.0 / CN-DCDC-32V / LM2596S-3.3 刻意不在库。
|
||||
*/
|
||||
samples: [
|
||||
{
|
||||
name: '候选缺库·派数据工单',
|
||||
input: {
|
||||
customerCode: 'CU-0142',
|
||||
requestDescription: '工业电源项目在用 LM2596S-5.0(TI,12V→5V),原厂 PCN 转 NRND,客户要求验证替代料;应用 12V→5V、工业温域、输入上限 40V',
|
||||
originalInternalPn: 'LM2596S-5.0',
|
||||
candidateInternalPns: ['XL2596S-5.0', 'LM2596S-ADJ'],
|
||||
},
|
||||
expect: { ports: ['assessment-released'] },
|
||||
},
|
||||
{
|
||||
name: '都在库·常路',
|
||||
input: {
|
||||
customerCode: 'CU-0142',
|
||||
requestDescription: 'LM2596S-5.0 转 NRND,客户先评估同族可调版 LM2596S-ADJ 能否替代(愿意改外围);应用 12V→5V、工业温域',
|
||||
originalInternalPn: 'LM2596S-5.0',
|
||||
candidateInternalPns: ['LM2596S-ADJ'],
|
||||
},
|
||||
expect: { ports: ['assessment-released'] },
|
||||
},
|
||||
{
|
||||
name: '原PN缺库·硬阻塞',
|
||||
input: {
|
||||
customerCode: 'CU-0142',
|
||||
requestDescription: '客户另一块板在用 LM2596S-3.3(3.3V 固定版),同批 PCN,要求评估 LM2596S-ADJ 替代;应用 12V→3.3V',
|
||||
originalInternalPn: 'LM2596S-3.3',
|
||||
candidateInternalPns: ['LM2596S-ADJ'],
|
||||
},
|
||||
expect: { ports: ['assessment-released'] },
|
||||
},
|
||||
{
|
||||
name: '三候选·含硬约束不推荐',
|
||||
input: {
|
||||
customerCode: 'CU-0142',
|
||||
requestDescription: '工业电源 12V→5V,输入上限 40V,工业温域;三个候选:XL2596S-5.0、LM2596S-ADJ、国产 CN-DCDC-32V(输入上限只有 32V)',
|
||||
originalInternalPn: 'LM2596S-5.0',
|
||||
candidateInternalPns: ['XL2596S-5.0', 'LM2596S-ADJ', 'CN-DCDC-32V'],
|
||||
},
|
||||
expect: { ports: ['assessment-released'] },
|
||||
},
|
||||
{
|
||||
name: '新客户·先建档',
|
||||
input: {
|
||||
customerCode: 'CU-9001',
|
||||
customerName: '深圳恒星电源有限公司',
|
||||
requestDescription: '新客户口述:工业电源 12V→5V 在用 LM2596S-5.0,想评估 LM2596S-ADJ',
|
||||
originalInternalPn: 'LM2596S-5.0',
|
||||
candidateInternalPns: ['LM2596S-ADJ'],
|
||||
},
|
||||
expect: { ports: ['assessment-released'] },
|
||||
},
|
||||
],
|
||||
|
||||
entry: { file: 'index.js', checksum: 'dev-entry-part-substitution-assessment' },
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* ACT-WORK-05 差异评估的技能 —— 载方法,不载权力。
|
||||
* 剧本写「这一步做什么、写哪几列」,技能写「为什么这么判、拿不准时按哪条走」。
|
||||
*/
|
||||
import { skillIdSchema } from '@contracts';
|
||||
import type { Skill } from '@contracts';
|
||||
|
||||
export const ASSESSMENT_SKILLS: Skill[] = [
|
||||
{
|
||||
id: skillIdSchema.parse('substitution.normalize-parameters'),
|
||||
label: '把多来源资料整成可比对的标准参数对照表',
|
||||
applicability: '输入确认之后、逐项比对之前:原 PN 与每个候选的参数集要摆成同名同单位的一对一。',
|
||||
method: [
|
||||
'参数集在 `parts.profile.normalized_parameter_set` 里,一行一参数:`参数名 | 值 | 单位 | hard/soft | 说明`。',
|
||||
'对照表按**原 PN 的参数名**为骨架:原 PN 有的参数,候选没有对应行 → 候选值写「未披露」(它会成为 UNKNOWN);',
|
||||
'候选多出来的参数不比(比对只回答「能不能替」,多出来的能力不构成差异)。',
|
||||
'单位以原 PN 为准:候选单位不同先换算(mV→V、kHz→MHz),换算不了就不改值、在说明里写明 —— 不许静默改数。',
|
||||
'约束类型以原 PN 那一行的 hard/soft 为准;原 PN 没标的按 GOV 口径:封装 / 工艺 / 电气边界(电压 / 电流 / 温度 / 阈值)一律 hard,其余 soft。',
|
||||
'**缺参数不得伪造**:查不到的写「未披露」,不写估计值、不写「约」。',
|
||||
].join('\n'),
|
||||
crystallized: [],
|
||||
knowledgeRefs: ['标准化参数集行语法', '硬/软约束口径'],
|
||||
version: '1.0.0',
|
||||
},
|
||||
{
|
||||
id: skillIdSchema.parse('substitution.difference-judgement'),
|
||||
label: '逐项四态由工具算,证据与影响由你写',
|
||||
applicability: '对照表齐了,要出 MATCH / MISMATCH / UNKNOWN / CONDITIONAL 时。',
|
||||
method: [
|
||||
'四态**不许自己判**:调 `part-substitution-assessment.compare-parameters`,把每个候选的全部参数对一次性传进去(candidates 数组,一次一批)。',
|
||||
'每对参数都带 `evidenceRef`(值从哪份记录来:「P-08」「P-10 Rev H §6.5」「数据工单 <单号> · 确认人 <人>」)——硬约束没有证据引用,工具不判 MATCH。',
|
||||
'客户在应用场景里给了边界(如「输入上限 40V」)就写进那一对的 `requirement`(`>= 40`),候选不满足直接 MISMATCH。',
|
||||
'工具出参照抄进 `substitution.difference_item`:difference_status / compare_result / risk_level 一字不改;',
|
||||
'`impact_statement` 你写:MATCH 写「无」,其余说清对应用 / 验证 / 报价的影响;UNKNOWN 必须写 `unknown_disposition`(挂验证任务号或补来源动作),不许悬空。',
|
||||
'推荐等级同样由工具定(`part-substitution-assessment.recommend`,传每个候选的 counts),`recommendation_reason` 照工具的 reason 扩写,不许把不推荐改成推荐。',
|
||||
].join('\n'),
|
||||
crystallized: [],
|
||||
knowledgeRefs: ['四态口径', '推荐等级四档'],
|
||||
version: '1.0.0',
|
||||
},
|
||||
{
|
||||
id: skillIdSchema.parse('substitution.landing'),
|
||||
label: '落账只写分析层,客户批准不碰',
|
||||
applicability: '审核通过之后拟 P-11 / 回写 P-01 时。',
|
||||
method: [
|
||||
'`analysis_status` 从 `ai_draft` 推到 `fae_confirmed`;`customer_approval_status` 由独立审批流维护 —— 本活动**没有这一列的写权**,写了会被拒。',
|
||||
'P-01 回写五项:risk_level(取各候选里最高的)、verification_requirement(把所有验证任务号列全)、output_ref(替代分析报告号)、next_action(「提交 ACT-GATE-03 评审放行」)、current_stage 写 `gate03`。',
|
||||
'规则版本随结论落账:每条 P-11 主记录与差异明细都写 rule_version(工具出参的 ruleVersion),中途规则更新不影响进行中的评估。',
|
||||
'被打回重做时改的是**台账本身**(理由 / 依据 / 去向),不是把同一份原样再递一遍。',
|
||||
].join('\n'),
|
||||
crystallized: [],
|
||||
knowledgeRefs: ['analysis_status 与 customer_approval_status 分离'],
|
||||
version: '1.0.0',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* ACT-WORK-05 差异评估 · 视觉底 —— 逐字节抄自 I01(`brand-content-toolkit/src/views/prd-style.ts`)。
|
||||
*
|
||||
* 本活动的四份目标件(`docs/SS02-ACT-WORK-05-html/*.html`)与 E04 / I01 的目标件是同一套
|
||||
* 设计语言产出的(卡壳 / 卡头渐变 / 判据框 / 表格排版全同);`activities/*` 只准 import
|
||||
* `@sdk` / `@contracts`,包之间没有代码通道,只能各自持有一份。改这份不要改口径:
|
||||
* 数据驱动的开关(三档占位 / 缺项红带 / 状态徽 / 编辑闸)在 `report.ts` 的 `BIND_CSS`。
|
||||
*/
|
||||
|
||||
export const PRD_CSS = `
|
||||
.proto-root {
|
||||
--ink:#1f2329; --ink-2:#41464e; --ink-3:#646a73; --ink-4:#8f959e;
|
||||
--line:#dee0e3; --line-soft:#e8eaed; --soft:#f8f9fb;
|
||||
--brand:#3370ff; --brand-deep:#245bdb; --brand-soft:#e1eaff; --brand-wash:#f0f4ff;
|
||||
--human:#a9711e; --human-soft:#f6ecd8; --human-line:#e6d3a8;
|
||||
--ledger:#b23a2e; --ledger-soft:#f7e4e0; --ledger-line:#ecc5bd;
|
||||
--ok:#278717; --ok-soft:#e8f8e4;
|
||||
--danger:#d83931; --danger-soft:#feeae9;
|
||||
--warn-accent:#a9711e; --warn-soft:#f6ecd8;
|
||||
--mono: "SF Mono", Menlo, Consolas, monospace;
|
||||
--t-fast: .16s;
|
||||
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
color: var(--ink);
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.proto-root, .proto-root * { box-sizing: border-box; }
|
||||
|
||||
/* ==================== 卡壳(嵌进工作区,不自带浮起——外层已经是一张卡)==================== */
|
||||
.proto-root .jc-report { background: #fff; overflow: hidden; }
|
||||
|
||||
.proto-root .jc-rp-head { display: flex; align-items: center; gap: 14px; padding: 18px 22px; border-bottom: 2px solid var(--ink); }
|
||||
.proto-root .jc-report.tone-human .jc-rp-head { background: linear-gradient(120deg, #fffaf1 0%, #fff 55%, #fbf6ea 100%); }
|
||||
.proto-root .jc-report.tone-ledger .jc-rp-head { background: linear-gradient(120deg, #fdf3f1 0%, #fff 55%, #fbf2f0 100%); }
|
||||
|
||||
.proto-root .jc-rp-badge {
|
||||
flex: none; width: 44px; height: 44px; border-radius: 10px;
|
||||
display: grid; place-items: center; color: #fff; font-weight: 800; font-size: 13px;
|
||||
}
|
||||
.proto-root .jc-report.tone-human .jc-rp-badge { background: var(--human); box-shadow: 0 6px 16px rgba(169,113,30,.25); }
|
||||
.proto-root .jc-report.tone-ledger .jc-rp-badge { background: var(--ledger); box-shadow: 0 6px 16px rgba(178,58,46,.25); }
|
||||
|
||||
.proto-root .jc-rp-tit { flex: 1; min-width: 0; }
|
||||
.proto-root .jc-rp-tit em { font-style: normal; font-size: 11px; letter-spacing: .08em; color: var(--ink-4); font-weight: 700; }
|
||||
.proto-root .jc-rp-tit h3 { margin: 2px 0 0; font-size: 19px; font-weight: 800; line-height: 1.3; }
|
||||
.proto-root .jc-rp-sub { font-size: 12px; color: var(--ink-3); margin-top: 3px; }
|
||||
|
||||
.proto-root .jc-rp-side { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; flex: none; }
|
||||
/* 目标件 side 第二行:步骤(静态文案),同目标件 .step 的字号字色 */
|
||||
.proto-root .jc-rp-step { font-size: 12px; color: var(--ink-3); }
|
||||
/* 关联任务号:全长 UUID 如实给,排版上截尾 —— 截断是排版,不是数据 */
|
||||
.proto-root .jc-rp-taskid {
|
||||
display: inline-block; max-width: 150px; overflow: hidden; text-overflow: ellipsis;
|
||||
white-space: nowrap; vertical-align: bottom; font-family: var(--mono); font-size: 11px;
|
||||
}
|
||||
.proto-root .jc-rp-tag { font-size: 11px; font-weight: 800; padding: 2px 10px; border-radius: 999px; border: 1px solid; }
|
||||
.proto-root .jc-report.tone-human .jc-rp-tag { color: var(--human); background: var(--human-soft); border-color: var(--human-line); }
|
||||
.proto-root .jc-report.tone-ledger .jc-rp-tag { color: var(--ledger); background: var(--ledger-soft); border-color: var(--ledger-line); }
|
||||
.proto-root .jc-rp-wait { font-size: 12px; color: var(--ink-3); }
|
||||
.proto-root .jc-rp-wait.warn { color: var(--danger); font-weight: 700; }
|
||||
|
||||
.proto-root .jc-rp-bd { padding: 20px 22px; }
|
||||
|
||||
/* 【做什么】/【交回什么】这类分区小标——括号label + 破折号说明,同一行 */
|
||||
.proto-root .jc-rp-part { font-size: 11px; font-weight: 800; letter-spacing: .12em; color: var(--ink-4); margin: 20px 0 8px; }
|
||||
.proto-root .jc-rp-part:first-child { margin-top: 0; }
|
||||
.proto-root .jc-rp-part b { color: var(--ink-3); }
|
||||
|
||||
/* AI 已完成 / 需要你 —— 说明框,左侧色条按 tone 走 */
|
||||
.proto-root .jc-derive { border: 1px solid var(--line); border-left: 3px solid; border-radius: 8px; background: var(--soft); padding: 14px 16px; margin-bottom: 16px; }
|
||||
.proto-root .jc-report.tone-human .jc-derive { border-left-color: var(--human); }
|
||||
.proto-root .jc-report.tone-ledger .jc-derive { border-left-color: var(--ledger); }
|
||||
.proto-root .jc-derive p { margin: 0; font-size: 13px; color: var(--ink-2); }
|
||||
.proto-root .jc-derive p + p { margin-top: 6px; }
|
||||
.proto-root .jc-derive b { color: var(--ink); }
|
||||
.proto-root .jc-derive .done { color: var(--ok); font-weight: 700; }
|
||||
.proto-root .jc-report.tone-human .jc-derive .need { color: var(--human); font-weight: 700; }
|
||||
.proto-root .jc-report.tone-ledger .jc-derive .need { color: var(--ledger); font-weight: 700; }
|
||||
|
||||
/* 验收判据框——目标件里两份验收工单同一色(赭红),不随 tone 变 */
|
||||
.proto-root .jc-criteria { border: 1px solid var(--ledger-line); border-radius: 10px; background: var(--ledger-soft); padding: 13px 16px; margin-bottom: 18px; }
|
||||
.proto-root .jc-criteria-t { font-size: 11px; font-weight: 800; letter-spacing: .08em; color: var(--ledger); margin-bottom: 7px; }
|
||||
.proto-root .jc-criteria ul { margin: 0; padding-left: 18px; font-size: 12.5px; color: var(--ink-2); }
|
||||
.proto-root .jc-criteria li { margin: 4px 0; }
|
||||
.proto-root .jc-criteria b { color: var(--ink); }
|
||||
|
||||
/* 表格基础排版(交互态的显隐开关在 BIND_CSS) */
|
||||
/*
|
||||
* **表宽超出就得能横着滑**(2026-08-23 用户:「表格已经显示不全了,不能左右滑动」)。
|
||||
* 从前这里是 overflow: hidden —— 那是为了让圆角裁住表头,可它同时把超出的列
|
||||
* **直接裁掉且滑不动**:列一多(或某列为了放下下拉框加宽)就看不全,人还没有任何办法。
|
||||
* quote-* 各包一直是 overflow-x: auto,这两个新包抄版式时把这一条抄丢了。
|
||||
* (注释里不写反引号 —— 整段 CSS 是模版字符串,一个反引号就把它截断,本轮踩第三次。)
|
||||
* 竖向仍 hidden:件是整篇铺开跟着外层滚的,表格自己不该再套一层竖滚动条。
|
||||
*/
|
||||
.proto-root .jc-tbl-wrap { border: 1px solid var(--line-soft); border-radius: 10px; overflow-x: auto; overflow-y: hidden; margin-bottom: 4px; }
|
||||
|
||||
/*
|
||||
* 资料名 = 可点开的那一格(2026-08-23)。**看得出能点**才会有人点:
|
||||
* 品牌蓝 + 下划虚线 + 手型;悬停实线。点了去哪由外层接(件里一行脚本也没有)。
|
||||
*/
|
||||
.proto-root .jc-fileln { color: var(--brand); border-bottom: 1px dashed var(--brand-soft); cursor: pointer; transition: all var(--t-fast); }
|
||||
.proto-root .jc-fileln:hover { border-bottom-style: solid; background: var(--brand-wash); }
|
||||
/* 空标题不该长得像能点开 —— 那一格根本没有资料 */
|
||||
.proto-root .jc-fileln:empty { border-bottom: 0; cursor: default; background: none; }
|
||||
|
||||
/* 格式徽:摆的是这份资料交出去时的样子(md / html / docx / xlsx) */
|
||||
.proto-root .jc-fmt { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 19px;
|
||||
padding: 1px 7px; border-radius: 5px; font-size: 10px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
|
||||
background: var(--soft); color: var(--ink-3); border: 1px solid var(--line); }
|
||||
.proto-root .jc-fmt.v-docx { background: #eaf1ff; color: #2b5fd9; border-color: #c3d6ff; }
|
||||
.proto-root .jc-fmt.v-xlsx { background: #e9f7ee; color: #1f7a45; border-color: #bfe6cd; }
|
||||
.proto-root .jc-fmt.v-html { background: #fdf0e6; color: #b25e15; border-color: #f3d3b4; }
|
||||
.proto-root .jc-fmt.v-md { background: #f2f3f5; color: #5a6068; border-color: #dfe1e5; }
|
||||
.proto-root .jc-tbl-in { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
.proto-root .jc-tbl-in th { text-align: left; background: var(--soft); font-size: 11px; letter-spacing: .04em; font-weight: 800; color: var(--ink-3); padding: 9px 11px; border-bottom: 1px solid var(--line); }
|
||||
.proto-root .jc-tbl-in th.req::after { content: " *"; color: var(--danger); font-size: 11px; }
|
||||
.proto-root .jc-tbl-in th.sug::after { content: " 建议"; color: var(--warn-accent); font-size: 10px; font-weight: 600; }
|
||||
.proto-root .jc-tbl-in th.opt::after { content: " 选填"; color: var(--ink-4); font-size: 10px; font-weight: 600; }
|
||||
.proto-root .jc-tbl-in td { padding: 9px 11px; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
|
||||
.proto-root .jc-tbl-in tr:last-child td { border-bottom: 0; }
|
||||
.proto-root .jc-tbl-in td.jc-rn { width: 38px; white-space: nowrap; font-family: var(--mono); font-size: 11px; color: var(--ink-4); text-align: center; position: relative; }
|
||||
|
||||
.proto-root .jc-vcell { display: block; font-size: 12.5px; color: var(--ink); line-height: 1.5; overflow-wrap: anywhere; }
|
||||
.proto-root .jc-vcell.mono { font-family: var(--mono); font-size: 11.5px; color: var(--ink-2); }
|
||||
.proto-root .jc-vcell.cust { font-weight: 700; }
|
||||
.proto-root .jc-vcell.dim { color: var(--ink-4); text-decoration: line-through; }
|
||||
.proto-root .jc-diffnew { font-weight: 800; color: var(--ok); }
|
||||
|
||||
.proto-root .jc-cell, .proto-root select.jc-cell, .proto-root textarea.jc-cell {
|
||||
width: 100%; min-width: 60px; border: 1px solid var(--line); border-radius: 6px;
|
||||
padding: 6px 8px; font: inherit; font-size: 12.5px; color: var(--ink); background: #fff;
|
||||
}
|
||||
.proto-root .jc-cell.mono { font-family: var(--mono); font-size: 11.5px; }
|
||||
.proto-root .jc-cell:focus { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-wash); outline: none; }
|
||||
|
||||
.proto-root .jc-rowdel {
|
||||
width: 22px; height: 22px; margin: 0 auto; cursor: pointer;
|
||||
border: 1px solid var(--line); border-radius: 6px; padding: 0;
|
||||
font: inherit; font-size: 14px; line-height: 1; color: var(--ink-3); background: #fff;
|
||||
transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
|
||||
}
|
||||
.proto-root .jc-rowdel:hover { border-color: var(--danger); color: var(--danger); background: var(--danger-soft); }
|
||||
|
||||
.proto-root .jc-addrow em { font-style: normal; font-size: 11.5px; color: var(--ink-4); }
|
||||
.proto-root .jc-addrow button {
|
||||
cursor: pointer; border: 1px solid var(--brand-soft); border-radius: 7px; padding: 5px 12px;
|
||||
font: inherit; font-size: 12px; font-weight: 700; color: var(--brand-deep); background: var(--brand-wash);
|
||||
}
|
||||
.proto-root .jc-addrow button:hover { background: var(--brand-soft); }
|
||||
|
||||
/* 本工单只承载内容,提交/审批请在对话区——目标件三份文件都有这一条 */
|
||||
.proto-root .jc-flow { margin-top: 16px; padding: 9px 12px; border-radius: 8px; background: var(--soft); border: 1px dashed var(--line); font-size: 12px; color: var(--ink-3); }
|
||||
.proto-root .jc-flow b { color: var(--ink); }
|
||||
|
||||
.proto-root .jc-rp-foot { padding: 13px 22px; border-top: 1px solid var(--line-soft); font-size: 11.5px; color: var(--ink-4); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px; }
|
||||
.proto-root .jc-rp-foot span:last-child { font-family: var(--mono); }
|
||||
`;
|
||||
@@ -0,0 +1,5 @@
|
||||
export const P08_MASTER_CREATE = '<div class="proto-root"></div>';
|
||||
export const P10_DOCUMENT_CREATE = '<div class="proto-root"></div>';
|
||||
export const EVAL_INPUT_LIST = '<div class="proto-root"></div>';
|
||||
export const DIFFERENCE_REVIEW = '<div class="proto-root"></div>';
|
||||
export const LANDING_CONFIRM = '<div class="proto-root"></div>';
|
||||
Reference in New Issue
Block a user