mirror of
https://github.com/katanemo/plano.git
synced 2026-05-09 07:42:43 +02:00
simplifying ui
This commit is contained in:
parent
eb7d7be76c
commit
43799ded85
1 changed files with 91 additions and 154 deletions
|
|
@ -1,13 +1,11 @@
|
|||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
import httpx
|
||||
import streamlit as st
|
||||
|
||||
# Configuration
|
||||
PLANO_ENDPOINT = os.getenv("PLANO_ENDPOINT", "http://localhost:8001/v1")
|
||||
CASE_SERVICE_URL = "http://localhost:10540"
|
||||
|
||||
st.set_page_config(
|
||||
page_title="Credit Risk Case Copilot",
|
||||
|
|
@ -18,7 +16,7 @@ st.set_page_config(
|
|||
|
||||
|
||||
# Load scenarios
|
||||
def load_scenario(scenario_file):
|
||||
def load_scenario(scenario_file: str):
|
||||
"""Load scenario JSON from file."""
|
||||
try:
|
||||
with open(scenario_file, "r") as f:
|
||||
|
|
@ -30,13 +28,15 @@ def load_scenario(scenario_file):
|
|||
# Initialize session state
|
||||
if "assessment_result" not in st.session_state:
|
||||
st.session_state.assessment_result = None
|
||||
if "case_id" not in st.session_state:
|
||||
st.session_state.case_id = None
|
||||
if "raw_result" not in st.session_state:
|
||||
st.session_state.raw_result = None
|
||||
if "application_json" not in st.session_state:
|
||||
st.session_state.application_json = "{}"
|
||||
|
||||
|
||||
# Header
|
||||
st.title("🏦 Credit Risk Case Copilot")
|
||||
st.markdown("**AI-Powered Credit Risk Assessment & Case Management**")
|
||||
st.markdown("A minimal UI for the Plano + CrewAI credit risk demo.")
|
||||
st.divider()
|
||||
|
||||
# Sidebar
|
||||
|
|
@ -67,38 +67,45 @@ with st.sidebar:
|
|||
# JSON input area
|
||||
application_json = st.text_area(
|
||||
"Loan Application JSON",
|
||||
value=st.session_state.get("application_json", "{}"),
|
||||
height=400,
|
||||
value=st.session_state.application_json,
|
||||
height=380,
|
||||
help="Paste or edit loan application JSON",
|
||||
)
|
||||
|
||||
# Assess button
|
||||
if st.button("🔍 Assess Risk", type="primary", use_container_width=True):
|
||||
try:
|
||||
# Parse JSON
|
||||
application_data = json.loads(application_json)
|
||||
col_a, col_b = st.columns(2)
|
||||
|
||||
# Call Plano orchestrator
|
||||
with st.spinner("Running risk assessment..."):
|
||||
response = httpx.post(
|
||||
f"{PLANO_ENDPOINT}/chat/completions",
|
||||
json={
|
||||
"model": "gpt-4o",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": f"Assess credit risk for this loan application:\n\n{json.dumps(application_data, indent=2)}",
|
||||
}
|
||||
],
|
||||
},
|
||||
timeout=60.0,
|
||||
)
|
||||
with col_a:
|
||||
if st.button("🔍 Assess Risk", type="primary", use_container_width=True):
|
||||
try:
|
||||
application_data = json.loads(application_json)
|
||||
|
||||
with st.spinner("Running risk assessment..."):
|
||||
response = httpx.post(
|
||||
f"{PLANO_ENDPOINT}/chat/completions",
|
||||
json={
|
||||
# Use risk_reasoning if you’re standardizing on aliases.
|
||||
# If you want plain OpenAI model routing, set "gpt-4o".
|
||||
"model": "risk_reasoning",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": (
|
||||
"Assess credit risk for this loan application:\n\n"
|
||||
f"{json.dumps(application_data, indent=2)}"
|
||||
),
|
||||
}
|
||||
],
|
||||
},
|
||||
timeout=60.0,
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
content = result["choices"][0]["message"]["content"]
|
||||
raw = response.json()
|
||||
st.session_state.raw_result = raw
|
||||
|
||||
# Extract JSON from response
|
||||
content = raw["choices"][0]["message"]["content"]
|
||||
|
||||
# Extract JSON block from response
|
||||
if "```json" in content:
|
||||
json_start = content.index("```json") + 7
|
||||
json_end = content.index("```", json_start)
|
||||
|
|
@ -107,160 +114,90 @@ with st.sidebar:
|
|||
st.session_state.assessment_result = assessment
|
||||
st.success("✅ Risk assessment complete!")
|
||||
else:
|
||||
st.error("Could not parse assessment result")
|
||||
st.session_state.assessment_result = None
|
||||
st.error(
|
||||
"Could not parse JSON assessment from the agent response."
|
||||
)
|
||||
else:
|
||||
st.session_state.assessment_result = None
|
||||
st.session_state.raw_result = {
|
||||
"status_code": response.status_code,
|
||||
"text": response.text,
|
||||
}
|
||||
st.error(f"Error: {response.status_code} - {response.text}")
|
||||
|
||||
except json.JSONDecodeError:
|
||||
st.error("Invalid JSON format")
|
||||
except Exception as e:
|
||||
st.error(f"Error: {str(e)}")
|
||||
except json.JSONDecodeError:
|
||||
st.error("Invalid JSON format")
|
||||
except Exception as e:
|
||||
st.error(f"Error: {str(e)}")
|
||||
|
||||
with col_b:
|
||||
if st.button("🧹 Clear", use_container_width=True):
|
||||
st.session_state.assessment_result = None
|
||||
st.session_state.raw_result = None
|
||||
st.session_state.application_json = "{}"
|
||||
st.rerun()
|
||||
|
||||
|
||||
# Main content area
|
||||
if st.session_state.assessment_result:
|
||||
result = st.session_state.assessment_result
|
||||
|
||||
# Risk summary
|
||||
st.header("Risk Assessment Summary")
|
||||
st.header("Decision")
|
||||
|
||||
col1, col2, col3, col4 = st.columns(4)
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
risk_color = {"LOW": "🟢", "MEDIUM": "🟡", "HIGH": "🔴"}
|
||||
st.metric(
|
||||
"Risk Band",
|
||||
f"{risk_color.get(result['risk_band'], '⚪')} {result['risk_band']}",
|
||||
)
|
||||
risk_band = result.get("risk_band", "UNKNOWN")
|
||||
st.metric("Risk Band", f"{risk_color.get(risk_band, '⚪')} {risk_band}")
|
||||
|
||||
with col2:
|
||||
st.metric("Confidence", f"{result['confidence']:.1%}")
|
||||
confidence = result.get("confidence", 0.0)
|
||||
try:
|
||||
st.metric("Confidence", f"{float(confidence):.0%}")
|
||||
except Exception:
|
||||
st.metric("Confidence", str(confidence))
|
||||
|
||||
with col3:
|
||||
st.metric("Recommended Action", result["recommended_action"])
|
||||
|
||||
with col4:
|
||||
st.metric("Documents Required", len(result.get("required_documents", [])))
|
||||
st.metric("Recommended Action", result.get("recommended_action", "REVIEW"))
|
||||
|
||||
st.divider()
|
||||
|
||||
# Tabbed interface
|
||||
tab1, tab2, tab3, tab4, tab5 = st.tabs(
|
||||
[
|
||||
"📊 Risk Summary",
|
||||
"🎯 Risk Drivers",
|
||||
"📋 Policy & Compliance",
|
||||
"📝 Decision Memo",
|
||||
"🔍 Audit Trail",
|
||||
]
|
||||
)
|
||||
tab1, tab2, tab3 = st.tabs(["🧾 Summary", "📝 Decision Memo", "🧪 Raw Output"])
|
||||
|
||||
with tab1:
|
||||
st.subheader("Summary")
|
||||
|
||||
human = result.get("human_response", "")
|
||||
if human:
|
||||
st.write(human.split("```")[0].strip())
|
||||
else:
|
||||
st.info("No human-readable summary available.")
|
||||
|
||||
st.divider()
|
||||
st.subheader("Normalized Application")
|
||||
st.json(result.get("normalized_application", {}))
|
||||
|
||||
st.subheader("Assessment Overview")
|
||||
st.write(result.get("human_response", "").split("```")[0])
|
||||
|
||||
with tab2:
|
||||
st.subheader("Risk Drivers")
|
||||
drivers = result.get("drivers", [])
|
||||
|
||||
for driver in drivers:
|
||||
impact_color = {"CRITICAL": "🔴", "HIGH": "🟠", "MEDIUM": "🟡", "LOW": "🟢"}
|
||||
st.markdown(
|
||||
f"**{impact_color.get(driver['impact'], '⚪')} {driver['factor']}** ({driver['impact']})"
|
||||
)
|
||||
st.write(driver["evidence"])
|
||||
st.divider()
|
||||
st.subheader("Decision Memo")
|
||||
memo = result.get("decision_memo", "")
|
||||
if memo:
|
||||
st.markdown(memo)
|
||||
else:
|
||||
st.info("No decision memo available.")
|
||||
|
||||
with tab3:
|
||||
st.subheader("Policy Checks")
|
||||
checks = result.get("policy_checks", [])
|
||||
st.subheader("Raw Output")
|
||||
with st.expander("Show raw agent response JSON"):
|
||||
st.json(st.session_state.raw_result or {})
|
||||
|
||||
for check in checks:
|
||||
status_icon = {"PASS": "✅", "FAIL": "❌", "WARNING": "⚠️"}
|
||||
st.markdown(
|
||||
f"{status_icon.get(check['status'], '❓')} **{check['check']}**: {check['details']}"
|
||||
)
|
||||
|
||||
st.divider()
|
||||
|
||||
exceptions = result.get("exceptions", [])
|
||||
if exceptions:
|
||||
st.subheader("⚠️ Policy Exceptions")
|
||||
for exc in exceptions:
|
||||
st.warning(exc)
|
||||
|
||||
st.divider()
|
||||
|
||||
st.subheader("📎 Required Documents")
|
||||
docs = result.get("required_documents", [])
|
||||
for doc in docs:
|
||||
st.write(f"- {doc}")
|
||||
|
||||
with tab4:
|
||||
st.subheader("Decision Memo")
|
||||
st.markdown(result.get("decision_memo", "No memo available"))
|
||||
|
||||
with tab5:
|
||||
st.subheader("Audit Trail")
|
||||
audit = result.get("audit_trail", {})
|
||||
st.json(audit)
|
||||
|
||||
# Case creation
|
||||
st.divider()
|
||||
st.header("📁 Case Management")
|
||||
|
||||
col1, col2 = st.columns([3, 1])
|
||||
|
||||
with col1:
|
||||
if st.session_state.case_id:
|
||||
st.success(f"✅ Case created: **{st.session_state.case_id}**")
|
||||
else:
|
||||
st.info(
|
||||
"Create a case to store this assessment in the case management system"
|
||||
)
|
||||
|
||||
with col2:
|
||||
if not st.session_state.case_id:
|
||||
if st.button("📁 Create Case", type="primary", use_container_width=True):
|
||||
try:
|
||||
# Create case via direct API
|
||||
case_data = {
|
||||
"applicant_name": result["normalized_application"][
|
||||
"applicant_name"
|
||||
],
|
||||
"loan_amount": result["normalized_application"]["loan_amount"],
|
||||
"risk_band": result["risk_band"],
|
||||
"confidence": result["confidence"],
|
||||
"recommended_action": result["recommended_action"],
|
||||
"required_documents": result.get("required_documents", []),
|
||||
"policy_exceptions": result.get("exceptions", []),
|
||||
"notes": result.get("decision_memo", "")[:500],
|
||||
}
|
||||
|
||||
response = httpx.post(
|
||||
f"{CASE_SERVICE_URL}/cases", json=case_data, timeout=10.0
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
case_result = response.json()
|
||||
st.session_state.case_id = case_result["case_id"]
|
||||
st.rerun()
|
||||
else:
|
||||
st.error(f"Failed to create case: {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Error creating case: {str(e)}")
|
||||
else:
|
||||
if st.button("🔄 Reset", use_container_width=True):
|
||||
st.session_state.case_id = None
|
||||
st.session_state.assessment_result = None
|
||||
st.rerun()
|
||||
with st.expander("Show parsed assessment JSON"):
|
||||
st.json(result)
|
||||
|
||||
else:
|
||||
st.info(
|
||||
"👈 Select a scenario or paste a loan application JSON in the sidebar, then click 'Assess Risk'"
|
||||
"👈 Select a scenario or paste a loan application JSON in the sidebar, then click **Assess Risk**."
|
||||
)
|
||||
|
||||
st.subheader("Sample Application Format")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue