import time import random from datetime import datetime import streamlit as st from simulator import generate_accounts, generate_transaction, score_transaction # ─── Page config ────────────────────────────────────────────────────────────── st.set_page_config( page_title="Fraud Analytics — Enterprise Monitor", page_icon="🏛️", layout="wide", initial_sidebar_state="expanded", ) # ─── Styling ────────────────────────────────────────────────────────────────── st.markdown(""" """, unsafe_allow_html=True) # ─── Session state init ─────────────────────────────────────────────────────── def _init(): st.session_state.setdefault("running", False) st.session_state.setdefault("accounts", []) st.session_state.setdefault("feed", []) # สะสมจากบนลงล่าง st.session_state.setdefault("alerts", []) # สะสมจากบนลงล่าง st.session_state.setdefault("step", 1) st.session_state.setdefault("tx_counter", 1) st.session_state.setdefault("total_tx", 0) st.session_state.setdefault("total_block", 0) st.session_state.setdefault("total_alert", 0) st.session_state.setdefault("total_pass", 0) st.session_state.setdefault("amount_risk", 0.0) _init() # ─── Sidebar (Configuration & Hidden Controls) ──────────────────────────────── with st.sidebar: st.markdown("### ⚙️ Engine Settings") st.caption("Adjust variables for the transaction generator.") st.divider() speed = st.slider("Generator Delay (seconds)", 0.3, 3.0, 1.0, 0.1) fraud_pct = st.slider("Mule Account Ratio (%)", 5, 40, 12, 1) n_accs = st.slider("Accounts Network Size", 3, 8, 5, 1) st.divider() st.markdown("**System Actions**") reset_btn = st.button("↺ Reset Simulation Data", use_container_width=True) if reset_btn: for k in ["running","accounts","feed","alerts","step","tx_counter", "total_tx","total_block","total_alert","total_pass","amount_risk"]: st.session_state.pop(k, None) _init() st.rerun() # GitHub Link ตรึงไว้ล่างสุดของ Sidebar st.markdown("""

Want to learn more?

