Risk_Manager / app.py
GenAICoder's picture
Update app.py
f55eedf verified
Raw
History Blame Contribute Delete
34.6 kB
# app.py
import pandas as pd
import gradio as gr
# ---------------------------------------------------
# HELPERS
# ---------------------------------------------------
from helper.vintage_helpers import (
create_booking_vintage
)
from helper.data_merger import (
merge_acq_perf
)
# ---------------------------------------------------
# METRICS
# ---------------------------------------------------
from metrics.mix_metrics import (
calculate_vintage_mix,
calculate_limit_mix
)
# ---------------------------------------------------
# ANALYTICS
# ---------------------------------------------------
from analytics.performance_analysis import (
generate_metric_view
)
from analytics.ai_assistant import (
generate_ai_answer
)
from analytics.deep_dive_agentic import (
run_deep_dive_analysis
)
# ---------------------------------------------------
# VISUALIZATIONS - VINTAGE CURVES
# ---------------------------------------------------
from visualizations.vintage_curves import (
generate_delinquency_metric_chart,
generate_multi_metric_comparison,
generate_segment_delinquency_curve
)
# ---------------------------------------------------
# VISUALIZATIONS - SEGMENT RANKING
# ---------------------------------------------------
from visualizations.segment_ranking import (
generate_segment_risk_heatmap,
generate_segment_risk_ranking,
generate_multi_category_risk_comparison,
calculate_portfolio_risk_summary
)
# ---------------------------------------------------
# LOAD DATA
# ---------------------------------------------------
acq = pd.read_csv(
"data/acquisition.csv"
)
perf = pd.read_csv(
"data/performance.csv"
)
# ---------------------------------------------------
# CREATE BOOKING VINTAGE
# ---------------------------------------------------
acq = create_booking_vintage(
acq,
booking_date_col="booking_date"
)
# ---------------------------------------------------
# CREATE MASTER PERFORMANCE DATASET
# ---------------------------------------------------
master_df = merge_acq_perf(
acq_df=acq,
perf_df=perf
)
# ---------------------------------------------------
# ACQUISITION ANALYSIS
# ---------------------------------------------------
def run_acquisition_analysis(
analysis_type,
category
):
# -----------------------------------------
# PORTFOLIO MIX
# -----------------------------------------
if analysis_type == "Portfolio Mix":
result = (
acq.groupby(
["booking_vintage", category]
)
.agg(
count=("account_id", "nunique"),
balance=("credit_limit", "sum")
)
.reset_index()
)
vintage_total = (
result.groupby("booking_vintage")["count"]
.transform("sum")
)
result["rate"] = (
result["count"] / vintage_total
) * 100
result["rate"] = (
result["rate"]
.round(2)
)
# -----------------------------------------
# CREDIT LINE CONCENTRATION
# -----------------------------------------
elif analysis_type == "Credit Line Concentration":
result = (
acq.groupby(
["booking_vintage", category]
)
.agg(
count=("account_id", "nunique"),
balance=("credit_limit", "sum")
)
.reset_index()
)
vintage_total = (
result.groupby("booking_vintage")["balance"]
.transform("sum")
)
result["rate"] = (
result["balance"] / vintage_total
) * 100
result["rate"] = (
result["rate"]
.round(2)
)
else:
return pd.DataFrame()
# -----------------------------------------
# STANDARDIZED OUTPUT
# -----------------------------------------
result = result.rename(
columns={
"booking_vintage": "Vintage",
category: "Category",
"count": "Count",
"balance": "Balance",
"rate": "Rate"
}
)
return result[
[
"Vintage",
"Category",
"Count",
"Balance",
"Rate"
]
]
# ---------------------------------------------------
# PERFORMANCE ANALYSIS
# ---------------------------------------------------
def run_performance_analysis(
metric_name,
view_level
):
# -----------------------------------------
# VIEW MAPPING
# -----------------------------------------
view_mapping = {
"Overall": None,
"Channel":
"sourcing_channel",
"FICO":
"fico_band",
"City Tier":
"city_tier",
"Occupation":
"occupation_type"
}
group_col = view_mapping[
view_level
]
# -----------------------------------------
# CALL ANALYTICS ENGINE
# -----------------------------------------
result = generate_metric_view(
df=master_df,
metric_name=metric_name,
group_col=group_col
)
# -----------------------------------------
# STANDARDIZE OUTPUT
# -----------------------------------------
if group_col is not None:
result = result.rename(
columns={
group_col: "Category"
}
)
else:
result["Category"] = "Overall"
# -----------------------------------------
# IDENTIFY RATE COLUMN
# -----------------------------------------
rate_col = [
col for col in result.columns
if "rate" in col.lower()
][0]
# -----------------------------------------
# OUTPUT FORMAT
# -----------------------------------------
final_result = pd.DataFrame()
final_result["Vintage"] = (
result["booking_vintage"]
)
final_result["Category"] = (
result["Category"]
)
final_result["Count"] = (
result["total_accounts"]
)
final_result["Balance"] = (
result["total_balance"]
)
final_result["Rate"] = (
result[rate_col]
.round(2)
)
return final_result
# ---------------------------------------------------
# VINTAGE CURVES ANALYSIS
# ---------------------------------------------------
def generate_vintage_curve_single(
metric_name
):
"""Generate single vintage curve for a metric."""
try:
fig = generate_delinquency_metric_chart(
df=master_df,
metric_name=metric_name,
chart_type="line"
)
return fig
except Exception as e:
return f"Error generating vintage curve: {str(e)}"
def generate_vintage_curves_comparison():
"""Generate comparison of all vintage curves."""
try:
fig = generate_multi_metric_comparison(
df=master_df,
metrics=["30+@3", "30+@6", "60+@6", "Yr1 NCL"]
)
return fig
except Exception as e:
return f"Error generating comparison: {str(e)}"
def generate_segmented_vintage_curve(
metric_name,
category
):
"""Generate vintage curve segmented by category."""
try:
fig = generate_segment_delinquency_curve(
df=master_df,
metric_name=metric_name,
category=category
)
return fig
except Exception as e:
return f"Error generating segmented curve: {str(e)}"
# ---------------------------------------------------
# SEGMENT RANKING ANALYSIS
# ---------------------------------------------------
def generate_segment_risk_heatmap_chart():
"""Generate risk heatmap across all segments and metrics."""
try:
fig = generate_segment_risk_heatmap(
df=master_df
)
return fig
except Exception as e:
return f"Error generating heatmap: {str(e)}"
def generate_high_risk_segments_ranking(
metric_name,
category
):
"""Generate ranking of high-risk segments."""
try:
fig = generate_segment_risk_ranking(
df=master_df,
metric_name=metric_name,
category=category,
top_n=10
)
return fig
except Exception as e:
return f"Error generating ranking: {str(e)}"
def generate_multi_category_comparison(
metric_name
):
"""Generate risk comparison across all categories."""
try:
fig = generate_multi_category_risk_comparison(
df=master_df,
metric_name=metric_name
)
return fig
except Exception as e:
return f"Error generating comparison: {str(e)}"
def generate_portfolio_summary():
"""Generate portfolio risk summary."""
try:
summary_df = calculate_portfolio_risk_summary(
df=master_df
)
return summary_df
except Exception as e:
return f"Error generating summary: {str(e)}"
def ask_ai_question(question, as_of_month, segment, history):
if not question or str(question).strip() == "":
return history or [], history or [], ""
try:
answer = generate_ai_answer(
question=question,
df=master_df,
as_of_month=as_of_month,
segment=segment if segment != "" else None
)
except Exception as exc:
answer = f"AI error: {str(exc)}"
history = history or []
history.append({"role": "user", "content": question})
history.append({"role": "assistant", "content": answer})
return history, history, ""
def run_deep_dive_on_question(question):
if not question or str(question).strip() == "":
return "Please enter a question."
try:
result = run_deep_dive_analysis(question, acq, perf, master_df)
# Check if analysis was successful
if not result["success"]:
return f"**Error in Deep Dive Analysis:**\n{result.get('error', 'Unknown error')}"
# Check if we have empty results
if not result.get("requirements") and not result.get("all_results"):
return (
f"**Question:** {result['question']}\n\n"
f"⚠️ **No analysis requirements generated.** \n"
f"This may indicate:\n"
f"- The LLM couldn't parse the question clearly\n"
f"- API connectivity issue\n"
f"\nPlease try:\n"
f"- Rephrasing your question more specifically\n"
f"- Asking about specific metrics (30+@6, Yr1 NCL) or segments (FICO, Channel, City Tier)\n"
f"- Breaking down complex questions into simpler components\n\n"
f"**Raw Response:** {result.get('interpretation', 'No interpretation')}"
)
output = f"**Question:** {result['question']}\n\n"
# Display each requirement and its result
if result.get("requirements"):
output += "## Analysis Requirements\n\n"
for i, req in enumerate(result["requirements"], 1):
output += f"**Requirement {i}:** {req.get('title', 'Untitled')}\n"
output += f"- {req.get('description', 'No description')}\n\n"
if "code" in req:
output += f"Generated Code:\n```python\n{req['code']}\n```\n\n"
else:
output += "⚠️ No requirements generated\n\n"
# Display execution results
if result.get("all_results"):
output += "## Execution Results\n\n"
for i, res in enumerate(result["all_results"], 1):
output += f"**Requirement {i} Results:**\n"
if res.get("success"):
output += f"✓ Executed successfully\n"
output += f"```\n{res.get('result', 'No result')}\n```\n\n"
else:
output += f"✗ Execution failed: {res.get('error', 'Unknown error')}\n\n"
else:
output += "⚠️ No execution results\n\n"
# Display synthesis & insights
if result.get("interpretation"):
output += "## Analyst Interpretation\n\n"
output += f"{result['interpretation']}"
else:
output += "⚠️ No interpretation available"
return output
except Exception as exc:
import traceback
return f"**Deep Dive Analysis Error:**\n```\n{traceback.format_exc()}\n```"
# ---------------------------------------------------
# PORTFOLIO OVERVIEW (Calendar Snapshot)
# ---------------------------------------------------
def _detect_date_column(df):
candidates = [
"reporting_month",
"observation_date",
"observation_month",
"obs_date",
"date",
"calendar_month",
"month",
"report_date"
]
for c in candidates:
if c in df.columns:
return c
return None
def get_calendar_months():
date_col = _detect_date_column(master_df)
if date_col is None:
return []
ser = pd.to_datetime(master_df[date_col], errors="coerce")
months = ser.dt.to_period("M").astype(str).dropna().unique().tolist()
months.sort()
return months
def _filter_master_by_month(as_of_month):
# as_of_month expected like "YYYY-MM"
date_col = _detect_date_column(master_df)
if date_col is None or not as_of_month:
return master_df.copy()
ser = pd.to_datetime(master_df[date_col], errors="coerce").dt.to_period("M").astype(str)
return master_df[ser == as_of_month].copy()
def generate_portfolio_overview(as_of_month, segment):
"""
Returns a small DataFrame with key portfolio snapshot metrics for the selected calendar month and segment.
Metrics: Total Accounts, Open Accounts (balance>0), Bad Accounts (dpd>=30), Overall NCL Rate (dollar %), Average FICO.
"""
df = _filter_master_by_month(as_of_month)
# If a segmentation column is provided, return per-segment breakdown
valid_segments = [
"fico_band",
"sourcing_channel",
"city_tier",
"occupation_type"
]
if segment in valid_segments and segment in df.columns:
grp = segment
total_accounts = df.groupby(grp)["account_id"].nunique()
if "balance" in df.columns:
open_accounts = (
df.loc[df["balance"] > 0].groupby(grp)["account_id"].nunique()
)
total_balance = df.groupby(grp)["balance"].sum()
else:
open_accounts = pd.Series(0, index=total_accounts.index)
total_balance = pd.Series(0, index=total_accounts.index)
if "dpd" in df.columns:
bad_accounts = (
df.loc[df["dpd"].fillna(0) >= 30].groupby(grp)["account_id"].nunique()
)
bad_balance = (
df.assign(_bad_balance=df["balance"].where(df["dpd"].fillna(0) >= 30, 0))
.groupby(grp)["_bad_balance"].sum()
) if "balance" in df.columns else pd.Series(0, index=total_accounts.index)
else:
bad_accounts = pd.Series(0, index=total_accounts.index)
bad_balance = pd.Series(0, index=total_accounts.index)
# NCL if present
ncl_cols = [c for c in df.columns if "ncl" in c.lower()]
if len(ncl_cols) > 0:
total_ncl = df.groupby(grp)[ncl_cols[0]].sum()
ncl_rate = (total_ncl / total_balance * 100).round(2).fillna(0)
else:
ncl_rate = (bad_balance / total_balance * 100).round(2).fillna(0)
# average fico per group if available
if "fico_score" in df.columns:
avg_fico = df.groupby(grp)["fico_score"].mean().round(1)
else:
avg_fico = pd.Series(float("nan"), index=total_accounts.index)
result = pd.DataFrame({
"Segment": total_accounts.index,
"Total_Accounts": total_accounts.values,
"Open_Accounts": open_accounts.reindex(total_accounts.index).fillna(0).astype(int).values,
"Bad_Accounts": bad_accounts.reindex(total_accounts.index).fillna(0).astype(int).values,
"Total_Balance": total_balance.reindex(total_accounts.index).fillna(0).values,
"NCL_Rate_pct": ncl_rate.reindex(total_accounts.index).fillna(0).values,
"Avg_FICO": avg_fico.reindex(total_accounts.index).fillna(float("nan")).values
})
# Sort by NCL rate descending
result = result.sort_values("NCL_Rate_pct", ascending=False).reset_index(drop=True)
return result
# Default: single-line overview
total_accounts = df["account_id"].nunique() if "account_id" in df.columns else 0
open_accounts = (
df.loc[df["balance"] > 0, "account_id"].nunique()
if "balance" in df.columns
else total_accounts
)
bad_accounts = (
df.loc[df["dpd"].fillna(0) >= 30, "account_id"].nunique()
if "dpd" in df.columns
else 0
)
# overall NCL rate (dollar-based) fallback logic
ncl_cols = [c for c in df.columns if "ncl" in c.lower()]
overall_ncl_rate = None
if len(ncl_cols) > 0 and "balance" in df.columns:
ncl_sum = df[ncl_cols[0]].sum(skipna=True)
bal_sum = df["balance"].sum(skipna=True)
overall_ncl_rate = (ncl_sum / bal_sum * 100) if bal_sum > 0 else None
else:
# fallback: use bad balance / total balance as proxy
if "balance" in df.columns and "dpd" in df.columns:
bad_bal = df.loc[df["dpd"].fillna(0) >= 30, "balance"].sum()
bal_sum = df["balance"].sum()
overall_ncl_rate = (bad_bal / bal_sum * 100) if bal_sum > 0 else None
if overall_ncl_rate is None:
overall_ncl_rate = float("nan")
else:
overall_ncl_rate = round(overall_ncl_rate, 2)
# average fico
avg_fico = None
if "fico_score" in df.columns:
avg_fico = round(df["fico_score"].dropna().mean(), 1)
elif "fico_band" in df.columns:
def band_mid(b):
try:
parts = b.split("-")
return (int(parts[0]) + int(parts[1])) / 2
except Exception:
return None
mid_vals = df["fico_band"].dropna().apply(band_mid).dropna()
avg_fico = round(mid_vals.mean(), 1) if not mid_vals.empty else float("nan")
else:
avg_fico = float("nan")
overview = pd.DataFrame({
"Metric": [
"As Of Month",
"Total Accounts",
"Open Accounts",
"Bad Accounts (dpd>=30)",
"Overall NCL Rate (%)",
"Average FICO"
],
"Value": [
as_of_month if as_of_month else "All",
int(total_accounts),
int(open_accounts),
int(bad_accounts),
overall_ncl_rate,
avg_fico
]
})
return overview
# ---------------------------------------------------
# DYNAMIC DROPDOWNS
# ---------------------------------------------------
def update_analysis_dropdown(
dataset
):
# -----------------------------------------
# ACQUISITION
# -----------------------------------------
if dataset == "Acquisition":
return gr.update(
choices=[
"Portfolio Mix",
"Credit Line Concentration"
],
value="Portfolio Mix"
)
# -----------------------------------------
# PERFORMANCE
# -----------------------------------------
elif dataset == "Performance":
return gr.update(
choices=[
"30+@3",
"30+@6",
"60+@6",
"Yr1 NCL"
],
value="30+@6"
)
def update_category_dropdown(
dataset
):
# -----------------------------------------
# ACQUISITION
# -----------------------------------------
if dataset == "Acquisition":
return gr.update(
choices=[
"fico_band",
"sourcing_channel",
"city_tier",
"occupation_type"
],
value="fico_band"
)
# -----------------------------------------
# PERFORMANCE
# -----------------------------------------
elif dataset == "Performance":
return gr.update(
choices=[
"Overall",
"Channel",
"FICO",
"City Tier",
"Occupation"
],
value="Overall"
)
# ---------------------------------------------------
# MASTER ROUTER
# ---------------------------------------------------
def run_analysis(
dataset,
analysis,
category
):
# -----------------------------------------
# ACQUISITION
# -----------------------------------------
if dataset == "Acquisition":
return run_acquisition_analysis(
analysis_type=analysis,
category=category
)
# -----------------------------------------
# PERFORMANCE
# -----------------------------------------
elif dataset == "Performance":
return run_performance_analysis(
metric_name=analysis,
view_level=category
)
else:
return pd.DataFrame()
# ---------------------------------------------------
# GRADIO UI
# ---------------------------------------------------
with gr.Blocks() as app:
gr.Markdown(
"# Risk Analytics Manager Agent - Phase 2"
)
with gr.Tabs():
# =================================================
# TAB 1: BASIC ANALYSIS (Phase 1)
# =================================================
with gr.TabItem("📊 Basic Analysis"):
gr.Markdown(
"## Phase 1: Acquisition & Performance Analysis"
)
with gr.Row():
dataset_dropdown = gr.Dropdown(
choices=[
"Acquisition",
"Performance"
],
value="Acquisition",
label="Dataset"
)
analysis_dropdown = gr.Dropdown(
choices=[
"Portfolio Mix",
"Credit Line Concentration"
],
value="Portfolio Mix",
label="Analysis"
)
category_dropdown = gr.Dropdown(
choices=[
"fico_band",
"sourcing_channel",
"city_tier",
"occupation_type"
],
value="fico_band",
label="Category / View"
)
# -----------------------------------------
# DYNAMIC DROPDOWNS
# -----------------------------------------
dataset_dropdown.change(
fn=update_analysis_dropdown,
inputs=dataset_dropdown,
outputs=analysis_dropdown
)
dataset_dropdown.change(
fn=update_category_dropdown,
inputs=dataset_dropdown,
outputs=category_dropdown
)
# -----------------------------------------
# RUN BUTTON
# -----------------------------------------
run_button = gr.Button(
"Run Analysis",
variant="primary"
)
output_table = gr.Dataframe()
run_button.click(
fn=run_analysis,
inputs=[
dataset_dropdown,
analysis_dropdown,
category_dropdown
],
outputs=output_table
)
# =================================================
# TAB 2: VINTAGE CURVES (Phase 2)
# =================================================
with gr.TabItem("📈 Vintage Curves"):
gr.Markdown(
"## Phase 2: Vintage Delinquency Curves Analysis"
)
with gr.Row():
metric_dropdown = gr.Dropdown(
choices=[
"30+@3",
"30+@6",
"60+@6",
"Yr1 NCL"
],
value="30+@6",
label="Delinquency Metric"
)
vintage_chart_type = gr.Radio(
choices=["Single Metric", "All Metrics Comparison"],
value="Single Metric",
label="Chart Type"
)
def update_vintage_view(metric, chart_type):
if chart_type == "Single Metric":
return generate_vintage_curve_single(metric)
else:
return generate_vintage_curves_comparison()
vintage_chart = gr.Plot(
label="Vintage Curve"
)
gen_vintage_btn = gr.Button(
"Generate Vintage Curve",
variant="primary"
)
gen_vintage_btn.click(
fn=update_vintage_view,
inputs=[metric_dropdown, vintage_chart_type],
outputs=vintage_chart
)
gr.Markdown(
"### Segmented Vintage Curves"
)
with gr.Row():
segment_metric = gr.Dropdown(
choices=[
"30+@3",
"30+@6",
"60+@6",
"Yr1 NCL"
],
value="30+@6",
label="Metric"
)
segment_category = gr.Dropdown(
choices=[
"fico_band",
"sourcing_channel",
"city_tier",
"occupation_type"
],
value="fico_band",
label="Category"
)
segmented_chart = gr.Plot(
label="Segmented Vintage Curve"
)
gen_segment_btn = gr.Button(
"Generate Segmented Curve",
variant="primary"
)
gen_segment_btn.click(
fn=generate_segmented_vintage_curve,
inputs=[segment_metric, segment_category],
outputs=segmented_chart
)
# =================================================
# TAB 3: SEGMENT RANKING (Phase 2)
# =================================================
with gr.TabItem("⚠️ Segment Ranking"):
gr.Markdown(
"## Phase 2: High-Risk Segment Analysis"
)
# --------- HEATMAP SECTION ---------
gr.Markdown(
"### 🔥 Overall Risk Heatmap"
)
gr.Markdown(
"Risk scores across all delinquency metrics and segments"
)
heatmap_chart = gr.Plot(
label="Risk Heatmap"
)
gen_heatmap_btn = gr.Button(
"Generate Risk Heatmap",
variant="primary"
)
gen_heatmap_btn.click(
fn=generate_segment_risk_heatmap_chart,
outputs=heatmap_chart
)
gr.Markdown(
"---"
)
# --------- HIGH-RISK RANKING SECTION ---------
gr.Markdown(
"### 📊 High-Risk Segments Ranking"
)
with gr.Row():
ranking_metric = gr.Dropdown(
choices=[
"30+@3",
"30+@6",
"60+@6",
"Yr1 NCL"
],
value="30+@6",
label="Metric"
)
ranking_category = gr.Dropdown(
choices=[
"fico_band",
"sourcing_channel",
"city_tier",
"occupation_type"
],
value="fico_band",
label="Category"
)
ranking_chart = gr.Plot(
label="High-Risk Segments"
)
gen_ranking_btn = gr.Button(
"Generate Risk Ranking",
variant="primary"
)
gen_ranking_btn.click(
fn=generate_high_risk_segments_ranking,
inputs=[ranking_metric, ranking_category],
outputs=ranking_chart
)
gr.Markdown(
"---"
)
# --------- MULTI-CATEGORY COMPARISON ---------
gr.Markdown(
"### 🔀 Cross-Category Risk Comparison"
)
comparison_metric = gr.Dropdown(
choices=[
"30+@3",
"30+@6",
"60+@6",
"Yr1 NCL"
],
value="30+@6",
label="Metric"
)
comparison_chart = gr.Plot(
label="Multi-Category Comparison"
)
gen_comparison_btn = gr.Button(
"Generate Comparison",
variant="primary"
)
gen_comparison_btn.click(
fn=generate_multi_category_comparison,
inputs=comparison_metric,
outputs=comparison_chart
)
gr.Markdown(
"---"
)
# --------- PORTFOLIO SUMMARY ---------
gr.Markdown(
"### 📋 Portfolio Risk Summary"
)
summary_table = gr.Dataframe(
label="Risk Summary"
)
gen_summary_btn = gr.Button(
"Generate Summary",
variant="primary"
)
gen_summary_btn.click(
fn=generate_portfolio_summary,
outputs=summary_table
)
# =================================================
# TAB 4: PORTFOLIO OVERVIEW (Calendar Snapshot)
# =================================================
with gr.TabItem("📅 Portfolio Overview"):
gr.Markdown("## Portfolio Snapshot by Calendar Month")
with gr.Row():
calendar_month_dropdown = gr.Dropdown(
choices=get_calendar_months(),
value=(get_calendar_months()[-1] if len(get_calendar_months()) > 0 else None),
label="Calendar Month (YYYY-MM)"
)
overview_segment_dropdown = gr.Dropdown(
choices=[
"fico_band",
"sourcing_channel",
"city_tier",
"occupation_type"
],
value="fico_band",
label="Segment (for drill)"
)
gen_overview_btn = gr.Button("Generate Snapshot", variant="primary")
overview_table = gr.Dataframe(label="Portfolio Overview")
gen_overview_btn.click(
fn=generate_portfolio_overview,
inputs=[calendar_month_dropdown, overview_segment_dropdown],
outputs=overview_table
)
# =================================================
# TAB 5: DEEP DIVE AGENTIC ANALYSIS
# =================================================
with gr.TabItem("🔬 Deep Dive Analysis"):
gr.Markdown(
"## Agentic Code Generation & Execution\n\n"
"Ask a complex question and the AI will:\n"
"1. Generate pandas code to analyze the data\n"
"2. Execute the code safely\n"
"3. Interpret the results with risk management insights"
)
deep_dive_question = gr.Textbox(
lines=4,
placeholder="E.g., 'Compare 30+ delinquency trends across fico bands and identify worst performing vintages.'",
label="Analysis Question"
)
deep_dive_run_btn = gr.Button("Run Deep Dive Analysis", variant="primary")
deep_dive_output = gr.Markdown(label="Analysis Output")
deep_dive_run_btn.click(
fn=run_deep_dive_on_question,
inputs=deep_dive_question,
outputs=deep_dive_output
)
# =================================================
# TAB 6: CHAT WITH YOUR DATA
# =================================================
with gr.TabItem("💬 Chat with Your Data"):
gr.Markdown(
"## Ask questions in plain language and get risk manager insights"
)
with gr.Row():
ai_month_dropdown = gr.Dropdown(
choices=["All"] + get_calendar_months(),
value="All" if len(get_calendar_months()) > 0 else None,
label="As Of Month (YYYY-MM or All)"
)
ai_segment_dropdown = gr.Dropdown(
choices=["", "fico_band", "sourcing_channel", "city_tier", "occupation_type"],
value="",
label="Segment Context"
)
ai_question = gr.Textbox(
lines=3,
placeholder="Ask about NCL, vintage performance, high-risk segments, or portfolio trends..."
)
ai_chatbot = gr.Chatbot()
ai_history = gr.State([])
ai_ask_btn = gr.Button("Ask AI", variant="primary")
ai_ask_btn.click(
fn=ask_ai_question,
inputs=[ai_question, ai_month_dropdown, ai_segment_dropdown, ai_history],
outputs=[ai_chatbot, ai_history, ai_question]
)
app.launch()