Update app.py
Browse files
app.py
CHANGED
|
@@ -76,9 +76,6 @@ st.markdown("""
|
|
| 76 |
font-size: 13px;
|
| 77 |
}
|
| 78 |
.github-card a:hover { text-decoration: underline; }
|
| 79 |
-
|
| 80 |
-
/* Bouncing element style to target the end of tables */
|
| 81 |
-
.scroll-target { height: 1px; }
|
| 82 |
</style>
|
| 83 |
""", unsafe_allow_html=True)
|
| 84 |
|
|
@@ -170,7 +167,6 @@ with metric_col:
|
|
| 170 |
with watchlist_col:
|
| 171 |
st.markdown("<p style='font-size:12px; font-weight:600; color:#475569; margin:0 0 6px 0;'>🎯 Account Audit Matrix</p>", unsafe_allow_html=True)
|
| 172 |
if st.session_state.accounts:
|
| 173 |
-
# ตารางรายชื่อบัญชีแบบกะทัดรัด
|
| 174 |
acc_table = """<table class="bank-table">
|
| 175 |
<tr><th>Account ID</th><th>Baseline Risk</th><th>Operational Status</th></tr>"""
|
| 176 |
for acc in st.session_state.accounts:
|
|
@@ -210,7 +206,7 @@ with alert_col:
|
|
| 210 |
def build_feed_table(feed_list) -> str:
|
| 211 |
html_table = """<table class="bank-table">
|
| 212 |
<tr><th>Time</th><th>Tx ID</th><th>Routing Sequence</th><th>Amount</th><th>Score</th><th>Action</th></tr>"""
|
| 213 |
-
for f_tx in feed_list[-40:]:
|
| 214 |
style_cls = "text-block" if "Block" in f_tx["decision"] else "text-alert" if "Alert" in f_tx["decision"] else "text-pass"
|
| 215 |
html_table += f"""<tr>
|
| 216 |
<td style='color:#64748b; font-family:monospace;'>{f_tx.get('tx_time', '00:00:00')}</td>
|
|
@@ -225,13 +221,21 @@ def build_feed_table(feed_list) -> str:
|
|
| 225 |
|
| 226 |
# ─── Simulation Core Loop & Rendering Logic ──────────────────────────────────
|
| 227 |
if st.session_state.running and st.session_state.accounts:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
while st.session_state.running:
|
| 229 |
# Pick account & process transaction
|
| 230 |
account = random.choice(st.session_state.accounts)
|
| 231 |
raw_tx = generate_transaction(account, step=st.session_state.step, tx_counter=st.session_state.tx_counter)
|
| 232 |
tx = score_transaction(raw_tx)
|
| 233 |
|
| 234 |
-
#
|
| 235 |
tx['tx_time'] = datetime.now().strftime("%H:%M:%S")
|
| 236 |
|
| 237 |
# Handle status state mutations
|
|
@@ -239,18 +243,18 @@ if st.session_state.running and st.session_state.accounts:
|
|
| 239 |
if "Block" in d: account["status"] = "blocked"
|
| 240 |
elif "Alert" in d: account["status"] = "alert"
|
| 241 |
|
| 242 |
-
#
|
| 243 |
st.session_state.feed = st.session_state.feed + [tx]
|
| 244 |
st.session_state.total_tx += 1
|
| 245 |
st.session_state.tx_counter += 1
|
| 246 |
|
| 247 |
if "Block" in d:
|
| 248 |
st.session_state.total_block += 1
|
| 249 |
-
st.session_state.alerts = st.session_state.alerts + [tx]
|
| 250 |
st.session_state.amount_risk += tx["amount"]
|
| 251 |
elif "Alert" in d:
|
| 252 |
st.session_state.total_alert += 1
|
| 253 |
-
st.session_state.alerts = st.session_state.alerts + [tx]
|
| 254 |
st.session_state.amount_risk += tx["amount"] * 0.5
|
| 255 |
else:
|
| 256 |
st.session_state.total_pass += 1
|
|
@@ -258,14 +262,13 @@ if st.session_state.running and st.session_state.accounts:
|
|
| 258 |
if st.session_state.tx_counter % 6 == 0:
|
| 259 |
st.session_state.step += 1
|
| 260 |
|
| 261 |
-
#
|
| 262 |
-
|
| 263 |
-
st.markdown(build_feed_table(st.session_state.feed), unsafe_allow_html=True)
|
| 264 |
|
| 265 |
-
#
|
| 266 |
-
with
|
| 267 |
if st.session_state.alerts:
|
| 268 |
-
for a_tx in st.session_state.alerts[-20:]:
|
| 269 |
title_text = f"🚨 [{a_tx['tx_time']}] {a_tx['tx_id']} | ฿{a_tx['amount']:,.0f} ➔ {a_tx['decision'].upper()}"
|
| 270 |
with st.expander(title_text):
|
| 271 |
st.markdown(f"""
|
|
@@ -284,27 +287,26 @@ if st.session_state.running and st.session_state.accounts:
|
|
| 284 |
|
| 285 |
time.sleep(speed)
|
| 286 |
else:
|
| 287 |
-
# Static render loop
|
| 288 |
with feed_box:
|
| 289 |
if st.session_state.feed:
|
| 290 |
st.markdown(build_feed_table(st.session_state.feed), unsafe_allow_html=True)
|
| 291 |
else:
|
| 292 |
st.info("System Ready. Click **▶ Start / Resume** to map ledger feeds.")
|
| 293 |
|
| 294 |
-
with
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
st.success("🟢 OPERATIONAL STATUS: SECURED — No anomalies flagged by models currently.")
|
|
|
|
| 76 |
font-size: 13px;
|
| 77 |
}
|
| 78 |
.github-card a:hover { text-decoration: underline; }
|
|
|
|
|
|
|
|
|
|
| 79 |
</style>
|
| 80 |
""", unsafe_allow_html=True)
|
| 81 |
|
|
|
|
| 167 |
with watchlist_col:
|
| 168 |
st.markdown("<p style='font-size:12px; font-weight:600; color:#475569; margin:0 0 6px 0;'>🎯 Account Audit Matrix</p>", unsafe_allow_html=True)
|
| 169 |
if st.session_state.accounts:
|
|
|
|
| 170 |
acc_table = """<table class="bank-table">
|
| 171 |
<tr><th>Account ID</th><th>Baseline Risk</th><th>Operational Status</th></tr>"""
|
| 172 |
for acc in st.session_state.accounts:
|
|
|
|
| 206 |
def build_feed_table(feed_list) -> str:
|
| 207 |
html_table = """<table class="bank-table">
|
| 208 |
<tr><th>Time</th><th>Tx ID</th><th>Routing Sequence</th><th>Amount</th><th>Score</th><th>Action</th></tr>"""
|
| 209 |
+
for f_tx in feed_list[-40:]:
|
| 210 |
style_cls = "text-block" if "Block" in f_tx["decision"] else "text-alert" if "Alert" in f_tx["decision"] else "text-pass"
|
| 211 |
html_table += f"""<tr>
|
| 212 |
<td style='color:#64748b; font-family:monospace;'>{f_tx.get('tx_time', '00:00:00')}</td>
|
|
|
|
| 221 |
|
| 222 |
# ─── Simulation Core Loop & Rendering Logic ──────────────────────────────────
|
| 223 |
if st.session_state.running and st.session_state.accounts:
|
| 224 |
+
|
| 225 |
+
# 📌 สร้าง Placeholder ไว้ "นอกลูป" แค่ครั้งเดียว เพื่อล็อคหัวตารางให้ไม่วาดซ้ำซาก
|
| 226 |
+
with feed_box:
|
| 227 |
+
feed_placeholder = st.empty()
|
| 228 |
+
|
| 229 |
+
with alert_box:
|
| 230 |
+
alert_placeholder = st.empty()
|
| 231 |
+
|
| 232 |
while st.session_state.running:
|
| 233 |
# Pick account & process transaction
|
| 234 |
account = random.choice(st.session_state.accounts)
|
| 235 |
raw_tx = generate_transaction(account, step=st.session_state.step, tx_counter=st.session_state.tx_counter)
|
| 236 |
tx = score_transaction(raw_tx)
|
| 237 |
|
| 238 |
+
# ฝังเวลาปัจจุบันของระบบธนาคารระดับวินาที
|
| 239 |
tx['tx_time'] = datetime.now().strftime("%H:%M:%S")
|
| 240 |
|
| 241 |
# Handle status state mutations
|
|
|
|
| 243 |
if "Block" in d: account["status"] = "blocked"
|
| 244 |
elif "Alert" in d: account["status"] = "alert"
|
| 245 |
|
| 246 |
+
# สะสมข้อมูลธุรกรรมต่อท้ายลิสต์เดิมลงไปข้างล่าง
|
| 247 |
st.session_state.feed = st.session_state.feed + [tx]
|
| 248 |
st.session_state.total_tx += 1
|
| 249 |
st.session_state.tx_counter += 1
|
| 250 |
|
| 251 |
if "Block" in d:
|
| 252 |
st.session_state.total_block += 1
|
| 253 |
+
st.session_state.alerts = st.session_state.alerts + [tx]
|
| 254 |
st.session_state.amount_risk += tx["amount"]
|
| 255 |
elif "Alert" in d:
|
| 256 |
st.session_state.total_alert += 1
|
| 257 |
+
st.session_state.alerts = st.session_state.alerts + [tx]
|
| 258 |
st.session_state.amount_risk += tx["amount"] * 0.5
|
| 259 |
else:
|
| 260 |
st.session_state.total_pass += 1
|
|
|
|
| 262 |
if st.session_state.tx_counter % 6 == 0:
|
| 263 |
st.session_state.step += 1
|
| 264 |
|
| 265 |
+
# 📌 เขียนทับกล่องเดิมเพื่อเพิ่มแถวใหม่ (+1 แถวต่อท้ายในตารางเดี่ยว) หัวตารางจะไม่ซ้ำแล้ว
|
| 266 |
+
feed_placeholder.markdown(build_feed_table(st.session_state.feed), unsafe_allow_html=True)
|
|
|
|
| 267 |
|
| 268 |
+
# อัปเดตฝั่ง Alerts Queue ในกล่องเดิมเช่นกันแบบเรียบเนียน
|
| 269 |
+
with alert_placeholder.container():
|
| 270 |
if st.session_state.alerts:
|
| 271 |
+
for a_tx in st.session_state.alerts[-20:]:
|
| 272 |
title_text = f"🚨 [{a_tx['tx_time']}] {a_tx['tx_id']} | ฿{a_tx['amount']:,.0f} ➔ {a_tx['decision'].upper()}"
|
| 273 |
with st.expander(title_text):
|
| 274 |
st.markdown(f"""
|
|
|
|
| 287 |
|
| 288 |
time.sleep(speed)
|
| 289 |
else:
|
| 290 |
+
# Static render loop เมื่อกด PAUSE (ตารางคงสภาพล่าสุดนิ่งๆ ไว้ให้ตรวจสอบ)
|
| 291 |
with feed_box:
|
| 292 |
if st.session_state.feed:
|
| 293 |
st.markdown(build_feed_table(st.session_state.feed), unsafe_allow_html=True)
|
| 294 |
else:
|
| 295 |
st.info("System Ready. Click **▶ Start / Resume** to map ledger feeds.")
|
| 296 |
|
| 297 |
+
with alert_box:
|
| 298 |
+
if st.session_state.alerts:
|
| 299 |
+
for a_tx in st.session_state.alerts[-20:]:
|
| 300 |
+
title_text = f"🚨 [{a_tx['tx_time']}] {a_tx['tx_id']} | ฿{a_tx['amount']:,.0f} ➔ {a_tx['decision'].upper()}"
|
| 301 |
+
with st.expander(title_text):
|
| 302 |
+
st.markdown(f"""
|
| 303 |
+
**Incident Deep-Dive Metrics:**
|
| 304 |
+
* **Timestamp:** `{a_tx['tx_time']}`
|
| 305 |
+
* **Source Account:** `{a_tx['account_id']}`
|
| 306 |
+
* **Destination Target:** `{a_tx['dest_id']}`
|
| 307 |
+
* **Model A Probability:** `{a_tx['prob_a']:.3f}`
|
| 308 |
+
* **Model B Probability:** `{a_tx['prob_b']:.3f}`
|
| 309 |
+
* **Weighted Ensemble Score:** `{a_tx['final_score']:.3f}`
|
| 310 |
+
""")
|
| 311 |
+
else:
|
| 312 |
+
st.success("🟢 OPERATIONAL STATUS: SECURED — No anomalies flagged by models currently.")
|
|
|