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 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 fastapi.responses import FileResponse, StreamingResponse
from typing import Any, Literal from typing import Any, Literal
from pydantic import BaseModel from pydantic import BaseModel
@@ -44,7 +44,7 @@ from web.mcp_env_catalog import (
normalize_for_compare, normalize_for_compare,
) )
from web.progress_store import ProgressStore 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 ( from web.paperclip_client import (
COMPANIES as PAPERCLIP_COMPANIES, COMPANIES as PAPERCLIP_COMPANIES,
accept_interaction as pc_accept_interaction, accept_interaction as pc_accept_interaction,
@@ -1337,8 +1337,12 @@ async def api_case_get(case_number: str):
@app.put("/api/cases/{case_number}") @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.""" """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( result = await cases_tools.case_update(
case_number=case_number, case_number=case_number,
status=req.status, status=req.status,
@@ -1351,10 +1355,30 @@ async def api_case_update(case_number: str, req: CaseUpdateRequest):
expected_outcome=req.expected_outcome, expected_outcome=req.expected_outcome,
) )
try: try:
return json.loads(result) parsed = json.loads(result)
except json.JSONDecodeError: except json.JSONDecodeError:
raise HTTPException(404, result) 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") @app.delete("/api/cases")
async def api_case_delete(case_number: str, remove_files: bool = False): 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 logging
import os import os
from datetime import datetime
from typing import Any from typing import Any
import httpx import httpx
@@ -81,3 +82,35 @@ async def pc_request(
if raise_on_error: if raise_on_error:
resp.raise_for_status() resp.raise_for_status()
return resp 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,
)