4 Commits

Author SHA1 Message Date
015e553d06 fix: add debug log and null company_id comment to webhook scheduling
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 4m16s
2026-05-16 17:13:07 +00:00
6bdf9786ac feat: emit case-status webhook on status change in PUT /api/cases/:case 2026-05-16 17:10:30 +00:00
d87f9c5a5f fix: include case details in webhook failure warning log 2026-05-16 17:08:33 +00:00
a0fab1f6de feat: add emit_case_status_webhook helper 2026-05-16 17:06:37 +00:00
2 changed files with 61 additions and 4 deletions

View File

@@ -20,7 +20,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "mcp-server" / "
import zipfile
from fastapi import FastAPI, File, Form, HTTPException, UploadFile
from fastapi import BackgroundTasks, FastAPI, File, Form, HTTPException, UploadFile
from fastapi.responses import FileResponse, StreamingResponse
from typing import Any, Literal
from pydantic import BaseModel
@@ -44,7 +44,7 @@ from web.mcp_env_catalog import (
normalize_for_compare,
)
from web.progress_store import ProgressStore
from web.paperclip_api import pc_request
from web.paperclip_api import emit_case_status_webhook, pc_request
from web.paperclip_client import (
COMPANIES as PAPERCLIP_COMPANIES,
accept_interaction as pc_accept_interaction,
@@ -1337,8 +1337,12 @@ async def api_case_get(case_number: str):
@app.put("/api/cases/{case_number}")
async def api_case_update(case_number: str, req: CaseUpdateRequest):
async def api_case_update(case_number: str, req: CaseUpdateRequest, background_tasks: BackgroundTasks):
"""Update case details."""
# Capture old status before the update so we can detect changes.
existing = await db.get_case_by_number(case_number)
old_status = (existing or {}).get("status", "")
result = await cases_tools.case_update(
case_number=case_number,
status=req.status,
@@ -1351,10 +1355,30 @@ async def api_case_update(case_number: str, req: CaseUpdateRequest):
expected_outcome=req.expected_outcome,
)
try:
return json.loads(result)
parsed = json.loads(result)
except json.JSONDecodeError:
raise HTTPException(404, result)
# Emit webhook when status changes (fire-and-forget via BackgroundTasks).
new_status = req.status
if new_status and old_status != new_status:
prefix = case_number[:1]
company_id = (
PAPERCLIP_COMPANIES["licensing"] if prefix == "1"
else PAPERCLIP_COMPANIES["betterment"] if prefix in ("8", "9")
else None
)
background_tasks.add_task(
emit_case_status_webhook,
case_number=case_number,
old_status=old_status,
new_status=new_status,
company_id=company_id, # None is safe — plugin handles unknown company gracefully
)
logger.debug("webhook scheduled: case %s %s%s", case_number, old_status, new_status)
return parsed
@app.delete("/api/cases")
async def api_case_delete(case_number: str, remove_files: bool = False):

View File

@@ -19,6 +19,7 @@ from __future__ import annotations
import logging
import os
from datetime import datetime
from typing import Any
import httpx
@@ -81,3 +82,35 @@ async def pc_request(
if raise_on_error:
resp.raise_for_status()
return resp
async def emit_case_status_webhook(
case_number: str,
old_status: str,
new_status: str,
company_id: str | None = None,
run_id: str | None = None,
) -> None:
"""Notify the Paperclip plugin that a case status changed.
Fire-and-forget: logs errors but never raises, so callers aren't blocked.
"""
try:
await pc_request(
"POST",
"/api/plugins/marcusgroup.legal-ai/webhooks/case-status",
json={
"caseNumber": case_number,
"oldStatus": old_status,
"newStatus": new_status,
"companyId": company_id,
"timestamp": datetime.utcnow().isoformat() + "Z",
},
run_id=run_id,
timeout=5.0,
)
except Exception as exc:
logger.warning(
"emit_case_status_webhook failed for case %s (%s%s): %s",
case_number, old_status, new_status, exc,
)