AshenH commited on
Commit
56c4a12
·
verified ·
1 Parent(s): 2b1b1b6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +64 -12
app.py CHANGED
@@ -112,11 +112,9 @@ def plot_ladder(df: pd.DataFrame):
112
  ax.text(0.5, 0.5, "No data", ha="center", va="center")
113
  ax.axis("off")
114
  return fig
115
- pivot = df.pivot(index="time_bucket", columns="bucket", values="amount").fillna(0)
116
  order = ["T+1", "T+2..7", "T+8..30", "T+31+"]
117
  pivot = pivot.reindex(order)
118
- # Convert to millions for plotting
119
- pivot = pivot / 1_000_000
120
  fig, ax = plt.subplots(figsize=(7, 4))
121
  assets = pivot["Assets"] if "Assets" in pivot.columns else zeros_like_index(pivot.index)
122
  sof = pivot["SoF"] if "SoF" in pivot.columns else zeros_like_index(pivot.index)
@@ -162,6 +160,17 @@ GROUP BY 1,2
162
  ORDER BY 1,2;
163
  """
164
 
 
 
 
 
 
 
 
 
 
 
 
165
  def irr_sql(cols: List[str]) -> str:
166
  has_months = "months" in cols
167
  has_ir = "interest_rate" in cols
@@ -181,11 +190,11 @@ def irr_sql(cols: List[str]) -> str:
181
  # =========================
182
  # Dashboard callback
183
  # =========================
184
- def run_dashboard() -> Tuple[str, str, str, str, str, Any, pd.DataFrame, pd.DataFrame]:
185
  """
186
  Returns:
187
- status, as_of, assets_t1, sof_t1, net_gap_t1, figure, ladder_df, irr_df
188
- (text KPIs to avoid component type errors)
189
  """
190
  try:
191
  conn = connect_md()
@@ -207,24 +216,55 @@ def run_dashboard() -> Tuple[str, str, str, str, str, Any, pd.DataFrame, pd.Data
207
  sof_t1 = safe_num(kpi["sof_t1"].iloc[0]) if not kpi.empty else 0.0
208
  net_gap = safe_num(kpi["net_gap_t1"].iloc[0]) if not kpi.empty else 0.0
209
 
210
- # 4) Ladder & IRR
211
  ladder = conn.execute(LADDER_SQL).fetchdf()
212
  irr = conn.execute(irr_sql(cols)).fetchdf()
 
 
 
 
213
  if "Portfolio Value (LKR Mn)" in irr.columns:
214
  irr["Portfolio Value (LKR Mn)"] = irr["Portfolio Value (LKR Mn)"].map('{:,.2f}'.format)
 
 
 
 
 
215
 
216
  # 5) Chart
217
  fig = plot_ladder(ladder)
218
 
 
219
  assets_t1_mn_str = f"{(assets_t1 / 1_000_000):,.2f}"
220
  sof_t1_mn_str = f"{(sof_t1 / 1_000_000):,.2f}"
221
  net_gap_mn_str = f"{(net_gap / 1_000_000):,.2f}"
 
222
 
223
  a1_text = f"The amount of Assets maturing tomorrow (T+1) is **LKR {assets_t1_mn_str} Mn**."
224
  a2_text = f"The amount of Sources of Funds (SoF) maturing tomorrow (T+1) is **LKR {sof_t1_mn_str} Mn**."
225
  a3_text = f"The resulting Net Liquidity Gap for tomorrow (T+1) is **LKR {net_gap_mn_str} Mn**."
226
 
227
- status = f"Connected to Database (as of {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')})"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  return (
229
  status,
230
  as_of,
@@ -234,6 +274,8 @@ def run_dashboard() -> Tuple[str, str, str, str, str, Any, pd.DataFrame, pd.Data
234
  fig,
235
  ladder,
236
  irr,
 
 
237
  )
238
 
239
  except Exception as e:
@@ -249,6 +291,8 @@ def run_dashboard() -> Tuple[str, str, str, str, str, Any, pd.DataFrame, pd.Data
249
  fig,
250
  empty_df,
251
  empty_df,
 
 
252
  )
253
 
254
  # =========================
@@ -269,13 +313,21 @@ with gr.Blocks(title=APP_TITLE) as demo:
269
  a2 = gr.Markdown("The amount of Sources of Funds (SoF) maturing tomorrow (T+1) is...")
270
  a3 = gr.Markdown("The resulting Net Liquidity Gap for tomorrow (T+1) is...")
271
 
272
- chart = gr.Plot(label="Maturity Ladder")
273
- ladder_df = gr.Dataframe(label="Ladder Detail")
274
- irr_df = gr.Dataframe(label="Interest-Rate Risk (approx)")
 
 
 
 
 
 
 
 
275
 
276
  refresh_btn.click(
277
  fn=run_dashboard,
278
- outputs=[status, as_of, a1, a2, a3, chart, ladder_df, irr_df],
279
  )
280
 
281
  if __name__ == "__main__":
 
112
  ax.text(0.5, 0.5, "No data", ha="center", va="center")
113
  ax.axis("off")
114
  return fig
115
+ pivot = df.pivot(index="time_bucket", columns="bucket", values="Amount (LKR Mn)").fillna(0)
116
  order = ["T+1", "T+2..7", "T+8..30", "T+31+"]
117
  pivot = pivot.reindex(order)
 
 
118
  fig, ax = plt.subplots(figsize=(7, 4))
119
  assets = pivot["Assets"] if "Assets" in pivot.columns else zeros_like_index(pivot.index)
120
  sof = pivot["SoF"] if "SoF" in pivot.columns else zeros_like_index(pivot.index)
 
160
  ORDER BY 1,2;
161
  """
