Spaces:
Sleeping
Sleeping
Update analytics/ai_assistant.py
Browse files- analytics/ai_assistant.py +72 -1
analytics/ai_assistant.py
CHANGED
|
@@ -123,6 +123,77 @@ def build_portfolio_context(df: pd.DataFrame, as_of_month: str | None = None, se
|
|
| 123 |
|
| 124 |
return "\n".join(lines)
|
| 125 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
def _get_inference_client():
|
| 128 |
if not HF_TOKEN:
|
|
@@ -136,7 +207,7 @@ def _get_inference_client():
|
|
| 136 |
|
| 137 |
|
| 138 |
def generate_ai_answer(question: str, df: pd.DataFrame, as_of_month: str | None = None, segment: str | None = None):
|
| 139 |
-
summary =
|
| 140 |
prompt = (
|
| 141 |
"You are a senior risk manager assistant responding to portfolio analytics questions. "
|
| 142 |
"Use the risk context below and answer the user clearly, describing what is happening, why it is happening, and what actions should be considered. "
|
|
|
|
| 123 |
|
| 124 |
return "\n".join(lines)
|
| 125 |
|
| 126 |
+
def build_calendar_performance_context(df: pd.DataFrame, as_of_month: str | None = None):
|
| 127 |
+
if not as_of_month or as_of_month == "All":
|
| 128 |
+
return ""
|
| 129 |
+
|
| 130 |
+
month_df = _filter_by_month(df, as_of_month)
|
| 131 |
+
if month_df.empty:
|
| 132 |
+
return f"No data found for {as_of_month}."
|
| 133 |
+
|
| 134 |
+
lines = [f"Calendar month snapshot for {as_of_month}:"]
|
| 135 |
+
if "balance" in month_df.columns:
|
| 136 |
+
lines.append(f" - Total balance: {month_df['balance'].sum(skipna=True):.0f}")
|
| 137 |
+
lines.append(f" - Total accounts: {month_df['account_id'].nunique() if 'account_id' in month_df.columns else 0}")
|
| 138 |
+
if "dpd" in month_df.columns:
|
| 139 |
+
lines.append(f" - Accounts with dpd>=30: {month_df.loc[month_df['dpd'].fillna(0) >= 30, 'account_id'].nunique() if 'account_id' in month_df.columns else 0}")
|
| 140 |
+
|
| 141 |
+
for metric in RISK_METRICS:
|
| 142 |
+
try:
|
| 143 |
+
view = generate_metric_view(month_df, metric_name=metric, group_col=None)
|
| 144 |
+
rate_col = [c for c in view.columns if "rate" in c.lower()][0]
|
| 145 |
+
metric_rate = view.loc[0, rate_col]
|
| 146 |
+
lines.append(f" - {metric} rate: {_fmt_pct(metric_rate)}")
|
| 147 |
+
except Exception:
|
| 148 |
+
continue
|
| 149 |
+
|
| 150 |
+
return "\n".join(lines)
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def build_vintage_performance_context(df: pd.DataFrame):
|
| 154 |
+
if "booking_vintage" not in df.columns:
|
| 155 |
+
return ""
|
| 156 |
+
|
| 157 |
+
lines = ["Vintage performance summary:"]
|
| 158 |
+
for metric in RISK_METRICS:
|
| 159 |
+
try:
|
| 160 |
+
view = generate_metric_view(df, metric_name=metric, group_col="booking_vintage")
|
| 161 |
+
rate_col = [c for c in view.columns if "rate" in c.lower()][0]
|
| 162 |
+
if view.empty:
|
| 163 |
+
continue
|
| 164 |
+
top = view.sort_values(rate_col, ascending=False).head(2)
|
| 165 |
+
bottom = view.sort_values(rate_col, ascending=True).head(1)
|
| 166 |
+
lines.append(
|
| 167 |
+
f" - {metric}: highest risk vintage {top.iloc[0]['booking_vintage']} at {_fmt_pct(top.iloc[0][rate_col])}, "
|
| 168 |
+
f"lowest risk vintage {bottom.iloc[0]['booking_vintage']} at {_fmt_pct(bottom.iloc[0][rate_col])}."
|
| 169 |
+
)
|
| 170 |
+
except Exception:
|
| 171 |
+
continue
|
| 172 |
+
|
| 173 |
+
return "\n".join(lines)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def build_context_for_question(df: pd.DataFrame, as_of_month: str | None = None, segment: str | None = None, question: str | None = None):
|
| 177 |
+
sections = [build_portfolio_context(df, as_of_month=as_of_month, segment=segment)]
|
| 178 |
+
q = (question or "").lower()
|
| 179 |
+
|
| 180 |
+
if "month" in q or "calendar" in q or "as of" in q:
|
| 181 |
+
month_context = build_calendar_performance_context(df, as_of_month=as_of_month)
|
| 182 |
+
if month_context:
|
| 183 |
+
sections.append(month_context)
|
| 184 |
+
|
| 185 |
+
if "vintage" in q or "trend" in q or "booking vintage" in q or "compare" in q:
|
| 186 |
+
vintage_context = build_vintage_performance_context(df)
|
| 187 |
+
if vintage_context:
|
| 188 |
+
sections.append(vintage_context)
|
| 189 |
+
|
| 190 |
+
if segment and segment in df.columns:
|
| 191 |
+
segment_context = build_portfolio_context(df, as_of_month=as_of_month, segment=segment)
|
| 192 |
+
if segment_context:
|
| 193 |
+
sections.append(segment_context)
|
| 194 |
+
|
| 195 |
+
return "\n\n".join(section for section in sections if section)
|
| 196 |
+
|
| 197 |
|
| 198 |
def _get_inference_client():
|
| 199 |
if not HF_TOKEN:
|
|
|
|
| 207 |
|
| 208 |
|
| 209 |
def generate_ai_answer(question: str, df: pd.DataFrame, as_of_month: str | None = None, segment: str | None = None):
|
| 210 |
+
summary = build_context_for_question(df, as_of_month=as_of_month, segment=segment, question=question)
|
| 211 |
prompt = (
|
| 212 |
"You are a senior risk manager assistant responding to portfolio analytics questions. "
|
| 213 |
"Use the risk context below and answer the user clearly, describing what is happening, why it is happening, and what actions should be considered. "
|