File size: 8,135 Bytes
f4807d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a9f69f2
f4807d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
"""
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")))