Read Technical Docs
""", unsafe_allow_html=True) # ─── Main Header & Dynamic Bank Controls ────────────────────────────────────── title_col, ctrl_col = st.columns([2, 1]) with title_col: st.markdown("## 🏛️ Fraud Operations — Live Matrix Dashboard") status_txt = "🟢 MONITORING ACTIVE" if st.session_state.running else "⏸️ MONITORING PAUSED" st.caption(f"{status_txt} · Model Ensemble Framework A × B · Threshold Rules Enabled") with ctrl_col: st.markdown("
", unsafe_allow_html=True) c1, c2 = st.columns(2) start_btn = c1.button("▶ Start / Resume", use_container_width=True, type="primary", disabled=st.session_state.running) pause_btn = c2.button("⏸ Pause", use_container_width=True, disabled=not st.session_state.running) if start_btn: if not st.session_state.accounts: seed = random.randint(0, 99999) st.session_state.accounts = generate_accounts(n=n_accs, seed=seed) st.session_state.running = True st.rerun() if pause_btn: st.session_state.running = False st.rerun() st.divider() # ─── Top Split Grid: Financial Metrics & Accounts Grid ──────────────────────── metric_col, watchlist_col = st.columns([6, 4], gap="medium") with metric_col: m1, m2, m3, m4 = st.columns(4) m1.metric("Processed Transactions", f"{st.session_state.total_tx} tx") m2.metric("Critical Blocked", st.session_state.total_block) m3.metric("OTP Challenges", st.session_state.total_alert) m4.metric("Funds at Risk Exposure", f"฿{st.session_state.amount_risk:,.0f}") with watchlist_col: st.markdown("

🎯 Account Audit Matrix

", unsafe_allow_html=True) if st.session_state.accounts: acc_table = """""" for acc in st.session_state.accounts: icon, status_color = { "normal": ("🟢 Normal", "background:#dcfce7;color:#16a34a"), "alert": ("🟡 Warning", "background:#fef3c7;color:#d97706"), "blocked": ("🔴 Blocked", "background:#fee2e2;color:#dc2626") }.get(acc["status"], ("⚪ Unknown", "")) risk_badge = f"🔥 {acc['risk_level'].upper()}" if acc['risk_level'] == 'high' else acc['risk_level'].upper() acc_table += f"""""" acc_table += "
Account IDBaseline RiskOperational Status
{acc['account_id']} {risk_badge} {icon}
" st.markdown(acc_table, unsafe_allow_html=True) else: st.caption("No accounts mapped. Click 'Start / Resume' to link core systems.") st.markdown("
", unsafe_allow_html=True) # ─── Bottom Split Grid: Fixed-Height Containers ─────────────────────────────── feed_col, alert_col = st.columns([55, 45], gap="medium") with feed_col: st.markdown("#### ⏱️ Real-Time Transaction Ledger") st.caption("Global financial traffic logging (Ordered: $001 \\rightarrow 002 \\rightarrow 003$)") feed_box = st.container(height=480) with alert_col: st.markdown("#### 🚨 Security Incident Alerts Queue") st.caption("Decisions requiring compliance review or automated intercept") alert_box = st.container(height=480) # ─── Function to build table HTML (Append-friendly) ────────────────────────── def build_feed_table(feed_list) -> str: html_table = """""" for f_tx in feed_list[-40:]: style_cls = "text-block" if "Block" in f_tx["decision"] else "text-alert" if "Alert" in f_tx["decision"] else "text-pass" html_table += f"""""" html_table += "
TimeTx IDRouting SequenceAmountScoreAction
{f_tx.get('tx_time', '00:00:00')} {f_tx['tx_id']} {f_tx['account_id']} ➔ {f_tx['dest_id']} ฿{f_tx['amount']:,.0f} {f_tx['final_score']:.2f} {f_tx['decision'].upper()}
" return html_table # ─── Simulation Core Loop & Rendering Logic ────────────────────────────────── if st.session_state.running and st.session_state.accounts: # 📌 สร้าง Placeholder ไว้ "นอกลูป" แค่ครั้งเดียว เพื่อล็อคหัวตารางให้ไม่วาดซ้ำซาก with feed_box: feed_placeholder = st.empty() with alert_box: alert_placeholder = st.empty() while st.session_state.running: # Pick account & process transaction account = random.choice(st.session_state.accounts) raw_tx = generate_transaction(account, step=st.session_state.step, tx_counter=st.session_state.tx_counter) tx = score_transaction(raw_tx) # ฝังเวลาปัจจุบันของระบบธนาคารระดับวินาที tx['tx_time'] = datetime.now().strftime("%H:%M:%S") # Handle status state mutations d = tx["decision"] if "Block" in d: account["status"] = "blocked" elif "Alert" in d: account["status"] = "alert" # สะสมข้อมูลธุรกรรมต่อท้ายลิสต์เดิมลงไปข้างล่าง st.session_state.feed = st.session_state.feed + [tx] st.session_state.total_tx += 1 st.session_state.tx_counter += 1 if "Block" in d: st.session_state.total_block += 1 st.session_state.alerts = st.session_state.alerts + [tx] st.session_state.amount_risk += tx["amount"] elif "Alert" in d: st.session_state.total_alert += 1 st.session_state.alerts = st.session_state.alerts + [tx] st.session_state.amount_risk += tx["amount"] * 0.5 else: st.session_state.total_pass += 1 if st.session_state.tx_counter % 6 == 0: st.session_state.step += 1 # 📌 เขียนทับกล่องเดิมเพื่อเพิ่มแถวใหม่ (+1 แถวต่อท้ายในตารางเดี่ยว) หัวตารางจะไม่ซ้ำแล้ว feed_placeholder.markdown(build_feed_table(st.session_state.feed), unsafe_allow_html=True) # อัปเดตฝั่ง Alerts Queue ในกล่องเดิมเช่นกันแบบเรียบเนียน with alert_placeholder.container(): if st.session_state.alerts: for a_tx in st.session_state.alerts[-20:]: title_text = f"🚨 [{a_tx['tx_time']}] {a_tx['tx_id']} | ฿{a_tx['amount']:,.0f} ➔ {a_tx['decision'].upper()}" with st.expander(title_text): st.markdown(f""" **Incident Deep-Dive Metrics:** * **Timestamp:** `{a_tx['tx_time']}` * **Source Account:** `{a_tx['account_id']}` * **Destination Target:** `{a_tx['dest_id']}` * **Model A Probability (Behavior Engine):** `{a_tx['prob_a']:.3f}` * **Model B Probability (Counterparty Network):** `{a_tx['prob_b']:.3f}` * **Weighted Ensemble Score:** `{a_tx['final_score']:.3f}` * **Pipeline Detection Layer:** `{a_tx['layer']}` * **Hard Rules Active:** `Rule 1 (Remote Access): {a_tx['rule1']}` | `Rule 2 (Hesitation Pattern): {a_tx['rule2']}` """) else: st.success("🟢 OPERATIONAL STATUS: SECURED — No anomalies flagged by models currently.") time.sleep(speed) else: # Static render loop เมื่อกด PAUSE (ตารางคงสภาพล่าสุดนิ่งๆ ไว้ให้ตรวจสอบ) with feed_box: if st.session_state.feed: st.markdown(build_feed_table(st.session_state.feed), unsafe_allow_html=True) else: st.info("System Ready. Click **▶ Start / Resume** to map ledger feeds.") with alert_box: if st.session_state.alerts: for a_tx in st.session_state.alerts[-20:]: title_text = f"🚨 [{a_tx['tx_time']}] {a_tx['tx_id']} | ฿{a_tx['amount']:,.0f} ➔ {a_tx['decision'].upper()}" with st.expander(title_text): st.markdown(f""" **Incident Deep-Dive Metrics:** * **Timestamp:** `{a_tx['tx_time']}` * **Source Account:** `{a_tx['account_id']}` * **Destination Target:** `{a_tx['dest_id']}` * **Model A Probability:** `{a_tx['prob_a']:.3f}` * **Model B Probability:** `{a_tx['prob_b']:.3f}` * **Weighted Ensemble Score:** `{a_tx['final_score']:.3f}` """) else: st.success("🟢 OPERATIONAL STATUS: SECURED — No anomalies flagged by models currently.")