| 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) |
|
|