Skip to content

Household

Household

Bases: Group

The Household class represents a household and contains information about its residents. We assume four subgroups: 0 - kids 1 - young adults 2 - adults 3 - old adults

Source code in june/groups/household.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
class Household(Group):
    """The Household class represents a household and contains information about
    its residents.
    We assume four subgroups:
    0 - kids
    1 - young adults
    2 - adults
    3 - old adults

    """

    __slots__ = (
        "area",
        "type",
        "composition_type",
        "max_size",
        "residents",
        "quarantine_starting_date",
        "residences_to_visit",
        "being_visited",
        "household_to_care",
        "receiving_care"
    )

    # class SubgroupType(IntEnum):
    #     kids = 0
    #     young_adults = 1
    #     adults = 2
    #     old_adults = 3

    def __init__(self, type=None, area=None, max_size=np.inf, composition_type=None, registered_members_ids=None
    ):
        """
        Type should be on of ["family", "student", "young_adults", "old", "other", "nokids", "ya_parents", "communal"].
        Relatives is a list of people that are related to the family living in the household
        """

        super().__init__()        
        self.area = area
        self.type = type
        self.quarantine_starting_date = -99
        self.max_size = max_size
        self.residents = ()
        self.residences_to_visit = defaultdict(tuple)
        self.household_to_care = None
        self.being_visited = False  # this is True when people from other households have been added to the group
        self.receiving_care = False
        self.composition_type = composition_type
        self.registered_members_ids = registered_members_ids if registered_members_ids is not None else {}

    def _get_leisure_subgroup_for_person(self, person):
        """

        Args:
            person: 

        """
        if person.age <= 17:
            subgroup = self.SubgroupType.kids
        elif person.age <= 25:
            subgroup = self.SubgroupType.young_adults
        elif person.age < 65:
            subgroup = self.SubgroupType.adults
        else:
            subgroup = self.SubgroupType.old_adults
        return subgroup

    def add_to_registered_members(self, person_id, subgroup_type=0):
        """Add a person to the registered members list for a specific subgroup.

        Args:
            person_id (int): The ID of the person to add
            subgroup_type (int, optional, optional): The subgroup to add the person to (default: 0)

        """
        # Create the subgroup if it doesn't exist
        if subgroup_type not in self.registered_members_ids:
            self.registered_members_ids[subgroup_type] = []

        # Add the person if not already in the list
        if person_id not in self.registered_members_ids[subgroup_type]:
            self.registered_members_ids[subgroup_type].append(person_id)

    def add(self, person, subgroup_type=None, activity="residence"):
        """

        Args:
            person: 
            subgroup_type: (Default value = None)
            activity: (Default value = "residence")

        """
        if subgroup_type is None:
            subgroup_type = self.get_leisure_subgroup_type(person)

        if activity == "leisure":
            subgroup_type = self.get_leisure_subgroup_type(person)
            person.subgroups.leisure = self[subgroup_type]
            self[subgroup_type].append(person)
            self.being_visited = True
        elif activity == "residence":
            self[subgroup_type].append(person)
            self.residents = tuple((*self.residents, person))
            person.subgroups.residence = self[subgroup_type]
        else:
            raise NotImplementedError(f"Activity {activity} not supported in household")

    def get_leisure_subgroup_type(cls, person):
        """A person wants to come and visit this household. We need to assign the person
        to the relevant age subgroup, and make sure the residents welcome him and
        don't go do any other leisure activities.

        Args:
            person: 

        """
        if person.age <= 17:
            return cls.SubgroupType.kids
        elif person.age <= 25:
            return cls.SubgroupType.young_adults
        elif person.age < 65:
            return cls.SubgroupType.adults
        else:
            return cls.SubgroupType.old_adults

    def make_household_residents_stay_home(self, to_send_abroad=None):
        """Forces the residents to stay home if they are away doing leisure.
        This is used to welcome visitors.

        Args:
            to_send_abroad: (Default value = None)

        """
        for mate in self.residents:
            if mate.busy:
                if (
                    mate.leisure is not None
                ):  # this person has already been assigned somewhere
                    if not mate.leisure.external:
                        if mate not in mate.leisure.people:
                            # person active somewhere else, let's not disturb them
                            continue
                        mate.leisure.remove(mate)
                    else:
                        ret = to_send_abroad.delete_person(mate, mate.leisure)
                        if ret:
                            # person active somewhere else, let's not disturb them
                            continue
                    mate.subgroups.leisure = mate.residence
                    mate.residence.append(mate)
            else:
                mate.subgroups.leisure = (
                    mate.residence  # person will be added later in the simulator.
                )

    # @property
    # def kids(self):
    #     return self.subgroups[self.SubgroupType.kids]

    # @property
    # def young_adults(self):
    #     return self.subgroups[self.SubgroupType.young_adults]

    # @property
    # def adults(self):
    #     return self.subgroups[self.SubgroupType.adults]

    # @property
    # def old_adults(self):
    #     return self.subgroups[self.SubgroupType.old_adults]

    @property
    def coordinates(self):
        """ """
        return self.area.coordinates

    @property
    def n_residents(self):
        """ """
        return len(self.residents)

    def quarantine(self, time, quarantine_days, household_compliance):
        """

        Args:
            time: 
            quarantine_days: 
            household_compliance: 

        """
        if self.type == "communal":
            return False
        if self.quarantine_starting_date:
            if (
                self.quarantine_starting_date
                < time
                < self.quarantine_starting_date + quarantine_days
            ):
                return random() < household_compliance
        return False

    @property
    def super_area(self):
        """ """
        try:
            return self.area.super_area
        except AttributeError:
            return None

    def clear(self):
        """ """
        super().clear()
        self.being_visited = False
        self.receiving_care = False

    def get_interactive_group(self, people_from_abroad=None):
        """

        Args:
            people_from_abroad: (Default value = None)

        """
        return InteractiveHousehold(self, people_from_abroad=people_from_abroad)

    def get_leisure_subgroup(self, person, subgroup_type, to_send_abroad):
        """

        Args:
            person: 
            subgroup_type: 
            to_send_abroad: 

        """
        self.being_visited = True
        self.make_household_residents_stay_home(to_send_abroad=to_send_abroad)
        return self[self._get_leisure_subgroup_for_person(person=person)]

    def get_all_registered_members_ids(self):
        """ """

        all_member_ids = [member_id for subgroup_members in self.registered_members_ids.values() 
                    for member_id in subgroup_members]

        return all_member_ids

