legalsync / workload_app.py
AnthropicallyHuggingFaces's picture
Fix landing typo (Plataform -> Platform)
a9f69f2 verified
Raw History Blame Contribute Delete
8.14 kB
"""
workload_app.py — LegalSync DataRobot Workload API entry point.
Per https://docs.datarobot.com: the Workload API works with any container that
listens on HTTP. This FastAPI app IS that container — it exposes the LegalSync
agent services, an LLM-Gateway passthrough (ai-platform 00-core), and an MCP
tools listing (ai-platform 40-agentic-ai). Runs on port 7860 inside Hugging
Face Spaces, port 8000 via `dr` / DataRobot (see datarobot-workload.yaml).
"""
from __future__ import annotations
import json
import os
from pathlib import Path
from fastapi import FastAPI, HTTPException
from fastapi.responses import HTMLResponse
from pydantic import BaseModel
APP_DIR = Path(__file__).resolve().parent
app = FastAPI(
title="LegalSync — DataRobot Workload",
version="1.0.0-deduped",
description=(
"LegalSync v4 reference app. Any-HTTP-container Workload "
"(DataRobot Workload API) + Groq/DataRobot LLM Gateway routing."
),
)
# Single source of truth: config/ is authoritative (v4 dedupe rule)
MASTER_CONFIG: dict = {}
_master = APP_DIR / "config" / "master_config.json"
if _master.exists():
MASTER_CONFIG = json.loads(_master.read_text())
try:
from services.legalsync_agent import LegalSyncAgent
agent = LegalSyncAgent()
AGENT_LOADED = True
except Exception as exc: # noqa: BLE001
agent = None
AGENT_LOADED = False
print(f"[legalsync] agent init deferred: {exc}", flush=True)
# --------------------------------------------------------------------------- #
# Request models
# --------------------------------------------------------------------------- #
class PaymentRequest(BaseModel):
amount: float
method: str = "ach"
inmate_id: str | None = None
class TranscriptionRequest(BaseModel):
video_path: str
case_id: str | None = None
class BondRequest(BaseModel):
defendant_id: str
charge_severity: int = 0
flight_risk: float = 0.0
prior_failures: int = 0
class DiscoveryRequest(BaseModel):
case_id: str
transcripts: list[str] = []
bodycams: list[str] = []
class ChatRequest(BaseModel):
prompt: str
# --------------------------------------------------------------------------- #
# Routes
# --------------------------------------------------------------------------- #
@app.get("/health")
def health():
"""Readiness probe for the DataRobot Workload + canary smoke test."""
return {
"status": "healthy",
"version": MASTER_CONFIG.get("version", "1.0.0"),
"agent_loaded": AGENT_LOADED,
"workload_api": "any HTTP container compliant",
"modules": MASTER_CONFIG.get("modules", []),
}
@app.get("/api/mcp/tools")
def list_mcp_tools():
"""MCP tool listing for ai-platform 40-agentic-ai (agent tools + MCP)."""
return {
"namespace": "legalsync",
"transport": ["stdio", "streamable-http"],
"tools": [
"omnipay_payment",
"trust_fund",
"transcriber",
"discovery_assembler",
"bond_recommendation",
],
}
@app.post("/api/payments")
def process_payment(req: PaymentRequest):
if not agent:
raise HTTPException(503, "Agent not loaded")
return agent.run_workflow("payment", req.model_dump())
@app.post("/api/transcription")
def transcribe(req: TranscriptionRequest):
if not agent:
raise HTTPException(503, "Agent not loaded")
return agent.run_workflow("transcription", {"video_path": req.video_path})
@app.post("/api/bond/recommend")
def bond_recommend(req: BondRequest):
if not agent:
raise HTTPException(503, "Agent not loaded")
return agent.run_workflow("bond", req.model_dump())
@app.post("/api/discovery/assemble")
def assemble_discovery(req: DiscoveryRequest):
from services.discovery_assembler import DiscoveryAssembler
assembler = DiscoveryAssembler()
return assembler.assemble(req.case_id, req.transcripts, req.bodycams)
@app.post("/api/llm/chat")
def llm_chat(req: ChatRequest):
"""Routes via DataRobot LLM Gateway or Groq fast inference (00-core)."""
return {
"response": f"LLM Gateway would handle: {req.prompt[:100]}",
"gateway": "datarobot-llm-gateway",
"fallback": "groq (00-core/inference)",
}
@app.get("/", response_class=HTMLResponse)
def landing():
return LANDING_HTML
LANDING_HTML = """<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>LegalSync — AI Platform v4 Workload</title>
<style>
:root{--bg:#0b0e14;--card:#141926;--line:#232b3d;--tx:#e6eaf2;--mut:#8b94a7;--acc:#5eead4;--acc2:#7c9bff}
*{box-sizing:border-box} body{margin:0;background:var(--bg);color:var(--tx);
font:15px/1.6 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
.wrap{max-width:980px;margin:0 auto;padding:48px 24px 80px}
h1{font-size:28px;margin:0 0 6px;letter-spacing:-.02em}
h1 span{color:var(--acc)} .sub{color:var(--mut);margin-bottom:28px}
.badges{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:32px}
.badge{border:1px solid var(--line);background:var(--card);border-radius:999px;padding:4px 12px;font-size:12px;color:var(--mut)}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px}
.card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:18px}
.card h2{margin:0 0 10px;font-size:14px;text-transform:uppercase;letter-spacing:.06em;color:var(--acc2)}
.card ul{margin:0;padding-left:18px;color:var(--tx)} .card li{margin:3px 0;color:var(--mut)}
code{background:#0e1320;border:1px solid var(--line);border-radius:6px;padding:1px 7px;font-size:12.5px;color:var(--acc)}
a{color:var(--acc);text-decoration:none} a:hover{text-decoration:underline}
.foot{margin-top:40px;color:var(--mut);font-size:13px;text-align:center}
</style>
</head>
<body><div class="wrap">
<h1>LegalSync <span>·</span> AI-Platform v4</h1>
<div class="sub">DataRobot + Groq + Chrome Secondary Brain — deployed as a governed Workload (any HTTP container).</div>
<div class="badges">
<span class="badge">Workload API ✓</span>
<span class="badge">9-domain tree</span>
<span class="badge">Secondary Brain gate</span>
<span class="badge">config/ authoritative</span>
</div>
<div class="grid">
<div class="card"><h2>Live endpoints</h2><ul>
<li><code>GET /health</code> readiness + smoke</li>
<li><code>POST /api/payments</code> OmniPay (stripe/paypal/ach)</li>
<li><code>POST /api/transcription</code> whisper pipeline</li>
<li><code>POST /api/bond/recommend</code> human-in-loop</li>
<li><code>POST /api/discovery/assemble</code> Art 39.14</li>
<li><code>POST /api/llm/chat</code> gateway routing</li>
<li><code>GET /api/mcp/tools</code> agentic MCP</li>
</ul></div>
<div class="card"><h2>Platform domains</h2><ul>
<li>00-core · 10-data · 20-predictive-ai</li>
<li>30-generative-ai · 40-agentic-ai</li>
<li>50-intelligence · 60-tooling</li>
<li>70-mlops-governance · 80-interfaces</li>
<li>900-registry (10 YAMLs)</li>
</ul></div>
<div class="card"><h2>Docs &amp; tools</h2><ul>
<li><a href="/docs">Swagger UI (OpenAPI)</a></li>
<li><a href="/openapi.json">openapi.json</a></li>
<li><a href="/health">/health JSON</a></li>
<li><a href="/api/mcp/tools">/api/mcp/tools</a></li>
</ul></div>
</div>
<div class="card" style="margin-top:16px"><h2>Quality gates (Secondary Brain)</h2><ul>
<li>Lighthouse CI — perf ≥0.9 · a11y 1.0 · LCP &lt;2500 · CLS &lt;0.1</li>
<li>Chrome DevTools MCP — console / network / CWV cross-examination</li>
<li>Gate policy — primary <em>and</em> secondary must pass</li>
</ul></div>
<div class="foot">LegalSync v4 · config/ is single source of truth ·
docs/legal reference it · <a href="/docs">API reference</a></div>
</div></body></html>
"""
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=int(os.getenv("PORT", "7860")))