import streamlit as st import simpy import numpy as np import pandas as pd import plotly.graph_objects as go import json import requests import time from dataclasses import dataclass, field from typing import List, Optional st.set_page_config( page_title="Meridia TRS Simulator β€” WCO Aligned", page_icon="🌐", layout="wide", initial_sidebar_state="expanded" ) # ══════════════════════════════════════════════════════════════════════════════ # WCO COLOUR THEME (WCO brand: dark navy #003366, accent blue #0066CC, # gold/amber #F5A623, white #FFFFFF, light grey #F0F4F8) # ══════════════════════════════════════════════════════════════════════════════ st.markdown(""" """, unsafe_allow_html=True) # ══════════════════════════════════════════════════════════════════════════════ # COUNTRY BENCHMARK DATABASE (WCO Member data + WTO TFA commitments) # ══════════════════════════════════════════════════════════════════════════════ COUNTRY_PRESETS = { "Custom / Generic": { "ports": {"Sea": "Seaport", "Air": "Airport", "Land": "Land Border"}, "volumes": {"Sea": 50, "Air": 10, "Land": 25}, "baseline_art": {"Sea": 48, "Air": 24, "Land": 36}, "target_sea": 48, "target_air": 24, "target_land": 36, "wto_tfa_cat": "A", "region": "Global", "existing_sw": False, "existing_aeo": False, }, "India": { "ports": {"Sea": "JNPT Mumbai", "Air": "Delhi IGI Airport", "Land": "Attari/Petrapole ICP"}, "volumes": {"Sea": 60, "Air": 15, "Land": 30}, "baseline_art": {"Sea": 85, "Air": 44, "Land": 120}, "target_sea": 48, "target_air": 24, "target_land": 48, "wto_tfa_cat": "A", "region": "Asia-Pacific", "existing_sw": True, "existing_aeo": True, }, "Kenya": { "ports": {"Sea": "Port of Mombasa", "Air": "JKIA Nairobi", "Land": "Malaba Border"}, "volumes": {"Sea": 40, "Air": 8, "Land": 35}, "baseline_art": {"Sea": 96, "Air": 48, "Land": 168}, "target_sea": 72, "target_air": 48, "target_land": 72, "wto_tfa_cat": "B", "region": "Eastern/Southern Africa", "existing_sw": True, "existing_aeo": False, }, "Ghana": { "ports": {"Sea": "Tema Port", "Air": "Kotoka Int'l Airport", "Land": "Aflao Border"}, "volumes": {"Sea": 35, "Air": 6, "Land": 20}, "baseline_art": {"Sea": 120, "Air": 72, "Land": 144}, "target_sea": 96, "target_air": 48, "target_land": 96, "wto_tfa_cat": "C", "region": "West Africa", "existing_sw": False, "existing_aeo": False, }, "Vietnam": { "ports": {"Sea": "Cat Lai Port HCMC", "Air": "Tan Son Nhat Airport", "Land": "Lao Cai Border"}, "volumes": {"Sea": 70, "Air": 20, "Land": 40}, "baseline_art": {"Sea": 60, "Air": 30, "Land": 72}, "target_sea": 36, "target_air": 18, "target_land": 48, "wto_tfa_cat": "A", "region": "Asia-Pacific", "existing_sw": True, "existing_aeo": True, }, "Morocco": { "ports": {"Sea": "Port of Casablanca", "Air": "Mohammed V Airport", "Land": "Bab Sebta"}, "volumes": {"Sea": 45, "Air": 10, "Land": 25}, "baseline_art": {"Sea": 72, "Air": 36, "Land": 96}, "target_sea": 48, "target_air": 24, "target_land": 48, "wto_tfa_cat": "A", "region": "North Africa/Middle East", "existing_sw": True, "existing_aeo": True, }, "Colombia": { "ports": {"Sea": "Port of Cartagena", "Air": "El Dorado BogotΓ‘", "Land": "Ipiales Border"}, "volumes": {"Sea": 40, "Air": 12, "Land": 20}, "baseline_art": {"Sea": 84, "Air": 36, "Land": 120}, "target_sea": 48, "target_air": 24, "target_land": 72, "wto_tfa_cat": "A", "region": "Latin America/Caribbean", "existing_sw": True, "existing_aeo": True, }, } WCO_REGIONS = ["Global","Asia-Pacific","Eastern/Southern Africa","West Africa", "North Africa/Middle East","Latin America/Caribbean","Europe","Gulf Region"] # ══════════════════════════════════════════════════════════════════════════════ # OPENROUTER LLM β€” free tier fallback chain # ══════════════════════════════════════════════════════════════════════════════ MODELS_TO_TRY = [ ("qwen/qwen3-coder:free", "Qwen3 Coder"), ("qwen/qwen3-next-80b-a3b-instruct:free", "Qwen3 Next 80B"), ("openai/gpt-oss-120b:free", "GPT OSS 120B"), ("google/gemma-4-26b-a4b-it:free", "Gemma 4 26B"), ("nousresearch/hermes-3-llama-3.1-405b:free", "Hermes 3 Llama 405B"), ("deepseek/deepseek-r1:free", "DeepSeek R1"), ("google/gemini-2.0-flash-exp:free", "Gemini 2.0 Flash"), ("meta-llama/llama-3.1-8b-instruct:free", "Llama 3.1 8B"), ("mistralai/mistral-7b-instruct:free", "Mistral 7B"), ] def call_llm(prompt: str, api_key: str, system: str = "") -> tuple[str, str]: """Try each free model in order; return (text, model_name_used).""" headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "HTTP-Referer": "https://meridia-trs.hf.space", "X-Title": "Meridia WCO TRS Simulator", } messages = [] if system: messages.append({"role": "system", "content": system}) messages.append({"role": "user", "content": prompt}) for model_id, model_name in MODELS_TO_TRY: try: resp = requests.post( "https://openrouter.ai/api/v1/chat/completions", headers=headers, json={"model": model_id, "messages": messages, "max_tokens": 1200}, timeout=30, ) if resp.status_code == 200: data = resp.json() text = data["choices"][0]["message"]["content"].strip() if text and len(text) > 50: return text, model_name except Exception: continue return "⚠ Could not reach any free LLM model. Check your API key or try again.", "None" # ══════════════════════════════════════════════════════════════════════════════ # DATA MODEL β€” WCO TRS Guide v4 2025 Β§2.1.4 timestamps # ══════════════════════════════════════════════════════════════════════════════ @dataclass class BoE: shipment_id: str port_type: str filing_type: str pga_involved: bool aeo_status: str channel: str machine_release: bool = False t_arrival: float = 0.0 t_lodged: float = 0.0 t_assessed: float = 0.0 t_payment: float = 0.0 t_ooc: float = 0.0 @property def total_hours(self): return max(self.t_ooc - self.t_arrival, 0) @property def seg_prearr(self): return max(self.t_lodged - self.t_arrival, 0) @property def seg_customs(self): return max(self.t_assessed - self.t_lodged, 0) @property def seg_oga_duty(self): return max(self.t_payment - self.t_assessed, 0) @property def seg_logistics(self): return max(self.t_ooc - self.t_payment, 0) # ══════════════════════════════════════════════════════════════════════════════ # SIMULATION ENGINE # ══════════════════════════════════════════════════════════════════════════════ def run_simulation(params: dict, country_cfg: dict) -> List[BoE]: results: List[BoE] = [] rng = np.random.default_rng(42) env = simpy.Environment() vols = country_cfg.get("volumes", {"Sea":50,"Air":10,"Land":25}) base = country_cfg.get("baseline_art", {"Sea":48,"Air":24,"Land":36}) officers_sea = simpy.Resource(env, capacity=params["officers_sea"]) officers_air = simpy.Resource(env, capacity=params["officers_air"]) officers_land = simpy.Resource(env, capacity=params["officers_land"]) # Speed multiplier derived from baseline ART (normalised to 48h sea) PORT_CONFIG = { "Sea": {"volume": vols["Sea"], "resource": officers_sea, "speed": base["Sea"] / 48.0}, "Air": {"volume": vols["Air"], "resource": officers_air, "speed": base["Air"] / 24.0}, "Land": {"volume": vols["Land"], "resource": officers_land, "speed": base["Land"] / 48.0}, } counter = [0] def process_boe(env, port_type, cfg): counter[0] += 1 sid = f"{port_type[0]}-{counter[0]:04d}" is_advance = rng.random() < (params["advance_filing_pct"] / 100) is_aeo = rng.random() < (params["aeo_enrollment_pct"] / 100) is_pga = rng.random() < params.get("pga_probability", 0.35) aeo_tier = rng.choice(["T1","T2","T3"]) if is_aeo else "None" rms_thr = params["rms_facilitation_pct"] / 100 roll = rng.random() channel = "Green" if roll < rms_thr else ("Yellow" if roll < rms_thr+0.25 else "Red") boe = BoE(shipment_id=sid, port_type=port_type, filing_type="Advance" if is_advance else "Normal", pga_involved=is_pga, aeo_status=aeo_tier, channel=channel) boe.t_arrival = env.now # Seg A: Arrival β†’ Lodgement spd = cfg["speed"] if is_advance: yield env.timeout(rng.gamma(1.2, 0.8) * spd) else: yield env.timeout(rng.gamma(2, 12) * spd) boe.t_lodged = env.now # Seg B: Lodgement β†’ Assessment if channel == "Green": yield env.timeout(rng.gamma(1, 0.3) * spd) else: with cfg["resource"].request() as req: yield req if channel == "Yellow": d = rng.gamma(2, 3) * spd * (0.5 if is_aeo else 1.0) else: d = rng.gamma(3, 8) * spd * (0.6 if is_aeo else 1.0) yield env.timeout(d) boe.t_assessed = env.now # Seg C: Assessment β†’ Duty + OGA duty = 0 if (is_aeo and params["deferred_duty"]) else rng.gamma(2, 2) * spd if is_pga: oga = rng.gamma(2, 2 if params["pga_single_window"] else 8) * spd else: oga = 0 yield env.timeout(max(duty, oga)) boe.t_payment = env.now # Seg D: Payment β†’ OOC if channel == "Green" and params["auto_ooc"]: boe.machine_release = True yield env.timeout(0) elif is_aeo: yield env.timeout(rng.gamma(1.5, 0.8) * spd) else: yield env.timeout(rng.gamma(2, 1.5) * spd) boe.t_ooc = env.now results.append(boe) def port_gen(env, pt, cfg): for _ in range(cfg["volume"]): env.process(process_boe(env, pt, cfg)) yield env.timeout(rng.exponential(1.5)) for pt, cfg in PORT_CONFIG.items(): env.process(port_gen(env, pt, cfg)) env.run(until=800) return results # ══════════════════════════════════════════════════════════════════════════════ # PHASER.JS MAP (WCO colour theme: navy + white + blue) # ══════════════════════════════════════════════════════════════════════════════ def build_phaser_scene(results, sp, country_cfg): art = round(sp.get("avg_art",0),1) med = round(sp.get("median_art",0),1) fac = round(sp.get("green_pct",0),1) mach = round(sp.get("machine_pct",0),1) t48 = round(sp.get("target48_pct",0),1) aeo = round(sp.get("aeo_pct",0),1) ports_cfg = country_cfg.get("ports", {"Sea":"Seaport","Air":"Airport","Land":"Land Border"}) sea_label = ports_cfg.get("Sea","Seaport")[:16] air_label = ports_cfg.get("Air","Airport")[:16] land_label = ports_cfg.get("Land","Land Border")[:16] sea_n = len([r for r in results if r.port_type=="Sea"]) air_n = len([r for r in results if r.port_type=="Air"]) land_n = len([r for r in results if r.port_type=="Land"]) g_n = len([r for r in results if r.channel=="Green"]) y_n = len([r for r in results if r.channel=="Yellow"]) r_n = len([r for r in results if r.channel=="Red"]) dots = [] for r in results[:80]: s = "fast" if r.total_hours<3 else ("ok" if r.total_hours<24 else ("slow" if r.total_hours<48 else "late")) dots.append({"id":r.shipment_id,"port":r.port_type,"hours":round(r.total_hours,1), "status":s,"ch":r.channel,"machine":r.machine_release}) dots_j = json.dumps(dots) has_data = "true" if results else "false" art_col = "#1A8A4A" if art<24 else ("#D4750A" if art<48 else "#CC2200") t48_col = "#1A8A4A" if t48>80 else ("#D4750A" if t48>60 else "#CC2200") return f"""
WCO TRS β€” Avg Release Time
{art}h
Median: {med}h  |  Target: {sp.get('target_sea',48)}h
RMS Channels
G {g_n} Y {y_n} R {r_n}
{fac}% facilitated
Auto-OOC
{mach}%
Machine release
WTO TFA ≀{sp.get('target_sea',48)}h
{t48}%
Art.7.6.1 achievement
AEO Enrolled
{aeo}%
WCO SAFE Framework
{sea_label}{sea_n} {air_label}{air_n} {land_label}{land_n}
LEGEND ● <3h ● <24h ● <48h ● >48h
WCO TRS SIMULATOR
{'
🌐

CONFIGURE LEVERS & RUN SIMULATION

WCO TIME RELEASE STUDY SIMULATOR READY

' if not results else ''}
""" # ══════════════════════════════════════════════════════════════════════════════ # TRS CHART β€” WCO colours # ══════════════════════════════════════════════════════════════════════════════ def build_trs_chart(results, country_cfg, sp): ports = ["Sea","Air","Land","ALL PORTS"] pm = {"Sea":"Sea","Air":"Air","Land":"Land"} data = {p:{"A":[],"B":[],"C":[],"D":[]} for p in ports} for r in results: pk = pm[r.port_type] for s,v in [("A",r.seg_prearr),("B",r.seg_customs),("C",r.seg_oga_duty),("D",r.seg_logistics)]: data[pk][s].append(v); data["ALL PORTS"][s].append(v) avgs = {p:{s:np.mean(v) if v else 0 for s,v in sd.items()} for p,sd in data.items()} port_labels = [ country_cfg["ports"].get("Sea","Sea"), country_cfg["ports"].get("Air","Air"), country_cfg["ports"].get("Land","Land"), "ALL PORTS" ] segs = [ ("A","Seg A β€” Pre-arrival / Lodgement","#003366"), ("B","Seg B β€” Customs Assessment","#0066CC"), ("C","Seg C β€” OGA / Duty Payment","#F5A623"), ("D","Seg D β€” Post-clearance Logistics","#6B8BAE"), ] fig = go.Figure() for sid,sname,col in segs: fig.add_trace(go.Bar( name=sname, x=port_labels, y=[avgs[p][sid] for p in ports], marker_color=col, text=[f"{avgs[p][sid]:.1f}h" for p in ports], textposition="inside", textfont=dict(family="Source Code Pro, monospace",size=10,color="white"), )) t_sea = country_cfg.get("target_sea",48) t_air = country_cfg.get("target_air",24) fig.add_hline(y=t_sea, line_dash="dash", line_color="#CC2200", line_width=1.5, annotation_text=f"{t_sea}h β€” Sea/Land Target (WTO TFA)", annotation_font_color="#CC2200", annotation_font_size=9) if t_air != t_sea: fig.add_hline(y=t_air, line_dash="dot", line_color="#D4750A", line_width=1, annotation_text=f"{t_air}h β€” Air Target", annotation_font_color="#D4750A", annotation_font_size=9) fig.add_hline(y=3, line_dash="dashdot", line_color="#1A8A4A", line_width=1, annotation_text="3h β€” Jaigaon LCS Best Practice", annotation_font_color="#1A8A4A", annotation_font_size=9) # Baseline bars (ghost) base = country_cfg.get("baseline_art",{}) base_vals = [base.get("Sea",0),base.get("Air",0),base.get("Land",0), np.mean(list(base.values()))] fig.add_trace(go.Scatter( x=port_labels, y=base_vals, mode="markers", name="Baseline ART (before reform)", marker=dict(symbol="diamond",size=12,color="#CC2200", line=dict(color="white",width=1)), )) fig.update_layout( barmode="stack", plot_bgcolor="white", paper_bgcolor="#F0F4F8", font=dict(family="Source Sans 3, sans-serif",color="#003366",size=12), title=dict( text="WCO TRS β€” Release Time by Port Mode (Segments A–D)", font=dict(size=14,color="#003366",family="Source Sans 3"),x=0.5), xaxis=dict(gridcolor="#E8EFF7",linecolor="#C5D5E8",tickfont=dict(size=11)), yaxis=dict(gridcolor="#E8EFF7",linecolor="#C5D5E8", title=dict(text="Hours β€” Arrival to Physical Release", font=dict(color="#6B8BAE",size=11))), legend=dict(orientation="h",y=-0.28,font=dict(size=10),bgcolor="rgba(0,0,0,0)"), margin=dict(t=60,b=110,l=60,r=20),height=420, ) return fig def build_channel_chart(results): ports = ["Sea","Air","Land"] ch_d = {p:{"Green":0,"Yellow":0,"Red":0} for p in ports} for r in results: ch_d[r.port_type][r.channel]+=1 fig = go.Figure() for ch,col in [("Green","#1A8A4A"),("Yellow","#D4750A"),("Red","#CC2200")]: fig.add_trace(go.Bar( name=ch,x=ports,y=[ch_d[p][ch] for p in ports], marker_color=col,marker_opacity=0.85, text=[ch_d[p][ch] for p in ports],textposition="inside", textfont=dict(family="Source Code Pro",size=11,color="white"), )) fig.update_layout( barmode="stack",plot_bgcolor="white",paper_bgcolor="#F0F4F8", font=dict(family="Source Sans 3",color="#003366",size=12), title=dict(text="WCO RMS Channel Distribution by Port", font=dict(size=13,color="#003366"),x=0.5), xaxis=dict(gridcolor="#E8EFF7"), yaxis=dict(gridcolor="#E8EFF7", title=dict(text="BoE Count",font=dict(color="#6B8BAE",size=11))), legend=dict(orientation="h",y=-0.2,font=dict(size=10),bgcolor="rgba(0,0,0,0)"), margin=dict(t=50,b=80,l=50,r=20),height=300, ) return fig # ══════════════════════════════════════════════════════════════════════════════ # LLM ANALYSIS β€” builds a rich prompt from simulation results # ══════════════════════════════════════════════════════════════════════════════ def build_llm_prompt(results, sp, params, country_cfg): ports_cfg = country_cfg.get("ports",{}) base = country_cfg.get("baseline_art",{}) t_sea = country_cfg.get("target_sea",48) seg_means = {} for pt in ["Sea","Air","Land"]: sub = [r for r in results if r.port_type==pt] if sub: seg_means[pt] = { "A": round(np.mean([r.seg_prearr for r in sub]),1), "B": round(np.mean([r.seg_customs for r in sub]),1), "C": round(np.mean([r.seg_oga_duty for r in sub]),1), "D": round(np.mean([r.seg_logistics for r in sub]),1), "total": round(np.mean([r.total_hours for r in sub]),1), "baseline": base.get(pt,0), "port_name": ports_cfg.get(pt,pt), } biggest_seg = {} for pt,d in seg_means.items(): segs = {"Pre-arrival (Seg A)":d["A"],"Customs Assessment (Seg B)":d["B"], "OGA/Duty (Seg C)":d["C"],"Post-clearance (Seg D)":d["D"]} biggest_seg[pt] = max(segs, key=segs.get) policy_used = [] if params["advance_filing_pct"]>50: policy_used.append(f"Advance Filing ({params['advance_filing_pct']}%)") if params["rms_facilitation_pct"]>50: policy_used.append(f"RMS Facilitation ({params['rms_facilitation_pct']}%)") if params["aeo_enrollment_pct"]>30: policy_used.append(f"AEO Enrollment ({params['aeo_enrollment_pct']}%)") if params["pga_single_window"]: policy_used.append("PGA Single Window") if params["deferred_duty"]: policy_used.append("Deferred Duty") if params["auto_ooc"]: policy_used.append("Auto Out-of-Charge") prompt = f"""You are a WCO (World Customs Organization) trade facilitation expert and TRS analyst. A Customs administration has run a Time Release Study (TRS) simulation aligned with the WCO TRS Guide v4 2025. COUNTRY / PORT CONFIGURATION: - Country: {country_cfg.get('country_name','Generic Customs Administration')} - Region: {country_cfg.get('region','Global')} - WTO TFA Category: {country_cfg.get('wto_tfa_cat','A')} - Sea target: {t_sea}h | Air target: {country_cfg.get('target_air',24)}h SIMULATION RESULTS SUMMARY: - Total BoEs simulated: {len(results)} - Overall Mean ART: {sp.get('avg_art',0):.1f}h | Median: {sp.get('median_art',0):.1f}h - Within target (≀{t_sea}h): {sp.get('target48_pct',0):.0f}% - Green channel (facilitated): {sp.get('green_pct',0):.0f}% - Machine release (Auto-OOC): {sp.get('machine_pct',0):.0f}% - AEO enrolled: {sp.get('aeo_pct',0):.0f}% SEGMENT BREAKDOWN (Mean hours per port): """ for pt,d in seg_means.items(): improvement = round(d["baseline"]-d["total"],1) if d["baseline"]>0 else 0 prompt += f""" {pt} Port ({d['port_name']}): Baseline ART: {d['baseline']}h β†’ Simulated ART: {d['total']}h (improvement: {improvement}h) Seg A Pre-arrival: {d['A']}h | Seg B Customs: {d['B']}h | Seg C OGA/Duty: {d['C']}h | Seg D Logistics: {d['D']}h Biggest bottleneck: {biggest_seg[pt]} """ prompt += f""" POLICY LEVERS ACTIVE IN THIS SIMULATION: {', '.join(policy_used) if policy_used else 'None (baseline run)'} Please provide: 1. A 3-4 sentence EXECUTIVE SUMMARY of what the simulation shows, as you would write in a WCO TRS Final Report (Β§2.3.6). 2. BOTTLENECK ANALYSIS β€” for each port, identify the biggest time segment and explain why it matters. 3. TOP 3 WCO TOOL RECOMMENDATIONS β€” specific WCO instruments, conventions, or frameworks (e.g. Revised Kyoto Convention Standard 3.21, SAFE Framework AEO, Single Window Recommendation, TFA Article 7.6) that would address the identified bottlenecks. Be specific about which standard or instrument. 4. NEXT STEPS β€” a concrete 3-step action plan for the Customs administration. Keep the total response under 500 words. Use clear headings. Be specific and reference WCO instruments by name. """ return prompt SYSTEM_PROMPT = """You are a senior WCO (World Customs Organization) trade facilitation adviser with expertise in: - WCO TRS Guide (Version 4, 2025) methodology - WCO Revised Kyoto Convention (RKC) β€” General Annex Standards - WCO SAFE Framework of Standards - WTO Trade Facilitation Agreement (TFA) Article 7 - WCO Single Window Compendium - Risk Management Guidelines (RMS/CRA) - Authorized Economic Operator (AEO) programmes You give practical, evidence-based advice grounded in WCO instruments. Always cite specific WCO standards.""" # ══════════════════════════════════════════════════════════════════════════════ # INTRO PAGE COMPONENT # ══════════════════════════════════════════════════════════════════════════════ def render_intro(): st.markdown("""
🌐

WCO Time Release Study Simulator

Aligned with WCO TRS Guide Version 4, 2025 Β· WTO TFA Article 7.6.1 Β· SAFE Framework

""", unsafe_allow_html=True) col1, col2 = st.columns([3,2]) with col1: st.markdown("""
WHAT IS THIS?

This simulator allows any Customs administration to model the impact of trade facilitation policy reforms on cargo release times β€” before implementing them in the field. It is built on the WCO Time Release Study (TRS) methodology (Guide v4, 2025), the internationally recognised tool for measuring border clearance efficiency mandated by WTO TFA Article 7.6.1.

The simulator models three ports (Sea, Air, Land Border) and measures the four WCO TRS time segments: Seg A (Pre-arrival/Lodgement) β†’ Seg B (Customs Assessment) β†’ Seg C (OGA/Duty Payment) β†’ Seg D (Post-clearance/OOC Release).

""", unsafe_allow_html=True) st.markdown("""
HOW THE SIMULATION WORKS

The engine uses SimPy discrete-event simulation with Gamma probability distributions β€” the same statistical approach recommended in WCO TRS Β§2.3.1 to replicate the long-tail delay patterns seen in real customs data. Each Bill of Entry (BoE) is a state machine that progresses through WCO business process steps (Β§2.1.4 Appendix 1).

""", unsafe_allow_html=True) with col2: st.markdown("""
HOW TO USE THIS APP
  1. Select your country (or enter custom parameters)
  2. Set policy levers in the sidebar
  3. Click β–Ά RUN SIMULATION
  4. View the isometric port map with live cargo status
  5. Analyse the TRS stacked bar chart (Segments A–D)
  6. Get AI-powered analysis with WCO tool recommendations
  7. Download the Audit Ledger CSV for your records
WHO IS THIS FOR?
""", unsafe_allow_html=True) # WCO instrument reference with st.expander("πŸ“š WCO Instruments Referenced in This Simulator"): c1,c2,c3 = st.columns(3) with c1: st.markdown(""" **WCO TRS Guide v4 2025** - Β§2.1.4 Business process model - Β§2.1.6 Sampling methodology - Β§2.3.1 Data analysis (mean/median) - Β§2.3.3 Data visualisation - Β§2.3.6 Final report format """) with c2: st.markdown(""" **WCO Revised Kyoto Convention** - Standard 3.21 β€” Advance lodgement - Standard 6.2 β€” Risk management - Standard 7.2 β€” AEO benefits - Specific Annex J β€” AEO """) with c3: st.markdown(""" **WTO TFA & SAFE Framework** - TFA Art. 7.6.1 β€” ART publication - TFA Art. 7.4 β€” Risk management - SAFE Pillar 2 β€” AEO - WCO Single Window Compendium """) # ══════════════════════════════════════════════════════════════════════════════ # MAIN APP # ══════════════════════════════════════════════════════════════════════════════ def main(): # ── Header banner ─────────────────────────────────────────────────────── st.markdown("""

🌐 Meridia TRS Simulator

WORLD CUSTOMS ORGANIZATION Β· TIME RELEASE STUDY Β· GUIDE v4 2025

WTO TFA ART.7.6.1
Compliant simulation methodology
""", unsafe_allow_html=True) # ── Sidebar ────────────────────────────────────────────────────────────── with st.sidebar: st.markdown("## 🌐 WCO TRS Simulator") st.markdown("

POLICY PARAMETERS

", unsafe_allow_html=True) # Country selector country_name = st.selectbox("Country / Administration", list(COUNTRY_PRESETS.keys()), index=0) country_cfg = COUNTRY_PRESETS[country_name].copy() country_cfg["country_name"] = country_name st.markdown("---") st.markdown("**Pre-arrival & Risk**") advance_pct = st.slider("Advance Filing %", 0,100,40, help="WCO RKC Standard 3.21 β€” pre-arrival declaration") rms_pct = st.slider("RMS Facilitation %", 0,100,50, help="WCO RMS: sets Green channel probability") aeo_pct = st.slider("AEO Enrollment %", 0,100,30, help="WCO SAFE Framework trusted trader programme") st.markdown("**System Enablers**") pga_sw = st.toggle("PGA Single Window", value=country_cfg.get("existing_sw",False)) deferred = st.toggle("Deferred Duty (AEO)", value=False) auto_ooc = st.toggle("Auto Out-of-Charge", value=False) st.markdown("**Officer Capacity**") o_sea = st.slider("Sea Officers", 1,20,8) o_air = st.slider("Air Officers", 1,10,4) o_land = st.slider("Land Officers", 1,15,6) # Advanced β€” collapsed with st.expander("βš™ Advanced Country Config (Optional)"): st.markdown("*Override country defaults below*") st.markdown("**Port Names**") for pt in ["Sea","Air","Land"]: country_cfg["ports"][pt] = st.text_input( f"{pt} Port Name", country_cfg["ports"].get(pt, pt), key=f"port_{pt}") st.markdown("**Benchmark Targets (hours)**") country_cfg["target_sea"] = st.number_input("Sea/Land target (h)", 12,240, int(country_cfg.get("target_sea",48)), key="ts") country_cfg["target_air"] = st.number_input("Air target (h)", 6,120, int(country_cfg.get("target_air",24)), key="ta") st.markdown("**Baseline ART (before reforms)**") for pt in ["Sea","Air","Land"]: country_cfg["baseline_art"][pt] = st.number_input( f"{pt} baseline ART (h)", 0, 500, int(country_cfg["baseline_art"].get(pt,48)), key=f"base_{pt}") st.markdown("**Port Volumes (BoEs per cycle)**") for pt in ["Sea","Air","Land"]: country_cfg["volumes"][pt] = st.number_input( f"{pt} volume", 1, 200, int(country_cfg["volumes"].get(pt,50)), key=f"vol_{pt}") country_cfg["region"] = st.selectbox("WCO Region", WCO_REGIONS, index=WCO_REGIONS.index(country_cfg.get("region","Global"))) country_cfg["wto_tfa_cat"] = st.selectbox("WTO TFA Category", ["A","B","C"], index=["A","B","C"].index(country_cfg.get("wto_tfa_cat","A"))) params_extra = { "pga_probability": st.slider("OGA involvement %",0,100,35,key="pga_prob") / 100 } st.markdown("---") # OpenRouter API key with st.expander("πŸ€– AI Analysis (OpenRouter API Key)"): api_key = st.text_input("OpenRouter API Key", value=st.session_state.get("api_key",""), type="password", help="Get free key at openrouter.ai") if api_key: st.session_state["api_key"] = api_key st.caption("Uses free-tier models. No cost to you.") st.markdown("---") run_btn = st.button("β–Ά RUN SIMULATION", use_container_width=True) reset_btn = st.button("β†Ί RESET", use_container_width=True) # ── Session state ──────────────────────────────────────────────────────── if "results" not in st.session_state: st.session_state.results = [] if "sim_params" not in st.session_state: st.session_state.sim_params = {} if "country_cfg" not in st.session_state: st.session_state.country_cfg = country_cfg if "llm_result" not in st.session_state: st.session_state.llm_result = "" if "llm_model" not in st.session_state: st.session_state.llm_model = "" if reset_btn: st.session_state.results = []; st.session_state.sim_params = {} st.session_state.llm_result = ""; st.session_state.llm_model = "" if run_btn: params = dict( advance_filing_pct=advance_pct, rms_facilitation_pct=rms_pct, aeo_enrollment_pct=aeo_pct, pga_single_window=pga_sw, deferred_duty=deferred, auto_ooc=auto_ooc, officers_sea=o_sea, officers_air=o_air, officers_land=o_land, pga_probability=locals().get("params_extra",{}).get("pga_probability",0.35), ) with st.spinner(f"Running WCO TRS simulation for {country_name}..."): results = run_simulation(params, country_cfg) arts = [r.total_hours for r in results] t_sea = country_cfg.get("target_sea",48) sp = dict( avg_art = float(np.mean(arts)) if arts else 0, median_art = float(np.median(arts)) if arts else 0, green_pct = len([r for r in results if r.channel=="Green"])/len(results)*100 if results else 0, aeo_pct = len([r for r in results if r.aeo_status!="None"])/len(results)*100 if results else 0, machine_pct = len([r for r in results if r.machine_release])/len(results)*100 if results else 0, target48_pct = len([a for a in arts if a<=t_sea])/len(arts)*100 if arts else 0, target24_pct = len([a for a in arts if a<=24])/len(arts)*100 if arts else 0, target_sea = t_sea, ) st.session_state.results = results st.session_state.sim_params = sp st.session_state.country_cfg = country_cfg st.session_state.llm_result = "" st.session_state.sim_params["params"] = params results = st.session_state.results sim_params = st.session_state.sim_params ccfg = st.session_state.get("country_cfg", country_cfg) # ── Tabs ───────────────────────────────────────────────────────────────── tab0,tab1,tab2,tab3,tab4,tab5 = st.tabs([ "πŸ“– ABOUT", "πŸ—Ί PORT VIEW", "πŸ“Š TRS REPORT", "πŸ“ˆ RMS CHANNELS", "πŸ€– AI ANALYSIS", "πŸ“‹ AUDIT LEDGER", ]) with tab0: render_intro() with tab1: st.components.v1.html(build_phaser_scene(results, sim_params, ccfg), height=472, scrolling=False) if results: st.markdown("---") c1,c2,c3,c4,c5 = st.columns(5) c1.metric("Avg Release Time", f"{sim_params['avg_art']:.1f}h", f"Median {sim_params['median_art']:.1f}h") c2.metric("Green Channel", f"{sim_params['green_pct']:.0f}%","RMS Facilitated") c3.metric("Machine Release", f"{sim_params['machine_pct']:.0f}%","Auto-OOC") c4.metric(f"Within {sim_params.get('target_sea',48)}h Target", f"{sim_params['target48_pct']:.0f}%","WTO TFA Art.7.6") c5.metric("AEO Enrolled", f"{sim_params['aeo_pct']:.0f}%","SAFE Framework") with tab2: if not results: st.info("Run the simulation to generate the WCO TRS chart.") else: st.plotly_chart(build_trs_chart(results, ccfg, sim_params), use_container_width=True) rows = [] for pt in ["Sea","Air","Land"]: sub = [r for r in results if r.port_type==pt] if not sub: continue rows.append({ "Port": ccfg["ports"].get(pt,pt), "Mode": pt, "n": len(sub), "Seg A (h)": round(np.mean([r.seg_prearr for r in sub]),2), "Seg B (h)": round(np.mean([r.seg_customs for r in sub]),2), "Seg C (h)": round(np.mean([r.seg_oga_duty for r in sub]),2), "Seg D (h)": round(np.mean([r.seg_logistics for r in sub]),2), "Mean ART": round(np.mean([r.total_hours for r in sub]),2), "Median ART":round(np.median([r.total_hours for r in sub]),2), "Baseline": ccfg["baseline_art"].get(pt,"-"), }) st.dataframe(pd.DataFrame(rows), use_container_width=True, hide_index=True) art = sim_params["avg_art"] t = sim_params.get("target_sea",48) st.markdown("#### Policy Insights") ca,cb = st.columns(2) with ca: if art<3: st.success("πŸ† Jaigaon LCS level β€” ART < 3h. World-class.") elif art60 and rms_pct>60: st.success("βœ… Advance Filing + RMS >60% β€” optimal WCO pathway active.") else: st.info("πŸ’‘ Raise both Advance Filing and RMS above 60% for Jaigaon-level ART.") if not pga_sw: st.warning("⚠ PGA without Single Window causes Segment C bottleneck.") with tab3: if not results: st.info("Run simulation to see RMS channel data.") else: st.plotly_chart(build_channel_chart(results), use_container_width=True) ch_rows = [] for ch in ["Green","Yellow","Red"]: sub = [r for r in results if r.channel==ch] if sub: ch_rows.append({ "Channel":ch,"Count":len(sub), "Mean ART (h)": round(np.mean([r.total_hours for r in sub]),2), "Median ART (h)":round(np.median([r.total_hours for r in sub]),2), "% Within target":round(len([r for r in sub if r.total_hours<=sim_params.get("target_sea",48)])/len(sub)*100,1), }) st.dataframe(pd.DataFrame(ch_rows), use_container_width=True, hide_index=True) with tab4: st.markdown("### πŸ€– AI-Powered WCO TRS Analysis") st.markdown( "The AI adviser analyses your simulation results and recommends specific " "WCO instruments, conventions, and standards to address your bottlenecks. " "Uses free LLM models via OpenRouter β€” no cost." ) if not results: st.info("Run the simulation first, then come back here for AI analysis.") else: api_key_val = st.session_state.get("api_key","") if not api_key_val: st.warning("Enter your OpenRouter API key in the sidebar (free at openrouter.ai) to enable AI analysis.") else: col_btn, col_info = st.columns([2,3]) with col_btn: if st.button("πŸ€– Generate WCO Analysis", use_container_width=True): prompt = build_llm_prompt( results, sim_params, sim_params.get("params",{}), ccfg ) with st.spinner("Consulting WCO trade facilitation AI adviser..."): text, model_used = call_llm(prompt, api_key_val, SYSTEM_PROMPT) st.session_state.llm_result = text st.session_state.llm_model = model_used with col_info: st.caption("AI tries 9 free models in order. Typical response: 15–30 seconds.") if st.session_state.llm_result: st.markdown(f"""
WCO Trade Facilitation Analysis via {st.session_state.llm_model}
{st.session_state.llm_result}
""", unsafe_allow_html=True) # Download analysis st.download_button( "⬇ Download AI Analysis (TXT)", data=st.session_state.llm_result.encode("utf-8"), file_name=f"WCO_TRS_Analysis_{ccfg.get('country_name','Generic')}.txt", mime="text/plain", ) with tab5: if not results: st.info("Run simulation to populate the audit ledger.") else: df = pd.DataFrame([{ "Shipment_ID": r.shipment_id, "Port_Mode": r.port_type, "Port_Name": ccfg["ports"].get(r.port_type, r.port_type), "Filing_Type": r.filing_type, "RMS_Channel": r.channel, "OGA_Involved": r.pga_involved, "AEO_Status": r.aeo_status, "Machine_Release": r.machine_release, "Seg_A_PreArr_h": round(r.seg_prearr, 2), "Seg_B_Customs_h": round(r.seg_customs, 2), "Seg_C_OGA_Duty_h": round(r.seg_oga_duty, 2), "Seg_D_Logistics_h": round(r.seg_logistics,2), "Total_Hours": round(r.total_hours, 2), f"Within_{sim_params.get('target_sea',48)}h": r.total_hours<=sim_params.get("target_sea",48), "Within_24h": r.total_hours<=24, } for r in results]) st.dataframe(df, use_container_width=True, height=380) st.download_button( "⬇ Download Meridia_TRS_Ledger.csv (WCO Format)", data=df.to_csv(index=False).encode("utf-8"), file_name=f"WCO_TRS_Ledger_{ccfg.get('country_name','Generic').replace(' ','_')}.csv", mime="text/csv", use_container_width=True, ) st.markdown("""
WCO TRS METHODOLOGY NOTE — Segments per §2.1.4: A=Arrival→Lodgement (T0→T1) · B=Customs Assessment (T1→T2) · C=OGA/Duty Payment (T2→T3) · D=Post-clearance Logistics (T3→T4). Mean & Median both reported per §2.3.1. RMS: Green=auto · Yellow=documentary · Red=physical. WTO TFA Art.7.6.1 benchmarks apply. Data suitable for WCO TRS software import.
""", unsafe_allow_html=True) if __name__ == "__main__": main()