feat(v2): 实现SQLite对公租赁经验加工与卡片预审轨迹闭环

This commit is contained in:
2026-10-01 13:38:03 +08:00
parent 9c351b08d3
commit fa4163b2d0
60 changed files with 2111 additions and 59 deletions
+6 -8
View File
@@ -10,7 +10,7 @@ from copy import deepcopy
from services.assistant_store import get, put, listing, event, uid, now, digest, finish_time_event
from services import prescreen, prescreen_store
from services.assistant_data import read_cards
from services.skill_executor import execute_skill, execute_rules
from services.skill_executor import execute_skill, execute_rules, applicability_checks
from services.assistant_experiences import apply_experiences
from services import assistant_model
from contracts.project_assistant import SkillResult, ProjectSnapshot
@@ -55,7 +55,6 @@ def reconcile(db, project_id, run_id, checks):
put(db, "finding", finding)
key = "new" if not old else "resolved" if status == "已解决" and previous_status != status else "reopened" if previous_status == "已解决" and status != previous_status else "retained"
changes[key].append(finding["id"])
# Unexecuted checks remain unresolved/stale, never disappear into an implied pass.
executed = {c["code"] for c in checks}
for code, old in prior.items():
if code not in executed:
@@ -203,7 +202,6 @@ class DurableWorker:
db.execute("UPDATE assistant_jobs SET state='failed' WHERE id=? AND owner=?", (run["id"], self.owner))
finish_time_event(db, "run", run["id"])
event(db, run["project_id"], "run.failed", self.owner, {"error_type": type(exc).__name__}, run["id"])
# AC-48:失败运行同样要能被页面读到,并明确显示未完成原因
self.save_prescreen_view(db, current)
finally:
done.set()
@@ -232,7 +230,6 @@ class DurableWorker:
run["snapshot_id"] = snapshot["id"]
current = get(db, "run", run["id"])
current["snapshot_id"] = snapshot["id"]
# Freeze applicable experience versions once; recovery never reads a newer release.
current["experience_versions"] = [get(db, "experience_version", r["version_id"]) for r in listing(db, "experience_release")] if run.get("feature_flags", {}).get("experiences", True) else []
put(db, "run", current)
run.update(current)
@@ -262,7 +259,10 @@ class DurableWorker:
if time.monotonic() >= deadline:
raise TimeoutError("运行时间预算已耗尽")
engine = skill["engine"]
if engine == "model":
gate = applicability_checks(skill, values, sources) if engine not in ("rules", "experience") else None
if gate is not None:
step["findings"] = gate
elif engine == "model":
payload, trace = self.bounded_model(lambda: assistant_model.invoke(skill, dict(snapshot, values=values, sources=sources), all_checks, components["rules"], run["requirement"], guidance,
run["model_config"], budget, cancelled=lambda: not self.alive(run) or time.monotonic() >= deadline, feedback=run.get("feedback_context")), max(.1, deadline - time.monotonic()), run)
step["outputs"] = {"requirement_response": payload["requirement_response"]}
@@ -280,7 +280,7 @@ class DurableWorker:
step["findings"] = [c for c in step["findings"] if not set(c["fields"]).issubset(required_fields)]
if engine == "ratio":
step["output_sources"] = {skill["output"]: sources.get(skill["numerator"], []) + sources.get(skill["denominator"], [])}
step["missing_inputs"] = [f for f in skill.get("inputs", []) if f != "*" and values.get(f) is None]
step["missing_inputs"] = [] if step["findings"] and all(c["result"] == "not_applicable" for c in step["findings"]) else [f for f in skill.get("inputs", []) if f != "*" and values.get(f) is None]
step["status"] = "partial" if any(c["result"] in ("unknown", "error") for c in step["findings"]) else "completed"
except Exception as exc:
step.update(status="failed", errors=[{"type": type(exc).__name__, "message": str(exc)[:400] if isinstance(exc, (ValueError, TimeoutError)) else "步骤执行失败,请检查内网模型或数据服务"}])
@@ -319,9 +319,7 @@ class DurableWorker:
db.execute("UPDATE assistant_jobs SET state=? WHERE id=? AND owner=?", (current["status"], run["id"], self.owner))
finish_time_event(db, "run", run["id"])
self.service.mark_stale(db, run["project_id"], ["*"])
# A new result invalidates existing drafts, not the findings just reconciled.
for finding in findings:
put(db, "finding", finding)
event(db, run["project_id"], "run." + current["status"], self.owner, {"summary": current["summary"], "superseded": current["superseded"]}, run["id"])
# 纠偏第一批(BR-45):运行结束即产出新版预审视图,页面直接读它。
self.save_prescreen_view(db, current)