1105 / tab2_quarantine.py
smartTranscend's picture
Rename tab2_quarantine (4).py to tab2_quarantine.py
a42986a verified
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)