162
 
163
+ GAP_DRIVERS_SQL = f"""
164
+ SELECT
165
+ product,
166
+ bucket,
167
+ SUM(Portfolio_value) / 1000000.0 AS "Amount (LKR Mn)"
168
+ FROM {VIEW_FQN}
169
+ WHERE days_to_maturity <= 1
170
+ GROUP BY 1, 2
171
+ ORDER BY 3 DESC;
172
+ """
173
+
174
  def irr_sql(cols: List[str]) -> str:
175
  has_months = "months" in cols
176
  has_ir = "interest_rate" in cols
 
190
  # =========================
191
  # Dashboard callback
192
  # =========================
193
+ def run_dashboard() -> Tuple[str, str, str, str, str, Any, pd.DataFrame, pd.DataFrame, str, pd.DataFrame]:
194
  """
195
  Returns:
196
+ status, as_of, a1_text, a2_text, a3_text, figure, ladder_df, irr_df,
197
+ explain_text, drivers_df
198
  """
199
  try:
200
  conn = connect_md()
 
216
  sof_t1 = safe_num(kpi["sof_t1"].iloc[0]) if not kpi.empty else 0.0
217
  net_gap = safe_num(kpi["net_gap_t1"].iloc[0]) if not kpi.empty else 0.0
218
 
219
+ # 4) Ladder, IRR, and Gap Drivers
220
  ladder = conn.execute(LADDER_SQL).fetchdf()
221
  irr = conn.execute(irr_sql(cols)).fetchdf()
222
+ drivers = conn.execute(GAP_DRIVERS_SQL).fetchdf()
223
+
224
+ if "Amount (LKR Mn)" in ladder.columns:
225
+ ladder["Amount (LKR Mn)"] = ladder["Amount (LKR Mn)"].map('{:,.2f}'.format)
226
  if "Portfolio Value (LKR Mn)" in irr.columns:
227
  irr["Portfolio Value (LKR Mn)"] = irr["Portfolio Value (LKR Mn)"].map('{:,.2f}'.format)
228
+ if "Amount (LKR Mn)" in drivers.columns:
229
+ drivers_display = drivers.copy()
230
+ drivers_display["Amount (LKR Mn)"] = drivers_display["Amount (LKR Mn)"].map('{:,.2f}'.format)
231
+ else:
232
+ drivers_display = pd.DataFrame()
233
 
234
  # 5) Chart
