smartTranscend commited on
Commit
3c4b07c
·
verified ·
1 Parent(s): 78b7495

Delete tab2_quarantine.py

Browse files
Files changed (1) hide show
  1. tab2_quarantine.py +0 -443
tab2_quarantine.py DELETED
@@ -1,443 +0,0 @@
1
- import streamlit as st
2
- import plotly.graph_objects as go
3
- import numpy as np
4
- import pandas as pd
5
- from openai import OpenAI
6
-
7
- def init_tab2_session_state():
8
- if 'api_key' not in st.session_state:
9
- st.session_state.api_key = ""
10
- if 'tab2_chat_history' not in st.session_state:
11
- st.session_state.tab2_chat_history = []
12
- if 'tab2_ct_booster' not in st.session_state:
13
- st.session_state.tab2_ct_booster = 18.0
14
- if 'tab2_days_booster' not in st.session_state:
15
- st.session_state.tab2_days_booster = 7
16
- if 'tab2_ct_no_booster' not in st.session_state:
17
- st.session_state.tab2_ct_no_booster = 18.0
18
- if 'tab2_days_no_booster' not in st.session_state:
19
- st.session_state.tab2_days_no_booster = 7
20
-
21
- BASE_DATA = {
22
- '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,
23
- 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},
24
- '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,
25
- 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},
26
- '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,
27
- 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},
28
- '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,
29
- 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}
30
- }
31
-
32
- def get_effectiveness_from_data(data_key, days):
33
- data = BASE_DATA[data_key]
34
- if days < 1: return 0.0
35
- if days > 21: return data[21]
36
- if days in data: return data[days]
37
- day_lower = int(days)
38
- day_upper = day_lower + 1
39
- if day_upper > 21: return data[21]
40
- ratio = days - day_lower
41
- return data[day_lower] * (1 - ratio) + data[day_upper] * ratio
42
-
43
- def get_quarantine_effectiveness(ct_value, days, has_booster):
44
- if ct_value > 25: return 0.0
45
- booster_suffix = 'booster' if has_booster else 'no_booster'
46
- if ct_value <= 18:
47
- data_key = f'ct18_{booster_suffix}'
48
- effectiveness = get_effectiveness_from_data(data_key, days)
49
- if ct_value < 10:
50
- boost_factor = 1.0 + (10 - ct_value) * 0.005
51
- effectiveness = min(effectiveness * boost_factor, 1.0)
52
- return effectiveness
53
- else:
54
- ct18_key = f'ct18_{booster_suffix}'
55
- ct22_key = f'ct22_{booster_suffix}'
56
- eff_ct18 = get_effectiveness_from_data(ct18_key, days)
57
- eff_ct22 = get_effectiveness_from_data(ct22_key, days)
58
- ratio = (ct_value - 18) / 7
59
- return min(eff_ct18 * (1 - ratio) + eff_ct22 * ratio, 1.0)
60
-
61
- def find_days_for_target_quarantine(ct_value, target_effectiveness, has_booster):
62
- for days in range(1, 22):
63
- if get_quarantine_effectiveness(ct_value, days, has_booster) >= target_effectiveness:
64
- return days
65
- return 21
66
-
67
- def get_ct_category(ct_value):
68
- if ct_value <= 18: return "高病毒量 (Ct ≤ 18)"
69
- elif ct_value <= 25: return "中病毒量 (18 < Ct ≤ 25)"
70
- else: return "低病毒量 (Ct > 25,假設無傳染性)"
71
-
72
- def extract_quarantine_params(text):
73
- import re
74
- text_lower = text.lower()
75
-
76
- # 提取 Ct 值
77
- ct_patterns = [r'ct\s*[=值]?\s*(\d+\.?\d*)', r'(\d+\.?\d*)\s*ct']
78
- ct_value = None
79
- for pattern in ct_patterns:
80
- match = re.search(pattern, text_lower)
81
- if match:
82
- try:
83
- ct_value = float(match.group(1))
84
- if 10 <= ct_value <= 25:
85
- break
86
- except:
87
- continue
88
-
89
- # 提取天數
90
- day_patterns = [r'(\d+)\s*天', r'隔離\s*(\d+)', r'檢疫\s*(\d+)']
91
- days = None
92
- for pattern in day_patterns:
93
- match = re.search(pattern, text)
94
- if match:
95
- try:
96
- days = int(match.group(1))
97
- if 1 <= days <= 21:
98
- break
99
- except:
100
- continue
101
-
102
- # 檢查目標效益
103
- target_eff = None
104
- eff_patterns = [r'要.*?(\d+).*?%', r'達到.*?(\d+).*?%', r'(\d+).*?%.*?效益']
105
- for pattern in eff_patterns:
106
- match = re.search(pattern, text)
107
- if match:
108
- try:
109
- target_eff = float(match.group(1)) / 100
110
- if 0 <= target_eff <= 1:
111
- break
112
- except:
113
- continue
114
- if '90%' in text: target_eff = 0.9
115
- if '100%' in text or '百分之百' in text: target_eff = 1.0
116
-
117
- # 🔥 關鍵改進:檢測是否明確只問一種疫苗狀態
118
- has_booster = None
119
-
120
- # 明確只問有加強劑的模式
121
- only_with_booster_patterns = [
122
- r'(只|僅|單獨|專門).{0,5}(有|已接種|��了).{0,5}(加強劑|疫苗)',
123
- r'^(有|已接種|打了).{0,5}(加強劑|疫苗).*[??]$' # 以"有加強劑"開頭,問號結尾
124
- ]
125
-
126
- # 明確只問無加強劑的模式
127
- only_without_booster_patterns = [
128
- r'(只|僅|單獨|專門).{0,5}(無|沒有|未接種|沒打).{0,5}(加強劑|疫苗)',
129
- r'^(無|沒有|未接種|沒打).{0,5}(加強劑|疫苗).*[??]$'
130
- ]
131
-
132
- for pattern in only_with_booster_patterns:
133
- if re.search(pattern, text):
134
- has_booster = True
135
- break
136
-
137
- if has_booster is None:
138
- for pattern in only_without_booster_patterns:
139
- if re.search(pattern, text):
140
- has_booster = False
141
- break
142
-
143
- # ✅ 預設:如果沒有明確指定,返回 None,觸發雙情境分析
144
- return ct_value, days, target_eff, has_booster
145
-
146
- def call_gpt4_quarantine(prompt, api_key):
147
- try:
148
- client = OpenAI(api_key=api_key)
149
- response = client.chat.completions.create(
150
- model="gpt-4",
151
- messages=[
152
- {"role": "system", "content": "你是COVID-19 Omicron防疫專家,擅長同時分析有/無加強劑兩種情境。"},
153
- {"role": "user", "content": prompt}
154
- ],
155
- temperature=0.7,
156
- max_tokens=1500
157
- )
158
- return response.choices[0].message.content
159
- except Exception as e:
160
- return f"❌ API 呼叫失敗: {str(e)}"
161
-
162
- def create_3d_plot(ct_mesh, day_mesh, effectiveness_mesh, selected_ct, quarantine_days,
163
- user_eff, has_booster, title, colorscale, marker_color):
164
- fig = go.Figure()
165
- fig.add_trace(go.Surface(
166
- x=ct_mesh, y=day_mesh, z=effectiveness_mesh, colorscale=colorscale, showscale=True,
167
- colorbar=dict(title="防疫效益", tickvals=[0, 0.25, 0.5, 0.75, 1.0], ticktext=['0%', '25%', '50%', '75%', '100%']),
168
- opacity=0.9, name=title,
169
- 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))
170
- ))
171
- fig.add_trace(go.Scatter3d(
172
- x=[selected_ct], y=[quarantine_days], z=[user_eff], mode='markers+text',
173
- marker=dict(size=12, color=marker_color, symbol='circle', line=dict(color='white', width=3)),
174
- text=[f'{user_eff*100:.0f}%'], textposition='top center',
175
- textfont=dict(size=14, color='white', family='Arial Black'), name='當前情境', showlegend=True
176
- ))
177
- fig.update_layout(
178
- title={'text': title, 'x': 0.5, 'xanchor': 'center', 'font': {'size': 16}},
179
- scene=dict(
180
- xaxis=dict(title='X-病毒量(Ct值)', range=[10, 25], tickvals=[10, 15, 18, 20, 25],
181
- showgrid=True, gridwidth=2, gridcolor='rgb(200, 200, 200)', showbackground=True, backgroundcolor='rgba(240, 240, 240, 0.9)'),
182
- yaxis=dict(title='Y-隔離檢疫天數', range=[1, 21], tickvals=[1, 5, 7, 10, 14, 21],
183
- showgrid=True, gridwidth=2, gridcolor='rgb(200, 200, 200)', showbackground=True, backgroundcolor='rgba(240, 240, 240, 0.9)'),
184
- zaxis=dict(title='Z-防疫效益', range=[0, 1], tickvals=[0, 0.25, 0.5, 0.75, 1.0], ticktext=['0%', '25%', '50%', '75%', '100%'],
185
- showgrid=True, gridwidth=2, gridcolor='rgb(200, 200, 200)', showbackground=True, backgroundcolor='rgba(240, 240, 240, 0.9)'),
186
- camera=dict(eye=dict(x=1.5, y=-1.5, z=1.3), center=dict(x=0, y=0, z=0)),
187
- aspectmode='manual', aspectratio=dict(x=1, y=1.2, z=0.8)
188
- ),
189
- height=600, showlegend=True,
190
- legend=dict(x=0.02, y=0.98, bgcolor='rgba(255, 255, 255, 0.9)', bordercolor='black', borderwidth=1),
191
- margin=dict(l=0, r=0, t=40, b=0)
192
- )
193
- return fig
194
-
195
- def render():
196
- init_tab2_session_state()
197
-
198
- with st.sidebar:
199
- st.header("🏥 防疫決策工具 (隔離檢疫)")
200
- st.markdown("**Omicron 變異株 - 雙情境對比**")
201
- st.markdown("---")
202
-
203
- st.subheader("💉 有加強劑情境")
204
- ct_booster = st.slider("🦠 接觸者Ct值", 10.0, 25.0, st.session_state.tab2_ct_booster, 0.5, key="tab2_ct_booster_slider")
205
- st.session_state.tab2_ct_booster = ct_booster
206
- st.caption("10 (高病毒量) ← → 25 (中病毒量)")
207
-
208
- days_booster = st.slider("📅 隔離檢疫天數", 1, 21, st.session_state.tab2_days_booster, key="tab2_days_booster_slider")
209
- st.session_state.tab2_days_booster = days_booster
210
- st.caption("1天 ← → 21天")
211
-
212
- eff_booster = get_quarantine_effectiveness(ct_booster, days_booster, True)
213
- st.metric("📊 防疫效益", f"{eff_booster * 100:.0f}%", "有加強劑")
214
-
215
- st.markdown("---")
216
- st.subheader("💊 無加強劑情境")
217
-
218
- ct_no_booster = st.slider("🦠 接觸者Ct值 ", 10.0, 25.0, st.session_state.tab2_ct_no_booster, 0.5, key="tab2_ct_no_booster_slider")
219
- st.session_state.tab2_ct_no_booster = ct_no_booster
220
-
221
- days_no_booster = st.slider("📅 隔離檢疫天數 ", 1, 21, st.session_state.tab2_days_no_booster, key="tab2_days_no_booster_slider")
222
- st.session_state.tab2_days_no_booster = days_no_booster
223
-
224
- eff_no_booster = get_quarantine_effectiveness(ct_no_booster, days_no_booster, False)
225
- st.metric("📊 防疫效益", f"{eff_no_booster * 100:.0f}%", "無加強劑")
226
-
227
- st.markdown("---")
228
- improvement = (eff_booster - eff_no_booster) * 100
229
- if improvement > 0:
230
- st.metric("💉 疫苗加成", f"+{improvement:.0f}%", "效益提升")
231
- elif improvement < 0:
232
- st.metric("⚠️ 效益差異", f"{improvement:.0f}%", "無加強劑較高")
233
-
234
- st.markdown("---")
235
- if st.button("🤖 生成 AI 雙情境分析", type="primary", use_container_width=True, key="tab2_ai_report"):
236
- if not st.session_state.api_key:
237
- st.error("❌ 請先輸入 OpenAI API Key")
238
- else:
239
- with st.spinner("AI 正在分析兩種情境..."):
240
- prompt = f"""你是COVID-19 Omicron防疫專家。請對比分析以下兩種情境:
241
-
242
- **情境1: 有加強劑**
243
- - Ct 值: {ct_booster}
244
- - 隔離天數: {days_booster} 天
245
- - 防疫效益: {eff_booster*100:.1f}%
246
-
247
- **情境2: 無加強劑**
248
- - Ct 值: {ct_no_booster}
249
- - 隔離天數: {days_no_booster} 天
250
- - 防疫效益: {eff_no_booster*100:.1f}%
251
-
252
- **疫苗加成效益:** {improvement:.1f} 個百分點
253
-
254
- 請提供:
255
- 1. 兩種情境的效益評估與對比
256
- 2. 疫苗接種對隔離政策的影響分析
257
- 3. 針對兩種情境的具體隔離建議
258
- 4. 實務操作建議
259
-
260
- 請用繁體中文回答,簡潔專業。"""
261
-
262
- response = call_gpt4_quarantine(prompt, st.session_state.api_key)
263
- st.session_state.tab2_chat_history.append({
264
- "type": "dual_summary",
265
- "ct_booster": ct_booster, "days_booster": days_booster, "eff_booster": eff_booster,
266
- "ct_no_booster": ct_no_booster, "days_no_booster": days_no_booster, "eff_no_booster": eff_no_booster,
267
- "response": response
268
- })
269
-
270
- with st.expander("🎯 使用說明"):
271
- st.markdown("""
272
- **左側滑桿:** 手動探索參數,觀察3D圖變化
273
- **右側AI:** 獨立查詢,不會影響左側滑桿
274
- **AI特性:** 自動同時分析有/無加強劑兩種情況
275
- """)
276
-
277
- col1, col2 = st.columns([2, 1])
278
-
279
- with col1:
280
- ct_range = np.arange(10, 25.5, 0.5)
281
- day_range = np.arange(1, 22, 1)
282
- ct_mesh, day_mesh = np.meshgrid(ct_range, day_range)
283
- effectiveness_with_booster = np.zeros_like(ct_mesh)
284
- effectiveness_without_booster = np.zeros_like(ct_mesh)
285
-
286
- for i in range(len(day_range)):
287
- for j in range(len(ct_range)):
288
- effectiveness_with_booster[i, j] = get_quarantine_effectiveness(ct_mesh[i, j], day_mesh[i, j], True)
289
- effectiveness_without_booster[i, j] = get_quarantine_effectiveness(ct_mesh[i, j], day_mesh[i, j], False)
290
-
291
- user_eff_booster = get_quarantine_effectiveness(ct_booster, days_booster, True)
292
- user_eff_no_booster = get_quarantine_effectiveness(ct_no_booster, days_no_booster, False)
293
-
294
- st.markdown("### 💉 有加強劑情境")
295
- fig1 = create_3d_plot(ct_mesh, day_mesh, effectiveness_with_booster, ct_booster, days_booster,
296
- user_eff_booster, True, "隔離檢疫效益 - 有加強劑 (Omicron)",
297
- [[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')
298
- st.plotly_chart(fig1, use_container_width=True)
299
-
300
- st.markdown("### ⚠️ 無加強劑情境")
301
- fig2 = create_3d_plot(ct_mesh, day_mesh, effectiveness_without_booster, ct_no_booster, days_no_booster,
302
- user_eff_no_booster, False, "隔離檢疫效益 - 無加強劑 (Omicron)",
303
- [[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')
304
- st.plotly_chart(fig2, use_container_width=True)
305
-
306
- st.markdown("### 📊 雙情境對比")
307
- col_info1, col_info2 = st.columns(2)
308
- with col_info1:
309
- st.info(f"""**💉 有加強劑情境**\n- Ct值: {ct_booster} ({get_ct_category(ct_booster)})\n- 隔離: {days_booster} 天\n- 效益: {user_eff_booster*100:.0f}%""")
310
- with col_info2:
311
- st.warning(f"""**⚠️ 無加強劑情境**\n- Ct值: {ct_no_booster} ({get_ct_category(ct_no_booster)})\n- 隔離: {days_no_booster} 天\n- 效益: {user_eff_no_booster*100:.0f}%""")
312
-
313
- with col2:
314
- st.subheader("🤖 AI 智能查詢")
315
- st.info("💡 AI會自動分析有/無加強劑兩種情況,不會影響左側滑桿")
316
-
317
- user_input = st.text_input("輸入問題", placeholder="例如: Ct 18 隔離7天效益如何?", label_visibility="collapsed", key="tab2_user_input")
318
-
319
- if st.button("📤 發送", use_container_width=True, key="tab2_send"):
320
- if not st.session_state.api_key:
321
- st.error("❌ 請先輸入 OpenAI API Key")
322
- elif user_input.strip():
323
- with st.spinner("AI 分析中..."):
324
- extracted_ct, extracted_days, target_eff, extracted_booster = extract_quarantine_params(user_input)
325
-
326
- # 使用提取的Ct值,若無則使用滑桿的值作為參考
327
- ct_to_use = extracted_ct if extracted_ct else ct_booster
328
-
329
- # 🔥 根據查詢類型構建不同的 prompt
330
- if target_eff is not None:
331
- # 反向查詢:要達到X%效益需要多少天
332
- days_with = find_days_for_target_quarantine(ct_to_use, target_eff, True)
333
- days_without = find_days_for_target_quarantine(ct_to_use, target_eff, False)
334
- eff_with = get_quarantine_effectiveness(ct_to_use, days_with, True)
335
- eff_without = get_quarantine_effectiveness(ct_to_use, days_without, False)
336
-
337
- prompt = f"""用戶問題:{user_input}
338
-
339
- 根據精確計算結果:
340
-
341
- **情境1 - 有接種加強劑:**
342
- - Ct 值: {ct_to_use}
343
- - 目標效益: {target_eff*100:.0f}%
344
- - 需要隔離: {days_with} 天
345
- - 實際達到: {eff_with*100:.1f}%
346
-
347
- **情境2 - 沒有接種加強劑:**
348
- - Ct 值: {ct_to_use}
349
- - 目標效益: {target_eff*100:.0f}%
350
- - 需要隔離: {days_without} 天
351
- - 實際達到: {eff_without*100:.1f}%
352
-
353
- **關鍵差異:** 接種加強劑可以減少 {days_without - days_with} 天隔離
354
-
355
- 請用繁體中文簡潔回答:
356
- 1️⃣ 有加強劑需要隔離{days_with}天
357
- 2️⃣ 沒有加強劑需要隔離{days_without}天
358
- 3️⃣ 疫苗可以縮短{days_without - days_with}天
359
-
360
- 直接回答數據,不要解釋背景知識。"""
361
-
362
- elif extracted_days is not None:
363
- # 正向查詢:隔離X天的效益
364
- days_to_use = extracted_days
365
- eff_with = get_quarantine_effectiveness(ct_to_use, days_to_use, True)
366
- eff_without = get_quarantine_effectiveness(ct_to_use, days_to_use, False)
367
-
368
- prompt = f"""用戶問題:{user_input}
369
-
370
- 根據精確計算結果:
371
-
372
- **情境1 - 有接種加強劑:**
373
- - Ct 值: {ct_to_use}
374
- - 隔離天數: {days_to_use} 天
375
- - 防疫效益: {eff_with*100:.1f}%
376
-
377
- **情境2 - 沒有接種加強劑:**
378
- - Ct 值: {ct_to_use}
379
- - 隔離天數: {days_to_use} 天
380
- - 防疫效益: {eff_without*100:.1f}%
381
-
382
- **關鍵差異:** 疫苗提升 {(eff_with - eff_without)*100:.1f} 個百分點效益
383
-
384
- 請用繁體中文簡潔回答:
385
- 1️⃣ 有加強劑:隔離{days_to_use}天可達{eff_with*100:.1f}%效益
386
- 2️⃣ 沒有加強劑:隔離{days_to_use}天可達{eff_without*100:.1f}%效益
387
- 3️⃣ 疫苗優勢:提升{(eff_with - eff_without)*100:.1f}個百分點
388
-
389
- 直接回答數據,不要解釋背景知識。"""
390
-
391
- else:
392
- # 一般查詢
393
- prompt = f"""用戶問題:{user_input}
394
-
395
- 作為專業防疫顧問,請針對 Ct={ct_to_use} 的情況,同時分析有/無加強劑兩種情境。
396
-
397
- 請用繁體中文回答,必須包含:
398
- 1️⃣ 有接種加強劑的情況(天數、效益)
399
- 2️⃣ 沒有接種加強劑的情況(天數、效益)
400
- 3️⃣ 兩者比較(疫苗優勢)
401
-
402
- 簡潔回答,不要解釋背景知識。"""
403
-
404
- response = call_gpt4_quarantine(prompt, st.session_state.api_key)
405
-
406
- # ✅ 只保存對話記錄,完全不影響左側滑桿
407
- st.session_state.tab2_chat_history.append({
408
- "type": "user_query",
409
- "question": user_input,
410
- "response": response
411
- })
412
-
413
- st.markdown("---")
414
- st.markdown("##### 📜 對話記錄")
415
- if st.session_state.tab2_chat_history:
416
- for chat in reversed(st.session_state.tab2_chat_history):
417
- with st.container():
418
- if chat["type"] == "dual_summary":
419
- st.markdown(f"""**🤖 AI雙情境分析**
420
- - 💉有加強劑: Ct={chat['ct_booster']}, {chat['days_booster']}天, {chat['eff_booster']*100:.0f}%
421
- - ⚠️無加強劑: Ct={chat['ct_no_booster']}, {chat['days_no_booster']}天, {chat['eff_no_booster']*100:.0f}%""")
422
- st.markdown(chat["response"])
423
- else:
424
- st.markdown(f"**👤 問題:** {chat['question']}")
425
- st.markdown(f"**🤖 回答:** {chat['response']}")
426
- st.markdown("---")
427
- if st.button("🗑️ 清除記錄", use_container_width=True, key="tab2_clear"):
428
- st.session_state.tab2_chat_history = []
429
- st.rerun()
430
- else:
431
- st.info("💡 輸入問題開始查詢\n\nAI會自動分析兩種情況")
432
-
433
- st.markdown("---")
434
- with st.expander("📊 數據對比表"):
435
- test_days = [3, 5, 7, 10, 14]
436
- st.markdown(f"**以 Ct={ct_booster} 為例**")
437
- df = pd.DataFrame({
438
- '天數': test_days,
439
- '有加強劑': [f"{get_quarantine_effectiveness(ct_booster,d,True)*100:.0f}%" for d in test_days],
440
- '無加強劑': [f"{get_quarantine_effectiveness(ct_booster,d,False)*100:.0f}%" for d in test_days],
441
- '差異': [f"+{(get_quarantine_effectiveness(ct_booster,d,True)-get_quarantine_effectiveness(ct_booster,d,False))*100:.0f}%" for d in test_days]
442
- })
443
- st.dataframe(df, use_container_width=True)