Skip to content

Commit fcf4293

Browse files
CriticalHealthProfile now anchors ward admissions at severeness_onset (now on parity with SevereHealthProfile)
1 parent 918a070 commit fcf4293

7 files changed

Lines changed: 49 additions & 42 deletions

File tree

‎data/DefaultConf.toml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,16 @@
142142
icu_admission_to_ventilation_admission = 0
143143
ventilation_admission_to_ventilation_discharge = 0
144144
ventilation_discharge_to_icu_discharge = 0
145-
# hospital admission delay (after critical onset)
146-
[Pathogens.Covid19.progressions.Critical.health.critical_onset_to_hospital_admission]
145+
# ward admission delay (after severeness onset), unless the ICU claims the patient first
146+
[Pathogens.Covid19.progressions.Critical.health.severeness_onset_to_hospital_admission]
147147
distribution = "Poisson"
148148
parameters = [1]
149149
# ward stay length when not admitted to ICU (after hospital admission)
150150
[Pathogens.Covid19.progressions.Critical.health.hospital_admission_to_hospital_discharge]
151151
distribution = "Poisson"
152152
parameters = [10]
153-
# ICU admission delay (after hospital admission)
154-
[Pathogens.Covid19.progressions.Critical.health.hospital_admission_to_icu_admission]
153+
# ICU admission delay (after critical onset)
154+
[Pathogens.Covid19.progressions.Critical.health.critical_onset_to_icu_admission]
155155
distribution = "Poisson"
156156
parameters = [1]
157157
# ICU stay length when not ventilated (after ICU admission)

‎docs/src/config-files.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,13 @@ If you want to set up a custom config file, you can copy this one into your own
132132
icu_admission_to_ventilation_admission = 0
133133
ventilation_admission_to_ventilation_discharge = 0
134134
ventilation_discharge_to_icu_discharge = 0
135-
[Pathogens.Covid19.progressions.Critical.health.critical_onset_to_hospital_admission]
135+
[Pathogens.Covid19.progressions.Critical.health.severeness_onset_to_hospital_admission]
136136
distribution = "Poisson"
137137
parameters = [1]
138138
[Pathogens.Covid19.progressions.Critical.health.hospital_admission_to_hospital_discharge]
139139
distribution = "Poisson"
140140
parameters = [10]
141-
[Pathogens.Covid19.progressions.Critical.health.hospital_admission_to_icu_admission]
141+
[Pathogens.Covid19.progressions.Critical.health.critical_onset_to_icu_admission]
142142
distribution = "Poisson"
143143
parameters = [1]
144144
[Pathogens.Covid19.progressions.Critical.health.icu_admission_to_icu_discharge]