coordinates property

n_residents property

super_area property

__init__(type=None, area=None, max_size=np.inf, composition_type=None, registered_members_ids=None)

Type should be on of ["family", "student", "young_adults", "old", "other", "nokids", "ya_parents", "communal"]. Relatives is a list of people that are related to the family living in the household

Source code in june/groups/household.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def __init__(self, type=None, area=None, max_size=np.inf, composition_type=None, registered_members_ids=None
):
    """
    Type should be on of ["family", "student", "young_adults", "old", "other", "nokids", "ya_parents", "communal"].
    Relatives is a list of people that are related to the family living in the household
    """

    super().__init__()        
    self.area = area
    self.type = type
    self.quarantine_starting_date = -99
    self.max_size = max_size
    self.residents = ()
    self.residences_to_visit = defaultdict(tuple)
    self.household_to_care = None
    self.being_visited = False  # this is True when people from other households have been added to the group
    self.receiving_care = False
    self.composition_type = composition_type
    self.registered_members_ids = registered_members_ids if registered_members_ids is not None else {}

add(person, subgroup_type=None, activity='residence')

Parameters:

Name Type Description Default
person
required
subgroup_type

(Default value = None)

None
activity

(Default value = "residence")

'residence'
Source code in june/groups/household.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def add(self, person, subgroup_type=None, activity="residence"):
    """

    Args:
        person: 
        subgroup_type: (Default value = None)
        activity: (Default value = "residence")

    """
    if subgroup_type is None:
        subgroup_type = self.get_leisure_subgroup_type(person)

    if activity == "leisure":
        subgroup_type = self.get_leisure_subgroup_type(person)
        person.subgroups.leisure = self[subgroup_type]
        self[subgroup_type].append(person)
        self.being_visited = True
    elif activity == "residence":
        self[subgroup_type].append(person)
        self.residents = tuple((*self.residents, person))
        person.subgroups.residence = self[subgroup_type]
    else:
        raise NotImplementedError(f"Activity {activity} not supported in household")

