feat(seance): predecessor by-case לצריכת-סוכן דרך ה-plugin (#220 המשך)
מוסיף get_predecessor_for_case(case_number) — האח case-scoped של
get_predecessor_context (by-issue). סוכנים חושבים ב-case_number (לא issue-UUID),
לכן זהו הנתיב שכלי-ה-plugin legal_predecessor_context (PR נפרד ב-plugin-legal-ai)
יקרא. מחזיר מסקנות (summary) של ריצות-heartbeat שהסתיימו על-פני issues של התיק,
newest-first, עם summary בלבד (ריצות cancelled ריקות מסוננות).
- shaper משותף _shape_predecessor_run (G2 — נתיב-shaping יחיד ל-by-issue+by-case).
- join בטוח i.id::text = payload->>'issueId' (לא cast של payload חופשי ל-uuid).
- Port pc_get_predecessor_for_case (read-only); endpoint
GET /api/operations/cases/{case_number}/predecessor.
- 2 בדיקות (shaping+identifier, case-לא-נמצא). אומת חי: 1043-02-26/8125-09-24
מחזירים מסקנות-דיספוזיציה אמיתיות.
Invariants: G12 (fetch מהמעטפת, המגע מהשער; הכלי יחיה ב-plugin=מעטפת מוצהרת, לא
ב-mcp-server), G2 (shaper יחיד). המשך: PR ב-plugin-legal-ai (כלי) + הוראת-HEARTBEAT.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -198,3 +198,54 @@ def test_get_recent_escalations_defaults_severity_when_untagged(monkeypatch):
|
||||
(item,) = asyncio.run(pc.get_recent_escalations())["items"]
|
||||
assert item["severity"] == "medium" # fallback
|
||||
assert item["created_at"] is None
|
||||
|
||||
|
||||
# ── predecessor by case (#220 agent-facing) — resolve project then shape runs ──
|
||||
class _ProjectRunsConn:
|
||||
def __init__(self, project, runs):
|
||||
self._project, self._runs = project, runs
|
||||
|
||||
async def fetchrow(self, query, *args):
|
||||
return self._project
|
||||
|
||||
async def fetch(self, query, *args):
|
||||
return self._runs
|
||||
|
||||
async def close(self):
|
||||
pass
|
||||
|
||||
|
||||
def test_get_predecessor_for_case_shapes_runs(monkeypatch):
|
||||
from datetime import datetime
|
||||
|
||||
runs = [
|
||||
{
|
||||
"id": "run-1", "status": "succeeded",
|
||||
"started_at": datetime(2026, 7, 5, 10, 0, 0),
|
||||
"finished_at": datetime(2026, 7, 5, 10, 5, 0),
|
||||
"summary": "הלולאה נסגרה כראוי", "error_code": None,
|
||||
"session_id_after": "sess-9", "agent_name": "עוזר משפטי",
|
||||
"identifier": "CMP-141",
|
||||
},
|
||||
]
|
||||
|
||||
async def _connect(_url):
|
||||
return _ProjectRunsConn({"id": "proj-1"}, runs)
|
||||
|
||||
monkeypatch.setattr(pc.asyncpg, "connect", _connect)
|
||||
result = asyncio.run(pc.get_predecessor_for_case("1043-02-26"))
|
||||
assert result["ok"] is True and result["case_number"] == "1043-02-26"
|
||||
(run,) = result["runs"]
|
||||
assert run["run_id"] == "run-1"
|
||||
assert run["identifier"] == "CMP-141" # by-case adds which issue
|
||||
assert run["started_at"] == "2026-07-05T10:00:00"
|
||||
assert "הלולאה" in run["summary"]
|
||||
|
||||
|
||||
def test_get_predecessor_for_case_unknown_case_is_empty(monkeypatch):
|
||||
async def _connect(_url):
|
||||
return _ProjectRunsConn(None, []) # no project row
|
||||
|
||||
monkeypatch.setattr(pc.asyncpg, "connect", _connect)
|
||||
result = asyncio.run(pc.get_predecessor_for_case("9999-99-99"))
|
||||
assert result == {"ok": True, "case_number": "9999-99-99", "runs": []}
|
||||
|
||||
Reference in New Issue
Block a user