Skip to content

intervention_systems

add_diagnostic_survey(campaign, drug=None, diagnostic_type=DiagnosticType.BLOOD_SMEAR_PARASITES, detection_threshold=40, measurement_sensitivity=0.1, treatment_delay=None, start_day=1, node_ids=None, event_name='Diagnostic Survey', positive_diagnosis_configs=None, negative_diagnosis_configs=None, received_test_event='Received_Test', target_demographics_config=None, property_restrictions=None, disqualifying_properties=None, repetition_config=None, trigger_condition_list=None, duration=-1, triggered_campaign_delay=None, check_eligibility_at_trigger=False, expire_recent_drugs=False)

Add a scheduled or triggered diagnostic survey to the campaign using a MalariaDiagnostic. Upon positive or negative diagnosis, the interventions in positive_diagnosis_configs or negative_diagnosis_configs are distributed to the individual.

The drug parameter provides a convenient shorthand: when set, it builds drug intervention configs and appends them to positive_diagnosis_configs automatically. If treatment_delay is also set, the drugs are wrapped in a DelayedIntervention.

The diagnostic broadcasts "TestedPositive" and "TestedNegative" events. If positive_diagnosis_configs or negative_diagnosis_configs are provided, separate triggered events listen for internal tether broadcasts and distribute the configured interventions.

Parameters:

Name Type Description Default
campaign (campaign, required)

The campaign object to which events will be added.

required
drug Union[str, list]

Drug regimen to distribute on positive diagnosis. Either a drug code string from DRUG_CODES (e.g. "AL", "DP") or a list of drug intervention objects (e.g. AdherentDrug instances). When provided, the resolved drug interventions are added to positive_diagnosis_configs. Default: None.

None
diagnostic_type Union[DiagnosticType, str]

Type of malaria diagnostic. See DiagnosticType for valid values. Default: DiagnosticType.BLOOD_SMEAR_PARASITES.

BLOOD_SMEAR_PARASITES
detection_threshold float

Detection threshold whose units depend on diagnostic_type. Default: 40.

40
measurement_sensitivity float

Volume of blood tested in microliters (blood-smear diagnostics). Default: 0.1.

0.1
treatment_delay BaseDistribution

Delay distribution between positive diagnosis and drug distribution. Only used when drug is provided. For example, ConstantDistribution(3) for a fixed 3-day delay. Default: None (no delay).

None
start_day float

Simulation day the survey is created. If triggered, runs on trigger. Default: 1.

1
node_ids Optional[List[int]]

Node IDs where the survey applies. None applies to all nodes.

None
event_name str

Descriptive name for the campaign event. Default: "Diagnostic Survey".

'Diagnostic Survey'
positive_diagnosis_configs list

Intervention objects distributed to individuals who test positive. If drug is also provided, the drug interventions are appended to this list.

None
negative_diagnosis_configs list

Intervention objects distributed to individuals who test negative.

None
received_test_event str

Event broadcast when an individual receives the test. Default: "Received_Test".

'Received_Test'
target_demographics_config TargetDemographicsConfig

Targeting configuration (coverage, age range, gender).

None
property_restrictions PropertyRestrictions

Individual/node property restrictions for receiving the diagnostic.

None
disqualifying_properties list

Property key:value pairs that prevent an individual from receiving the diagnostic.

None
repetition_config RepetitionConfig

Repetition configuration. For scheduled surveys, passed directly to add_intervention_scheduled. For triggered surveys with repetitions, implemented via delayed relay broadcasts.

None
trigger_condition_list list

Events that trigger the survey. If None, the survey is scheduled.

None
duration float

Days to listen for trigger events. -1 means indefinite. Default: -1.

-1
triggered_campaign_delay BaseDistribution

Delay distribution between trigger and survey distribution.

None
check_eligibility_at_trigger bool

If True and the triggered survey is delayed, property restrictions are checked at the initial trigger rather than at distribution time. Default: False.

False
expire_recent_drugs bool

If True, adds "DrugStatus:None" to property restrictions for the positive result action so only individuals without recent drugs receive positive-diagnosis interventions. Default: False.

False

Returns:

Type Description
None

None, adds events to the campaign.

