-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathconstants.py
More file actions
365 lines (294 loc) · 9.15 KB
/
constants.py
File metadata and controls
365 lines (294 loc) · 9.15 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
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
'''
Constants are held as functions to make it easier to do sensitivity
analyses later.
'''
import enum
import numpy.random as rng
import inflation
# These don't really match with the rest of the functions in the file but
# it is probably the best place to put these enumerators
class Coordinates(object):
def __init__(self, latitude, longitude):
self.latitude = latitude
self.longitude = longitude
class Sex(enum.IntEnum):
MALE = 0
FEMALE = 1
class States(enum.IntEnum):
'''
Make the model a little easier to read
'''
GEN_POP = 0
MRS_0 = 1
MRS_1 = 2
MRS_2 = 3
MRS_3 = 4
MRS_4 = 5
MRS_5 = 6
MRS_6 = 7
DEATH = MRS_6
NUMBER_OF_STATES = 8
def time_limit_tpa():
return 270
def time_limit_evt():
return 360
def door_to_needle_primary(base_case=False):
'''
Primary data from Gregg Fonarow, not yet published.
Mean -> 69.17
STD -> 41.67
25th -> 47.00
75th -> 83.00
Median -> 61.00
'''
time = None
if base_case:
time = 61.00
else:
time = rng.uniform(47.00, 83.00)
return time
def door_to_needle_comprehensive(base_case=False):
'''
Primary data from Gregg Fonarow, not yet published.
Mean -> 58.92
STD -> 35.26
25th -> 39.00
75th -> 70.00
Median -> 52.00
'''
time = None
if base_case:
time = 52.00
else:
time = rng.uniform(39.00, 70.00)
return time
def door_to_intra_arterial_comprehensive(base_case=False):
'''
Primary data from Gregg Fonarow, not yet published.
Mean -> 174.21
STD -> 105.00
25th -> 192.00
75th -> 83.00
Median -> 145.00
'''
time = None
if base_case:
time = 145.00
else:
time = rng.uniform(83.00, 192.00)
return time
class Times(object):
'''
Initial setup is with base-case times, but we include a function
to allow for
'''
door_needle_primary = door_to_needle_primary(True)
door_needle_comprehensive = door_to_needle_comprehensive(True)
door_to_intra_arterial = door_to_intra_arterial_comprehensive(True)
# Assumed right now to be the difference between door to IA
# and door to needle. This is likely an overestimate of the time
# to EVT.
transfer_to_intra_arterial = (door_to_intra_arterial - door_needle_primary)
@staticmethod
def get_random_set():
Times.door_needle_primary = door_to_needle_primary()
Times.door_needle_comprehensive = door_to_needle_comprehensive()
Times.door_to_intra_arterial = door_to_intra_arterial_comprehensive()
# Assumed right now to be the difference between door to IA
# and door to needle. This is likely an overestimate of the time
# to EVT.
Times.transfer_to_intra_arterial = (
Times.door_to_intra_arterial - Times.door_needle_primary)
@staticmethod
def set_to_default():
Times.door_needle_primary = door_to_needle_primary(True)
Times.door_needle_comprehensive = door_to_needle_comprehensive(True)
Times.door_to_intra_arterial = door_to_intra_arterial_comprehensive(
True)
Times.transfer_to_intra_arterial = (
Times.door_to_intra_arterial - Times.door_needle_primary)
def race_to_nihss(race):
'''
For now assuming that this is a constant; it was originally with the
ischemic transitions, but at this point it makes more sense for it to
just be a constant since it's used for hemorrhagic strokes as well
'''
nihss = None
# Perez de la Ossa et al. Stroke 2014, Schlemm analysis
if race == 0:
nihss = 1
else:
nihss = -0.39 + 2.39 * race
return nihss
def no_tx_where_to_go(race):
'''
http://stroke.ahajournals.org/content/45/1/87.full
Okay so this cutoff is based on work by Perez de la Ossa where she finds
that patients with a RACE >= 5 should be considered as with an LVO,
and patients with a RACE < 5 are likely not canditates for invasive
therapies.
'''
if race >= 5:
return "Comprehensive"
else:
return "Primary"
def nihss_to_race(nihss):
'''
Based on a simple linear regression
'''
race = None
if nihss == 1:
race = 0
else:
race = (nihss + 0.39) / 2.39
return race
# PLUMBER Study
def p_call_is_mimic():
# incude TIA
return (1635 + 191) / 2402
# PLUMBER Study
def p_call_is_hemorrhagic():
# include ICH and SAH
return (16 + 85) / 2402
def break_up_ais_patients(p_good_outcome, NIHSS):
'''
From pooled meta-analysis in supplement of Saver et al. 2016, we
break up the good and bad outcome (mRS 0 - 2 and 3 - 5 respectively)
patients into proportions independent of time to treatment
However, we consider the proportion of patients that die to be a constant
regardless of time to treatment
Probaility of mortality: 0.171361502
Probabilities of mRS 0 - 2: 0.205627706, 0.341991342, 0.452380952
Probabilities of mRS 3 - 5: 0.35678392, 0.432160804, 0.211055276
'''
# Assume that probability of death is always constant
# Stratified by NIHSS, ask Dr. Schwamm to get raw data for a continuous
# approach
genpop = 0
mrs6 = None
if NIHSS < 7:
mrs6 = 0.042
elif NIHSS < 13:
mrs6 = 0.139
elif NIHSS < 21:
mrs6 = 0.316
else:
mrs6 = 0.535
# Good outcomes
mrs0 = 0.205627706 * p_good_outcome
mrs1 = 0.341991342 * p_good_outcome
mrs2 = p_good_outcome - mrs1 - mrs0
# And bad outcomes
mrs3 = 0.35678392 * (1 - p_good_outcome - mrs6)
mrs4 = 0.432160804 * (1 - p_good_outcome - mrs6)
mrs5 = 0.211055276 * (1 - p_good_outcome - mrs6)
return [genpop, mrs0, mrs1, mrs2, mrs3, mrs4, mrs5, mrs6]
HAZARDS_MORTALITY = {
States.GEN_POP: 1,
States.MRS_0: 1.53,
States.MRS_1: 1.52,
States.MRS_2: 2.17,
States.MRS_3: 3.18,
States.MRS_4: 4.55,
States.MRS_5: 6.55
}
def hazard_mort(mrs):
'''
Again keep it as a function so that it's easier to do sensitivity analyses
later on.
'''
return HAZARDS_MORTALITY[mrs]
UTILITIES = {
States.GEN_POP: 1.00,
States.MRS_0: 1.00,
States.MRS_1: 0.84,
States.MRS_2: 0.78,
States.MRS_3: 0.71,
States.MRS_4: 0.44,
States.MRS_5: 0.18
}
def utilities_mrs(mrs):
return UTILITIES[mrs]
# -----------------------------------
# COSTS
# -----------------------------------
class Costs(object):
TARGET_YEAR = inflation.Conversion.LAST_YEAR
# 2014, Dewilde
DAYS_90_ISCHEMIC = {
States.GEN_POP: 0,
States.MRS_0: 6302,
States.MRS_1: 9448,
States.MRS_2: 14918,
States.MRS_3: 26218,
States.MRS_4: 32502,
States.MRS_5: 26071
}
# 2008, Christensen
DAYS_90_ICH = {
States.GEN_POP: 0,
States.MRS_0: 9500,
States.MRS_1: 15500,
States.MRS_2: 18700,
States.MRS_3: 27400,
States.MRS_4: 27300,
States.MRS_5: 27300
}
# 2014, Dewilde
ANNUAL = {
States.GEN_POP: 0,
States.MRS_0: 2921,
States.MRS_1: 3905,
States.MRS_2: 6501,
States.MRS_3: 16922,
States.MRS_4: 42335,
States.MRS_5: 39723
}
# 2008, Christensen
DEATH = 8100
# 2014, Sevick
IVT = 13419
# 2014, Kleindorfer
EVT = 6400
# 2010, Mohr
TRANSFER = 763
@staticmethod
def inflate(TARGET_YEAR):
for state in Costs.DAYS_90_ISCHEMIC:
Costs.DAYS_90_ISCHEMIC[state] = (inflation.Conversion.run(
2014, TARGET_YEAR, Costs.DAYS_90_ISCHEMIC[state]))
for state in Costs.DAYS_90_ICH:
Costs.DAYS_90_ICH[state] = (inflation.Conversion.run(
2008, TARGET_YEAR, Costs.DAYS_90_ICH[state]))
for state in Costs.ANNUAL:
Costs.ANNUAL[state] = (inflation.Conversion.run(
2014, TARGET_YEAR, Costs.ANNUAL[state]))
Costs.DEATH = inflation.Conversion.run(2008, TARGET_YEAR, Costs.DEATH)
Costs.IVT = inflation.Conversion.run(2014, TARGET_YEAR, Costs.IVT)
Costs.EVT = inflation.Conversion.run(2014, TARGET_YEAR, Costs.EVT)
Costs.TRANSFER = inflation.Conversion.run(2010, TARGET_YEAR,
Costs.TRANSFER)
def cost_ivt():
return Costs.IVT
def cost_evt():
return Costs.EVT
def cost_transfer():
return Costs.TRANSFER
def first_year_costs(states_hemorrhagic, states_ischemic):
cost_hemorrhagic = [
states_hemorrhagic[i] * ((90 / 360) * Costs.DAYS_90_ICH[i] +
((360 - 90) / 360) * Costs.ANNUAL[i])
for i in range(States.DEATH)
]
cost_hemorrhagic.append(Costs.DEATH * states_hemorrhagic[States.DEATH])
cost_ischemic = [
states_ischemic[i] * ((90 / 360) * Costs.DAYS_90_ISCHEMIC[i] +
((360 - 90) / 360) * Costs.ANNUAL[i])
for i in range(States.DEATH)
]
cost_ischemic.append(Costs.DEATH * states_ischemic[States.DEATH])
return sum(cost_hemorrhagic) + sum(cost_ischemic)
def annual_cost(states):
cost = sum([states[i] * Costs.ANNUAL[i] for i in range(States.DEATH)])
cost += states[States.DEATH] * Costs.DEATH
return cost