Skip to content

Commit

Permalink
optimised validation method
Browse files Browse the repository at this point in the history
  • Loading branch information
lrdossan committed Sep 25, 2023
1 parent eedf822 commit cdc2b60
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 98 deletions.
105 changes: 20 additions & 85 deletions caimira/apps/calculator/co2_model_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ class CO2FormData(model_generator.FormData):
exposed_start: model_generator.minutes_since_midnight
fitting_ventilation_states: list
fitting_ventilation_type: str
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_coffee_break_option: str
infected_coffee_duration: int
infected_dont_have_breaks_with_exposed: bool
infected_finish: model_generator.minutes_since_midnight
infected_lunch_finish: model_generator.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: model_generator.minutes_since_midnight #Used if infected_dont_have_breaks_with_exposed
infected_lunch_finish: model_generator.minutes_since_midnight
infected_lunch_option: bool
infected_lunch_start: model_generator.minutes_since_midnight
infected_people: int
infected_start: model_generator.minutes_since_midnight
room_volume: float
Expand Down Expand Up @@ -98,73 +98,22 @@ def from_dict(self, form_data: typing.Dict) -> "CO2FormData":
raise ValueError(f'Invalid argument "{html.escape(key)}" given')

instance = self(**form_data)
instance.validate()
instance.validate_population_parameters()
return instance

def validate(self):
# Validate number of infected <= number of total people
if self.infected_people >= self.total_people:
raise ValueError('Number of infected people cannot be more or equal than number of total people.')

# Validate time intervals selected by user
time_intervals = [
['exposed_start', 'exposed_finish'],
['infected_start', 'infected_finish'],
]
if self.exposed_lunch_option:
time_intervals.append(['exposed_lunch_start', 'exposed_lunch_finish'])
if self.infected_dont_have_breaks_with_exposed and self.infected_lunch_option:
time_intervals.append(['infected_lunch_start', 'infected_lunch_finish'])

for start_name, end_name in time_intervals:
start = getattr(self, start_name)
end = getattr(self, end_name)
if start > end:
raise ValueError(
f"{start_name} must be less than {end_name}. Got {start} and {end}.")

def validate_lunch(start, finish):
lunch_start = getattr(self, f'{population}_lunch_start')
lunch_finish = getattr(self, f'{population}_lunch_finish')
return (start <= lunch_start <= finish and
start <= lunch_finish <= finish)

def get_lunch_mins(population):
lunch_mins = 0
if getattr(self, f'{population}_lunch_option'):
lunch_mins = getattr(self, f'{population}_lunch_finish') - getattr(self, f'{population}_lunch_start')
return lunch_mins

def get_coffee_mins(population):
coffee_mins = 0
if getattr(self, f'{population}_coffee_break_option') != 'coffee_break_0':
coffee_mins = COFFEE_OPTIONS_INT[getattr(self, f'{population}_coffee_break_option')] * getattr(self, f'{population}_coffee_duration')
return coffee_mins

def get_activity_mins(population):
return getattr(self, f'{population}_finish') - getattr(self, f'{population}_start')

populations = ['exposed', 'infected'] if self.infected_dont_have_breaks_with_exposed else ['exposed']
for population in populations:
# Validate lunch time within the activity times.
if (getattr(self, f'{population}_lunch_option') and
not validate_lunch(getattr(self, f'{population}_start'), getattr(self, f'{population}_finish'))
):
raise ValueError(
f"{population} lunch break must be within presence times."
)

# Length of breaks < length of activity
if (get_lunch_mins(population) + get_coffee_mins(population)) >= get_activity_mins(population):
raise ValueError(
f"Length of breaks >= Length of {population} presence."
)

validation_tuples = [('exposed_coffee_break_option', COFFEE_OPTIONS_INT),
('infected_coffee_break_option', COFFEE_OPTIONS_INT),]
for attr_name, valid_set in validation_tuples:
if getattr(self, attr_name) not in valid_set:
raise ValueError(f"{getattr(self, attr_name)} is not a valid value for {attr_name}")
def population_present_changes(self) -> typing.List[float]:
state_change_times = set(self.infected_present_interval().transition_times())
state_change_times.update(self.exposed_present_interval().transition_times())
return sorted(state_change_times)

def ventilation_transition_times(self) -> typing.Tuple[float, ...]:
# Check what type of ventilation is considered for the fitting
if self.fitting_ventilation_type == 'fitting_natural_ventilation':
vent_states = self.fitting_ventilation_states
vent_states.append(self.CO2_data['times'][-1])
return tuple(vent_states)
else:
return tuple((self.CO2_data['times'][0], self.CO2_data['times'][-1]))

def build_model(self, size=DEFAULT_MC_SAMPLE_SIZE) -> models.CO2DataModel: # type: ignore
infected_population: models.Population = self.infected_population().build_model(size)
Expand All @@ -180,21 +129,7 @@ def build_model(self, size=DEFAULT_MC_SAMPLE_SIZE) -> models.CO2DataModel: # typ
ventilation_transition_times=self.ventilation_transition_times(),
times=self.CO2_data['times'],
CO2_concentrations=self.CO2_data['CO2'],
)