‎src/pathogen/health_progression/health_profiles.jl‎

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,18 @@ end
5151
Health profile for an infection whose peak tier is `critical`: a hospital admission that can
5252
escalate to ICU and ventilation, plus mortality risk. Each step's probability is conditional
5353
on the step below (`hospital_to_icu_probability` = P(ICU | hospitalized), `icu_to_ventilation_probability` =
54-
P(ventilation | ICU)); discharges chain inward-out so the stays nest by construction. Timings
55-
are anchored at the infection's `critical_onset`. Care and mortality are computed independently
56-
here and reconciled once, downstream, by `compute_health!`.
54+
P(ventilation | ICU)); discharges chain inward-out so the stays nest by construction. Care and
55+
mortality are computed independently here and reconciled once, downstream, by `compute_health!`.
56+
57+
The ward is anchored at `severeness_onset`, the ICU at `critical_onset`. If the ICU admission comes
58+
first, the ward admission is moved to it.
5759
5860
# Parameters
5961
- `hospital_probability::Real`: Hospital admission probability (`0.0` by default).
60-
- `critical_onset_to_hospital_admission::Union{Distribution, Real}`: Admission delay after critical onset.
62+
- `severeness_onset_to_hospital_admission::Union{Distribution, Real}`: Admission delay after severeness onset, unless the ICU admission comes first.
6163
- `hospital_admission_to_hospital_discharge::Union{Distribution, Real}`: Ward stay length when the patient does not enter the ICU.
6264
- `hospital_to_icu_probability::Real`: ICU probability for a hospitalized patient (`0.0` by default).
63-
- `hospital_admission_to_icu_admission::Union{Distribution, Real}`: Delay from hospital to ICU admission.
65+
- `critical_onset_to_icu_admission::Union{Distribution, Real}`: ICU admission delay after critical onset.
6466
- `icu_admission_to_icu_discharge::Union{Distribution, Real}`: ICU stay length when the patient is not ventilated.
6567
- `icu_to_ventilation_probability::Real`: Ventilation probability for an ICU patient (`0.0` by default).
6668
- `icu_admission_to_ventilation_admission::Union{Distribution, Real}`: Delay from ICU to ventilation admission.
@@ -72,10 +74,10 @@ here and reconciled once, downstream, by `compute_health!`.
7274
"""
7375
struct CriticalHealthProfile <: HealthProfile
7476
hospital_probability::Real
75-
critical_onset_to_hospital_admission::Union{Distribution, Real}
77+
severeness_onset_to_hospital_admission::Union{Distribution, Real}
7678
hospital_admission_to_hospital_discharge::Union{Distribution, Real}
7779
hospital_to_icu_probability::Real
78-
hospital_admission_to_icu_admission::Union{Distribution, Real}
80+
critical_onset_to_icu_admission::Union{Distribution, Real}
7981
icu_admission_to_icu_discharge::Union{Distribution, Real}
8082
icu_to_ventilation_probability::Real
8183
icu_admission_to_ventilation_admission::Union{Distribution, Real}
@@ -87,10 +89,10 @@ struct CriticalHealthProfile <: HealthProfile
8789

8890
function CriticalHealthProfile(;
8991
hospital_probability = 0.0,
90-
critical_onset_to_hospital_admission = 0,
92+
severeness_onset_to_hospital_admission = 0,
9193
hospital_admission_to_hospital_discharge = 0,
9294
hospital_to_icu_probability = 0.0,
93-
hospital_admission_to_icu_admission = 0,
95+
critical_onset_to_icu_admission = 0,
9496
icu_admission_to_icu_discharge = 0,
9597
icu_to_ventilation_probability = 0.0,
9698
icu_admission_to_ventilation_admission = 0,
@@ -107,9 +109,9 @@ struct CriticalHealthProfile <: HealthProfile
107109
0.0 <= p <= 1.0 || throw(ArgumentError("$nm must be between 0 and 1 (got $p)."))
108110
end
109111

110-
return new(hospital_probability, critical_onset_to_hospital_admission,
112+
return new(hospital_probability, severeness_onset_to_hospital_admission,
111113
hospital_admission_to_hospital_discharge, hospital_to_icu_probability,
112-
hospital_admission_to_icu_admission, icu_admission_to_icu_discharge,
114+
critical_onset_to_icu_admission, icu_admission_to_icu_discharge,
113115
icu_to_ventilation_probability, icu_admission_to_ventilation_admission,
114116
ventilation_admission_to_ventilation_discharge, ventilation_discharge_to_icu_discharge,
115117
icu_discharge_to_hospital_discharge, death_probability, critical_onset_to_death)
@@ -119,9 +121,9 @@ end
119121
"""
120122
calculate_health_profile(cc::CriticalHealthProfile, individual::Individual, infection::InfectionState, rng::Xoshiro)
121123
122-
Care and mortality contribution of a single `critical`-peak infection, anchored at
123-
`infection.critical_onset`. Hospital, ICU, and ventilation escalate via nested Bernoulli draws;
124-
death is drawn independently of them.
124+
Care and mortality contribution of a single `critical`-peak infection, the ward anchored at
125+
`infection.severeness_onset` and the ICU at `infection.critical_onset`. Hospital, ICU, and
126+
ventilation escalate via nested Bernoulli draws; death is drawn independently of them.
125127
"""
126128
function calculate_health_profile(cc::CriticalHealthProfile, individual::Individual, infection::InfectionState, rng::Xoshiro)
127129
hospital_admission::Int16 = Int16(-1)
@@ -133,9 +135,11 @@ function calculate_health_profile(cc::CriticalHealthProfile, individual::Individ
133135
death::Int16 = Int16(-1)
134136

135137
if gems_rand(rng) <= cc.hospital_probability
136-
hospital_admission = rand_round(infection.critical_onset + _rand_val(cc.critical_onset_to_hospital_admission, rng), rng)
138+
hospital_admission = rand_round(infection.severeness_onset + _rand_val(cc.severeness_onset_to_hospital_admission, rng), rng)
137139
if gems_rand(rng) <= cc.hospital_to_icu_probability
138-
icu_admission = rand_round(hospital_admission + _rand_val(cc.hospital_admission_to_icu_admission, rng), rng)
140+
icu_admission = rand_round(infection.critical_onset + _rand_val(cc.critical_onset_to_icu_admission, rng), rng)
141+
# the ward admission cannot come after the ICU admission
142+
hospital_admission = min(hospital_admission, icu_admission)
139143
if gems_rand(rng) <= cc.icu_to_ventilation_probability
140144
ventilation_admission = rand_round(icu_admission + _rand_val(cc.icu_admission_to_ventilation_admission, rng), rng)
141145
ventilation_discharge = rand_round(ventilation_admission + _rand_val(cc.ventilation_admission_to_ventilation_discharge, rng), rng)

‎src/pathogen/legacy_progressions/legacy_config.jl‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
_is_legacy_critical(crit)
1313
1414
`true` if a `Critical` progression config table uses the pre-decoupling health format, i.e. carries
15-
`icu_admission_to_death` or `severeness_onset_to_hospital_admission`. A modern `Critical` (new disease
15+
`icu_admission_to_death` or `hospital_discharge_to_severeness_offset`. A modern `Critical` (new disease
1616
tier, optionally with embedded care) carries neither.
1717
"""
1818
_is_legacy_critical(crit) = crit isa Dict &&
19-
(haskey(crit, "icu_admission_to_death") || haskey(crit, "severeness_onset_to_hospital_admission"))
19+
(haskey(crit, "icu_admission_to_death") || haskey(crit, "hospital_discharge_to_severeness_offset"))
2020

