feat(generate): סימון-סטטוס לריצת-הרקע של "הפקת מסמכים" (#227 ג)
עד כה ה-"מייצר…" כיסה רק את שנייה-ה-POST; אחרי זה לא היה חיווי אם ההפקה
רצה/הצליחה/נכשלה — ריצה שבוטלה נראתה כמו הצלחה. עכשיו צ'יפ-סטטוס ליד כל
כפתור-הפקה: בתור · רץ+שעון-חי · הושלם+מתי · נכשל+סיבה+"נסה שוב".
**נשמר-שרת (דרישת חיים):** הסטטוס נגזר בשרת מ-issue-הילד של ה-CEO +
ה-heartbeat_run האחרון + קיום-הקובץ — לא מזיכרון-הדף. שעון-הריצה מעוגן
ל-started_at של השרת, כך שעזיבה+חזרה לדף מציגות את המצב והזמן הנכונים,
בלי צורך להישאר בדף.
Backend:
- paperclip_client.get_generation_run_status — קורא issue-ילד + latest run
מ-Paperclip DB (read-only, מאחורי שער-הפלטפורמה G12); dedup לפי כותרת.
- GET /api/cases/{n}/generate/{action}/status — ממפה ל-idle/queued/running/
done/failed + output_ready + started_at/finished_at (UTC). run שהסתיים בלי
קובץ = failed (שלא ייראה כהצלחה).
Frontend:
- useGenerateStatus (poll 5s רק כשפעיל; עוצר בטרמינלי) + GenerateStatusChip
(שעון-ריצה חי מ-started_at; רכיב חסר-מצב). כפתורי-ההפקה מרעננים את
שאילתת-הסטטוס ב-onSuccess. שורה 3 (החלטה מלאה, סינכרוני) — בלי צ'יפ.
עיצוב: מוקאפ 29-generate-status-indicator אושר ע"י חיים דרך שער Claude Design (X17).
tsc + eslint + py_compile ירוקים. (api:types לריענון post-deploy.)
Invariants: G12 (Paperclip רק בשער), G1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1384,6 +1384,73 @@ async def wake_ceo_for_action(
|
||||
}
|
||||
|
||||
|
||||
# Substring of the generation child-issue title per action (see _ceo_action_brief).
|
||||
GENERATION_TITLE_SUBSTR = {
|
||||
"party_claims_summary": "סיכום-מנהלים",
|
||||
"interim_draft": "טיוטת טענות-הצדדים",
|
||||
}
|
||||
|
||||
|
||||
async def get_generation_run_status(case_number: str, action: str) -> dict:
|
||||
"""Server-derived run status for a "הפקת מסמכים" generation action (#227 ג).
|
||||
|
||||
Reconstructs the state of the most recent generation child issue for
|
||||
(case, action) from Paperclip — the child issue + its latest heartbeat_run —
|
||||
so the UI indicator survives navigation/reload (it never depends on the
|
||||
browser having stayed on the page). Read-only Paperclip DB access, behind the
|
||||
platform port (G12).
|
||||
|
||||
Returns a serializable dict::
|
||||
|
||||
{"issue_id", "issue_status", "run_status", "started_at", "finished_at",
|
||||
"error", "error_code"}
|
||||
|
||||
with all timestamps as ISO-8601 UTC strings (or None). ``issue_id`` is None
|
||||
when no generation child issue exists yet (→ the endpoint reports 'idle').
|
||||
"""
|
||||
substr = GENERATION_TITLE_SUBSTR.get(action)
|
||||
if not substr:
|
||||
return {"issue_id": None}
|
||||
|
||||
def _iso(dt) -> str | None:
|
||||
return dt.isoformat() if dt is not None else None
|
||||
|
||||
conn = await asyncpg.connect(PAPERCLIP_DB_URL)
|
||||
try:
|
||||
issue = await conn.fetchrow(
|
||||
"""SELECT id, status
|
||||
FROM issues
|
||||
WHERE title LIKE ('%' || $1 || '%') AND title LIKE ('%' || $2 || '%')
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1""",
|
||||
case_number, substr,
|
||||
)
|
||||
if issue is None:
|
||||
return {"issue_id": None}
|
||||
|
||||
run = await conn.fetchrow(
|
||||
"""SELECT hr.status, hr.started_at, hr.finished_at, hr.error, hr.error_code
|
||||
FROM heartbeat_runs hr
|
||||
JOIN agent_wakeup_requests awr ON hr.wakeup_request_id = awr.id
|
||||
WHERE awr.payload->>'issueId' = $1
|
||||
ORDER BY hr.created_at DESC
|
||||
LIMIT 1""",
|
||||
str(issue["id"]),
|
||||
)
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
return {
|
||||
"issue_id": str(issue["id"]),
|
||||
"issue_status": issue["status"],
|
||||
"run_status": run["status"] if run else None,
|
||||
"started_at": _iso(run["started_at"]) if run else None,
|
||||
"finished_at": _iso(run["finished_at"]) if run else None,
|
||||
"error": (run["error"] if run else None) or None,
|
||||
"error_code": (run["error_code"] if run else None) or None,
|
||||
}
|
||||
|
||||
|
||||
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