Source code in emodpy_malaria/campaign/intervention_systems.py
def add_diagnostic_survey(
        campaign: api_campaign,
        drug: Union[str, list] = None,
        diagnostic_type: Union[DiagnosticType, str] = DiagnosticType.BLOOD_SMEAR_PARASITES,
        detection_threshold: float = 40,
        measurement_sensitivity: float = 0.1,
        treatment_delay: BaseDistribution = None,
        start_day: float = 1,
        node_ids: Optional[List[int]] = None,
        event_name: str = "Diagnostic Survey",
        positive_diagnosis_configs: list = None,
        negative_diagnosis_configs: list = None,
        received_test_event: str = "Received_Test",
        target_demographics_config: TargetDemographicsConfig = None,
        property_restrictions: PropertyRestrictions = None,
        disqualifying_properties: list = None,
        repetition_config: RepetitionConfig = None,
        trigger_condition_list: list = None,
        duration: float = -1,
        triggered_campaign_delay: BaseDistribution = None,
        check_eligibility_at_trigger: bool = False,
        expire_recent_drugs: bool = False) -> None:
    """
    Add a scheduled or triggered diagnostic survey to the campaign using a
    [MalariaDiagnostic](https://emod.idmod.org/emodpy-malaria/autoapi/emodpy_malaria/campaign/individual_intervention/).
    Upon positive or negative diagnosis, the interventions in
    **positive_diagnosis_configs** or **negative_diagnosis_configs** are distributed
    to the individual.

    The **drug** parameter provides a convenient shorthand: when set, it builds drug
    intervention configs and appends them to **positive_diagnosis_configs**
    automatically. If **treatment_delay** is also set, the drugs are wrapped in a
    [DelayedIntervention](https://emod.idmod.org/emodpy-malaria/autoapi/emodpy_malaria/campaign/individual_intervention/).

    The diagnostic broadcasts ``"TestedPositive"`` and ``"TestedNegative"`` events.
    If **positive_diagnosis_configs** or **negative_diagnosis_configs** are provided,
    separate triggered events listen for internal tether broadcasts and distribute the
    configured interventions.

    Args:
        campaign (api_campaign, required):
            The campaign object to which events will be added.

        drug (Union[str, list], optional):
            Drug regimen to distribute on positive diagnosis. Either a drug code
            string from `DRUG_CODES` (e.g. ``"AL"``, ``"DP"``) or a list
            of drug intervention objects (e.g.
            [AdherentDrug](https://emod.idmod.org/emodpy-malaria/autoapi/emodpy_malaria/campaign/individual_intervention/)
            instances). When provided, the resolved drug interventions are added
            to **positive_diagnosis_configs**. Default: None.

        diagnostic_type (Union[DiagnosticType, str], optional):
            Type of malaria diagnostic. See
            [DiagnosticType](https://emod.idmod.org/emodpy-malaria/autoapi/emodpy_malaria/utils/emod_enum/) for valid values.
            Default: ``DiagnosticType.BLOOD_SMEAR_PARASITES``.

        detection_threshold (float, optional):
            Detection threshold whose units depend on **diagnostic_type**.
            Default: 40.

        measurement_sensitivity (float, optional):
            Volume of blood tested in microliters (blood-smear diagnostics).
            Default: 0.1.

        treatment_delay (BaseDistribution, optional):
            Delay distribution between positive diagnosis and drug distribution.
            Only used when drug is provided. For example,
            ``ConstantDistribution(3)`` for a fixed 3-day delay.
            Default: None (no delay).

        start_day (float, optional):
            Simulation day the survey is created. If triggered, runs on trigger.
            Default: 1.

        node_ids (Optional[List[int]], optional):
            Node IDs where the survey applies. ``None`` applies to all nodes.

        event_name (str, optional):
            Descriptive name for the campaign event. Default: ``"Diagnostic Survey"``.

        positive_diagnosis_configs (list, optional):
            Intervention objects distributed to individuals who test positive.
            If drug is also provided, the drug interventions are appended
            to this list.

        negative_diagnosis_configs (list, optional):
            Intervention objects distributed to individuals who test negative.

        received_test_event (str, optional):
            Event broadcast when an individual receives the test.
            Default: ``"Received_Test"``.

        target_demographics_config (TargetDemographicsConfig, optional):
            Targeting configuration (coverage, age range, gender).

        property_restrictions (PropertyRestrictions, optional):
            Individual/node property restrictions for receiving the diagnostic.

        disqualifying_properties (list, optional):
            Property key:value pairs that prevent an individual from receiving
            the diagnostic.

        repetition_config (RepetitionConfig, optional):
            Repetition configuration. For scheduled surveys, passed directly to
            ``add_intervention_scheduled``. For triggered surveys with repetitions,
            implemented via delayed relay broadcasts.

        trigger_condition_list (list, optional):
            Events that trigger the survey. If ``None``, the survey is scheduled.

        duration (float, optional):
            Days to listen for trigger events. ``-1`` means indefinite.
            Default: -1.

        triggered_campaign_delay (BaseDistribution, optional):
            Delay distribution between trigger and survey distribution.

        check_eligibility_at_trigger (bool, optional):
            If ``True`` and the triggered survey is delayed, property restrictions
            are checked at the initial trigger rather than at distribution time.
            Default: False.

        expire_recent_drugs (bool, optional):
            If ``True``, adds ``"DrugStatus:None"`` to property restrictions for
            the positive result action so only individuals without recent drugs
            receive positive-diagnosis interventions. Default: False.

    Returns:
        None, adds events to the campaign.
    """
    if not isinstance(diagnostic_type, DiagnosticType):
        try:
            diagnostic_type = DiagnosticType(diagnostic_type)
        except ValueError:
            raise ValueError(
                f"Invalid diagnostic_type {diagnostic_type!r}. "
                f"Valid options: {list(DiagnosticType)}.")

    if drug is not None:
        if isinstance(drug, str):
            drug_configs = drug_configs_from_code(campaign, drug)
        elif isinstance(drug, list):
            for i, item in enumerate(drug):
                if not isinstance(item, AdherentDrug):
                    raise TypeError(
                        f"drug[{i}] must be an AdherentDrug instance, "
                        f"got {type(item).__name__}.")
            drug_configs = drug
        else:
            raise TypeError(
                f"drug must be a string (drug code) or a list of AdherentDrug instances, "
                f"got {type(drug).__name__}.")
        if treatment_delay is not None:
            if len(drug_configs) > 1:
                drug_mid = MultiInterventionDistributor(
                    campaign, intervention_list=drug_configs)
            else:
                drug_mid = drug_configs[0]
            drug_configs = [DelayedIntervention(
                campaign,
                delay_period_distribution=treatment_delay,
                intervention_to_distribute_at_delay_completion=drug_mid)]
        if positive_diagnosis_configs is None:
            positive_diagnosis_configs = drug_configs
        else:
            positive_diagnosis_configs = list(positive_diagnosis_configs) + drug_configs

    received_test = BroadcastEvent(campaign, broadcast_event=received_test_event)

    tested_positive_tether = f"TestedPositive_{random.randint(1, 100000)}"
    tested_negative_tether = f"TestedNegative_{random.randint(1, 100000)}"

    positive_action = MultiInterventionDistributor(
        campaign, intervention_list=[
            BroadcastEvent(campaign, broadcast_event="TestedPositive"),
            BroadcastEvent(campaign, broadcast_event=tested_positive_tether)])

    negative_action = MultiInterventionDistributor(
        campaign, intervention_list=[
            BroadcastEvent(campaign, broadcast_event="TestedNegative"),
            BroadcastEvent(campaign, broadcast_event=tested_negative_tether)])

    diagnostic = _make_diagnostic(
        campaign, diagnostic_type, detection_threshold, measurement_sensitivity,
        positive_diagnosis=positive_action,
        negative_diagnosis=negative_action)

    interventions = [diagnostic, received_test]
    if disqualifying_properties:
        interventions = [MultiInterventionDistributor(
            campaign, intervention_list=interventions,
            common_intervention_parameters=CommonInterventionParameters(
                disqualifying_properties=disqualifying_properties))]

    repetitions = repetition_config.number_repetitions if repetition_config else 1
    tsteps_btwn = repetition_config.timesteps_between_repetitions if repetition_config else 0

    if trigger_condition_list:
        if duration == -1:
            diagnosis_config_listening_duration = -1
        else:
            diagnosis_config_listening_duration = duration + 1

        actual_trigger = list(trigger_condition_list)
        actual_prop_restrictions = property_restrictions

        if repetitions > 1 or triggered_campaign_delay is not None:
            trigger_prop_restrictions = None
            if check_eligibility_at_trigger:
                trigger_prop_restrictions = property_restrictions
                actual_prop_restrictions = None

            broadcast_event_name = f"Diagnostic_Survey_Now_{random.randint(1, 100000)}"
            for x in range(repetitions):
                rep_offset = x * tsteps_btwn
                if triggered_campaign_delay is not None and rep_offset <= 0:
                    delay_dist = triggered_campaign_delay
                elif rep_offset > 0:
                    delay_dist = ConstantDistribution(rep_offset)
                else:
                    delay_dist = None
                add_intervention_triggered(
                    campaign,
                    intervention_list=[BroadcastEvent(campaign, broadcast_event=broadcast_event_name)],
                    triggers_list=trigger_condition_list,
                    start_day=start_day + 1,
                    duration=duration,
                    event_name="Diag_Survey_Now",
                    node_ids=node_ids,
                    delay_distribution=delay_dist,
                    property_restrictions=trigger_prop_restrictions)
            actual_trigger = [broadcast_event_name]

        add_intervention_triggered(
            campaign,
            intervention_list=interventions,
            triggers_list=actual_trigger,
            start_day=start_day + 1,
            duration=duration,
            event_name=event_name,
            node_ids=node_ids,
            target_demographics_config=target_demographics_config,
            property_restrictions=actual_prop_restrictions)
    else:
        diagnosis_config_listening_duration = duration
        add_intervention_scheduled(
            campaign,
            intervention_list=interventions,
            start_day=start_day + 1,
            node_ids=node_ids,
            target_demographics_config=target_demographics_config,
            property_restrictions=property_restrictions,
            repetition_config=repetition_config)

    positive_prop_restrictions = property_restrictions
    if expire_recent_drugs:
        if property_restrictions and property_restrictions.individual_property_restrictions:
            augmented = [group + ["DrugStatus:None"]
                         for group in property_restrictions.individual_property_restrictions]
            positive_prop_restrictions = PropertyRestrictions(
                individual_property_restrictions=augmented,
                node_property_restrictions=property_restrictions.node_property_restrictions)
        else:
            positive_prop_restrictions = PropertyRestrictions(
                individual_property_restrictions=[["DrugStatus:None"]])

    if positive_diagnosis_configs:
        add_intervention_triggered(
            campaign,
            intervention_list=positive_diagnosis_configs,
            triggers_list=[tested_positive_tether],
            start_day=start_day,
            duration=diagnosis_config_listening_duration,
            event_name=f"{event_name} Positive Result Action",
            node_ids=node_ids,
            property_restrictions=positive_prop_restrictions)

    if negative_diagnosis_configs:
        add_intervention_triggered(
            campaign,
            intervention_list=negative_diagnosis_configs,
            triggers_list=[tested_negative_tether],
            start_day=start_day,
            duration=diagnosis_config_listening_duration,
            event_name=f"{event_name} Negative Result Action",
            node_ids=node_ids)

