fix(arguments): route "חשב טיעונים" through the legal-analyst agent
The /aggregate-arguments endpoint ran an in-container BackgroundTask that called claude_session (the local `claude` CLI) — which does not exist in the FastAPI container. The button silently produced nothing, and on `force` it destructively DELETEd existing arguments *before* the doomed LLM call. Replace the inline task with the established delegation pattern used by "חלץ עובדות שמאיות" (extract-appraiser-facts): a cheap in-container DB pre-check (no_claims / exists), then a Paperclip wakeup of the company's legal-analyst, which runs mcp__legal-ai__aggregate_claims_to_arguments locally (where the CLI lives) and reports back. `force` now runs locally too, so delete+recompute are atomic on the host — no more destructive failure. Frontend: AggregateArgumentsResult becomes a discriminated union (queued | no_claims | exists | skipped) and the toast is status-accurate instead of the misleading fixed "refresh in a minute". Invariants: G12 (Paperclip touch confined to paperclip_client behind the agent_platform_port), G2 (replaces the broken path, no parallel capability), engineering §6 (explicit statuses, no silent swallow). UI change is logic/toast only (no visual-layout change) — within the Claude-Design-gate bug-fix exemption. Richer inline status panels deferred to the gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
94
web/app.py
94
web/app.py
@@ -75,6 +75,7 @@ from web.agent_platform_port import (
|
||||
pc_respond_to_interaction,
|
||||
pc_restore_project,
|
||||
pc_wake_analyst_for_appraiser_facts,
|
||||
pc_wake_analyst_for_argument_aggregation,
|
||||
rename_case_project,
|
||||
pc_wake_ceo,
|
||||
pc_wake_ceo_for_feedback_fold,
|
||||
@@ -2454,41 +2455,78 @@ async def api_get_claims(case_number: str):
|
||||
# the FastAPI container it short-circuits with status="llm_unavailable".
|
||||
|
||||
@app.post("/api/cases/{case_number}/aggregate-arguments")
|
||||
async def api_aggregate_arguments(
|
||||
case_number: str,
|
||||
background_tasks: BackgroundTasks,
|
||||
force: bool = False,
|
||||
):
|
||||
"""Aggregate raw claims into distinct legal arguments via Claude.
|
||||
async def api_aggregate_arguments(case_number: str, force: bool = False):
|
||||
"""Queue claim→argument aggregation by waking the legal-analyst agent.
|
||||
|
||||
Runs as a BackgroundTask because the LLM pass can take 30-90 seconds.
|
||||
The aggregation itself calls `claude_session.query_json()`, which shells
|
||||
out to the local `claude` CLI — present on the agent host, **absent in
|
||||
this FastAPI container**. Running it inline as a BackgroundTask (the old
|
||||
behaviour) silently produced nothing, and on `force` it destructively
|
||||
deleted the existing arguments *before* the doomed LLM call. So we
|
||||
delegate to the analyst exactly like `extract-appraiser-facts`: create a
|
||||
child Paperclip issue, assign it to the company's analyst, and trigger a
|
||||
wakeup. The analyst runs the MCP tool locally and posts results.
|
||||
|
||||
Cheap in-container pre-checks (DB only, no LLM) short-circuit before any
|
||||
agent is spun up:
|
||||
- `no_claims` — there are no raw claims to aggregate yet.
|
||||
- `exists` — arguments already computed and `force` is False.
|
||||
|
||||
Response shape:
|
||||
{"status": "queued", "sub_issue_id", "analyst_id", "main_issue_id"}
|
||||
or {"status": "no_claims"|"exists", "total", "message"}
|
||||
or {"status": "skipped", "reason": "no_api_key"|"no_analyst"|"no_issue"}
|
||||
"""
|
||||
case = await db.get_case_by_number(case_number)
|
||||
if not case:
|
||||
raise HTTPException(404, f"תיק {case_number} לא נמצא")
|
||||
|
||||
async def _run() -> None:
|
||||
try:
|
||||
from legal_mcp.services import argument_aggregator
|
||||
result = await argument_aggregator.aggregate_claims_to_arguments(
|
||||
UUID(case["id"]), force=force,
|
||||
)
|
||||
logger.info(
|
||||
"aggregate_arguments[%s] finished: %s",
|
||||
case_number, result,
|
||||
)
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.exception(
|
||||
"aggregate_arguments[%s] failed: %s", case_number, e,
|
||||
)
|
||||
case_id = UUID(case["id"])
|
||||
pool = await db.get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
claim_count = await conn.fetchval(
|
||||
"SELECT COUNT(*) FROM claims WHERE case_id = $1", case_id,
|
||||
)
|
||||
existing_args = await conn.fetchval(
|
||||
"SELECT COUNT(*) FROM legal_arguments WHERE case_id = $1", case_id,
|
||||
)
|
||||
|
||||
background_tasks.add_task(_run)
|
||||
return {
|
||||
"status": "started",
|
||||
"case_number": case_number,
|
||||
"force": force,
|
||||
"message": "Aggregation started in background. Poll /legal-arguments for results.",
|
||||
}
|
||||
if not claim_count:
|
||||
return {
|
||||
"status": "no_claims",
|
||||
"total": 0,
|
||||
"message": (
|
||||
"אין טענות גולמיות בתיק. הרץ קודם חילוץ טענות (extract_claims) "
|
||||
"ואז חשב טיעונים."
|
||||
),
|
||||
}
|
||||
if existing_args and not force:
|
||||
return {
|
||||
"status": "exists",
|
||||
"total": existing_args,
|
||||
"message": (
|
||||
f"כבר קיימים {existing_args} טיעונים מאוגדים. השתמש בכפתור "
|
||||
"החישוב-מחדש כדי לחשב מחדש (מוחק ובונה מחדש)."
|
||||
),
|
||||
}
|
||||
|
||||
# Route to the analyst of the correct company by case-number prefix.
|
||||
prefix = case_number[:1]
|
||||
company_id = (
|
||||
PAPERCLIP_COMPANIES["licensing"] if prefix == "1"
|
||||
else PAPERCLIP_COMPANIES["betterment"] if prefix in ("8", "9")
|
||||
else ""
|
||||
)
|
||||
|
||||
try:
|
||||
result = await pc_wake_analyst_for_argument_aggregation(
|
||||
case_number, company_id=company_id, force=force,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("analyst wakeup failed for argument aggregation %s", case_number)
|
||||
raise HTTPException(500, f"לא ניתן לשלוח לאנליטיקאי: {e}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@app.get("/api/cases/{case_number}/legal-arguments")
|
||||
|
||||
Reference in New Issue
Block a user