modelcourt / app.py
existcode's picture
reupload
f983375 verified
Raw
History Blame Contribute Delete
33 kB
from __future__ import annotations
import inspect
from html import escape
from typing import Any
import gradio as gr
import plotly.graph_objects as go
from core.config import (
MODEL_COURT_API_KEY,
MODEL_COURT_ENDPOINT,
MODEL_COURT_EXECUTION_PROFILE,
MODEL_COURT_MODE,
MODEL_COURT_MODEL,
)
from core.court import (
run_model_court,
run_model_court_benchmark,
run_model_court_parallel,
)
from core.court_client import get_court_client
from core.export import export_court_markdown
DEMO_CASE = """\
Claim title: Escalation case with disputed slip-and-fall evidence
Claim amount: $50,000
The claimant says she slipped on a wet floor near the frozen food aisle and injured her
back and wrist. The policy was active on the incident date. Store staff filed an incident
report the same day and the claimant went to urgent care that evening. There were no
witnesses. The store manager says there is no camera angle covering the aisle. The file
also notes a prior claim two years ago involving a similar back injury. Medical bills and
photos of the wet floor were submitted, but the maintenance log for that hour is missing.
"""
STRONG_CASE = """\
Claim title: Warehouse pallet injury claim
Claim amount: $18,500
The policy was active on the loss date. A warehouse worker was struck by a falling pallet
after a forklift clipped the rack. Two witnesses signed statements that match the incident
report. Video evidence confirms the rack movement and timestamp. The claimant received
same-day medical treatment for a shoulder injury. No prior claim history is listed.
"""
DENIAL_CASE = """\
Claim title: Water damage claim with lapsed coverage
Claim amount: $32,000
The claimant reports water damage to the basement after a pipe burst. The claim was filed
six weeks after the loss date. The policy had lapsed two months prior to the incident due
to non-payment, and the claimant was notified of the lapse in writing. The adjuster notes
inconsistent statements about when the claimant discovered the damage. No photos were
submitted at the time of filing. A plumber report was submitted two weeks after the claim
was filed. No witnesses. The file notes a prior water damage claim three years ago.
"""
MODEL_PROFILE_CHOICES = ["Deterministic Review", "AMD/Qwen Review", "AMD/Qwen Benchmark"]
PROFILE_CHOICES = (
MODEL_PROFILE_CHOICES if MODEL_COURT_MODE != "deterministic" else ["Deterministic Review"]
)
print(
"Model Court config:",
f"mode={MODEL_COURT_MODE}",
f"endpoint={MODEL_COURT_ENDPOINT or '<empty>'}",
f"model={MODEL_COURT_MODEL}",
f"api_key_set={bool(MODEL_COURT_API_KEY)}",
)
def _inference_status() -> str:
endpoint_status = (
"not configured"
if MODEL_COURT_MODE == "deterministic" or not MODEL_COURT_ENDPOINT
else "configured"
)
if (
MODEL_COURT_MODE != "deterministic"
and MODEL_COURT_ENDPOINT
and "localhost" in MODEL_COURT_ENDPOINT
):
endpoint_status = "local endpoint"
mode_label = (
"Deterministic fallback"
if MODEL_COURT_MODE == "deterministic"
else "AMD/Qwen model-backed"
)
return (
"<div class='mc-status-banner'>"
f"<b>Mode:</b> {mode_label}"
f"<span><b>Target model:</b> {MODEL_COURT_MODEL}</span>"
f"<span><b>AMD endpoint:</b> {endpoint_status}</span>"
"</div>"
)
CUSTOM_CSS = """
:root {
--mc-bg: #f7f4ee;
--mc-panel: #ffffff;
--mc-ink: #18231f;
--mc-muted: #69736e;
--mc-line: #d9ded8;
--mc-green: #1f6f4a;
--mc-green-dark: #164f36;
--mc-red: #b42318;
--mc-gold: #a96f13;
}
html,
body,
gradio-app {
background: var(--mc-bg) !important;
}
.gradio-container {
max-width: 1440px !important;
margin: 0 auto !important;
padding: 22px !important;
color: var(--mc-ink) !important;
background: var(--mc-bg) !important;
}
.gradio-container,
.gradio-container p,
.gradio-container li,
.gradio-container label,
.gradio-container span,
.gradio-container textarea,
.gradio-container input,
.gradio-container select {
color: var(--mc-ink) !important;
}
#mc-header {
padding: 16px 20px;
margin-bottom: 14px;
border: 1px solid #dce3dd;
border-radius: 10px;
background: #ffffff;
box-shadow: 0 8px 22px rgba(24, 35, 31, 0.08);
}
#mc-header h1 {
margin: 0 0 4px;
color: var(--mc-ink) !important;
font-size: 26px;
line-height: 1.1;
font-weight: 800;
}
#mc-header p,
#mc-header strong {
color: #34443d !important;
}
#mc-header p {
max-width: 960px;
margin: 0;
font-size: 14px;
line-height: 1.5;
}
.mc-mode-strip {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.mc-mode-strip span {
display: inline-flex;
align-items: center;
min-height: 30px;
padding: 5px 10px;
border: 1px solid #d7dfd8;
border-radius: 999px;
background: #f7faf7;
color: #25352f !important;
font-size: 12px;
font-weight: 650;
letter-spacing: 0.03em;
text-transform: uppercase;
}
.mc-status-banner {
display: flex;
flex-wrap: wrap;
gap: 10px 18px;
align-items: center;
margin-bottom: 14px;
padding: 11px 13px;
border: 1px solid #d9c48f;
border-radius: 8px;
background: #fff8e6;
color: #4d3711 !important;
font-size: 13px;
}
.mc-status-banner,
.mc-status-banner * {
color: #4d3711 !important;
}
.mc-input-panel,
.mc-output-panel {
padding: 16px;
border: 1px solid var(--mc-line);
border-radius: 10px;
background: var(--mc-panel) !important;
box-shadow: 0 8px 24px rgba(23, 32, 51, 0.08);
}
.mc-input-panel {
border-top: 4px solid var(--mc-green);
}
.mc-output-panel {
border-top: 4px solid var(--mc-green);
}
.mc-section-label {
margin: 0 0 8px;
color: var(--mc-muted) !important;
font-size: 12px;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.mc-section-label p {
font-size: 12px !important;
color: var(--mc-muted) !important;
font-weight: 800 !important;
letter-spacing: 0.08em !important;
text-transform: uppercase !important;
margin: 0 !important;
}
.mc-gate {
padding: 10px 12px;
border: 1px solid #fedf89;
border-radius: 8px;
background: #fffaeb;
color: #7a4b00 !important;
font-weight: 650;
font-size: 13px;
}
.mc-gate *,
.mc-gate p {
color: #7a4b00 !important;
font-size: 13px !important;
margin: 0 !important;
}
.mc-verdict {
min-height: 160px;
padding: 16px;
border: 1px solid #b7d7c2;
border-radius: 8px;
background: #f6fef9;
}
.mc-verdict,
.mc-verdict * {
color: var(--mc-ink) !important;
}
.mc-verdict h3 {
margin-top: 0;
color: var(--mc-green) !important;
font-size: 24px;
}
.mc-demo-row button,
.mc-demo-row .gr-button,
.mc-submit,
.mc-submit button,
.mc-submit .gr-button {
min-height: 40px;
font-weight: 650 !important;
}
.mc-submit,
.mc-submit button,
.mc-submit .gr-button {
border: 0 !important;
border-radius: 8px !important;
background: var(--mc-green) !important;
color: #ffffff !important;
box-shadow: 0 10px 22px rgba(31, 111, 74, 0.20);
}
.mc-submit:hover,
.mc-submit button:hover,
.mc-submit .gr-button:hover {
background: var(--mc-green-dark) !important;
}
.mc-input-panel .block,
.mc-input-panel .form,
.mc-input-panel .wrap,
.mc-output-panel .block,
.mc-output-panel .form,
.mc-output-panel .wrap {
background: #ffffff !important;
border-color: var(--mc-line) !important;
color: var(--mc-ink) !important;
}
.mc-output-panel .prose,
.mc-output-panel .md,
.mc-output-panel [data-testid="markdown"],
.mc-output-panel .markdown-text,
.mc-output-panel .gap,
.mc-output-panel .accordion,
.mc-output-panel .accordion-content {
background: #ffffff !important;
}
.mc-input-panel textarea,
.mc-input-panel input,
.mc-input-panel select,
.mc-input-panel [role="listbox"],
.mc-input-panel [data-testid="textbox"],
.mc-input-panel [data-testid="dropdown"],
.mc-output-panel textarea,
.mc-output-panel [data-testid="textbox"] {
background: #ffffff !important;
border-color: #cbd5e1 !important;
color: var(--mc-ink) !important;
}
.mc-input-panel textarea::placeholder,
.mc-input-panel input::placeholder {
color: #98a2b3 !important;
opacity: 1 !important;
}
.mc-input-panel button,
.mc-output-panel button {
color: var(--mc-ink) !important;
}
.mc-demo-row button {
background: #f2f4f7 !important;
border: 1px solid #d0d5dd !important;
color: #26362f !important;
}
.mc-demo-row button:hover,
.mc-demo-row .gr-button:hover {
background: #e9f5ee !important;
border-color: #aacdb8 !important;
color: var(--mc-green-dark) !important;
}
.tab-nav button {
border-radius: 6px 6px 0 0 !important;
font-weight: 650 !important;
}
.tab-nav button.selected {
color: var(--mc-green) !important;
border-bottom-color: var(--mc-green) !important;
}
.mc-output-panel .plot-container,
.mc-output-panel .js-plotly-plot,
.mc-output-panel [data-testid="plot"],
.mc-output-panel .gradio-plot,
.mc-output-panel .svelte-1yttk4p {
background: #ffffff !important;
}
footer {
display: none !important;
}
.mc-verdict-card {
display: grid;
gap: 14px;
}
.mc-case-summary {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
padding: 9px 10px;
border: 1px solid #cfe2d6;
border-radius: 8px;
background: #fbfdf9;
}
.mc-case-summary span {
display: inline-flex;
min-height: 24px;
align-items: center;
padding: 2px 8px;
border-radius: 999px;
background: #eef7f1;
color: var(--mc-green-dark) !important;
font-size: 12px;
font-weight: 750;
}
.mc-case-summary b {
margin-right: 2px;
color: var(--mc-ink) !important;
}
.mc-verdict-topline {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: flex-end;
}
.mc-decision {
padding: 4px 10px;
border-radius: 6px;
background: var(--mc-red);
color: #ffffff !important;
font-size: 30px;
font-weight: 850;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.mc-decision.approve {
background: var(--mc-green);
}
.mc-decision.settle,
.mc-decision.escalate,
.mc-decision.request-more-evidence {
background: var(--mc-gold);
}
.mc-metric {
display: grid;
gap: 2px;
padding: 3px 0;
}
.mc-metric b {
color: var(--mc-ink) !important;
font-size: 24px;
line-height: 1;
}
.mc-metric span {
color: var(--mc-muted) !important;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.mc-summary {
margin: 0;
color: #35453f !important;
font-size: 14px;
line-height: 1.5;
}
.mc-verdict-list {
margin: 0;
padding-left: 18px;
}
.mc-evidence-list,
.mc-role-list,
.mc-ranking-list,
.mc-cross-list,
.mc-agreement-list {
display: grid;
gap: 10px;
}
.mc-evidence-help {
margin: 0 0 10px;
padding: 9px 10px;
border: 1px solid #d9c48f;
border-radius: 8px;
background: #fff8e6;
color: #4d3711 !important;
font-size: 13px;
font-weight: 650;
line-height: 1.4;
}
.mc-evidence-item,
.mc-role-card,
.mc-ranking-item,
.mc-cross-item,
.mc-agreement-item {
padding: 12px;
border: 1px solid var(--mc-line);
border-radius: 8px;
background: #ffffff;
}
.mc-evidence-head,
.mc-role-head,
.mc-ranking-head,
.mc-cross-head {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
margin-bottom: 8px;
}
.mc-evidence-id,
.mc-citation-chip,
.mc-rank-chip {
display: inline-flex;
align-items: center;
min-height: 24px;
padding: 2px 8px;
border-radius: 999px;
background: #e9f5ee;
color: var(--mc-green-dark) !important;
font-size: 12px;
font-weight: 800;
letter-spacing: 0.03em;
}
.mc-tag {
display: inline-flex;
min-height: 22px;
align-items: center;
padding: 2px 7px;
border-radius: 999px;
background: #f2f4f7;
color: #475467 !important;
font-size: 12px;
font-weight: 700;
text-transform: capitalize;
}
.mc-tag.claimant {
background: #e9f5ee;
color: var(--mc-green-dark) !important;
}
.mc-tag.carrier {
background: #fdecec;
color: #912018 !important;
}
.mc-tag.strong {
background: #ecfdf3;
color: #05603a !important;
}
.mc-tag.moderate {
background: #fff8e6;
color: #7a4b00 !important;
}
.mc-fact,
.mc-role-card p,
.mc-ranking-item p,
.mc-cross-item p,
.mc-agreement-item p {
margin: 0;
color: #24362f !important;
font-size: 14px;
line-height: 1.5;
}
.mc-role-card b,
.mc-role-head b,
.mc-ranking-item b,
.mc-cross-item b,
.mc-agreement-item b {
color: var(--mc-ink) !important;
}
.mc-role-card li,
.mc-ranking-item li,
.mc-cross-item li,
.mc-agreement-item li {
color: #24362f !important;
}
.mc-citation-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 8px 0;
}
.mc-mini-label {
color: var(--mc-muted) !important;
font-size: 12px;
font-weight: 750;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.mc-disclaimer {
padding: 9px 10px;
border: 1px solid #d9ded8;
border-radius: 8px;
background: #f8faf7;
}
.mc-disclaimer,
.mc-disclaimer *,
.mc-disclaimer p,
.mc-disclaimer em {
font-size: 12px !important;
color: #52615b !important;
line-height: 1.4 !important;
font-style: italic;
opacity: 1 !important;
}
"""
CHART_LAYOUT = {
"paper_bgcolor": "rgba(0,0,0,0)",
"plot_bgcolor": "#ffffff",
"font": {"color": "#18231f", "family": "Arial, sans-serif"},
}
def _confidence_chart(payload: dict[str, Any]) -> go.Figure:
arguments = payload["arguments"]
fig = go.Figure(
go.Bar(
x=[item["role"].replace("_", " ").title() for item in arguments],
y=[round(item["confidence"] * 100, 1) for item in arguments],
marker_color=["#69736e", "#1f6f4a", "#b42318", "#2f5d50", "#a96f13"],
)
)
fig.update_layout(
**CHART_LAYOUT,
title="Agent Confidence",
yaxis_title="Confidence (%)",
xaxis_title="Role",
height=240,
margin={"l": 40, "r": 20, "t": 42, "b": 74},
)
return fig
def _evidence_chart(payload: dict[str, Any]) -> go.Figure:
evidence = payload["evidence"]
sides = ["claimant", "carrier", "neutral"]
counts = {side: 0 for side in sides}
for item in evidence:
counts[item["supports"]] += 1
fig = go.Figure(
go.Bar(
x=[side.title() for side in sides],
y=[counts[side] for side in sides],
marker_color=["#1f6f4a", "#b42318", "#69736e"],
)
)
fig.update_layout(
**CHART_LAYOUT,
title="Evidence Balance",
yaxis_title="Evidence items",
height=220,
margin={"l": 40, "r": 20, "t": 42, "b": 42},
)
return fig
def _evidence_markdown(payload: dict[str, Any]) -> str:
rows = []
for item in payload["evidence"]:
evidence_id = escape(item["evidence_id"])
supports = escape(item["supports"])
strength = escape(item["strength"])
source = escape(item["source"].replace("_", " "))
fact = escape(item["fact"])
rows.append(
"<div class='mc-evidence-item'>"
"<div class='mc-evidence-head'>"
f"<span class='mc-evidence-id'>Evidence {evidence_id[1:]}</span>"
f"<span class='mc-tag {supports}'>{supports}</span>"
f"<span class='mc-tag {strength}'>{strength}</span>"
f"<span class='mc-tag'>{source}</span>"
"</div>"
f"<p class='mc-fact'>{fact}</p>"
"</div>"
)
help_text = (
"<p class='mc-evidence-help'>"
"Evidence IDs are stable references used by every Role Agent and the Judge."
"</p>"
)
return help_text + "<div class='mc-evidence-list'>" + "".join(rows) + "</div>"
def _citation_chips(evidence_ids: list[str]) -> str:
if not evidence_ids:
return "<span class='mc-tag'>No cited Evidence Items</span>"
return "".join(
f"<span class='mc-citation-chip'>{escape(evidence_id)}</span>"
for evidence_id in evidence_ids
)
def _arguments_markdown(payload: dict[str, Any]) -> str:
blocks: list[str] = []
for argument in payload["arguments"]:
role = escape(argument["role"].replace("_", " ").title())
stance = escape(argument["stance"])
action = escape(argument["recommended_action"])
claims = "".join(f"<li>{escape(claim)}</li>" for claim in argument["claims"])
objections = "".join(f"<li>{escape(item)}</li>" for item in argument["objections"])
block = (
"<div class='mc-role-card'>"
"<div class='mc-role-head'>"
f"<b>{role}</b>"
f"<span class='mc-tag'>{stance}</span>"
f"<span class='mc-tag'>{argument['confidence']:.0%} confidence</span>"
"</div>"
"<div class='mc-mini-label'>Cited Evidence Items</div>"
f"<div class='mc-citation-row'>{_citation_chips(argument['evidence_cited'])}</div>"
f"<p><b>Recommended action:</b> {action}</p>"
"<div class='mc-mini-label'>Claims</div>"
f"<ul>{claims}</ul>"
)
if objections:
block += "<div class='mc-mini-label'>Objections</div>" f"<ul>{objections}</ul>"
block += "</div>"
blocks.append(block)
return "<div class='mc-role-list'>" + "".join(blocks) + "</div>"
def _cross_exam_markdown(payload: dict[str, Any]) -> str:
items = []
for item in payload["cross_examination"]:
examiner = escape(item["examiner_role"].replace("_", " ").title())
target = escape(item["target_role"].replace("_", " ").title())
items.append(
"<div class='mc-cross-item'>"
"<div class='mc-cross-head'>"
f"<b>{examiner}</b><span class='mc-tag'>questions</span><b>{target}</b>"
"</div>"
f"<p><b>Challenge:</b> {escape(item['challenge'])}</p>"
f"<p><b>Response:</b> {escape(item['target_response'])}</p>"
f"<p><b>Unresolved risk:</b> {escape(item['unresolved_risk'])}</p>"
"</div>"
)
return "<div class='mc-cross-list'>" + "".join(items) + "</div>"
def _agreement_map_markdown(payload: dict[str, Any]) -> str:
agreement_map = payload.get("agreement_map")
if not agreement_map:
return "_Agreement Map not available._"
items = [f"<p class='mc-fact'>{escape(agreement_map['summary'])}</p>"]
for item in agreement_map["items"]:
roles = (
", ".join(role.replace("_", " ").title() for role in item.get("roles", []))
or "none"
)
items.append(
"<div class='mc-agreement-item'>"
f"<p><span class='mc-tag'>{escape(item['status'])}</span> {escape(item['point'])}</p>"
f"<p><b>Roles:</b> {escape(roles)}</p>"
"<div class='mc-mini-label'>Cited Evidence Items</div>"
f"<div class='mc-citation-row'>{_citation_chips(item.get('evidence_cited', []))}</div>"
"</div>"
)
return "<div class='mc-agreement-list'>" + "".join(items) + "</div>"
def _decision_ranking_markdown(payload: dict[str, Any]) -> str:
ranking = payload["verdict"].get("decision_ranking", [])
if not ranking:
return "_Decision Ranking not available._"
rows = []
for item in sorted(ranking, key=lambda candidate: candidate["rank"]):
decision = escape(item["decision"].replace("_", " ").title())
reason = escape(item["reason"])
rows.append(
"<div class='mc-ranking-item'>"
"<div class='mc-ranking-head'>"
f"<span class='mc-rank-chip'>Rank {item['rank']}</span>"
f"<b>{decision}</b>"
"</div>"
f"<p>{reason}</p>"
"<div class='mc-mini-label'>Cited Evidence Items</div>"
f"<div class='mc-citation-row'>{_citation_chips(item.get('evidence_cited', []))}</div>"
"</div>"
)
return "<div class='mc-ranking-list'>" + "".join(rows) + "</div>"
def _latency_chart(payload: dict[str, Any]) -> go.Figure:
latencies = payload.get("latencies", [])
if not latencies:
return go.Figure().update_layout(paper_bgcolor="#ffffff", plot_bgcolor="#ffffff")
fig = go.Figure(
go.Bar(
x=[item["role"].replace("_", " ").title() for item in latencies],
y=[item["latency_seconds"] for item in latencies],
marker_color=[
"#a96f13" if item.get("fallback_used") else "#1f6f4a" for item in latencies
],
text=[f"{item['latency_seconds']:.2f}s" for item in latencies],
textposition="outside",
)
)
fig.update_layout(
**CHART_LAYOUT,
title="Role Agent Latency",
yaxis_title="Seconds",
height=240,
margin={"l": 40, "r": 20, "t": 42, "b": 74},
)
return fig
def _performance_markdown(payload: dict[str, Any]) -> str:
lines = [
f"- Execution Profile: **{payload.get('execution_profile', 'Unknown')}**",
f"- Inference: `{payload.get('inference_mode', 'unknown')}`",
]
total = payload.get("total_latency_seconds", 0.0)
if total:
lines.append(f"- Total Tribunal latency: **{total:.2f}s**")
waves = payload.get("wave_latency_seconds", [])
if waves:
labels = [
"Evidence Clerk",
"First-wave Role Agents",
"Domain Expert",
"Cross-Examination",
"Agreement Clerk",
"Judge",
]
lines.extend(["", "### Wave Breakdown"])
for index, duration in enumerate(waves):
label = labels[index] if index < len(labels) else f"Wave {index + 1}"
lines.append(f"- {label}: **{duration:.3f}s**")
benchmark = payload.get("benchmark")
if benchmark:
lines.extend(
[
"",
"### AMD/Qwen Benchmark",
f"- Model: `{benchmark['model_name']}`",
f"- Endpoint mode: `{benchmark['endpoint_mode']}`",
f"- First-wave speedup: **{benchmark['first_wave_speedup']:.2f}x**",
f"- Full Tribunal speedup: **{benchmark['full_tribunal_speedup']:.2f}x**",
(
f"- Sequential total: **{benchmark['sequential']['total_seconds']:.2f}s**; "
f"parallel total: **{benchmark['parallel']['total_seconds']:.2f}s**"
),
]
)
if not waves and not benchmark:
lines.append("\n_Latency data is only available for model-backed execution profiles._")
return "\n".join(lines)
def _verdict_panel(payload: dict[str, Any]) -> str:
verdict = payload["verdict"]
decision = verdict["decision"].replace("_", " ")
decision_class = verdict["decision"].replace("_", "-")
case_title = escape(payload.get("case_title", "Submitted case"))
execution_profile = escape(payload.get("execution_profile", "Unknown profile"))
inference_mode = escape(payload.get("inference_mode", "unknown"))
evidence_count = len(payload.get("evidence", []))
next_steps = "".join(
f"<li>{escape(step)}</li>" for step in verdict.get("required_next_steps", [])[:3]
)
return (
"<div class='mc-verdict-card'>"
"<div class='mc-case-summary'>"
f"<span><b>Case</b>{case_title}</span>"
f"<span><b>Profile</b>{execution_profile}</span>"
f"<span><b>Evidence</b>{evidence_count} items</span>"
f"<span><b>Inference</b>{inference_mode}</span>"
"</div>"
"<div class='mc-verdict-topline'>"
f"<div class='mc-decision {decision_class}'>{escape(decision)}</div>"
"<div class='mc-metric'>"
f"<b>{verdict['confidence']:.0%}</b><span>Confidence</span>"
"</div>"
"<div class='mc-metric'>"
f"<b>{verdict['estimated_exposure']}</b><span>Exposure</span>"
"</div>"
"<div class='mc-metric'>"
"<b>Required</b><span>Human review</span>"
"</div>"
"</div>"
f"<p class='mc-summary'>{escape(verdict['summary'])}</p>"
f"<p class='mc-summary'><b>Dissent:</b> {escape(verdict['dissenting_opinion'])}</p>"
f"<ul class='mc-verdict-list'>{next_steps}</ul>"
"</div>"
)
def _textbox_copy_button_kwargs() -> dict[str, Any]:
parameters = inspect.signature(gr.Textbox.__init__).parameters
if "show_copy_button" in parameters:
return {"show_copy_button": True}
if "buttons" in parameters:
return {"buttons": ["copy"]}
return {}
def launch_court(
execution_profile: str,
case_title: str,
case_text: str,
decision_question: str,
) -> tuple[
Any,
...,
]:
case_text = (case_text or "").strip()
case_title = (case_title or "Submitted case").strip()
decision_question = (
(decision_question or "").strip()
or "Should this claim be approved, denied, settled, or escalated?"
)
empty_outputs = ("", "", "", "", "", "", go.Figure(), "", "", {})
if not case_text:
message = "⚠️ The case file is empty. Paste a claim narrative to get started."
return (message, *empty_outputs)
if len(case_text) < 40:
return ("⚠️ The case file is too short for a meaningful verdict.", *empty_outputs)
try:
if execution_profile == "Deterministic Review" or MODEL_COURT_MODE == "deterministic":
result = run_model_court(
case_text=case_text,
domain="insurance_claim",
decision_question=decision_question,
case_title=case_title,
)
else:
client = get_court_client(MODEL_COURT_MODE, MODEL_COURT_ENDPOINT, MODEL_COURT_MODEL)
if execution_profile == "AMD/Qwen Benchmark":
result = run_model_court_benchmark(
case_text=case_text,
client=client,
model_name=MODEL_COURT_MODEL,
endpoint_mode=MODEL_COURT_MODE,
domain="insurance_claim",
decision_question=decision_question,
case_title=case_title,
)
else:
result = run_model_court_parallel(
case_text=case_text,
client=client,
domain="insurance_claim",
decision_question=decision_question,
case_title=case_title,
)
except Exception as exc:
return (
f"⚠️ Court pipeline failed: {exc}",
*empty_outputs,
)
payload = result.model_dump(mode="json")
audit_md = "\n".join(f"- {flag}" for flag in payload["audit_flags"])
return (
_verdict_panel(payload),
_evidence_markdown(payload),
_arguments_markdown(payload),
_cross_exam_markdown(payload),
_agreement_map_markdown(payload),
_decision_ranking_markdown(payload),
audit_md,
_latency_chart(payload),
_performance_markdown(payload),
export_court_markdown(result),
payload,
)
with gr.Blocks(title="Model Court") as demo:
gr.Markdown(
"# Model Court\n"
"Five AI agents review a claim from different angles — advocate, counsel, domain expert, "
"risk officer, and judge. They disagree where the evidence is thin and reach consensus "
"where it isn't. The result is a ranked decision with every argument and objection "
"in plain view.\n\n"
"<div class='mc-mode-strip'>"
f"<span>Mode: {MODEL_COURT_MODE}</span>"
f"<span>Model: {MODEL_COURT_MODEL}</span>"
"<span>AMD MI300X</span>"
"<span>JSON Audit Trail</span>"
"</div>",
elem_id="mc-header",
)
gr.Markdown(_inference_status())
with gr.Row():
with gr.Column(scale=1, elem_classes="mc-input-panel"):
gr.Markdown("Case Intake", elem_classes="mc-section-label")
execution_profile = gr.Dropdown(
label="Execution profile",
choices=PROFILE_CHOICES,
value=(
MODEL_COURT_EXECUTION_PROFILE
if MODEL_COURT_EXECUTION_PROFILE in PROFILE_CHOICES
else "Deterministic Review"
),
)
case_title = gr.Textbox(
label="Case title",
value="Escalation case with disputed slip-and-fall evidence",
)
decision_question = gr.Textbox(
label="Decision question",
value="Should this claim be approved, denied, settled, or escalated?",
lines=2,
)
case_text = gr.Textbox(label="Case file", value=DEMO_CASE, lines=16)
gr.Markdown(
"_Model Court gives you a structured recommendation, not a final call. "
"A human adjuster still needs to approve before anything happens._",
elem_classes="mc-disclaimer",
)
with gr.Row(elem_classes="mc-demo-row"):
demo_case_btn = gr.Button("Try: Slip-and-fall (disputed)")
strong_case_btn = gr.Button("Try: Warehouse injury (approve)")
denial_case_btn = gr.Button("Try: Water damage (deny)")
submit = gr.Button("Run tribunal", variant="primary", elem_classes="mc-submit")
with gr.Column(scale=2, elem_classes="mc-output-panel"):
gr.Markdown("Tribunal Output", elem_classes="mc-section-label")
verdict_out = gr.Markdown(label="Verdict", elem_classes="mc-verdict")
gr.Markdown(
"**⚠️ A human must review and approve this verdict before any action is taken.**",
elem_classes="mc-gate",
)
with gr.Accordion("Evidence Items", open=False):
with gr.Group():
evidence_out = gr.Markdown()
with gr.Accordion("Role Agent Arguments", open=False):
with gr.Group():
arguments_out = gr.Markdown()
with gr.Accordion("Cross-Examination", open=False):
with gr.Group():
cross_exam_out = gr.Markdown()
with gr.Accordion("Agreement Map", open=False):
with gr.Group():
agreement_map_out = gr.Markdown()
with gr.Accordion("Decision Ranking", open=False):
with gr.Group():
decision_ranking_out = gr.Markdown()
with gr.Accordion("Audit Flags", open=False):
with gr.Group():
audit_flags_out = gr.Markdown()
with gr.Accordion("Performance", open=False):
with gr.Group():
latency_chart = gr.Plot(label="Role Agent Latency", show_label=False)
performance_out = gr.Markdown()
with gr.Accordion("Markdown Report", open=False):
with gr.Group():
report_out = gr.Textbox(lines=20, **_textbox_copy_button_kwargs())
with gr.Accordion("Full Audit Trail (JSON)", open=False):
with gr.Group():
json_report_out = gr.JSON()
demo_case_btn.click(
lambda: (
"Escalation case with disputed slip-and-fall evidence",
DEMO_CASE,
"Should this claim be approved, denied, settled, or escalated?",
),
outputs=[case_title, case_text, decision_question],
)
strong_case_btn.click(
lambda: (
"Warehouse pallet injury claim",
STRONG_CASE,
"Should this claim be approved, denied, settled, or escalated?",
),
outputs=[case_title, case_text, decision_question],
)
denial_case_btn.click(
lambda: (
"Water damage claim with lapsed coverage",
DENIAL_CASE,
"Should this claim be approved, denied, settled, or escalated?",
),
outputs=[case_title, case_text, decision_question],
)
submit.click(
launch_court,
inputs=[execution_profile, case_title, case_text, decision_question],
outputs=[
verdict_out,
evidence_out,
arguments_out,
cross_exam_out,
agreement_map_out,
decision_ranking_out,
audit_flags_out,
latency_chart,
performance_out,
report_out,
json_report_out,
],
)
if __name__ == "__main__":
demo.launch(css=CUSTOM_CSS)