Apply ExposureModel to both the expert and calculator apps.

This commit is contained in:
Phil Elson 2020-11-10 16:46:35 +01:00
parent 43da2d7521
commit 200e7cb14b
6 changed files with 75 additions and 50 deletions

View file

@ -217,7 +217,7 @@ class FormData:
return models.SpecificInterval(tuple(present_intervals))
def model_from_form(form: FormData) -> models.Model:
def model_from_form(form: FormData) -> models.ExposureModel:
# Initializes room with volume either given directly or as product of area and height
if form.volume_type == 'room_volume':
volume = form.room_volume
@ -250,19 +250,25 @@ def model_from_form(form: FormData) -> models.Model:
exposed_occupants = form.total_people - infected_occupants
# Initializes and returns a model with the attributes defined above
return models.Model(
room=room,
ventilation=form.ventilation(),
infected=models.InfectedPerson(
virus=virus,
presence=form.present_interval(),
mask=mask,
activity=infected_activity,
expiration=infected_expiration
return models.ExposureModel(
concentration_model=models.Model(
room=room,
ventilation=form.ventilation(),
infected=models.InfectedPopulation(
number=infected_occupants,
virus=virus,
presence=form.present_interval(),
mask=mask,
activity=infected_activity,
expiration=infected_expiration
),
),
infected_occupants=infected_occupants,
exposed_occupants=exposed_occupants,
exposed_activity=exposed_activity
exposed=models.Population(
number=exposed_occupants,
presence=form.present_interval(),
activity=exposed_activity,
mask=mask,
)
)

View file

@ -13,20 +13,19 @@ from cara import models
from .model_generator import FormData
def calculate_report_data(model: models.Model):
def calculate_report_data(model: models.ExposureModel):
resolution = 600
# TODO: Have this for exposed not infected.
t_start = model.infected.presence.boundaries()[0][0]
t_end = model.infected.presence.boundaries()[-1][1]
t_start = model.exposed.presence.boundaries()[0][0]
t_end = model.exposed.presence.boundaries()[-1][1]
times = list(np.linspace(t_start, t_end, resolution))
concentrations = [model.concentration(time) for time in times]
concentrations = [model.concentration_model.concentration(time) for time in times]
highest_const = max(concentrations)
prob = model.infection_probability()
er = model.infected.emission_rate(0.1)
exposed_occupants = model.exposed_occupants
r0 = prob * exposed_occupants / 100
er = model.concentration_model.infected.emission_rate(0.1)
exposed_occupants = model.exposed.number
r0 = model.reproduction_rate()
return {
"times": times,
@ -78,7 +77,7 @@ def minutes_to_time(minutes: int) -> str:
return f"{hour_string}:{minute_string}"
def build_report(model: models.Model, form: FormData):
def build_report(model: models.ExposureModel, form: FormData):
now = datetime.now()
time = now.strftime("%d/%m/%Y %H:%M:%S")
request = {"the": "form", "request": "data"}

View file

@ -20,7 +20,7 @@
<p class="data_title">Input data:</p>
<ul>
<li><p class="data_text">Room Volume: {{ model.room.volume }} m³</p></li>
<li><p class="data_text">Room Volume: {{ model.concentration_model.room.volume }} m³</p></li>
</ul>
<p class="data_title">Ventilation data:</p>

View file

@ -105,30 +105,29 @@ class WidgetView:
pass
def update(self):
model = self.model_state.dcs_instance()
model: models.ExposureModel = self.model_state.dcs_instance()
for plot in self.plots:
plot.update(model)
plot.update(model.concentration_model)
self.out.clear_output()
with self.out:
P = model.infection_probability()
print(f'Emission rate (quanta/hr): {model.infected.emission_rate(0)}')
print(f'Emission rate (quanta/hr): {model.concentration_model.infected.emission_rate(0.1)}')
print(f'Probability of infection: {np.round(P, 0)}%')
print(f'Number of exposed: {model.exposed_occupants}')
R0 = np.round(P / 100 * model.exposed_occupants, 1)
print(f'Number of exposed: {model.exposed.number}')
R0 = np.round(model.reproduction_rate(), 1)
print(f'Number of expected new cases (R0): {R0}')
def _build_widget(self, node):
self.widget.children += (self._build_room(node.room),)
self.widget.children += (self._build_ventilation(node.ventilation),)
self.widget.children += (self._build_infected(node.infected),)
self.widget.children += (self._build_room(node.concentration_model.room),)
self.widget.children += (self._build_ventilation(node.concentration_model.ventilation),)
self.widget.children += (self._build_infected(node.concentration_model.infected),)
self.widget.children += (self._build_exposed(node),)
def _build_exposed(self, node):
return collapsible(
[self._build_activity(node.exposed_activity)],
[self._build_activity(node.exposed.activity)],
title="Exposed"
)
@ -284,24 +283,30 @@ class WidgetView:
return self.widget
baseline_model = models.Model(
room=models.Room(volume=75),
ventilation=models.WindowOpening(
active=models.PeriodicInterval(period=120, duration=120),
inside_temp=models.PiecewiseConstant((0,24),(293,)),
outside_temp=models.PiecewiseConstant((0,24),(283,)),
cd_b=0.6, window_height=1.6, opening_length=0.6,
baseline_model = models.ExposureModel(
concentration_model=models.Model(
room=models.Room(volume=75),
ventilation=models.WindowOpening(
active=models.PeriodicInterval(period=120, duration=120),
inside_temp=models.PiecewiseConstant((0,24),(293,)),
outside_temp=models.PiecewiseConstant((0,24),(283,)),
cd_b=0.6, window_height=1.6, opening_length=0.6,
),
infected=models.InfectedPopulation(
number=1,
virus=models.Virus.types['SARS_CoV_2'],
presence=models.SpecificInterval(((0, 4), (5, 8))),
mask=models.Mask.types['No mask'],
activity=models.Activity.types['Light exercise'],
expiration=models.Expiration.types['Unmodulated Vocalization'],
),
),
infected=models.InfectedPerson(
virus=models.Virus.types['SARS_CoV_2'],
exposed=models.Population(
number=10,
presence=models.SpecificInterval(((0, 4), (5, 8))),
mask=models.Mask.types['No mask'],
activity=models.Activity.types['Light exercise'],
expiration=models.Expiration.types['Unmodulated Vocalization'],
mask=models.Mask.types['No mask'],
),
infected_occupants=1,
exposed_occupants=10,
exposed_activity=models.Activity.types['Light exercise'],
)
@ -330,13 +335,13 @@ class CARAStateBuilder(state.StateBuilder):
class ExpertApplication:
def __init__(self):
self.model_state = state.DataclassInstanceState(
models.Model,
models.ExposureModel,
state_builder=CARAStateBuilder(),
)
self.model_state.dcs_update_from(baseline_model)
# For the time-being, we have to initialise the select states. Careful
# as values might not correspond to what the baseline model says.
self.model_state.infected.mask.dcs_select('No mask')
self.model_state.concentration_model.infected.mask.dcs_select('No mask')
self.view = WidgetView(self.model_state)

View file

@ -419,6 +419,15 @@ class InfectedPopulation(Population):
#: The type of expiration that is being emitted whilst doing the activity.
expiration: Expiration
def emission_rate_if_present(self):
"""
The emission rate if the infected population is present.
Note that the rate is not currently time-dependent.
"""
def individual_emission_rate(self, time) -> float:
"""
The emission rate of a single individual in the population.
@ -426,9 +435,15 @@ class InfectedPopulation(Population):
"""
# Note: The original model avoids time dependence on the emission rate
# at the cost of implementing a piecewise (on time) concentration function.
if not self.person_present(time):
return 0
# Note: It is essential that the value of the emission rate is not
# itself a function of time. Any change in rate must be accompanied
# with a declaration of state change time, as is the case for things
# like Ventilation.
# Emission Rate (infectious quantum / h)
aerosols = self.expiration.aerosols(self.mask)
if np.isinf(aerosols):

View file

@ -6,4 +6,4 @@ def test_app():
# do anything fancy to verify how it looks etc., we leave that for manual
# testing.
expert_app = cara.apps.ExpertApplication()
assert expert_app.model_state.room.volume == 75
assert expert_app.model_state.concentration_model.room.volume == 75