2121
"""
2222
_normalize_legacy_pathogen!(params::Dict)

‎test/healthprogressiontest.jl‎

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -126,22 +126,22 @@ end
126126
critical_offset = Int16(15), severeness_offset = Int16(16), recovery = Int16(20))
127127
inf_crit = InfectionState(Int8(1), Int32(-1), dp_crit)
128128
# death is set well after the care ladder resolves
129-
cc = CriticalHealthProfile(hospital_probability = 1.0, critical_onset_to_hospital_admission = 0,
130-
hospital_to_icu_probability = 1.0, hospital_admission_to_icu_admission = 0,
129+
cc = CriticalHealthProfile(hospital_probability = 1.0, severeness_onset_to_hospital_admission = 0,
130+
hospital_to_icu_probability = 1.0, critical_onset_to_icu_admission = 0,
131131
icu_admission_to_icu_discharge = 5, icu_discharge_to_hospital_discharge = 3,
132132
death_probability = 1.0, critical_onset_to_death = 20)
133133
care2, outcome2 = calculate_health_profile(cc, ind, inf_crit, rng)
134-
@test care2.hospital_admission == 8
135-
@test care2.icu_admission == 8
134+
@test care2.hospital_admission == 5
135+
@test care2.icu_admission == 8
136136
@test care2.icu_discharge == 13
137137
@test care2.hospital_discharge == 16
138138
@test outcome2.death == 28
139139
@test care2.ventilation_admission == -1
140140

141141
# full critical ladder: guaranteed ventilation nests inside ICU which nests inside the ward.
142142
# discharges chain inward-out, so ventilation ends first, then ICU, then hospital.
143-
cc_vent = CriticalHealthProfile(hospital_probability = 1.0, critical_onset_to_hospital_admission = 0,
144-
hospital_to_icu_probability = 1.0, hospital_admission_to_icu_admission = 0,
143+
cc_vent = CriticalHealthProfile(hospital_probability = 1.0, severeness_onset_to_hospital_admission = 0,
144+
hospital_to_icu_probability = 1.0, critical_onset_to_icu_admission = 0,
145145
icu_to_ventilation_probability = 1.0, icu_admission_to_ventilation_admission = 0,
146146
ventilation_admission_to_ventilation_discharge = 4, ventilation_discharge_to_icu_discharge = 2,
147147
icu_discharge_to_hospital_discharge = 3)
@@ -154,7 +154,7 @@ end
154154

155155
# cascading-off caveat: hospital_to_icu_probability set without hospital_probability is a no-op,
156156
# since ICU is gated behind a hospital admission that never happens
157-
cc2 = CriticalHealthProfile(hospital_to_icu_probability = 1.0, hospital_admission_to_icu_admission = 0,
157+
cc2 = CriticalHealthProfile(hospital_to_icu_probability = 1.0, critical_onset_to_icu_admission = 0,
158158
icu_admission_to_icu_discharge = 5)
159159
care3, _ = calculate_health_profile(cc2, ind, inf_crit, rng)
160160
@test care3.hospital_admission == -1
@@ -232,7 +232,7 @@ end
232232
@testset "DefaultHealthProgression folds across a host's infections" begin
233233
rng = Xoshiro(1)
234234
cc = CriticalHealthProfile(hospital_probability = 1.0, hospital_to_icu_probability = 1.0, death_probability = 1.0,
235-
critical_onset_to_hospital_admission = 0, hospital_admission_to_icu_admission = 0,
235+
severeness_onset_to_hospital_admission = 0, critical_onset_to_icu_admission = 0,
236236
icu_admission_to_icu_discharge = 5, icu_discharge_to_hospital_discharge = 3,
237237
critical_onset_to_death = 9)
238238
hp = DefaultHealthProgression()
@@ -256,8 +256,11 @@ end
256256
filed = _filed(sched, 1)
257257
# two stays: hospital+ICU in, ICU+hospital out, twice over
258258
@test length(filed) == 8
259-
@test (Int16(4), CARE_HOSPITAL, true) in filed # pathogen 1, critical_onset 4
260-
@test (Int16(20), CARE_HOSPITAL, true) in filed # pathogen 2, critical_onset 20
259+
# the ward is entered at severeness onset, the ICU at critical onset
260+
@test (Int16(3), CARE_HOSPITAL, true) in filed # pathogen 1, severeness_onset 3
261+
@test (Int16(4), CARE_ICU, true) in filed # pathogen 1, critical_onset 4
262+
@test (Int16(6), CARE_HOSPITAL, true) in filed # pathogen 2, severeness_onset 6
263+
@test (Int16(20), CARE_ICU, true) in filed # pathogen 2, critical_onset 20
261264
@test count(f -> f[2] === CARE_HOSPITAL && f[3], filed) == 2
262265

263266
# earliest death wins
@@ -402,7 +405,7 @@ end
402405
hp = DefaultHealthProgression()
403406
# only pathogen 1 carries a profile; pathogen 2 misses the index entirely
404407
index = _idx((1, 0) => CriticalHealthProfile(hospital_probability = 0.4,
405-
critical_onset_to_hospital_admission = 1, hospital_admission_to_hospital_discharge = 3))
408+
severeness_onset_to_hospital_admission = 1, hospital_admission_to_hospital_discharge = 3))
406409

407410
function stays(n, coinfect::Bool)
408411
rng = Xoshiro(1234); episodes = 0
@@ -436,9 +439,9 @@ end
436439
critical_onset_to_critical_offset = Poisson(5), critical_offset_to_severeness_offset = Poisson(3),
437440
severeness_offset_to_recovery = Poisson(10))
438441
crit = Critical(; dkw..., health = CriticalHealthProfile(
439-
hospital_probability = 0.4, critical_onset_to_hospital_admission = Poisson(1),
442+
hospital_probability = 0.4, severeness_onset_to_hospital_admission = Poisson(1),
440443
hospital_admission_to_hospital_discharge = Poisson(8),
441-
hospital_to_icu_probability = 0.3, hospital_admission_to_icu_admission = Poisson(1),
444+
hospital_to_icu_probability = 0.3, critical_onset_to_icu_admission = Poisson(1),
442445
icu_admission_to_icu_discharge = Poisson(5), icu_discharge_to_hospital_discharge = Poisson(2),
443446
death_probability = 0.15, critical_onset_to_death = Poisson(6)))
444447
pA = Pathogen(id = 1, name = "A", progressions = [crit],
@@ -713,9 +716,9 @@ end
713716
"hospital_admission_to_hospital_discharge" => Dict("distribution" => "Poisson", "parameters" => [10])),
714717
"critical" => Dict(
715718
"hospital_probability" => 0.9, "hospital_to_icu_probability" => 0.6, "death_probability" => 0.3,
716-
"critical_onset_to_hospital_admission" => Dict("distribution" => "Poisson", "parameters" => [1]),
719+
"severeness_onset_to_hospital_admission" => Dict("distribution" => "Poisson", "parameters" => [1]),
717720
"hospital_admission_to_hospital_discharge" => Dict("distribution" => "Poisson", "parameters" => [10]),
718-
"hospital_admission_to_icu_admission" => Dict("distribution" => "Poisson", "parameters" => [1]),
721+
"critical_onset_to_icu_admission" => Dict("distribution" => "Poisson", "parameters" => [1]),
719722
"icu_admission_to_icu_discharge" => Dict("distribution" => "Poisson", "parameters" => [8]),
720723
"icu_discharge_to_hospital_discharge" => Dict("distribution" => "Poisson", "parameters" => [5]),
721724
"critical_onset_to_death" => Dict("distribution" => "Poisson", "parameters" => [7])))

‎test/postprocessortest.jl‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ import GEMS: _mean_contacts_per_age_group,
156156
critical_onset_to_critical_offset = Poisson(5), critical_offset_to_severeness_offset = Poisson(3),
157157
severeness_offset_to_recovery = Poisson(10))
158158
crit = Critical(; dkw..., health = CriticalHealthProfile(
159-
hospital_probability = 0.4, critical_onset_to_hospital_admission = Poisson(1),
159+
hospital_probability = 0.4, severeness_onset_to_hospital_admission = Poisson(1),
160160
hospital_admission_to_hospital_discharge = Poisson(8),
161161
death_probability = 0.15, critical_onset_to_death = Poisson(6)))
162162
mkp(i, nm) = Pathogen(id = i, name = nm, progressions = [crit],

‎test/testdata/TestConf.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@
9797
hospital_probability = 1.0
9898
hospital_to_icu_probability = 1.0
9999
death_probability = 0.3
100-
critical_onset_to_hospital_admission = 0
101-
hospital_admission_to_icu_admission = 0
100+
severeness_onset_to_hospital_admission = 0
101+
critical_onset_to_icu_admission = 0
102102

103103
# time to infectiousness (after exposure) [+1 will be added internally to avoid zero-day durations]
104104
[Pathogens.Covid19.progressions.Critical.exposure_to_infectiousness_onset]

0 commit comments

Comments
 (0)