add_drug_campaign(campaign, campaign_type=CampaignType.MDA, drug=None, start_days=None, target_demographics_config=None, repetition_config=None, property_restrictions=None, node_ids=None, drug_ineligibility_duration=0, receiving_drugs_event_name='Received_Campaign_Drugs', disqualifying_properties=None, diagnostic_type=DiagnosticType.BLOOD_SMEAR_PARASITES, diagnostic_threshold=40, measurement_sensitivity=0.1, treatment_delay=None, fmda_radius=0, node_selection_type=NodeSelectionType.DISTANCE_ONLY, trigger_coverage=1.0, snowballs=0, trigger_condition_list=None, duration=-1, triggered_campaign_delay=None, check_eligibility_at_trigger=False, trigger_name=None, birth_property_restrictions=None)

Add a drug intervention campaign from a list of malaria campaign types.

Campaign types:

  • MDA / SMC -- Mass drug administration. Distributes drugs directly.
  • MSAT / MTAT -- Mass screening and treatment. Runs a diagnostic survey and distributes drugs on positive result.
  • fMDA -- Focal mass drug administration. Diagnostic survey triggers drug distribution to the individual's node and neighboring nodes.
  • rfMSAT -- Reactive focal mass screening and treatment. Treatment of an index case triggers diagnostic surveys on neighboring nodes, cascading via snowballs.
  • rfMDA -- Reactive focal mass drug administration. Treatment of an index case triggers drug distribution to neighboring nodes.
  • PMC -- Preventive malaria chemoprevention (birth-triggered). Distributes drugs to newborns after a configurable delay.

