Files
mini-openclaw-v2/scripts/xingbang_prescreen_demo.py
T
qiaoqichaoandClaude Code d6bc8485f5 feat(v2): T11 改进治理与演示交付——对照门禁、泄漏作废、三模块演示脚本
覆盖 AC-15/16/34/37/41(BR-15/16/40、E-11/24)。

后端:
- contracts/improvement.py:ImprovementCandidate + ContrastReport/ContrastMetrics。
  零分母不得给比率或覆盖率(样本不足不是 0%);非独立评测不得给改进/退化结论;
  已发布必须引用既有不可变版本;没有对照证据不得进入待审核或已发布。
- services/improvements.py:
  · evaluate_contrast 在服务端判定泄漏——预测锁定前读到标签即整次评测作废(E-11),
    保留集来源进开发集的样本作废并单列,指标按分子/可观察分母/覆盖率/未观察分开报;
  · transition 状态机:draft→evaluating→ready→in_review→approved→released,
    自批拒绝、退化与结论不明拒绝发布、发布幂等且只引用既有版本、撤回只影响新运行。
- api/prescreen.py:候选列表、提出与状态机三个端点;评测请求体提交样本,
  泄漏与指标一律由服务端计算,不接受客户端交来的"成品报告"。
- contracts/prescreen.py:补 E-11/E-24 错误码与 409 映射。

前端一致性修正:
- src/lib/prescreen-run.ts 的呈现判定改用后端既有词汇
  (empty/list/grouped/radar/faceted,与 services/review_plan.presentation 一致),
  避免前后端各一套判定导致界面画出后端判定为不该画雷达的图。

演示交付:
- scripts/xingbang_prescreen_demo.py:按 08 §7 八步演示,只驱动已实现入口,
  未实现的步骤(批量清单、历史提炼、卡片治理)如实标注原因;
  工程演示与模型效果分列,本地专项/Jev/真实概率一律标未验证或未接通。
- backend/test_demo_script.py:冒烟断言八步状态、未实现原因与分列口径。

验证:backend 全量 unittest 519 项 OK;前端 npm run build 通过。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-30 09:25:25 +08:00

