feat(v2): 纠偏第一批——执行链产出新版预审记录(真实运行验收)
覆盖 AC-46/47/48/49(BR-45/46/47、E-27)。审计确认的 P1 缺口"正常发起评审不产生 新版预审记录"由此关闭:视图由执行链出口产生,不再依赖测试或脚本注入。 后端: - services/prescreen.py:新增 screening_from_run(),把一次真实运行的执行结果装配成 预审视图;维度与阶段按**本次实际执行的组件与核查项**投影 (dimensions_and_evidence:冻结方案声明过、且本次真的执行过的技能/规则才算维度; 经验核查等方案外伪步骤不算;阶段只表达证据成熟度,不看结论好坏)。 - services/assistant_worker.py:运行出口(含失败路径)统一调用 save_prescreen_view(), 运行结束即落库视图;装配失败单独记事件,不让运行一起丢。 - contracts/prescreen.py:视图带 evidence(界面"查看依据"的来源实体); 新增运行级错误码 RUN_STEP_FAILED(E-27)。 - 维度状态用本次执行的步骤与核查项算(含跑通但无问题的项), 只看待办 finding 会把通过的维度误判成未核查。 前端:面板新增"核查结果"区(每条可打开依据抽屉,显示来源、定位、原文片段与组件版本) 与降级记录展示;没有证据时明确写"按缺证处理",不编造。 测试:backend/test_prescreen_from_run.py 12 项,用真实 enqueue + DurableWorker.process_next (临时库)断言视图由执行链产生、失败与模型不可用如实留痕、重跑新增记录。 含反例:执行前不得存在任何记录。 实际验收(浏览器渲染 + 真实运行):状态 partial、10 维(5 已核查/2 待核实/3 阻断)、 4 条核查结果、14 条证据、1 条 RUN_STEP_FAILED 未完成项;记录见 docs/v2/17-batch-delivery-records.md(含剩余缺口:界面点击未自动化、材料确认未在浏览器走完、 演示角色需 XINGBANG_DEMO_ROLE_LOGIN=1、模型未配置)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ export default function PrescreenPanel({project, request, busy, setBusy, setErro
|
||||
const [simulation, setSimulation] = useState<Simulation | null>(null);
|
||||
const [scenarioState, setScenarioState] = useState<"baseline" | "computing" | "ready" | "failed">("baseline");
|
||||
const [openDimension, setOpenDimension] = useState<string>("");
|
||||
const [openFinding, setOpenFinding] = useState<string>("");
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const seq = useRef(0);
|
||||
const drawer = useRef<HTMLButtonElement | null>(null);
|
||||
@@ -106,12 +107,21 @@ export default function PrescreenPanel({project, request, busy, setBusy, setErro
|
||||
|
||||
function openBasis(id: string, element: HTMLElement | null) {
|
||||
opener.current = element;
|
||||
setOpenFinding(""); // 两个抽屉互斥,避免同时展开两条依据
|
||||
setOpenDimension(id);
|
||||
setTimeout(() => drawer.current?.focus(), 0);
|
||||
}
|
||||
|
||||
function openFindingBasis(id: string, element: HTMLElement | null) {
|
||||
opener.current = element;
|
||||
setOpenDimension("");
|
||||
setOpenFinding(id);
|
||||
setTimeout(() => drawer.current?.focus(), 0);
|
||||
}
|
||||
|
||||
function closeBasis() {
|
||||
setOpenDimension("");
|
||||
setOpenFinding("");
|
||||
opener.current?.focus();
|
||||
}
|
||||
|
||||
@@ -123,6 +133,9 @@ export default function PrescreenPanel({project, request, busy, setBusy, setErro
|
||||
// 刻度信息来自呈现层:运行视图只带阶段序数,因此默认同一刻度;方案带 scale_id 时按实际判断。
|
||||
const shown = presentationFor(dimensions.map((item: any) => ({scale_id: item.scale_id || "stage"})));
|
||||
const basis = dimensions.find((item) => item.id === openDimension) || null;
|
||||
const finding = (run?.findings || []).find((item: any) => (item.id || item.code) === openFinding) || null;
|
||||
const evidenceFor = (ids: string[]) => (run?.evidence || []).filter(
|
||||
(item: any) => ids.includes(item.id) || ids.includes(item.evidence_id));
|
||||
const scenarioStages = new Map((simulation?.dimensions || []).map((item) => [item.id, item]));
|
||||
const domain = domainBadge(run?.data_domain || "demo");
|
||||
|
||||
@@ -163,6 +176,20 @@ export default function PrescreenPanel({project, request, busy, setBusy, setErro
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>核查结果({(run.findings || []).length})</h3>
|
||||
{!(run.findings || []).length && <div className="ra-empty"><h2>本次没有待处理事项</h2>
|
||||
<p>没有发现问题不等于已经通过;正式结论仍由业务流程决定。</p></div>}
|
||||
{(run.findings || []).map((item: any) => <div className="xb-finding" key={item.id || item.code}>
|
||||
<div>
|
||||
<b>{item.title || item.code}</b>
|
||||
<p>{item.detail || item.code} · 状态 {item.status || "待核实"}{item.machine_result ? ` · 核查结果 ${item.machine_result}` : ""}</p>
|
||||
<p>依据 {(item.evidence_ids || []).length} 项{(item.basis_ids || []).length ? ` · 组件 ${item.basis_ids.join("、")}` : ""}</p>
|
||||
</div>
|
||||
<button className="ra-button" aria-label={`查看 ${item.title || item.code} 的依据`}
|
||||
onClick={(e) => openFindingBasis(item.id || item.code, e.currentTarget)}>查看依据</button>
|
||||
</div>)}
|
||||
{(run.findings || []).length > 0 && <p>核查结果来自本次执行;未完成项见上方降级记录,缺证不改写成通过。</p>}
|
||||
|
||||
<h3>核查维度({dimensions.length})</h3>
|
||||
{shown.layout === "empty" && <div className="ra-empty"><h2>{EMPTY_DIMENSIONS}</h2>
|
||||
<p>方案没有维度时不补占位轴,也不给出任何结论。</p></div>}
|
||||
@@ -206,6 +233,19 @@ export default function PrescreenPanel({project, request, busy, setBusy, setErro
|
||||
<button className="ra-button" onClick={() => applyScenario([])} disabled={busy}>返回真实预审(恢复基线)</button>
|
||||
</div>}
|
||||
|
||||
{finding && <div role="dialog" aria-modal="false" aria-label={`${finding.title || finding.code} 的依据`}>
|
||||
<h3>{finding.title || finding.code} · 依据与版本</h3>
|
||||
<p>核查结果 {finding.machine_result || "未记录"} · 状态 {finding.status || "待核实"} · 数据域 {domain.text}</p>
|
||||
<p>引用证据 {evidenceFor(finding.evidence_ids || []).length} 项:</p>
|
||||
{evidenceFor(finding.evidence_ids || []).map((item: any) => <p key={item.id || item.evidence_id}>
|
||||
<b>{item.source || "未标注来源"}</b> · {item.locator || "未标注定位"} · v{item.version || "—"}
|
||||
<br/>{String(item.value || "").slice(0, 240)}
|
||||
</p>)}
|
||||
{!evidenceFor(finding.evidence_ids || []).length && <p>本次没有可展示的原文片段:该核查项按缺证处理,不能据此判定通过或不通过。</p>}
|
||||
<p>引用组件:{(finding.basis_ids || []).join("、") || "未标注"}。证据来源可定位不等于材料真实性已核验,也不换算为概率。</p>
|
||||
<button ref={drawer} className="ra-button" onClick={closeBasis}>关闭依据</button>
|
||||
</div>}
|
||||
|
||||
{basis && <div role="dialog" aria-modal="false" aria-label={`${basis.id} 的依据`}>
|
||||
<h3>{basis.id} · 依据与版本</h3>
|
||||
<p>状态 {stateLabel(basis.state)} · 阶段 {stageName(basis.stage)} · 数据域 {domain.text}</p>
|
||||
|
||||
@@ -26,6 +26,9 @@ export type RunView = {
|
||||
findings: any[]; missing_inputs: string[]; applied_experiences: any[];
|
||||
probability: null; probability_view?: ProbabilityView | null;
|
||||
degradations: {code: string; message: string}[]; dimension_results: DimensionResult[];
|
||||
// 本次核查实际引用的证据:界面"查看依据"的实体来源(纠偏第一批)
|
||||
evidence: {id?: string; evidence_id?: string; source?: string; locator?: string;
|
||||
value?: string; version?: string}[];
|
||||
versions: Record<string, any>;
|
||||
};
|
||||
export type ScenarioDimensionState = {id: string; baseline_stage: number | null; scenario_stage: number | null; assumed: boolean};
|
||||
|
||||
Reference in New Issue
Block a user