"""
page3_simulation.py — Live Simulation with Full Canvas Animation
All bills complete in exactly 30 seconds. WCO themed, 5-lane layout.
"""
import streamlit as st
import streamlit.components.v1 as components
import plotly.graph_objects as go
import pandas as pd
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)
from simulation_engine import (generate_declarations, compute_risk_scores,
assign_channels, simulate_inspection_outcomes,
compute_updated_weights, get_default_weights,
compute_efficiency_metrics, RISK_AREAS)
# ─────────────────────────────────────────────────────────────────────────────
def build_animation_html(n_bills, bandwidth, exp_ratio,
channel_counts, detected, revenue, efficiency_idx):
red_n = channel_counts.get("RED", 0)
yellow_n = channel_counts.get("YELLOW", 0)
green_n = channel_counts.get("GREEN", 0)
explore_n = int(n_bills * bandwidth * exp_ratio)
return f"""
{n_bills}
Total Declarations
{yellow_n}
🟡 YELLOW Target
{efficiency_idx:.3f}
Efficiency Index
${revenue:,.0f}
Revenue Recovered
{detected}
Frauds Detected
"""
# ─────────────────────────────────────────────────────────────────────────────
def risk_score_distribution(df):
fig = go.Figure()
for ch, color, label in [
("GREEN","#00843D","🟢 GREEN"),
("YELLOW","#F5A800","🟡 YELLOW"),
("RED","#C8102E","🔴 RED"),
]:
cdf = df[df["channel"]==ch]
fig.add_trace(go.Violin(
y=cdf["fraud_score"], name=label,
fillcolor=color, opacity=0.7,
line_color=color, box_visible=True,
meanline_visible=True, points=False,
))
fig.update_layout(
paper_bgcolor="#070E1C", plot_bgcolor="#0B1220",
font=dict(family="IBM Plex Sans", color="#D0DCF0", size=12),
height=300,
title=dict(text="Risk Score Distribution by Channel",
font=dict(color=WCO_GOLD, size=13, family="Playfair Display"), x=0.5),
yaxis=dict(title="Fraud Score", gridcolor="#1E3A6E", range=[0,1]),
xaxis=dict(gridcolor="#1E3A6E"),
legend=dict(bgcolor="#0F1C35", bordercolor=WCO_BORDER),
margin=dict(l=50,r=20,t=50,b=40), violingap=0.3,
)
return fig
def area_heatmap(df):
pivot = pd.crosstab(df["risk_area"], df["channel"])
for col in ["RED","YELLOW","GREEN"]:
if col not in pivot.columns: pivot[col]=0
pivot = pivot[["RED","YELLOW","GREEN"]]
fig = go.Figure(go.Heatmap(
z=pivot.values, x=pivot.columns, y=pivot.index,
colorscale=[[0,"#0B1220"],[0.5,"#003087"],[1,"#C8A951"]],
text=pivot.values, texttemplate="%{text}", textfont={"size":13},
hovertemplate="%{y}
Channel: %{x}
Bills: %{z}",
showscale=True,
colorbar=dict(tickfont=dict(color="#D0DCF0"),
title="Bills", titlefont=dict(color=WCO_GOLD)),
))
fig.update_layout(
paper_bgcolor="#070E1C", plot_bgcolor="#0B1220",
font=dict(family="IBM Plex Sans", color="#D0DCF0", size=11),
height=280,
title=dict(text="Risk Area × Channel Heatmap",
font=dict(color=WCO_GOLD, size=13, family="Playfair Display"), x=0.5),
margin=dict(l=165,r=30,t=50,b=40),
)
return fig
# ─────────────────────────────────────────────────────────────────────────────
def show():
inject_global_css()
page_header("🔄", "Self-Learning Simulation Engine",
"DATE EXPLOITATION · gATE EXPLORATION · 30-SECOND LIVE CANVAS ANIMATION")
# ── Controls ──────────────────────────────────────────────────
st.markdown('🎛️ Simulation Control Panel
',
unsafe_allow_html=True)
c1,c2,c3,c4 = st.columns(4)
with c1: n_bills = st.slider("📦 Declarations", 200,1000,1000,step=100)
with c2: bandwidth = st.slider("📡 Bandwidth (%)", 5,30,10,step=1)/100
with c3: exp_ratio = st.slider("🔍 Exploration ε (%)",1,30,10,step=1)/100
with c4: seed = st.slider("🎲 Seed",1,99,42,step=1)
st.markdown(f"""
Strategy: DATE {100-int(exp_ratio*100)}% + gATE {int(exp_ratio*100)}%
| Bandwidth: {int(bandwidth*100)}% → {int(n_bills*bandwidth)} bills inspected
| Facilitated: {n_bills-int(n_bills*bandwidth)} bills (GREEN)
| ⏱ Animation duration: exactly 30 seconds for full batch
""", unsafe_allow_html=True)
col1,col2,_ = st.columns([1.2,1,4])
with col1: run_btn = st.button("▶ Run Simulation", type="primary", use_container_width=True)
with col2: reset_btn = st.button("🔄 Reset All", use_container_width=True)
if reset_btn:
for k in ["sim_df","sim_weights","sim_efficiency"]:
st.session_state.pop(k,None)
st.rerun()
# ── Run ───────────────────────────────────────────────────────
if run_btn or "sim_df" in st.session_state:
if run_btn or "sim_df" not in st.session_state:
weights = st.session_state.get("rule_weights", get_default_weights())
prog = st.progress(0, text="⚙️ Generating declarations...")
df = generate_declarations(n_bills, seed=seed)
prog.progress(25, text="🧠 DATE scoring...")
df = compute_risk_scores(df, weights)
prog.progress(50, text="⚖️ Hybrid channel routing...")
df = assign_channels(df, bandwidth=bandwidth, exploration_ratio=exp_ratio)
prog.progress(75, text="👮 Inspection outcomes...")
df = simulate_inspection_outcomes(df, seed=seed)
prog.progress(90, text="📈 Weight update & efficiency...")
uw = compute_updated_weights(df, weights)
eff = compute_efficiency_metrics(df)
prog.progress(100, text="✅ Launching animation!")
st.session_state.sim_df = df
st.session_state.sim_weights = uw
st.session_state.sim_efficiency = eff
df = st.session_state.sim_df
eff = st.session_state.sim_efficiency
hybrid = eff.get("hybrid", {})
ch = df["channel"].value_counts()
detected = int((df["inspection_outcome"]=="FRAUD_DETECTED").sum())
revenue = float(df["detected_revenue"].sum())
eff_idx = float(hybrid.get("efficiency_index", 0.0))
channel_counts = {
"RED": int(ch.get("RED",0)),
"YELLOW": int(ch.get("YELLOW",0)),
"GREEN": int(ch.get("GREEN",0)),
}
# ── Legend ────────────────────────────────────────────────
st.markdown('🎬 Live RMS Canvas Animation
',
unsafe_allow_html=True)
st.markdown("""
📌 30-second real-time simulation.
Each icon = one customs declaration flowing through the RMS pipeline.
■ Red glow = Fraud detected |
■ Purple glow = gATE exploration pick |
■ Blue = Standard routing |
Fraud bills arc → Offence DB vault (right lane)
""", unsafe_allow_html=True)
# ── CANVAS ANIMATION ──────────────────────────────────────
html_src = build_animation_html(
n_bills=n_bills, bandwidth=bandwidth, exp_ratio=exp_ratio,
channel_counts=channel_counts, detected=detected,
revenue=revenue, efficiency_idx=eff_idx,
)
components.html(html_src, height=720, scrolling=False)
# ── Supporting charts ──────────────────────────────────────
st.markdown("
", unsafe_allow_html=True)
st.markdown('📊 Channel Analytics
',
unsafe_allow_html=True)
ca,cb = st.columns([3,2])
with ca: st.plotly_chart(area_heatmap(df), use_container_width=True)
with cb: st.plotly_chart(risk_score_distribution(df), use_container_width=True)
# ── Risk area cards ───────────────────────────────────────
st.markdown('🗂️ Risk Area Breakdown
',
unsafe_allow_html=True)
acols = st.columns(5)
for i,(aname,acfg) in enumerate(RISK_AREAS.items()):
a_df = df[df["risk_area"]==aname]
color = acfg["color"]
ch_a = a_df["channel"].value_counts()
fraud_a= (a_df["inspection_outcome"]=="FRAUD_DETECTED").sum()
exp_a = a_df["is_exploration"].sum()
with acols[i]:
st.markdown(f"""
{acfg['icon']} {aname}
🔴 RED: {ch_a.get('RED',0)}
🟡 YEL: {ch_a.get('YELLOW',0)}
🟢 GRN: {ch_a.get('GREEN',0)}
🚨 Fraud: {fraud_a}
🔍 Expl: {exp_a}
""", unsafe_allow_html=True)
st.markdown("""
✅ Simulation complete. Go to Page 4 for full tables,
offence DB growth, and weight-evolution analysis.
""", unsafe_allow_html=True)
else:
st.markdown("""
🎬
Ready to Simulate
Configure parameters above and click
▶ Run Simulation
A 30-second canvas animation will show all declarations flowing
through RED / YELLOW / GREEN channels in real-time.
""", unsafe_allow_html=True)