| """ |
| page4_results.py β Simulation Results: 5 tables + charts for all analysis dimensions |
| """ |
|
|
| import streamlit as st |
| import plotly.graph_objects as go |
| import plotly.express as px |
| import pandas as pd |
| import numpy as np |
| from styles import (inject_global_css, page_header, metric_row, |
| WCO_GOLD, WCO_BLUE, WCO_GREEN, WCO_RED, |
| WCO_CARD_BG, WCO_BORDER, WCO_MUTED, RISK_COLORS) |
| from simulation_engine import (build_rule_hit_table, build_channel_table, |
| build_exploration_discovery_table, |
| build_offence_db_table, build_risk_score_table, |
| RISK_AREAS) |
|
|
| RISK_LEVEL_COLORS = { |
| "FRAUD_DETECTED": "#C8102E", |
| "DOC_QUERY": "#F5A800", |
| "CLEAN": "#00843D", |
| "NOT_INSPECTED": "#6B85AA", |
| } |
|
|
|
|
| def offence_db_growth_chart(df): |
| """Cumulative offence DB additions over bill sequence.""" |
| df_sorted = df.sort_index() |
| df_sorted["cum_offence"] = df_sorted["added_to_offence_db"].cumsum() |
|
|
| |
| traces = [] |
| for area_name, area_cfg in RISK_AREAS.items(): |
| a_df = df_sorted[df_sorted["risk_area"] == area_name].copy() |
| a_df["cum"] = a_df["added_to_offence_db"].cumsum() |
| traces.append(go.Scatter( |
| x=a_df.index, y=a_df["cum"], |
| name=f"{area_cfg['icon']} {area_name}", |
| line=dict(color=area_cfg["color"], width=2), |
| mode="lines", |
| hovertemplate=f"<b>{area_name}</b><br>Bill: %{{x}}<br>Cumulative: %{{y}}<extra></extra>", |
| )) |
| traces.append(go.Scatter( |
| x=df_sorted.index, y=df_sorted["cum_offence"], |
| name="π Total", line=dict(color=WCO_GOLD, width=3, dash="dot"), |
| hovertemplate="<b>Total</b><br>Bill: %{x}<br>Cumulative: %{y}<extra></extra>", |
| )) |
|
|
| fig = go.Figure(traces) |
| fig.update_layout( |
| paper_bgcolor="#070E1C", plot_bgcolor="#0B1220", |
| font=dict(family="IBM Plex Sans", color="#D0DCF0", size=11), |
| height=380, |
| title=dict(text="<b>Real-Time Offence Database Growth (Cumulative)</b>", |
| font=dict(color=WCO_GOLD, size=14, family="Playfair Display"), x=0.5), |
| xaxis=dict(title="Bill Sequence", gridcolor="#1E3A6E"), |
| yaxis=dict(title="Offences Added to DB", gridcolor="#1E3A6E"), |
| legend=dict(bgcolor="#0F1C35", bordercolor=WCO_BORDER, font=dict(size=10)), |
| margin=dict(l=55, r=20, t=55, b=45), |
| ) |
| return fig |
|
|
|
|
| def exploration_vs_exploitation_chart(df): |
| """Compare detection across exploit vs explore bills.""" |
| cats = ["Exploitation Bills", "Exploration Bills"] |
| exploit_df = df[df["is_exploration"] == 0] |
| explore_df = df[df["is_exploration"] == 1] |
|
|
| def detection_stats(sub): |
| tot = len(sub) |
| fraud = (sub["inspection_outcome"] == "FRAUD_DETECTED").sum() |
| rev = sub["detected_revenue"].sum() |
| rate = 100 * fraud / tot if tot else 0 |
| return tot, fraud, rev, rate |
|
|
| e_tot, e_fraud, e_rev, e_rate = detection_stats(exploit_df) |
| x_tot, x_fraud, x_rev, x_rate = detection_stats(explore_df) |
|
|
| fig = go.Figure() |
| fig.add_trace(go.Bar(name="π― Exploitations", x=["Bills","Frauds Detected","Revenue ($K)"], |
| y=[e_tot, e_fraud, e_rev/1000], |
| marker_color="#0066CC", opacity=0.85, |
| text=[e_tot, e_fraud, f"${e_rev/1000:.1f}K"], textposition="outside", |
| textfont=dict(color="#D0DCF0"))) |
| fig.add_trace(go.Bar(name="π Explorations", x=["Bills","Frauds Detected","Revenue ($K)"], |
| y=[x_tot, x_fraud, x_rev/1000], |
| marker_color="#CC77FF", opacity=0.85, |
| text=[x_tot, x_fraud, f"${x_rev/1000:.1f}K"], textposition="outside", |
| textfont=dict(color="#D0DCF0"))) |
| fig.update_layout( |
| paper_bgcolor="#070E1C", plot_bgcolor="#0B1220", |
| font=dict(family="IBM Plex Sans", color="#D0DCF0", size=12), |
| height=340, barmode="group", |
| title=dict(text="<b>Exploitation vs Exploration: Detection Comparison</b>", |
| font=dict(color=WCO_GOLD, size=14, family="Playfair Display"), x=0.5), |
| xaxis=dict(gridcolor="#1E3A6E"), |
| yaxis=dict(gridcolor="#1E3A6E", title="Count / Value"), |
| legend=dict(bgcolor="#0F1C35", bordercolor=WCO_BORDER), |
| margin=dict(l=50, r=20, t=55, b=40), |
| ) |
| return fig |
|
|
|
|
| def weight_evolution_chart(df, orig_weights, updated_weights): |
| """Visualise how rule weights increased after simulation.""" |
| rids, orig, updated, deltas = [], [], [], [] |
| for area in RISK_AREAS.values(): |
| for rule in area["rules"]: |
| rid = rule["id"] |
| o = orig_weights.get(rid, rule["weight"]) |
| u = updated_weights.get(rid, o) |
| rids.append(rid) |
| orig.append(o) |
| updated.append(u) |
| deltas.append(round(u - o, 4)) |
|
|
| fig = go.Figure() |
| fig.add_trace(go.Bar(x=rids, y=orig, name="Original Weights", |
| marker_color="#1E3A6E", opacity=0.9)) |
| fig.add_trace(go.Bar(x=rids, y=updated, name="Updated Weights", |
| marker_color=WCO_GOLD, opacity=0.85)) |
| fig.add_trace(go.Scatter(x=rids, y=deltas, name="Ξ Weight", mode="markers+lines", |
| marker=dict(color=WCO_RED, size=9, symbol="triangle-up"), |
| line=dict(color=WCO_RED, dash="dot", width=1.5), |
| yaxis="y2")) |
| fig.update_layout( |
| paper_bgcolor="#070E1C", plot_bgcolor="#0B1220", |
| font=dict(family="IBM Plex Sans", color="#D0DCF0", size=10), |
| height=360, barmode="group", |
| title=dict(text="<b>Rule Weight Evolution After Self-Learning Feedback</b>", |
| font=dict(color=WCO_GOLD, size=14, family="Playfair Display"), x=0.5), |
| xaxis=dict(gridcolor="#1E3A6E", tickangle=-45), |
| yaxis=dict(title="Weight", gridcolor="#1E3A6E"), |
| yaxis2=dict(title="Ξ Weight", overlaying="y", side="right", |
| gridcolor="#1E3A6E", showgrid=False), |
| legend=dict(bgcolor="#0F1C35", bordercolor=WCO_BORDER), |
| margin=dict(l=55, r=55, t=55, b=90), |
| ) |
| return fig |
|
|
|
|
| def styled_table(df: pd.DataFrame, title: str, highlight_col: str = None, |
| color_map: dict = None): |
| st.markdown(f""" |
| <div style="color:{WCO_GOLD};font-family:'Playfair Display',serif; |
| font-size:15px;font-weight:700;margin:16px 0 10px;"> |
| {title} |
| </div>""", unsafe_allow_html=True) |
| if highlight_col and color_map: |
| def _style(val): |
| col = color_map.get(val, "") |
| return f"background-color:{col}22;color:{col}" if col else "" |
| styled = df.style.applymap(_style, subset=[highlight_col]) \ |
| .set_properties(**{"background-color": WCO_CARD_BG, |
| "color": "#D0DCF0", "font-size": "12px"}) |
| st.dataframe(styled, use_container_width=True, height=min(38 * len(df) + 40, 420)) |
| else: |
| st.dataframe( |
| df.style.set_properties(**{"background-color": WCO_CARD_BG, |
| "color": "#D0DCF0", "font-size": "12px"}), |
| use_container_width=True, height=min(38 * len(df) + 40, 420), |
| ) |
|
|
|
|
| def exploration_scatter_tab(df: pd.DataFrame): |
| """ |
| Full scatter-plot breakdown of the bATE exploration logic applied to all bills. |
| Shows every formula component: |
| unc_i = -1.8 * |fraud_score - 0.5| + 1 |
| S_i = unc_i * log(pred_revenue + Ξ΅) |
| Colour = channel assignment. Shape = exploration pick or not. |
| """ |
| import numpy as np |
|
|
| |
| st.markdown(""" |
| <div class="wco-card-gold"> |
| <h3>π§ bATE Exploration Logic β All Bills Visualised</h3> |
| <div style="display:flex;gap:30px;flex-wrap:wrap;font-size:13px;color:#D0DCF0;line-height:2;"> |
| <div> |
| <b style="color:#CC77FF;">Step 1 β Uncertainty Score</b><br/> |
| <code style="background:#111D30;padding:4px 10px;border-radius:5px; |
| color:#C8A951;font-size:13px;"> |
| unc_i = β1.8 Γ |Ε·αΆΛ‘Λ’ β 0.5| + 1 |
| </code><br/> |
| <span style="color:#6B85AA;font-size:12px;"> |
| Max = 1.0 when fraud_score = 0.5 (model completely unsure)<br/> |
| Min β 0.1 when fraud_score β 0 or β 1 (model very confident) |
| </span> |
| </div> |
| <div> |
| <b style="color:#CC77FF;">Step 2 β Scale Factor (bATE)</b><br/> |
| <code style="background:#111D30;padding:4px 10px;border-radius:5px; |
| color:#C8A951;font-size:13px;"> |
| S_i = unc_i Γ log(Ε·Κ³α΅α΅ + Ξ΅) |
| </code><br/> |
| <span style="color:#6B85AA;font-size:12px;"> |
| High S_i = uncertain AND high revenue β top exploration candidates<br/> |
| K-means++ diversity applied on top to avoid redundant picks |
| </span> |
| </div> |
| </div> |
| </div>""", unsafe_allow_html=True) |
|
|
| st.markdown("<br/>", unsafe_allow_html=True) |
|
|
| |
| ch_color = {"RED": "#C8102E", "YELLOW": "#F5A800", "GREEN": "#00843D"} |
| ch_marker = {"RED": "circle", "YELLOW": "diamond", "GREEN": "square"} |
|
|
| def base_layout(title, xlab, ylab, h=460): |
| return dict( |
| paper_bgcolor="#070E1C", plot_bgcolor="#0B1220", |
| font=dict(family="IBM Plex Sans", color="#D0DCF0", size=11), |
| height=h, |
| title=dict(text=f"<b>{title}</b>", |
| font=dict(color=WCO_GOLD, size=14, |
| family="Playfair Display"), x=0.5), |
| xaxis=dict(title=xlab, gridcolor="#1E3A6E", zeroline=False), |
| yaxis=dict(title=ylab, gridcolor="#1E3A6E", zeroline=False), |
| legend=dict(bgcolor="#0F1C35", bordercolor=WCO_BORDER, |
| font=dict(size=10)), |
| margin=dict(l=60, r=20, t=55, b=55), |
| ) |
|
|
| |
| |
| |
| st.markdown('<div class="section-title">β Fraud Score vs Uncertainty Score β The unc_i Curve</div>', |
| unsafe_allow_html=True) |
| st.markdown("""<div class="alert-blue"> |
| The <b>uncertainty score unc_i</b> is a concave function maximised when |
| <code>fraud_score β 0.5</code>. Bills near 0.5 are the model's blind spots β |
| exploration targets. Bills near 0 or 1 are confident predictions (exploitation territory). |
| <b style="color:#CC77FF;">Purple stars</b> = actual gATE exploration picks. |
| </div>""", unsafe_allow_html=True) |
|
|
| |
| x_curve = np.linspace(0, 1, 300) |
| y_curve = -1.8 * np.abs(x_curve - 0.5) + 1 |
|
|
| fig1 = go.Figure() |
|
|
| |
| fig1.add_trace(go.Scatter( |
| x=x_curve, y=y_curve, |
| mode="lines", |
| name="unc_i = β1.8Γ|Ε·β0.5|+1 (theoretical)", |
| line=dict(color="#C8A951", width=2.5, dash="dot"), |
| hoverinfo="skip", |
| )) |
|
|
| |
| for ch in ["GREEN", "YELLOW", "RED"]: |
| sub = df[df["channel"] == ch] |
| non_exp = sub[sub["is_exploration"] == 0] |
| fig1.add_trace(go.Scatter( |
| x=non_exp["fraud_score"], |
| y=non_exp["uncertainty_score"], |
| mode="markers", |
| name=f"{ch} (exploitation)", |
| marker=dict( |
| color=ch_color[ch], size=5, opacity=0.45, |
| symbol=ch_marker[ch], |
| line=dict(width=0), |
| ), |
| hovertemplate=( |
| f"<b>{ch}</b><br>" |
| "Fraud Score: %{x:.3f}<br>" |
| "Uncertainty: %{y:.3f}<br>" |
| "<extra></extra>" |
| ), |
| )) |
|
|
| |
| exp_df = df[df["is_exploration"] == 1] |
| fig1.add_trace(go.Scatter( |
| x=exp_df["fraud_score"], |
| y=exp_df["uncertainty_score"], |
| mode="markers", |
| name="π gATE Exploration Picks", |
| marker=dict( |
| color="#CC77FF", size=11, opacity=0.95, |
| symbol="star", |
| line=dict(color="#ffffff", width=0.8), |
| ), |
| hovertemplate=( |
| "<b>EXPLORATION PICK</b><br>" |
| "Bill: %{customdata[0]}<br>" |
| "Fraud Score: %{x:.3f}<br>" |
| "Uncertainty: %{y:.3f}<br>" |
| "Risk Area: %{customdata[1]}<br>" |
| "<extra></extra>" |
| ), |
| customdata=exp_df[["bill_id", "risk_area"]].values, |
| )) |
|
|
| |
| fig1.add_vline(x=0.5, line=dict(color="#CC77FF", dash="dash", width=1.2), |
| annotation_text="Max uncertainty (0.5)", |
| annotation_font_color="#CC77FF", |
| annotation_position="top right") |
|
|
| |
| fig1.add_vrect(x0=0.3, x1=0.7, |
| fillcolor="#CC77FF", opacity=0.05, |
| line_width=0, |
| annotation_text="Exploration zone", |
| annotation_font_color="#CC77FF", |
| annotation_position="top left") |
|
|
| fig1.update_layout(**base_layout( |
| "Fraud Score vs Uncertainty Score | unc_i = β1.8 Γ |Ε·αΆΛ‘Λ’ β 0.5| + 1", |
| "Fraud Score Ε·αΆΛ‘Λ’ (DATE exploitation output)", |
| "Uncertainty Score unc_i", |
| h=480, |
| )) |
| st.plotly_chart(fig1, use_container_width=True) |
|
|
| |
| |
| |
| st.markdown('<div class="section-title">β‘ Uncertainty vs log(Revenue) β The S_i Scale Factor Space</div>', |
| unsafe_allow_html=True) |
| st.markdown("""<div class="alert-blue"> |
| <b>S_i = unc_i Γ log(Ε·Κ³α΅α΅ + Ξ΅)</b> β bills in the |
| <b style="color:#CC77FF;">top-right quadrant</b> score highest and are |
| the primary exploration candidates: they are uncertain <i>and</i> financially significant. |
| Bottom-left = low uncertainty low revenue = safe GREEN facilitation. |
| </div>""", unsafe_allow_html=True) |
|
|
| epsilon = 1e-6 |
| df_plot = df.copy() |
| df_plot["log_rev"] = np.log(df_plot["pred_revenue"] + epsilon) |
|
|
| fig2 = go.Figure() |
|
|
| for ch in ["GREEN", "YELLOW", "RED"]: |
| sub = df_plot[(df_plot["channel"] == ch) & (df_plot["is_exploration"] == 0)] |
| fig2.add_trace(go.Scatter( |
| x=sub["log_rev"], |
| y=sub["uncertainty_score"], |
| mode="markers", |
| name=f"{ch}", |
| marker=dict( |
| color=ch_color[ch], size=5, opacity=0.40, |
| symbol=ch_marker[ch], line=dict(width=0), |
| ), |
| hovertemplate=( |
| f"<b>{ch}</b><br>" |
| "log(Revenue): %{x:.2f}<br>" |
| "Uncertainty: %{y:.3f}<br>" |
| "Scale Factor S_i: %{customdata:.3f}<br>" |
| "<extra></extra>" |
| ), |
| customdata=sub["scale_factor"], |
| )) |
|
|
| |
| exp_plot = df_plot[df_plot["is_exploration"] == 1] |
| fig2.add_trace(go.Scatter( |
| x=exp_plot["log_rev"], |
| y=exp_plot["uncertainty_score"], |
| mode="markers", |
| name="π Exploration Picks", |
| marker=dict(color="#CC77FF", size=12, opacity=0.95, |
| symbol="star", line=dict(color="#fff", width=0.8)), |
| hovertemplate=( |
| "<b>EXPLORATION PICK</b><br>" |
| "Bill: %{customdata[0]}<br>" |
| "log(Rev): %{x:.2f}<br>" |
| "Uncertainty: %{y:.3f}<br>" |
| "S_i: %{customdata[1]:.3f}<br>" |
| "Risk Area: %{customdata[2]}<br>" |
| "<extra></extra>" |
| ), |
| customdata=exp_plot[["bill_id", "scale_factor", "risk_area"]].values, |
| )) |
|
|
| |
| fraud_plot = df_plot[df_plot["inspection_outcome"] == "FRAUD_DETECTED"] |
| fig2.add_trace(go.Scatter( |
| x=fraud_plot["log_rev"], |
| y=fraud_plot["uncertainty_score"], |
| mode="markers", |
| name="π¨ Fraud Detected", |
| marker=dict(color="#FF4466", size=8, opacity=0.85, |
| symbol="x", line=dict(color="#FF4466", width=1.5)), |
| hovertemplate=( |
| "<b>FRAUD DETECTED</b><br>" |
| "Bill: %{customdata[0]}<br>" |
| "Channel: %{customdata[1]}<br>" |
| "log(Rev): %{x:.2f}<br>" |
| "Uncertainty: %{y:.3f}<br>" |
| "<extra></extra>" |
| ), |
| customdata=fraud_plot[["bill_id", "channel"]].values, |
| )) |
|
|
| |
| x_med = df_plot["log_rev"].median() |
| y_med = df_plot["uncertainty_score"].median() |
| fig2.add_hline(y=y_med, line=dict(color="#1E3A6E", dash="dash", width=1)) |
| fig2.add_vline(x=x_med, line=dict(color="#1E3A6E", dash="dash", width=1)) |
|
|
| for (xa, ya, txt, col) in [ |
| (0.98, 0.98, "HIGH S_i<br>Explore", "#CC77FF"), |
| (0.02, 0.98, "Uncertain<br>Low Rev", "#F5A800"), |
| (0.98, 0.02, "High Rev<br>Confident", "#0066CC"), |
| (0.02, 0.02, "LOW S_i<br>Facilitate", "#00843D"), |
| ]: |
| fig2.add_annotation( |
| xref="paper", yref="paper", x=xa, y=ya, |
| text=f"<b>{txt}</b>", showarrow=False, |
| font=dict(color=col, size=10), |
| bgcolor=col+"18", bordercolor=col, |
| borderwidth=1, borderpad=4, |
| ) |
|
|
| fig2.update_layout(**base_layout( |
| "Uncertainty Score vs log(Revenue) | S_i = unc_i Γ log(Ε·Κ³α΅α΅ + Ξ΅)", |
| "log(Predicted Revenue Ε·Κ³α΅α΅)", |
| "Uncertainty Score unc_i", |
| h=500, |
| )) |
| st.plotly_chart(fig2, use_container_width=True) |
|
|
| |
| |
| |
| st.markdown('<div class="section-title">β’ Scale Factor S_i vs Fraud Score β Full bATE Decision Space</div>', |
| unsafe_allow_html=True) |
| st.markdown("""<div class="alert-blue"> |
| The <b>scale factor S_i</b> (y-axis) is what bATE actually ranks bills by. |
| <b>Bubble size = predicted revenue.</b> |
| Exploration picks (β) cluster at <b>mid fraud-score Γ high S_i</b> β uncertain AND valuable. |
| Exploitation picks cluster at <b>high fraud-score</b> regardless of uncertainty. |
| </div>""", unsafe_allow_html=True) |
|
|
| |
| rev_norm = ((df["pred_revenue"] - df["pred_revenue"].min()) / |
| (df["pred_revenue"].max() - df["pred_revenue"].min() + 1e-6)) |
| df_plot2 = df.copy() |
| df_plot2["bubble"] = 5 + rev_norm * 22 |
|
|
| fig3 = go.Figure() |
|
|
| |
| grn = df_plot2[(df_plot2["channel"] == "GREEN")] |
| fig3.add_trace(go.Scatter( |
| x=grn["fraud_score"], y=grn["scale_factor"], |
| mode="markers", |
| name="π’ GREEN (Facilitated)", |
| marker=dict(color="#00843D", size=grn["bubble"], opacity=0.25, |
| line=dict(width=0)), |
| hovertemplate=( |
| "<b>GREEN</b><br>Fraud Score: %{x:.3f}<br>" |
| "S_i: %{y:.3f}<br>Revenue: $%{customdata:,.0f}<extra></extra>" |
| ), |
| customdata=grn["pred_revenue"], |
| )) |
|
|
| |
| expl_picks = df_plot2[(df_plot2["channel"].isin(["RED","YELLOW"])) & |
| (df_plot2["is_exploration"] == 0)] |
| for ch in ["YELLOW", "RED"]: |
| sub = expl_picks[expl_picks["channel"] == ch] |
| fig3.add_trace(go.Scatter( |
| x=sub["fraud_score"], y=sub["scale_factor"], |
| mode="markers", |
| name=f"{'π΄' if ch=='RED' else 'π‘'} {ch} (Exploitation)", |
| marker=dict(color=ch_color[ch], size=sub["bubble"], |
| opacity=0.65, symbol=ch_marker[ch], |
| line=dict(color=ch_color[ch], width=0.5)), |
| hovertemplate=( |
| f"<b>{ch} β EXPLOITATION</b><br>" |
| "Fraud Score: %{x:.3f}<br>" |
| "S_i: %{y:.3f}<br>" |
| "Revenue: $%{customdata[0]:,.0f}<br>" |
| "Bill: %{customdata[1]}<br>" |
| "<extra></extra>" |
| ), |
| customdata=sub[["pred_revenue", "bill_id"]].values, |
| )) |
|
|
| |
| ep = df_plot2[df_plot2["is_exploration"] == 1] |
| fig3.add_trace(go.Scatter( |
| x=ep["fraud_score"], y=ep["scale_factor"], |
| mode="markers", |
| name="π gATE Exploration Picks", |
| marker=dict(color="#CC77FF", size=ep["bubble"] + 4, |
| opacity=1.0, symbol="star", |
| line=dict(color="#ffffff", width=1)), |
| hovertemplate=( |
| "<b>EXPLORATION PICK (gATE)</b><br>" |
| "Bill: %{customdata[0]}<br>" |
| "Risk Area: %{customdata[1]}<br>" |
| "Fraud Score: %{x:.3f}<br>" |
| "unc_i: %{customdata[2]:.3f}<br>" |
| "S_i: %{y:.3f}<br>" |
| "Pred Revenue: $%{customdata[3]:,.0f}<br>" |
| "<extra></extra>" |
| ), |
| customdata=ep[["bill_id", "risk_area", |
| "uncertainty_score", "pred_revenue"]].values, |
| )) |
|
|
| |
| fd = df_plot2[df_plot2["inspection_outcome"] == "FRAUD_DETECTED"] |
| fig3.add_trace(go.Scatter( |
| x=fd["fraud_score"], y=fd["scale_factor"], |
| mode="markers", |
| name="π¨ Fraud Detected", |
| marker=dict(color="#FF4466", size=10, opacity=0.9, |
| symbol="x-thin", line=dict(color="#FF4466", width=2)), |
| hovertemplate=( |
| "<b>FRAUD CONFIRMED</b><br>" |
| "Bill: %{customdata[0]}<br>" |
| "Channel: %{customdata[1]}<br>" |
| "Revenue Recovered: $%{customdata[2]:,.0f}<br>" |
| "<extra></extra>" |
| ), |
| customdata=fd[["bill_id", "channel", "detected_revenue"]].values, |
| )) |
|
|
| |
| n_explore = ep.shape[0] |
| if n_explore > 0: |
| si_threshold = df_plot2["scale_factor"].nlargest(n_explore * 3).min() |
| fig3.add_hline( |
| y=si_threshold, |
| line=dict(color="#CC77FF", dash="dash", width=1.3), |
| annotation_text=f" gATE S_i candidate threshold", |
| annotation_font_color="#CC77FF", |
| annotation_position="right", |
| ) |
|
|
| fig3.update_layout(**base_layout( |
| "Scale Factor S_i vs Fraud Score | Bubble Size = Predicted Revenue", |
| "Fraud Score Ε·αΆΛ‘Λ’ (DATE output β Exploitation axis)", |
| "Scale Factor S_i = unc_i Γ log(Ε·Κ³α΅α΅ + Ξ΅) (Exploration axis)", |
| h=520, |
| )) |
| st.plotly_chart(fig3, use_container_width=True) |
|
|
| |
| |
| |
| st.markdown('<div class="section-title">β£ Per Risk Area β Exploration Picks in Context</div>', |
| unsafe_allow_html=True) |
| st.markdown("""<div class="alert-blue"> |
| Same axes as Chart β but broken down by risk area. |
| Shows <b>which risk areas have the most uncertain bills</b> |
| and whether exploration picks are concentrated in the right zone. |
| </div>""", unsafe_allow_html=True) |
|
|
| area_names = list(RISK_AREAS.keys()) |
| cols_fa = st.columns(5) |
| for i, (area_name, area_cfg) in enumerate(RISK_AREAS.items()): |
| a_df = df[df["risk_area"] == area_name] |
| color = area_cfg["color"] |
| exp_a = a_df[a_df["is_exploration"] == 1] |
|
|
| fig_fa = go.Figure() |
| |
| fig_fa.add_trace(go.Scatter( |
| x=a_df["fraud_score"], y=a_df["uncertainty_score"], |
| mode="markers", |
| name="All Bills", |
| marker=dict(color=color, size=5, opacity=0.35, |
| line=dict(width=0)), |
| showlegend=False, |
| )) |
| |
| if len(exp_a) > 0: |
| fig_fa.add_trace(go.Scatter( |
| x=exp_a["fraud_score"], y=exp_a["uncertainty_score"], |
| mode="markers", |
| name="Exploration", |
| marker=dict(color="#CC77FF", size=10, opacity=0.95, |
| symbol="star", |
| line=dict(color="#fff", width=0.6)), |
| showlegend=False, |
| )) |
| |
| fd_a = a_df[a_df["inspection_outcome"] == "FRAUD_DETECTED"] |
| if len(fd_a) > 0: |
| fig_fa.add_trace(go.Scatter( |
| x=fd_a["fraud_score"], y=fd_a["uncertainty_score"], |
| mode="markers", |
| name="Fraud", |
| marker=dict(color="#FF4466", size=7, opacity=0.85, |
| symbol="x-thin", |
| line=dict(color="#FF4466", width=1.5)), |
| showlegend=False, |
| )) |
|
|
| fig_fa.add_vline(x=0.5, line=dict(color="#CC77FF33", width=1)) |
| fig_fa.update_layout( |
| paper_bgcolor="#070E1C", plot_bgcolor="#0B1220", |
| font=dict(color="#D0DCF0", size=9), height=240, |
| title=dict( |
| text=f"<b>{area_cfg['icon']} {area_name.split('/')[0]}</b>", |
| font=dict(color=color, size=11), x=0.5, |
| ), |
| xaxis=dict(title="Fraud Score", gridcolor="#1E3A6E", |
| range=[0,1], tickfont=dict(size=8)), |
| yaxis=dict(title="unc_i", gridcolor="#1E3A6E", |
| range=[0,1.1], tickfont=dict(size=8)), |
| margin=dict(l=35, r=8, t=40, b=35), |
| showlegend=False, |
| ) |
| with cols_fa[i]: |
| st.plotly_chart(fig_fa, use_container_width=True) |
| n_exp = len(exp_a) |
| n_tot = len(a_df) |
| n_fd = len(fd_a) |
| st.markdown(f""" |
| <div style="background:#0F1C35;border:1px solid {color}; |
| border-radius:8px;padding:10px;font-size:11px; |
| text-align:center;margin-top:-10px;"> |
| <span style="color:{color};">Total:</span> {n_tot}<br/> |
| <span style="color:#CC77FF;">β Explore:</span> {n_exp}<br/> |
| <span style="color:#FF4466;">π¨ Fraud:</span> {n_fd}<br/> |
| <span style="color:#C8A951;">Avg S_i:</span> |
| {a_df['scale_factor'].mean():.2f} |
| </div>""", unsafe_allow_html=True) |
|
|
| |
| |
| |
| st.markdown("<br/>", unsafe_allow_html=True) |
| exp_df_s = df[df["is_exploration"] == 1] |
| avg_unc = exp_df_s["uncertainty_score"].mean() |
| avg_si = exp_df_s["scale_factor"].mean() |
| avg_fs = exp_df_s["fraud_score"].mean() |
| all_avg_unc = df["uncertainty_score"].mean() |
| all_avg_si = df["scale_factor"].mean() |
| all_avg_fs = df["fraud_score"].mean() |
|
|
| st.markdown(f""" |
| <div class="wco-card-gold"> |
| <h3>π Exploration Selection Summary β Formula Validation</h3> |
| <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:8px;"> |
| |
| <div style="background:#111D30;border-radius:8px;padding:14px;text-align:center;"> |
| <div style="color:#6B85AA;font-size:11px;text-transform:uppercase; |
| letter-spacing:0.07em;margin-bottom:6px;">Avg Fraud Score Ε·αΆΛ‘Λ’</div> |
| <div style="color:#CC77FF;font-size:22px;font-weight:700; |
| font-family:'Georgia',serif;">{avg_fs:.3f}</div> |
| <div style="color:#6B85AA;font-size:11px;">Exploration picks</div> |
| <div style="color:#44CC88;font-size:18px;font-weight:700;margin-top:4px;">{all_avg_fs:.3f}</div> |
| <div style="color:#6B85AA;font-size:11px;">All bills (baseline)</div> |
| <div style="color:#C8A951;font-size:11px;margin-top:6px;"> |
| {'β
Closer to 0.5 = correct' if abs(avg_fs-0.5) < abs(all_avg_fs-0.5) else 'β Similar to baseline'} |
| </div> |
| </div> |
| |
| <div style="background:#111D30;border-radius:8px;padding:14px;text-align:center;"> |
| <div style="color:#6B85AA;font-size:11px;text-transform:uppercase; |
| letter-spacing:0.07em;margin-bottom:6px;">Avg Uncertainty unc_i</div> |
| <div style="color:#CC77FF;font-size:22px;font-weight:700; |
| font-family:'Georgia',serif;">{avg_unc:.3f}</div> |
| <div style="color:#6B85AA;font-size:11px;">Exploration picks</div> |
| <div style="color:#44CC88;font-size:18px;font-weight:700;margin-top:4px;">{all_avg_unc:.3f}</div> |
| <div style="color:#6B85AA;font-size:11px;">All bills (baseline)</div> |
| <div style="color:#C8A951;font-size:11px;margin-top:6px;"> |
| {'β
Higher unc = targeting blind spots' if avg_unc > all_avg_unc else 'β Check exploration ratio'} |
| </div> |
| </div> |
| |
| <div style="background:#111D30;border-radius:8px;padding:14px;text-align:center;"> |
| <div style="color:#6B85AA;font-size:11px;text-transform:uppercase; |
| letter-spacing:0.07em;margin-bottom:6px;">Avg Scale Factor S_i</div> |
| <div style="color:#CC77FF;font-size:22px;font-weight:700; |
| font-family:'Georgia',serif;">{avg_si:.3f}</div> |
| <div style="color:#6B85AA;font-size:11px;">Exploration picks</div> |
| <div style="color:#44CC88;font-size:18px;font-weight:700;margin-top:4px;">{all_avg_si:.3f}</div> |
| <div style="color:#6B85AA;font-size:11px;">All bills (baseline)</div> |
| <div style="color:#C8A951;font-size:11px;margin-top:6px;"> |
| {'β
Higher S_i = uncertain + valuable' if avg_si > all_avg_si else 'β Revenue weighting check'} |
| </div> |
| </div> |
| |
| </div> |
| </div>""", unsafe_allow_html=True) |
|
|
|
|
| def show(): |
| inject_global_css() |
| page_header("π", "Simulation Results & Analysis", |
| "RULE HIT TABLES Β· OFFENCE DATABASE GROWTH Β· EXPLORATION GAINS Β· WEIGHT EVOLUTION") |
|
|
| if "sim_df" not in st.session_state: |
| st.markdown(""" |
| <div style="background:#0F1C35;border:2px dashed #1E3A6E;border-radius:14px; |
| padding:60px;text-align:center;margin-top:30px;"> |
| <div style="font-size:40px;margin-bottom:12px;">β οΈ</div> |
| <div style="color:#F5A800;font-size:18px;font-family:'Playfair Display',serif;"> |
| No Simulation Data Found |
| </div> |
| <div style="color:#6B85AA;margin-top:10px;"> |
| Please run the simulation on <b>Page 3</b> first. |
| </div> |
| </div>""", unsafe_allow_html=True) |
| return |
|
|
| df = st.session_state.sim_df |
| updated_weights = st.session_state.get("sim_weights", {}) |
| orig_weights = st.session_state.get("rule_weights", {}) |
| efficiency = st.session_state.get("sim_efficiency", {}) |
|
|
| |
| fraud_total = (df["inspection_outcome"] == "FRAUD_DETECTED").sum() |
| offence_adds = df["added_to_offence_db"].sum() |
| explore_finds = df[(df["is_exploration"]==1) & (df["added_to_offence_db"]==1)].shape[0] |
| revenue = df["detected_revenue"].sum() |
| metric_row([ |
| (fraud_total, "Frauds Detected", WCO_RED), |
| (offence_adds, "Offence DB Additions", WCO_GOLD), |
| (explore_finds, "Exploration Discoveries", "#CC77FF"), |
| (f"${revenue:,.0f}", "Revenue Recovered", WCO_GREEN), |
| (f"{efficiency.get('hybrid',{}).get('efficiency_index',0):.3f}", |
| "Efficiency Index", WCO_GOLD), |
| ]) |
|
|
| tabs = st.tabs([ |
| "π T1: Rule Hits", "π‘ T2: Channel Stats", |
| "π T3: Exploration Finds", "ποΈ T4: Offence DB", |
| "βοΈ T5: Risk Scoring", "π Charts", |
| "π§ Exploration Logic", |
| ]) |
|
|
| |
| with tabs[0]: |
| st.markdown("""<div class="alert-blue"> |
| How many bills were hit by each risk rule, and precision of each rule. |
| </div>""", unsafe_allow_html=True) |
| df_t1 = build_rule_hit_table(df) |
|
|
| |
| fig_t1 = go.Figure(go.Bar( |
| x=df_t1["Rule ID"], y=df_t1["Total Bills Hit"], |
| marker_color=df_t1["Risk Area"].map( |
| {k: v["color"] for k, v in RISK_AREAS.items()}), |
| opacity=0.85, |
| text=df_t1["Precision (%)"].apply(lambda x: f"{x}%"), textposition="outside", |
| textfont=dict(color="#D0DCF0", size=10), |
| hovertext=df_t1["Rule Name"], hoverinfo="text+y", |
| )) |
| fig_t1.update_layout( |
| paper_bgcolor="#070E1C", plot_bgcolor="#0B1220", |
| font=dict(color="#D0DCF0", size=10), height=280, |
| title=dict(text="<b>Bills Hit per Rule (label = detection precision)</b>", |
| font=dict(color=WCO_GOLD, size=13), x=0.5), |
| xaxis=dict(gridcolor="#1E3A6E", tickangle=-45), |
| yaxis=dict(gridcolor="#1E3A6E", title="Bills Hit"), |
| margin=dict(l=45, r=15, t=45, b=75), |
| ) |
| st.plotly_chart(fig_t1, use_container_width=True) |
| styled_table(df_t1, "Table 1 β Risk Rule Hit Analysis") |
|
|
| |
| with tabs[1]: |
| st.markdown("""<div class="alert-blue"> |
| Bills treated per channel, exploitation vs exploration split, fraud detection per channel. |
| </div>""", unsafe_allow_html=True) |
| df_t2 = build_channel_table(df) |
|
|
| c1, c2 = st.columns(2) |
| with c1: |
| fig_ch = go.Figure(go.Bar( |
| x=df_t2["Channel"], |
| y=df_t2["Total Bills"], |
| marker_color=["#C8102E","#F5A800","#00843D"], opacity=0.85, |
| text=df_t2["Total Bills"], textposition="outside", |
| textfont=dict(color="#D0DCF0"), |
| )) |
| fig_ch.update_layout( |
| paper_bgcolor="#070E1C", plot_bgcolor="#0B1220", |
| font=dict(color="#D0DCF0", size=12), height=300, |
| title=dict(text="<b>Bills per Channel</b>", |
| font=dict(color=WCO_GOLD, size=13), x=0.5), |
| xaxis=dict(gridcolor="#1E3A6E"), |
| yaxis=dict(gridcolor="#1E3A6E"), |
| margin=dict(l=45, r=15, t=45, b=40), |
| ) |
| st.plotly_chart(fig_ch, use_container_width=True) |
| with c2: |
| fig_pie = go.Figure(go.Pie( |
| labels=["Exploitation","Exploration"], |
| values=[df_t2["Exploitation Bills"].sum(), df_t2["Exploration Bills"].sum()], |
| marker=dict(colors=["#0066CC","#CC77FF"], |
| line=dict(color="#070E1C", width=2)), |
| hole=0.55, textfont=dict(color="#D0DCF0"), |
| )) |
| fig_pie.update_layout( |
| paper_bgcolor="#070E1C", |
| font=dict(color="#D0DCF0"), height=300, |
| title=dict(text="<b>Exploit vs Explore Split</b>", |
| font=dict(color=WCO_GOLD, size=13), x=0.5), |
| legend=dict(bgcolor="#0F1C35"), |
| margin=dict(l=20, r=20, t=45, b=20), |
| ) |
| st.plotly_chart(fig_pie, use_container_width=True) |
|
|
| styled_table(df_t2, "Table 2 β Channel Treatment Statistics") |
|
|
| |
| with tabs[2]: |
| st.markdown("""<div class="alert-blue"> |
| New bills discovered and added to offence database via exploration strategy (concept drift detection). |
| </div>""", unsafe_allow_html=True) |
| df_t3 = build_exploration_discovery_table(df) |
| st.plotly_chart(exploration_vs_exploitation_chart(df), use_container_width=True) |
| styled_table(df_t3, "Table 3 β Exploration Discovery Analysis per Risk Area") |
|
|
| |
| with tabs[3]: |
| st.markdown("""<div class="alert-blue"> |
| Real-time offence database augmentation from Red + Yellow channel inspection feedback. |
| </div>""", unsafe_allow_html=True) |
| st.plotly_chart(offence_db_growth_chart(df), use_container_width=True) |
| df_t4 = build_offence_db_table(df) |
| styled_table(df_t4, "Table 4 β Offence Database Augmentation by Risk Area") |
|
|
| |
| areas = df_t4["Risk Area"].tolist() |
| vals = df_t4["Total Added"].tolist() |
| fig_radar = go.Figure(go.Scatterpolar( |
| r=vals + [vals[0]], theta=areas + [areas[0]], |
| fill="toself", fillcolor="rgba(200,169,81,0.15)", |
| line=dict(color=WCO_GOLD, width=2), |
| marker=dict(size=8, color=WCO_GOLD), |
| )) |
| fig_radar.update_layout( |
| paper_bgcolor="#070E1C", plot_bgcolor="#0B1220", |
| font=dict(color="#D0DCF0", size=11), height=360, |
| title=dict(text="<b>Offence DB Coverage Radar</b>", |
| font=dict(color=WCO_GOLD, size=13), x=0.5), |
| polar=dict(bgcolor="#0B1220", |
| angularaxis=dict(gridcolor="#1E3A6E", linecolor="#1E3A6E"), |
| radialaxis=dict(gridcolor="#1E3A6E", linecolor="#1E3A6E")), |
| margin=dict(l=60, r=60, t=55, b=40), |
| ) |
| st.plotly_chart(fig_radar, use_container_width=True) |
|
|
| |
| with tabs[4]: |
| st.markdown("""<div class="alert-blue"> |
| Per-transaction risk scores, rule hits, cumulative weights, and weight evolution after |
| self-learning feedback. Shows how detection efficiency drives automatic weight uplift. |
| </div>""", unsafe_allow_html=True) |
| df_t5 = build_risk_score_table(df, updated_weights) |
|
|
| |
| if orig_weights and updated_weights: |
| st.plotly_chart(weight_evolution_chart(df, orig_weights, updated_weights), |
| use_container_width=True) |
|
|
| styled_table(df_t5, "Table 5 β Transaction Risk Scoring & Weight Evolution", |
| highlight_col="Channel", |
| color_map={"RED": "#C8102E", "YELLOW": "#F5A800", "GREEN": "#00843D"}) |
|
|
| |
| st.markdown('<div class="section-title">π Rule Weight Update Summary</div>', |
| unsafe_allow_html=True) |
| weight_rows = [] |
| for area_name, area_cfg in RISK_AREAS.items(): |
| for rule in area_cfg["rules"]: |
| rid = rule["id"] |
| o = orig_weights.get(rid, rule["weight"]) |
| u = updated_weights.get(rid, o) |
| weight_rows.append({ |
| "Risk Area": area_name, |
| "Rule ID": rid, |
| "Rule Name": rule["name"], |
| "Original Weight":f"{o:.3f}", |
| "Updated Weight": f"{u:.3f}", |
| "Ξ Weight": f"+{u-o:.4f}" if u >= o else f"{u-o:.4f}", |
| "% Change": f"{100*(u-o)/o:.1f}%" if o > 0 else "β", |
| }) |
| df_wd = pd.DataFrame(weight_rows) |
| styled_table(df_wd, "Rule Weight Delta After Self-Learning Cycle") |
|
|
| |
| with tabs[5]: |
| c1, c2 = st.columns(2) |
| with c1: |
| |
| outcomes = df[df["channel"] != "GREEN"]["inspection_outcome"].value_counts() |
| fig_out = go.Figure(go.Pie( |
| labels=outcomes.index, values=outcomes.values, |
| marker=dict(colors=[RISK_LEVEL_COLORS.get(k,"#6B85AA") for k in outcomes.index], |
| line=dict(color="#070E1C", width=2)), |
| hole=0.5, textfont=dict(color="#D0DCF0"), |
| )) |
| fig_out.update_layout( |
| paper_bgcolor="#070E1C", font=dict(color="#D0DCF0"), height=340, |
| title=dict(text="<b>Inspection Outcome Distribution</b>", |
| font=dict(color=WCO_GOLD, size=13), x=0.5), |
| legend=dict(bgcolor="#0F1C35"), margin=dict(l=20,r=20,t=45,b=20), |
| ) |
| st.plotly_chart(fig_out, use_container_width=True) |
| with c2: |
| |
| rev_by_area = df.groupby("risk_area")["detected_revenue"].sum().reset_index() |
| colors = [RISK_AREAS[a]["color"] for a in rev_by_area["risk_area"]] |
| fig_rev = go.Figure(go.Bar( |
| x=rev_by_area["risk_area"], |
| y=rev_by_area["detected_revenue"], |
| marker_color=colors, opacity=0.85, |
| text=rev_by_area["detected_revenue"].apply(lambda x: f"${x:,.0f}"), |
| textposition="outside", textfont=dict(color="#D0DCF0", size=10), |
| )) |
| fig_rev.update_layout( |
| paper_bgcolor="#070E1C", plot_bgcolor="#0B1220", |
| font=dict(color="#D0DCF0", size=10), height=340, |
| title=dict(text="<b>Revenue Recovered by Risk Area</b>", |
| font=dict(color=WCO_GOLD, size=13), x=0.5), |
| xaxis=dict(gridcolor="#1E3A6E", tickangle=-25), |
| yaxis=dict(gridcolor="#1E3A6E", title="Revenue ($)"), |
| margin=dict(l=50, r=15, t=45, b=80), |
| ) |
| st.plotly_chart(fig_rev, use_container_width=True) |
|
|
| |
| sample_df = df.sample(min(300, len(df)), random_state=42) |
| fig_sc = px.scatter( |
| sample_df, x="fraud_score", y="detected_revenue", |
| color="channel", symbol="risk_area", |
| color_discrete_map={"RED":"#C8102E","YELLOW":"#F5A800","GREEN":"#00843D"}, |
| opacity=0.7, height=400, |
| labels={"fraud_score":"Fraud Score (DATE)","detected_revenue":"Revenue Detected ($)"}, |
| title="<b>Fraud Score vs Revenue Recovered (Sample 300 bills)</b>", |
| ) |
| fig_sc.update_layout( |
| paper_bgcolor="#070E1C", plot_bgcolor="#0B1220", |
| font=dict(color="#D0DCF0", family="IBM Plex Sans"), |
| title_font=dict(color=WCO_GOLD, family="Playfair Display"), |
| xaxis=dict(gridcolor="#1E3A6E"), yaxis=dict(gridcolor="#1E3A6E"), |
| legend=dict(bgcolor="#0F1C35", bordercolor=WCO_BORDER), |
| margin=dict(l=55, r=20, t=55, b=45), |
| ) |
| st.plotly_chart(fig_sc, use_container_width=True) |
|
|
| |
| with tabs[6]: |
| exploration_scatter_tab(df) |
|
|