-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlogic.py
More file actions
287 lines (247 loc) · 10.3 KB
/
logic.py
File metadata and controls
287 lines (247 loc) · 10.3 KB
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
import numpy as np
import plotly.graph_objects as go
from datetime import datetime
# --- 嘗試匯入 FPDF (防呆機制) ---
try:
from fpdf import FPDF
HAS_FPDF = True
except ImportError:
HAS_FPDF = False
# --- PDF 生成引擎 ---
if HAS_FPDF:
class PDFReport(FPDF):
def header(self):
self.set_font('Arial', 'B', 15)
self.cell(0, 10, 'RadShield AI - Clinical Decision Report', 0, 1, 'C')
self.ln(5)
def footer(self):
self.set_y(-15)
self.set_font('Arial', 'I', 8)
self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C')
def create_pdf(patient_data, risk_result):
pdf = PDFReport()
pdf.add_page()
pdf.set_font("Arial", size=12)
# 1. Patient Info
pdf.set_font("Arial", 'B', 12)
pdf.cell(0, 10, "1. Patient Information", 0, 1)
pdf.set_font("Arial", size=10)
pdf.cell(0, 6, f"Name: {patient_data['name_en']} (ID: {patient_data['id']})", 0, 1)
pdf.cell(0, 6, f"Age: {patient_data['age']} | Date: {datetime.now().strftime('%Y-%m-%d')}", 0, 1)
pdf.ln(5)
# 2. Risk Assessment
pdf.set_font("Arial", 'B', 12)
pdf.cell(0, 10, "2. Risk Stratification", 0, 1)
pdf.set_font("Arial", size=10)
# Acute
if risk_result['acute_level'] == 'High':
pdf.set_text_color(220, 53, 69)
else:
pdf.set_text_color(0, 0, 0)
pdf.cell(0, 6, f"Acute Toxicity Risk: {risk_result['acute_level']} (Score: {risk_result['acute_score']}/100)", 0, 1)
# Chronic
if risk_result['chronic_level'] == 'High':
pdf.set_text_color(220, 53, 69)
else:
pdf.set_text_color(0, 0, 0)
pdf.cell(0, 6, f"Late Toxicity Risk: {risk_result['chronic_level']} (Score: {risk_result['chronic_score']}/100)", 0, 1)
pdf.set_text_color(0, 0, 0)
pdf.ln(5)
# 3. Drivers
pdf.set_font("Arial", 'B', 12)
pdf.cell(0, 10, "3. Key Risk Drivers (Concept 4)", 0, 1)
pdf.set_font("Arial", size=10)
if risk_result['drivers_en']:
for driver in risk_result['drivers_en']:
pdf.cell(0, 6, f"- {driver}", 0, 1)
else:
pdf.cell(0, 6, "- No significant risk drivers identified.", 0, 1)
pdf.ln(5)
# 4. Recommendations
pdf.set_font("Arial", 'B', 12)
pdf.cell(0, 10, "4. Optimization Plan (Concept 3)", 0, 1)
pdf.set_font("Arial", size=10)
if risk_result['recommendations_en']:
for rec in risk_result['recommendations_en']:
pdf.multi_cell(0, 6, f"- {rec}")
else:
pdf.cell(0, 6, "- Standard of Care (SoC) recommended.", 0, 1)
pdf.ln(10)
pdf.set_font("Arial", 'I', 8)
pdf.cell(0, 6, "Disclaimer: This report is generated by a prototype AI system.", 0, 1)
return pdf.output(dest='S').encode('latin-1')
else:
def create_pdf(patient_data, risk_result):
raise ImportError("FPDF package not installed")
# --- 風險計算核心邏輯(整合版)---
def calculate_risk(patient_data):
"""
整合的風險計算函數,結合了兩個應用的邏輯
"""
acute_score = 10
chronic_score = 5
drivers = []
drivers_en = []
recommendations = []
recommendations_en = []
# 1. 臨床因子
if patient_data.get('ibd', False):
acute_score += 25
chronic_score += 30
drivers.append('發炎性腸道疾病 (IBD)')
drivers_en.append('Inflammatory Bowel Disease (IBD)')
if patient_data.get('diabetes', False):
chronic_score += 10
if patient_data.get('anticoagulants', False):
acute_score += 15
chronic_score += 25
drivers.append('使用抗凝血劑 (Bleeding Risk)')
drivers_en.append('Anticoagulant Usage (High Bleeding Risk)')
# 2. 基因組因子 (Concept 4)
if patient_data.get('atm_mutation', False):
acute_score += 20
chronic_score += 25
drivers.append('ATM 基因突變 (Radiosensitivity)')
drivers_en.append('ATM Gene Mutation (Radiosensitivity)')
if patient_data.get('nbn_mutation', False):
acute_score += 15
chronic_score += 20
drivers.append('NBN 基因突變')
drivers_en.append('NBN Gene Mutation')
if patient_data.get('lig1_mutation', False):
acute_score += 12
chronic_score += 18
drivers.append('LIG1 基因突變 (複製修復異常)')
drivers_en.append('LIG1 Mutation (Replication Repair Defect)')
if patient_data.get('lig4_mutation', False):
acute_score += 30
chronic_score += 40
drivers.append('LIG4 基因表現異常 (DNA Repair Defect)')
drivers_en.append('LIG4 Expression Abnormality (DNA Repair Defect)')
if patient_data.get('pcna_mutation', False):
acute_score += 10
chronic_score += 15
drivers.append('PCNA 變異 (複製壓力升高)')
drivers_en.append('PCNA Mutation (Replication Stress)')
if patient_data.get('rev3l_mutation', False):
acute_score += 18
chronic_score += 25
drivers.append('REV3L 變異 (Translesion 合成風險)')
drivers_en.append('REV3L Mutation (TLS Risk)')
if patient_data.get('polh_mutation', False):
acute_score += 16
chronic_score += 20
drivers.append('POLH 變異 (損傷修復不足)')
drivers_en.append('POLH Mutation (TLS Inefficiency)')
if patient_data.get('xpc_mutation', False):
acute_score += 14
chronic_score += 15
drivers.append('XPC 變異 (NER 缺陷)')
drivers_en.append('XPC Mutation (NER Defect)')
# 3. 劑量學因子
v70 = patient_data.get('rectum_v70', 0)
v50 = patient_data.get('rectum_v50', 0)
if v70 > 20:
chronic_score += (v70 - 20) * 2
drivers.append(f'Rectum V70 過高 ({v70}%)')
drivers_en.append(f'High Rectum V70 ({v70}%)')
if v50 > 50:
acute_score += (v50 - 50) * 1.5
# 4. 治療模式因子(來自 HTML 版本)
modality_multiplier = patient_data.get('modality', 1.0)
acute_score = acute_score * modality_multiplier
chronic_score = chronic_score * modality_multiplier
# 5. 年齡因子
age = patient_data.get('age', 60)
if age > 75:
chronic_score += 5
if age > 70:
acute_score += 3
acute_score = min(acute_score, 100)
chronic_score = min(chronic_score, 100)
def get_level(score):
if score < 30: return 'Low', 'green'
if score < 60: return 'Intermediate', 'orange'
return 'High', 'red'
acute_level, acute_color = get_level(acute_score)
chronic_level, chronic_color = get_level(chronic_score)
# 產生建議
if chronic_level == 'High' or v70 > 20:
recommendations.append("💡 建議使用直腸隔離凝膠 (SpaceOAR) 以增加物理距離")
recommendations.append("💡 需重新優化治療計畫:限制 Rectum V70 < 15%")
recommendations_en.append("Apply Rectal Spacer (SpaceOAR) to increase physical separation.")
recommendations_en.append("Re-optimize treatment plan: Tighten Rectum V70 constraint to < 15%.")
if any([patient_data.get('atm_mutation', False),
patient_data.get('lig4_mutation', False),
patient_data.get('rev3l_mutation', False),
patient_data.get('polh_mutation', False)]):
recommendations.append("🧬 基因組顯示高輻射敏感性:考慮減少單次劑量或轉用質子治療 (Proton)")
recommendations_en.append("Genomic Radiosensitivity: Consider hypofractionation reduction or Proton Therapy.")
if patient_data.get('anticoagulants', False) and acute_score > 40:
recommendations.append("💊 出血風險高:建議會診心臟科評估暫停抗凝血劑")
recommendations_en.append("High Bleeding Risk: Consult cardiology regarding temporary suspension of anticoagulants.")
# 計算綜合風險分數(用於族群定位圖表)
overall_score = (acute_score + chronic_score) / 2
return {
"acute_score": acute_score,
"chronic_score": chronic_score,
"overall_score": overall_score,
"acute_level": acute_level,
"chronic_level": chronic_level,
"drivers": drivers,
"drivers_en": drivers_en,
"recommendations": recommendations,
"recommendations_en": recommendations_en
}
# --- 生成族群風險定位圖表(來自 HTML 版本)---
def create_population_chart(patient_score):
"""
創建族群風險分佈圖表,顯示病患在族群中的位置
"""
# 生成常態分佈數據(模擬 1200 例病患)
mean = 40
std = 15
x = np.linspace(0, 100, 100)
y = (1 / (std * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x - mean) / std) ** 2)
# 標準化 y 值以便視覺化
y_normalized = y / y.max() * 100
fig = go.Figure()
# 族群分佈曲線(藍色主題)
fig.add_trace(go.Scatter(
x=x,
y=y_normalized,
mode='lines',
name='族群分佈 (Population)',
line=dict(color='#3b82f6', width=3),
fill='tozeroy',
fillcolor='rgba(59, 130, 246, 0.15)'
))
# 目前病患位置(深藍色標記)
patient_y = (1 / (std * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((patient_score - mean) / std) ** 2)
patient_y_normalized = patient_y / y.max() * 100
fig.add_trace(go.Scatter(
x=[patient_score],
y=[patient_y_normalized],
mode='markers',
name='目前病患 (Current Patient)',
marker=dict(
size=18,
color='#1e40af',
symbol='circle',
line=dict(width=3, color='white')
),
hovertemplate=f'風險分數: {patient_score:.1f}<extra></extra>'
))
fig.update_layout(
# title="族群風險定位 (Population Study)", # 移除重複標題
xaxis_title="預測風險分數 (Risk Score)",
yaxis_title="相對頻率",
height=300,
hovermode='x unified',
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
margin=dict(l=0, r=0, t=40, b=0),
plot_bgcolor='white',
paper_bgcolor='white',
font=dict(color='#1e293b')
)
return fig