""" page2_risk_register.py — Risk Register, 3×3 Matrix, Rules, Weights """ import streamlit as st 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, WCO_GREY_BG) from simulation_engine import RISK_AREAS, get_default_weights # ── Static risk matrix data ────────────────────────────────────────────────── RISK_MATRIX_DATA = { "Drugs & Narcotics": {"likelihood": 3, "impact": 3, "priority": 1, "color": "#C8102E"}, "Revenue Leakage": {"likelihood": 3, "impact": 2, "priority": 2, "color": "#F5A800"}, "IPR Enforcement": {"likelihood": 2, "impact": 2, "priority": 3, "color": "#9B59B6"}, "Environmental/Plastic Waste": {"likelihood": 2, "impact": 3, "priority": 4, "color": "#00843D"}, "Wildlife Smuggling": {"likelihood": 1, "impact": 3, "priority": 5, "color": "#E67E22"}, } RISK_PRIORITY_DATA = [ { "Priority": 1, "Risk Area": "Drugs & Narcotics", "Likelihood": "High (3)", "Impact": "High (3)", "Risk Level": "CRITICAL", "Strategy": "Exploit + Explore", "Annual Target": "Zero tolerance – 100% risk-score flagging", "WCO Reference": "Compendium Vol.1 Ch.3", }, { "Priority": 2, "Risk Area": "Revenue Leakage", "Likelihood": "High (3)", "Impact": "Medium (2)", "Risk Level": "HIGH", "Strategy": "DATE Exploitation", "Annual Target": "≥85% recovery rate on flagged shipments", "WCO Reference": "Compendium Vol.2 Ch.6", }, { "Priority": 3, "Risk Area": "IPR Enforcement", "Likelihood": "Medium (2)", "Impact": "Medium (2)", "Risk Level": "MEDIUM", "Strategy": "Hybrid (DATE 80/gATE 20)", "Annual Target": "500 seizures/year, 30% uplift YoY", "WCO Reference": "TRIPS/WCO IPR Guidelines", }, { "Priority": 4, "Risk Area": "Environmental/Plastic Waste", "Likelihood": "Medium (2)", "Impact": "High (3)", "Risk Level": "HIGH", "Strategy": "gATE Exploration", "Annual Target": "Basel Convention compliance 95%", "WCO Reference": "Compendium Vol.3 Ch.9", }, { "Priority": 5, "Risk Area": "Wildlife Smuggling", "Likelihood": "Low (1)", "Impact": "High (3)", "Risk Level": "MEDIUM-HIGH", "Strategy": "Hybrid (DATE 70/gATE 30)", "Annual Target": "CITES compliance 100%; 200 cases/year", "WCO Reference": "CITES/WCO Wildlife Guidelines", }, ] RISK_COLORS_LEVEL = { "CRITICAL": "#C8102E", "HIGH": "#F5A800", "MEDIUM-HIGH": "#E67E22", "MEDIUM": "#9B59B6", "LOW": "#00843D", } def risk_matrix_chart(): """3×3 WCO Risk Matrix with plotted risks.""" fig = go.Figure() # Background zones zone_colors = [ # (x0,y0,x1,y1, fill) (0.5, 2.5, 3.5, 3.5, "rgba(200,16,46,0.18)"), # High-High (0.5, 1.5, 3.5, 2.5, "rgba(245,168,0,0.13)"), # Medium row (0.5, 0.5, 3.5, 1.5, "rgba(0,132,61,0.12)"), # Low row ] for x0, y0, x1, y1, fill in zone_colors: fig.add_shape(type="rect", x0=x0, y0=y0, x1=x1, y1=y1, fillcolor=fill, line_width=0, layer="below") # Grid lines for v in [1.5, 2.5]: fig.add_shape(type="line", x0=0.5, y0=v, x1=3.5, y1=v, line=dict(color="#1E3A6E", width=1)) fig.add_shape(type="line", x0=v, y0=0.5, x1=v, y1=3.5, line=dict(color="#1E3A6E", width=1)) # Plot risks for name, d in RISK_MATRIX_DATA.items(): short = name.replace("Environmental/Plastic Waste","Env/Plastic").replace("Revenue Leakage","Revenue") fig.add_trace(go.Scatter( x=[d["likelihood"]], y=[d["impact"]], mode="markers+text", marker=dict(size=38, color=d["color"], opacity=0.88, line=dict(color="#C8A951", width=1.5)), text=[f"P{d['priority']}"], textfont=dict(color="white", size=13, family="Georgia"), textposition="middle center", name=short, hovertemplate=(f"{name}
Likelihood: {d['likelihood']}" f"
Impact: {d['impact']}
Priority: {d['priority']}"), )) fig.update_layout( paper_bgcolor="#070E1C", plot_bgcolor="#0B1220", font=dict(family="IBM Plex Sans", color="#D0DCF0"), height=420, margin=dict(l=60, r=20, t=50, b=60), xaxis=dict( title="Likelihood →", range=[0.5, 3.5], tickvals=[1,2,3], ticktext=["Low","Medium","High"], gridcolor="#1E3A6E", zerolinecolor="#1E3A6E", title_font_color=WCO_GOLD, ), yaxis=dict( title="← Impact", range=[0.5, 3.5], tickvals=[1,2,3], ticktext=["Low","Medium","High"], gridcolor="#1E3A6E", zerolinecolor="#1E3A6E", title_font_color=WCO_GOLD, ), title=dict(text="WCO 3×3 Risk Matrix — Customs Risk Areas", font=dict(color=WCO_GOLD, size=15, family="Playfair Display, Georgia"), x=0.5), showlegend=True, legend=dict(bgcolor="#0F1C35", bordercolor=WCO_BORDER, borderwidth=1, font=dict(size=11), x=1.01, y=0.98), ) return fig def rules_weight_editor(default_weights: dict) -> dict: """Render editable weight sliders per rule and return current weights.""" st.markdown('
⚙️ Risk Rules Configuration & Annual Weights
', unsafe_allow_html=True) st.markdown("""
Adjust annual weights for each risk rule. Weights are automatically recalibrated after each simulation based on detection efficiency (self-learning feedback loop).
""", unsafe_allow_html=True) updated = {} for area_name, area_cfg in RISK_AREAS.items(): icon = area_cfg["icon"] color = area_cfg["color"] with st.expander(f"{icon} {area_name}", expanded=False): sub_label = "" if "sub_areas" in area_cfg: sub_label = f" *(Sub-areas: {', '.join(area_cfg['sub_areas'])})*" st.markdown(f"{sub_label}", unsafe_allow_html=True) rule_data = [] for rule in area_cfg["rules"]: rid = rule["id"] rname = rule["name"] sub = rule.get("sub", "") col1, col2, col3 = st.columns([1, 3, 2]) with col1: st.markdown(f"{rid}", unsafe_allow_html=True) if sub: st.caption(sub) with col2: st.markdown(f"{rname}", unsafe_allow_html=True) with col3: w = st.slider( f"Weight [{rid}]", 0.05, 0.60, float(default_weights.get(rid, rule["weight"])), step=0.01, key=f"w_{rid}", label_visibility="collapsed", ) updated[rid] = w rule_data.append((rid, rname, sub, w)) st.markdown("
", unsafe_allow_html=True) # Mini weight chart if rule_data: ids = [r[0] for r in rule_data] names = [r[1][:30] for r in rule_data] vals = [r[3] for r in rule_data] mini = go.Figure(go.Bar( x=ids, y=vals, marker_color=color, opacity=0.85, text=[f"{v:.2f}" for v in vals], textposition="outside", textfont=dict(color="#D0DCF0", size=10), hovertext=names, hoverinfo="text+y", )) mini.update_layout( paper_bgcolor="transparent", plot_bgcolor="#0B1220", height=180, margin=dict(l=10, r=10, t=10, b=30), font=dict(color="#D0DCF0", size=10), xaxis=dict(gridcolor="#1E3A6E"), yaxis=dict(gridcolor="#1E3A6E", range=[0, 0.65]), showlegend=False, ) st.plotly_chart(mini, use_container_width=True) return updated def show(): inject_global_css() page_header("📋", "Risk Register & Risk Matrix", "WCO RISK MANAGEMENT COMPENDIUM · 5 RISK AREAS · ANNUAL WEIGHT CONFIGURATION") metric_row([ ("5", "Risk Areas", WCO_GOLD), ("26", "Risk Rules Total", WCO_BLUE), ("3×3", "Risk Matrix", WCO_RED), ("Annual","Weight Cycle", WCO_GREEN), ("WCO", "Compendium Ref.", WCO_GOLD), ]) # ── Risk Matrix ─────────────────────────────────────────────── st.markdown('
📊 3×3 Risk Priority Matrix
', unsafe_allow_html=True) c_mat, c_legend = st.columns([2, 1]) with c_mat: st.plotly_chart(risk_matrix_chart(), use_container_width=True) with c_legend: st.markdown("""

