GenAICoder commited on
Commit
0f25d3c
ยท
verified ยท
1 Parent(s): 2b968b1

Create appv2.py

Browse files
Files changed (1) hide show
  1. appv2.py +823 -0
appv2.py ADDED
@@ -0,0 +1,823 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+
3
+ import pandas as pd
4
+ import gradio as gr
5
+
6
+ # ---------------------------------------------------
7
+ # HELPERS
8
+ # ---------------------------------------------------
9
+
10
+ from helper.vintage_helpers import (
11
+ create_booking_vintage
12
+ )
13
+
14
+ from helper.data_merger import (
15
+ merge_acq_perf
16
+ )
17
+
18
+ # ---------------------------------------------------
19
+ # METRICS
20
+ # ---------------------------------------------------
21
+
22
+ from metrics.mix_metrics import (
23
+ calculate_vintage_mix,
24
+ calculate_limit_mix
25
+ )
26
+
27
+ # ---------------------------------------------------
28
+ # ANALYTICS
29
+ # ---------------------------------------------------
30
+
31
+ from analytics.performance_analysis import (
32
+ generate_metric_view
33
+ )
34
+
35
+ # ---------------------------------------------------
36
+ # VISUALIZATIONS - VINTAGE CURVES
37
+ # ---------------------------------------------------
38
+
39
+ from visualizations.vintage_curves import (
40
+ generate_delinquency_metric_chart,
41
+ generate_multi_metric_comparison,
42
+ generate_segment_delinquency_curve
43
+ )
44
+
45
+ # ---------------------------------------------------
46
+ # VISUALIZATIONS - SEGMENT RANKING
47
+ # ---------------------------------------------------
48
+
49
+ from visualizations.segment_ranking import (
50
+ generate_segment_risk_heatmap,
51
+ generate_segment_risk_ranking,
52
+ generate_multi_category_risk_comparison,
53
+ calculate_portfolio_risk_summary
54
+ )
55
+
56
+ # ---------------------------------------------------
57
+ # LOAD DATA
58
+ # ---------------------------------------------------
59
+
60
+ acq = pd.read_csv(
61
+ "data/acquisition.csv"
62
+ )
63
+
64
+ perf = pd.read_csv(
65
+ "data/performance.csv"
66
+ )
67
+
68
+ # ---------------------------------------------------
69
+ # CREATE BOOKING VINTAGE
70
+ # ---------------------------------------------------
71
+
72
+ acq = create_booking_vintage(
73
+ acq,
74
+ booking_date_col="booking_date"
75
+ )
76
+
77
+ # ---------------------------------------------------
78
+ # CREATE MASTER PERFORMANCE DATASET
79
+ # ---------------------------------------------------
80
+
81
+ master_df = merge_acq_perf(
82
+ acq_df=acq,
83
+ perf_df=perf
84
+ )
85
+
86
+ # ---------------------------------------------------
87
+ # ACQUISITION ANALYSIS
88
+ # ---------------------------------------------------
89
+
90
+ def run_acquisition_analysis(
91
+ analysis_type,
92
+ category
93
+ ):
94
+
95
+ # -----------------------------------------
96
+ # PORTFOLIO MIX
97
+ # -----------------------------------------
98
+
99
+ if analysis_type == "Portfolio Mix":
100
+
101
+ result = (
102
+ acq.groupby(
103
+ ["booking_vintage", category]
104
+ )
105
+ .agg(
106
+ count=("account_id", "nunique"),
107
+ balance=("credit_limit", "sum")
108
+ )
109
+ .reset_index()
110
+ )
111
+
112
+ vintage_total = (
113
+ result.groupby("booking_vintage")["count"]
114
+ .transform("sum")
115
+ )
116
+
117
+ result["rate"] = (
118
+ result["count"] / vintage_total
119
+ ) * 100
120
+
121
+ result["rate"] = (
122
+ result["rate"]
123
+ .round(2)
124
+ )
125
+
126
+ # -----------------------------------------
127
+ # CREDIT LINE CONCENTRATION
128
+ # -----------------------------------------
129
+
130
+ elif analysis_type == "Credit Line Concentration":
131
+
132
+ result = (
133
+ acq.groupby(
134
+ ["booking_vintage", category]
135
+ )
136
+ .agg(
137
+ count=("account_id", "nunique"),
138
+ balance=("credit_limit", "sum")
139
+ )
140
+ .reset_index()
141
+ )
142
+
143
+ vintage_total = (
144
+ result.groupby("booking_vintage")["balance"]
145
+ .transform("sum")
146
+ )
147
+
148
+ result["rate"] = (
149
+ result["balance"] / vintage_total
150
+ ) * 100
151
+
152
+ result["rate"] = (
153
+ result["rate"]
154
+ .round(2)
155
+ )
156
+
157
+ else:
158
+
159
+ return pd.DataFrame()
160
+
161
+ # -----------------------------------------
162
+ # STANDARDIZED OUTPUT
163
+ # -----------------------------------------
164
+
165
+ result = result.rename(
166
+ columns={
167
+ "booking_vintage": "Vintage",
168
+ category: "Category",
169
+ "count": "Count",
170
+ "balance": "Balance",
171
+ "rate": "Rate"
172
+ }
173
+ )
174
+
175
+ return result[
176
+ [
177
+ "Vintage",
178
+ "Category",
179
+ "Count",
180
+ "Balance",
181
+ "Rate"
182
+ ]
183
+ ]
184
+
185
+ # ---------------------------------------------------
186
+ # PERFORMANCE ANALYSIS
187
+ # ---------------------------------------------------
188
+
189
+ def run_performance_analysis(
190
+ metric_name,
191
+ view_level
192
+ ):
193
+
194
+ # -----------------------------------------
195
+ # VIEW MAPPING
196
+ # -----------------------------------------
197
+
198
+ view_mapping = {
199
+
200
+ "Overall": None,
201
+
202
+ "Channel":
203
+ "sourcing_channel",
204
+
205
+ "FICO":
206
+ "fico_band",
207
+
208
+ "City Tier":
209
+ "city_tier",
210
+
211
+ "Occupation":
212
+ "occupation_type"
213
+ }
214
+
215
+ group_col = view_mapping[
216
+ view_level
217
+ ]
218
+
219
+ # -----------------------------------------
220
+ # CALL ANALYTICS ENGINE
221
+ # -----------------------------------------
222
+
223
+ result = generate_metric_view(
224
+ df=master_df,
225
+ metric_name=metric_name,
226
+ group_col=group_col
227
+ )
228
+
229
+ # -----------------------------------------
230
+ # STANDARDIZE OUTPUT
231
+ # -----------------------------------------
232
+
233
+ if group_col is not None:
234
+
235
+ result = result.rename(
236
+ columns={
237
+ group_col: "Category"
238
+ }
239
+ )
240
+
241
+ else:
242
+
243
+ result["Category"] = "Overall"
244
+
245
+ # -----------------------------------------
246
+ # IDENTIFY RATE COLUMN
247
+ # -----------------------------------------
248
+
249
+ rate_col = [
250
+ col for col in result.columns
251
+ if "rate" in col.lower()
252
+ ][0]
253
+
254
+ # -----------------------------------------
255
+ # OUTPUT FORMAT
256
+ # -----------------------------------------
257
+
258
+ final_result = pd.DataFrame()
259
+
260
+ final_result["Vintage"] = (
261
+ result["booking_vintage"]
262
+ )
263
+
264
+ final_result["Category"] = (
265
+ result["Category"]
266
+ )
267
+
268
+ final_result["Count"] = (
269
+ result["total_accounts"]
270
+ )
271
+
272
+ final_result["Balance"] = (
273
+ result["total_balance"]
274
+ )
275
+
276
+ final_result["Rate"] = (
277
+ result[rate_col]
278
+ .round(2)
279
+ )
280
+
281
+ return final_result
282
+
283
+ # ---------------------------------------------------
284
+ # VINTAGE CURVES ANALYSIS
285
+ # ---------------------------------------------------
286
+
287
+ def generate_vintage_curve_single(
288
+ metric_name
289
+ ):
290
+ """Generate single vintage curve for a metric."""
291
+ try:
292
+ fig = generate_delinquency_metric_chart(
293
+ df=master_df,
294
+ metric_name=metric_name,
295
+ chart_type="line"
296
+ )
297
+ return fig
298
+ except Exception as e:
299
+ return f"Error generating vintage curve: {str(e)}"
300
+
301
+
302
+ def generate_vintage_curves_comparison():
303
+ """Generate comparison of all vintage curves."""
304
+ try:
305
+ fig = generate_multi_metric_comparison(
306
+ df=master_df,
307
+ metrics=["30+@3", "30+@6", "60+@6", "Yr1 NCL"]
308
+ )
309
+ return fig
310
+ except Exception as e:
311
+ return f"Error generating comparison: {str(e)}"
312
+
313
+
314
+ def generate_segmented_vintage_curve(
315
+ metric_name,
316
+ category
317
+ ):
318
+ """Generate vintage curve segmented by category."""
319
+ try:
320
+ fig = generate_segment_delinquency_curve(
321
+ df=master_df,
322
+ metric_name=metric_name,
323
+ category=category
324
+ )
325
+ return fig
326
+ except Exception as e:
327
+ return f"Error generating segmented curve: {str(e)}"
328
+
329
+ # ---------------------------------------------------
330
+ # SEGMENT RANKING ANALYSIS
331
+ # ---------------------------------------------------
332
+
333
+ def generate_segment_risk_heatmap_chart():
334
+ """Generate risk heatmap across all segments and metrics."""
335
+ try:
336
+ fig = generate_segment_risk_heatmap(
337
+ df=master_df
338
+ )
339
+ return fig
340
+ except Exception as e:
341
+ return f"Error generating heatmap: {str(e)}"
342
+
343
+
344
+ def generate_high_risk_segments_ranking(
345
+ metric_name,
346
+ category
347
+ ):
348
+ """Generate ranking of high-risk segments."""
349
+ try:
350
+ fig = generate_segment_risk_ranking(
351
+ df=master_df,
352
+ metric_name=metric_name,
353
+ category=category,
354
+ top_n=10
355
+ )
356
+ return fig
357
+ except Exception as e:
358
+ return f"Error generating ranking: {str(e)}"
359
+
360
+
361
+ def generate_multi_category_comparison(
362
+ metric_name
363
+ ):
364
+ """Generate risk comparison across all categories."""
365
+ try:
366
+ fig = generate_multi_category_risk_comparison(
367
+ df=master_df,
368
+ metric_name=metric_name
369
+ )
370
+ return fig
371
+ except Exception as e:
372
+ return f"Error generating comparison: {str(e)}"
373
+
374
+
375
+ def generate_portfolio_summary():
376
+ """Generate portfolio risk summary."""
377
+ try:
378
+ summary_df = calculate_portfolio_risk_summary(
379
+ df=master_df
380
+ )
381
+ return summary_df
382
+ except Exception as e:
383
+ return f"Error generating summary: {str(e)}"
384
+
385
+ # ---------------------------------------------------
386
+ # DYNAMIC DROPDOWNS
387
+ # ---------------------------------------------------
388
+
389
+ def update_analysis_dropdown(
390
+ dataset
391
+ ):
392
+
393
+ # -----------------------------------------
394
+ # ACQUISITION
395
+ # -----------------------------------------
396
+
397
+ if dataset == "Acquisition":
398
+
399
+ return gr.update(
400
+ choices=[
401
+ "Portfolio Mix",
402
+ "Credit Line Concentration"
403
+ ],
404
+ value="Portfolio Mix"
405
+ )
406
+
407
+ # -----------------------------------------
408
+ # PERFORMANCE
409
+ # -----------------------------------------
410
+
411
+ elif dataset == "Performance":
412
+
413
+ return gr.update(
414
+ choices=[
415
+ "30+@3",
416
+ "30+@6",
417
+ "60+@6",
418
+ "Yr1 NCL"
419
+ ],
420
+ value="30+@6"
421
+ )
422
+
423
+
424
+ def update_category_dropdown(
425
+ dataset
426
+ ):
427
+
428
+ # -----------------------------------------
429
+ # ACQUISITION
430
+ # -----------------------------------------
431
+
432
+ if dataset == "Acquisition":
433
+
434
+ return gr.update(
435
+ choices=[
436
+ "fico_band",
437
+ "sourcing_channel",
438
+ "city_tier",
439
+ "occupation_type"
440
+ ],
441
+ value="fico_band"
442
+ )
443
+
444
+ # -----------------------------------------
445
+ # PERFORMANCE
446
+ # -----------------------------------------
447
+
448
+ elif dataset == "Performance":
449
+
450
+ return gr.update(
451
+ choices=[
452
+ "Overall",
453
+ "Channel",
454
+ "FICO",
455
+ "City Tier",
456
+ "Occupation"
457
+ ],
458
+ value="Overall"
459
+ )
460
+
461
+ # ---------------------------------------------------
462
+ # MASTER ROUTER
463
+ # ---------------------------------------------------
464
+
465
+ def run_analysis(
466
+ dataset,
467
+ analysis,
468
+ category
469
+ ):
470
+
471
+ # -----------------------------------------
472
+ # ACQUISITION
473
+ # -----------------------------------------
474
+
475
+ if dataset == "Acquisition":
476
+
477
+ return run_acquisition_analysis(
478
+ analysis_type=analysis,
479
+ category=category
480
+ )
481
+
482
+ # -----------------------------------------
483
+ # PERFORMANCE
484
+ # -----------------------------------------
485
+
486
+ elif dataset == "Performance":
487
+
488
+ return run_performance_analysis(
489
+ metric_name=analysis,
490
+ view_level=category
491
+ )
492
+
493
+ else:
494
+
495
+ return pd.DataFrame()
496
+
497
+ # ---------------------------------------------------
498
+ # GRADIO UI
499
+ # ---------------------------------------------------
500
+
501
+ with gr.Blocks() as app:
502
+
503
+ gr.Markdown(
504
+ "# Risk Analytics Manager Agent - Phase 2"
505
+ )
506
+
507
+ with gr.Tabs():
508
+
509
+ # =================================================
510
+ # TAB 1: BASIC ANALYSIS (Phase 1)
511
+ # =================================================
512
+
513
+ with gr.TabItem("๐Ÿ“Š Basic Analysis"):
514
+
515
+ gr.Markdown(
516
+ "## Phase 1: Acquisition & Performance Analysis"
517
+ )
518
+
519
+ with gr.Row():
520
+
521
+ dataset_dropdown = gr.Dropdown(
522
+ choices=[
523
+ "Acquisition",
524
+ "Performance"
525
+ ],
526
+ value="Acquisition",
527
+ label="Dataset"
528
+ )
529
+
530
+ analysis_dropdown = gr.Dropdown(
531
+ choices=[
532
+ "Portfolio Mix",
533
+ "Credit Line Concentration"
534
+ ],
535
+ value="Portfolio Mix",
536
+ label="Analysis"
537
+ )
538
+
539
+ category_dropdown = gr.Dropdown(
540
+ choices=[
541
+ "fico_band",
542
+ "sourcing_channel",
543
+ "city_tier",
544
+ "occupation_type"
545
+ ],
546
+ value="fico_band",
547
+ label="Category / View"
548
+ )
549
+
550
+ # -----------------------------------------
551
+ # DYNAMIC DROPDOWNS
552
+ # -----------------------------------------
553
+
554
+ dataset_dropdown.change(
555
+ fn=update_analysis_dropdown,
556
+ inputs=dataset_dropdown,
557
+ outputs=analysis_dropdown
558
+ )
559
+
560
+ dataset_dropdown.change(
561
+ fn=update_category_dropdown,
562
+ inputs=dataset_dropdown,
563
+ outputs=category_dropdown
564
+ )
565
+
566
+ # -----------------------------------------
567
+ # RUN BUTTON
568
+ # -----------------------------------------
569
+
570
+ run_button = gr.Button(
571
+ "Run Analysis",
572
+ variant="primary"
573
+ )
574
+
575
+ output_table = gr.Dataframe()
576
+
577
+ run_button.click(
578
+ fn=run_analysis,
579
+ inputs=[
580
+ dataset_dropdown,
581
+ analysis_dropdown,
582
+ category_dropdown
583
+ ],
584
+ outputs=output_table
585
+ )
586
+
587
+ # =================================================
588
+ # TAB 2: VINTAGE CURVES (Phase 2)
589
+ # =================================================
590
+
591
+ with gr.TabItem("๐Ÿ“ˆ Vintage Curves"):
592
+
593
+ gr.Markdown(
594
+ "## Phase 2: Vintage Delinquency Curves Analysis"
595
+ )
596
+
597
+ with gr.Row():
598
+
599
+ metric_dropdown = gr.Dropdown(
600
+ choices=[
601
+ "30+@3",
602
+ "30+@6",
603
+ "60+@6",
604
+ "Yr1 NCL"
605
+ ],
606
+ value="30+@6",
607
+ label="Delinquency Metric"
608
+ )
609
+
610
+ vintage_chart_type = gr.Radio(
611
+ choices=["Single Metric", "All Metrics Comparison"],
612
+ value="Single Metric",
613
+ label="Chart Type"
614
+ )
615
+
616
+ def update_vintage_view(metric, chart_type):
617
+ if chart_type == "Single Metric":
618
+ return generate_vintage_curve_single(metric)
619
+ else:
620
+ return generate_vintage_curves_comparison()
621
+
622
+ vintage_chart = gr.Plot(
623
+ label="Vintage Curve"
624
+ )
625
+
626
+ gen_vintage_btn = gr.Button(
627
+ "Generate Vintage Curve",
628
+ variant="primary"
629
+ )
630
+
631
+ gen_vintage_btn.click(
632
+ fn=update_vintage_view,
633
+ inputs=[metric_dropdown, vintage_chart_type],
634
+ outputs=vintage_chart
635
+ )
636
+
637
+ gr.Markdown(
638
+ "### Segmented Vintage Curves"
639
+ )
640
+
641
+ with gr.Row():
642
+
643
+ segment_metric = gr.Dropdown(
644
+ choices=[
645
+ "30+@3",
646
+ "30+@6",
647
+ "60+@6",
648
+ "Yr1 NCL"
649
+ ],
650
+ value="30+@6",
651
+ label="Metric"
652
+ )
653
+
654
+ segment_category = gr.Dropdown(
655
+ choices=[
656
+ "fico_band",
657
+ "sourcing_channel",
658
+ "city_tier",
659
+ "occupation_type"
660
+ ],
661
+ value="fico_band",
662
+ label="Category"
663
+ )
664
+
665
+ segmented_chart = gr.Plot(
666
+ label="Segmented Vintage Curve"
667
+ )
668
+
669
+ gen_segment_btn = gr.Button(
670
+ "Generate Segmented Curve",
671
+ variant="primary"
672
+ )
673
+
674
+ gen_segment_btn.click(
675
+ fn=generate_segmented_vintage_curve,
676
+ inputs=[segment_metric, segment_category],
677
+ outputs=segmented_chart
678
+ )
679
+
680
+ # =================================================
681
+ # TAB 3: SEGMENT RANKING (Phase 2)
682
+ # =================================================
683
+
684
+ with gr.TabItem("โš ๏ธ Segment Ranking"):
685
+
686
+ gr.Markdown(
687
+ "## Phase 2: High-Risk Segment Analysis"
688
+ )
689
+
690
+ # --------- HEATMAP SECTION ---------
691
+
692
+ gr.Markdown(
693
+ "### ๐Ÿ”ฅ Overall Risk Heatmap"
694
+ )
695
+
696
+ gr.Markdown(
697
+ "Risk scores across all delinquency metrics and segments"
698
+ )
699
+
700
+ heatmap_chart = gr.Plot(
701
+ label="Risk Heatmap"
702
+ )
703
+
704
+ gen_heatmap_btn = gr.Button(
705
+ "Generate Risk Heatmap",
706
+ variant="primary"
707
+ )
708
+
709
+ gen_heatmap_btn.click(
710
+ fn=generate_segment_risk_heatmap_chart,
711
+ outputs=heatmap_chart
712
+ )
713
+
714
+ gr.Markdown(
715
+ "---"
716
+ )
717
+
718
+ # --------- HIGH-RISK RANKING SECTION ---------
719
+
720
+ gr.Markdown(
721
+ "### ๐Ÿ“Š High-Risk Segments Ranking"
722
+ )
723
+
724
+ with gr.Row():
725
+
726
+ ranking_metric = gr.Dropdown(
727
+ choices=[
728
+ "30+@3",
729
+ "30+@6",
730
+ "60+@6",
731
+ "Yr1 NCL"
732
+ ],
733
+ value="30+@6",
734
+ label="Metric"
735
+ )
736
+
737
+ ranking_category = gr.Dropdown(
738
+ choices=[
739
+ "fico_band",
740
+ "sourcing_channel",
741
+ "city_tier",
742
+ "occupation_type"
743
+ ],
744
+ value="fico_band",
745
+ label="Category"
746
+ )
747
+
748
+ ranking_chart = gr.Plot(
749
+ label="High-Risk Segments"
750
+ )
751
+
752
+ gen_ranking_btn = gr.Button(
753
+ "Generate Risk Ranking",
754
+ variant="primary"
755
+ )
756
+
757
+ gen_ranking_btn.click(
758
+ fn=generate_high_risk_segments_ranking,
759
+ inputs=[ranking_metric, ranking_category],
760
+ outputs=ranking_chart
761
+ )
762
+
763
+ gr.Markdown(
764
+ "---"
765
+ )
766
+
767
+ # --------- MULTI-CATEGORY COMPARISON ---------
768
+
769
+ gr.Markdown(
770
+ "### ๐Ÿ”€ Cross-Category Risk Comparison"
771
+ )
772
+
773
+ comparison_metric = gr.Dropdown(
774
+ choices=[
775
+ "30+@3",
776
+ "30+@6",
777
+ "60+@6",
778
+ "Yr1 NCL"
779
+ ],
780
+ value="30+@6",
781
+ label="Metric"
782
+ )
783
+
784
+ comparison_chart = gr.Plot(
785
+ label="Multi-Category Comparison"
786
+ )
787
+
788
+ gen_comparison_btn = gr.Button(
789
+ "Generate Comparison",
790
+ variant="primary"
791
+ )
792
+
793
+ gen_comparison_btn.click(
794
+ fn=generate_multi_category_comparison,
795
+ inputs=comparison_metric,
796
+ outputs=comparison_chart
797
+ )
798
+
799
+ gr.Markdown(
800
+ "---"
801
+ )
802
+
803
+ # --------- PORTFOLIO SUMMARY ---------
804
+
805
+ gr.Markdown(
806
+ "### ๐Ÿ“‹ Portfolio Risk Summary"
807
+ )
808
+
809
+ summary_table = gr.Dataframe(
810
+ label="Risk Summary"
811
+ )
812
+
813
+ gen_summary_btn = gr.Button(
814
+ "Generate Summary",
815
+ variant="primary"
816
+ )
817
+
818
+ gen_summary_btn.click(
819
+ fn=generate_portfolio_summary,
820
+ outputs=summary_table
821
+ )
822
+
823
+ app.launch()