Parameters:

Name Type Description Default
campaign (campaign, required)

The campaign object.

required
campaign_type Union[CampaignType, str]

The type of drug campaign:

  • MDA -- Mass drug administration; distributes drugs to everyone.
  • SMC -- Seasonal malaria chemoprevention; same as MDA.
  • MSAT -- Mass screening and treatment; diagnose then treat positives.
  • MTAT -- Mass testing and treatment; same as MSAT.
  • fMDA -- Focal MDA; diagnostic triggers drugs to nearby nodes.
  • rfMSAT -- Reactive focal MSAT; index-case treatment triggers diagnostic surveys on neighboring nodes, cascading via snowballs.
  • rfMDA -- Reactive focal MDA; index-case treatment triggers drug distribution to neighboring nodes.
  • PMC -- Preventive malaria chemoprevention; birth-triggered drug distribution to newborns.

Default: CampaignType.MDA.

MDA
drug (Union[str, list[AdherentDrug]], required)

The drug regimen to distribute. Either a drug code string from DRUG_CODES (e.g. "AL", "DP", "SP") or a list of drug intervention objects (e.g. AdherentDrug instances).

None
start_days list

Simulation days for drug distribution. Default: [1].

None
target_demographics_config TargetDemographicsConfig

Targeting configuration (coverage, age, gender, residents_only).

None
repetition_config RepetitionConfig

Repetition and interval configuration.

None
property_restrictions PropertyRestrictions

Individual/node property restrictions.

None
node_ids Optional[List[int]]

Node IDs. None applies to all nodes.

None
drug_ineligibility_duration float

Days to set DrugStatus:RecentDrug after receiving drugs. Default: 0.

0
receiving_drugs_event_name str

Event broadcast on drug receipt. Default: "Received_Campaign_Drugs".

'Received_Campaign_Drugs'
disqualifying_properties list

Properties that prevent drug receipt.

None
diagnostic_type Union[DiagnosticType, str]

Diagnostic type for screening campaigns. Default: DiagnosticType.BLOOD_SMEAR_PARASITES.

BLOOD_SMEAR_PARASITES
diagnostic_threshold float

Detection threshold. Default: 40.

40
measurement_sensitivity float

Measurement sensitivity. Default: 0.1.

0.1
treatment_delay BaseDistribution

Delay distribution between diagnosis and drug distribution (MSAT, fMDA) or between index case treatment and RCD response (rfMSAT, rfMDA). For example, ConstantDistribution(3) for a fixed 3-day delay. Default: None (no delay).

None
fmda_radius float

Radius in km for focal response. Default: 0.

0
node_selection_type Union[NodeSelectionType, str]

Node selection for focal broadcasts. Default: NodeSelectionType.DISTANCE_ONLY.

DISTANCE_ONLY
trigger_coverage float

Fraction of trigger events initiating RCD (rfMSAT, rfMDA) or fraction receiving diagnostic in fMDA. Default: 1.0.

1.0
snowballs int

Number of cascading snowball rounds for rfMSAT. Default: 0.

0
trigger_condition_list list

Events that trigger the campaign. None means scheduled.

None
duration float

Days to listen for triggers. -1 means indefinite. Default: -1.

-1
triggered_campaign_delay BaseDistribution

Delay distribution after trigger before campaign runs.

None
check_eligibility_at_trigger bool

Check property restrictions at trigger time vs distribution time. Default: False.

False
trigger_name str

PMC trigger name (e.g. "IPTi_1"). Required for PMC campaigns.

None
birth_property_restrictions PropertyRestrictions

Property restrictions for the PMC birth trigger event.

None

Returns:

Name Type Description
dict dict

Metadata with campaign type, drug code, and coverage info.

