diff --git a/caimira/apps/calculator/co2_model_generator.py b/caimira/apps/calculator/co2_model_generator.py
index a4a6a9c2..3544fe54 100644
--- a/caimira/apps/calculator/co2_model_generator.py
+++ b/caimira/apps/calculator/co2_model_generator.py
@@ -182,8 +182,7 @@ class CO2FormData(FormData):
return models.CO2DataModel(
data_registry=self.data_registry,
room_volume=self.room_volume,
- number=models.IntPiecewiseConstant(transition_times=tuple(all_state_changes), values=tuple(total_people)),
- presence=None,
+ occupancy=models.IntPiecewiseConstant(transition_times=tuple(all_state_changes), values=tuple(total_people)),
ventilation_transition_times=self.ventilation_transition_times(),
times=self.CO2_data['times'],
CO2_concentrations=self.CO2_data['CO2'],
diff --git a/caimira/apps/calculator/static/js/co2_form.js b/caimira/apps/calculator/static/js/co2_form.js
index e846bbcf..ee31c24e 100644
--- a/caimira/apps/calculator/static/js/co2_form.js
+++ b/caimira/apps/calculator/static/js/co2_form.js
@@ -268,16 +268,20 @@ function displayFittingData(json_response) {
" m³/h"
);
let ventilation_table =
- "
| Time (HH:MM) | ACH value (h⁻¹) |
";
- json_response["ventilation_values"].forEach((val, index) => {
+ "| Time (HH:MM) | ACH value (h⁻¹) | Flow rate (L/s/person) |
";
+ json_response["ventilation_values"].forEach((CO2_val, index) => {
let transition_times = displayTransitionTimesHourFormat(
json_response["transition_times"][index],
json_response["transition_times"][index + 1]
);
- ventilation_table += `| ${transition_times} | ${val.toPrecision(
- 2
- )} |
`;
+
+ ventilation_table += `
+ | ${transition_times} |
+ ${CO2_val.toPrecision(2)} |
+ ${json_response['ventilation_lsp_values'][index].toPrecision(2)} |
+
`;
});
+
$("#disable_fitting_algorithm").prop("disabled", false);
$("#ventilation_rate_fit").html(ventilation_table);
$("#generate_fitting_data").html("Fit data");
diff --git a/caimira/apps/templates/base/calculator.form.html.j2 b/caimira/apps/templates/base/calculator.form.html.j2
index 745612e6..497bf33e 100644
--- a/caimira/apps/templates/base/calculator.form.html.j2
+++ b/caimira/apps/templates/base/calculator.form.html.j2
@@ -380,7 +380,7 @@
-
+
HEPA filtration:
diff --git a/caimira/models.py b/caimira/models.py
index 88f9ffb1..b22b7a5f 100644
--- a/caimira/models.py
+++ b/caimira/models.py
@@ -1532,28 +1532,30 @@ class CO2DataModel:
'''
data_registry: DataRegistry
room_volume: float
- number: typing.Union[int, IntPiecewiseConstant]
- presence: typing.Optional[Interval]
+ occupancy: IntPiecewiseConstant
ventilation_transition_times: typing.Tuple[float, ...]
times: typing.Sequence[float]
CO2_concentrations: typing.Sequence[float]
- def CO2_concentrations_from_params(self,
- exhalation_rate: float,
- ventilation_values: typing.Tuple[float, ...]) -> typing.List[_VectorisedFloat]:
- CO2_concentrations = CO2ConcentrationModel(
+ def CO2_concentration_model(self,
+ exhalation_rate: float,
+ ventilation_values: typing.Tuple[float, ...]) -> CO2ConcentrationModel:
+ return CO2ConcentrationModel(
data_registry=self.data_registry,
room=Room(volume=self.room_volume),
ventilation=CustomVentilation(PiecewiseConstant(
self.ventilation_transition_times, ventilation_values)),
CO2_emitters=SimplePopulation(
- number=self.number,
- presence=self.presence,
+ number=self.occupancy,
+ presence=None,
activity=Activity(
exhalation_rate=exhalation_rate, inhalation_rate=exhalation_rate),
)
)
- return [CO2_concentrations.concentration(time) for time in self.times]
+
+ def CO2_concentrations_from_params(self, CO2_concentration_model: CO2ConcentrationModel) -> typing.List[_VectorisedFloat]:
+ # Calculate the predictive CO2 concentration
+ return [CO2_concentration_model.concentration(time) for time in self.times]
def CO2_fit_params(self):
if len(self.times) != len(self.CO2_concentrations):
@@ -1566,10 +1568,11 @@ class CO2DataModel:
def fun(x):
exhalation_rate = x[0]
ventilation_values = tuple(x[1:])
- the_concentrations = self.CO2_concentrations_from_params(
+ CO2_concentration_model = self.CO2_concentration_model(
exhalation_rate=exhalation_rate,
ventilation_values=ventilation_values
)
+ the_concentrations = self.CO2_concentrations_from_params(CO2_concentration_model)
return np.sqrt(np.sum((np.array(self.CO2_concentrations) -
np.array(the_concentrations))**2))
# The goal is to minimize the difference between the two different curves (known concentrations vs. predicted concentrations)
@@ -1577,10 +1580,49 @@ class CO2DataModel:
bounds=[(0, None) for _ in range(len(self.ventilation_transition_times))],
options={'xtol': 1e-3})
+ # Final prediction
exhalation_rate = res_dict['x'][0]
- ventilation_values = res_dict['x'][1:]
- predictive_CO2 = self.CO2_concentrations_from_params(exhalation_rate=exhalation_rate, ventilation_values=ventilation_values)
- return {"exhalation_rate": exhalation_rate, "ventilation_values": list(ventilation_values), 'predictive_CO2': list(predictive_CO2)}
+ ventilation_values = res_dict['x'][1:] # In ACH
+
+ # Final CO2ConcentrationModel with obtained prediction
+ the_CO2_concentration_model = self.CO2_concentration_model(
+ exhalation_rate=exhalation_rate,
+ ventilation_values=ventilation_values
+ )
+ the_predictive_CO2 = self.CO2_concentrations_from_params(the_CO2_concentration_model)
+
+ # Ventilation in L/s/person
+ def max_occupancy_in_interval(start: float, stop: float) -> int:
+ """
+ Given a certain ventilation interval, get the maximum number of
+ people in that period od time.
+ """
+ max_people: int = 0
+ for i, (people_start, people_stop) in enumerate(zip(self.occupancy.transition_times[:-1],
+ self.occupancy.transition_times[1:])):
+ if people_stop <= start or people_start >= stop:
+ continue
+ if self.occupancy.values[i] > max_people: max_people = self.occupancy.values[i]
+ return max_people
+
+ vent_volume_liter_person = []
+ for i, (vent_start, vent_stop) in enumerate(zip(self.ventilation_transition_times[:-1],
+ self.ventilation_transition_times[1:])):
+ max_people = max_occupancy_in_interval(vent_start, vent_stop)
+ if max_people == 0:
+ # If in a certain interval there are no occupancy, the flow rate per second/person is 0
+ vent_volume_liter_person.append(0)
+ else:
+ vent_volume_liter_person.append(
+ ventilation_values[i] / 3600 * self.room_volume / max_people * 1000
+ ) # 1m^3 = 1000L
+
+ return {
+ "exhalation_rate": exhalation_rate,
+ "ventilation_values": list(ventilation_values),
+ "ventilation_lsp_values": vent_volume_liter_person,
+ 'predictive_CO2': list(the_predictive_CO2)
+ }
@dataclass(frozen=True)
diff --git a/caimira/tests/models/test_fitting_algorithm.py b/caimira/tests/models/test_fitting_algorithm.py
index 65b6f447..b917f9a1 100644
--- a/caimira/tests/models/test_fitting_algorithm.py
+++ b/caimira/tests/models/test_fitting_algorithm.py
@@ -41,9 +41,8 @@ def test_fitting_algorithm(data_registry, activity_type, ventilation_active, air
data_model = models.CO2DataModel(
data_registry=data_registry,
room_volume=75,
- number=models.IntPiecewiseConstant(transition_times=tuple(
+ occupancy=models.IntPiecewiseConstant(transition_times=tuple(
[8, 12, 13, 17]), values=tuple([2, 1, 2])),
- presence=None,
ventilation_transition_times=tuple(ventilation_active),
times=times,
CO2_concentrations=CO2_concentrations