Refine the form attributes & values in readiness for exposing a public perma-URL
This commit is contained in:
parent
182294348f
commit
3b620b0be0
6 changed files with 271 additions and 249 deletions
|
|
@ -12,35 +12,34 @@ from cara import data
|
|||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
minutes_since_midnight = typing.NewType('minutes_since_midnight', int)
|
||||
@dataclass
|
||||
class FormData:
|
||||
# Number of minutes after 00:00
|
||||
exposed_finish: int
|
||||
exposed_lunch_finish: int
|
||||
exposed_lunch_start: int
|
||||
exposed_start: int
|
||||
infected_finish: int
|
||||
infected_lunch_finish: int #Used if infected_dont_have_breaks_with_exposed
|
||||
infected_lunch_start: int #Used if infected_dont_have_breaks_with_exposed
|
||||
infected_start: int
|
||||
|
||||
activity_type: str
|
||||
air_changes: float
|
||||
air_supply: float
|
||||
ceiling_height: float
|
||||
exposed_coffee_breaks: int
|
||||
exposed_coffee_break_option: str
|
||||
exposed_coffee_duration: int
|
||||
exposed_finish: minutes_since_midnight
|
||||
exposed_lunch_finish: minutes_since_midnight
|
||||
exposed_lunch_option: bool
|
||||
exposed_lunch_start: minutes_since_midnight
|
||||
exposed_start: minutes_since_midnight
|
||||
floor_area: float
|
||||
hepa_amount: float
|
||||
hepa_option: bool
|
||||
infected_coffee_breaks: int #Used if infected_dont_have_breaks_with_exposed
|
||||
infected_coffee_break_option: str #Used if infected_dont_have_breaks_with_exposed
|
||||
infected_coffee_duration: int #Used if infected_dont_have_breaks_with_exposed
|
||||
infected_dont_have_breaks_with_exposed: bool
|
||||
infected_finish: minutes_since_midnight
|
||||
infected_lunch_finish: minutes_since_midnight #Used if infected_dont_have_breaks_with_exposed
|
||||
infected_lunch_option: bool #Used if infected_dont_have_breaks_with_exposed
|
||||
infected_lunch_start: minutes_since_midnight #Used if infected_dont_have_breaks_with_exposed
|
||||
infected_people: int
|
||||
infected_start: minutes_since_midnight
|
||||
mask_type: str
|
||||
mask_wearing: str
|
||||
mask_wearing_option: str
|
||||
mechanical_ventilation_type: str
|
||||
model_version: str
|
||||
opening_distance: float
|
||||
|
|
@ -57,14 +56,14 @@ class FormData:
|
|||
window_type: str
|
||||
window_width: float
|
||||
windows_number: int
|
||||
windows_open: str
|
||||
window_opening_regime: str
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, form_data: typing.Dict) -> "FormData":
|
||||
# Take a copy of the form data so that we can mutate it.
|
||||
form_data = form_data.copy()
|
||||
|
||||
valid_na_values = ['windows_open', 'window_type', 'mechanical_ventilation_type', 'infected_dont_have_breaks_with_exposed']
|
||||
valid_na_values = ['window_opening_regime', 'window_type', 'mechanical_ventilation_type', 'infected_dont_have_breaks_with_exposed']
|
||||
for name in valid_na_values:
|
||||
if not form_data.get(name, ''):
|
||||
form_data[name] = 'not-applicable'
|
||||
|
|
@ -101,7 +100,7 @@ class FormData:
|
|||
air_changes=float(form_data['air_changes']),
|
||||
air_supply=float(form_data['air_supply']),
|
||||
ceiling_height=float(form_data['ceiling_height']),
|
||||
exposed_coffee_breaks=int(form_data['exposed_coffee_breaks']),
|
||||
exposed_coffee_break_option=form_data['exposed_coffee_break_option'],
|
||||
exposed_coffee_duration=int(form_data['exposed_coffee_duration']),
|
||||
exposed_finish=form_data['exposed_finish'],
|
||||
exposed_lunch_finish=form_data['exposed_lunch_finish'],
|
||||
|
|
@ -111,7 +110,7 @@ class FormData:
|
|||
floor_area=float(form_data['floor_area']),
|
||||
hepa_amount=float(form_data['hepa_amount']),
|
||||
hepa_option=form_data['hepa_option'],
|
||||
infected_coffee_breaks=int(form_data['infected_coffee_breaks']),
|
||||
infected_coffee_break_option=form_data['infected_coffee_break_option'],
|
||||
infected_coffee_duration=int(form_data['infected_coffee_duration']),
|
||||
infected_dont_have_breaks_with_exposed=form_data['infected_dont_have_breaks_with_exposed'],
|
||||
infected_finish=form_data['infected_finish'],
|
||||
|
|
@ -121,7 +120,7 @@ class FormData:
|
|||
infected_people=int(form_data['infected_people']),
|
||||
infected_start=form_data['infected_start'],
|
||||
mask_type=form_data['mask_type'],
|
||||
mask_wearing=form_data['mask_wearing'],
|
||||
mask_wearing_option=form_data['mask_wearing_option'],
|
||||
mechanical_ventilation_type=form_data['mechanical_ventilation_type'],
|
||||
model_version=form_data['model_version'],
|
||||
opening_distance=float(form_data['opening_distance']),
|
||||
|
|
@ -138,7 +137,7 @@ class FormData:
|
|||
window_type=form_data['window_type'],
|
||||
window_width=float(form_data['window_width']),
|
||||
windows_number=int(form_data['windows_number']),
|
||||
windows_open=form_data['windows_open'],
|
||||
window_opening_regime=form_data['window_opening_regime'],
|
||||
)
|
||||
instance.validate()
|
||||
return instance
|
||||
|
|
@ -161,13 +160,15 @@ class FormData:
|
|||
raise ValueError(
|
||||
f"{start_name} must be less than {end_name}. Got {start} and {end}.")
|
||||
|
||||
validation_tuples = [('activity_type', ACTIVITY_TYPES),
|
||||
validation_tuples = [('activity_type', ACTIVITY_TYPES),
|
||||
('exposed_coffee_break_option', COFFEE_OPTIONS_INT),
|
||||
('infected_coffee_break_option', COFFEE_OPTIONS_INT),
|
||||
('mechanical_ventilation_type', MECHANICAL_VENTILATION_TYPES),
|
||||
('mask_type', MASK_TYPES),
|
||||
('mask_wearing', MASK_WEARING),
|
||||
('mask_wearing_option', MASK_WEARING_OPTIONS),
|
||||
('ventilation_type', VENTILATION_TYPES),
|
||||
('volume_type', VOLUME_TYPES),
|
||||
('windows_open', WINDOWS_OPEN),
|
||||
('window_opening_regime', WINDOWS_OPENING_REGIMES),
|
||||
('window_type', WINDOWS_TYPES)]
|
||||
for attr_name, valid_set in validation_tuples:
|
||||
if getattr(self, attr_name) not in valid_set:
|
||||
|
|
@ -185,9 +186,9 @@ class FormData:
|
|||
|
||||
def ventilation(self) -> models._VentilationBase:
|
||||
always_on = models.PeriodicInterval(period=120, duration=120)
|
||||
# Initializes a ventilation instance as a window if 'natural' is selected, or as a HEPA-filter otherwise
|
||||
if self.ventilation_type == 'natural':
|
||||
if self.windows_open == 'interval':
|
||||
# Initializes a ventilation instance as a window if 'natural_ventilation' is selected, or as a HEPA-filter otherwise
|
||||
if self.ventilation_type == 'natural_ventilation':
|
||||
if self.window_opening_regime == 'windows_open_periodically':
|
||||
window_interval = models.PeriodicInterval(self.windows_frequency, self.windows_duration)
|
||||
else:
|
||||
window_interval = always_on
|
||||
|
|
@ -198,7 +199,7 @@ class FormData:
|
|||
outside_temp = data.GenevaTemperatures[month]
|
||||
|
||||
ventilation: models.Ventilation
|
||||
if self.window_type == 'sliding':
|
||||
if self.window_type == 'window_sliding':
|
||||
ventilation = models.SlidingWindow(
|
||||
active=window_interval,
|
||||
inside_temp=inside_temp,
|
||||
|
|
@ -207,7 +208,7 @@ class FormData:
|
|||
opening_length=self.opening_distance,
|
||||
number_of_windows=self.windows_number,
|
||||
)
|
||||
elif self.window_type == 'hinged':
|
||||
elif self.window_type == 'window_hinged':
|
||||
ventilation = models.HingedWindow(
|
||||
active=window_interval,
|
||||
inside_temp=inside_temp,
|
||||
|
|
@ -218,10 +219,10 @@ class FormData:
|
|||
number_of_windows=self.windows_number,
|
||||
)
|
||||
|
||||
elif self.ventilation_type == "no-ventilation":
|
||||
elif self.ventilation_type == "no_ventilation":
|
||||
ventilation = models.AirChange(active=always_on, air_exch=0.)
|
||||
else:
|
||||
if self.mechanical_ventilation_type == 'air_changes':
|
||||
if self.mechanical_ventilation_type == 'mech_type_air_changes':
|
||||
ventilation = models.AirChange(active=always_on, air_exch=self.air_changes)
|
||||
else:
|
||||
ventilation = models.HVACMechanical(
|
||||
|
|
@ -236,7 +237,7 @@ class FormData:
|
|||
def mask(self) -> models.Mask:
|
||||
# Initializes the mask type if mask wearing is "continuous", otherwise instantiates the mask attribute as
|
||||
# the "No mask"-mask
|
||||
mask = models.Mask.types[self.mask_type if self.mask_wearing == "continuous" else 'No mask']
|
||||
mask = models.Mask.types[self.mask_type if self.mask_wearing_option == "mask_on" else 'No mask']
|
||||
return mask
|
||||
|
||||
def infected_population(self) -> models.InfectedPopulation:
|
||||
|
|
@ -337,6 +338,12 @@ class FormData:
|
|||
else:
|
||||
return self.exposed_lunch_break_times()
|
||||
|
||||
def exposed_number_of_coffee_breaks(self) -> int:
|
||||
return COFFEE_OPTIONS_INT[self.exposed_coffee_break_option]
|
||||
|
||||
def infected_number_of_coffee_breaks(self) -> int:
|
||||
return COFFEE_OPTIONS_INT[self.infected_coffee_break_option]
|
||||
|
||||
def _coffee_break_times(self, activity_start, activity_finish, coffee_breaks, coffee_duration, lunch_start, lunch_finish) -> models.BoundarySequence_t:
|
||||
time_before_lunch = lunch_start - activity_start
|
||||
time_after_lunch = activity_finish - lunch_finish
|
||||
|
|
@ -353,22 +360,24 @@ class FormData:
|
|||
return breaks
|
||||
|
||||
def exposed_coffee_break_times(self) -> models.BoundarySequence_t:
|
||||
if not self.exposed_coffee_breaks:
|
||||
exposed_coffee_breaks = self.exposed_number_of_coffee_breaks()
|
||||
if exposed_coffee_breaks == 0:
|
||||
return ()
|
||||
if self.exposed_lunch_option:
|
||||
breaks = self._coffee_break_times(self.exposed_start, self.exposed_finish, self.exposed_coffee_breaks, self.exposed_coffee_duration, self.exposed_lunch_start, self.exposed_lunch_finish)
|
||||
breaks = self._coffee_break_times(self.exposed_start, self.exposed_finish, exposed_coffee_breaks, self.exposed_coffee_duration, self.exposed_lunch_start, self.exposed_lunch_finish)
|
||||
else:
|
||||
breaks = self._compute_breaks_in_interval(self.exposed_start, self.exposed_finish, self.exposed_coffee_breaks, self.exposed_coffee_duration)
|
||||
breaks = self._compute_breaks_in_interval(self.exposed_start, self.exposed_finish, exposed_coffee_breaks, self.exposed_coffee_duration)
|
||||
return breaks
|
||||
|
||||
def infected_coffee_break_times(self) -> models.BoundarySequence_t:
|
||||
if self.infected_dont_have_breaks_with_exposed:
|
||||
if not self.infected_coffee_breaks:
|
||||
infected_coffee_breaks = self.infected_number_of_coffee_breaks()
|
||||
if infected_coffee_breaks == 0:
|
||||
return ()
|
||||
if self.infected_lunch_option:
|
||||
breaks = self._coffee_break_times(self.infected_start, self.infected_finish, self.infected_coffee_breaks, self.infected_coffee_duration, self.infected_lunch_start, self.infected_lunch_finish)
|
||||
breaks = self._coffee_break_times(self.infected_start, self.infected_finish, infected_coffee_breaks, self.infected_coffee_duration, self.infected_lunch_start, self.infected_lunch_finish)
|
||||
else:
|
||||
breaks = self._compute_breaks_in_interval(self.infected_start, self.infected_finish, self.infected_coffee_breaks, self.infected_coffee_duration)
|
||||
breaks = self._compute_breaks_in_interval(self.infected_start, self.infected_finish, infected_coffee_breaks, self.infected_coffee_duration)
|
||||
return breaks
|
||||
else:
|
||||
return self.exposed_coffee_break_times()
|
||||
|
|
@ -519,7 +528,7 @@ def expiration_blend(expiration_weights: typing.Dict[models.Expiration, int]) ->
|
|||
|
||||
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':
|
||||
if form.volume_type == 'room_volume_explicit':
|
||||
volume = form.room_volume
|
||||
else:
|
||||
volume = form.floor_area * form.ceiling_height
|
||||
|
|
@ -543,7 +552,7 @@ def baseline_raw_form_data():
|
|||
'air_changes': '',
|
||||
'air_supply': '',
|
||||
'ceiling_height': '',
|
||||
'exposed_coffee_breaks': '4',
|
||||
'exposed_coffee_break_option': 'coffee_break_4',
|
||||
'exposed_coffee_duration': '10',
|
||||
'exposed_finish': '18:00',
|
||||
'exposed_lunch_finish': '13:30',
|
||||
|
|
@ -553,7 +562,7 @@ def baseline_raw_form_data():
|
|||
'floor_area': '',
|
||||
'hepa_amount': '250',
|
||||
'hepa_option': '0',
|
||||
'infected_coffee_breaks': '4',
|
||||
'infected_coffee_break_option': 'coffee_break_4',
|
||||
'infected_coffee_duration': '10',
|
||||
'infected_dont_have_breaks_with_exposed': '1',
|
||||
'infected_finish': '18:00',
|
||||
|
|
@ -563,7 +572,7 @@ def baseline_raw_form_data():
|
|||
'infected_people': '1',
|
||||
'infected_start': '09:00',
|
||||
'mask_type': 'Type I',
|
||||
'mask_wearing': 'removed',
|
||||
'mask_wearing_option': 'mask_off',
|
||||
'mechanical_ventilation_type': '',
|
||||
'model_version': 'v1.2.0',
|
||||
'opening_distance': '0.2',
|
||||
|
|
@ -572,32 +581,34 @@ def baseline_raw_form_data():
|
|||
'room_volume': '75',
|
||||
'simulation_name': 'Test',
|
||||
'total_people': '10',
|
||||
'ventilation_type': 'natural',
|
||||
'volume_type': 'room_volume',
|
||||
'ventilation_type': 'natural_ventilation',
|
||||
'volume_type': 'room_volume_explicit',
|
||||
'windows_duration': '',
|
||||
'windows_frequency': '',
|
||||
'window_height': '2',
|
||||
'window_type': 'sliding',
|
||||
'window_type': 'window_sliding',
|
||||
'window_width': '2',
|
||||
'windows_number': '1',
|
||||
'windows_open': 'always'
|
||||
'window_opening_regime': 'windows_open_permanently'
|
||||
}
|
||||
|
||||
|
||||
ACTIVITY_TYPES = {'office', 'meeting', 'training', 'callcentre', 'library', 'workshop', 'lab', 'gym'}
|
||||
MECHANICAL_VENTILATION_TYPES = {'air_changes', 'air_supply', 'not-applicable'}
|
||||
MECHANICAL_VENTILATION_TYPES = {'mech_type_air_changes', 'mech_type_air_supply', 'not-applicable'}
|
||||
MASK_TYPES = {'Type I', 'FFP2'}
|
||||
MASK_WEARING = {'continuous', 'removed'}
|
||||
VENTILATION_TYPES = {'natural', 'mechanical', 'no-ventilation'}
|
||||
VOLUME_TYPES = {'room_volume', 'room_dimensions'}
|
||||
WINDOWS_OPEN = {'always', 'interval', 'not-applicable'}
|
||||
WINDOWS_TYPES = {'sliding', 'hinged', 'not-applicable'}
|
||||
MASK_WEARING_OPTIONS = {'mask_on', 'mask_off'}
|
||||
VENTILATION_TYPES = {'natural_ventilation', 'mechanical_ventilation', 'no_ventilation'}
|
||||
VOLUME_TYPES = {'room_volume_explicit', 'room_volume_from_dimensions'}
|
||||
WINDOWS_OPENING_REGIMES = {'windows_open_permanently', 'windows_open_periodically', 'not-applicable'}
|
||||
WINDOWS_TYPES = {'window_sliding', 'window_hinged', 'not-applicable'}
|
||||
|
||||
COFFEE_OPTIONS_INT = {'coffee_break_0':0, 'coffee_break_1':1, 'coffee_break_2':2, 'coffee_break_4':4}
|
||||
|
||||
|
||||
def time_string_to_minutes(time: str) -> int:
|
||||
def time_string_to_minutes(time: str) -> minutes_since_midnight:
|
||||
"""
|
||||
Converts time from string-format to an integer number of minutes after 00:00
|
||||
:param time: A string of the form "HH:MM" representing a time of day
|
||||
:return: The number of minutes between 'time' and 00:00
|
||||
"""
|
||||
return 60 * int(time[:2]) + int(time[3:])
|
||||
return minutes_since_midnight(60 * int(time[:2]) + int(time[3:]))
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ def manufacture_alternative_scenarios(form: FormData) -> typing.Dict[str, models
|
|||
scenarios = {}
|
||||
|
||||
# Two special option cases - HEPA and/or FFP2 masks.
|
||||
FFP2_being_worn = bool(form.mask_wearing == 'continuous' and form.mask_type == 'FFP2')
|
||||
FFP2_being_worn = bool(form.mask_wearing_option == 'mask_on' and form.mask_type == 'FFP2')
|
||||
if FFP2_being_worn and form.hepa_option:
|
||||
scenarios['Base scenario with HEPA and FFP2 masks'] = form.build_model()
|
||||
elif FFP2_being_worn:
|
||||
|
|
@ -156,20 +156,20 @@ def manufacture_alternative_scenarios(form: FormData) -> typing.Dict[str, models
|
|||
if form.hepa_option:
|
||||
form = dataclasses.replace(form, hepa_option=False)
|
||||
|
||||
with_mask = dataclasses.replace(form, mask_wearing='continuous')
|
||||
without_mask = dataclasses.replace(form, mask_wearing='removed')
|
||||
with_mask = dataclasses.replace(form, mask_wearing_option='mask_on')
|
||||
without_mask = dataclasses.replace(form, mask_wearing_option='mask_off')
|
||||
|
||||
if form.ventilation_type == 'mechanical':
|
||||
if form.ventilation_type == 'mechanical_ventilation':
|
||||
scenarios['Mechanical ventilation with Type I masks'] = with_mask.build_model()
|
||||
scenarios['Mechanical ventilation without masks'] = without_mask.build_model()
|
||||
|
||||
elif form.ventilation_type == 'natural':
|
||||
elif form.ventilation_type == 'natural_ventilation':
|
||||
scenarios['Windows open with Type I masks'] = with_mask.build_model()
|
||||
scenarios['Windows open without masks'] = without_mask.build_model()
|
||||
|
||||
# No matter the ventilation scheme, we include scenarios which don't have any ventilation.
|
||||
with_mask_no_vent = dataclasses.replace(with_mask, ventilation_type='no-ventilation')
|
||||
without_mask_or_vent = dataclasses.replace(without_mask, ventilation_type='no-ventilation')
|
||||
with_mask_no_vent = dataclasses.replace(with_mask, ventilation_type='no_ventilation')
|
||||
without_mask_or_vent = dataclasses.replace(without_mask, ventilation_type='no_ventilation')
|
||||
scenarios['No ventilation with Type I masks'] = with_mask_no_vent.build_model()
|
||||
scenarios['Neither ventilation nor masks'] = without_mask_or_vent.build_model()
|
||||
|
||||
|
|
|
|||
|
|
@ -19,19 +19,19 @@ function removeErrorFor(referenceNode) {
|
|||
/* -------Required fields------- */
|
||||
function require_fields(obj) {
|
||||
switch ($(obj).attr('id')) {
|
||||
case "room_type_volume":
|
||||
case "room_data_volume":
|
||||
require_room_volume(true);
|
||||
require_room_dimensions(false);
|
||||
break;
|
||||
case "room_type_dimensions":
|
||||
case "room_data_dimensions":
|
||||
require_room_volume(false);
|
||||
require_room_dimensions(true);
|
||||
break;
|
||||
case "mechanical":
|
||||
case "mechanical_ventilation":
|
||||
require_mechanical_ventilation(true);
|
||||
require_natural_ventilation(false);
|
||||
break;
|
||||
case "natural":
|
||||
case "natural_ventilation":
|
||||
require_mechanical_ventilation(false);
|
||||
require_natural_ventilation(true);
|
||||
break;
|
||||
|
|
@ -41,18 +41,18 @@ function require_fields(obj) {
|
|||
case "window_hinged":
|
||||
require_window_width(true);
|
||||
break;
|
||||
case "air_type_changes":
|
||||
case "mech_type_air_changes":
|
||||
require_air_changes(true);
|
||||
require_air_supply(false);
|
||||
break;
|
||||
case "air_type_supply":
|
||||
case "mech_type_air_supply":
|
||||
require_air_changes(false);
|
||||
require_air_supply(true);
|
||||
break;
|
||||
case "interval":
|
||||
case "windows_open_periodically":
|
||||
require_venting(true);
|
||||
break;
|
||||
case "always":
|
||||
case "windows_open_permanently":
|
||||
require_venting(false);
|
||||
break;
|
||||
case "hepa_yes":
|
||||
|
|
@ -82,10 +82,10 @@ function require_fields(obj) {
|
|||
|
||||
function unrequire_fields(obj) {
|
||||
switch (obj.id) {
|
||||
case "mechanical":
|
||||
case "mechanical_ventilation":
|
||||
require_mechanical_ventilation(false);
|
||||
break;
|
||||
case "natural":
|
||||
case "natural_ventilation":
|
||||
require_natural_ventilation(false);
|
||||
break;
|
||||
default:
|
||||
|
|
@ -106,11 +106,11 @@ function require_room_dimensions(option) {
|
|||
}
|
||||
|
||||
function require_mechanical_ventilation(option) {
|
||||
$("#air_type_changes").prop('required', option);
|
||||
$("#air_type_supply").prop('required', option);
|
||||
$("#mech_type_air_changes").prop('required', option);
|
||||
$("#mech_type_air_supply").prop('required', option);
|
||||
if (!option) {
|
||||
removeInvalid("#air_changes");
|
||||
removeInvalid("#air_supply");
|
||||
require_input_field("#air_changes", option);
|
||||
require_input_field("#air_supply", option);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,8 +120,13 @@ function require_natural_ventilation(option) {
|
|||
require_input_field("#opening_distance", option);
|
||||
$("#window_sliding").prop('required', option);
|
||||
$("#window_hinged").prop('required', option);
|
||||
$("#always").prop('required', option);
|
||||
$("#interval").prop('required', option);
|
||||
$("#windows_open_permanently").prop('required', option);
|
||||
$("#windows_open_periodically").prop('required', option);
|
||||
if (!option) {
|
||||
require_input_field("#window_width", option);
|
||||
require_input_field("#windows_duration", option);
|
||||
require_input_field("#windows_frequency", option);
|
||||
}
|
||||
}
|
||||
|
||||
function require_window_width(option) {
|
||||
|
|
@ -172,8 +177,8 @@ function require_lunch(id, option) {
|
|||
}
|
||||
|
||||
function require_mask(option) {
|
||||
$("#mask_type1").prop('required', option);
|
||||
$("#mask_ffp2").prop('required', option);
|
||||
$("#mask_type_1").prop('required', option);
|
||||
$("#mask_type_ffp2").prop('required', option);
|
||||
}
|
||||
|
||||
function require_hepa(option) {
|
||||
|
|
@ -225,8 +230,8 @@ function on_ventilation_type_change() {
|
|||
getChildElement($(this)).hide();
|
||||
unrequire_fields(this);
|
||||
|
||||
// Clear inputs for this newly hidden child element.
|
||||
getChildElement($(this)).find('input').not('input[type=radio]').val('');
|
||||
//Clear invalid inputs for this newly hidden child element
|
||||
removeInvalid(getChildElement($(this)).find('input').not('input[type=radio]').attr('id'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -293,14 +298,14 @@ function validate_form(form) {
|
|||
var activityBreaksObj= document.getElementById("activity_breaks");
|
||||
removeErrorFor(activityBreaksObj);
|
||||
|
||||
var lunch_mins = 0;
|
||||
if (document.getElementById(activity+"_lunch_option_yes").checked) {
|
||||
var lunch_mins = 0;
|
||||
var lunch_start = document.getElementById(activity+"_lunch_start");
|
||||
var lunch_finish = document.getElementById(activity+"_lunch_finish");
|
||||
lunch_mins = parseTimeToMins(lunch_finish.value) - parseTimeToMins(lunch_start.value);
|
||||
}
|
||||
|
||||
var coffee_breaks = parseInt(document.querySelector('input[name="'+activity+'_coffee_breaks"]:checked').value);
|
||||
var coffee_breaks = parseInt(document.querySelector('input[name="'+activity+'_coffee_break_option"]:checked').value);
|
||||
var coffee_duration = parseInt(document.getElementById(activity+"_coffee_duration").value);
|
||||
var coffee_mins = coffee_breaks * coffee_duration;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "layout.html.j2" %}
|
||||
|
||||
{% set MODEL_VERSION="v1.3.0" %}
|
||||
{% set MODEL_VERSION="v1.4.0" %}
|
||||
{% set DEBUG=False %}
|
||||
{% set active_page="calculator/" %}
|
||||
|
||||
|
|
@ -45,14 +45,14 @@
|
|||
<div data-tooltip="The area you wish to study (choose one of the 2 options). Use GIS Portal or measure.">
|
||||
<span class="tooltip_text">?</span>
|
||||
</div><br>
|
||||
<input type="radio" id="room_type_volume" name="volume_type" value="room_volume" onclick="require_fields(this)" tabindex="-1" required>
|
||||
<label for="room_type_volume">Room volume:</label>
|
||||
<input type="number" step="any" id="room_volume" class="non_zero" name="room_volume" placeholder="Room volume (m³)" min="0" data-has-radio="#room_type_volume"><br>
|
||||
<input type="radio" id="room_type_dimensions" name="volume_type" value="room_dimensions" onclick="require_fields(this)" tabindex="-1" required>
|
||||
<label for="room_type_dimensions">Floor area:</label>
|
||||
<input type="number" step="any" id="floor_area" class="non_zero" name="floor_area" placeholder="Room floor area (m²)" min="0" data-has-radio="#room_type_dimensions"><br>
|
||||
<label for="room_type_dimensions">Ceiling height:</label>
|
||||
<input type="number" step="any" id="ceiling_height" class="non_zero" name="ceiling_height" placeholder="Room ceiling height (m²)" min="0" data-has-radio="#room_type_dimensions"><br>
|
||||
<input type="radio" id="room_data_volume" name="volume_type" value="room_volume_explicit" onclick="require_fields(this)" tabindex="-1" required>
|
||||
<label for="room_data_volume">Room volume:</label>
|
||||
<input type="number" step="any" id="room_volume" class="non_zero" name="room_volume" placeholder="Room volume (m³)" min="0" data-has-radio="#room_data_volume"><br>
|
||||
<input type="radio" id="room_data_dimensions" name="volume_type" value="room_volume_from_dimensions" onclick="require_fields(this)" tabindex="-1" required>
|
||||
<label for="room_data_dimensions">Floor area:</label>
|
||||
<input type="number" step="any" id="floor_area" class="non_zero" name="floor_area" placeholder="Room floor area (m²)" min="0" data-has-radio="#room_data_dimensions"><br>
|
||||
<label for="room_data_dimensions">Ceiling height:</label>
|
||||
<input type="number" step="any" id="ceiling_height" class="non_zero" name="ceiling_height" placeholder="Room ceiling height (m²)" min="0" data-has-radio="#room_data_dimensions"><br>
|
||||
<hr width="80%">
|
||||
|
||||
<!-- Ventilation Options -->
|
||||
|
|
@ -61,39 +61,39 @@
|
|||
<span class="tooltip_text">?</span>
|
||||
</div><br>
|
||||
Ventilation type:
|
||||
<input type="radio" id="no_ventilation" name="ventilation_type" value="no-ventilation" checked>
|
||||
<input type="radio" id="no_ventilation" name="ventilation_type" value="no_ventilation" checked>
|
||||
<label for="no_ventilation">No ventilation</label>
|
||||
<input type="radio" id="mechanical" name="ventilation_type" value="mechanical" data-enables="#DIVmechanical_ventilation">
|
||||
<label for="mechanical">Mechanical</label>
|
||||
<input type="radio" id="natural" name="ventilation_type" value="natural" data-enables="#DIVnatural_ventilation">
|
||||
<label for="natural">Natural</label><br>
|
||||
<input type="radio" id="mechanical_ventilation" name="ventilation_type" value="mechanical_ventilation" data-enables="#DIVmechanical_ventilation">
|
||||
<label for="mechanical_ventilation">Mechanical</label>
|
||||
<input type="radio" id="natural_ventilation" name="ventilation_type" value="natural_ventilation" data-enables="#DIVnatural_ventilation">
|
||||
<label for="natural_ventilation">Natural</label><br>
|
||||
|
||||
<div id="DIVmechanical_ventilation" class="tabbed" style="display:none">
|
||||
<input type="radio" id="air_type_supply" name="mechanical_ventilation_type" value="air_supply" onclick="require_fields(this)" tabindex="-1">
|
||||
<label for="air_type_supply">Air supply flow rate</label>
|
||||
<input type="number" step="any" id="air_supply" class="non_zero" name="air_supply" min="0" placeholder="(m³ / hour)" data-has-radio="#air_type_supply"><br>
|
||||
<input type="radio" id="air_type_changes" name="mechanical_ventilation_type" value="air_changes" onclick="require_fields(this)" tabindex="-1">
|
||||
<label for="air_type_changes">Air changes per hour</label>
|
||||
<input type="number" step="any" id="air_changes" class="non_zero" name="air_changes" min="0" placeholder="(h⁻¹) only fresh air" data-has-radio="#air_type_changes"><br>
|
||||
<input type="radio" id="mech_type_air_supply" name="mechanical_ventilation_type" value="mech_type_air_supply" onclick="require_fields(this)" tabindex="-1">
|
||||
<label for="mech_type_air_supply">Air supply flow rate</label>
|
||||
<input type="number" step="any" id="air_supply" class="non_zero" name="air_supply" min="0" placeholder="(m³ / hour)" data-has-radio="#mech_type_air_supply"><br>
|
||||
<input type="radio" id="mech_type_air_changes" name="mechanical_ventilation_type" value="mech_type_air_changes" onclick="require_fields(this)" tabindex="-1">
|
||||
<label for="mech_type_air_changes">Air changes per hour</label>
|
||||
<input type="number" step="any" id="air_changes" class="non_zero" name="air_changes" min="0" placeholder="(h⁻¹) only fresh air" data-has-radio="#mech_type_air_changes"><br>
|
||||
</div>
|
||||
|
||||
<div id="DIVnatural_ventilation" class="tabbed" style="display:none">
|
||||
Number of windows: <input type="number" id="windows_number" class="non_zero" name="windows_number" min="1"><br>
|
||||
Height of window: <input type="number" step="any" id="window_height" class="non_zero" name="window_height" placeholder="meters" min="0"><br>
|
||||
Window type:
|
||||
<input type="radio" id="window_sliding" name="window_type" value="sliding" onclick="require_fields(this)" checked="checked">
|
||||
Window type:
|
||||
<input type="radio" id="window_sliding" name="window_type" value="window_sliding" onclick="require_fields(this)" checked="checked">
|
||||
<label for="window_sliding">Sliding / Side-Hung</label>
|
||||
<input type="radio" id="window_hinged" name="window_type" value="hinged" onclick="require_fields(this)">
|
||||
<input type="radio" id="window_hinged" name="window_type" value="window_hinged" onclick="require_fields(this)">
|
||||
<label for="window_hinged">Top- or Bottom-Hung</label> <br>
|
||||
Width of window: <input type="number" step="any" id="window_width" class="non_zero disabled" name="window_width" placeholder="meters" min="0" data-has-radio="#window_hinged"><br>
|
||||
Opening distance: <input type="number" step="any" id="opening_distance" class="non_zero" name="opening_distance" placeholder="meters" min="0"><br>
|
||||
Windows open:</span><br>
|
||||
<span class="tabbed"><input type="radio" id="always" name="windows_open" value="always" onclick="require_fields(this)" checked="checked"></span>
|
||||
<label for="always">Permanently</label><br>
|
||||
<span class="tabbed"><input type="radio" id="interval" name="windows_open" value="interval" onclick="require_fields(this)"></span>
|
||||
<label for="interval">Periodically:</label>
|
||||
<input type="number" step="any" id="windows_duration" class="disabled" name="windows_duration" placeholder="Duration (min)" min="1" data-has-radio="#interval"> /
|
||||
<input type="number" step="any" id="windows_frequency" class="disabled" name="windows_frequency" placeholder="Frequency (min)" min="1" data-has-radio="#interval">
|
||||
Windows open:</span><br>
|
||||
<span class="tabbed"><input type="radio" id="windows_open_permanently" name="window_opening_regime" value="windows_open_permanently" onclick="require_fields(this)" checked="checked"></span>
|
||||
<label for="windows_open_permanently">Permanently</label><br>
|
||||
<span class="tabbed"><input type="radio" id="windows_open_periodically" name="window_opening_regime" value="windows_open_periodically" onclick="require_fields(this)"></span>
|
||||
<label for="windows_open_periodically">Periodically:</label>
|
||||
<input type="number" step="any" id="windows_duration" class="disabled" name="windows_duration" placeholder="Duration (min)" min="1" data-has-radio="#windows_open_periodically"> /
|
||||
<input type="number" step="any" id="windows_frequency" class="disabled" name="windows_frequency" placeholder="Frequency (min)" min="1" data-has-radio="#windows_open_periodically">
|
||||
<br>
|
||||
</div>
|
||||
|
||||
|
|
@ -110,15 +110,15 @@
|
|||
<span class="tooltip_text">?</span>
|
||||
</div><br>
|
||||
Are masks worn when occupants are at workstations?
|
||||
<input type="radio" id="mask_on" name="mask_wearing" value="continuous" required>
|
||||
<input type="radio" id="mask_on" name="mask_wearing_option" value="mask_on" required>
|
||||
<label for="mask_on">Yes</label>
|
||||
<input type="radio" id="mask_off" name="mask_wearing" value="removed" required checked="checked">
|
||||
<input type="radio" id="mask_off" name="mask_wearing_option" value="mask_off" required checked="checked">
|
||||
<label for="mask_off">No</label><br>
|
||||
Type of masks used:
|
||||
<input type="radio" id="mask_type1" name="mask_type" value="Type I" checked="checked" onclick="require_fields(this)">
|
||||
<label for="mask_type1">Type 1</label>
|
||||
<input type="radio" id="mask_ffp2" name="mask_type" value="FFP2" onclick="require_fields(this)">
|
||||
<label for="mask_ffp2">FFP2</label><br>
|
||||
<input type="radio" id="mask_type_1" name="mask_type" value="Type I" checked="checked" onclick="require_fields(this)">
|
||||
<label for="mask_type_1">Type 1</label>
|
||||
<input type="radio" id="mask_type_ffp2" name="mask_type" value="FFP2" onclick="require_fields(this)">
|
||||
<label for="mask_type_ffp2">FFP2</label><br>
|
||||
<hr width="80%">
|
||||
</div>
|
||||
|
||||
|
|
@ -194,11 +194,11 @@
|
|||
|
||||
<!-- Coffee Options -->
|
||||
Coffee Breaks:
|
||||
<input type="radio" id="exposed_coffee_break_0" name="exposed_coffee_breaks" value="0" checked="checked">
|
||||
<input type="radio" id="exposed_coffee_break_0" name="exposed_coffee_break_option" value="coffee_break_0" checked="checked">
|
||||
<label for="exposed_coffee_break_0" >No breaks</label>
|
||||
<input type="radio" id="exposed_coffee_break_2" name="exposed_coffee_breaks" value="2">
|
||||
<input type="radio" id="exposed_coffee_break_2" name="exposed_coffee_break_option" value="coffee_break_2">
|
||||
<label for="exposed_coffee_break_2">2</label>
|
||||
<input type="radio" id="exposed_coffee_break_4" name="exposed_coffee_breaks" value="4">
|
||||
<input type="radio" id="exposed_coffee_break_4" name="exposed_coffee_break_option" value="coffee_break_4">
|
||||
<label for="exposed_coffee_break_4">4</label><br>
|
||||
|
||||
Duration (minutes):
|
||||
|
|
@ -228,11 +228,11 @@
|
|||
|
||||
<!-- Coffee Options -->
|
||||
Coffee Breaks:
|
||||
<input type="radio" id="infected_coffee_break_0" name="infected_coffee_breaks" value="0" checked="checked">
|
||||
<input type="radio" id="infected_coffee_break_0" name="infected_coffee_break_option" value="coffee_break_0" checked="checked">
|
||||
<label for="infected_coffee_break_0" >No breaks</label>
|
||||
<input type="radio" id="infected_coffee_break_2" name="infected_coffee_breaks" value="2">
|
||||
<input type="radio" id="infected_coffee_break_2" name="infected_coffee_break_option" value="coffee_break_2">
|
||||
<label for="infected_coffee_break_2">2</label>
|
||||
<input type="radio" id="infected_coffee_break_4" name="infected_coffee_breaks" value="4">
|
||||
<input type="radio" id="infected_coffee_break_4" name="infected_coffee_break_option" value="coffee_break_4">
|
||||
<label for="infected_coffee_break_4">4</label><br>
|
||||
|
||||
Duration (minutes):
|
||||
|
|
@ -247,7 +247,7 @@
|
|||
</div>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
|
||||
<br style="clear:both;">
|
||||
<i>Coffee breaks are spread evenly throughout the day.</i><br>
|
||||
<hr width="80%">
|
||||
|
|
|
|||
|
|
@ -41,13 +41,13 @@
|
|||
<p class="data_title">Ventilation data:</p>
|
||||
<ul>
|
||||
<li><p class="data_text">Mechanical ventilation:
|
||||
{% if form.ventilation_type == "mechanical" %}
|
||||
{% if form.ventilation_type == "mechanical_ventilation" %}
|
||||
Yes </p></li>
|
||||
<ul>
|
||||
<li><p class="data_subtext">
|
||||
{% if form.mechanical_ventilation_type == "air_supply"%}
|
||||
{% if form.mechanical_ventilation_type == "mech_type_air_supply"%}
|
||||
Air supply flow rate: {{ form.air_supply }} m³ / hour
|
||||
{% elif form.mechanical_ventilation_type == "air_changes"%}
|
||||
{% elif form.mechanical_ventilation_type == "mech_type_air_changes"%}
|
||||
Air changes per hour: {{ form.air_changes }} h⁻¹
|
||||
{% endif %}
|
||||
</p></li>
|
||||
|
|
@ -56,24 +56,24 @@
|
|||
No </li>
|
||||
{% endif %}
|
||||
<li><p class="data_text">Natural ventilation:
|
||||
{% if form.ventilation_type == "natural"%}
|
||||
{% if form.ventilation_type == "natural_ventilation"%}
|
||||
Yes </p></li>
|
||||
<ul>
|
||||
<li><p class="data_subtext">Number of windows: {{ form.windows_number }}</p></li>
|
||||
<li><p class="data_subtext">Height of window: {{ form.window_height }} m</p></li>
|
||||
<li><p class="data_subtext">Window type:
|
||||
{% if form.window_type == "hinged" %}
|
||||
{% if form.window_type == "window_hinged" %}
|
||||
Top- or Bottom-Hung</p></li>
|
||||
<li><p class="data_subtext">Width of window: {{ form.window_width }} m</p></li>
|
||||
{% elif form.window_type == "sliding" %}
|
||||
{% elif form.window_type == "window_sliding" %}
|
||||
Sliding / Side-Hung</p></li>
|
||||
{% endif %}
|
||||
<li><p class="data_subtext">Opening distance: {{ form.opening_distance }} m</p></li>
|
||||
<li><p class="data_subtext">Windows open:
|
||||
{% if form.windows_open == "interval" %}
|
||||
Periodically for {{ form.windows_duration | readable_minutes}}
|
||||
<li><p class="data_subtext">Windows open:
|
||||
{% if form.window_opening_regime == "windows_open_periodically" %}
|
||||
Periodically for {{ form.windows_duration | readable_minutes}}
|
||||
every {{ form.windows_frequency | readable_minutes}}
|
||||
{% elif form.windows_open == "always" %}
|
||||
{% elif form.window_opening_regime == "windows_open_permanently" %}
|
||||
Permanently
|
||||
{% endif %}
|
||||
</p></li>
|
||||
|
|
@ -141,8 +141,8 @@
|
|||
No
|
||||
{% endif %}
|
||||
</p></li>
|
||||
<li><p class="data_text">Coffee breaks: {{ form.exposed_coffee_breaks }}
|
||||
{% if form.exposed_coffee_breaks > 0 %}
|
||||
<li><p class="data_text">Coffee breaks: {{ form.exposed_number_of_coffee_breaks() }}
|
||||
{% if form.exposed_number_of_coffee_breaks() > 0 %}
|
||||
each of {{ form.exposed_coffee_duration }} minutes duration
|
||||
</p></li>
|
||||
<ul>
|
||||
|
|
@ -166,8 +166,8 @@
|
|||
No
|
||||
{% endif %}
|
||||
</p></li>
|
||||
<li><p class="data_text">Coffee breaks: {{ form.infected_coffee_breaks }}
|
||||
{% if form.infected_coffee_breaks > 0 %}
|
||||
<li><p class="data_text">Coffee breaks: {{ form.infected_number_of_coffee_breaks() }}
|
||||
{% if form.infected_number_of_coffee_breaks() > 0 %}
|
||||
each of {{ form.infected_coffee_duration }} minutes duration
|
||||
</p></li>
|
||||
<ul>
|
||||
|
|
@ -179,12 +179,13 @@
|
|||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<p class="data_title">Mask wearing:</p>
|
||||
<p class="data_title">Mask wearing:</p>
|
||||
<ul>
|
||||
<li><p class="data_text">Masks worn at workstations? {{ 'Yes' if form.mask_wearing == "continuous" else 'No' }} </p></li>
|
||||
{% if form.mask_wearing == "continuous" %}
|
||||
<li><p class="data_text">Masks worn at workstations? {{ 'Yes' if form.mask_wearing_option == "mask_on" else 'No' }} </p></li>
|
||||
{% if form.mask_wearing_option == "mask_on" %}
|
||||
<li><p class="data_text">Mask type: {{ form.mask_type }}</p></li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
|
||||
<p class="result_title">Results:</p>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import pytest
|
||||
|
||||
from cara.apps.calculator import model_generator
|
||||
from cara.apps.calculator.model_generator import minutes_since_midnight
|
||||
from cara import models
|
||||
from cara import data
|
||||
import numpy as np
|
||||
|
|
@ -29,7 +30,7 @@ def test_blend_expiration():
|
|||
assert r == expected
|
||||
|
||||
|
||||
def test_ventilation_slidingwindow(baseline_form):
|
||||
def test_ventilation_slidingwindow(baseline_form: model_generator.FormData):
|
||||
room = models.Room(75)
|
||||
window = models.SlidingWindow(
|
||||
active=models.PeriodicInterval(period=120, duration=10),
|
||||
|
|
@ -37,11 +38,11 @@ def test_ventilation_slidingwindow(baseline_form):
|
|||
outside_temp=data.GenevaTemperatures['Dec'],
|
||||
window_height=1.6, opening_length=0.6,
|
||||
)
|
||||
baseline_form.ventilation_type = 'natural'
|
||||
baseline_form.ventilation_type = 'natural_ventilation'
|
||||
baseline_form.windows_duration = 10
|
||||
baseline_form.windows_frequency = 120
|
||||
baseline_form.windows_open = 'interval'
|
||||
baseline_form.window_type = 'sliding'
|
||||
baseline_form.window_opening_regime = 'windows_open_periodically'
|
||||
baseline_form.window_type = 'window_sliding'
|
||||
baseline_form.event_month = 'December'
|
||||
baseline_form.window_height = 1.6
|
||||
baseline_form.opening_distance = 0.6
|
||||
|
|
@ -51,7 +52,7 @@ def test_ventilation_slidingwindow(baseline_form):
|
|||
[baseline_form.ventilation().air_exchange(room, t) for t in ts])
|
||||
|
||||
|
||||
def test_ventilation_hingedwindow(baseline_form):
|
||||
def test_ventilation_hingedwindow(baseline_form: model_generator.FormData):
|
||||
room = models.Room(75)
|
||||
window = models.HingedWindow(
|
||||
active=models.PeriodicInterval(period=120, duration=10),
|
||||
|
|
@ -59,11 +60,11 @@ def test_ventilation_hingedwindow(baseline_form):
|
|||
outside_temp=data.GenevaTemperatures['Dec'],
|
||||
window_height=1.6, window_width=1., opening_length=0.6,
|
||||
)
|
||||
baseline_form.ventilation_type = 'natural'
|
||||
baseline_form.ventilation_type = 'natural_ventilation'
|
||||
baseline_form.windows_duration = 10
|
||||
baseline_form.windows_frequency = 120
|
||||
baseline_form.windows_open = 'interval'
|
||||
baseline_form.window_type = 'hinged'
|
||||
baseline_form.window_opening_regime = 'windows_open_periodically'
|
||||
baseline_form.window_type = 'window_hinged'
|
||||
baseline_form.event_month = 'December'
|
||||
baseline_form.window_height = 1.6
|
||||
baseline_form.window_width = 1.
|
||||
|
|
@ -74,14 +75,14 @@ def test_ventilation_hingedwindow(baseline_form):
|
|||
[baseline_form.ventilation().air_exchange(room, t) for t in ts])
|
||||
|
||||
|
||||
def test_ventilation_mechanical(baseline_form):
|
||||
def test_ventilation_mechanical(baseline_form: model_generator.FormData):
|
||||
room = models.Room(75)
|
||||
mech = models.HVACMechanical(
|
||||
active=models.PeriodicInterval(period=120, duration=120),
|
||||
q_air_mech=500.,
|
||||
)
|
||||
baseline_form.ventilation_type = 'mechanical'
|
||||
baseline_form.mechanical_ventilation_type = 'mechanical'
|
||||
baseline_form.ventilation_type = 'mechanical_ventilation'
|
||||
baseline_form.mechanical_ventilation_type = 'mech_type_air_supply'
|
||||
baseline_form.air_supply = 500.
|
||||
|
||||
ts = np.linspace(8, 16, 100)
|
||||
|
|
@ -89,14 +90,14 @@ def test_ventilation_mechanical(baseline_form):
|
|||
[baseline_form.ventilation().air_exchange(room, t) for t in ts])
|
||||
|
||||
|
||||
def test_ventilation_airchanges(baseline_form):
|
||||
def test_ventilation_airchanges(baseline_form: model_generator.FormData):
|
||||
room = models.Room(75)
|
||||
airchange = models.AirChange(
|
||||
active=models.PeriodicInterval(period=120, duration=120),
|
||||
air_exch=3.,
|
||||
)
|
||||
baseline_form.ventilation_type = 'mechanical'
|
||||
baseline_form.mechanical_ventilation_type = 'air_changes'
|
||||
baseline_form.ventilation_type = 'mechanical_ventilation'
|
||||
baseline_form.mechanical_ventilation_type = 'mech_type_air_changes'
|
||||
baseline_form.air_changes = 3.
|
||||
|
||||
ts = np.linspace(8, 16, 100)
|
||||
|
|
@ -104,7 +105,7 @@ def test_ventilation_airchanges(baseline_form):
|
|||
[baseline_form.ventilation().air_exchange(room, t) for t in ts])
|
||||
|
||||
|
||||
def test_ventilation_window_hepa(baseline_form):
|
||||
def test_ventilation_window_hepa(baseline_form: model_generator.FormData):
|
||||
room = models.Room(75)
|
||||
window = models.SlidingWindow(
|
||||
active=models.PeriodicInterval(period=120, duration=10),
|
||||
|
|
@ -118,10 +119,10 @@ def test_ventilation_window_hepa(baseline_form):
|
|||
)
|
||||
ventilation = models.MultipleVentilation((window,hepa))
|
||||
|
||||
baseline_form.ventilation_type = 'natural'
|
||||
baseline_form.ventilation_type = 'natural_ventilation'
|
||||
baseline_form.windows_duration = 10
|
||||
baseline_form.windows_frequency = 120
|
||||
baseline_form.windows_open = 'interval'
|
||||
baseline_form.window_opening_regime = 'windows_open_periodically'
|
||||
baseline_form.event_month = 'December'
|
||||
baseline_form.window_height = 1.6
|
||||
baseline_form.opening_distance = 0.6
|
||||
|
|
@ -132,97 +133,101 @@ def test_ventilation_window_hepa(baseline_form):
|
|||
[baseline_form.ventilation().air_exchange(room, t) for t in ts])
|
||||
|
||||
|
||||
def test_infected_present_intervals(baseline_form):
|
||||
def present_times(interval: models.Interval) -> models.BoundarySequence_t:
|
||||
assert isinstance(interval, models.SpecificInterval)
|
||||
return interval.present_times
|
||||
|
||||
|
||||
def test_infected_present_intervals(baseline_form: model_generator.FormData):
|
||||
baseline_form.infected_dont_have_breaks_with_exposed = False
|
||||
baseline_form.exposed_coffee_duration = 15
|
||||
baseline_form.exposed_coffee_breaks = 2
|
||||
baseline_form.exposed_start = 9 * 60
|
||||
baseline_form.exposed_finish = 17 * 60
|
||||
baseline_form.exposed_lunch_start = 12 * 60 + 30
|
||||
baseline_form.exposed_lunch_finish = 13 * 60 + 30
|
||||
baseline_form.infected_start = 10 * 60
|
||||
baseline_form.infected_finish = 15 * 60
|
||||
baseline_form.exposed_coffee_break_option = 'coffee_break_2'
|
||||
baseline_form.exposed_start = minutes_since_midnight(9 * 60)
|
||||
baseline_form.exposed_finish = minutes_since_midnight(17 * 60)
|
||||
baseline_form.exposed_lunch_start = minutes_since_midnight(12 * 60 + 30)
|
||||
baseline_form.exposed_lunch_finish = minutes_since_midnight(13 * 60 + 30)
|
||||
baseline_form.infected_start = minutes_since_midnight(10 * 60)
|
||||
baseline_form.infected_finish = minutes_since_midnight(15 * 60)
|
||||
correct = ((10, 10+37/60), (10+52/60, 12.5), (13.5, 15.0))
|
||||
assert baseline_form.infected_present_interval().present_times == correct
|
||||
assert present_times(baseline_form.infected_present_interval()) == correct
|
||||
|
||||
|
||||
def test_exposed_present_intervals(baseline_form):
|
||||
def test_exposed_present_intervals(baseline_form: model_generator.FormData):
|
||||
baseline_form.exposed_coffee_duration = 15
|
||||
baseline_form.exposed_coffee_breaks = 2
|
||||
baseline_form.exposed_start = 9 * 60
|
||||
baseline_form.exposed_finish = 17 * 60
|
||||
baseline_form.exposed_lunch_start = 12 * 60 + 30
|
||||
baseline_form.exposed_lunch_finish = 13 * 60 + 30
|
||||
baseline_form.exposed_coffee_break_option = 'coffee_break_2'
|
||||
baseline_form.exposed_start = minutes_since_midnight(9 * 60)
|
||||
baseline_form.exposed_finish = minutes_since_midnight(17 * 60)
|
||||
baseline_form.exposed_lunch_start = minutes_since_midnight(12 * 60 + 30)
|
||||
baseline_form.exposed_lunch_finish = minutes_since_midnight(13 * 60 + 30)
|
||||
correct = ((9, 10+37/60), (10+52/60, 12.5), (13.5, 15+7/60), (15+22/60, 17.0))
|
||||
assert baseline_form.exposed_present_interval().present_times == correct
|
||||
assert present_times(baseline_form.exposed_present_interval()) == correct
|
||||
|
||||
|
||||
def test_present_intervals_common_breaks(baseline_form):
|
||||
def test_present_intervals_common_breaks(baseline_form: model_generator.FormData):
|
||||
baseline_form.infected_dont_have_breaks_with_exposed = False
|
||||
baseline_form.infected_coffee_duration = baseline_form.exposed_coffee_duration = 15
|
||||
baseline_form.infected_coffee_breaks = baseline_form.exposed_coffee_breaks = 2
|
||||
baseline_form.exposed_lunch_start = baseline_form.infected_lunch_start = 12 * 60 + 30
|
||||
baseline_form.exposed_lunch_finish = baseline_form.infected_lunch_finish = 13 * 60 + 30
|
||||
baseline_form.exposed_start = 9 * 60
|
||||
baseline_form.exposed_finish = 17 * 60
|
||||
baseline_form.infected_start = 9 * 60
|
||||
baseline_form.infected_finish = 16 * 60
|
||||
baseline_form.infected_coffee_break_option = baseline_form.exposed_coffee_break_option = 'coffee_break_2'
|
||||
baseline_form.exposed_lunch_start = baseline_form.infected_lunch_start = minutes_since_midnight(12 * 60 + 30)
|
||||
baseline_form.exposed_lunch_finish = baseline_form.infected_lunch_finish = minutes_since_midnight(13 * 60 + 30)
|
||||
baseline_form.exposed_start = minutes_since_midnight(9 * 60)
|
||||
baseline_form.exposed_finish = minutes_since_midnight(17 * 60)
|
||||
baseline_form.infected_start = minutes_since_midnight(9 * 60)
|
||||
baseline_form.infected_finish = minutes_since_midnight(16 * 60)
|
||||
correct_exposed = ((9, 10+37/60), (10+52/60, 12.5), (13.5, 15+7/60), (15+22/60, 17.0))
|
||||
correct_infected = ((9, 10+37/60), (10+52/60, 12.5), (13.5, 15+7/60), (15+22/60, 16.0))
|
||||
assert baseline_form.exposed_present_interval().present_times == correct_exposed
|
||||
assert baseline_form.infected_present_interval().present_times == correct_infected
|
||||
assert present_times(baseline_form.exposed_present_interval()) == correct_exposed
|
||||
assert present_times(baseline_form.infected_present_interval()) == correct_infected
|
||||
|
||||
|
||||
def test_present_intervals_split_breaks(baseline_form):
|
||||
def test_present_intervals_split_breaks(baseline_form: model_generator.FormData):
|
||||
baseline_form.infected_dont_have_breaks_with_exposed = True
|
||||
baseline_form.infected_coffee_duration = baseline_form.exposed_coffee_duration = 15
|
||||
baseline_form.infected_coffee_breaks = baseline_form.exposed_coffee_breaks = 2
|
||||
baseline_form.infected_lunch_start = baseline_form.exposed_lunch_start = 12 * 60 + 30
|
||||
baseline_form.infected_lunch_finish = baseline_form.exposed_lunch_finish = 13 * 60 + 30
|
||||
baseline_form.exposed_start = 9 * 60
|
||||
baseline_form.exposed_finish = 17 * 60
|
||||
baseline_form.infected_start = 9 * 60
|
||||
baseline_form.infected_finish = 16 * 60
|
||||
baseline_form.infected_coffee_break_option = baseline_form.exposed_coffee_break_option = 'coffee_break_2'
|
||||
baseline_form.infected_lunch_start = baseline_form.exposed_lunch_start = minutes_since_midnight(12 * 60 + 30)
|
||||
baseline_form.infected_lunch_finish = baseline_form.exposed_lunch_finish = minutes_since_midnight(13 * 60 + 30)
|
||||
baseline_form.exposed_start = minutes_since_midnight(9 * 60)
|
||||
baseline_form.exposed_finish = minutes_since_midnight(17 * 60)
|
||||
baseline_form.infected_start = minutes_since_midnight(9 * 60)
|
||||
baseline_form.infected_finish = minutes_since_midnight(16 * 60)
|
||||
correct_exposed = ((9, 10+37/60), (10+52/60, 12.5), (13.5, 15+7/60), (15+22/60, 17.0))
|
||||
correct_infected = ((9, 10+37/60), (10+52/60, 12.5), (13.5, 14+37/60), (14+52/60, 16.0))
|
||||
assert baseline_form.exposed_present_interval().present_times == correct_exposed
|
||||
assert baseline_form.infected_present_interval().present_times == correct_infected
|
||||
assert present_times(baseline_form.exposed_present_interval()) == correct_exposed
|
||||
assert present_times(baseline_form.infected_present_interval()) == correct_infected
|
||||
|
||||
|
||||
def test_exposed_present_intervals_starting_with_lunch(baseline_form):
|
||||
baseline_form.exposed_coffee_breaks = 0
|
||||
baseline_form.exposed_start = baseline_form.exposed_lunch_start = 13 * 60
|
||||
baseline_form.exposed_finish = 18 * 60
|
||||
baseline_form.exposed_lunch_finish = 14 * 60
|
||||
def test_exposed_present_intervals_starting_with_lunch(baseline_form: model_generator.FormData):
|
||||
baseline_form.exposed_coffee_break_option = 'coffee_break_0'
|
||||
baseline_form.exposed_start = baseline_form.exposed_lunch_start = minutes_since_midnight(13 * 60)
|
||||
baseline_form.exposed_finish = minutes_since_midnight(18 * 60)
|
||||
baseline_form.exposed_lunch_finish = minutes_since_midnight(14 * 60)
|
||||
correct = ((14.0, 18.0), )
|
||||
assert baseline_form.exposed_present_interval().present_times == correct
|
||||
assert present_times(baseline_form.exposed_present_interval()) == correct
|
||||
|
||||
|
||||
def test_exposed_present_intervals_ending_with_lunch(baseline_form):
|
||||
baseline_form.exposed_coffee_breaks = 0
|
||||
baseline_form.exposed_start = 11 * 60
|
||||
baseline_form.exposed_finish = baseline_form.exposed_lunch_start = 13 * 60
|
||||
baseline_form.exposed_lunch_finish = 14 * 60
|
||||
def test_exposed_present_intervals_ending_with_lunch(baseline_form: model_generator.FormData):
|
||||
baseline_form.exposed_coffee_break_option = 'coffee_break_0'
|
||||
baseline_form.exposed_start = minutes_since_midnight(11 * 60)
|
||||
baseline_form.exposed_finish = baseline_form.exposed_lunch_start = minutes_since_midnight(13 * 60)
|
||||
baseline_form.exposed_lunch_finish = minutes_since_midnight(14 * 60)
|
||||
correct = ((11.0, 13.0),)
|
||||
assert baseline_form.exposed_present_interval().present_times == correct
|
||||
assert present_times(baseline_form.exposed_present_interval()) == correct
|
||||
|
||||
|
||||
def test_exposed_present_lunch_end_before_beginning(baseline_form):
|
||||
baseline_form.exposed_coffee_breaks = 0
|
||||
baseline_form.exposed_lunch_start = 14 * 60
|
||||
baseline_form.exposed_lunch_finish = 13 * 60
|
||||
def test_exposed_present_lunch_end_before_beginning(baseline_form: model_generator.FormData):
|
||||
baseline_form.exposed_coffee_break_option = 'coffee_break_0'
|
||||
baseline_form.exposed_lunch_start = minutes_since_midnight(14 * 60)
|
||||
baseline_form.exposed_lunch_finish = minutes_since_midnight(13 * 60)
|
||||
with pytest.raises(ValueError):
|
||||
baseline_form.validate()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def coffee_break_between_1045_and_1115(baseline_form):
|
||||
baseline_form.exposed_coffee_breaks = 1
|
||||
def coffee_break_between_1045_and_1115(baseline_form: model_generator.FormData):
|
||||
baseline_form.exposed_coffee_break_option = 'coffee_break_1'
|
||||
baseline_form.exposed_coffee_duration = 30
|
||||
baseline_form.exposed_start = 10 * 60
|
||||
baseline_form.exposed_finish = 12 * 60
|
||||
baseline_form.exposed_start = minutes_since_midnight(10 * 60)
|
||||
baseline_form.exposed_finish = minutes_since_midnight(12 * 60)
|
||||
baseline_form.exposed_lunch_option = False
|
||||
|
||||
coffee_breaks = baseline_form.exposed_coffee_break_times()
|
||||
assert coffee_breaks == ((10.75 * 60, 11.25 * 60),)
|
||||
return baseline_form
|
||||
|
|
@ -263,9 +268,9 @@ def test_present_only_for_coffee_ends(coffee_break_between_1045_and_1115):
|
|||
assert interval.boundaries() == ()
|
||||
|
||||
|
||||
def time2mins(time: str):
|
||||
def time2mins(time: str) -> minutes_since_midnight:
|
||||
# Convert times like "14:30" to decimal, like 14.5 * 60.
|
||||
return int(time.split(':')[0]) * 60 + int(time.split(':')[1])
|
||||
return minutes_since_midnight(int(time.split(':')[0]) * 60 + int(time.split(':')[1]))
|
||||
|
||||
|
||||
def hours2time(hours: float):
|
||||
|
|
@ -280,8 +285,8 @@ def assert_boundaries(interval, boundaries_in_time_string_form):
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def breaks_every_25_mins_for_20_mins(baseline_form):
|
||||
baseline_form.exposed_coffee_breaks = 4
|
||||
def breaks_every_25_mins_for_20_mins(baseline_form: model_generator.FormData):
|
||||
baseline_form.exposed_coffee_break_option = 'coffee_break_4'
|
||||
baseline_form.exposed_coffee_duration = 20
|
||||
baseline_form.exposed_start = time2mins("10:00")
|
||||
baseline_form.exposed_finish = time2mins("14:10")
|
||||
|
|
@ -325,60 +330,60 @@ def test_present_only_during_second_break(breaks_every_25_mins_for_20_mins):
|
|||
assert_boundaries(interval, [])
|
||||
|
||||
|
||||
def test_valid_no_lunch(baseline_form):
|
||||
def test_valid_no_lunch(baseline_form: model_generator.FormData):
|
||||
# Check that it is valid to have a 0 length lunch if no lunch is selected.
|
||||
baseline_form.exposed_lunch_option = False
|
||||
baseline_form.exposed_lunch_start = 0
|
||||
baseline_form.exposed_lunch_finish = 0
|
||||
baseline_form.exposed_lunch_start = minutes_since_midnight(0)
|
||||
baseline_form.exposed_lunch_finish = minutes_since_midnight(0)
|
||||
assert baseline_form.validate() is None
|
||||
|
||||
|
||||
def test_no_breaks(baseline_form):
|
||||
def test_no_breaks(baseline_form: model_generator.FormData):
|
||||
# Check that the times are correct in the absence of breaks.
|
||||
baseline_form.infected_dont_have_breaks_with_exposed = False
|
||||
baseline_form.exposed_lunch_option = False
|
||||
baseline_form.exposed_coffee_breaks = 0
|
||||
baseline_form.exposed_start = 9 * 60
|
||||
baseline_form.exposed_finish = 17 * 60
|
||||
baseline_form.infected_start = 10 * 60
|
||||
baseline_form.infected_finish = 15 * 60
|
||||
baseline_form.exposed_coffee_break_option = 'coffee_break_0'
|
||||
baseline_form.exposed_start = minutes_since_midnight(9 * 60)
|
||||
baseline_form.exposed_finish = minutes_since_midnight(17 * 60)
|
||||
baseline_form.infected_start = minutes_since_midnight(10 * 60)
|
||||
baseline_form.infected_finish = minutes_since_midnight(15 * 60)
|
||||
exposed_correct = ((9, 17),)
|
||||
infected_correct = ((10, 15),)
|
||||
assert baseline_form.exposed_present_interval().present_times == exposed_correct
|
||||
assert baseline_form.infected_present_interval().present_times == infected_correct
|
||||
assert present_times(baseline_form.exposed_present_interval()) == exposed_correct
|
||||
assert present_times(baseline_form.infected_present_interval()) == infected_correct
|
||||
|
||||
|
||||
def test_coffee_lunch_breaks(baseline_form):
|
||||
def test_coffee_lunch_breaks(baseline_form: model_generator.FormData):
|
||||
baseline_form.exposed_coffee_duration = 30
|
||||
baseline_form.exposed_coffee_breaks = 4
|
||||
baseline_form.exposed_start = 9 * 60
|
||||
baseline_form.exposed_finish = 18 * 60
|
||||
baseline_form.exposed_lunch_start = 12 * 60 + 30
|
||||
baseline_form.exposed_lunch_finish = 13 * 60 + 30
|
||||
baseline_form.exposed_coffee_break_option = 'coffee_break_4'
|
||||
baseline_form.exposed_start = minutes_since_midnight(9 * 60)
|
||||
baseline_form.exposed_finish = minutes_since_midnight(18 * 60)
|
||||
baseline_form.exposed_lunch_start = minutes_since_midnight(12 * 60 + 30)
|
||||
baseline_form.exposed_lunch_finish = minutes_since_midnight(13 * 60 + 30)
|
||||
correct = ((9, 9+50/60), (10+20/60, 11+10/60), (11+40/60, 12+30/60),
|
||||
(13+30/60, 14+40/60), (15+10/60, 16+20/60), (16+50/60, 18))
|
||||
np.testing.assert_allclose(baseline_form.exposed_present_interval().present_times, correct, rtol=1e-14)
|
||||
np.testing.assert_allclose(present_times(baseline_form.exposed_present_interval()), correct, rtol=1e-14)
|
||||
|
||||
|
||||
def test_coffee_lunch_breaks_unbalance(baseline_form):
|
||||
def test_coffee_lunch_breaks_unbalance(baseline_form: model_generator.FormData):
|
||||
baseline_form.exposed_coffee_duration = 30
|
||||
baseline_form.exposed_coffee_breaks = 2
|
||||
baseline_form.exposed_start = 9 * 60
|
||||
baseline_form.exposed_finish = 13 * 60 + 30
|
||||
baseline_form.exposed_lunch_start = 12 * 60 + 30
|
||||
baseline_form.exposed_lunch_finish = 13 * 60 + 30
|
||||
baseline_form.exposed_coffee_break_option = 'coffee_break_2'
|
||||
baseline_form.exposed_start = minutes_since_midnight(9 * 60)
|
||||
baseline_form.exposed_finish = minutes_since_midnight(13 * 60 + 30)
|
||||
baseline_form.exposed_lunch_start = minutes_since_midnight(12 * 60 + 30)
|
||||
baseline_form.exposed_lunch_finish = minutes_since_midnight(13 * 60 + 30)
|
||||
correct = ((9, 9+50/60), (10+20/60, 11+10/60), (11+40/60, 12+30/60))
|
||||
np.testing.assert_allclose(baseline_form.exposed_present_interval().present_times, correct, rtol=1e-14)
|
||||
np.testing.assert_allclose(present_times(baseline_form.exposed_present_interval()), correct, rtol=1e-14)
|
||||
|
||||
|
||||
def test_coffee_breaks(baseline_form):
|
||||
def test_coffee_breaks(baseline_form: model_generator.FormData):
|
||||
baseline_form.exposed_coffee_duration = 10
|
||||
baseline_form.exposed_coffee_breaks = 4
|
||||
baseline_form.exposed_start = 9 * 60
|
||||
baseline_form.exposed_finish = 10 * 60
|
||||
baseline_form.exposed_coffee_break_option = 'coffee_break_4'
|
||||
baseline_form.exposed_start = minutes_since_midnight(9 * 60)
|
||||
baseline_form.exposed_finish = minutes_since_midnight(10 * 60)
|
||||
baseline_form.exposed_lunch_option = False
|
||||
correct = ((9, 9+4/60), (9+14/60, 9+18/60), (9+28/60, 9+32/60), (9+42/60, 9+46/60), (9+56/60, 10))
|
||||
np.testing.assert_allclose(baseline_form.exposed_present_interval().present_times, correct, rtol=1e-14)
|
||||
np.testing.assert_allclose(present_times(baseline_form.exposed_present_interval()), correct, rtol=1e-14)
|
||||
|
||||
|
||||
def test_key_validation(baseline_form_data):
|
||||
|
|
|
|||
Loading…
Reference in a new issue