Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate HSI_Contraception_FamilyPlanningAppt.EXPECTED_APPT_FOOTPRINT at class initialisation #1524

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

matt-graham
Copy link
Collaborator

@matt-graham matt-graham commented Nov 21, 2024

Resolves #1467

Changes HSI_Contraception_FamilyPlanningAppt.EXPECTED_APPT_FOOTPRINT from being a property to a static attribute, with the value evaluated when the class (HSI event) is initialised (scheduled).

This may change the value associated with EXPECTED_APPT_FOOTPRINT as it is now depends on the targets properties (specifically the current contraception method indicated by the categorical co_contraception property) at the time of scheduling rather than evaluation of EXPECTED_APPT_FOOTPRINT. Marking this as draft for now as will need @EvaJanouskova and/or @BinglingICL to verify if this change in behaviour is reasonable.

As discussed in #1467 repeated dataframe accesses when dynamically evaluating EXPECTED_APPT_FOOTPRINT for this HSI event where becoming a significant performance bottleneck - from some short profiled runs locally the changes here give around a 20% reduction in run time.

@matt-graham matt-graham changed the title Generate`EXPECTED_APPT_FOOTPRINT at class initialisation Generate HSI_Contraception_FamilyPlanningAppt.EXPECTED_APPT_FOOTPRINT at class initialisation Nov 21, 2024
@matt-graham matt-graham marked this pull request as draft November 21, 2024 16:04
@matt-graham
Copy link
Collaborator Author

From running the tests locally it looks like these changes break at least one test in tests/test_contraception.py:

_______________________________________________________________________________ test_record_of_appt_footprint_for_switching_to_methods[83563095832589325021] ________________________________________________________________________________
tests/test_contraception.py:485: in test_record_of_appt_footprint_for_switching_to_methods
    assert is_list_longer_than_length_of_one_and_with_first_element_nonblank_and_subsequent_blank(
E   AssertionError: assert False
E    +  where False = <function test_record_of_appt_footprint_for_switching_to_methods.<locals>.is_list_longer_than_length_of_one_and_with_first_element_nonblank_and_subsequent_blank at 0x7fb3ac5d45e0>([{'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, ...])
E    +    where [{'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, ...] = <function test_record_of_appt_footprint_for_switching_to_methods.<locals>.get_appt_footprints at 0x7fb3ac614c20>(switch_from='not_using', switch_to='female_sterilization', consumables_available=False)

I am not entirely sure what this test is meant to be checking. The full output of the call get_appt_footprints(switch_from='not_using', switch_to='female_sterilization', consumables_available=False) which is passed to is_list_longer_than_length_of_one_and_with_first_element_nonblank_and_subsequent_blank is

[{'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}, {'MinorSurg': 1}]

with the last condition (subsequent elements after first blank) failing.

@matt-graham
Copy link
Collaborator Author

My understanding of why the test is failing is that the HSI event reschedules itself if a lack of consumables meant the change to contraception method could not be enacted

if (not co_administrated) and (
self._number_of_times_run < self.module.parameters['max_number_of_runs_of_hsi_if_consumable_not_available']
):
self.reschedule()

with the reschedule function passing in self as the HSI event instance

def reschedule(self):
"""Schedule for this same HSI_Event to occur tomorrow."""
self.module.sim.modules['HealthSystem'].schedule_hsi_event(hsi_event=self,
topen=self.sim.date + pd.DateOffset(days=1),
tclose=None,
priority=1)

This means in any subsequent runs of HSI event apply method the value of EXPECTED_APPT_FOOTPRINT will remain what is was when event was first scheduled and __init__ method called (and so be based on the current contraception method at that point), as the rescheduled events do not create a new HSI event instance.

I believe the intended interface for communicating changes to the appointment footprint from the initial expected footprint at the point of scheduling is for the apply method to return the actual appointment footprint. Adding a line

return self._get_appt_footprint(current_method)

to the end of the apply method to return the appointment footprint based on current_method in the call to apply appears to fix the previously failing test.

I initially also added a line current_method = _new_contraceptive to the if block

if current_method != _new_contraceptive:
# Do the change:
self.module.do_and_log_individual_contraception_change(
woman_id=self.target,
old=current_method,
new=_new_contraceptive
)

so that current_method at the end of the method when self._get_appt_footprint is called reflects the method after a change has been enacted but this led to another test failure

FAILED tests/test_contraception.py::test_record_of_appt_footprint_for_switching_to_methods[83563095832589325021] - AssertionError: assert [{'FamPlan': 1}] == [{'PharmDispensing': 1}]

so I am assuming the actual appointment footprint should be based on the current contraception method at the point HSI event is being run not after it has run (and the method has potentially changed).

@matt-graham
Copy link
Collaborator Author

/run profiling

@matt-graham matt-graham marked this pull request as ready for review November 22, 2024 11:36
@BinglingICL
Copy link
Collaborator

Hi Matt, thanks for the neat and clear change and careful checks. I think the changes you've made look fine and agree with your saying that "actual appointment footprint should be based on the current contraception method at the point HSI event is being run not after it has run (and the method has potentially changed)".

Would be grateful if Eva @EvaJanouskova could double check the logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants