Source code for emodpy_malaria.interventions.diag_survey

"""
This module contains functionality for diagnostic survey interventions.
"""
import random
import emod_api.interventions.utils as utils
from emod_api import schema_to_class as s2c
from emod_api.interventions.common import *
from emodpy_malaria.interventions.common import _malaria_diagnostic, add_campaign_event


[docs] def add_diagnostic_survey( campaign, coverage: float = 1, repetitions: int = 1, tsteps_btwn_repetitions: int = 365, target: object = 'Everyone', start_day: int = 1, diagnostic_type: str = 'BLOOD_SMEAR_PARASITES', diagnostic_threshold: float = 40, measurement_sensitivity: float = 0.1, event_name: str = "Diagnostic Survey", node_ids: list = None, positive_diagnosis_configs: list = None, negative_diagnosis_configs: list = None, received_test_event: str = 'Received_Test', ind_property_restrictions: list = None, disqualifying_properties: list = None, trigger_condition_list: list = None, listening_duration: int = -1, triggered_campaign_delay: int = 0, check_eligibility_at_trigger: bool = False, expire_recent_drugs: any = None): """ Add an intervention to create either a scheduled or a triggered event to the campaign using the :py:class:`~emodpy_malaria.interventions.common.MalariaDiagnostic` class, an individual-level class, to test individuals. Upon positive or negative diagnosis, the list of events to occur (as defined in **positive_diagnosis_configs** or **negative_diagnosis_configs**) is then executed. These events can trigger other listening interventions. Args: camp: The :py:obj:`emod_api:emod_api.campaign` object for building, modifying, and writing campaign configuration files. coverage: The probability an individual receives the diagnostic. repetitions: Number of repetitions of the survey intervention. tsteps_btwn_repetitions: Timesteps between repetitions. target: A dictionary targeting an age range and gender of individuals for treatment. In the format ``{"agemin": x, "agemax": y, "gender": z}``. Default is Everyone. start_day: The day of the simulation on which the intervention is created. If triggered, runs on trigger, not on **start_day**. diagnostic_type: Type of malaria diagnostic used. Default is **BLOOD_SMEAR_PARASITES**. Available options are: * BLOOD_SMEAR_PARASITES * BLOOD_SMEAR_GAMETOCYTES * PCR_PARASITES * PCR_GAMETOCYTES * PF_HRP2 * TRUE_INFECTION_STATUS * TRUE_PARASITE_DENSITY * FEVER diagnostic_threshold: The diagnostic detection threshold based on **diagnostic_type**: TRUE_INFECTION_STATUS BLOOD_SMEAR_PARASITES In parasites/microliter, use measurement = float( 1.0 / measurementSensitivity * Poisson(measurementSensitivity * true_parasite_density)). BLOOD_SMEAR_GAMETOCYTES In gametocytes/microliter, use measurement = float( 1.0 / measurementSensitivity * Poisson(measurementSensitivity * true_gametocyte_density)). PCR_PARASITES and PCR_GAMETOCYTES Use the true values and an algorithm based on the paper `Improving statistical inference on pathogen densities estimated by quantitative molecular methods : malaria gametocytaemia as a case study <https://doi.org/10.1186/s12859-014-0402-2>`_. PF_HRP2 Add a new method to get the PfHRP2 value and check against the threshold. TRUE_PARASITE_DENSITY Check the true parasite density against the threshold. FEVER Check the person's fever against the threshold. measurement_sensitivity: Setting for **Measurement_Sensitivity** in :py:class:`~emodpy_malaria.interventions.common.MalariaDiagnostic`. event_name: Description of the event. node_ids: The list of nodes to apply this intervention to (**Node_List** parameter). If not provided, set value of NodeSetAll. positive_diagnosis_configs: List of events to happen to an individual who receives a positive result from test. negative_diagnosis_configs: List of events to happen to individual who receives a negative result from test. received_test_event: String for individuals to broadcast upon receiving diagnostic. ind_property_restrictions: List of IndividualProperty key:value pairs that individuals must have to receive the diagnostic intervention. For example, ``[{"IndividualProperty1":"PropertyValue1"}, {"IndividualProperty2":"PropertyValue2"}]``. Default is no restrictions. disqualifying_properties: List of IndividualProperty key:value pairs that cause an intervention to be aborted. For example, ``[{"IndividualProperty1":"PropertyValue1"}, {"IndividualProperty2":"PropertyValue2"}]``. trigger_condition_list: List of events that will trigger a diagnostic survey. listening_duration: Duration after start day to stop listening for events, as specified in **trigger_condition_list**. Default is -1, non-stop listening. triggered_campaign_delay: Delay of running the intervention after receiving a trigger from the **trigger_condition_list**. 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. expire_recent_drugs: Adds ``[{"DrugStatus:None"}]`` to **Property_Restrictions_Within_Node** for positive test config, so only those with that property receive drugs. Returns: None """ if ind_property_restrictions is None: ind_property_restrictions = [] if disqualifying_properties is None: disqualifying_properties = [] received_test_event = BroadcastEvent(campaign, Event_Trigger=received_test_event) tested_positive = BroadcastEvent(campaign, Event_Trigger="TestedPositive") tested_negative = BroadcastEvent(campaign, Event_Trigger="TestedNegative") tested_positive_tether = "TestedPositive_{}".format(random.randint(1, 100000)) tested_negative_tether = "TestedNegative_{}".format(random.randint(1, 100000)) intervention_cfg = _malaria_diagnostic( campaign, measurement_sensitivity=measurement_sensitivity, detection_threshold=diagnostic_threshold, diagnostic_type=diagnostic_type) bcast = BroadcastEvent(campaign, Event_Trigger=tested_positive_tether) mid = MultiInterventionDistributor(campaign, Intervention_List=[tested_positive, bcast]) intervention_cfg.Positive_Diagnosis_Config = mid intervention_cfg.Negative_Diagnosis_Config = MultiInterventionDistributor( campaign, Intervention_List=[tested_negative, BroadcastEvent(campaign, Event_Trigger=tested_negative_tether)]) interventions = [intervention_cfg, received_test_event] gender = "All" age_min = 0 age_max = 9.3228e+35 if target != "Everyone" and isinstance(target, dict): try: age_min = target["agemin"] age_max = target["agemax"] if 'gender' in target: gender = target["gender"] target = "ExplicitAgeRangesAndGender" else: target = "ExplicitAgeRanges" except KeyError: raise KeyError("Unknown target_group parameter. Please pass in 'Everyone' or a dictionary of " "{'agemin' : x, 'agemax' : y, 'gender': 'Female'} to target to individuals between x and " "y years of age, and (optional) gender.\n") if trigger_condition_list: if listening_duration == -1: diagnosis_config_listening_duration = -1 else: diagnosis_config_listening_duration = listening_duration + 1 # if once triggered, you want the diagnostic survey to repeat or if there is a delay (or both) # this creates a series of broadcast events that once triggered send out "Diagnostic_Survey_Now" # at pre-determined intervals if repetitions > 1 or triggered_campaign_delay > 0: # create a trigger for each of the delays. trigger_ind_property_restrictions = [] if check_eligibility_at_trigger: trigger_ind_property_restrictions = ind_property_restrictions ind_property_restrictions = [] broadcast_event = "Diagnostic_Survey_Now_{}".format(random.randint(1, 100000)) for x in range(repetitions): tcde = TriggeredCampaignEvent( campaign, Start_Day=start_day + 1, Event_Name="Diag_Survey_Now", Node_Ids=node_ids, Triggers=trigger_condition_list, Duration=listening_duration, Intervention_List=[BroadcastEvent(campaign, broadcast_event)], Property_Restrictions=trigger_ind_property_restrictions, Delay=triggered_campaign_delay + (x * tsteps_btwn_repetitions)) campaign.add(tcde) trigger_condition_list = [broadcast_event] survey_event = TriggeredCampaignEvent( campaign, Start_Day=start_day + 1, Event_Name=event_name, Node_Ids=node_ids, Triggers=trigger_condition_list, Target_Residents_Only=1, Duration=listening_duration, Demographic_Coverage=coverage, Target_Age_Min=age_min, Target_Age_Max=age_max, Target_Gender=gender, Property_Restrictions=ind_property_restrictions, Disqualifying_Properties=disqualifying_properties, Intervention_List=interventions) campaign.add(survey_event) else: diagnosis_config_listening_duration = listening_duration add_campaign_event( campaign, start_day=start_day + 1, node_ids=node_ids, ind_property_restrictions=ind_property_restrictions, repetitions=repetitions, timesteps_between_repetitions=tsteps_btwn_repetitions, demographic_coverage=coverage, target_age_min=age_min, target_age_max=age_max, target_gender=gender, individual_intervention=interventions) if expire_recent_drugs: if ind_property_restrictions: for property_restriction in ind_property_restrictions: property_restriction["DrugStatus"] = "None" else: ind_property_restrictions = [{"DrugStatus": "None"}] if positive_diagnosis_configs: tested_positive_event = TriggeredCampaignEvent( campaign, Start_Day=start_day, Event_Name=event_name + "Positive Result Action", Node_Ids=node_ids, Duration=diagnosis_config_listening_duration, Property_Restrictions=ind_property_restrictions, Triggers=[tested_positive_tether], Intervention_List=positive_diagnosis_configs ) campaign.add(tested_positive_event) if negative_diagnosis_configs: tested_negative_event = TriggeredCampaignEvent( campaign, Start_Day=start_day, Event_Name=event_name + "Negative Result Action", Node_Ids=node_ids, Duration=diagnosis_config_listening_duration, Triggers=[tested_negative_tether], Intervention_List=negative_diagnosis_configs ) campaign.add(tested_negative_event) return