Source code in emodpy_malaria/campaign/intervention_systems.py
def add_drug_campaign(campaign: api_campaign,
                      campaign_type: Union[CampaignType, str] = CampaignType.MDA,
                      drug: Union[str, list[AdherentDrug]] = None,
                      start_days: list = None,
                      target_demographics_config: TargetDemographicsConfig = None,
                      repetition_config: RepetitionConfig = None,
                      property_restrictions: PropertyRestrictions = None,
                      node_ids: Optional[List[int]] = None,
                      drug_ineligibility_duration: float = 0,
                      receiving_drugs_event_name: str = "Received_Campaign_Drugs",
                      disqualifying_properties: list = None,
                      diagnostic_type: Union[DiagnosticType, str] = DiagnosticType.BLOOD_SMEAR_PARASITES,
                      diagnostic_threshold: float = 40,
                      measurement_sensitivity: float = 0.1,
                      treatment_delay: BaseDistribution = None,
                      fmda_radius: float = 0,
                      node_selection_type: Union[NodeSelectionType, str] = NodeSelectionType.DISTANCE_ONLY,
                      trigger_coverage: float = 1.0,
                      snowballs: int = 0,
                      trigger_condition_list: list = None,
                      duration: float = -1,
                      triggered_campaign_delay: BaseDistribution = None,
                      check_eligibility_at_trigger: bool = False,
                      trigger_name: str = None,
                      birth_property_restrictions: PropertyRestrictions = None) -> dict:
    """
    Add a drug intervention campaign from a list of malaria campaign types.

    Campaign types:

    * **MDA** / **SMC** -- Mass drug administration. Distributes drugs directly.
    * **MSAT** / **MTAT** -- Mass screening and treatment. Runs a diagnostic survey
      and distributes drugs on positive result.
    * **fMDA** -- Focal mass drug administration. Diagnostic survey triggers drug
      distribution to the individual's node and neighboring nodes.
    * **rfMSAT** -- Reactive focal mass screening and treatment. Treatment of an
      index case triggers diagnostic surveys on neighboring nodes, cascading via
      **snowballs**.
    * **rfMDA** -- Reactive focal mass drug administration. Treatment of an index
      case triggers drug distribution to neighboring nodes.
    * **PMC** -- Preventive malaria chemoprevention (birth-triggered). Distributes
      drugs to newborns after a configurable delay.

    Args:
        campaign (api_campaign, required):
            The campaign object.
        campaign_type (Union[CampaignType, str], optional):
            The type of drug campaign:

            * ``MDA`` -- Mass drug administration; distributes drugs to everyone.
            * ``SMC`` -- Seasonal malaria chemoprevention; same as MDA.
            * ``MSAT`` -- Mass screening and treatment; diagnose then treat positives.
            * ``MTAT`` -- Mass testing and treatment; same as MSAT.
            * ``fMDA`` -- Focal MDA; diagnostic triggers drugs to nearby nodes.
            * ``rfMSAT`` -- Reactive focal MSAT; index-case treatment triggers
              diagnostic surveys on neighboring nodes, cascading via **snowballs**.
            * ``rfMDA`` -- Reactive focal MDA; index-case treatment triggers drug
              distribution to neighboring nodes.
            * ``PMC`` -- Preventive malaria chemoprevention; birth-triggered drug
              distribution to newborns.

            Default: ``CampaignType.MDA``.
        drug (Union[str, list[AdherentDrug]], required):
            The drug regimen to distribute. Either a drug code string from
            `DRUG_CODES` (e.g. ``"AL"``, ``"DP"``, ``"SP"``) or a list of
            drug intervention objects (e.g.
            [AdherentDrug](https://emod.idmod.org/emodpy-malaria/autoapi/emodpy_malaria/campaign/individual_intervention/)
            instances).
        start_days (list, optional):
            Simulation days for drug distribution. Default: ``[1]``.
        target_demographics_config (TargetDemographicsConfig, optional):
            Targeting configuration (coverage, age, gender, residents_only).
        repetition_config (RepetitionConfig, optional):
            Repetition and interval configuration.
        property_restrictions (PropertyRestrictions, optional):
            Individual/node property restrictions.
        node_ids (Optional[List[int]], optional):
            Node IDs. ``None`` applies to all nodes.
        drug_ineligibility_duration (float, optional):
            Days to set ``DrugStatus:RecentDrug`` after receiving drugs.
            Default: 0.
        receiving_drugs_event_name (str, optional):
            Event broadcast on drug receipt. Default: ``"Received_Campaign_Drugs"``.
        disqualifying_properties (list, optional):
            Properties that prevent drug receipt.
        diagnostic_type (Union[DiagnosticType, str], optional):
            Diagnostic type for screening campaigns.
            Default: ``DiagnosticType.BLOOD_SMEAR_PARASITES``.
        diagnostic_threshold (float, optional):
            Detection threshold. Default: 40.
        measurement_sensitivity (float, optional):
            Measurement sensitivity. Default: 0.1.
        treatment_delay (BaseDistribution, optional):
            Delay distribution between diagnosis and drug distribution (MSAT, fMDA)
            or between index case treatment and RCD response (rfMSAT, rfMDA).
            For example, ``ConstantDistribution(3)`` for a fixed 3-day delay.
            Default: None (no delay).
        fmda_radius (float, optional):
            Radius in km for focal response. Default: 0.
        node_selection_type (Union[NodeSelectionType, str], optional):
            Node selection for focal broadcasts.
            Default: ``NodeSelectionType.DISTANCE_ONLY``.
        trigger_coverage (float, optional):
            Fraction of trigger events initiating RCD (rfMSAT, rfMDA) or fraction
            receiving diagnostic in fMDA. Default: 1.0.
        snowballs (int, optional):
            Number of cascading snowball rounds for rfMSAT. Default: 0.
        trigger_condition_list (list, optional):
            Events that trigger the campaign. ``None`` means scheduled.
        duration (float, optional):
            Days to listen for triggers. ``-1`` means indefinite. Default: -1.
        triggered_campaign_delay (BaseDistribution, optional):
            Delay distribution after trigger before campaign runs.
        check_eligibility_at_trigger (bool, optional):
            Check property restrictions at trigger time vs distribution time.
            Default: False.
        trigger_name (str, optional):
            PMC trigger name (e.g. ``"IPTi_1"``). Required for PMC campaigns.
        birth_property_restrictions (PropertyRestrictions, optional):
            Property restrictions for the PMC birth trigger event.

    Returns:
        dict: Metadata with campaign type, drug code, and coverage info.
    """
    if not isinstance(campaign_type, CampaignType):
        try:
            campaign_type = CampaignType(campaign_type)
        except ValueError:
            raise ValueError(
                f"Invalid campaign_type {campaign_type!r}. "
                f"Valid options: {list(CampaignType)}.")

    if drug is None:
        raise ValueError(
            "drug is required: provide a drug code string (e.g. 'AL', 'DP') "
            "or a list of drug intervention objects.")
    if isinstance(drug, str):
        drug_code = drug
        drug_configs = drug_configs_from_code(campaign, drug_code)
    elif isinstance(drug, list):
        for i, item in enumerate(drug):
            if not isinstance(item, AdherentDrug):
                raise TypeError(
                    f"drug[{i}] must be an AdherentDrug instance, "
                    f"got {type(item).__name__}.")
        drug_code = None
        drug_configs = drug
    else:
        raise TypeError(
            f"drug must be a string (drug code) or a list of AdherentDrug instances, "
            f"got {type(drug).__name__}.")

    receiving_drugs_event = BroadcastEvent(
        campaign, broadcast_event=receiving_drugs_event_name)
    if campaign_type in (CampaignType.rfMSAT, CampaignType.rfMDA):
        receiving_drugs_event = BroadcastEvent(
            campaign, broadcast_event="Received_RCD_Drugs")
    if drug_code and "Vehicle" in drug_code:
        receiving_drugs_event = BroadcastEvent(
            campaign, broadcast_event="Received_Vehicle")

    expire_recent_drugs = None
    if drug_ineligibility_duration > 0:
        expire_recent_drugs = PropertyValueChanger(
            campaign,
            target_property_key="DrugStatus",
            target_property_value="RecentDrug",
            revert=drug_ineligibility_duration)

    if start_days is None:
        start_days = [1]
    if disqualifying_properties is None:
        disqualifying_properties = []

    if campaign_type in (CampaignType.MDA, CampaignType.SMC):
        if treatment_delay is not None:
            raise ValueError("treatment_delay is not used in MDA or SMC campaigns.")
        _add_mda(campaign, start_days=start_days, drug_configs=drug_configs,
                 receiving_drugs_event=receiving_drugs_event,
                 expire_recent_drugs=expire_recent_drugs,
                 target_demographics_config=target_demographics_config,
                 repetition_config=repetition_config,
                 property_restrictions=property_restrictions,
                 disqualifying_properties=disqualifying_properties,
                 node_ids=node_ids,
                 trigger_condition_list=trigger_condition_list,
                 duration=duration,
                 triggered_campaign_delay=triggered_campaign_delay,
                 check_eligibility_at_trigger=check_eligibility_at_trigger)

    elif campaign_type in (CampaignType.MSAT, CampaignType.MTAT):
        _add_msat(campaign, start_days=start_days, drug_configs=drug_configs,
                  receiving_drugs_event=receiving_drugs_event,
                  expire_recent_drugs=expire_recent_drugs,
                  target_demographics_config=target_demographics_config,
                  repetition_config=repetition_config,
                  property_restrictions=property_restrictions,
                  disqualifying_properties=disqualifying_properties,
                  node_ids=node_ids,
                  diagnostic_type=diagnostic_type,
                  diagnostic_threshold=diagnostic_threshold,
                  measurement_sensitivity=measurement_sensitivity,
                  treatment_delay=treatment_delay,
                  trigger_condition_list=trigger_condition_list,
                  duration=duration,
                  triggered_campaign_delay=triggered_campaign_delay,
                  check_eligibility_at_trigger=check_eligibility_at_trigger)

    elif campaign_type == CampaignType.fMDA:
        _add_fmda(campaign, start_days=start_days, drug_configs=drug_configs,
                  receiving_drugs_event=receiving_drugs_event,
                  expire_recent_drugs=expire_recent_drugs,
                  target_demographics_config=target_demographics_config,
                  repetition_config=repetition_config,
                  property_restrictions=property_restrictions,
                  disqualifying_properties=disqualifying_properties,
                  node_ids=node_ids,
                  diagnostic_type=diagnostic_type,
                  diagnostic_threshold=diagnostic_threshold,
                  measurement_sensitivity=measurement_sensitivity,
                  treatment_delay=treatment_delay,
                  trigger_coverage=trigger_coverage,
                  fmda_radius=fmda_radius,
                  node_selection_type=node_selection_type,
                  trigger_condition_list=trigger_condition_list,
                  duration=duration,
                  triggered_campaign_delay=triggered_campaign_delay,
                  check_eligibility_at_trigger=check_eligibility_at_trigger)

    elif campaign_type == CampaignType.rfMSAT:
        _add_rfmsat(campaign, start_day=start_days[0], drug_configs=drug_configs,
                    receiving_drugs_event=receiving_drugs_event,
                    expire_recent_drugs=expire_recent_drugs,
                    target_demographics_config=target_demographics_config,
                    property_restrictions=property_restrictions,
                    disqualifying_properties=disqualifying_properties,
                    node_ids=node_ids,
                    diagnostic_type=diagnostic_type,
                    diagnostic_threshold=diagnostic_threshold,
                    measurement_sensitivity=measurement_sensitivity,
                    treatment_delay=treatment_delay,
                    trigger_coverage=trigger_coverage,
                    fmda_radius=fmda_radius,
                    node_selection_type=node_selection_type,
                    snowballs=snowballs,
                    duration=duration)

    elif campaign_type == CampaignType.rfMDA:
        _add_rfmda(campaign, start_day=start_days[0], drug_configs=drug_configs,
                   receiving_drugs_event=receiving_drugs_event,
                   expire_recent_drugs=expire_recent_drugs,
                   target_demographics_config=target_demographics_config,
                   property_restrictions=property_restrictions,
                   disqualifying_properties=disqualifying_properties,
                   node_ids=node_ids,
                   treatment_delay=treatment_delay,
                   trigger_coverage=trigger_coverage,
                   fmda_radius=fmda_radius,
                   node_selection_type=node_selection_type,
                   duration=duration)

    elif campaign_type == CampaignType.PMC:
        if treatment_delay is not None:
            raise ValueError("treatment_delay is not used in PMC campaigns.")
        _add_pmc(campaign, start_day=start_days[0], drug_configs=drug_configs,
                 trigger_name=trigger_name,
                 target_demographics_config=target_demographics_config,
                 node_ids=node_ids,
                 duration=duration,
                 triggered_campaign_delay=triggered_campaign_delay,
                 property_restrictions=property_restrictions,
                 birth_property_restrictions=birth_property_restrictions)

    return {
        "drug_campaign.type": campaign_type,
        "drug_campaign.drug": drug_code or drug,
        "drug_campaign.trigger_coverage": trigger_coverage,
        "drug_campaign.coverage": (
            target_demographics_config.demographic_coverage
            if target_demographics_config else 1.0),
    }