add_to_registered_members(person_id, subgroup_type=0)

Add a person to the registered members list for a specific subgroup.

Parameters:

Name Type Description Default
person_id int

The ID of the person to add

required
subgroup_type (int, optional)

The subgroup to add the person to (default: 0)

0
Source code in june/groups/household.py
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def add_to_registered_members(self, person_id, subgroup_type=0):
    """Add a person to the registered members list for a specific subgroup.

    Args:
        person_id (int): The ID of the person to add
        subgroup_type (int, optional, optional): The subgroup to add the person to (default: 0)

    """
    # Create the subgroup if it doesn't exist
    if subgroup_type not in self.registered_members_ids:
        self.registered_members_ids[subgroup_type] = []

    # Add the person if not already in the list
    if person_id not in self.registered_members_ids[subgroup_type]:
        self.registered_members_ids[subgroup_type].append(person_id)

clear()

Source code in june/groups/household.py
222
223
224
225
226
def clear(self):
    """ """
    super().clear()
    self.being_visited = False
    self.receiving_care = False

get_all_registered_members_ids()

Source code in june/groups/household.py
250
251
252
253
254
255
256
def get_all_registered_members_ids(self):
    """ """

    all_member_ids = [member_id for subgroup_members in self.registered_members_ids.values() 
                for member_id in subgroup_members]

    return all_member_ids

get_interactive_group(people_from_abroad=None)

Parameters:

Name Type Description Default
people_from_abroad

(Default value = None)

None
Source code in june/groups/household.py
228
229
230
231
232
233
234
235
def get_interactive_group(self, people_from_abroad=None):
    """

    Args:
        people_from_abroad: (Default value = None)

    """
    return InteractiveHousehold(self, people_from_abroad=people_from_abroad)

get_leisure_subgroup(person, subgroup_type, to_send_abroad)

Parameters:

Name Type Description Default
person
required
subgroup_type
required
to_send_abroad
required
Source code in june/groups/household.py
237
238
239
240
241
242
243
244
245
246
247
248
def get_leisure_subgroup(self, person, subgroup_type, to_send_abroad):
    """

    Args:
        person: 
        subgroup_type: 
        to_send_abroad: 

    """
    self.being_visited = True
    self.make_household_residents_stay_home(to_send_abroad=to_send_abroad)
    return self[self._get_leisure_subgroup_for_person(person=person)]

get_leisure_subgroup_type(person)

A person wants to come and visit this household. We need to assign the person to the relevant age subgroup, and make sure the residents welcome him and don't go do any other leisure activities.

Parameters:

Name Type Description Default
person
required
Source code in june/groups/household.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
def get_leisure_subgroup_type(cls, person):
    """A person wants to come and visit this household. We need to assign the person
    to the relevant age subgroup, and make sure the residents welcome him and
    don't go do any other leisure activities.

    Args:
        person: 

    """
    if person.age <= 17:
        return cls.SubgroupType.kids
    elif person.age <= 25:
        return cls.SubgroupType.young_adults
    elif person.age < 65:
        return cls.SubgroupType.adults
    else:
        return cls.SubgroupType.old_adults

make_household_residents_stay_home(to_send_abroad=None)

Forces the residents to stay home if they are away doing leisure. This is used to welcome visitors.

