Dynamic Occupancy Groups
The dynamic occupancy introduces the capability to handle multiple occupancy groups, composed of infected and/or exposed population within an event. Instead of having one single set of parameters for all the exposed (susceptible) occupants, this feature enables the model to have n groups, each with a specific occupancy profile and set of short-range interactions. This can also be used to define dynamic occupancy groups for the CO2 fitting algorithm.
Tip
When in development mode, this feature enables the model to have n groups, each with their specific characteristics, e.g. masks, physical activity or host immunities. Check below for more details.
This page covers the description, input structure, and results structure of this feature.
Feature Description
The feature revolves around the concept of a new ExposureModelGroup class, which encapsulates a set of ExposureModel instances. Each ExposureModel represents a distinct group of exposed occupants.
Input Structure
The modelling of dynamic occupancy with the definition of groups is controlled by the occupancy input sent by the frontend, initially defined by an empty dictionary:
occupancy = "{}"
In case the occupancy is not given as input in the request, or if its value is set to the default (empty dictionary), the algorithm will execute as in the legacy version of CAiMIRA (i.e. without dynamic groups).
Parameters
The occupancy object defines various occupancy groups, each identified by an unique key (group_1, group_2, etc.). Each group includes:
total_people: The total number of individuals in the group, including both infected and exposed individualsinfected: The total number of infected individuals in the grouppresence: A list of time intervals (start_timeandfinish_time) during which the group was present, formatted asHH:MM
Note
The infected value must be an integer that does not exceed total_people and has a minimum value of 0 when the group consists solely of exposed individuals. The group key is a custom string chosen by the user to identify the respective group.
Example
occupancy = {
"group_1": {
"total_people": 5,
"infected": 2,
"presence": [
{"start_time": "09:00", "finish_time": "12:00"},
{"start_time": "13:00", "finish_time": "17:00"}
]
},
"group_2": {
"total_people": 10,
"infected": 5,
"presence": [
{"start_time": "10:00", "finish_time": "11:00"}
]
}
}
- The first group, identified by
group_1, consists of 5 individuals (3 exposed, 2 infected), present in two time intervals:09:00to12:0013:00to17:00
- The second group, identified by
group_2, consists of 10 individuals (5 exposed, 5 infected), present from:10:00to11:00
Info
The number of groups in the occupancy input results in the creation of multiple exposure group objects. From the previous example, where two groups are defined, the two exposure instances of each group will have following structure, respectively:
- Exposed population from
group_1-3people from09:00to12:00and from13:00to17:00 - Exposed population from
group_2-5people from10:00to11:00
The combination of the presence and number of infected of all groups leads to the creation of a single infected object (InfectedPopulation), which is replicated across all groups. From the previous example, the respective instance will be composed of the following structure:
2infected, from09:00to10:00(2people fromgroup_1and0people fromgroup_2)7infected, from10:00to11:00(2people fromgroup_1and5people fromgroup_2)2infected, from11:00to12:00(2people fromgroup_1and0people fromgroup_2)0infected, from12:00to13:00(0people fromgroup_1and0people fromgroup_2)2infected, from13:00to17:00(2people fromgroup_1and0people fromgroup_2)
Note that the infected population contributes equally to the long-range concentration in all the occupant groups.
Short-range interactions
The short_range_interactions object defines the number of short-range interactions per occupancy group. Each set of interactions is identified by its corresponding group key (group_1, group_2, etc.), mapping to an object that specifies:
expiration: expiration type of that short-range interaction (Breathing,Speaking, orShouting)start_time: when the interaction starts, formatted asHH:MMduration: duration of the interaction (in minutes)
Short-range interactions are processed separately form the long-range exposure. Each short-range object in the same group assumes that the same exposed occupant in that group is interacting with one of the infectors during the specified period. In other words, these interactions are all linked to the same occupant and the total short-range exposure will be the cumulative sum of all the short-range objects in the occupancy group. To be able to appoint different short-range interactions to different exposed occupants in a given group, a workaround is to subdivide them into smaller groups. See examples below.
Example 1
The occupancy object is the one defined above (here).
short_range_interactions = {
"group_1": [
{"expiration": "Shouting", "start_time": "10:00", "duration": 30},
{"expiration": "Speaking", "start_time": "11:15", "duration": 15}
],
"group_2": [
{"expiration": "Shouting", "start_time": "10:15", "duration": 30}
]
}
group_1has 2 interactions with exposed occupant A in two time intervals:Shouting, from10:00for30minutesSpeaking, from11:15for15minutes
group_2has 1 interaction with exposed occupant B, in a single time interval:Shoutingfrom10:15for30minutes
Example 2
occupancy = {
"group_1_A": {
"total_people": 4,
"infected": 1,
"presence": [
{"start_time": "09:00", "finish_time": "12:00"},
{"start_time": "13:00", "finish_time": "17:00"}
]
},
"group_1_B": {
"total_people": 1,
"infected": 1,
"presence": [
{"start_time": "09:00", "finish_time": "12:00"},
{"start_time": "13:00", "finish_time": "17:00"}
]
},
...
}
...
short_range_interactions = {
"group_1_A": [
{"expiration": "Shouting", "start_time": "10:00", "duration": 30},
],
"group_1_B": [
{"expiration": "Speaking", "start_time": "11:15", "duration": 15}
],
}
-
group_1_Ahas 1 interaction with exposed occupant A in a single time interval:Shouting, from10:00for30minutes,
-
group_1_Bhas 1 interaction with exposed occupant B in a single time interval:Speaking, from11:15for15minutes.
Note
The input format must follow this dictionary structure, regardless of whether occupancy is defined. For example, in the legacy format, the short-range group should always be specified within a dictionary under the identifier group_1, as this is the default single-group identifier (see here).
Extensive validation is conducted in the algorithm to guarantee that the interactions are related to an existing occupancy group (if applicable), and that the times are within the concerned group simulation time.
Warning
Short-range interactions cannot overlap within the same group. At any given time, only one short-range interaction between the exposed and infected individuals is allowed per group.
Model generator (development mode)
Following the previous JSON example of the occupancy input, the ExposureModelGroup object that would be generated should have the following structure:
Structure
model=ExposureModelGroup(
exposure_models=(
ExposureModel(
data_registry=...,
concentration_model=concentration_model_infected,
short_range=(
ShortRangeModel(
data_registry=...,
expiration="Shouting",
activity=...,
presence=SpecificInterval(((10., 11.5),)),
distance=...,
),
ShortRangeModel(
data_registry=...,
expiration="Shouting",
activity=...,
presence=SpecificInterval(((11.25, 11.5),)),
distance=...,
),
),
exposed=Population(
number=3,
presence=SpecificInterval(((9., 12.), (13., 17.))),
activity=...,
mask=...,
host_immunity=...,
),
geographical_data=...,
exposed_to_short_range=...,
identifier="group_1',
),
ExposureModel(
data_registry=...,
concentration_model=concentration_model_infected,
short_range=(
ShortRangeModel(
data_registry=...,
expiration="Shouting",
activity=...,
presence=SpecificInterval(((10.25, 10.75),)),
distance=...,
),
),
exposed=Population(
number=5,
presence=SpecificInterval(((10., 11.),)),
activity=...,
mask=...,
host_immunity=...,
),
geographical_data=...,
exposed_to_short_range=...,
identifier="group_2",
)
),
data_registry=...,
)
In which the ConcentrationModel object should have the following structure:
concentration_model_infected=ConcentrationModel(
data_registry=...,
room=...,
ventilation=...,
infected=InfectedPopulation(
data_registry=...,
number=IntPiecewiseConstant(
transition_times=(9.0, 10.0, 11.0, 12.0, 13.0, 17.0),
values=(2, 7, 2, 0, 2),
),
presence=None,
virus=...,
mask=...,
activity=...,
expiration=...,
host_immunity=...,
),
evaporation_factor=...,
)
In line with the example above, we see:
- 2
ExposureModel: one for each occupancy group - 3
ShortRangeModel: two forgroup_1and one forgroup_2 - In the
exposedpopulation (Population):- the
numberis calculated fromtotal_people-infected - the
presenceinterval is taken from theoccupancygroup object - the
mask,activity, etc. can be different for each group
- the
- In the
infectedpopulation (InfectedPopulation):- the
numberinterval is calculated from theoccupancygroup object as a total sum of the infected from all groups at eachpresenceinterval
- the
- The
ConcentrationModelshall be the same in eachExposureModel
Note
As previously described, each occupancy group (group_1 and group_2) leads to the creation of one ExposureModel, and the ConcentrationModel, originated from the combination of the presence and number of infected, should be the same for all ExposureModel groups.
Note that the InfectedPopulation object is shared across all groups, meaning the infected population contributes equally to the concentration in each model. To ensure consistency, the algorithm verifies that the number and presence parameters of InfectedPopulation remain identical across all ExposureModel instances.
How to interpret one IntPiecewiseConstant instance?
The IntPiecewiseConstant inherits from the PiecewiseConstant (see here), and expects the transition_times and values as arguments.
In the provided example, the infected population has transition_times of (9.0, 10.0, 11.0, 12.0, 13.0, 17.0) with corresponding values (2, 7, 2, 0, 2). This data can be interpreted as follows:
- Between
9.0 (09:00)and10.0 (10:00),2infected were present - Between
10.0 (10:00)and11.0 (11:00),7infected were present - Between
11.0 (11:00)and12.0 (12:00),2infected were present - Between
12.0 (12:00)and13.0 (13:00),0infected were present - Between
13.0 (13:00)and17.0 (17:00),2infected were present
Results structure (model output)
After the defining the simulation and when making a request to the API, the response is structured as a JSON object that consists of a status field indicating success or failure, along with a message providing additional details. If successful, the report_data object contains the computed exposure results per group and model configuration.
Example
{
"status": "success",
"message": "Results generated successfully",
"report_data": {
"model": ...,
"times": ...,
"CO2_concentrations": ...,
"groups": {
"group_1": {
"prob_inf": ...,
"prob_inf_sd": ...,
"prob_dist": ...,
"prob_hist_count": ...,
"prob_hist_bins": ...,
"expected_new_cases": ...,
"exposed_presence_intervals": ...,
"concentrations": ...,
"cumulative_doses": ...,
"long_range_prob": ...,
"long_range_expected_new_cases": ...,
"short_range_interactions": ...,
"concentrations_zoomed": ...,
"long_range_cumulative_doses": ...
},
"group_2": ...
}
}
}
The output response is structured as follows:
-
Top-Level Fields:
status(string) – Indicates whether the request was successful ("success") or if an error occurred ("error").message(string) – Provides a brief description of the response outcome.
-
report_dataObject:model(object) – Representation of the exposure model used.times(array) – Full simulation timestamps.CO2_concentrations(array) – CO2 concentration levels over time.groups(object) – Contains the exposure results for each group.
-
groupsObject:Each key inside groups corresponds to a specific group (e.g.,
"group_1","group_2") and contains the following fields:prob_inf(float) – Mean probability of infection. For occupant groups with short-range interactions, this includes both short- and long-range exposures.prob_inf_sd(float) – Standard deviation of the probability of infection.prob_dist(array) – Probability distribution values.prob_hist_count(array) – Histogram counts for probability distribution.prob_hist_bins(array) – Histogram bin edges for probability distribution.expected_new_cases(float) – Expected number of new cases within the group.exposed_presence_intervals(array) – Time intervals when group members were exposed.concentrations(array) – Viral concentration levels over time.cumulative_doses(array) – Accumulated viral doses over time.
If short-range interactions are considered, additional fields will be included:
long_range_prob(float) – Mean probability of infection of occupant groups without short-range interactions.long_range_expected_new_cases(float) – Expected number of new cases of occupant groups without short-range interactions.short_range_interactions(object) – Contains expiration times and short-range exposure data.concentrations_zoomed(array) – Higher-resolution long-range concentration data.long_range_cumulative_doses(array) – Accumulated viral doses from long-range interactions.
Note
Regardless of whether the occupancy is defined or not, the output format will always follow the described structure.
When using the legacy structure, a group object with a predefined identifier is created (group_1).
Conclusion
The dynamic occupancy feature enhances CAiMIRA's flexibility by allowing to define different occupancy groups in a certain event, leading to a more detailed and accurate risk assessment in heterogenous environments.
For examples on how to extend and use the REST API, see REST API.