add_treatment_seeking(campaign, targets, drug=None, start_day=1, node_ids=None, property_restrictions=None, drug_ineligibility_duration=0, duration=-1, broadcast_event_name='Received_Treatment')

Add event-triggered treatment-seeking behavior to the campaign. When an individual broadcasts one of the trigger events (e.g. NewClinicalCase), they receive a drug regimen and a broadcast event indicating treatment was received.

Each entry in targets produces a separate triggered campaign event, allowing different trigger/coverage/age/delay combinations in the same call.

Parameters:

Name Type Description Default
campaign (campaign, required)

The campaign object to which events will be added.

required
targets (list[dict], required)

A list of dictionaries, each defining one trigger-and-target combination. Each dictionary supports the following keys:

  • trigger (str, required) -- The individual event that triggers drug distribution (e.g. "NewClinicalCase", "NewSevereCase").
  • coverage (float, optional) -- Fraction of qualifying individuals who receive treatment. Default: 1.0.
  • agemin (float, optional) -- Minimum age in years. Default: 0.
  • agemax (float, optional) -- Maximum age in years. Default: MAX_AGE_YEARS.
  • rate (float, optional) -- Rate parameter for an exponential delay (1 / mean delay in days) between trigger and treatment. A value of 0 means treatment is immediate. Default: 0.

