feat(case-ui): כרטיס "הפקת מסמכים" עם 3 כפתורי-הפק דטרמיניסטיים (#214)
מוסיף כרטיס "הפקת מסמכים" בראש טאב "טיוטות והערות" (mockup 26 — תוספת ל-18h),
תוספת לעמוד הקיים, כל הסקשנים נשמרים.
- כפתור 1 "הפק סיכום מנהלים" → endpoint חדש POST /generate/party-claims-summary
→ CEO wakeup סטרוקטורלי action="party_claims_summary" (שלב H2, host-side).
Polling: GET /research/party-claims-summary (200=מוכן → פתח/DOCX).
- כפתור 2 "הפק טיוטה של טענות הצדדים" → endpoint חדש POST /generate/interim-draft
→ CEO wakeup action="interim_draft" (שלב H, host-side).
Polling: רשימת ה-exports עד שמופיע טיוטת-ביניים-{case}-vN.docx.
- כפתור 3 "הפק טיוטת החלטה מלאה" → ה-useExportDocx הקיים (in-container, ללא LLM);
הועבר מהבאנר (כפתור "הפק DOCX" הוסר משם — אין כפילות). disabled+title כשאין
בלוקים כתובים (isDraftReady).
מסלול-עירור יחיד דרך ה-Platform Port (pc_wake_ceo_for_action) → API helper
/api/agents/{id}/wakeup, לעולם לא DB insert. Israel-time + cache-invalidation.
Invariants: G12 (מגע-Paperclip רק ב-paperclip_client + agent_platform_port;
leak-guard ירוק) · G2 (אין מסלול-עירור מקביל — שימוש חוזר ב-pc_request הקיים) ·
G10 (הפקה מגודרת-יו"ר/CEO) · INV-UI9 (Asia/Jerusalem) · X6 (UI↔API contract).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1195,6 +1195,73 @@ async def wake_ceo_agent(issue_id: str, case_number: str, company_id: str = "")
|
||||
return result
|
||||
|
||||
|
||||
async def wake_ceo_for_action(
|
||||
case_number: str,
|
||||
action: str,
|
||||
company_id: str = "",
|
||||
) -> dict:
|
||||
"""Wake the CEO with a deterministic *structured action* on the case's MAIN issue.
|
||||
|
||||
Drives the UI "הפקת מסמכים" buttons (TaskMaster #214): the CEO reads
|
||||
``payload.action`` and routes deterministically to its שלב H / שלב H2
|
||||
side-quests (legal-ceo.md §"פעולות סטרוקטורליות") — no free-text parsing.
|
||||
|
||||
action="party_claims_summary" → שלב H2 (summarize_party_claims)
|
||||
action="interim_draft" → שלב H (write_interim_draft + export_interim_draft)
|
||||
|
||||
These are side-quests on the EXISTING ``[ערר {case_number}]`` issue — no child
|
||||
issue is created (the CEO must not change ``cases.status`` or spawn sub-agents).
|
||||
Wakeup goes through the Paperclip API (``POST /api/agents/{id}/wakeup``), never a
|
||||
direct DB insert (CLAUDE.md hard rule). Returns
|
||||
``{"status": "ok"|"skipped", ...}``.
|
||||
"""
|
||||
if not PAPERCLIP_BOARD_API_KEY:
|
||||
logger.warning("PAPERCLIP_BOARD_API_KEY not set — skipping CEO action wakeup")
|
||||
return {"status": "skipped", "reason": "no_api_key"}
|
||||
|
||||
issues = await get_case_issues(case_number)
|
||||
if not issues:
|
||||
logger.warning("No Paperclip issues found for case %s — skipping CEO action", case_number)
|
||||
return {"status": "skipped", "reason": "no_issue"}
|
||||
|
||||
# The main case issue — prefer the in-progress one, else the canonical
|
||||
# "[ערר {case_number}]" issue, else the oldest. (Same selection spirit as
|
||||
# wake_curator_for_final, but we never spawn a child — it's a side-quest.)
|
||||
main_issue = (
|
||||
next((i for i in issues if i.get("status") == "in_progress"), None)
|
||||
or next((i for i in issues if f"[ערר {case_number}]" in (i.get("title") or "")), None)
|
||||
or issues[0]
|
||||
)
|
||||
main_issue_id = main_issue["id"]
|
||||
|
||||
ceo_id = CEO_AGENTS.get(company_id, CEO_AGENT_ID)
|
||||
wake_resp = await pc_request(
|
||||
"POST",
|
||||
f"/api/agents/{ceo_id}/wakeup",
|
||||
json={
|
||||
"source": "on_demand",
|
||||
"triggerDetail": "manual",
|
||||
"reason": f"generate_{action}_{case_number}",
|
||||
"payload": {
|
||||
"issueId": main_issue_id,
|
||||
"action": action,
|
||||
"case_number": case_number,
|
||||
},
|
||||
},
|
||||
raise_on_error=True,
|
||||
)
|
||||
logger.info(
|
||||
"CEO action wakeup for case %s: action=%s issue=%s ceo=%s status=%s",
|
||||
case_number, action, main_issue_id, ceo_id, wake_resp.status_code,
|
||||
)
|
||||
return {
|
||||
"status": "ok",
|
||||
"action": action,
|
||||
"issue_id": main_issue_id,
|
||||
"ceo_id": ceo_id,
|
||||
}
|
||||
|
||||
|
||||
def _curator_task_brief(task: str, case_number: str, final_filename: str) -> tuple[str, str]:
|
||||
"""Build the (sub-issue title, description) for a staged final-decision task.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user