Files

219 lines
13 KiB
Python
Raw Permalink Normal View History

"""兴邦预审 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())