from tracemalloc import start
import emod_api.interventions.utils as utils
from emod_api.utils import Distributions
from emod_api import schema_to_class as s2c
from enum import Enum
import copy
from typing import List
schema_path = None
old_adhoc_trigger_style = True
cached_be = None
cached_mid = None
cached_sec = None
cached_ce = None
_MAX_AGE = 365 * 125
###
### Generic
###
[docs]class TargetGender(Enum):
Male = "Male"
Female = "Female"
All = "All"
[docs]def BroadcastEvent(
camp,
Event_Trigger: str = 'Births'
):
"""
Wrapper function to create and return a BroadcastEvent intervention.
Args:
camp: emod_api.campaign object with schema_path set.
Event_Trigger: A valid trigger/event/signal.
Returns:
ReadOnlyDict: Schema-based smart dictionary representing a new
BroadastEvent intervention ready to be added to a campaign.
"""
if Event_Trigger is None or Event_Trigger == "":
raise ValueError("BroadcastEvent called with an empty Event_Trigger. Please specify a string.")
global cached_be
if cached_be is None:
global schema_path
schema_path = (camp.schema_path if camp is not None else schema_path)
cached_be = s2c.get_class_with_defaults("BroadcastEvent", schema_path)
intervention = copy.deepcopy(cached_be)
try:
intervention.Broadcast_Event = camp.get_send_trigger(Event_Trigger, old=old_adhoc_trigger_style)
except ValueError as ex:
raise ValueError(str(ex) + "\n\nMaybe you need to set 'old_adhoc_trigger_style' to false in this module?\n")
return intervention
[docs]def broadcast_node_event(campaign,
broadcast_event: str,
cost_to_consumer: float = 0.0,
disqualifying_properties: List[str] = None,
dont_allow_duplicates: bool = False,
intervention_name: str = "BroadcastNodeEvent",
new_property_value: str = ""):
"""
Wrapper function to create and return a BroadcastNodeEvent intervention.
Args:
campaign: emod_api.campaign object with schema_path set
broadcast_event: String that will be broadcast as Node-level event
cost_to_consumer: The unit cost of the intervention campaign that will be assigned to the specified nodes.
disqualifying_properties: A list of NodeProperty key:value pairs that cause an intervention to be aborted.
Generally used to control the flow of health care access. For example, to prevent the same individual
from accessing health care via two different routes at the same time.
dont_allow_duplicates: If a node's container has the same intervention, set to True to prevent them from
receiving another copy of the intervention. Supported by all intervention classes.
intervention_name: The optional name used to refer to this intervention as a means to differentiate it
from others that use the same class.
new_property_value: An optional NodeProperty key:value pair that will be assigned when the intervention is
distributed. Generally used to indicate the broad category of health care cascade to which an intervention
belongs to prevent individuals from accessing care through multiple pathways.
Returns:
ReadOnlyDict: BroadcastNodeEvent intervention ready to be added to a campaign.
"""
if not broadcast_event:
raise ValueError("BroadcastNodeEvent called with an empty broadcast_event. Please specify a "
"non-empty string.\n")
global schema_path
schema_path = campaign.schema_path if campaign is not None else schema_path
if not disqualifying_properties:
disqualifying_properties = []
intervention = s2c.get_class_with_defaults("BroadcastNodeEvent", schema_path)
intervention.Broadcast_Event = broadcast_event
campaign.custom_node_events.append(broadcast_event)
intervention.Cost_To_Consumer = cost_to_consumer
intervention.Disqualifying_Properties = disqualifying_properties
intervention.Dont_Allow_Duplicates = 1 if dont_allow_duplicates else 0
intervention.Intervention_Name = intervention_name
intervention.New_Property_Value = new_property_value
return intervention
[docs]def BroadcastEventToOtherNodes(
camp,
Event_Trigger,
Node_Selection_Type="DISTANCE_ONLY",
Max_Distance_To_Other_Nodes_Km=-1,
Include_My_Node=1
):
"""
Wrapper function to create and return a BroadcastEventToOtherNodes intervention.
Args:
camp: emod_api.campaign object with schema_path set.
Event_Trigger: A valid trigger/event/signal.
Node_Selection_Type: TBD.
Max_Distance_To_Other_Nodes_Km: TBD.
Include_My_Node: TBD.
Returns:
ReadOnlyDict: Schema-based smart dictionary representing a new
BroadastEvent intervention ready to be added to a campaign.
"""
if Event_Trigger is None or Event_Trigger == "":
raise ValueError("BroadcastEventToOtherNodes called with an empty Event_Trigger. "
"Please specify a non-empty string.")
global schema_path
schema_path = camp.schema_path if camp is not None else schema_path
intervention = s2c.get_class_with_defaults("BroadcastEventToOtherNodes", schema_path)
intervention.Event_Trigger = camp.get_send_trigger(Event_Trigger, old=old_adhoc_trigger_style)
intervention.Node_Selection_Type = Node_Selection_Type
if Max_Distance_To_Other_Nodes_Km != -1:
intervention.Max_Distance_To_Other_Nodes_Km = Max_Distance_To_Other_Nodes_Km
intervention.Include_My_Node = Include_My_Node
return intervention
[docs]def add_broadcast_coordinator_event(
campaign,
broadcast_event: str,
start_day: int = 0,
cost_to_consumer: float = None,
coordinator_name: str = "BroadcastCoordinatorEvent"):
"""
Creates and adds BroadcastCoordinatorEvent intervention to the campaign
Args:
campaign: emod_api.campaign object with schema_path set.
broadcast_event: String that will be broadcast as Coordinator-level event.
start_day: The day in the simulation on which to send out this event.
cost_to_consumer: The unit cost of broadcasting the event.
coordinator_name: The unique identifying coordinator name, which is useful with the output report,
ReportCoordinatorEventRecorder.csv
Returns:
Nothing
"""
if not broadcast_event:
raise ValueError("BroadcastCoordinatorEvent called with an empty broadcast_event. "
"Please specify a non-empty string.\n")
global schema_path
schema_path = campaign.schema_path if campaign is not None else schema_path
b_c_e = s2c.get_class_with_defaults("BroadcastCoordinatorEvent", schema_path)
b_c_e.Broadcast_Event = broadcast_event
campaign.custom_coordinator_events.append(broadcast_event)
b_c_e.Cost_To_Consumer = cost_to_consumer
b_c_e.Coordinator_Name = coordinator_name
event = s2c.get_class_with_defaults("CampaignEvent", schema_path)
event.Start_Day = start_day
event.Event_Coordinator_Config = b_c_e
campaign.add(event)
[docs]def MultiInterventionDistributor(
camp,
Intervention_List
):
"""
Wrapper function to create and return a MultiInterventionDistributor intervention.
Args:
camp: emod_api.campaign object with schema_path set.
Intervention_List: List of 1 or more valid intervention dictionaries to be
distributed together.
Returns:
ReadOnlyDict: Schema-based smart dictionary representing a new
MultiInterventionDistributor intervention ready to be added to a campaign.
"""
global schema_path
schema_path = camp.schema_path if camp is not None else schema_path
if Intervention_List is None or type(Intervention_List) is not list or len(Intervention_List) == 0:
raise ValueError("Intervention_List is empty or None or not a list.")
global cached_mid
if cached_mid is None:
cached_mid = s2c.get_class_with_defaults("MultiInterventionDistributor", schema_path)
intervention = copy.deepcopy(cached_mid)
intervention.Intervention_List = copy.deepcopy(Intervention_List)
if "Intervention_Name" in Intervention_List[0]:
intervention.Intervention_Name = Intervention_List[0].Intervention_Name
else:
intervention.Intervention_Name = "MultiInterventionDistributor"
return intervention
[docs]def DelayedIntervention(
camp,
Configs: List[dict],
Delay_Dict: dict):
"""
Wrapper function to create and return a DelayedIntervention containing a list of individual-level interventions.
Args:
camp: emod_api.campaign object with schema_path set.
Configs: A list of individual-level interventions
Delay_Dict: Dictionary with delay parameters, please use emod_api.utils.Distributions
to generate the delay parameters.
Returns:
ReadOnlyDict: Schema-based smart dictionary representing a new
DelayedIntervention intervention ready to be added to a campaign.
"""
global schema_path
schema_path = camp.schema_path if camp is not None else schema_path
if not Configs or not isinstance(Configs, list):
raise ValueError("Configs needs to be a list of interventions.\n")
intervention = s2c.get_class_with_defaults("DelayedIntervention", schema_path)
intervention.Actual_IndividualIntervention_Configs = copy.deepcopy(Configs)
# Set the delay parameters
Distributions.set_distribution_parameters(distribution_containing_obj=intervention, distribution=Delay_Dict,
prefix="Delay_Period_")
return intervention
[docs]def HSB(
camp,
Event_Or_Config="Event",
Config=None,
Event="NoTrigger",
Tendency=1.0,
Single_Use=True,
Name="HSB"
):
"""
Wrapper function to create and return a HealthSeekingBehaviour intervention.
Args:
camp: emod_api.campaign object with schema_path set.
Event_Or_Config: "Event" or "Config".
Config: Complete, valid intervention configuration to be distributed.
Event: Event/Trigger/Signal to be broadcast, alternative to an intervention.
Tendency: Daily probability of 'seeking care' aka distributing payload intervention.
Single_Use: One-and-done, or continuous?
Name: Intervention Name. Useful if you want to provide uniqueness and not worry about
duplicate intervention management.
Returns:
ReadOnlyDict: Schema-based smart dictionary representing a new
HSB intervention ready to be added to a campaign.
"""
global schema_path
schema_path = camp.schema_path if camp is not None else schema_path
intervention = s2c.get_class_with_defaults("SimpleHealthSeekingBehavior", schema_path)
intervention.Event_Or_Config = Event_Or_Config
if intervention.Event_Or_Config == "Event":
intervention.Actual_IndividualIntervention_Event = camp.get_send_trigger(Event, old=old_adhoc_trigger_style)
else:
if Config is None:
raise ValueError("You specified 'Config' but no actual Config.")
intervention.Actual_IndividualIntervention_Config = Config
intervention.Tendency = Tendency
intervention.Intervention_Name = Name
intervention.Single_Use = Single_Use
return intervention
[docs]def NLHTI(
camp,
Triggers: List[str],
Interventions: List[dict],
Property_Restrictions=None,
Demographic_Coverage=1.0,
Target_Age_Min: float = 0,
Target_Age_Max: float = _MAX_AGE,
Target_Gender="All",
Target_Residents_Only=False,
Duration: float = -1,
Blackout_Event_Trigger=None,
Blackout_Period=None,
Blackout_On_First_Occurrence=None,
Disqualifying_Properties=None,
):
"""
Wrapper function to create and return a NodeLevelHealthTriggeredIntervention intervention.
Args:
camp: emod_api.campaign object with schema_path set.
Triggers: List of Triggers/Events/Signals
Interventions: List of interventions to distrbute when signal is heard.
Property_Restrictions: Individual Properties that an agent must have to qualify for intervention.
Demographic_Coverage: Percentage of individuals to receive intervention.
Target_Age_Min: Minimum age (in years).
Target_Age_Max: Maximum age (in years).
Target_Gender: All, Male, or Female.
Target_Residents_Only: Not used.
Duration: How long this listen-and-distribute should last.
Blackout_Event_Trigger: Not used.
Blackout_Period: Not used.
Blackout_On_First_Occurrence: Not used.
Disqualifying_Properties: Not used.
Returns:
ReadOnlyDict: Schema-based smart dictionary representing a new
NLHTI intervention ready to be added to a campaign.
"""
global schema_path
schema_path = camp.schema_path if camp is not None else schema_path
intervention = s2c.get_class_with_defaults("NodeLevelHealthTriggeredIV", schema_path)
intervention.Trigger_Condition_List = [camp.get_recv_trigger(trigger, old=old_adhoc_trigger_style) for trigger in
Triggers]
if Target_Age_Min > 0 or Target_Age_Max < _MAX_AGE:
intervention.Target_Age_Min = Target_Age_Min
intervention.Target_Age_Max = Target_Age_Max
if Target_Gender != "All":
intervention.Target_Gender = Target_Gender
intervention.Target_Demographic = "ExplicitAgeRangesAndGender"
intervention.Target_Residents_Only = Target_Residents_Only
intervention.Demographic_Coverage = Demographic_Coverage
intervention.Duration = Duration
intervention.Blackout_Event_Trigger = Blackout_Event_Trigger
intervention.Blackout_Period = Blackout_Period
intervention.Blackout_On_First_Occurrence = Blackout_On_First_Occurrence
prs = utils._convert_prs(Property_Restrictions)
if len(prs) > 0 and type(prs[0]) is dict:
intervention.Property_Restrictions_Within_Node = prs
intervention.pop("Property_Restrictions")
else:
intervention.Property_Restrictions = prs
if "Property_Restrictions_Within_Node" in intervention:
intervention.pop("Property_Restrictions_Within_Node")
if len(Interventions) == 1:
intervention.Actual_IndividualIntervention_Config = Interventions[0]
else:
intervention.Actual_IndividualIntervention_Config = MultiInterventionDistributor(camp, Interventions)
return intervention
[docs]def PropertyValueChanger(
camp,
Target_Property_Key,
Target_Property_Value,
Daily_Probability=1.0,
Maximum_Duration=1,
Revert=-1,
Intervention_Name="",
Event_Trigger_Distributed="",
Event_Trigger_Expired=""
):
"""
Wrapper function to create and return a PropertyValueChanger intervention.
Args:
camp: emod_api.campaign object with schema_path set.
Target_Property_Key. The key part of the new key-value pair of the IP.
Target_Property_Key. The key part of the new key-value pair of the IP.
Target_Property_Value. The value part of the new key-value pair of the IP.
New_Property_Value.. Optional IP key:value part to be set, common to all interventions.
Daily_Probability. The daily probability that an individual will move to the Target_Property_Value.
Maximum_Duration. The maximum amount of time individuals have to move to a new group. This timing works
in conjunction with Daily_Probability.
Revert. The number of days before an individual moves back to their original group.
Intervention_Name. Optional Intervention_Name. Useful if managing a replacement policy.
Event_Trigger_Distributed. Optional broadcast trigger to be published when PVC is distributed.
Event_Trigger_Expired. Optional broadcast trigger to be published when PVC is expired.
Returns:
ReadOnlyDict: Schema-based smart dictionary representing a new
PropertyValueChanger intervention ready to be added to a campaign.
"""
global schema_path
schema_path = camp.schema_path if camp is not None else schema_path
intervention = s2c.get_class_with_defaults("PropertyValueChanger", schema_path)
intervention.Target_Property_Key = Target_Property_Key
intervention.Target_Property_Value = Target_Property_Value
intervention.Daily_Probability = Daily_Probability
intervention.Maximum_Duration = Maximum_Duration
if Revert != -1:
intervention.Revert = Revert
if len(Intervention_Name) > 0:
intervention.Intervention_Name = Intervention_Name
if len(Event_Trigger_Distributed) > 0:
intervention.Event_Trigger_Distributed = Event_Trigger_Distributed
if len(Event_Trigger_Expired) > 0:
intervention.Event_Trigger_Expired = Event_Trigger_Expired
return intervention
[docs]def ScheduledCampaignEvent(
camp,
Start_Day: int,
Node_Ids=None,
Nodeset_Config=None,
Number_Repetitions: int = 1,
Timesteps_Between_Repetitions: int = -1,
Event_Name: str = "Scheduled_Campaign_Event", # not set, doesn't exist in schema
Property_Restrictions=None,
Demographic_Coverage: float = 1.0,
Target_Age_Min=0,
Target_Age_Max=_MAX_AGE,
Target_Gender: str = "All",
Target_Residents_Only: bool = False,
Intervention_List=None):
"""
Wrapper function to create and return a ScheduledCampaignEvent intervention.
The alternative to a ScheduledCampaignEvent is a TriggeredCampaignEvent.
Args:
camp: emod_api.campaign object with schema_path set.
Start_Day: When to start.
Event_Name: Name for overall campaign event, of no functional meaning. Not in schema and not yet used.
Node_Ids: Nodes to target with this intervenion
Nodeset_Config: Nodes to target with this intervenion, return from utils.do_nodes().
.. deprecated:: 2.x
Use parameter Node_Ids instead
Property_Restrictions: Individual Properties a person must have to receive the intervention(s).
Number_Repetitions: N/A
Timesteps_Between_Repetitions: N/A
Demographic_Coverage: Percentage of individuals to receive intervention.
Target_Age_Min: Minimum age (in years).
Target_Age_Max: Maximum age (in years).
Target_Gender: All, Male, or Female.
Intervention_List: List of 1 or more valid intervention dictionaries to be
distributed together.
Returns:
ReadOnlyDict: Schema-based smart dictionary representing a new
ScheduledCampaignEvent intervention ready to be added to a campaign.
"""
global schema_path
schema_path = camp.schema_path if camp is not None else schema_path
# Not checking Intervention_list because MultiInterventionDistributor checks
event = s2c.get_class_with_defaults("CampaignEvent", schema_path)
global cached_sec
if cached_sec is None:
cached_sec = s2c.get_class_with_defaults("StandardEventCoordinator", schema_path)
coordinator = copy.deepcopy(cached_sec)
coordinator.Demographic_Coverage = Demographic_Coverage
coordinator.Number_Repetitions = Number_Repetitions
coordinator.Timesteps_Between_Repetitions = Timesteps_Between_Repetitions
# Second, hook them up
event.Event_Coordinator_Config = coordinator
event.Start_Day = float(Start_Day)
if Nodeset_Config is not None and Node_Ids is not None:
raise AssertionError("Node_Ids and Nodeset_Config are set. Please set only one.")
if Nodeset_Config is not None:
event.Nodeset_Config = Nodeset_Config
else:
# The real default is an empty list
if Node_Ids is None:
Node_Ids = []
node_ids = Node_Ids
event.Nodeset_Config = utils.do_nodes(camp.schema_path, node_ids)
if len(Intervention_List) > 1:
coordinator.Intervention_Config = MultiInterventionDistributor(camp, Intervention_List)
else:
coordinator.Intervention_Config = Intervention_List[0]
prs = utils._convert_prs(Property_Restrictions)
if len(prs) > 0 and type(prs[0]) is dict:
coordinator.Property_Restrictions_Within_Node = prs
coordinator.pop("Property_Restrictions")
else:
coordinator.Property_Restrictions = prs
coordinator.pop("Property_Restrictions_Within_Node")
coordinator.Demographic_Coverage = Demographic_Coverage
if Target_Age_Min > 0 or Target_Age_Max < _MAX_AGE:
coordinator.Target_Age_Min = Target_Age_Min
if Target_Age_Max is not None and Target_Age_Max < _MAX_AGE: # overwrite default from schema
coordinator.Target_Age_Max = Target_Age_Max
if Target_Gender != "All":
coordinator.Target_Gender = Target_Gender
coordinator.Target_Demographic = "ExplicitAgeRangesAndGender"
coordinator.Target_Residents_Only = Target_Residents_Only
return event
[docs]def TriggeredCampaignEvent(
camp,
Start_Day: int,
Event_Name: str,
Triggers: List[str],
Intervention_List: List[dict],
Node_Ids: List[int] = None,
Nodeset_Config: dict = None,
Node_Property_Restrictions: List[dict] = None,
Property_Restrictions: List[dict] = None,
Number_Repetitions: int = 1,
Timesteps_Between_Repetitions: int = -1,
Demographic_Coverage: float = 1.0,
Target_Age_Min: float = 0,
Target_Age_Max: float = _MAX_AGE,
Target_Gender: str = "All",
Target_Residents_Only: bool = False,
Duration: float = -1,
Blackout_Event_Trigger: str = None,
Blackout_Period: float = 0,
Blackout_On_First_Occurrence=0,
Disqualifying_Properties: List[str] = None,
Delay: dict = None
):
"""
Wrapper function to create and return a TriggeredCampaignEvent intervention.
The alternative to a TriggeredCampaignEvent is a ScheduledCampaignEvent.
Args:
camp: emod_api.campaign object with schema_path set.
Start_Day: When to start.
Event_Name: Name for overall campaign event, of no functional meaning. Not in schema and not yet used.
Node_Ids: Nodes to target with this intervenion
Nodeset_Config: Nodes to target with this intervenion, return from utils.do_nodes().
.. deprecated:: 2.x
Use parameter Node_Ids instead
Triggers: List of triggers/events/signals to listen to in order to trigger distribution.
Intervention_List: List of 1 or more valid individual-level intervention dictionaries to be
distributed together.
Node_Property_Restrictions: N/A.
Property_Restrictions: Individual Properties a person must have to receive the intervention(s).
Demographic_Coverage: Percentage of individuals to receive intervention.
Target_Age_Min: Minimum age (in years).
Target_Age_Max: Maximum age (in years).
Target_Gender: All, Male, or Female.
Target_Residents_Only: TBD.
Duration: How long this listen-and-distribute should last.
Blackout_Event_Trigger: Not used.
Blackout_Period: Not used.
Blackout_On_First_Occurrence: Not used.
Disqualifying_Properties: Not used.
Delay: Optional delay between trigger and actual distribution, a distribution dictionary generated by
emod_api.utils.Distributions class.
Returns:
ReadOnlyDict: Schema-based smart dictionary representing a new
TriggeredCampaignEvent intervention ready to be added to a campaign.
"""
global schema_path
schema_path = camp.schema_path if camp is not None else schema_path
if Node_Property_Restrictions is None:
Node_Property_Restrictions = []
if Property_Restrictions is None:
Property_Restrictions = []
global cached_ce
if cached_ce is None:
cached_ce = s2c.get_class_with_defaults("CampaignEvent", schema_path)
event = copy.deepcopy(cached_ce)
event.Start_Day = float(Start_Day)
if Nodeset_Config is not None and Node_Ids is not None:
raise AssertionError("Node_Ids and Nodeset_Config are set. Please use only one.")
if Nodeset_Config is not None:
event.Nodeset_Config = Nodeset_Config
else:
# The real default is an empty list.
if Node_Ids is None:
Node_Ids = []
node_ids = Node_Ids
event.Nodeset_Config = utils.do_nodes(camp.schema_path, node_ids)
global cached_sec
if cached_sec is None:
cached_sec = s2c.get_class_with_defaults("StandardEventCoordinator", schema_path)
coordinator = copy.deepcopy(cached_sec)
coordinator.Number_Repetitions = Number_Repetitions
coordinator.Timesteps_Between_Repetitions = Timesteps_Between_Repetitions
if Delay:
if isinstance(Delay, dict):
Intervention_List = [DelayedIntervention(camp, Intervention_List, Delay_Dict=Delay)]
elif isinstance(Delay, int) or isinstance(Delay, float):
Intervention_List = [DelayedIntervention(camp, Intervention_List, Delay_Dict=Distributions.constant(Delay))]
else:
raise AssertionError(f"Delay must be a dictionary or a number. It is {type(Delay)}.\n")
intervention = NLHTI(
camp,
Triggers,
Intervention_List,
Duration=Duration,
Property_Restrictions=Property_Restrictions,
Demographic_Coverage=Demographic_Coverage,
Target_Age_Min=Target_Age_Min,
Target_Age_Max=Target_Age_Max,
Target_Gender=Target_Gender,
Target_Residents_Only=Target_Residents_Only,
Blackout_Event_Trigger=Blackout_Event_Trigger,
Blackout_Period=Blackout_Period,
Blackout_On_First_Occurrence=Blackout_On_First_Occurrence
)
event.Event_Coordinator_Config = coordinator
coordinator.Intervention_Config = intervention
return event
[docs]def add_triggered_coordinator_event(campaign,
start_day: int = 0,
listening_duration: float = -1,
start_trigger_condition_list: list = None,
stop_trigger_condition_list: list = None,
demographic_coverage: float = None,
target_num_individuals: int = None,
node_ids: list = None,
repetitions: int = None,
timesteps_between_repetitions: int = None,
ind_property_restrictions: list = None,
node_property_restrictions: list = None,
target_age_min: float = None,
target_age_max: float = None,
target_gender: TargetGender = TargetGender.All,
target_residents_only: bool = False,
completion_event: str = "",
coordinator_name: str = "TriggeredEventCoordinator",
node_interventions: any = None,
individual_interventions: any = None):
"""
Wrapper function to create and adds a TriggeredCoordinatorEvent. The intervention is triggered by
COORDINATOR-level event, and sends out a COORDINATOR-level event when done.
Args:
campaign: campaign object to which the intervention will be added, and schema_path container
start_day: The day the intervention is given out.
listening_duration: The number of time steps that the coordinator will monitor for triggers before expiring.
Default is -1, which is indefinitely.
start_trigger_condition_list: A list of the COORDINATOR-level events that will trigger intervention distribution.
stop_trigger_condition_list: A list of the COORDINATOR-level events that will stop intervention from happening
if the intervention is repeating.
demographic_coverage: This value is the probability that each individual in the target population will
receive the intervention. It does not guarantee that the exact fraction of the target population set by
Demographic_Coverage receives the intervention.
target_num_individuals: The exact number of people to select out of the targeted group. If this value is set,
demographic_coverage parameter is ignored
node_ids: List of nodes to which to distribute the intervention. [] or None, indicates all nodes
will get the intervention
repetitions: The number of times an intervention is given, used with timesteps_between_repetitions. -1 means
the intervention repeats forever. Sets **Number_Repetitions**
timesteps_between_repetitions: The interval, in timesteps, between repetitions. Ignored if repetitions = 1.
Sets **Timesteps_Between_Repetitions**
ind_property_restrictions: A list of dictionaries of IndividualProperties, which are needed for the individual
to receive the intervention. Sets the **Property_Restrictions_Within_Node**
node_property_restrictions: "A list of the NodeProperty key:value pairs, as defined in the demographics file,
that nodes must have to be targeted by the intervention.
target_age_min: The lower end of ages targeted for an intervention, in years. Sets **Target_Age_Min**
target_age_max: The upper end of ages targeted for an intervention, in years. Sets **Target_Age_Max**
target_gender: The gender targeted for an intervention: All, Male, or Female.
target_residents_only: When set to True, the intervention is only distributed to individuals that began
the simulation in the node (i.e. those that claim the node as their residence)
completion_event: A COORDINATOR-level event to send out when the coordinator has expired.
coordinator_name: The unique identifying coordinator name used to identify the different coordinators in
reports.
node_interventions: A node-level intervention or a list of node-level interventions that will be distributed
when one of the triggers from start_trigger_condition_list is heard.
individual_interventions: A individual-level intervention or a list of individual-level interventions that
will be distributed when one of the triggers from start_trigger_condition_list is heard.
Returns:
None
"""
if not start_trigger_condition_list:
raise ValueError("Please define triggers for start_trigger_condition_list. Cannot be empty or None.\n")
if bool(node_interventions) == bool(individual_interventions):
raise ValueError("Please define either node_interventions or individual_interventions. "
"They are mutually exclusive.\n")
if node_interventions and (demographic_coverage or target_num_individuals or target_gender != TargetGender.All
or target_age_min or
target_age_max or target_residents_only or ind_property_restrictions):
raise ValueError("demographic_coverage, target_num_individuals, target_gender, target_age_min, target_age_max, "
"target_residents_only, and ind_property_restrictions are not used when passing in "
"node_interventions\n")
if individual_interventions and (bool(demographic_coverage) == bool(target_num_individuals)):
raise ValueError("Please define either demographic_coverage or target_num_individuals, but not both, "
"when passing in individual_interventions\n")
schema_path = campaign.schema_path
event = s2c.get_class_with_defaults("CampaignEvent", schema_path)
event.Start_Day = start_day
event.Nodeset_Config = utils.do_nodes(schema_path, node_ids)
if node_interventions:
if isinstance(node_interventions, list) and len(node_interventions) > 1:
multi_intervention_distributor = s2c.get_class_with_defaults("MultiNodeInterventionDistributor",
schema_path)
multi_intervention_distributor.Node_Intervention_List = node_interventions
intervention = multi_intervention_distributor
elif isinstance(node_interventions, list) and len(node_interventions) == 1:
intervention = node_interventions[0] # if only one intervention, just use that
elif isinstance(node_interventions, dict):
intervention = node_interventions
else:
raise ValueError("Invalid type for node_interventions: " + str(type(node_interventions)))
else:
if isinstance(individual_interventions, list) and len(individual_interventions) > 1:
multi_intervention_distributor = s2c.get_class_with_defaults("MultiInterventionDistributor", schema_path)
multi_intervention_distributor.Intervention_List = individual_interventions
intervention = multi_intervention_distributor
elif isinstance(individual_interventions, list) and len(individual_interventions) == 1:
intervention = individual_interventions[0] # if only one intervention, just use that
elif isinstance(individual_interventions, dict):
intervention = individual_interventions
else:
raise ValueError("Invalid type for individual_interventions: " + str(type(individual_interventions)))
# configuring the coordinator
coordinator = s2c.get_class_with_defaults("TriggeredEventCoordinator", schema_path)
coordinator.Intervention_Config = intervention
if target_num_individuals is not None: # so they can set it to 0 for some reason
coordinator.Target_Num_Individuals = target_num_individuals
elif demographic_coverage is not None: # so they can set it to 0 for some reason
coordinator.Demographic_Coverage = demographic_coverage
coordinator.Target_Residents_Only = target_residents_only
if repetitions and timesteps_between_repetitions:
coordinator.Number_Repetitions = repetitions
coordinator.Timesteps_Between_Repetitions = timesteps_between_repetitions
elif bool(repetitions) != bool(timesteps_between_repetitions):
raise ValueError("Please define both repetitions and timesteps_between_repetitions. "
"Cannot define one without the other.\n")
if ind_property_restrictions:
coordinator.Property_Restrictions_Within_Node = ind_property_restrictions
if node_property_restrictions:
coordinator.Node_Property_Restrictions = node_property_restrictions
if target_age_min: # 0 is the default, we don't have to set it explicitly
coordinator.Target_Age_Min = target_age_min
if target_age_max is not None and target_age_max <= coordinator.Target_Age_Min:
raise ValueError(f"target_age_max ({target_age_max}) must be greater than or equal to "
f"target_age_min ({target_age_min}).\n")
else:
coordinator.Target_Age_Max = target_age_max
if target_gender != TargetGender.All:
coordinator.Target_Gender = target_gender.name
coordinator.Target_Demographic = "ExplicitAgeRangesAndGender"
coordinator.Duration = listening_duration
coordinator.Coordinator_Name = coordinator_name
coordinator.Start_Trigger_Condition_List = start_trigger_condition_list
campaign.custom_coordinator_events.extend(start_trigger_condition_list)
if stop_trigger_condition_list:
coordinator.Stop_Trigger_Condition_List = stop_trigger_condition_list
campaign.custom_coordinator_events.extend(stop_trigger_condition_list)
if completion_event:
coordinator.Completion_Event = completion_event
campaign.custom_coordinator_events.append(completion_event)
event.Event_Coordinator_Config = coordinator
campaign.add(event)
[docs]def StandardDiagnostic(
camp,
Base_Sensitivity: float = 1.0,
Base_Specificity: float = 1.0,
Days_To_Diagnosis: float = 0.0,
Event_Trigger_Distributed: str = None,
Event_Trigger_Expired: str = None,
Positive_Diagnosis_Intervention=None,
Positive_Diagnosis_Event: str = "PositiveResult",
Negative_Diagnosis_Intervention=None,
Negative_Diagnosis_Event: str = "NegativeResult",
Treatment_Fraction: float = 1.0
):
"""
Wrapper function to create and return a StandardDiagnostic intervention.
Args:
camp: emod_api.campaign object with schema_path set.
Base_Sensitivity: base sensitivity [0..1]
Base_Specificity: base specificity [0..1]
Days_To_Diagnosis: days to diagnosis
Event_Trigger_Distributed: A trigger that is fired when intervention was distributed
Event_Trigger_Expired: A trigger that is fired when intervention has expired
Positive_Diagnosis_Intervention: Intervention that is distributed in case of a positive diagnosis.
If set, no events may be configured.
Positive_Diagnosis_Event: A trigger that is fired in case of a positive diagnosis
Negative_Diagnosis_Intervention: Intervention that is distributed in case of a Negative diagnosis.
If set, no events may be configured. Not used outside of Malaria-Ongoing yet.
Negative_Diagnosis_Event: A trigger that is fired in case of a Negative diagnosis. Not used outside of
Malaria-Ongoing yet.
Treatment_Fraction: treatment fraction [0..1]
Returns:
ReadOnlyDict: Schema-based smart dictionary representing a new
MultiInterventionDistributor intervention ready to be added to a campaign.
"""
global schema_path
schema_path = camp.schema_path if camp is not None else schema_path
if Positive_Diagnosis_Intervention is not None and (
Event_Trigger_Distributed is not None or Event_Trigger_Expired is not None):
raise Exception(
"Events and intervention are configured. Configuration of events and intervention are mutually exclusive.")
# First, get the objects
# Only in Malaria-Ongoing the class is called StandardDiagnostic
try:
intervention = s2c.get_class_with_defaults("StandardDiagnostic", schema_path)
if Positive_Diagnosis_Intervention is None:
intervention.Positive_Diagnosis_Config = BroadcastEvent(camp, Positive_Diagnosis_Event)
else:
intervention.Positive_Diagnosis_Config = Positive_Diagnosis_Intervention
if Negative_Diagnosis_Intervention is None:
intervention.Negative_Diagnosis_Config = BroadcastEvent(camp, Negative_Diagnosis_Event)
else:
intervention.Negative_Diagnosis_Config = Negative_Diagnosis_Intervention
except ValueError:
# Non Malaria, use SimpleDiagnostic
intervention = s2c.get_class_with_defaults("SimpleDiagnostic", schema_path)
if Positive_Diagnosis_Intervention is None:
intervention.Positive_Diagnosis_Config = BroadcastEvent(camp, Positive_Diagnosis_Event)
if Event_Trigger_Distributed:
intervention.Event_Trigger_Distributed = Event_Trigger_Distributed
if Event_Trigger_Expired:
intervention.Event_Trigger_Expired = Event_Trigger_Expired
else:
intervention.Positive_Diagnosis_Config = Positive_Diagnosis_Intervention
intervention.Base_Sensitivity = Base_Sensitivity
intervention.Base_Specificity = Base_Specificity
intervention.Days_To_Diagnosis = Days_To_Diagnosis
intervention.Treatment_Fraction = Treatment_Fraction
return intervention
[docs]def triggered_campaign_delay_event(camp, start_day, trigger, delay, intervention, ip_targeting=None, coverage=1.0):
"""
Create and return a campaign event that responds to a trigger after a delay with an intervention.
Args:
camp: emod_api.campaign object with schema_path set.
start_day: Day the event will start.
delay: Dictionary with delay parameters, please use emod_api.utils.Distributions
to generate the delay parameters.
trigger: E.g., "NewInfection".
intervention: List of 1 or more valid intervention dictionaries to be distributed together.
ip_targeting: Optional Individual Properties required for someone to receive the intervention(s).
coverage: Fraction of the population that will receive the intervention.
Returns:
Campaign event.
"""
if not ip_targeting:
ip_targeting = []
delay_iv = DelayedIntervention(camp, Configs=[intervention], Delay_Dict=delay)
event = TriggeredCampaignEvent(camp, Start_Day=start_day, Event_Name="triggered_delayed_intervention",
Triggers=[camp.get_recv_trigger(trigger, old=True)],
Intervention_List=[delay_iv], Property_Restrictions=ip_targeting,
Demographic_Coverage=coverage)
return event
#
# The following function is intended to replace triggered_campaign_delay_event.
# There's a bit of a structure here:
# change_individual_property() ->
# change_individual_property_scheduled() ->
# ScheduledCampaignEvent()
# OR
# change_individual_property_triggered ->
# triggered_campaign_event_with_optional_delay
# TriggeredCampaignEvent()
[docs]def triggered_campaign_event_with_optional_delay(camp,
start_day,
triggers,
intervention,
delay=None,
duration=-1,
ip_targeting=None,
coverage=1.0,
target_age_min=0,
target_age_max=_MAX_AGE,
target_sex="All",
target_residents_only=False,
blackout=True,
check_at_trigger=False
):
"""
Create and return a campaign event that responds to a trigger after a delay with an intervention.
Args:
camp: emod_api.campaign object with schema_path set.
start_day: When to start.
triggers: List of signals to listen for/trigger on. E.g., "NewInfection".
intervention: List of 1 or more valid intervention dictionaries to be distributed together.
delay: Optional dictionary of 1 or 2 params that are the literal Delay_Distribution parameters,
but without the distribution, which is inferred. E.g., { "Delay_Period_Exponential": 5 }. If omitted,
intervention is immediate.
duration: How long to listen.
ip_targeting: Optional Individual Properties required for someone to receive the intervntion(s).
coverage: Fraction of target population to reach.
target_age_min: Minimum age to target.
target_age_max: Maximum age to target.
target_sex: Optional target just "MALE" or "FEMALE" individuals.
target_residents_only: Set to True to target only the individuals who
started the simulation in this node and are still in the node.
blackout: Set to True if you don't want the triggered intervention
to be distributed to the same person more than once a day.
check_at_trigger: if triggered event is delayed, you have an
option to check individual/node's eligibility at the initial trigger
or when the event is actually distributed after delay.
Returns:
Campaign event.
"""
if delay:
# If delay is just a number, convert to dict.
delay_iv = DelayedIntervention(camp, Configs=[intervention], Delay_Dict=delay)
event = TriggeredCampaignEvent(camp, Start_Day=start_day, Event_Name="Delayed Triggered Intervention",
Triggers=triggers, Intervention_List=[delay_iv],
Property_Restrictions=ip_targeting, Demographic_Coverage=coverage,
Target_Age_Min=target_age_min, Target_Age_Max=target_age_max,
Target_Gender=target_sex, Target_Residents_Only=target_residents_only,
Duration=duration)
else:
event = TriggeredCampaignEvent(camp, Start_Day=start_day, Event_Name="Immediate Triggered Intervention",
Triggers=triggers, Intervention_List=[intervention],
Property_Restrictions=ip_targeting, Demographic_Coverage=coverage,
Target_Age_Min=target_age_min, Target_Age_Max=target_age_max,
Target_Gender=target_sex, Target_Residents_Only=target_residents_only,
Duration=duration)
return event
[docs]def change_individual_property_at_age(camp, new_ip_key, new_ip_value, change_age_in_days, revert_in_days,
ip_targeting_key, ip_targeting_value, coverage=1.0):
"""
Create and return a campaign event that changes a person's Individual Properties once they turns a certain age.
e.g., change_individual_property_at_age(cb, 'ForestGoing', 'LovesForest', coverage=0.6,
change_age_in_days=15*365, revert=20*365)
Args:
camp: emod_api.campaign object with schema_path set.
new_ip_key: The new IP key.
new_ip_value: The new IP value.
change_age_in_days: The age at which the individual transitions (in units of days).
revert_in_days: How many days they remain with the new property.
ip_targeting_key: The IP key a person must have to receive this.
ip_targeting_value: The IP value a person must have to receive this.
coverage: Optional fraction to limit this to a subset of the target population.
Returns:
Campaign event.
"""
iv = PropertyValueChanger(camp, Target_Property_Key=new_ip_key, Target_Property_Value=new_ip_value,
Revert=revert_in_days)
# TBD: migrate this to use triggered_campaign_event_with_optional_delay
return triggered_campaign_delay_event(camp, start_day=1, trigger="Births", coverage=coverage,
delay={"Delay_Period_Constant": change_age_in_days}, intervention=iv,
ip_targeting={ip_targeting_key: ip_targeting_value})
[docs]def change_individual_property_triggered(camp,
triggers: list,
new_ip_key: str,
new_ip_value: str,
start_day: int = 0,
daily_prob: float = 1,
max_duration: int = 9.3228e+35,
revert_in_days: int = -1,
node_ids: list = None, # where
ip_restrictions: list = None,
coverage: float = 1.0,
target_age_min: float = 0,
target_age_max: float = _MAX_AGE,
target_sex: str = "All",
target_residents_only: bool = False,
delay=None,
listening_duration: int = -1,
blackout: bool = True,
check_at_trigger: bool = False
):
"""
Change Individual Properties when a certain trigger is observed.
Args:
camp: The instance containing the campaign builder and accumulator.
triggers: A list of the events that will trigger the intervention.
new_ip_key: The individual property key to assign to the
individual. For example, InterventionStatus.
new_ip_value: The individual property value to assign to the
individual. For example, RecentDrug.
start_day: The day on which to start distributing the intervention
(**Start_Day** parameter).
node_ids: The list of nodes to apply this intervention to. If not provided, defaults to all nodes.
daily_prob: The daily probability that an individual's property value
will be updated (**Daily_Probability** parameter).
max_duration: The maximum amount of time individuals have to move to a new
**daily_prob**; individuals not moved to the new value by the end of
**max_duration** keep the same value.
revert_in_days: The number of days before a node reverts to its original
property value. Default of 0 means the new value is kept forever.
ip_restrictions: The IndividualProperty key:value pairs to target.
coverage: The proportion of the population that will receive the
intervention (**Demographic_Coverage** parameter).
target_age_min: Minimum age to target.
target_age_max: Maximum age to target.
target_sex: Optional target just "MALE" or "FEMALE" individuals.
target_residents_only: Set to True to target only the individuals who
started the simulation in this node and are still in the node.
delay: The number of days the campaign is delayed after being triggered.
listening_duration: The number of time steps that the
triggered campaign will be active for. Default is -1, which is
indefinitely.
blackout (advanced): Set to True if you don't want the triggered intervention
to be distributed to the same person more than once a day.
check_at_trigger (advanced): if triggered event is delayed, you have an
option to check individual/node's eligibility at the initial trigger
or when the event is actually distributed after delay.
Returns:
N/A.
"""
iv = PropertyValueChanger(camp, Target_Property_Key=new_ip_key, Target_Property_Value=new_ip_value,
Daily_Probability=daily_prob, Maximum_Duration=max_duration, Revert=revert_in_days)
tce = triggered_campaign_event_with_optional_delay(camp, start_day=start_day, intervention=iv, triggers=triggers,
delay=delay, ip_targeting=ip_restrictions, coverage=coverage,
target_age_min=target_age_min, target_age_max=target_age_max,
target_sex=target_sex,
target_residents_only=target_residents_only,
duration=listening_duration, blackout=blackout,
check_at_trigger=check_at_trigger)
camp.add(tce)
[docs]def change_individual_property_scheduled(camp,
new_ip_key,
new_ip_value,
start_day: int = 0,
number_repetitions: int = 1,
timesteps_between_reps: int = -1,
node_ids: list = None, # where
daily_prob: float = 1,
max_duration: int = 9.3228e+35,
revert_in_days: int = -1,
ip_restrictions: list = None,
coverage: float = 1.0,
target_age_min: float = 0,
target_age_max: float = _MAX_AGE,
target_sex: str = "All",
target_residents_only: bool = False
):
"""
Change Individual Properties at a given time.
Args:
camp: The instance containing the campaign builder and accumulator.
new_ip_key: The individual property key to assign to the
individual. For example, InterventionStatus.
new_ip_value: The individual property value to assign to the
individual. For example, RecentDrug.
start_day: The day on which to start distributing the intervention
(**Start_Day** parameter).
node_ids: The list of nodes to apply this intervention to. If not provided, defaults to all nodes.
daily_prob: The daily probability that an individual's property value
will be updated (**Daily_Probability** parameter).
max_duration: The maximum amount of time individuals have to move to a new
**daily_prob**; individuals not moved to the new value by the end of
**max_duration** keep the same value.
revert_in_days: The number of days before an individual reverts to its original
property value. Default of -1 means the new value is kept forever.
ip_restrictions: The IndividualProperty key:value pairs to target.
coverage: The proportion of the population that will receive the
intervention (**Demographic_Coverage** parameter).
target_age_min: Minimum age to target.
target_age_max: Maximum age to target.
target_sex: Optional target just "MALE" or "FEMALE" individuals.
target_residents_only: Set to True to target only the individuals who
started the simulation in this node and are still in the node.
Returns:
N/A.
"""
iv = PropertyValueChanger(camp, Target_Property_Key=new_ip_key, Target_Property_Value=new_ip_value,
Daily_Probability=daily_prob, Maximum_Duration=max_duration, Revert=revert_in_days)
sce = ScheduledCampaignEvent(camp, Intervention_List=[iv], Start_Day=start_day, Node_Ids=node_ids,
Number_Repetitions=number_repetitions,
Timesteps_Between_Repetitions=timesteps_between_reps,
Property_Restrictions=ip_restrictions, Demographic_Coverage=coverage,
Target_Age_Min=target_age_min, Target_Age_Max=target_age_max, Target_Gender=target_sex,
Target_Residents_Only=target_residents_only)
camp.add(sce)
[docs]def change_individual_property(camp,
target_property_name: str, # what...
target_property_value: str,
start_day: int = 0, # when
number_repetitions: int = 1,
timesteps_between_reps: int = -1,
node_ids: list = None, # where
daily_prob: float = 1,
max_duration: int = 9.3228e+35,
revert: int = -1,
coverage: float = 1, # who
ip_restrictions: list = None,
target_age_min: float = 0,
target_age_max: float = _MAX_AGE,
target_sex: str = "All",
target_residents_only: bool = False,
trigger_condition_list: list = None, # the why...
triggered_campaign_delay: int = 0, # (well, kind of when again)
listening_duration: int = -1,
blackout_flag: bool = True, # advanced trigger stuff
check_eligibility_at_trigger: bool = False):
"""
Add an intervention that changes the individual property value to another on a
particular day OR after a triggering event using the
**PropertyValueChanger** class. Deprecated. Prefer change_individual_property_scheduled
or change_individual_property_triggered depending on the use case.
Args:
camp: emod_api.campaign object with schema_path set.
target_property_name: The individual property key to assign to the
individual. For example, Risk.
target_property_value: The individual property value to assign to the
individual. For example, High.
start_day: The day on which to start distributing the intervention.
number_repetitions: Optional repeater value. Does not work with triggers.
timesteps_between_reps: Gap between repetitions, optional. Does not work with triggers.
node_ids: The list of nodes to apply this intervention to. Defaults to all.
daily_prob: The daily probability that an individual's property value
will be updated (**Daily_Probability** parameter).
max_duration: The number of days to continue the intervention after
**start_day**.
revert: The number of days before an individual reverts to its original
property value. Default of -1 means the new value is kept forever.
coverage: The proportion of the population that will receive the
intervention (**Demographic_Coverage** parameter).
ip_restrictions: The IndividualProperty key:value pairs to target.
Usually this will be the same key but different from the
target_property_xxx entries.
target_residents_only: Set to True to target only the individuals who
started the simulation in this node and are still in the node.
target_age_min: Optional minimum age, defaults to 0.
target_age_max: Optional maximum age, defaults to inf.
target_sex: Optional target sex, defaults to both.
triggered_campaign_delay: The number of days the campaign is delayed
after being triggered.
trigger_condition_list: A list of the events that will
trigger the intervention. If included, **start_day** is the day
when monitoring for triggers begins.
listening_duration: The number of time steps that the
triggered campaign will be active for. Default is -1, which is
indefinitely.
blackout_flag: Set to True if you don't want the triggered intervention
to be distributed to the same person more than once a day.
check_eligibility_at_trigger: if triggered event is delayed, you have an
option to check individual/node's eligibility at the initial trigger
or when the event is actually distributed after delay.
Returns:
None
"""
if trigger_condition_list:
return change_individual_property_triggered(camp,
start_day=start_day,
coverage=coverage,
max_duration=max_duration,
daily_prob=daily_prob,
triggers=trigger_condition_list,
new_ip_key=target_property_name,
new_ip_value=target_property_value,
revert_in_days=revert,
ip_restrictions=ip_restrictions,
target_age_min=target_age_min,
target_age_max=target_age_max,
target_sex=target_sex,
target_residents_only=target_residents_only,
delay=triggered_campaign_delay,
listening_duration=listening_duration,
blackout=blackout_flag,
check_at_trigger=check_eligibility_at_trigger,
node_ids=node_ids)
else:
return change_individual_property_scheduled(camp,
start_day=start_day,
coverage=coverage,
max_duration=max_duration,
daily_prob=daily_prob,
number_repetitions=number_repetitions,
timesteps_between_reps=timesteps_between_reps,
new_ip_key=target_property_name,
new_ip_value=target_property_value,
revert_in_days=revert,
ip_restrictions=ip_restrictions,
target_residents_only=target_residents_only,
target_age_min=target_age_min,
target_age_max=target_age_max,
target_sex=target_sex,
node_ids=node_ids
)