Parameters:

Name Type Description Default
to_send_abroad

(Default value = None)

None
Source code in june/groups/household.py
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def make_household_residents_stay_home(self, to_send_abroad=None):
    """Forces the residents to stay home if they are away doing leisure.
    This is used to welcome visitors.

    Args:
        to_send_abroad: (Default value = None)

    """
    for mate in self.residents:
        if mate.busy:
            if (
                mate.leisure is not None
            ):  # this person has already been assigned somewhere
                if not mate.leisure.external:
                    if mate not in mate.leisure.people:
                        # person active somewhere else, let's not disturb them
                        continue
                    mate.leisure.remove(mate)
                else:
                    ret = to_send_abroad.delete_person(mate, mate.leisure)
                    if ret:
                        # person active somewhere else, let's not disturb them
                        continue
                mate.subgroups.leisure = mate.residence
                mate.residence.append(mate)
        else:
            mate.subgroups.leisure = (
                mate.residence  # person will be added later in the simulator.
            )

quarantine(time, quarantine_days, household_compliance)

Parameters:

Name Type Description Default
time
required
quarantine_days
required
household_compliance
required
Source code in june/groups/household.py
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
def quarantine(self, time, quarantine_days, household_compliance):
    """

    Args:
        time: 
        quarantine_days: 
        household_compliance: 

    """
    if self.type == "communal":
        return False
    if self.quarantine_starting_date:
        if (
            self.quarantine_starting_date
            < time
            < self.quarantine_starting_date + quarantine_days
        ):
            return random() < household_compliance
    return False

Households

Bases: Supergroup

Contains all households for the given area, and information about them.

Source code in june/groups/household.py
259
260
261
262
263
264
265
class Households(Supergroup):
    """Contains all households for the given area, and information about them."""

    venue_class = Household

    def __init__(self, households: List[venue_class]):
        super().__init__(members=households)

InteractiveHousehold

Bases: InteractiveGroup

Source code in june/groups/household.py
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
class InteractiveHousehold(InteractiveGroup):
    """ """
    def has_isolating_residents(self, current_time):
        """Check if any household residents are currently in self-isolation.

        Args:
            current_time (float): Current simulation time in days from start

        Returns:
            bool: True if any resident is currently in isolation, False otherwise

        """
        if current_time is None:
            return False

        for person in self.group.residents:
            if (hasattr(person, 'test_and_trace') and 
                person.test_and_trace is not None and
                person.test_and_trace.isolation_start_time is not None and
                person.test_and_trace.isolation_end_time is not None):

                # Check if we're currently within the isolation period
                if (person.test_and_trace.isolation_start_time <= current_time <= 
                    person.test_and_trace.isolation_end_time):
                    return True
        return False

    def get_processed_beta(self, betas, beta_reductions, current_time=None):
        """Enhanced version that applies isolation precautions if residents are isolating.

        In the case of households, we need to apply the beta reduction of household visits
        if the household has a visit, otherwise we apply the beta reduction for a normal
        household. Additionally, if any residents are in isolation, we apply extra
        precautionary reductions to model social distancing within the household.

        Args:
            betas (dict): Base transmission intensities for different venue types
            beta_reductions (dict): Policy-based beta reductions
            current_time (float, optional, optional): Current simulation time in days from start (Default value = None)

        Returns:
            float: Final processed beta value for this household

        """
        # Determine base beta and spec based on household state
        if self.group.receiving_care:
            # important than this goes first than being visited
            beta = betas["care_visits"]
            spec = "care_visits"
        elif self.group.being_visited:
            beta = betas["household_visits"]
            spec = "household_visits"
        else:
            beta = betas["household"]
            spec = "household"

        # Get standard policy reduction
        beta_reduction = beta_reductions.get(spec, 1.0)

        # Apply isolation precautions if anyone is isolating
        if current_time is not None and self.has_isolating_residents(current_time):
            # Additional reductions for isolation precautions
            isolation_precautions = {
                "household": 0.7,        # 30% reduction in household transmission
                "household_visits": 0.1, # 90% reduction in visits (discouraged)
                "care_visits": 0.6       # 40% reduction in care visits (extra PPE/precautions)
            }
            isolation_reduction = isolation_precautions.get(spec, 1.0)
            beta_reduction *= isolation_reduction

        # Apply regional compliance and return final beta
        regional_compliance = self.super_area.region.regional_compliance
        final_beta = beta * (1 + regional_compliance * (beta_reduction - 1))

        return final_beta

