feat: emit missing_precedent + export_complete webhooks to plugin
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 9s

Adds two webhook emitters in paperclip_api.py that the plugin's
onWebhook handler now routes by ``eventType``:

* ``emit_missing_precedent_webhook(...)`` — fires from
  POST /api/missing-precedents on first insert (non-duplicate).
  The plugin surfaces an askUserQuestions interaction on the
  linked issue so Daphna can choose upload / irrelevant / defer
  without needing to open the legal-ai UI.

* ``emit_export_complete_webhook(...)`` — fires from
  POST /api/cases/{n}/export-docx after a successful export. The
  plugin attaches a "final-decision" markdown document with a
  download link to the linked Paperclip issue.

Both are fire-and-forget BackgroundTasks — failures are logged
but never block the originating request. Company resolution
follows the same 1xxx→licensing / 8-9xxx→betterment rule used
by emit_case_status_webhook.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 13:29:04 +00:00
parent 1d4f214abe
commit b01722b1b4
2 changed files with 151 additions and 7 deletions

View File

@@ -100,6 +100,7 @@ async def emit_case_status_webhook(
"POST",
"/api/plugins/marcusgroup.legal-ai/webhooks/case-status",
json={
"eventType": "status_change",
"caseNumber": case_number,
"oldStatus": old_status,
"newStatus": new_status,
@@ -114,3 +115,90 @@ async def emit_case_status_webhook(
"emit_case_status_webhook failed for case %s (%s%s): %s",
case_number, old_status, new_status, exc,
)
async def emit_missing_precedent_webhook(
*,
case_number: str,
missing_precedent_id: str,
citation: str,
cited_by_party: str | None = None,
cited_by_party_name: str | None = None,
legal_topic: str | None = None,
legal_issue: str | None = None,
company_id: str | None = None,
run_id: str | None = None,
) -> None:
"""Tell the plugin that a missing precedent was logged for a case.
The plugin uses this to surface an ``askUserQuestions`` interaction
on the linked Paperclip issue so the chair can decide whether to
upload the cited precedent or mark it irrelevant.
Fire-and-forget.
"""
try:
await pc_request(
"POST",
"/api/plugins/marcusgroup.legal-ai/webhooks/case-status",
json={
"eventType": "missing_precedent_created",
"caseNumber": case_number,
"companyId": company_id,
"timestamp": datetime.now(timezone.utc).isoformat(),
"missingPrecedent": {
"id": missing_precedent_id,
"citation": citation,
"citedByParty": cited_by_party,
"citedByPartyName": cited_by_party_name,
"legalTopic": legal_topic,
"legalIssue": legal_issue,
},
},
run_id=run_id,
timeout=5.0,
)
except Exception as exc:
logger.warning(
"emit_missing_precedent_webhook failed for case %s (%s): %s",
case_number, citation, exc,
)
async def emit_export_complete_webhook(
*,
case_number: str,
docx_filename: str,
docx_title: str | None = None,
company_id: str | None = None,
run_id: str | None = None,
) -> None:
"""Tell the plugin that a final DOCX was exported for a case.
The plugin uses this to attach a "final decision" document to the
linked Paperclip issue (markdown body with a download link to the
DOCX). Binary attachment is intentionally avoided — the SDK's
``documents.upsert`` accepts text only.
Fire-and-forget.
"""
try:
await pc_request(
"POST",
"/api/plugins/marcusgroup.legal-ai/webhooks/case-status",
json={
"eventType": "export_complete",
"caseNumber": case_number,
"companyId": company_id,
"timestamp": datetime.now(timezone.utc).isoformat(),
"docxFilename": docx_filename,
"docxTitle": docx_title or f"החלטה סופית — {case_number}",
},
run_id=run_id,
timeout=5.0,
)
except Exception as exc:
logger.warning(
"emit_export_complete_webhook failed for case %s (%s): %s",
case_number, docx_filename, exc,
)