Matrix Legend

🟥 CRITICAL — Immediate action, maximum resources
🟧 HIGH — Priority 2, enhanced monitoring
🟪 MEDIUM-HIGH — Balanced exploit/explore
🟩 MEDIUM — Exploration-led discovery
P1 = Drugs & Narcotics (High-High)
P2 = Revenue Leakage (High-Medium)
P3 = IPR Enforcement (Med-Med)
P4 = Environmental (Med-High)
P5 = Wildlife (Low-High)
""", unsafe_allow_html=True) # ── Priority Table ──────────────────────────────────────────── st.markdown('
🗂️ Risk Prioritisation Table
', unsafe_allow_html=True) df_prio = pd.DataFrame(RISK_PRIORITY_DATA) rows_html = "" for _, row in df_prio.iterrows(): level_col = RISK_COLORS_LEVEL.get(row["Risk Level"], WCO_MUTED) area_col = RISK_MATRIX_DATA.get(row["Risk Area"], {}).get("color", WCO_MUTED) rows_html += f""" P{row['Priority']} {row['Risk Area']} {row['Likelihood']} {row['Impact']} {row['Risk Level']} {row['Strategy']} {row['Annual Target']} {row['WCO Reference']} """ st.markdown(f""" {rows_html}
PriorityRisk AreaLikelihoodImpact Risk LevelSelection StrategyAnnual TargetWCO Reference
""", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) # ── Rules & Weights Editor ──────────────────────────────────── if "rule_weights" not in st.session_state: st.session_state.rule_weights = get_default_weights() updated_weights = rules_weight_editor(st.session_state.rule_weights) if st.button("💾 Save Weights to Session", type="primary"): st.session_state.rule_weights = updated_weights st.success("✅ Weights saved — navigate to Page 3 to run simulation with updated weights.") # ── Risk area summary cards ─────────────────────────────────── st.markdown('
📌 Risk Area Summary Cards
', unsafe_allow_html=True) cols = st.columns(5) for i, (area_name, area_cfg) in enumerate(RISK_AREAS.items()): color = area_cfg["color"] icon = area_cfg["icon"] n_rules = len(area_cfg["rules"]) illicit = area_cfg["base_illicit_rate"] sub_txt = ", ".join(area_cfg.get("sub_areas", [])) with cols[i]: st.markdown(f"""
{icon}
{area_name}
{'
'+sub_txt+'
' if sub_txt else ''}
{n_rules} Risk Rules
Base Rate: {illicit*100:.0f}%
""", unsafe_allow_html=True)