Example::

targets = [
    {"trigger": "NewClinicalCase", "coverage": 0.8,
     "agemin": 15, "agemax": 70, "rate": 0.3},
    {"trigger": "NewSevereCase", "coverage": 0.9}
]
required
drug list[str]

Names of antimalarial drugs to distribute. Each name must match a drug configured in the simulation's Malaria_Drug_Params. Default: ["Artemether", "Lumefantrine"].

None
start_day float

The simulation day on which the triggered event begins listening. Default: 1.

1
node_ids Optional[List[int]]

Node IDs where the event applies. If None, applies to all nodes. Default: None.

None
property_restrictions PropertyRestrictions

A PropertyRestrictions object specifying individual property restrictions for receiving treatment. Default: None.

None
drug_ineligibility_duration float

Number of days an individual is ineligible for additional drugs after receiving treatment. Implemented by setting the individual property DrugStatus to RecentDrug for this duration. Set to 0 to disable. Default: 0.

0
duration float

Number of days the triggered event listens for triggers. A value of -1 means it listens indefinitely. Default: -1.

-1
broadcast_event_name str

Event broadcast when an individual receives treatment. Default: "Received_Treatment".

'Received_Treatment'

Returns:

Type Description
None

None, adds events to the campaign.

Example

from emodpy_malaria.campaign.intervention_systems import add_treatment_seeking from emod_api import campaign as api_campaign my_campaign = api_campaign my_campaign.set_schema("path_to_schema.json") from emodpy_malaria.campaign.common import PropertyRestrictions add_treatment_seeking( ... campaign=my_campaign, ... targets=[ ... {"trigger": "NewClinicalCase", "coverage": 0.8, "rate": 0.3}, ... {"trigger": "NewSevereCase", "coverage": 0.9} ... ], ... drug=["Artemether", "Lumefantrine"], ... start_day=1, ... property_restrictions=PropertyRestrictions( ... individual_property_restrictions=[["Risk:High"]]), ... drug_ineligibility_duration=14 ... )