def population_present_changes(self) -> typing.List[float]:
state_change_times = set(self.infected_present_interval().transition_times())
state_change_times.update(self.exposed_present_interval().transition_times())
return sorted(state_change_times)

def ventilation_transition_times(self) -> typing.Tuple[float, ...]:
# Check what type of ventilation is considered for the fitting
if self.fitting_ventilation_type == 'fitting_natural_ventilation':
vent_states = self.fitting_ventilation_states
vent_states.append(self.CO2_data['times'][-1])
return tuple(vent_states)
else:
return tuple((self.CO2_data['times'][0], self.CO2_data['times'][-1]))
)


for _field in dataclasses.fields(CO2FormData):
Expand Down
32 changes: 21 additions & 11 deletions caimira/apps/calculator/model_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,11 @@ def to_dict(cls, form: "FormData", strip_defaults: bool = False) -> dict:
if default is not NO_DEFAULT and value in [default, 'not-applicable']:
form_dict.pop(attr)
return form_dict

def validate(self):
# Validate number of infected people == 1 when activity is Conference/Training.
if self.activity_type == 'training' and self.infected_people > 1:
raise ValueError('Conference/Training activities are limited to 1 infected.')

def validate_population_parameters(self):
# Validate number of infected <= number of total people
elif self.infected_people >= self.total_people:
raise ValueError('Number of infected people cannot be more or equal than number of total people.')
if self.infected_people >= self.total_people:
raise ValueError('Number of infected people cannot be more or equal than number of total people.')

# Validate time intervals selected by user
time_intervals = [
Expand Down Expand Up @@ -190,7 +187,7 @@ def get_coffee_mins(population):
def get_activity_mins(population):
return getattr(self, f'{population}_finish') - getattr(self, f'{population}_start')

populations = ['exposed', 'infected'] if self.infected_dont_have_breaks_with_exposed else ['exposed']
populations = ['exposed', 'infected'] if self.infected_dont_have_breaks_with_exposed else ['exposed']
for population in populations:
# Validate lunch time within the activity times.
if (getattr(self, f'{population}_lunch_option') and
Expand All @@ -205,10 +202,17 @@ def get_activity_mins(population):
raise ValueError(
f"Length of breaks >= Length of {population} presence."
)

for attr_name, valid_set in [('exposed_coffee_break_option', COFFEE_OPTIONS_INT),
('infected_coffee_break_option', COFFEE_OPTIONS_INT)]:
if getattr(self, attr_name) not in valid_set:
raise ValueError(f"{getattr(self, attr_name)} is not a valid value for {attr_name}")

validation_tuples = [('activity_type', ACTIVITY_TYPES),
('exposed_coffee_break_option', COFFEE_OPTIONS_INT),
('infected_coffee_break_option', COFFEE_OPTIONS_INT),
def validate(self):
# Validate population parameters
self.validate_population_parameters()

validation_tuples = [('activity_type', ACTIVITY_TYPES),
('mechanical_ventilation_type', MECHANICAL_VENTILATION_TYPES),
('mask_type', MASK_TYPES),
('mask_wearing_option', MASK_WEARING_OPTIONS),
Expand All @@ -221,10 +225,16 @@ def get_activity_mins(population):
('ascertainment_bias', CONFIDENCE_LEVEL_OPTIONS),
('vaccine_type', VACCINE_TYPE),
('vaccine_booster_type', VACCINE_BOOSTER_TYPE),]

for attr_name, valid_set in validation_tuples:
if getattr(self, attr_name) not in valid_set:
raise ValueError(f"{getattr(self, attr_name)} is not a valid value for {attr_name}")

# Validate number of infected people == 1 when activity is Conference/Training.
if self.activity_type == 'training' and self.infected_people > 1:
raise ValueError('Conference/Training activities are limited to 1 infected.')

# Validate ventilation parameters
if self.ventilation_type == 'natural_ventilation':
if self.window_type == 'not-applicable':
raise ValueError(
Expand Down
3 changes: 2 additions & 1 deletion caimira/apps/calculator/report_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,12 @@ def readable_minutes(minutes: int) -> str:


def hour_format(hour: float) -> str:
# Convert float hour to HH:MM format
hours = int(hour)
minutes = int(hour % 1 * 60)

return f"{hours}:{minutes if minutes != 0 else '00'}"


def percentage(absolute: float) -> float:
return absolute * 100

Expand Down
3 changes: 2 additions & 1 deletion caimira/apps/calculator/static/js/co2_form.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Input data for CO2 fitting algorithm
const CO2_data_form = [
"CO2_data",
"exposed_coffee_break_option",
Expand Down Expand Up @@ -104,7 +105,7 @@ function uploadFile(endpoint) {
}
}

// Call function to convert Excel file to JSON and further processing
// Convert Excel file to JSON and further processing
try {
generateJSONStructure(endpoint, data);
// If all validations pass, process the file here or display a success message
Expand Down

0 comments on commit cdc2b60

Please sign in to comment.