219 lines
13 KiB
Python
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.
"""兴邦预审 P0 模拟闭环演示脚本(T11)。
依据:docs/v2/08-data-pilot-and-demo.md §7 的八步演示顺序。
**诚实性要求**(08 §7 末、06 §6):脚本只驱动本仓库已经实现并验证过的入口;
没有实现的步骤打印"未实现"及其原因,不伪造屏幕状态;模型效果与工程演示
分列,模拟标签不得包装成真实业务验证。
用法:backend/venv/Scripts/python.exe scripts/xingbang_prescreen_demo.py
全程在临时目录内使用临时数据库,不触碰真实 xingbang_data。
"""
from __future__ import annotations
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "backend"))
from contracts.prescreen import ErrorCode # noqa: E402
from services import (assistant_store as store, improvements, judgment_adapters, # noqa: E402
judgment_provider as jp, prescreen, prescreen_policy,
prescreen_sandbox, prescreen_store, review_plan, trajectory)
DONE = "已执行"
NOT_DONE = "未实现"
MODEL_UNVERIFIED = "未验证(无权重与授权)"
FINDINGS = [
{"code": "corporate.cashflow", "result": "unknown", "stage": 1, "evidence_ids": ["EV-1"]},
{"code": "corporate.title", "result": "unknown", "stage": 1, "evidence_ids": ["EV-2"]},
{"code": "corporate.deal", "result": "checked", "stage": 2, "evidence_ids": ["EV-3"]},
]
DIMENSIONS = [
{"id": "DC-CASH", "component_refs": [{"id": "corporate.cashflow"}]},
{"id": "DC-TITLE", "component_refs": [{"id": "corporate.title"}]},
{"id": "DC-DEAL", "component_refs": [{"id": "corporate.deal"}]},
]
PLAN = {
"plan": {"id": "corporate.default", "version": "1.0.0"},
"skills": [{"id": "corporate.cashflow", "version": "1.0.0"}],
"rules": [], "template": {"id": "template.ai-pre-review", "version": "1.0.0"},
"content_hash": "demo-hash", "dimensions": DIMENSIONS,
}
STEPS = [
(1, "3,000 条候选清单、生成版本与多结果分布", NOT_DONE,
"T04 的服务已在单测中验证,但本脚本不重复驱动批量生成;清单需真实数据授权"),
(2, "选开发集案例,展开问询/补件/会议事件,提炼 Skill 与 advisory Rule", NOT_DONE,
"历史提炼走 T05 的治理入口,需要真实历史材料;此处不伪造提炼结果"),
(3, "自然语言改适用范围 + 人工新增卡片,统一审核发布", NOT_DONE,
"卡片治理入口属 T05;演示脚本不代替审核动作"),
(4, "数据卡 + Skills + Rules 组方案,维度随方案变化,Tools 只在依据中展示", DONE, None),
(5, "缺证 → 主审核查 → 问题与补件 → 演示概率来源", DONE, None),
(6, "勾选动作比较情景、撤销恢复基线", DONE, None),
(7, "回放完整 trajectory,对齐模拟会议记录,查看材料版本差异", DONE, None),
(8, "从漏检提出改进候选,隔离样本对照后人工审核发布,旧版仍可回放", DONE, None),
]
def run_demo() -> dict:
facts: dict = {"steps": [], "sandbox": {}, "trajectory": {}, "improvement": {}}
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmp:
db_path = Path(tmp) / "demo.sqlite3"
store.initialize(db_path)
# 步骤 4:方案维度(去重与刻度判定)
dimensions = review_plan.resolve_dimensions([
{"id": "DC-CASH", "label": "回款核验", "scale_id": "evidence-stage",
"scale_version": "1.0.0", "order": 1, "component_refs": [{"id": "corporate.cashflow", "version": "1.0.0"}],
"levels": [{"value": i, "label": name, "criterion": f"证据阶段:{name}"} for i, name in
enumerate(("未提供", "仅有说明", "资料待核", "交叉核验", "人工复核"))]},
{"id": "DC-TITLE", "label": "权属核验", "scale_id": "evidence-stage",
"scale_version": "1.0.0", "order": 2, "component_refs": [{"id": "corporate.title", "version": "1.0.0"}],
"levels": [{"value": i, "label": name, "criterion": f"证据阶段:{name}"} for i, name in
enumerate(("未提供", "仅有说明", "资料待核", "交叉核验", "人工复核"))]},
{"id": "DC-DEAL", "label": "交易要素", "scale_id": "evidence-stage",
"scale_version": "1.0.0", "order": 3, "component_refs": [{"id": "corporate.deal", "version": "1.0.0"}],
"levels": [{"value": i, "label": name, "criterion": f"证据阶段:{name}"} for i, name in
enumerate(("未提供", "仅有说明", "资料待核", "交叉核验", "人工复核"))]},
])
presentation = review_plan.presentation(dimensions)
facts["presentation"] = presentation["layout"]
# 步骤 5:预审执行(专项关闭,无校准概率)
config = jp.resolve_config(False)
decision = prescreen_policy.decide(FINDINGS)
decision["findings"] = FINDINGS
decision["missing_inputs"] = ["近六个月银行流水", "设备权属材料"]
screening = prescreen.assemble(
{"id": "run-demo", "project_id": "DEMO-026", "revision": 1,
"created_at": store.now(), "input_snapshot_id": "snap-demo"},
PLAN, decision, config)
with store.transaction(db_path) as db:
prescreen_store.save(db, screening)
facts["recommendation"] = screening["recommendation"]
facts["probability_is_null"] = screening["probability"] is None
# 步骤 6:沙盘(演示值只在演示域、组合查表)
with store.transaction(db_path) as db:
stored = prescreen_store.load(db, "DEMO-026", "run-demo")
baseline = prescreen_sandbox.simulate(stored, [])
both = prescreen_sandbox.simulate(stored, ["assume_bank_flow", "assume_title"])
facts["sandbox"] = {
"baseline": baseline["probability_view"]["demo_estimate"]["value"],
"combined": both["probability_view"]["demo_estimate"]["value"],
"label": baseline["probability_view"]["demo_estimate"]["label"],
"unchanged_dimensions": [item["id"] for item in both["dimensions"]
if not item["assumed"]],
}
# 步骤 7:轨迹回放与模拟会议结果
with store.transaction(db_path) as db:
store.put(db, "run", {"id": "run-demo", "project_id": "DEMO-026",
"status": "completed", "input_snapshot_id": "snap-demo",
"created_at": store.now(), "steps": [
{"run_id": "run-demo", "step_id": "corporate.cashflow",
"skill": {"id": "corporate.cashflow", "version": "1.0.0"},
"status": "completed", "input_snapshot_id": "snap-demo",
"started_at": store.now(), "outputs": {"note": "已核查"},
"findings": [{"code": "cashflow", "result": "unknown",
"evidence_ids": ["EV-1"]}],
"errors": [], "missing_inputs": []},
{"run_id": "run-demo", "step_id": "corporate.title",
"skill": {"id": "corporate.title", "version": "1.0.0"},
"status": "failed", "input_snapshot_id": "snap-demo",
"started_at": store.now(), "outputs": {},
"findings": [], "missing_inputs": [],
"errors": [{"code": "SKILL_TIMEOUT",
"message": "演示中的失败步骤"}]}]})
replay = trajectory.replay(db, "DEMO-026", "run-demo")
trajectory.record(db, "DEMO-026",
{"raw_code": "附条件通过", "meeting_id": "M-DEMO",
"occurred_at": store.now()}, "demo-reviewer",
known_snapshots=["snap-demo"])
outcomes = trajectory.listing(db, "DEMO-026")
facts["trajectory"] = {
"steps": [item["status"] for item in replay["steps"]],
"failed_preserved": any(item["status"] == "failed" for item in replay["steps"]),
"unmatched": len(outcomes["unmatched"]),
"observed_denominator": outcomes["counts_toward_denominator"]["observed"],
}
# 步骤 8:改进候选(合成样本、独立评测、可回放旧版)
with store.transaction(db_path) as db:
candidate = improvements.propose(db, "DEMO-026", {
"target": "knowledge", "target_ref": {"id": "card-demo", "version": "1.0.0"},
"proposed_revision_ref": {"id": "card-demo", "version": "1.1.0"},
"change_summary": "把回款核验经验扩到设备直租",
"source_run_ids": ["run-demo"]}, "demo-manager", "demo")
evaluation = {"baseline_ref": {"id": "current", "version": "1.0.0"},
"candidate_ref": {"id": "prescreen.v2", "version": "0.2.0"},
"dataset_ref": "demo/synthetic-manifest.json",
"split_ref": "demo-split.v1", "independent": True,
"samples": [
{"id": "S-1", "split": "dev", "observed": True,
"baseline_pass": True, "candidate_pass": True,
"predicted_at": "2026-09-29T01:00:00+00:00",
"label_revealed_at": "2026-09-29T03:00:00+00:00"},
{"id": "S-2", "split": "dev", "observed": True,
"baseline_pass": False, "candidate_pass": True,
"predicted_at": "2026-09-29T01:00:00+00:00",
"label_revealed_at": "2026-09-29T03:00:00+00:00"}]}
evaluated = improvements.transition(db, candidate["id"], "evaluate", "demo-reviewer",
evaluation=evaluation)
improvements.transition(db, evaluated["id"], "submit", "demo-reviewer")
approved = improvements.transition(db, evaluated["id"], "approve", "demo-reviewer")
released = improvements.transition(
db, approved["id"], "release", "demo-reviewer",
publisher=lambda body: {"id": "card-demo", "version": "1.1.0"})
facts["improvement"] = {"verdict": evaluated["contrast"]["verdict"],
"status": released["status"],
"released_ref": released["released_ref"]}
# 专项:离线 fixture 可跑,live 未接通
facts["judgment_offline"] = judgment_adapters.load_fixture(
"question_bank_extraction.json")["rubric_version"]
facts["demo_table_version"] = prescreen_sandbox.DEMO_TABLE_VERSION
facts["budget_code"] = ErrorCode.JUDGMENT_CONTEXT_EXCEEDED.value
return facts
def main() -> int:
facts = run_demo()
print("兴邦预审 P0 模拟闭环演示(合成数据 · 不连接真实数据与模型)")
print("=" * 78)
for index, title, status, reason in STEPS:
print(f"{index}. [{status}] {title}")
if reason:
print(f" 原因:{reason}")
print("-" * 78)
print("工程演示(本仓库已实现并验证的入口)")
print(f" · 维度呈现:{facts['presentation']}")
print(f" · 主审意见:{facts['recommendation']} · 真实概率为空:{facts['probability_is_null']}")
sandbox = facts["sandbox"]
print(f" · 沙盘:基线 {sandbox['baseline']:.0%} → 组合 {sandbox['combined']:.0%}"
f"({sandbox['label']})")
print(f" · 未变化的维度仍列出:{sandbox['unchanged_dimensions']}")
trace = facts["trajectory"]
print(f" · 轨迹步骤状态:{trace['steps']} · 失败步骤保留:{trace['failed_preserved']}")
print(f" · 会议结果:unmatched {trace['unmatched']} 条 · 已观察分母 {trace['observed_denominator']}")
imp = facts["improvement"]
print(f" · 改进对照:{imp['verdict']} → {imp['status']}({imp['released_ref']})")
print("-" * 78)
print("模型效果(与上面的工程演示分开计)")
print(f" · 本地专项:{MODEL_UNVERIFIED};当前只有离线 fixture({facts['judgment_offline']})")
print(f" · Jev:未接通(无端点与凭据)")
print(f" · 真实审批概率:未校准,恒为空;界面上的百分数来自演示表 "
f"{facts['demo_table_version']},不是模型输出")
print(f" · 超预算时的拒绝码({facts['budget_code']})证明预算守卫生效,与概率无关")
print("=" * 78)
print("以上均为合成数据与演示值,不能作为真实业务效果或审批依据。")
return 0
if __name__ == "__main__":
raise SystemExit(main())