Spaces:
Sleeping
Sleeping
File size: 15,559 Bytes
fbb910f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 | import streamlit as st
import plotly.graph_objects as go
import numpy as np
import pandas as pd
from openai import OpenAI
# 初始化 tab2 專屬的 session state
def init_tab2_session_state():
if 'tab2_chat_history' not in st.session_state:
st.session_state.tab2_chat_history = []
# 有加強劑的參數
if 'tab2_booster_ct' not in st.session_state:
st.session_state.tab2_booster_ct = 18.0
if 'tab2_booster_days' not in st.session_state:
st.session_state.tab2_booster_days = 7
# 沒有加強劑的參數
if 'tab2_no_booster_ct' not in st.session_state:
st.session_state.tab2_no_booster_ct = 18.0
if 'tab2_no_booster_days' not in st.session_state:
st.session_state.tab2_no_booster_days = 7
# 基礎數據:從 Table 2 論文原始數據
BASE_DATA = {
# Ct <= 18, 無加強劑
'ct18_no_booster': {
1: 0.10, 2: 0.25, 3: 0.39, 4: 0.51, 5: 0.61,
6: 0.70, 7: 0.76, 8: 0.81, 9: 0.84, 10: 0.87,
11: 0.89, 12: 0.91, 13: 0.93, 14: 0.94, 15: 0.95,
16: 0.96, 17: 0.96, 18: 0.97, 19: 0.97, 20: 0.98, 21: 0.98
},
# Ct <= 18, 有加強劑
'ct18_booster': {
1: 0.44, 2: 0.65, 3: 0.77, 4: 0.84, 5: 0.89,
6: 0.92, 7: 0.94, 8: 0.95, 9: 0.97, 10: 0.97,
11: 0.97, 12: 0.98, 13: 0.98, 14: 0.99, 15: 0.99,
16: 0.99, 17: 0.99, 18: 0.99, 19: 0.99, 20: 1.00, 21: 1.00
},
# Ct 18-25, 無加強劑
'ct22_no_booster': {
1: 0.49, 2: 0.57, 3: 0.65, 4: 0.72, 5: 0.78,
6: 0.83, 7: 0.86, 8: 0.89, 9: 0.91, 10: 0.92,
11: 0.94, 12: 0.95, 13: 0.96, 14: 0.97, 15: 0.97,
16: 0.98, 17: 0.98, 18: 0.98, 19: 0.98, 20: 0.99, 21: 0.99
},
# Ct 18-25, 有加強劑
'ct22_booster': {
1: 0.68, 2: 0.80, 3: 0.87, 4: 0.91, 5: 0.94,
6: 0.95, 7: 0.96, 8: 0.97, 9: 0.98, 10: 0.98,
11: 0.98, 12: 0.99, 13: 0.99, 14: 0.99, 15: 0.99,
16: 1.00, 17: 1.00, 18: 1.00, 19: 1.00, 20: 1.00, 21: 1.00
}
}
def get_effectiveness_from_data(data_key, days):
"""從基礎數據中獲取效益值(帶線性插值)"""
data = BASE_DATA[data_key]
if days < 1:
return 0.0
if days > 21:
return data[21]
if days in data:
return data[days]
# 線性插值
day_lower = int(days)
day_upper = day_lower + 1
if day_upper > 21:
return data[21]
eff_lower = data[day_lower]
eff_upper = data[day_upper]
ratio = days - day_lower
effectiveness = eff_lower * (1 - ratio) + eff_upper * ratio
return effectiveness
def get_quarantine_effectiveness(ct_value, days, has_booster):
"""獲取隔離效益"""
if ct_value > 25:
return 0.0
booster_suffix = 'booster' if has_booster else 'no_booster'
if ct_value <= 18:
data_key = f'ct18_{booster_suffix}'
effectiveness = get_effectiveness_from_data(data_key, days)
if ct_value < 10:
boost_factor = 1.0 + (10 - ct_value) * 0.005
effectiveness = min(effectiveness * boost_factor, 1.0)
return effectiveness
else:
ct18_key = f'ct18_{booster_suffix}'
ct22_key = f'ct22_{booster_suffix}'
eff_ct18 = get_effectiveness_from_data(ct18_key, days)
eff_ct22 = get_effectiveness_from_data(ct22_key, days)
ratio = (ct_value - 18) / 7
effectiveness = eff_ct18 * (1 - ratio) + eff_ct22 * ratio
return min(effectiveness, 1.0)
def get_ct_category(ct_value):
"""獲取 Ct 值分類"""
if ct_value <= 18:
return "高病毒量 (Ct ≤ 18)"
elif ct_value <= 25:
return "中病毒量 (18 < Ct ≤ 25)"
else:
return "低病毒量 (Ct > 25,假設無傳染性)"
def create_3d_plot(ct_mesh, day_mesh, effectiveness_mesh, selected_ct, quarantine_days,
user_eff, has_booster, title, colorscale, marker_color):
"""創建單一 3D 曲面圖"""
fig = go.Figure()
fig.add_trace(go.Surface(
x=ct_mesh,
y=day_mesh,
z=effectiveness_mesh,
colorscale=colorscale,
showscale=True,
colorbar=dict(
title="防疫效益",
tickvals=[0, 0.25, 0.5, 0.75, 1.0],
ticktext=['0%', '25%', '50%', '75%', '100%']
),
opacity=0.9,
name=title,
contours=dict(
x=dict(show=True, color='white', width=1),
y=dict(show=True, color='white', width=1),
z=dict(show=True, color='white', width=1)
)
))
fig.add_trace(go.Scatter3d(
x=[selected_ct],
y=[quarantine_days],
z=[user_eff],
mode='markers+text',
marker=dict(size=12, color=marker_color, symbol='circle',
line=dict(color='white', width=3)),
text=[f'{user_eff*100:.0f}%'],
textposition='top center',
textfont=dict(size=14, color='white', family='Arial Black'),
name='當前情境',
showlegend=True
))
fig.update_layout(
title={
'text': title,
'x': 0.5,
'xanchor': 'center',
'font': {'size': 16}
},
scene=dict(
xaxis=dict(
title='X-病毒量(Ct值)',
range=[10, 25],
tickvals=[10, 15, 18, 20, 25],
showgrid=True,
gridwidth=2,
gridcolor='rgb(200, 200, 200)',
showbackground=True,
backgroundcolor='rgba(240, 240, 240, 0.9)'
),
yaxis=dict(
title='Y-隔離檢疫天數',
range=[1, 21],
tickvals=[1, 5, 7, 10, 14, 21],
showgrid=True,
gridwidth=2,
gridcolor='rgb(200, 200, 200)',
showbackground=True,
backgroundcolor='rgba(240, 240, 240, 0.9)'
),
zaxis=dict(
title='Z-防疫效益',
range=[0, 1],
tickvals=[0, 0.25, 0.5, 0.75, 1.0],
ticktext=['0%', '25%', '50%', '75%', '100%'],
showgrid=True,
gridwidth=2,
gridcolor='rgb(200, 200, 200)',
showbackground=True,
backgroundcolor='rgba(240, 240, 240, 0.9)'
),
camera=dict(
eye=dict(x=1.5, y=-1.5, z=1.3),
center=dict(x=0, y=0, z=0)
),
aspectmode='manual',
aspectratio=dict(x=1, y=1.2, z=0.8)
),
height=600,
showlegend=True,
legend=dict(
x=0.02,
y=0.98,
bgcolor='rgba(255, 255, 255, 0.9)',
bordercolor='black',
borderwidth=1
),
margin=dict(l=0, r=0, t=40, b=0)
)
return fig
def render():
"""渲染 Tab2 的完整內容"""
init_tab2_session_state()
# 側邊欄 - 控制面板
with st.sidebar:
st.header("🏥 隔離檢疫決策工具")
st.markdown("---")
# 有加強劑的參數設定
st.subheader("💉 有加強劑情境")
booster_ct = st.slider(
"🦠 接觸者Ct值",
min_value=10.0,
max_value=25.0,
value=st.session_state.tab2_booster_ct,
step=0.5,
help="Ct值越低代表病毒量越高 (Ct>25視為無傳染性)",
key="tab2_booster_ct_slider"
)
st.session_state.tab2_booster_ct = booster_ct
booster_days = st.slider(
"📅 隔離天數",
min_value=1,
max_value=21,
value=st.session_state.tab2_booster_days,
help="需要隔離檢疫的天數",
key="tab2_booster_days_slider"
)
st.session_state.tab2_booster_days = booster_days
booster_eff = get_quarantine_effectiveness(booster_ct, booster_days, True)
st.metric(
label="📊 防疫效益",
value=f"{booster_eff * 100:.0f}%",
delta="有加強劑"
)
st.markdown("---")
# 沒有加強劑的參數設定
st.subheader("⚠️ 無加強劑情境")
no_booster_ct = st.slider(
"🦠 接觸者Ct值",
min_value=10.0,
max_value=25.0,
value=st.session_state.tab2_no_booster_ct,
step=0.5,
help="Ct值越低代表病毒量越高 (Ct>25視為無傳染性)",
key="tab2_no_booster_ct_slider"
)
st.session_state.tab2_no_booster_ct = no_booster_ct
no_booster_days = st.slider(
"📅 隔離天數",
min_value=1,
max_value=21,
value=st.session_state.tab2_no_booster_days,
help="需要隔離檢疫的天數",
key="tab2_no_booster_days_slider"
)
st.session_state.tab2_no_booster_days = no_booster_days
no_booster_eff = get_quarantine_effectiveness(no_booster_ct, no_booster_days, False)
st.metric(
label="📊 防疫效益",
value=f"{no_booster_eff * 100:.0f}%",
delta="無加強劑"
)
st.markdown("---")
# 情境說明
with st.expander("🎯 使用情境", expanded=False):
st.markdown("""
當找到疑似接觸者時,防疫人員需要決定:
**「要隔離/檢疫多少天?」**
考量因素:
- 接觸者的病毒量 (Ct值)
- 是否已接種加強劑
""")
with st.expander("✅ Omicron 特性"):
st.markdown("""
相較於 Alpha 變異株:
- **傳播更快** 但症狀較輕
- **疫苗保護** 顯著縮短隔離時間
- **Ct > 25** 視為無傳染性
""")
with st.expander("💉 疫苗影響"):
st.markdown("""
**加強劑的效益:**
- 大幅縮短所需隔離天數
- 相同天數下效益更高
- 例: 達到90%效益
- 有加強劑: 5天
- 無加強劑: 11天
""")
# 主要內容區
st.markdown("### 📊 隔離檢疫效益 3D 視覺化")
# 生成 3D 數據
ct_range = np.arange(10, 25.5, 0.5)
day_range = np.arange(1, 22, 1)
ct_mesh, day_mesh = np.meshgrid(ct_range, day_range)
# 計算兩組效益值
effectiveness_with_booster = np.zeros_like(ct_mesh)
effectiveness_without_booster = np.zeros_like(ct_mesh)
for i in range(len(day_range)):
for j in range(len(ct_range)):
effectiveness_with_booster[i, j] = get_quarantine_effectiveness(ct_mesh[i, j], day_mesh[i, j], True)
effectiveness_without_booster[i, j] = get_quarantine_effectiveness(ct_mesh[i, j], day_mesh[i, j], False)
# 創建兩個並排的圖表
col1, col2 = st.columns(2)
with col1:
st.markdown("#### 💉 有加強劑情境")
fig1 = create_3d_plot(
ct_mesh, day_mesh, effectiveness_with_booster,
booster_ct, booster_days,
booster_eff,
True,
"隔離檢疫效益 - 有加強劑 (Omicron 變異株)",
[
[0.0, 'rgb(239, 68, 68)'],
[0.33, 'rgb(245, 158, 11)'],
[0.67, 'rgb(16, 185, 129)'],
[1.0, 'rgb(59, 130, 246)']
],
'blue'
)
st.plotly_chart(fig1, use_container_width=True)
ct_category_booster = get_ct_category(booster_ct)
st.info(f"**情境:** Ct值 = **{booster_ct}** ({ct_category_booster}), 隔離 **{booster_days}** 天 | 💉 已接種加強劑")
if booster_eff >= 0.9:
st.success("💡 **意義:** 隔離時間充足,可有效防止疫情傳播")
elif booster_eff >= 0.7:
st.warning("💡 **意義:** 隔離效果良好,但建議視情況延長")
elif booster_eff >= 0.5:
st.warning("💡 **意義:** 隔離效果一般,建議延長隔離時間")
else:
st.error("💡 **意義:** 隔離效果不足,需要大幅延長隔離時間")
with col2:
st.markdown("#### ⚠️ 無加強劑情境")
fig2 = create_3d_plot(
ct_mesh, day_mesh, effectiveness_without_booster,
no_booster_ct, no_booster_days,
no_booster_eff,
False,
"隔離檢疫效益 - 無加強劑 (Omicron 變異株)",
[
[0.0, 'rgb(239, 68, 68)'],
[0.33, 'rgb(245, 158, 11)'],
[0.67, 'rgb(16, 185, 129)'],
[1.0, 'rgb(59, 130, 246)']
],
'red'
)
st.plotly_chart(fig2, use_container_width=True)
ct_category_no_booster = get_ct_category(no_booster_ct)
st.info(f"**情境:** Ct值 = **{no_booster_ct}** ({ct_category_no_booster}), 隔離 **{no_booster_days}** 天 | ⚠️ 未接種加強劑")
if no_booster_eff >= 0.9:
st.success("💡 **意義:** 隔離時間充足,可有效防止疫情傳播")
elif no_booster_eff >= 0.7:
st.warning("💡 **意義:** 隔離效果良好,但建議視情況延長")
elif no_booster_eff >= 0.5:
st.warning("💡 **意義:** 隔離效果一般,建議延長隔離時間")
else:
st.error("💡 **意義:** 隔離效果不足,需要大幅延長隔離時間")
# 疫苗建議
improvement = (booster_eff - no_booster_eff) * 100
if improvement > 0:
st.info(f"💉 **提示:** 若接種加強劑,在相同參數下可提升 {improvement:.0f}% 效益")
# 底部說明區域
st.markdown("---")
col_a, col_b = st.columns(2)
with col_a:
with st.expander("💡 操作說明", expanded=False):
st.markdown("""
**3D 圖表說明:**
- 🔵 **左側圖表**: 有加強劑的隔離效益 (藍綠色)
- 🔴 **右側圖表**: 無加強劑的隔離效益 (橙紅色)
- 💎 **菱形標記**: 您當前選擇的情境
- 兩圖對比可清楚看出**疫苗的效益差異**
**互動操作:**
- 🖱️ 拖曳旋轉視角
- 🔍 滾輪縮放
- 🎚️ 使用左側滑桿調整參數
""")
with col_b:
with st.expander("📊 查看詳細數據", expanded=False):
# 創建對比表格
test_days_list = [3, 5, 7, 10, 14]
data = {
'隔離天數': test_days_list,
'有加強劑 (Ct=' + str(booster_ct) + ')': [f"{get_quarantine_effectiveness(booster_ct, d, True)*100:.0f}%" for d in test_days_list],
'無加強劑 (Ct=' + str(no_booster_ct) + ')': [f"{get_quarantine_effectiveness(no_booster_ct, d, False)*100:.0f}%" for d in test_days_list],
'效益差異': [f"+{(get_quarantine_effectiveness(booster_ct, d, True) - get_quarantine_effectiveness(no_booster_ct, d, False))*100:.0f}%" for d in test_days_list]
}
df = pd.DataFrame(data)
st.dataframe(df, use_container_width=True)
|