get_processed_beta(betas, beta_reductions, current_time=None)

Enhanced version that applies isolation precautions if residents are isolating.

In the case of households, we need to apply the beta reduction of household visits if the household has a visit, otherwise we apply the beta reduction for a normal household. Additionally, if any residents are in isolation, we apply extra precautionary reductions to model social distancing within the household.

Parameters:

Name Type Description Default
betas dict

Base transmission intensities for different venue types

required
beta_reductions dict

Policy-based beta reductions

required
current_time (float, optional)

Current simulation time in days from start (Default value = None)

None

Returns:

Name Type Description
float

Final processed beta value for this household

Source code in june/groups/household.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def get_processed_beta(self, betas, beta_reductions, current_time=None):
    """Enhanced version that applies isolation precautions if residents are isolating.

    In the case of households, we need to apply the beta reduction of household visits
    if the household has a visit, otherwise we apply the beta reduction for a normal
    household. Additionally, if any residents are in isolation, we apply extra
    precautionary reductions to model social distancing within the household.

    Args:
        betas (dict): Base transmission intensities for different venue types
        beta_reductions (dict): Policy-based beta reductions
        current_time (float, optional, optional): Current simulation time in days from start (Default value = None)

    Returns:
        float: Final processed beta value for this household

    """
    # Determine base beta and spec based on household state
    if self.group.receiving_care:
        # important than this goes first than being visited
        beta = betas["care_visits"]
        spec = "care_visits"
    elif self.group.being_visited:
        beta = betas["household_visits"]
        spec = "household_visits"
    else:
        beta = betas["household"]
        spec = "household"

    # Get standard policy reduction
    beta_reduction = beta_reductions.get(spec, 1.0)

    # Apply isolation precautions if anyone is isolating
    if current_time is not None and self.has_isolating_residents(current_time):
        # Additional reductions for isolation precautions
        isolation_precautions = {
            "household": 0.7,        # 30% reduction in household transmission
            "household_visits": 0.1, # 90% reduction in visits (discouraged)
            "care_visits": 0.6       # 40% reduction in care visits (extra PPE/precautions)
        }
        isolation_reduction = isolation_precautions.get(spec, 1.0)
        beta_reduction *= isolation_reduction

    # Apply regional compliance and return final beta
    regional_compliance = self.super_area.region.regional_compliance
    final_beta = beta * (1 + regional_compliance * (beta_reduction - 1))

    return final_beta

has_isolating_residents(current_time)

Check if any household residents are currently in self-isolation.

Parameters:

Name Type Description Default
current_time float

Current simulation time in days from start

required

Returns:

Name Type Description
bool

True if any resident is currently in isolation, False otherwise

Source code in june/groups/household.py
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
def has_isolating_residents(self, current_time):
    """Check if any household residents are currently in self-isolation.

    Args:
        current_time (float): Current simulation time in days from start

    Returns:
        bool: True if any resident is currently in isolation, False otherwise

    """
    if current_time is None:
        return False

    for person in self.group.residents:
        if (hasattr(person, 'test_and_trace') and 
            person.test_and_trace is not None and
            person.test_and_trace.isolation_start_time is not None and
            person.test_and_trace.isolation_end_time is not None):

            # Check if we're currently within the isolation period
            if (person.test_and_trace.isolation_start_time <= current_time <= 
                person.test_and_trace.isolation_end_time):
                return True
    return False