235
  fig = plot_ladder(ladder)
236
 
237
+ # 6) Explanations
238
  assets_t1_mn_str = f"{(assets_t1 / 1_000_000):,.2f}"
239
  sof_t1_mn_str = f"{(sof_t1 / 1_000_000):,.2f}"
240
  net_gap_mn_str = f"{(net_gap / 1_000_000):,.2f}"
241
+ gap_sign_str = "positive" if net_gap >= 0 else "negative"
242
 
243
  a1_text = f"The amount of Assets maturing tomorrow (T+1) is **LKR {assets_t1_mn_str} Mn**."
244
  a2_text = f"The amount of Sources of Funds (SoF) maturing tomorrow (T+1) is **LKR {sof_t1_mn_str} Mn**."
245
  a3_text = f"The resulting Net Liquidity Gap for tomorrow (T+1) is **LKR {net_gap_mn_str} Mn**."
246
 
247
+ # Build "Why" text
248
+ sof_drivers = drivers[drivers["bucket"] == "SoF"]
249
+ asset_drivers = drivers[drivers["bucket"] == "Assets"]
250
+ top_sof_prod = sof_drivers.iloc[0] if not sof_drivers.empty else None
251
+ top_asset_prod = asset_drivers.iloc[0] if not asset_drivers.empty else None
252
+
253
+ explain_text = f"### Why is the T+1 Gap {gap_sign_str}?\n\n"
254
+ if top_sof_prod is not None:
255
+ explain_text += f"* **Largest Liability Maturity:** The largest outflow comes from `{top_sof_prod['product']}`, with **LKR {top_sof_prod['Amount (LKR Mn)']:,.2f} Mn** maturing.\n"
256
+ else:
257
+ explain_text += "* **Largest Liability Maturity:** No significant liabilities are maturing tomorrow.\n"
258
+
259
+ if top_asset_prod is not None:
260
+ explain_text += f"* **Largest Asset Inflow:** The largest inflow comes from `{top_asset_prod['product']}`, with **LKR {top_asset_prod['Amount (LKR Mn)']:,.2f} Mn** maturing.\n"
261
+ else:
262
+ explain_text += "* **Largest Asset Inflow:** No significant assets are maturing to provide inflows tomorrow.\n"
263
+
264
+ # Note: The data source does not contain features for seasonal analysis (e.g., day_of_week, is_month_end).
265
+ explain_text += "* **Seasonal Pattern:** Analysis not possible without relevant time-series features in the source data."
266
+
267
+ status = f"✅ OK (as of {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')})"
268
  return (
269
  status,
270
  as_of,
 
274
  fig,
275
  ladder,
276
  irr,
277
+ explain_text,
278
+ drivers_display,
279
  )
280
 
281
  except Exception as e:
 
291
  fig,
292
  empty_df,
293
  empty_df,
294
+ "Analysis could not be performed.",
295
+ empty_df,
296
  )
297
 
298
  # =========================
 
313
  a2 = gr.Markdown("The amount of Sources of Funds (SoF) maturing tomorrow (T+1) is...")
314
  a3 = gr.Markdown("The resulting Net Liquidity Gap for tomorrow (T+1) is...")
315
 
316
+ with gr.Row():
317
+ with gr.Column(scale=2):
318
+ chart = gr.Plot(label="Maturity Ladder")
319
+ ladder_df = gr.Dataframe(label="Ladder Detail")
320
+ irr_df = gr.Dataframe(label="Interest-Rate Risk (approx)")
321
+ with gr.Column(scale=1):
322
+ explain_text = gr.Markdown("Analysis of the T+1 gap will appear here...")
323
+ drivers_df = gr.Dataframe(
324
+ label="T+1 Gap Drivers (Top Products)",
325
+ headers=["Product", "Bucket", "Amount (LKR Mn)"],
326
+ )
327
 
328
  refresh_btn.click(
329
  fn=run_dashboard,
330
+ outputs=[status, as_of, a1, a2, a3, chart, ladder_df, irr_df, explain_text, drivers_df],
331
  )
332
 
333
  if __name__ == "__main__":