Source code in emodpy_malaria/campaign/intervention_systems.py
def add_treatment_seeking(campaign: api_campaign,
                          targets: list[dict],
                          drug: list[str] = None,
                          start_day: float = 1,
                          node_ids: Optional[List[int]] = None,
                          property_restrictions: PropertyRestrictions = None,
                          drug_ineligibility_duration: float = 0,
                          duration: float = -1,
                          broadcast_event_name: str = "Received_Treatment") -> None:
    """
    Add event-triggered treatment-seeking behavior to the campaign. When an individual
    broadcasts one of the trigger events (e.g. ``NewClinicalCase``), they receive a
    drug regimen and a broadcast event indicating treatment was received.

    Each entry in **targets** produces a separate triggered campaign event, allowing
    different trigger/coverage/age/delay combinations in the same call.

    Args:
        campaign (api_campaign, required):
            The campaign object to which events will be added.

        targets (list[dict], required):
            A list of dictionaries, each defining one trigger-and-target combination.
            Each dictionary supports the following keys:

            * ``trigger`` (str, required) -- The individual event that triggers
              drug distribution (e.g. ``"NewClinicalCase"``, ``"NewSevereCase"``).
            * ``coverage`` (float, optional) -- Fraction of qualifying individuals
              who receive treatment. Default: 1.0.
            * ``agemin`` (float, optional) -- Minimum age in years. Default: 0.
            * ``agemax`` (float, optional) -- Maximum age in years.
              Default: [MAX_AGE_YEARS](https://emod.idmod.org/emodpy-malaria/autoapi/emodpy_malaria/campaign/common/).
            * ``rate`` (float, optional) -- Rate parameter for an exponential delay
              (1 / mean delay in days) between trigger and treatment. A value of 0
              means treatment is immediate. Default: 0.

            Example::

                targets = [
                    {"trigger": "NewClinicalCase", "coverage": 0.8,
                     "agemin": 15, "agemax": 70, "rate": 0.3},
                    {"trigger": "NewSevereCase", "coverage": 0.9}
                ]

        drug (list[str], optional):
            Names of antimalarial drugs to distribute. Each name must match a drug
            configured in the simulation's Malaria_Drug_Params.
            Default: ``["Artemether", "Lumefantrine"]``.

        start_day (float, optional):
            The simulation day on which the triggered event begins listening.
            Default: 1.

        node_ids (Optional[List[int]], optional):
            Node IDs where the event applies. If ``None``, applies to all nodes.
            Default: None.

        property_restrictions (PropertyRestrictions, optional):
            A [PropertyRestrictions](https://emod.idmod.org/emodpy-malaria/autoapi/emodpy_malaria/campaign/common/) object
            specifying individual property restrictions for receiving treatment.
            Default: None.

        drug_ineligibility_duration (float, optional):
            Number of days an individual is ineligible for additional drugs after
            receiving treatment. Implemented by setting the individual property
            ``DrugStatus`` to ``RecentDrug`` for this **duration**. Set to 0 to disable.
            Default: 0.

        duration (float, optional):
            Number of days the triggered event listens for triggers. A value of -1
            means it listens indefinitely.
            Default: -1.

        broadcast_event_name (str, optional):
            Event broadcast when an individual receives treatment.
            Default: ``"Received_Treatment"``.

    Returns:
        None, adds events to the campaign.

    Example:
        >>> from emodpy_malaria.campaign.intervention_systems import add_treatment_seeking
        >>> from emod_api import campaign as api_campaign
        >>> my_campaign = api_campaign
        >>> my_campaign.set_schema("path_to_schema.json")
        >>> from emodpy_malaria.campaign.common import PropertyRestrictions
        >>> add_treatment_seeking(
        ...     campaign=my_campaign,
        ...     targets=[
        ...         {"trigger": "NewClinicalCase", "coverage": 0.8, "rate": 0.3},
        ...         {"trigger": "NewSevereCase", "coverage": 0.9}
        ...     ],
        ...     drug=["Artemether", "Lumefantrine"],
        ...     start_day=1,
        ...     property_restrictions=PropertyRestrictions(
        ...         individual_property_restrictions=[["Risk:High"]]),
        ...     drug_ineligibility_duration=14
        ... )
    """
    if drug is None:
        drug = ["Artemether", "Lumefantrine"]

    if not targets:
        raise ValueError(
            "Please define targets for treatment seeking. It is a list of dictionaries:\n"
            'ex: [{"trigger": "NewClinicalCase", "coverage": 0.8, "agemin": 15, '
            '"agemax": 70, "rate": 0.3}]')

    for target in targets:
        if "trigger" not in target:
            raise ValueError(
                "Please define trigger for each target dictionary.\n"
                'ex: [{"trigger": "NewClinicalCase", "coverage": 0.7, "agemax": 3}]')
        if "seek" in target:
            raise ValueError(
                "The 'seek' parameter has been removed. Please remove it from your "
                "targets dictionary and modify the coverage parameter directly.")

    interventions = [AntimalarialDrug(campaign, drug_type=d) for d in drug]
    interventions.append(BroadcastEvent(campaign, broadcast_event=broadcast_event_name))

    if drug_ineligibility_duration > 0:
        interventions.append(PropertyValueChanger(
            campaign,
            target_property_key="DrugStatus",
            target_property_value="RecentDrug",
            revert=drug_ineligibility_duration))

    for target in targets:
        coverage = target.get("coverage", 1.0)
        age_min = target.get("agemin", 0)
        age_max = target.get("agemax", MAX_AGE_YEARS)
        rate = target.get("rate", 0)

        target_demographics_config = TargetDemographicsConfig(
            demographic_coverage=coverage,
            target_age_min=age_min,
            target_age_max=age_max)

        delay_distribution = None
        if rate > 0:
            delay_distribution = ExponentialDistribution(1.0 / rate)

        add_intervention_triggered(
            campaign=campaign,
            intervention_list=interventions,
            triggers_list=[target["trigger"]],
            start_day=start_day,
            duration=duration,
            event_name="Treatment_Seeking_Behavior",
            node_ids=node_ids,
            delay_distribution=delay_distribution,
            target_demographics_config=target_demographics_config,
            property_restrictions=property_restrictions)

drug_configs_from_code(campaign, drug_code)

Build a list of AntimalarialDrug interventions from a shorthand drug code.

Parameters:

Name Type Description Default
campaign (campaign, required)

The campaign object.

required
drug_code (str, required)

A key from DRUG_CODES (e.g. "AL", "DP", "SP").

required

Returns:

Type Description
list[AntimalarialDrug]

list[AntimalarialDrug]: One intervention per drug in the regimen.

Raises:

Type Description
ValueError

If drug_code is not in DRUG_CODES.

Source code in emodpy_malaria/campaign/intervention_systems.py
def drug_configs_from_code(campaign: api_campaign, drug_code: str) -> list[AntimalarialDrug]:
    """
    Build a list of [AntimalarialDrug](https://emod.idmod.org/emodpy-malaria/autoapi/emodpy_malaria/campaign/individual_intervention/)
    interventions from a shorthand drug code.

    Args:
        campaign (api_campaign, required):
            The campaign object.
        drug_code (str, required):
            A key from `DRUG_CODES` (e.g. ``"AL"``, ``"DP"``, ``"SP"``).

    Returns:
        list[AntimalarialDrug]: One intervention per drug in the regimen.

    Raises:
        ValueError: If *drug_code* is not in `DRUG_CODES`.
    """
    if not drug_code or drug_code not in DRUG_CODES:
        valid = ", ".join(DRUG_CODES.keys())
        raise ValueError(f"Invalid drug_code {drug_code!r}. Valid codes: {valid}")
    return [
        AntimalarialDrug(campaign, drug_type=d, common_intervention_parameters=CommonInterventionParameters(cost=1))
        for d in DRUG_CODES[drug_code]
    ]