Files
mini-openclaw/scripts/assistant_acceptance.py

84 lines
5.2 KiB
Python

"""Reproducible offline acceptance and latency evidence in an isolated database."""
import argparse
import base64
import json
import os
import statistics
import sys
import tempfile
import time
from datetime import datetime
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "backend"))
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--output", type=Path, default=ROOT / "test-evidence" / ("assistant-acceptance-" + datetime.now().strftime("%Y%m%d-%H%M%S")))
args = parser.parse_args()
output = args.output.resolve()
if not output.is_relative_to(ROOT / "test-evidence"):
raise ValueError("验收输出必须位于当前仓库 test-evidence")
output.mkdir(parents=True, exist_ok=False)
os.environ.update(XINGBANG_LOCAL_MODEL_URL="", XINGBANG_AUTH_USERS="", XINGBANG_REQUIRE_AUTH="0")
from api import risk_agent_config as catalog
from services.project_assistant import ProjectAssistantService
from services.assistant_worker import DurableWorker
from services.assistant_store import listing
identity = {"id": "offline-acceptance", "role": "admin", "projects": ["*"], "preview": True}
records = []
with tempfile.TemporaryDirectory(dir=output, ignore_cleanup_errors=True) as tmp:
catalog.DB_PATH = Path(tmp) / "catalog.sqlite3"
service = ProjectAssistantService(Path(tmp) / "assistant.sqlite3")
worker = DurableWorker(service)
def run(project_id):
start = time.perf_counter()
queued = service.enqueue(project_id, identity, "核对现金流与来源")
queued_ms = (time.perf_counter() - start) * 1000
worker.process_next()
result = service.resource("run", queued["id"])
records.append({"project": project_id, "run_id": result["id"], "enqueue_ms": round(queued_ms, 2), "total_ms": round((time.perf_counter() - start) * 1000, 2), "status": result["status"], "summary": result["summary"]})
return result
first = run("DEMO-B")
assert first["summary"]["coverage"] == "1.0800" and first["summary"]["open"] == 4
project = service.resource("project", "DEMO-B")
service.supplement("DEMO-B", identity["id"], {"due_diligence_liabilities": "13200", "duplicate_asset_count": 0}, [], project["revision"], "离线验收补件")
second = run("DEMO-B")
assert len(second["change_summary"]["resolved"]) == 2
scenario = service.compare_scenario("DEMO-B", "4000", identity["id"])
assert scenario["scenario"]["coverage"] == "1.3500" and len(scenario["unresolved"]) == 1
feedback = service.feedback("DEMO-B", identity["id"], {"category": "rule_omission", "target_kind": "run", "target_id": second["id"], "note": "追加回款凭证检查"})
experience = service.save_experience("DEMO-B", identity["id"], {"title": "回款凭证检查", "trigger": "存在回款依赖", "action": "补充到账依据", "exceptions": "已有到账凭证", "scope": "team", "team": "demo", "feedback_id": feedback["id"], "conditions": {"collection.dependency": True}, "required_field": "collection.evidence", "exception_field": "collection.evidence", "expires_at": "2099-01-01T00:00:00+00:00"})
experience = service.experience_action("DEMO-B", experience["id"], identity, "evaluate", experience["revision"], "隔离正反例评测")
service.experience_action("DEMO-B", experience["id"], identity, "publish", experience["revision"], "本机模拟独立审核")
c = run("DEMO-C")
a = run("DEMO-A")
assert any(f["code"].startswith("experience.") for f in c["findings"])
assert not any(f["code"].startswith("experience.") for f in a["findings"])
reports = []
for template in ("template.due-diligence", "template.review", "template.committee"):
report = service.create_report("DEMO-B", second["id"], identity["id"], template)
target = output / (template + ".docx")
target.write_bytes(service.export_report("DEMO-B", report["id"]))
reports.append({"path": target.name, "template": template, "snapshot_id": report["snapshot_id"], "visual_qa": "pending-renderer"})
times = []
for _ in range(30):
start = time.perf_counter()
service.resource("run", second["id"])
times.append((time.perf_counter() - start) * 1000)
with service.connect() as db:
for document in listing(db, "document"):
(output / document["name"]).write_bytes(base64.b64decode(document["raw_base64"]))
summary = {"mode": "offline-deterministic-no-live-model", "records": records, "scenario": scenario,
"poll_ms_p95": round(sorted(times)[28], 2), "poll_ms_median": round(statistics.median(times), 2),
"reports": reports, "experience_positive_and_negative": "passed", "preserved_live_database": True}
(output / "summary.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps({"output": str(output), "mode": summary["mode"], "poll_ms_p95": summary["poll_ms_p95"], "records": records}, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())