diff --git a/.github/workflows/run-on-comment.yml b/.github/workflows/run-on-comment.yml index 3fdc74b53d..0edf974935 100644 --- a/.github/workflows/run-on-comment.yml +++ b/.github/workflows/run-on-comment.yml @@ -126,7 +126,7 @@ jobs: - name: Get comment-bot token if: always() && steps.has_permissions.outputs.result == 'true' id: get_comment_bot_token - uses: peter-murray/workflow-application-token-action@dc0413987a085fa17d19df9e47d4677cf81ffef3 + uses: peter-murray/workflow-application-token-action@8e4e6fbf6fcc8a272781d97597969d21b3812974 with: application_id: ${{ secrets.application-id }} application_private_key: ${{ secrets.application-private-key }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 60b0f10cc4..5d39c44840 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -46,9 +46,10 @@ jobs: name: Set matrix run: | set -e + shopt -s globstar # Find all test files and generate their list in JSON format VAR_FILES="{\"include\":[" - for file in tests/test_*.py; do + for file in tests/**/test_*.py; do VAR_FILES="${VAR_FILES}{\"file\":\"${file}\"}," done VAR_FILES="${VAR_FILES}]}" diff --git a/.gitignore b/.gitignore index 9711f1da10..6805a8cd3d 100644 --- a/.gitignore +++ b/.gitignore @@ -124,6 +124,7 @@ src/**/_version.py # Generated TLO docs files docs/_*.rst +docs/_*.html docs/hsi_events.csv docs/parameters.rst docs/reference/modules.rst diff --git a/docs/conf.py b/docs/conf.py index 52eb0ff76b..f738017398 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,6 @@ 'sphinx.ext.ifconfig', 'sphinx.ext.napoleon', 'sphinx.ext.todo', - 'sphinx.ext.viewcode', 'rawfiles' ] @@ -56,7 +55,6 @@ html_theme = 'sphinx_rtd_theme' html_use_smartypants = True -html_last_updated_fmt = '%b %d, %Y' html_split_index = False html_show_copyright = False html_sidebars = { diff --git a/docs/publications.bib b/docs/publications.bib new file mode 100644 index 0000000000..6948b618e3 --- /dev/null +++ b/docs/publications.bib @@ -0,0 +1,427 @@ + +@misc{mohan_framework_2024, + title = {Theory of {Change} {Framework} for {Economic} {Evaluation} {Using} {Health} {System} {Models}}, + url = {https://pure.york.ac.uk/portal/en/publications/theory-of-change-framework-for-economic-evaluation-using-health-s}, + abstract = {All-disease health systems models (HSMs) represent the new frontier of economic evaluation to help guide sector-wide resource allocation, allowing for decision analysis in the context of interacting health system capacity constraints. Although there are frameworks for how health systems and their relationship with health outcomes may be characterised, there is a gap in the literature in providing a comprehensive list of health system components and a template for impact pathways from health system components to health outcomes to consider when designing, using and communicating HSMs for economic evaluation. This paper provides a conceptual framework to serve as a theoretical underpinning for the design and use of HSMs developed for economic evaluation. The framework builds upon previous literature as well as our experience developing the Thanzi La Onse (TLO) Model for Malawi.}, + publisher = {York Research Database}, + author = {Mohan, Sakshi and Revill, Paul and Chalkley, Martin and Colbourn, Tim and Mangal, Tara and Molaro, Margherita and Nkhoma, Dominic and She, Bingling and Walker, Simon and Phillips, Andrew and Hallet, Timothy and Sculpher, Mark}, + month = nov, + year = {2024}, + keywords = {Theoretical frameworks}, +} + +@inproceedings{mohan_potential_2024, + address = {AUT}, + title = {The {Potential} {Impact} of {Investments} in {Supply} {Chain} {Strengthening} ({Retrospective} analysis)}, + url = {https://doi.org/10.15124/yao-7b1g-n044}, + doi = {10.15124/yao-7b1g-n044}, + abstract = {Supply chain strengthening (SCS) is a key component in the overall strategy of countries to move towards universal health coverage. Estimating the health benefit of investments in such health system strengthening (HSS) interventions has been challenging because these benefits are mediated through their impact on the delivery of a wide range of healthcare interventions, creating a problem of attribution. We overcome this challenge by simulating the impact of SCS within the Thanzi La Onse (TLO) model, an individual-based simulation of health care needs and service delivery for Malawi, drawing upon demographic, epidemiological and routine healthcare system data (on facilities, staff and consumables). In this study, we combine the results of a previous inferential analysis on the factors associated with consumable availability at health facilities in Malawi with the TLO model to estimate the potential for health impact of SCS interventions in the country. We do this by first predicting the expected change in consumable availability by making a positive change to these factors using previously fitted multi-level regression models of consumable availability. We then run the TLO model with these improved consumable availability estimates. The difference in the DALYs accrued by the simulated population under the baseline availability of consumables and that under improved consumable availability estimates gives us the potential for health impact of SCS interventions which would influence these factors. Countries regularly need to make decisions on allocating resources across a range of health interventions (including service delivery and HSS). Crucial to guide these decisions is a value-for-money (VfM) assessment comparing these interventions. Our analysis offers the first step in estimating the VfM of a sample of SCS interventions and can guide Malawi in its evaluation of alternative health sector investments.}, + language = {en}, + urldate = {2024-11-18}, + booktitle = {European {Health} {Economics} {Association} ({EuHEA}) conference 2024}, + publisher = {White Rose Research Repository}, + author = {Mohan, Sakshi}, + month = nov, + year = {2024}, + keywords = {Analyses using the model}, +} + +@misc{nkhoma_thanzi_2024, + title = {Thanzi {La} {Mawa} ({TLM}) datasets: health worker time and motion, patient exit interview and follow-up, and health facility resources, perceptions and quality in {Malawi}}, + copyright = {© 2024, Posted by Cold Spring Harbor Laboratory. This pre-print is available under a Creative Commons License (Attribution-NonCommercial-NoDerivs 4.0 International), CC BY-NC-ND 4.0, as described at http://creativecommons.org/licenses/by-nc-nd/4.0/}, + shorttitle = {Thanzi {La} {Mawa} ({TLM}) datasets}, + url = {https://www.medrxiv.org/content/10.1101/2024.11.14.24317330v1}, + doi = {10.1101/2024.11.14.24317330}, + abstract = {The Thanzi La Mawa (TLM) study aims to enhance understanding of healthcare delivery and resource allocation in Malawi by capturing real-world data across a range of health facilities. To inform the Thanzi La Onse (TLO) model, which is the first comprehensive health system model developed for any country, this study uses a cross-sectional, mixed-methods approach to collect data on healthcare worker productivity, patient experiences, facility resources, and care quality. The TLM dataset includes information from 29 health facilities sampled across Malawi, covering facility audits, patient exit interviews, follow-ups, time and motion studies, and healthcare worker interviews, conducted from January to May 2024. +Through these data collection tools, the TLM study gathers insights into critical areas such as time allocation of health workers, healthcare resource availability, patient satisfaction, and overall service quality. This data is crucial for enhancing the TLO model’s capacity to answer complex policy questions related to health resource allocation in Malawi. The study also offers a structured framework that other countries in East, Central, and Southern Africa can adopt to improve their healthcare systems. +By documenting methods and protocols, this paper provides valuable guidance for researchers and policymakers interested in healthcare system evaluation and improvement. Given the formal adoption of the TLO model in Malawi, the TLM dataset serves as a foundation for ongoing analyses into quality of care, healthcare workforce efficiency, and patient outcomes. This study seeks to support informed decision-making and future implementation of comprehensive healthcare system models in similar settings.}, + language = {en}, + urldate = {2024-11-18}, + publisher = {medRxiv}, + author = {Nkhoma, Dominic and Chitsulo, Precious and Mulwafu, Watipaso and Mnjowe, Emmanuel and Tafesse, Wiktoria and Mohan, Sakshi and Hallet, Timothy B. and Collins, Joseph H. and Revill, Paul and Chalkley, Martin and Mwapasa, Victor and Mfutso-Bengo, Joseph and Colbourn, Tim}, + month = nov, + year = {2024}, + note = {ISSN: 2431-7330 +Pages: 2024.11.14.24317330}, + keywords = {Data Collection - Protocol and Analyses}, +} + +@article{rao_using_2024, + title = {Using economic analysis to inform health resource allocation: lessons from {Malawi}}, + volume = {3}, + issn = {2731-7501}, + shorttitle = {Using economic analysis to inform health resource allocation}, + doi = {10.1007/s44250-024-00115-4}, + abstract = {Despite making remarkable strides in improving health outcomes, Malawi faces concerns about sustaining the progress achieved due to limited fiscal space and donor dependency. The imperative for efficient health spending becomes evident, necessitating strategic allocation of resources to areas with the greatest impact on mortality and morbidity. Health benefits packages hold promise in supporting efficient resource allocation. However, despite defining these packages over the last two decades, their development and implementation have posed significant challenges for Malawi. In response, the Malawian government, in collaboration with the Thanzi la Onse Programme, has developed a set of tools and frameworks, primarily based on cost-effectiveness analysis, to guide the design of health benefits packages likely to achieve national health objectives. This review provides an overview of these tools and frameworks, accompanied by other related analyses, aiming to better align health financing with health benefits package prioritization. The paper is organized around five key policy questions facing decision-makers: (i) What interventions should the health system deliver? (ii) How should resources be allocated geographically? (iii) How should investments in health system inputs be prioritized? (iv) How should equity considerations be incorporated into resource allocation decisions? and (v) How should evidence generation be prioritized to support resource allocation decisions (guiding research)? The tools and frameworks presented here are intended to be compatible for use in diverse and often complex healthcare systems across Africa, supporting the health resource allocation process as countries pursue Universal Health Coverage.}, + language = {eng}, + number = {1}, + journal = {Discover Health Systems}, + author = {Rao, Megha and Nkhoma, Dominic and Mohan, Sakshi and Twea, Pakwanja and Chilima, Benson and Mfutso-Bengo, Joseph and Ochalek, Jessica and Hallett, Timothy B. and Phillips, Andrew N. and McGuire, Finn and Woods, Beth and Walker, Simon and Sculpher, Mark and Revill, Paul}, + year = {2024}, + pmid = {39022531}, + pmcid = {PMC11249770}, + keywords = {Theoretical Frameworks}, + pages = {48}, +} + +@article{hallett_estimates_2024, + title = {Estimates of resource use in the public-sector health-care system and the effect of strengthening health-care services in {Malawi} during 2015–19: a modelling study ({Thanzi} {La} {Onse})}, + issn = {2214-109X}, + shorttitle = {Estimates of resource use in the public-sector health-care system and the effect of strengthening health-care services in {Malawi} during 2015–19}, + url = {https://www.sciencedirect.com/science/article/pii/S2214109X24004133}, + doi = {10.1016/S2214-109X(24)00413-3}, + abstract = {Background +In all health-care systems, decisions need to be made regarding allocation of available resources. Evidence is needed for these decisions, especially in low-income countries. We aimed to estimate how health-care resources provided by the public sector were used in Malawi during 2015–19 and to estimate the effects of strengthening health-care services. +Methods +For this modelling study, we used the Thanzi La Onse model, an individual-based simulation model. The scope of the model was health care provided by the public sector in Malawi during 2015–19. Health-care services were delivered during health-care system interaction (HSI) events, which we characterised as occurring at a particular facility level and requiring a particular number of appointments. We developed mechanistic models for the causes of death and disability that were estimated to account for approximately 81\% of deaths and approximately 72\% of disability-adjusted life-years (DALYs) in Malawi during 2015–19, according to the Global Burden of Disease (GBD) estimates; we computed DALYs incurred in the population as the sum of years of life lost and years lived with disability. The disease models could interact with one another and with the underlying properties of each person. Each person in the Thanzi La Onse model had specific properties (eg, sex, district of residence, wealth percentile, smoking status, and BMI, among others), for which we measured distribution and evolution over time using demographic and health survey data. We also estimated the effect of different types of health-care system improvement. +Findings +We estimated that the public-sector health-care system in Malawi averted 41·2 million DALYs (95\% UI 38·6–43·8) during 2015–19, approximately half of the 84·3 million DALYs (81·5–86·9) that the population would otherwise have incurred. DALYs averted were heavily skewed to children aged 0–4 years due to services averting DALYs that would be caused by acute lower respiratory tract infection, HIV or AIDS, malaria, or neonatal disorders. DALYs averted among adults were mostly attributed to HIV or AIDS and tuberculosis. Under a scenario whereby each appointment took the time expected and health-care workers did not work for longer than contracted, the health-care system in Malawi during 2015–19 would have averted only 19·1 million DALYs (95\% UI 17·1–22·4), suggesting that approximately 21·3 million DALYS (20·0–23·6) of total effect were derived through overwork of health-care workers. If people becoming ill immediately accessed care, all referrals were successfully completed, diagnostic accuracy of health-care workers was as good as possible, and consumables (ie, medicines) were always available, 28·2\% (95\% UI 25·7–30·9) more DALYS (ie, 12·2 million DALYs [95\% UI 10·9–13·8]) could be averted. +Interpretation +The health-care system in Malawi provides substantial health gains with scarce resources. Strengthening interventions could potentially increase these gains, so should be a priority for investigation and investment. An individual-based simulation model of health-care service delivery is valuable for health-care system planning and strengthening. +Funding +The Wellcome Trust, UK Research and Innovation, the UK Medical Research Council, and Community Jameel.}, + urldate = {2024-11-14}, + journal = {The Lancet Global Health}, + author = {Hallett, Timothy B and Mangal, Tara D and Tamuri, Asif U and Arinaminpathy, Nimalan and Cambiano, Valentina and Chalkley, Martin and Collins, Joseph H and Cooper, Jonathan and Gillman, Matthew S and Giordano, Mosè and Graham, Matthew M and Graham, William and Hawryluk, Iwona and Janoušková, Eva and Jewell, Britta L and Lin, Ines Li and Manning Smith, Robert and Manthalu, Gerald and Mnjowe, Emmanuel and Mohan, Sakshi and Molaro, Margherita and Ng'ambi, Wingston and Nkhoma, Dominic and Piatek, Stefan and Revill, Paul and Rodger, Alison and Salmanidou, Dimitra and She, Bingling and Smit, Mikaela and Twea, Pakwanja D and Colbourn, Tim and Mfutso-Bengo, Joseph and Phillips, Andrew N}, + month = nov, + year = {2024}, + keywords = {Overview of the model}, +} + +@article{tafesse_faith-based_2021, + title = {Faith-based provision of sexual and reproductive healthcare in {Malawi}}, + volume = {282}, + issn = {02779536}, + url = {https://journals.scholarsportal.info/details/02779536/v282icomplete/nfp_fposarhim.xml}, + doi = {10.1016/j.socscimed.2021.113997}, + abstract = {Abstract Faith-based organisations constitute the second largest healthcare providers in Sub-Saharan Africa but their religious values might be in conflict with providing some sexual and reproductive health services. We undertake regression analysis on data detailing client-provider interactions from a facility census in Malawi and examine whether religious ownership of facilities is associated with the degree of adherence to family planning guidelines. We find that faith-based organisations offer fewer services related to the investigation and prevention of sexually transmitted infections (STIs) and the promotion of condom use. The estimates are robust to several sensitivity checks on the impact of client selection. Given the prevalence of faith-based facilities in Sub-Saharan Africa, our results suggest that populations across the region may be at risk from inadequate sexual and reproductive healthcare provision which could exacerbate the incidence of STIs, such as HIV/AIDS, and unplanned pregnancies. Highlights Investigates whether faith-based facilities provide fewer sexual health services. Uses data on client-provider interactions from a facility-level census from Malawi. Faith-based providers are less likely to investigate STIs and promote condoms. Results are robust to matching and are not driven by client selection.}, + number = {Complete}, + urldate = {2024-11-07}, + journal = {Social Science \& Medicine}, + author = {Tafesse, Wiktoria and Chalkley, Martin}, + year = {2021}, + note = {Publisher: Elsevier}, + keywords = {Faith-based providers, Healthcare, Healthcare provision, Least developed country, Ownership, Sexual and reproductive health}, +} + +@misc{li_lin_impact_2024, + address = {Rochester, NY}, + type = {{SSRN} {Scholarly} {Paper}}, + title = {The {Impact} and {Cost}-{Effectiveness} of {Pulse} {Oximetry} and {Oxygen} on {Acute} {Lower} {Respiratory} {Infection} {Outcomes} in {Children} {Under}-5 in {Malawi}: {A} {Modelling} {Study}}, + shorttitle = {The {Impact} and {Cost}-{Effectiveness} of {Pulse} {Oximetry} and {Oxygen} on {Acute} {Lower} {Respiratory} {Infection} {Outcomes} in {Children} {Under}-5 in {Malawi}}, + url = {https://papers.ssrn.com/abstract=4947417}, + doi = {10.2139/ssrn.4947417}, + abstract = {Background: Acute Lower Respiratory Infections (ALRI) are the leading cause of post-neonatal death in children under-5 globally. The impact, costs, and cost-effectiveness of routine pulse oximetry and oxygen on ALRI outcomes at scale remain unquantified. Methods: We evaluate the impact and cost-effectiveness of scaling up pulse oximetry and oxygen on childhood ALRI outcomes in Malawi using a new and detailed individual-based model, together with a comprehensive costing assessment for 2024 that includes both capital and operational expenditures. We model 15 scenarios ranging from no pulse oximetry or oxygen (null scenario) to high coverage (90\% pulse oximetry usage, and 80\% oxygen availability) across the health system. Cost-effectiveness results are presented in incremental cost-effectiveness ratio (ICER) and incremental net health benefit (INHB) using Malawi-specific cost-effectiveness threshold of \$80 per Disability-Adjusted Life Year (DALY) averted. Findings: The cost-effective strategy is the full scale-up of both pulse oximetry and oxygen to 90\% usage rate and 80\% availability, respectively. This combination results in 71\% of hypoxaemic ALRI cases accessing oxygen, averting 73,100 DALYs in the first year of implementation and 29\% of potential ALRI deaths, at an ICER of \$34 per DALY averted and \$894 per death averted. The INHB is 42,200 net DALYs averted. Interpretation: Pulse oximetry and oxygen are complementary cost-effective interventions in Malawi, where health expenditure is low, and should be scaled-up in parallel. Funding: UKRI, Wellcome Trust, DFID, EU, CHAI, Unitaid.Declaration of Interest: Besides funding from the Wellcome Trust and UK Research and Innovation going towards authors’ institutions, some authors took on private projects, outside the submitted work. ILL declares receiving consulting fees from ICDDR-B for her work for the Lancet Commission on Medical Oxygen Security related to this study. TC declares consulting fees donated to his institution from the Global Fund for related work, personal consulting fees from the UN Economic Commission for Africa, and non-paid work chairing a Trial Steering Committee for a trial of adolescent mental health interventions in Nepal. ANP declares receiving consulting fees from the Bill \& Melinda Gates Foundation. All other authors declare no competing interests.Ethical Approval: The Thanzi La Onse project received ethical approval from the College of Medicine Malawi Research Ethics Committee (COMREC, P.10/19/2820) in Malawi. Only anonymized secondary data are used in the Thanzi La Onse model including in the ALRI model used in this paper; therefore, individual informed consent was not required.}, + language = {en}, + urldate = {2024-11-10}, + publisher = {Social Science Research Network}, + author = {Li Lin, Ines and McCollum, Eric D. and Buckley, Eric Thomas and Cambiano, Valentina and Collins, Joseph H. and Graham, Matthew M. and Janoušková, Eva and King, Carina and Lufesi, Norman and Mangal, Tara Danielle and Mfutso-Bengo, Joseph Matthew and Mohan, Sakshi and Molaro, Margherita and Nkhoma, Dominic and Nsona, Humphreys and Rothkopf, Alexander and She, Bingling and Smith, Lisa and Tamuri, Asif U. and Revill, Paul and Phillips, Andrew N. and Hallett, Timothy B. and Colbourn, Tim}, + month = sep, + year = {2024}, + keywords = {Analyses using the model, Malawi, acute lower respiratory infections, cost-effectiveness, oxygen, pulse oximetry}, +} + +@article{tafesse_difference_2024, + title = {The difference in clinical knowledge between staff employed at faith-based and public facilities in {Malawi}}, + volume = {11}, + issn = {2167-2415}, + url = {https://cjgh.org/articles/10.15566/cjgh.v11i1.853}, + doi = {10.15566/cjgh.v11i1.853}, + abstract = {A peer-reviewed, scholarly, and multidisciplinary journal on global health policy and practice, promoting evidence-based and thoughtful analysis on effective and innovative approaches to global health from an integrated Christian perspective. The Journal publishes evidence-based research and Christian reflection addressing the biological, social, environmental, psychological, and spiritual determinants of health in a global context.\  The broad scope of the journal facilitates actionable learning and capacity building in development contexts within a scholarly framework.\  Topics include: Community and Public Health (Health Promotion/Prevention, Nutrition and Food Security, Environmental Health, Maternal and Child Health, Community Development) Health Care Services (Primary Health Care, Surgical Service, Disaster and Emergency, Rehabilitative services, Mental Health, Palliative Care) Organization (Administration and Finance, Policy and Advocacy, Workforce) Mission and Health (Theology, Outreach, Transformational Development) Conditions of Special Interest (HIV/AIDS, Non-Communicable Disease, Neglected Tropical Diseases)}, + language = {en-US}, + number = {1}, + urldate = {2024-11-07}, + journal = {Christian Journal for Global Health}, + author = {Tafesse, Wiktoria and Chalkley, Martin}, + month = feb, + year = {2024}, + keywords = {Healthcare provision}, +} + +@article{ngambi_cross-sectional_2020, + title = {A cross-sectional study on factors associated with health seeking behaviour of {Malawians} aged 15+ years in 2016}, + volume = {32}, + copyright = {Copyright (c) 2021}, + issn = {1995-7262}, + url = {https://www.ajol.info/index.php/mmj/article/view/202965}, + abstract = {IntroductionHealth seeking behaviour (HSB) refers to actions taken by individuals who are ill in order to find appropriate remedy. Most studies on HSB have only examined one symptom or covered only a specific geographical location within a country. In this study, we used a representative sample of adults to explore the factors associated with HSB in response to 30 symptoms reported by adult Malawians in 2016.MethodsWe used the 2016 Malawi Integrated Household Survey dataset. We fitted a multilevel logistic regression model of likelihood of ‘seeking care at a health facility’ using a forward step-wise selection method, with age, sex and reported symptoms entered as a priori variables. We calculated the odds ratios (ORs) and their associated 95\% confidence intervals (95\% CI). We set the level of statistical significance at P \< 0.05.Results Of 6909 adults included in the survey, 1907 (29\%) reported symptoms during the 2 weeks preceding the survey. Of these, 937 (57\%) sought care at a health facility. Adults in urban areas were more likely to seek health care at a health facility than those in rural areas (AOR = 1.65, 95\% CI: 1.19–2.30, P = 0.003). Females had a higher likelihood of seeking care from health facilities than males (AOR = 1.26, 95\% CI: 1.03–1.59, P = 0.029). Being of higher wealth status was associated with a higher likelihood of seeking care from a health facility (AOR = 1.58, 95\% CI: 1.16–2.16, P = 0.004). Having fever and eye problems were associated with higher likelihood of seeking care at a health facility, while having headache, stomach ache and respiratory tract infections were associated with lower likelihood of seeking care at a health facility.ConclusionThis study has shown that there is a need to understand and address individual, socioeconomic and geographical barriers to health seeking to increase access and appropriate use of health care and fast-track progress towards Universal Health Coverage among the adult population.}, + language = {en}, + number = {4}, + urldate = {2024-11-06}, + journal = {Malawi Medical Journal}, + author = {Ng'ambi, Wingston and Mangal, Tara and Phillips, Andrew and Colbourn, Tim and Nkhoma, Dominic and Bengo, Joseph Mfutso- and Revill, Paul and Hallett, Timothy B.}, + year = {2020}, + note = {Number: 4}, + keywords = {Health inequality, Healthcare seeking behaviour, Malawi, health seeking behaviour, integrated household survey}, + pages = {205--212}, +} + +@article{ngambi_socio-demographic_2022, + title = {Socio-demographic factors associated with early antenatal care visits among pregnant women in {Malawi}: 2004–2016}, + volume = {17}, + issn = {1932-6203}, + shorttitle = {Socio-demographic factors associated with early antenatal care visits among pregnant women in {Malawi}}, + url = {https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0263650}, + doi = {10.1371/journal.pone.0263650}, + abstract = {Introduction In 2016, the WHO published recommendations increasing the number of recommended antenatal care (ANC) visits per pregnancy from four to eight. Prior to the implementation of this policy, coverage of four ANC visits has been suboptimal in many low-income settings. In this study we explore socio-demographic factors associated with early initiation of first ANC contact and attending at least four ANC visits (“ANC4+”) in Malawi using the Malawi Demographic and Health Survey (MDHS) data collected between 2004 and 2016, prior to the implementation of new recommendations. Methods We combined data from the 2004–5, 2010 and 2015–16 MDHS using Stata version 16. Participants included all women surveyed between the ages of 15–49 who had given birth in the five years preceding the survey. We conducted weighted univariate, bivariate and multivariable logistic regression analysis of the effects of each of the predictor variables on the binary endpoint of the woman attending at least four ANC visits and having the first ANC attendance within or before the four months of pregnancy (ANC4+). To determine whether a factor was included in the model, the likelihood ratio test was used with a statistical significance of P{\textless} 0.05 as the threshold. Results We evaluated data collected in surveys in 2004/5, 2010 and 2015/6 from 26386 women who had given birth in the five years before being surveyed. The median gestational age, in months, at the time of presenting for the first ANC visit was 5 (inter quartile range: 4–6). The proportion of women initiating ANC4+ increased from 21.3\% in 2004–5 to 38.8\% in 2015–16. From multivariate analysis, there was increasing trend in ANC4+ from women aged 20–24 years (adjusted odds ratio (aOR) = 1.27, 95\%CI:1.05–1.53, P = 0.01) to women aged 45–49 years (aOR = 1.91, 95\%CI:1.18–3.09, P = 0.008) compared to those aged 15–19 years. Women from richest socio-economic position ((aOR = 1.32, 95\%CI:1.12–1.58, P{\textless}0.001) were more likely to demonstrate ANC4+ than those from low socio-economic position. Additionally, women who had completed secondary (aOR = 1.24, 95\%CI:1.02–1.51, P = 0.03) and tertiary (aOR = 2.64, 95\%CI:1.65–4.22, P{\textless}0.001) education were more likely to report having ANC4+ than those with no formal education. Conversely increasing parity was associated with a reduction in likelihood of ANC4+ with women who had previously delivered 2–3 (aOR = 0.74, 95\%CI:0.63–0.86, P{\textless}0.001), 4–5 (aOR = 0.65, 95\%CI:0.53–0.80, P{\textless}0.001) or greater than 6 (aOR = 0.61, 95\%CI: 0.47–0.79, {\textless}0.001) children being less likely to demonstrate ANC4+. Conclusion The proportion of women reporting ANC4+ and of key ANC interventions in Malawi have increased significantly since 2004. However, we found that most women did not access the recommended number of ANC visits in Malawi, prior to the 2016 WHO policy change which may mean that women are less likely to undertake the 2016 WHO recommendation of 8 contacts per pregnancy. Additionally, our results highlighted significant variation in coverage according to key socio-demographic variables which should be considered when devising national strategies to ensure that all women access the appropriate frequency of ANC visits during their pregnancy.}, + language = {en}, + number = {2}, + urldate = {2024-11-06}, + journal = {PLOS ONE}, + author = {Ng'ambi, Wingston Felix and Collins, Joseph H. and Colbourn, Tim and Mangal, Tara and Phillips, Andrew and Kachale, Fannie and Mfutso-Bengo, Joseph and Revill, Paul and Hallett, Timothy B.}, + month = feb, + year = {2022}, + note = {Publisher: Public Library of Science}, + keywords = {Age groups, Antenatal care, Children, Educational attainment, HIV, Healthcare seeking behaviour, Low income countries, Malawi, Pregnancy}, + pages = {e0263650}, +} + +@misc{hawryluk_potential_2020, + title = {The potential impact of including pre-school aged children in the praziquantel mass-drug administration programmes on the {S}.haematobium infections in {Malawi}: a modelling study}, + copyright = {© 2020, Posted by Cold Spring Harbor Laboratory. This pre-print is available under a Creative Commons License (Attribution-NonCommercial-NoDerivs 4.0 International), CC BY-NC-ND 4.0, as described at http://creativecommons.org/licenses/by-nc-nd/4.0/}, + shorttitle = {The potential impact of including pre-school aged children in the praziquantel mass-drug administration programmes on the {S}.haematobium infections in {Malawi}}, + url = {https://www.medrxiv.org/content/10.1101/2020.12.09.20246652v1}, + doi = {10.1101/2020.12.09.20246652}, + abstract = {Background Mass drug administration (MDA) of praziquantel is an intervention used in the treatment and prevention of schistosomiasis. In Malawi, MDA happens annually across high-risk districts and covers around 80\% of school aged children and 50\% of adults. The current formulation of praziquantel is not approved for use in the preventive chemotherapy for children under 5 years old, known as pre-school aged children (PSAC). However, a new formulation for PSAC will be available by 2022. A comprehensive analysis of the potential additional benefits of including PSAC in the MDA will be critical to guide policy-makers. +Methods We developed a new individual-based stochastic transmission model of Schistosoma haematobium for the 6 highest prevalence districts of Malawi. The model was used to evaluate the benefits of including PSAC in the MDA campaigns, with respect to the prevalence of high-intensity infections ({\textgreater} 500 eggs per ml of urine) and reaching the elimination target, meaning the prevalence of high-intensity infections under 5\% in all sentinel sites. The impact of different MDA frequencies and coverages is quantified by prevalence of high-intensity infection and number of rounds needed to decrease that prevalence below 1\%. +Results Including PSAC in the MDA campaigns can reduce the time needed to achieve the elimination target for S. haematobium infections in Malawi by one year. The modelling suggests that in the case of a lower threshold of high-intensity infection, currently set by WHO to 500 eggs per ml of urine, including PSAC in the preventive chemotherapy programmes for 5 years can reduce the number of the high-intensity infection case years for pre-school aged children by up to 9.1 years per 100 children. +Conclusions Regularly treating PSAC in the MDA is likely to lead to overall better health of children as well as a decrease in the severe morbidities caused by persistent schistosomiasis infections and bring forward the date of elimination. Moreover, mass administration of praziquantel to PSAC will decrease the prevalence among the SAC, who are at the most risk of infection.}, + language = {en}, + urldate = {2024-11-06}, + publisher = {medRxiv}, + author = {Hawryluk, Iwona and Mangal, Tara and Nguluwe, Andrew and Kambalame, Chikonzero and Banda, Stanley and Magaleta, Memory and Juziwelo, Lazarus and Hallett, Timothy B.}, + month = dec, + year = {2020}, + note = {Pages: 2020.12.09.20246652}, + keywords = {Analyses using the model}, +} + +@misc{molaro_potential_2024, + title = {The potential impact of declining development assistance for healthcare on population health: projections for {Malawi}}, + copyright = {© 2024, Posted by Cold Spring Harbor Laboratory. This pre-print is available under a Creative Commons License (Attribution 4.0 International), CC BY 4.0, as described at http://creativecommons.org/licenses/by/4.0/}, + shorttitle = {The potential impact of declining development assistance for healthcare on population health}, + url = {https://www.medrxiv.org/content/10.1101/2024.10.11.24315287v1}, + doi = {10.1101/2024.10.11.24315287}, + abstract = {Development assistance for health (DAH) to Malawi will likely decrease as a fraction of GDP in the next few decades. Given the country’s significant reliance on DAH for the delivery of its healthcare services, estimating the impact that this could have on health projections for the country is particularly urgent. We use the Malawi-specific, individual-based “all diseases – whole health-system” Thanzi La Onse model to estimate the impact this could have on health system capacities, proxied by the availability of human resources for health, and consequently on population health outcomes. We estimate that the projected changes in DAH could result in a 7-15.8\% increase in disability-adjusted life years compared to a scenario where health spending as a percentage of GDP remains unchanged. This could cause a reversal of gains achieved to date in many areas of health, although progress against HIV/AIDS appears to be less vulnerable. The burden due to non-communicable diseases, on the other hand, is found to increase irrespective of yearly growth in health expenditure, if assuming current reach and scope of interventions. Finally, we find that greater health expenditure will improve population health outcomes, but at a diminishing rate.}, + language = {en}, + urldate = {2024-11-06}, + publisher = {medRxiv}, + author = {Molaro, Margherita and Revill, Paul and Chalkley, Martin and Mohan, Sakshi and Mangal, Tara and Colbourn, Tim and Collins, Joseph H. and Graham, Matthew M. and Graham, William and Janoušková, Eva and Manthalu, Gerald and Mnjowe, Emmanuel and Mulwafu, Watipaso and Murray-Watson, Rachel and Twea, Pakwanja D. and Phillips, Andrew N. and She, Bingling and Tamuri, Asif U. and Nkhoma, Dominic and Mfutso-Bengo, Joseph and Hallett, Timothy B.}, + month = oct, + year = {2024}, + note = {Pages: 2024.10.11.24315287}, + keywords = {Analyses using the model}, +} + +@article{she_changes_2024, + title = {The changes in health service utilisation in {Malawi} during the {COVID}-19 pandemic}, + volume = {19}, + issn = {1932-6203}, + url = {https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0290823}, + doi = {10.1371/journal.pone.0290823}, + abstract = {Introduction The COVID-19 pandemic and the restriction policies implemented by the Government of Malawi may have disrupted routine health service utilisation. We aimed to find evidence for such disruptions and quantify any changes by service type and level of health care. Methods We extracted nationwide routine health service usage data for 2015–2021 from the electronic health information management systems in Malawi. Two datasets were prepared: unadjusted and adjusted; for the latter, unreported monthly data entries for a facility were filled in through systematic rules based on reported mean values of that facility or facility type and considering both reporting rates and comparability with published data. Using statistical descriptive methods, we first described the patterns of service utilisation in pre-pandemic years (2015–2019). We then tested for evidence of departures from this routine pattern, i.e., service volume delivered being below recent average by more than two standard deviations was viewed as a substantial reduction, and calculated the cumulative net differences of service volume during the pandemic period (2020–2021), in aggregate and within each specific facility. Results Evidence of disruptions were found: from April 2020 to December 2021, services delivered of several types were reduced across primary and secondary levels of care–including inpatient care (-20.03\% less total interactions in that period compared to the recent average), immunisation (-17.61\%), malnutrition treatment (-34.5\%), accidents and emergency services (-16.03\%), HIV (human immunodeficiency viruses) tests (-27.34\%), antiretroviral therapy (ART) initiations for adults (-33.52\%), and ART treatment for paediatrics (-41.32\%). Reductions of service volume were greatest in the first wave of the pandemic during April-August 2020, and whereas some service types rebounded quickly (e.g., outpatient visits from -17.7\% to +3.23\%), many others persisted at lower level through 2021 (e.g., under-five malnutrition treatment from -15.24\% to -42.23\%). The total reduced service volume between April 2020 and December 2021 was 8 066 956 (-10.23\%), equating to 444 units per 1000 persons. Conclusion We have found substantial evidence for reductions in health service delivered in Malawi during the COVID-19 pandemic which may have potential health consequences, the effect of which should inform how decisions are taken in the future to maximise the resilience of healthcare system during similar events.}, + language = {en}, + number = {1}, + urldate = {2024-11-06}, + journal = {PLOS ONE}, + author = {She, Bingling and Mangal, Tara D. and Adjabeng, Anna Y. and Colbourn, Tim and Collins, Joseph H. and Janoušková, Eva and Lin, Ines Li and Mnjowe, Emmanuel and Mohan, Sakshi and Molaro, Margherita and Phillips, Andrew N. and Revill, Paul and Smith, Robert Manning and Twea, Pakwanja D. and Nkhoma, Dominic and Manthalu, Gerald and Hallett, Timothy B.}, + month = jan, + year = {2024}, + note = {Publisher: Public Library of Science}, + keywords = {Analyses using the model, Antiretroviral therapy, COVID 19, HIV, HIV vaccines, Health care facilities, Malawi, Pandemics, Virus testing}, + pages = {e0290823}, +} + +@article{mangal_potential_2021, + title = {Potential impact of intervention strategies on {COVID}-19 transmission in {Malawi}: a mathematical modelling study}, + volume = {11}, + copyright = {© Author(s) (or their employer(s)) 2021. Re-use permitted under CC BY. Published by BMJ.. https://creativecommons.org/licenses/by/4.0/This is an open access article distributed in accordance with the Creative Commons Attribution 4.0 Unported (CC BY 4.0) license, which permits others to copy, redistribute, remix, transform and build upon this work for any purpose, provided the original work is properly cited, a link to the licence is given, and indication of whether changes were made. See: https://creativecommons.org/licenses/by/4.0/.}, + issn = {2044-6055, 2044-6055}, + shorttitle = {Potential impact of intervention strategies on {COVID}-19 transmission in {Malawi}}, + url = {https://bmjopen.bmj.com/content/11/7/e045196}, + doi = {10.1136/bmjopen-2020-045196}, + abstract = {Background COVID-19 mitigation strategies have been challenging to implement in resource-limited settings due to the potential for widespread disruption to social and economic well-being. Here we predict the clinical severity of COVID-19 in Malawi, quantifying the potential impact of intervention strategies and increases in health system capacity. +Methods The infection fatality ratios (IFR) were predicted by adjusting reported IFR for China, accounting for demography, the current prevalence of comorbidities and health system capacity. These estimates were input into an age-structured deterministic model, which simulated the epidemic trajectory with non-pharmaceutical interventions and increases in health system capacity. +Findings The predicted population-level IFR in Malawi, adjusted for age and comorbidity prevalence, is lower than that estimated for China (0.26\%, 95\% uncertainty interval (UI) 0.12\%–0.69\%, compared with 0.60\%, 95\% CI 0.4\% to 1.3\% in China); however, the health system constraints increase the predicted IFR to 0.83\%, 95\% UI 0.49\%–1.39\%. The interventions implemented in January 2021 could potentially avert 54 400 deaths (95\% UI 26 900–97 300) over the course of the epidemic compared with an unmitigated outbreak. Enhanced shielding of people aged ≥60 years could avert 40 200 further deaths (95\% UI 25 300–69 700) and halve intensive care unit admissions at the peak of the outbreak. A novel therapeutic agent which reduces mortality by 0.65 and 0.8 for severe and critical cases, respectively, in combination with increasing hospital capacity, could reduce projected mortality to 2.5 deaths per 1000 population (95\% UI 1.9–3.6). +Conclusion We find the interventions currently used in Malawi are unlikely to effectively prevent SARS-CoV-2 transmission but will have a significant impact on mortality. Increases in health system capacity and the introduction of novel therapeutics are likely to further reduce the projected numbers of deaths.}, + language = {en}, + number = {7}, + urldate = {2024-11-06}, + journal = {BMJ Open}, + author = {Mangal, Tara and Whittaker, Charlie and Nkhoma, Dominic and Ng'ambi, Wingston and Watson, Oliver and Walker, Patrick and Ghani, Azra and Revill, Paul and Colbourn, Timothy and Phillips, Andrew and Hallett, Timothy and Mfutso-Bengo, Joseph}, + month = jul, + year = {2021}, + pmid = {34301651}, + note = {Publisher: British Medical Journal Publishing Group +Section: Epidemiology}, + keywords = {Analyses using the model, COVID-19, epidemiology, infection control, public health}, + pages = {e045196}, +} + +@article{she_health_2024, + title = {Health workforce needs in {Malawi}: analysis of the {Thanzi} {La} {Onse} integrated epidemiological model of care}, + volume = {22}, + issn = {1478-4491}, + shorttitle = {Health workforce needs in {Malawi}}, + url = {https://doi.org/10.1186/s12960-024-00949-2}, + doi = {10.1186/s12960-024-00949-2}, + abstract = {To make the best use of health resources, it is crucial to understand the healthcare needs of a population—including how needs will evolve and respond to changing epidemiological context and patient behaviour—and how this compares to the capabilities to deliver healthcare with the existing workforce. Existing approaches to planning either rely on using observed healthcare demand from a fixed historical period or using models to estimate healthcare needs within a narrow domain (e.g., a specific disease area or health programme). A new data-grounded modelling method is proposed by which healthcare needs and the capabilities of the healthcare workforce can be compared and analysed under a range of scenarios: in particular, when there is much greater propensity for healthcare seeking.}, + number = {1}, + urldate = {2024-11-06}, + journal = {Human Resources for Health}, + author = {She, Bingling and Mangal, Tara D. and Prust, Margaret L. and Heung, Stephanie and Chalkley, Martin and Colbourn, Tim and Collins, Joseph H. and Graham, Matthew M. and Jewell, Britta and Joshi, Purava and Li Lin, Ines and Mnjowe, Emmanuel and Mohan, Sakshi and Molaro, Margherita and Phillips, Andrew N. and Revill, Paul and Smith, Robert Manning and Tamuri, Asif U. and Twea, Pakwanja D. and Manthalu, Gerald and Mfutso-Bengo, Joseph and Hallett, Timothy B.}, + month = sep, + year = {2024}, + keywords = {Analyses using the model, Health care needs, Health services, Health system interactions, Healthcare workforce, Model design}, + pages = {66}, +} + +@article{mohan_factors_2024, + title = {Factors associated with medical consumable availability in level 1 facilities in {Malawi}: a secondary analysis of a facility census}, + volume = {12}, + issn = {2214-109X}, + shorttitle = {Factors associated with medical consumable availability in level 1 facilities in {Malawi}}, + url = {https://www.sciencedirect.com/science/article/pii/S2214109X24000950}, + doi = {10.1016/S2214-109X(24)00095-0}, + abstract = {Background +Medical consumable stock-outs negatively affect health outcomes not only by impeding or delaying the effective delivery of services but also by discouraging patients from seeking care. Consequently, supply chain strengthening is being adopted as a key component of national health strategies. However, evidence on the factors associated with increased consumable availability is limited. +Methods +In this study, we used the 2018–19 Harmonised Health Facility Assessment data from Malawi to identify the factors associated with the availability of consumables in level 1 facilities, ie, rural hospitals or health centres with a small number of beds and a sparsely equipped operating room for minor procedures. We estimate a multilevel logistic regression model with a binary outcome variable representing consumable availability (of 130 consumables across 940 facilities) and explanatory variables chosen based on current evidence. Further subgroup analyses are carried out to assess the presence of effect modification by level of care, facility ownership, and a categorisation of consumables by public health or disease programme, Malawi's Essential Medicine List classification, whether the consumable is a drug or not, and level of average national availability. +Findings +Our results suggest that the following characteristics had a positive association with consumable availability—level 1b facilities or community hospitals had 64\% (odds ratio [OR] 1·64, 95\% CI 1·37–1·97) higher odds of consumable availability than level 1a facilities or health centres, Christian Health Association of Malawi and private-for-profit ownership had 63\% (1·63, 1·40–1·89) and 49\% (1·49, 1·24–1·80) higher odds respectively than government-owned facilities, the availability of a computer had 46\% (1·46, 1·32–1·62) higher odds than in its absence, pharmacists managing drug orders had 85\% (1·85, 1·40–2·44) higher odds than a drug store clerk, proximity to the corresponding regional administrative office (facilities greater than 75 km away had 21\% lower odds [0·79, 0·63–0·98] than facilities within 10 km of the district health office), and having three drug order fulfilments in the 3 months before the survey had 14\% (1·14, 1·02–1·27) higher odds than one fulfilment in 3 months. Further, consumables categorised as vital in Malawi's Essential Medicine List performed considerably better with 235\% (OR 3·35, 95\% CI 1·60–7·05) higher odds than other essential or non-essential consumables and drugs performed worse with 79\% (0·21, 0·08–0·51) lower odds than other medical consumables in terms of availability across facilities. +Interpretation +Our results provide evidence on the areas of intervention with potential to improve consumable availability. Further exploration of the health and resource consequences of the strategies discussed will be useful in guiding investments into supply chain strengthening. +Funding +UK Research and Innovation as part of the Global Challenges Research Fund (Thanzi La Onse; reference MR/P028004/1), the Wellcome Trust (Thanzi La Mawa; reference 223120/Z/21/Z), the UK Medical Research Council, the UK Department for International Development, and the EU (reference MR/R015600/1).}, + number = {6}, + urldate = {2024-11-06}, + journal = {The Lancet Global Health}, + author = {Mohan, Sakshi and Mangal, Tara D and Colbourn, Tim and Chalkley, Martin and Chimwaza, Chikhulupiliro and Collins, Joseph H and Graham, Matthew M and Janoušková, Eva and Jewell, Britta and Kadewere, Godfrey and Li Lin, Ines and Manthalu, Gerald and Mfutso-Bengo, Joseph and Mnjowe, Emmanuel and Molaro, Margherita and Nkhoma, Dominic and Revill, Paul and She, Bingling and Manning Smith, Robert and Tafesse, Wiktoria and Tamuri, Asif U and Twea, Pakwanja and Phillips, Andrew N and Hallett, Timothy B}, + month = jun, + year = {2024}, + keywords = {Analyses using the model}, + pages = {e1027--e1037}, +} + +@article{ngambi_factors_2020, + title = {Factors associated with healthcare seeking behaviour for children in {Malawi}: 2016}, + volume = {25}, + copyright = {© 2020 John Wiley \& Sons Ltd}, + issn = {1365-3156}, + shorttitle = {Factors associated with healthcare seeking behaviour for children in {Malawi}}, + url = {https://onlinelibrary.wiley.com/doi/abs/10.1111/tmi.13499}, + doi = {10.1111/tmi.13499}, + abstract = {Objective To characterise health seeking behaviour (HSB) and determine its predictors amongst children in Malawi in 2016. Methods We used the 2016 Malawi Integrated Household Survey data set. The outcome of interest was HSB, defined as seeking care at a health facility amongst people who reported one or more of a list of possible symptoms given on the questionnaire in the past two weeks. We fitted a multivariate logistic regression model of HSB using a forward step-wise selection method, with age, sex and symptoms entered as a priori variables. Results Of 5350 children, 1666 (32\%) had symptoms in the past two weeks. Of the 1666, 1008 (61\%) sought care at health facility. The children aged 5–14 years were less likely to be taken to health facilities for health care than those aged 0–4 years. Having fever vs. not having fever and having a skin problem vs. not having skin problem were associated with increased likelihood of HSB. Having a headache vs. not having a headache was associated with lower likelihood of accessing care at health facilities (AOR = 0.50, 95\% CI: 0.26–0.96, P = 0.04). Children from urban areas were more likely to be taken to health facilities for health care (AOR = 1.81, 95\% CI: 1.17–2.85, P = 0.008), as were children from households with a high wealth status (AOR = 1.86, 95\% CI: 1.25–2.78, P = 0.02). Conclusion There is a need to understand and address individual, socio-economic and geographical barriers to health seeking to increase access and use of health care and fast-track progress towards Universal Health Coverage.}, + language = {en}, + number = {12}, + urldate = {2024-11-06}, + journal = {Tropical Medicine \& International Health}, + author = {Ng'ambi, Wingston and Mangal, Tara and Phillips, Andrew and Colbourn, Tim and Mfutso-Bengo, Joseph and Revill, Paul and Hallett, Timothy B.}, + year = {2020}, + note = {\_eprint: https://onlinelibrary.wiley.com/doi/pdf/10.1111/tmi.13499}, + keywords = {Healthcare seeking behaviour, Malawi, determinants of health, healthcare seeking behaviour}, + pages = {1486--1495}, +} + +@article{manning_smith_estimating_2022, + title = {Estimating the health burden of road traffic injuries in {Malawi} using an individual-based model}, + volume = {9}, + issn = {2197-1714}, + url = {https://doi.org/10.1186/s40621-022-00386-6}, + doi = {10.1186/s40621-022-00386-6}, + abstract = {Road traffic injuries are a significant cause of death and disability globally. However, in some countries the exact health burden caused by road traffic injuries is unknown. In Malawi, there is no central reporting mechanism for road traffic injuries and so the exact extent of the health burden caused by road traffic injuries is hard to determine. A limited number of models predict the incidence of mortality due to road traffic injury in Malawi. These estimates vary greatly, owing to differences in assumptions, and so the health burden caused on the population by road traffic injuries remains unclear.}, + number = {1}, + urldate = {2024-11-06}, + journal = {Injury Epidemiology}, + author = {Manning Smith, Robert and Cambiano, Valentina and Colbourn, Tim and Collins, Joseph H. and Graham, Matthew and Jewell, Britta and Li Lin, Ines and Mangal, Tara D. and Manthalu, Gerald and Mfutso-Bengo, Joseph and Mnjowe, Emmanuel and Mohan, Sakshi and Ng’ambi, Wingston and Phillips, Andrew N. and Revill, Paul and She, Bingling and Sundet, Mads and Tamuri, Asif and Twea, Pakwanja D. and Hallet, Timothy B.}, + month = jul, + year = {2022}, + keywords = {Analyses using the model, Health burden, Individual-based model, Malawi, Road traffic injuries}, + pages = {21}, +} + +@article{mangal_assessing_2024, + title = {Assessing the effect of health system resources on {HIV} and tuberculosis programmes in {Malawi}: a modelling study}, + volume = {12}, + issn = {2214-109X}, + shorttitle = {Assessing the effect of health system resources on {HIV} and tuberculosis programmes in {Malawi}}, + url = {https://www.sciencedirect.com/science/article/pii/S2214109X24002596}, + doi = {10.1016/S2214-109X(24)00259-6}, + abstract = {Background +Malawi is progressing towards UNAIDS and WHO End TB Strategy targets to eliminate HIV/AIDS and tuberculosis. We aimed to assess the prospective effect of achieving these goals on the health and health system of the country and the influence of consumable constraints. +Methods +In this modelling study, we used the Thanzi la Onse (Health for All) model, which is an individual-based multi-disease simulation model that simulates HIV and tuberculosis transmission, alongside other diseases (eg, malaria, non-communicable diseases, and maternal diseases), and gates access to essential medicines according to empirical estimates of availability. The model integrates dynamic disease modelling with health system engagement behaviour, health system use, and capabilities (ie, personnel and consumables). We used 2018 data on the availability of HIV and tuberculosis consumables (for testing, treatment, and prevention) across all facility levels of the country to model three scenarios of HIV and tuberculosis programme scale-up from Jan 1, 2023, to Dec 31, 2033: a baseline scenario, when coverage remains static using existing consumable constraints; a constrained scenario, in which prioritised interventions are scaled up with fixed consumable constraints; and an unconstrained scenario, in which prioritised interventions are scaled up with maximum availability of all consumables related to HIV and tuberculosis care. +Findings +With uninterrupted medical supplies, in Malawi, we projected HIV and tuberculosis incidence to decrease to 26 (95\% uncertainty interval [UI] 19–35) cases and 55 (23–74) cases per 100 000 person-years by 2033 (from 152 [98–195] cases and 123 [99–160] cases per 100 000 person-years in 2023), respectively, with programme scale-up, averting a total of 12·21 million (95\% UI 11·39–14·16) disability-adjusted life-years. However, the effect was compromised by restricted access to key medicines, resulting in approximately 58 700 additional deaths (33 400 [95\% UI 22 000–41 000] due to AIDS and 25 300 [19 300–30 400] due to tuberculosis) compared with the unconstrained scenario. Between 2023 and 2033, eliminating HIV treatment stockouts could avert an estimated 12 100 deaths compared with the baseline scenario, and improved access to tuberculosis prevention medications could prevent 5600 deaths in addition to those achieved through programme scale-up alone. With programme scale-up under the constrained scenario, consumable stockouts are projected to require an estimated 14·3 million extra patient-facing hours between 2023 and 2033, mostly from clinical or nursing staff, compared with the unconstrained scenario. In 2033, with enhanced screening, 188 000 (81\%) of 232 900 individuals projected to present with active tuberculosis could start tuberculosis treatment within 2 weeks of initial presentation if all required consumables were available, but only 8600 (57\%) of 15 100 presenting under the baseline scenario. +Interpretation +Ignoring frailties in the health-care system, in particular the potential non-availability of consumables, in projections of HIV and tuberculosis programme scale-up might risk overestimating potential health impacts and underestimating required health system resources. Simultaneous health system strengthening alongside programme scale-up is crucial, and should yield greater benefits to population health while mitigating the strain on a heavily constrained health-care system. +Funding +Wellcome and UK Research and Innovation as part of the Global Challenges Research Fund.}, + number = {10}, + urldate = {2024-11-06}, + journal = {The Lancet Global Health}, + author = {Mangal, Tara D and Mohan, Sakshi and Colbourn, Timothy and Collins, Joseph H and Graham, Mathew and Jahn, Andreas and Janoušková, Eva and Lin, Ines Li and Smith, Robert Manning and Mnjowe, Emmanuel and Molaro, Margherita and Mwenyenkulu, Tisungane E and Nkhoma, Dominic and She, Bingling and Tamuri, Asif and Revill, Paul and Phillips, Andrew N and Mfutso-Bengo, Joseph and Hallett, Timothy B}, + month = oct, + year = {2024}, + keywords = {Analyses using the model}, + pages = {e1638--e1648}, +} + +@article{molaro_new_2024, + title = {A new approach to {Health} {Benefits} {Package} design: an application of the {Thanzi} {La} {Onse} model in {Malawi}}, + volume = {20}, + issn = {1553-7358}, + shorttitle = {A new approach to {Health} {Benefits} {Package} design}, + url = {https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1012462}, + doi = {10.1371/journal.pcbi.1012462}, + abstract = {An efficient allocation of limited resources in low-income settings offers the opportunity to improve population-health outcomes given the available health system capacity. Efforts to achieve this are often framed through the lens of “health benefits packages” (HBPs), which seek to establish which services the public healthcare system should include in its provision. Analytic approaches widely used to weigh evidence in support of different interventions and inform the broader HBP deliberative process however have limitations. In this work, we propose the individual-based Thanzi La Onse (TLO) model as a uniquely-tailored tool to assist in the evaluation of Malawi-specific HBPs while addressing these limitations. By mechanistically modelling—and calibrating to extensive, country-specific data—the incidence of disease, health-seeking behaviour, and the capacity of the healthcare system to meet the demand for care under realistic constraints on human resources for health available, we were able to simulate the health gains achievable under a number of plausible HBP strategies for the country. We found that the HBP emerging from a linear constrained optimisation analysis (LCOA) achieved the largest health gain—∼8\% reduction in disability adjusted life years (DALYs) between 2023 and 2042 compared to the benchmark scenario—by concentrating resources on high-impact treatments. This HBP however incurred a relative excess in DALYs in the first few years of its implementation. Other feasible approaches to prioritisation were assessed, including service prioritisation based on patient characteristics, rather than service type. Unlike the LCOA-based HBP, this approach achieved consistent health gains relative to the benchmark scenario on a year- to-year basis, and a 5\% reduction in DALYs over the whole period, which suggests an approach based upon patient characteristics might prove beneficial in the future.}, + language = {en}, + number = {9}, + urldate = {2024-11-06}, + journal = {PLOS Computational Biology}, + author = {Molaro, Margherita and Mohan, Sakshi and She, Bingling and Chalkley, Martin and Colbourn, Tim and Collins, Joseph H. and Connolly, Emilia and Graham, Matthew M. and Janoušková, Eva and Lin, Ines Li and Manthalu, Gerald and Mnjowe, Emmanuel and Nkhoma, Dominic and Twea, Pakwanja D. and Phillips, Andrew N. and Revill, Paul and Tamuri, Asif U. and Mfutso-Bengo, Joseph and Mangal, Tara D. and Hallett, Timothy B.}, + month = sep, + year = {2024}, + note = {Publisher: Public Library of Science}, + keywords = {Analyses using the model, Child and adolescent health policy, Epidemiology, HIV, Health care facilities, Health care policy, Health systems strengthening, Malawi, Medical risk factors}, + pages = {e1012462}, +} + +@misc{mangal_decade_2024, + title = {A {Decade} of {Progress} in {HIV}, {Malaria}, and {Tuberculosis} {Initiatives} in {Malawi}}, + copyright = {© 2024, Posted by Cold Spring Harbor Laboratory. This pre-print is available under a Creative Commons License (Attribution 4.0 International), CC BY 4.0, as described at http://creativecommons.org/licenses/by/4.0/}, + url = {https://www.medrxiv.org/content/10.1101/2024.10.08.24315077v1}, + doi = {10.1101/2024.10.08.24315077}, + abstract = {Objective Huge investments in HIV, TB, and malaria (HTM) control in Malawi have greatly reduced disease burden. However, the joint impact of these services across multiple health domains and the health system resources required to deliver them are not fully understood. +Methods An integrated epidemiological and health system model was used to assess the impact of HTM programmes in Malawi from 2010 to 2019, incorporating interacting disease dynamics, intervention effects, and health system usage. Four scenarios were examined, comparing actual programme delivery with hypothetical scenarios excluding programmes individually and collectively. +Findings From 2010-2019, HTM programmes were estimated to have prevented 1.08 million deaths and 74.89 million DALYs. An additional 15,600 deaths from other causes were also prevented. Life expectancy increased by 13.0 years for males and 16.9 years for females.The HTM programmes accounted for 24.2\% of all health system interactions, including 157.0 million screening/diagnostic tests and 23.2 million treatment appointments. Accounting for the anticipated health deterioration without HTM services, only 41.55 million additional healthcare worker hours were required (17.1\% of total healthcare worker time) to achieve these gains. The HTM programme eliminated the need for 123 million primary care appointments, offset by a net increase in inpatient care demand (9.4 million bed-days) that would have been necessary in its absence. +Conclusions HTM programmes have greatly increased life expectancy, providing direct and spillover effects on health. These investments have alleviated the burden on inpatient and emergency care, which requires more intensive healthcare provider involvement.}, + language = {en}, + urldate = {2024-11-06}, + publisher = {medRxiv}, + author = {Mangal, Tara Danielle and Molaro, Margherita and Nkhoma, Dominic and Colbourn, Timothy and Collins, Joseph H. and Janoušková, Eva and Graham, Matthew M. and Lin, Ines Li and Mnjowe, Emmanuel and Mwenyenkulu, Tisungane E. and Mohan, Sakshi and She, Bingling and Tamuri, Asif U. and Twea, Pakwanja D. and Winskill, Peter and Phillips, Andrew and Mfutso-Bengo, Joseph and Hallett, Timothy B.}, + month = oct, + year = {2024}, + note = {Pages: 2024.10.08.24315077}, + keywords = {Analyses using the model}, +} + +@article{colbourn_modeling_2023, + title = {Modeling {Contraception} and {Pregnancy} in {Malawi}: {A} {Thanzi} {La} {Onse} {Mathematical} {Modeling} {Study}}, + volume = {54}, + copyright = {© 2023 The Authors. Studies in Family Planning published by Wiley Periodicals LLC on behalf of Population Council.}, + issn = {1728-4465}, + shorttitle = {Modeling {Contraception} and {Pregnancy} in {Malawi}}, + url = {https://onlinelibrary.wiley.com/doi/abs/10.1111/sifp.12255}, + doi = {10.1111/sifp.12255}, + abstract = {Malawi has high unmet need for contraception with a costed national plan to increase contraception use. Estimating how such investments might impact future population size in Malawi can help policymakers understand effects and value of policies to increase contraception uptake. We developed a new model of contraception and pregnancy using individual-level data capturing complexities of contraception initiation, switching, discontinuation, and failure by contraception method, accounting for differences by individual characteristics. We modeled contraception scale-up via a population campaign to increase initiation of contraception (Pop) and a postpartum family planning intervention (PPFP). We calibrated the model without new interventions to the UN World Population Prospects 2019 medium variant projection of births for Malawi. Without interventions Malawi's population passes 60 million in 2084; with Pop and PPFP interventions. it peaks below 35 million by 2100. We compare contraception coverage and costs, by method, with and without interventions, from 2023 to 2050. We estimate investments in contraception scale-up correspond to only 0.9 percent of total health expenditure per capita though could result in dramatic reductions of current pressures of very rapid population growth on health services, schools, land, and society, helping Malawi achieve national and global health and development goals.}, + language = {en}, + number = {4}, + urldate = {2024-11-06}, + journal = {Studies in Family Planning}, + author = {Colbourn, Tim and Janoušková, Eva and Li Lin, Ines and Collins, Joseph and Connolly, Emilia and Graham, Matt and Jewel, Britta and Kachale, Fannie and Mangal, Tara and Manthalu, Gerald and Mfutso-Bengo, Joseph and Mnjowe, Emmanuel and Mohan, Sakshi and Molaro, Margherita and Ng'ambi, Wingston and Nkhoma, Dominic and Revill, Paul and She, Bingling and Manning Smith, Robert and Twea, Pakwanja and Tamuri, Asif and Phillips, Andrew and Hallett, Timothy B.}, + year = {2023}, + note = {\_eprint: https://onlinelibrary.wiley.com/doi/pdf/10.1111/sifp.12255}, + keywords = {Analyses using the model}, + pages = {585--607}, +} diff --git a/docs/publications.rst b/docs/publications.rst index e4b6e473bc..388567208b 100644 --- a/docs/publications.rst +++ b/docs/publications.rst @@ -1,57 +1,10 @@ - ============= Publications ============= These are the publications that have been generated either in the course of the model's development or its application. +:download:`Download a BibTeX file for all publications <./publications.bib>` -Overview of the Model -====================== - -* `A Healthcare Service Delivery and Epidemiological Model for Investigating Resource Allocation for Health: The Thanzi La Onse Model `_ - - -Analyses Using The Model -======================== -* `Health workforce needs in Malawi: analysis of the Thanzi La Onse integrated epidemiological model of care `_ - -* `A new approach to Health Benefits Package design: an application of the Thanzi La Onse model in Malawi `_ - -* `The Changes in Health Service Utilisation in Malawi During the COVID-19 Pandemic `_ - -* `Modeling Contraception and Pregnancy in Malawi: A Thanzi La Onse Mathematical Modeling Study `_ - -* `Factors Associated with Consumable Stock-Outs in Malawi: Evidence from a Facility Census `_ - -* `The Effects of Health System Frailties on the Projected Impact of the HIV and TB Programmes in Malawi `_ - -* `Estimating the health burden of road traffic injuries in Malawi using an individual-based model `_ - -* `The potential impact of intervention strategies on COVID-19 transmission in Malawi: A mathematical modelling study. `_ - -* `The potential impact of including pre-school aged children in the praziquantel mass-drug administration programmes on the S.haematobium infections in Malawi: a modelling study `_ - -* `A Decade of Progress in HIV, Malaria, and Tuberculosis Initiatives in Malawi. `_ - - -Healthcare Seeking Behaviour -============================ - -* `Socio-demographic factors associated with early antenatal care visits among pregnant women in Malawi: 2004–2016 `_ - -* `Factors associated with healthcare seeking behaviour for children in Malawi: 2016. `_ - -* `A cross-sectional study on factors associated with health seeking behaviour of Malawians aged 15+ years in 2016. `_ - - - - - - - - - - - - +.. raw:: html + :file: _publications_list.html diff --git a/docs/requirements.txt b/docs/requirements.txt index e891488c86..68751f2178 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,6 @@ sphinx>=1.3 sphinx-rtd-theme +pybtex pyyaml +requests tabulate diff --git a/docs/tlo_publications.py b/docs/tlo_publications.py new file mode 100644 index 0000000000..f810f08e5e --- /dev/null +++ b/docs/tlo_publications.py @@ -0,0 +1,249 @@ +"""Create publications page from BibTeX database file.""" + +import argparse +import calendar +from collections import defaultdict +from pathlib import Path +from warnings import warn + +import pybtex.database +import requests +from pybtex.backends.html import Backend as HTMLBackend +from pybtex.style.formatting import toplevel +from pybtex.style.formatting.unsrt import Style as UnsrtStyle +from pybtex.style.formatting.unsrt import date as publication_date +from pybtex.style.names import BaseNameStyle, name_part +from pybtex.style.sorting import BaseSortingStyle +from pybtex.style.template import ( + FieldIsMissing, + field, + first_of, + href, + join, + node, + optional, + sentence, + tag, + words, +) + + +class InlineHTMLBackend(HTMLBackend): + """Backend for bibliography output as plain list suitable for inclusion in a HTML document.""" + + def write_prologue(self): + self.output("
    \n") + + def write_epilogue(self): + self.output("
\n") + + def write_entry(self, _key, _label, text): + self.output(f"
  • {text}
  • \n") + + +class DateSortingStyle(BaseSortingStyle): + """Sorting style for bibliography in reverse (newest first) publication date order.""" + + def sorting_key(self, entry): + months = list(calendar.month_name) + return ( + -int(entry.fields.get("year")), + -months.index(entry.fields.get("month", "")), + entry.fields.get("title", ""), + ) + + +class LastOnlyNameStyle(BaseNameStyle): + """Name style showing only last names and associated name particles.""" + + def format(self, person, _abbr=False): + return join[ + name_part(tie=True)[person.rich_prelast_names], + name_part[person.rich_last_names], + name_part(before=", ")[person.rich_lineage_names], + ] + + +@node +def summarized_names(children, context, role, summarize_limit=3, **kwargs): + """Return formatted names with et al. summarization when number exceeds specified limit.""" + + assert not children + + try: + persons = context["entry"].persons[role] + except KeyError: + raise FieldIsMissing(role, context["entry"]) + + name_style = LastOnlyNameStyle() + if len(persons) > summarize_limit: + return words[name_style.format(persons[0]), "et al."].format_data(context) + else: + formatted_names = [name_style.format(person) for person in persons] + return join(**kwargs)[formatted_names].format_data(context) + + +class SummarizedStyle(UnsrtStyle): + """ + Bibliography style showing summarized names, year, title and journal with expandable details. + + Not suitable for use with LaTeX backend due to use of details tags. + """ + + default_sorting_style = DateSortingStyle + + def _format_summarized_names(self, role): + return summarized_names(role, sep=", ", sep2=" and ", last_sep=", and ") + + def _format_label(self, label): + return tag("em")[f"{label}: "] + + def _format_details_as_table(self, details): + return tag("table")[ + toplevel[ + *( + tag("tr")[toplevel[tag("td")[tag("em")[key]], tag("td")[value]]] + for key, value in details.items() + ) + ] + ] + + def _get_summary_template(self, e, type_): + bibtex_type_to_venue_field = {"article": "journal", "misc": "publisher", "inproceedings": "booktitle"} + venue_field = bibtex_type_to_venue_field[type_] + url = first_of[ + optional[join["https://doi.org/", field("doi", raw=True)]], + optional[field("url", raw=True)], + "#", + ] + return href[ + url, + sentence(sep=". ")[ + words[ + self._format_summarized_names("author"), + optional["(", field("year"), ")"], + ], + self.format_title(e, "title", as_sentence=False), + tag("em")[field(venue_field)], + ], + ] + + def _get_details_template(self, type_): + bibtex_type_to_label = {"article": "Journal article", "misc": "Pre-print", "inproceedings": "Conference paper"} + return self._format_details_as_table( + { + "Type": bibtex_type_to_label[type_], + "DOI": optional[field("doi")], + "Date": publication_date, + "Authors": self.format_names("author"), + "Abstract": field("abstract"), + } + ) + + def _get_summarized_template(self, e, type_): + summary_template = self._get_summary_template(e, type_) + details_template = self._get_details_template(type_) + return tag("details")[tag("summary")[summary_template], details_template] + + def get_article_template(self, e): + return self._get_summarized_template(e, "article") + + def get_misc_template(self, e): + return self._get_summarized_template(e, "misc") + + def get_inproceedings_template(self, e): + return self._get_summarized_template(e, "inproceedings") + + +def write_publications_list(stream, bibliography_data, section_names, backend, style): + """Write bibliography data with given backend and style to a stream splitting in to sections.""" + keys_by_section = defaultdict(list) + section_names = [name.lower() for name in section_names] + for key, entry in bibliography_data.entries.items(): + # Section names and keywords normalized to lower case to make matching case-insensitive + keywords = set(k.strip().lower() for k in entry.fields.get("keywords", "").split(",")) + section_names_in_keywords = keywords & set(section_names) + if len(section_names_in_keywords) == 1: + keys_by_section[section_names_in_keywords.pop()].append(key) + elif len(section_names_in_keywords) == 0: + msg = ( + f"BibTeX entry with key {key} does not have a keyword / tag corresponding to " + f"one of section names {section_names} and so will not be included in output." + ) + warn(msg, stacklevel=2) + else: + msg = ( + f"BibTeX entry with key {key} has multiple keywords / tags corresponding to " + f"section names {section_names} and so will not be included in output." + ) + warn(msg, stacklevel=2) + for section_name in section_names: + stream.write(f"

    {section_name.capitalize()}

    \n") + formatted_bibliography = style.format_bibliography( + bibliography_data, keys_by_section[section_name] + ) + backend.write_to_stream(formatted_bibliography, stream) + stream.write("\n") + + +if __name__ == "__main__": + docs_directory = Path(__file__).parent + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument( + "--bib-file", + type=Path, + default=docs_directory / "publications.bib", + help="BibTeX file containing publication details", + ) + parser.add_argument( + "--output-file", + type=Path, + default=docs_directory / "_publications_list.html", + help="File to write publication list to in HTML format", + ) + parser.add_argument( + "--update-from-zotero", + action="store_true", + help="Update BibTeX file at path specified by --bib-file from Zotero group library", + ) + parser.add_argument( + "--zotero-group-id", + default="5746396", + help="Integer identifier for Zotero group library", + ) + args = parser.parse_args() + if args.update_from_zotero: + endpoint_url = f"https://api.zotero.org/groups/{args.zotero_group_id}/items" + # Zotero API requires maximum number of results to return (limit parameter) + # to be explicitly specified for export formats such as bibtex and allows a + # maximum value of 100 - if we exceed this number of publications will need + # to switch to making multiple requests with different start indices + response = requests.get( + endpoint_url, params={"format": "bibtex", "limit": "100"} + ) + if response.ok: + with open(args.bib_file, "w") as bib_file: + bib_file.write(response.text) + else: + msg = ( + f"Request to {endpoint_url} failed with status code " + f"{response.status_code} ({response.reason})" + ) + raise RuntimeError(msg) + with open(args.output_file, "w") as output_file: + write_publications_list( + stream=output_file, + bibliography_data=pybtex.database.parse_file(args.bib_file), + section_names=[ + "Overview of the model", + "Analyses using the model", + "Healthcare seeking behaviour", + "Healthcare provision", + "Data Collection - Protocol and Analyses", + "Theoretical Frameworks", + ], + backend=InlineHTMLBackend(), + style=SummarizedStyle(), + ) diff --git a/docs/tlo_resources.py b/docs/tlo_resources.py index 02ea407263..afc7f4fed4 100644 --- a/docs/tlo_resources.py +++ b/docs/tlo_resources.py @@ -99,7 +99,7 @@ def excel_to_rst_table(input_path: Path, output_path: Path) -> None: def generate_docs_pages_from_resource_files( resources_directory: Path, docs_directory: Path, - max_file_size_bytes: int = 2**20, + max_file_size_bytes: int = 2**15, ) -> None: root_output_directory = docs_directory / "resources" root_output_directory.mkdir(exist_ok=True) diff --git a/docs/write-ups/CareOfWomenDuringPregnancy.docx b/docs/write-ups/CareOfWomenDuringPregnancy.docx deleted file mode 100644 index 029ca768fc..0000000000 --- a/docs/write-ups/CareOfWomenDuringPregnancy.docx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9ea2293d04b42ed60c719ff4864670138c886d499fb0ae730f12575cf2abbdc2 -size 760210 diff --git a/docs/write-ups/Epilepsy.docx b/docs/write-ups/Epilepsy.docx index fb8b66055b..344e6ad6fa 100644 --- a/docs/write-ups/Epilepsy.docx +++ b/docs/write-ups/Epilepsy.docx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b394045e585544fdb83e8ec71993c5f42c0f50bdc8b016f6712c1f2a86994c8f -size 2759724 +oid sha256:1f84018d4a66a782d95b057e25fee043458f907f5a9a973b6685f650c1e2be08 +size 2381944 diff --git a/docs/write-ups/Labour.docx b/docs/write-ups/Labour.docx deleted file mode 100644 index 551a9c4f25..0000000000 --- a/docs/write-ups/Labour.docx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0fdbc5a8a118384a63b3f622c909684af63e8011714f640015d4b63623f351b6 -size 931746 diff --git a/docs/write-ups/NewbornOutcomes.docx b/docs/write-ups/NewbornOutcomes.docx deleted file mode 100644 index b5b6ef0e29..0000000000 --- a/docs/write-ups/NewbornOutcomes.docx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b86e91a8bd336a09e9aa55e4f03ce6a41bc062e6b5d5ee51ef044ed3bec77264 -size 395887 diff --git a/docs/write-ups/PostnatalSupervisor.docx b/docs/write-ups/PostnatalSupervisor.docx deleted file mode 100644 index 85840e91b3..0000000000 --- a/docs/write-ups/PostnatalSupervisor.docx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3f1956bd6ae8a66ed8f7a9dce8a3a675166c906ec4f1959f3ef336d7d660f9ef -size 285655 diff --git a/docs/write-ups/PregnancySupervisor.docx b/docs/write-ups/PregnancySupervisor.docx deleted file mode 100644 index 9c08ece524..0000000000 --- a/docs/write-ups/PregnancySupervisor.docx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:82be768077e4933feac49ea9be676adc5df548639ddc3225929a9ffa650e76a2 -size 476845 diff --git a/pyproject.toml b/pyproject.toml index ce24c3a3cc..a8622e235d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ dev = [ # Profiling "ansi2html", "psutil", - "pyinstrument>=4.3", + "pyinstrument>=4.7", # Building requirements files "pip-tools", ] diff --git a/requirements/dev.txt b/requirements/dev.txt index a6e0468a19..e8bbc2e0c9 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -164,7 +164,7 @@ psutil==5.9.5 # via tlo (pyproject.toml) pycparser==2.21 # via cffi -pyinstrument==4.5.3 +pyinstrument==4.7.3 # via tlo (pyproject.toml) pyjwt[crypto]==2.8.0 # via diff --git a/resources/ResourceFile_Improved_Healthsystem_And_Healthcare_Seeking.xlsx b/resources/ResourceFile_Improved_Healthsystem_And_Healthcare_Seeking.xlsx index 7ec045407a..1586c251f4 100644 --- a/resources/ResourceFile_Improved_Healthsystem_And_Healthcare_Seeking.xlsx +++ b/resources/ResourceFile_Improved_Healthsystem_And_Healthcare_Seeking.xlsx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49282122ff1c60e3bf73765013b6770a2fbd3f0df9a6e3f71e1d4c40e9cdfa2a -size 48238 +oid sha256:e63c16cbd0a069d9d10cf3c7212c8804fb1a047397227485adf348728fa5403b +size 48334 diff --git a/resources/epilepsy/ResourceFile_Epilepsy.xlsx b/resources/epilepsy/ResourceFile_Epilepsy.xlsx index 4bdf5ee91c..8bfa24affb 100644 --- a/resources/epilepsy/ResourceFile_Epilepsy.xlsx +++ b/resources/epilepsy/ResourceFile_Epilepsy.xlsx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e3c38418df28aabb98602e1b00e77d3840143a9fff8de495230817042d2ed45 -size 1250058 +oid sha256:94938f9187d5573f068f458263cb6d37ca3ce776eb8dfc9542e5cee0543c8804 +size 1250009 diff --git a/src/scripts/calibration_analyses/analysis_scripts/analysis_demography_calibrations.py b/src/scripts/calibration_analyses/analysis_scripts/analysis_demography_calibrations.py index 974e50c567..64ac672158 100644 --- a/src/scripts/calibration_analyses/analysis_scripts/analysis_demography_calibrations.py +++ b/src/scripts/calibration_analyses/analysis_scripts/analysis_demography_calibrations.py @@ -260,6 +260,7 @@ def get_mean_pop_by_age_for_sex_and_year(sex, year): collapse_columns=True, only_mean=True ) + num_by_age = num_by_age.reindex(make_age_grp_types().categories) return num_by_age for year in [2010, 2015, 2018, 2029, 2049]: @@ -718,6 +719,7 @@ def get_counts_of_death_by_period_sex_agegrp(df): deaths_by_agesexperiod.loc[ (deaths_by_agesexperiod['Period'] == period) & (deaths_by_agesexperiod['Sex'] == sex)].groupby( by=['Variant', 'Age_Grp'])['Count'].sum()).unstack() + tot_deaths_byage.columns = pd.Index([label[1] for label in tot_deaths_byage.columns.tolist()]) tot_deaths_byage = tot_deaths_byage.transpose() diff --git a/src/scripts/epilepsy_analyses/analysis_epilepsy.py b/src/scripts/epilepsy_analyses/analysis_epilepsy.py index 735cbc6ce7..bba4d3c479 100644 --- a/src/scripts/epilepsy_analyses/analysis_epilepsy.py +++ b/src/scripts/epilepsy_analyses/analysis_epilepsy.py @@ -28,7 +28,7 @@ start_date = Date(2010, 1, 1) end_date = Date(2020, 1, 1) -popsize = 200000 +popsize = 100_000 # Establish the simulation object log_config = { @@ -40,10 +40,11 @@ 'tlo.methods.demography': logging.INFO, 'tlo.methods.healthsystem': logging.WARNING, 'tlo.methods.healthburden': logging.WARNING, + 'tlo.methods.population': logging.INFO, } } -sim = Simulation(start_date=start_date, seed=0, log_config=log_config) +sim = Simulation(start_date=start_date, seed=0, log_config=log_config, show_progress_bar=True) # make a dataframe that contains the switches for which interventions are allowed or not allowed # during this run. NB. These must use the exact 'registered strings' that the disease modules allow @@ -125,7 +126,8 @@ ) n_seiz_stat_1_3.plot() plt.title('Number with epilepsy (past or current)') -plt.ylim(0, 800000) +plt.gca().set_ylim(bottom=0) +plt.ylabel("Number (not scaled)") plt.tight_layout() plt.show() @@ -135,11 +137,25 @@ ) n_seiz_stat_2_3.plot() plt.title('Number with epilepsy (infrequent or frequent seizures)') -plt.ylim(0, 300000) +plt.gca().set_ylim(bottom=0) +plt.ylabel("Number (not scaled)") plt.tight_layout() plt.show() plt.clf() + +prop_antiepilep_seiz_infreq_or_freq = pd.Series( + output['tlo.methods.epilepsy']['epilepsy_logging']['prop_freq_or_infreq_seiz_on_antiep'].values, + index=output['tlo.methods.epilepsy']['epilepsy_logging']['date'] +) +prop_antiepilep_seiz_infreq_or_freq.plot(color='r') +plt.title('Proportion on antiepileptics\namongst people that have infrequent or frequent epileptic seizures') +plt.ylim(0, 1) +plt.tight_layout() +plt.show() +plt.clf() + + prop_antiepilep_seiz_stat_1 = pd.Series( output['tlo.methods.epilepsy']['epilepsy_logging']['prop_antiepilep_seiz_stat_1'].values, index=output['tlo.methods.epilepsy']['epilepsy_logging']['date'] @@ -179,7 +195,8 @@ ) n_epi_death.plot() plt.title('Number of deaths from epilepsy') -plt.ylim(0, 50) +plt.gca().set_ylim(bottom=0) +plt.ylabel("Number (not scaled)") plt.tight_layout() plt.show() plt.clf() @@ -190,11 +207,21 @@ ) n_antiep.plot() plt.title('Number of people on antiepileptics') -plt.ylim(0, 50000) +plt.gca().set_ylim(bottom=0) +plt.ylabel("Number (not scaled)") plt.tight_layout() plt.show() plt.clf() +(n_antiep / popsize).plot() +plt.title('Proportion of of people (whole population) on antiepileptics') +plt.gca().set_ylim(bottom=0) +plt.ylabel("Number (not scaled)") +plt.tight_layout() +plt.show() +plt.clf() + + epi_death_rate = pd.Series( output['tlo.methods.epilepsy']['epilepsy_logging']['epi_death_rate'].values, index=output['tlo.methods.epilepsy']['epilepsy_logging']['date'] @@ -233,8 +260,7 @@ for _row, period in enumerate(('2010-2014', '2015-2019')): ax = axs[_row] comparison.loc[(period, slice(None), slice(None), CAUSE_NAME)]\ - .droplevel([0, 1, 3])\ - .groupby(axis=0, level=0)\ + .groupby(axis=0, level=1)\ .sum()\ .plot(use_index=True, ax=ax) ax.set_ylabel('Deaths per year') diff --git a/src/scripts/hiv/projections_jan2023/analysis_logged_deviance.py b/src/scripts/hiv/projections_jan2023/analysis_logged_deviance.py index 7a2af7fbed..3902e5d49b 100644 --- a/src/scripts/hiv/projections_jan2023/analysis_logged_deviance.py +++ b/src/scripts/hiv/projections_jan2023/analysis_logged_deviance.py @@ -34,8 +34,8 @@ # %% Run the simulation start_date = Date(2010, 1, 1) -end_date = Date(2022, 1, 1) -popsize = 5000 +end_date = Date(2014, 1, 1) +popsize = 25000 # scenario = 1 @@ -51,7 +51,7 @@ "tlo.methods.tb": logging.INFO, "tlo.methods.demography": logging.INFO, # "tlo.methods.demography.detail": logging.WARNING, - # "tlo.methods.healthsystem.summary": logging.INFO, + "tlo.methods.healthsystem.summary": logging.INFO, # "tlo.methods.healthsystem": logging.INFO, # "tlo.methods.healthburden": logging.INFO, }, @@ -70,7 +70,7 @@ resourcefilepath=resourcefilepath, service_availability=["*"], # all treatment allowed mode_appt_constraints=1, # mode of constraints to do with officer numbers and time - cons_availability="default", # mode for consumable constraints (if ignored, all consumables available) + cons_availability="all", # mode for consumable constraints (if ignored, all consumables available) ignore_priority=False, # do not use the priority information in HSI event to schedule capabilities_coefficient=1.0, # multiplier for the capabilities of health officers use_funded_or_actual_staffing="actual", # actual: use numbers/distribution of staff available currently @@ -89,7 +89,7 @@ # set the scenario sim.modules["Hiv"].parameters["do_scaleup"] = True sim.modules["Hiv"].parameters["scaleup_start_year"] = 2019 -# sim.modules["Tb"].parameters["scenario"] = scenario +sim.modules["Tb"].parameters["first_line_test"] = 'xpert' # sim.modules["Tb"].parameters["scenario_start_date"] = Date(2010, 1, 1) # sim.modules["Tb"].parameters["scenario_SI"] = "z" diff --git a/src/scripts/profiling/run_profiling.py b/src/scripts/profiling/run_profiling.py index 6097177af9..180a7571ab 100644 --- a/src/scripts/profiling/run_profiling.py +++ b/src/scripts/profiling/run_profiling.py @@ -306,7 +306,8 @@ def run_profiling( timeline=False, color=True, flat=True, - processor_options={"show_regex": ".*/tlo/.*", "hide_regex": ".*/pandas/.*"} + flat_time="total", + processor_options={"show_regex": ".*/tlo/.*", "hide_regex": ".*/pandas/.*", "filter_threshold": 1e-3} ) converter = Ansi2HTMLConverter(title=output_name) print(f"Writing {output_html_file}", end="...", flush=True) diff --git a/src/tlo/bitset_handler/bitset_extension.py b/src/tlo/bitset_handler/bitset_extension.py index 6163b3e4db..92d7af734f 100644 --- a/src/tlo/bitset_handler/bitset_extension.py +++ b/src/tlo/bitset_handler/bitset_extension.py @@ -19,7 +19,6 @@ import numpy as np import pandas as pd -from numpy.dtypes import BytesDType # pylint: disable=E0611 from numpy.typing import NDArray from pandas._typing import TakeIndexer, type_t from pandas.core.arrays.base import ExtensionArray @@ -86,14 +85,14 @@ def construct_from_string(cls, string: str) -> BitsetDtype: if not isinstance(string, str): raise TypeError(f"'construct_from_string' expects a string, got {type(string)}") - string_has_bitset_prefix = re.match("bitset\((\d+)\):", string) + string_has_bitset_prefix = re.match(r"bitset\((\d+)\):", string) n_elements = None if string_has_bitset_prefix: prefix = string_has_bitset_prefix.group(0) # Remove prefix string = string.removeprefix(prefix) # Extract number of elements if provided though - n_elements = int(re.search("(\d+)", prefix).group(0)) + n_elements = int(re.search(r"(\d+)", prefix).group(0)) if "," not in string: raise TypeError( "Need at least 2 (comma-separated) elements in string to construct bitset." @@ -130,8 +129,8 @@ def name(self) -> str: return self.__str__() @property - def np_array_dtype(self) -> BytesDType: - return BytesDType(self.fixed_width) + def np_array_dtype(self) -> np.dtype: + return np.dtype((bytes, self.fixed_width)) @property def type(self) -> Type[np.bytes_]: @@ -374,7 +373,7 @@ def nbytes(self) -> int: def __init__( self, - data: Iterable[BytesDType] | np.ndarray[BytesDType], + data: Iterable | np.ndarray, dtype: BitsetDtype, copy: bool = False, ) -> None: @@ -688,7 +687,7 @@ def take( indices: TakeIndexer, *, allow_fill: bool = False, - fill_value: Optional[BytesDType | Set[ElementType]] = None, + fill_value: Optional[np.bytes_ | Set[ElementType]] = None, ) -> BitsetArray: if allow_fill: if isinstance(fill_value, set): diff --git a/src/tlo/cli.py b/src/tlo/cli.py index c5b0c3f86d..578c3a6619 100644 --- a/src/tlo/cli.py +++ b/src/tlo/cli.py @@ -13,9 +13,9 @@ import dateutil.parser import pandas as pd from azure import batch -from azure.batch import batch_auth from azure.batch import models as batch_models from azure.batch.models import BatchErrorException +from azure.common.credentials import ServicePrincipalCredentials from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError from azure.identity import DefaultAzureCredential from azure.keyvault.secrets import SecretClient @@ -132,9 +132,7 @@ def batch_submit(ctx, scenario_file, asserts_on, more_memory, keep_pool_alive, i azure_directory = f"{config['DEFAULT']['USERNAME']}/{job_id}" batch_client = get_batch_client( - config["BATCH"]["NAME"], - config["BATCH"]["KEY"], - config["BATCH"]["URL"] + config["BATCH"]["CLIENT_ID"], config["BATCH"]["SECRET"], config["AZURE"]["TENANT_ID"], config["BATCH"]["URL"] ) create_file_share( @@ -247,8 +245,16 @@ def batch_submit(ctx, scenario_file, asserts_on, more_memory, keep_pool_alive, i try: # Create the job that will run the tasks. - create_job(batch_client, vm_size, pool_node_count, job_id, - container_conf, [mount_configuration], keep_pool_alive) + create_job( + batch_client, + vm_size, + pool_node_count, + job_id, + container_conf, + [mount_configuration], + keep_pool_alive, + config["BATCH"]["SUBNET_ID"], + ) # Add the tasks to the job. add_tasks(batch_client, user_identity, job_id, image_name, @@ -297,9 +303,7 @@ def batch_terminate(ctx, job_id): return batch_client = get_batch_client( - config["BATCH"]["NAME"], - config["BATCH"]["KEY"], - config["BATCH"]["URL"] + config["BATCH"]["CLIENT_ID"], config["BATCH"]["SECRET"], config["AZURE"]["TENANT_ID"], config["BATCH"]["URL"] ) # check the job is running @@ -331,10 +335,9 @@ def batch_job(ctx, job_id, raw, show_tasks): print(">Querying batch system\r", end="") config = load_config(ctx.obj['config_file']) batch_client = get_batch_client( - config["BATCH"]["NAME"], - config["BATCH"]["KEY"], - config["BATCH"]["URL"] + config["BATCH"]["CLIENT_ID"], config["BATCH"]["SECRET"], config["AZURE"]["TENANT_ID"], config["BATCH"]["URL"] ) + tasks = None try: @@ -402,9 +405,7 @@ def batch_list(ctx, status, n, find, username): username = config["DEFAULT"]["USERNAME"] batch_client = get_batch_client( - config["BATCH"]["NAME"], - config["BATCH"]["KEY"], - config["BATCH"]["URL"] + config["BATCH"]["CLIENT_ID"], config["BATCH"]["SECRET"], config["AZURE"]["TENANT_ID"], config["BATCH"]["URL"] ) # create client to connect to file share @@ -581,9 +582,12 @@ def load_server_config(kv_uri, tenant_id) -> Dict[str, Dict]: return {"STORAGE": storage_config, "BATCH": batch_config, "REGISTRY": registry_config} -def get_batch_client(name, key, url): +def get_batch_client(client_id, secret, tenant_id, url): """Create a Batch service client""" - credentials = batch_auth.SharedKeyCredentials(name, key) + resource = "https://batch.core.windows.net/" + + credentials = ServicePrincipalCredentials(client_id=client_id, secret=secret, tenant=tenant_id, resource=resource) + batch_client = batch.BatchServiceClient(credentials, batch_url=url) return batch_client @@ -697,10 +701,19 @@ def upload_local_file(connection_string, local_file_path, share_name, dest_file_ print("ResourceNotFoundError:", ex.message) -def create_job(batch_service_client, vm_size, pool_node_count, job_id, - container_conf, mount_configuration, keep_pool_alive): +def create_job( + batch_service_client, + vm_size, + pool_node_count, + job_id, + container_conf, + mount_configuration, + keep_pool_alive, + subnet_id, +): """Creates a job with the specified ID, associated with the specified pool. + :param subnet_id: :param batch_service_client: A Batch service client. :type batch_service_client: `azure.batch.BatchServiceClient` :param str vm_size: Type of virtual machine to use as pool. @@ -740,6 +753,11 @@ def create_job(batch_service_client, vm_size, pool_node_count, job_id, $NodeDeallocationOption = taskcompletion; """ + network_configuration = batch_models.NetworkConfiguration( + subnet_id=subnet_id, + public_ip_address_configuration=batch_models.PublicIPAddressConfiguration(provision="noPublicIPAddresses"), + ) + pool = batch_models.PoolSpecification( virtual_machine_configuration=virtual_machine_configuration, vm_size=vm_size, @@ -747,6 +765,8 @@ def create_job(batch_service_client, vm_size, pool_node_count, job_id, task_slots_per_node=1, enable_auto_scale=True, auto_scale_formula=auto_scale_formula, + network_configuration=network_configuration, + target_node_communication_mode="simplified", ) auto_pool_specification = batch_models.AutoPoolSpecification( diff --git a/src/tlo/methods/consumables.py b/src/tlo/methods/consumables.py index 6fb43e4a6d..e51a95fe74 100644 --- a/src/tlo/methods/consumables.py +++ b/src/tlo/methods/consumables.py @@ -60,7 +60,7 @@ def __init__(self, self._item_code_designations = item_code_designations # Save all item_codes that are defined and pd.Series with probs of availability from ResourceFile - self.item_codes, self._processed_consumables_data = \ + self.item_codes, self._processed_consumables_data = \ self._process_consumables_data(availability_data=availability_data) # Set the availability based on the argument provided (this can be updated later after the class is initialised) @@ -199,7 +199,8 @@ def _determine_default_return_value(cons_availability, default_return_value): def _request_consumables(self, facility_info: 'FacilityInfo', # noqa: F821 - item_codes: dict, + essential_item_codes: dict, + optional_item_codes: Optional[dict] = None, to_log: bool = True, treatment_id: Optional[str] = None ) -> dict: @@ -208,41 +209,52 @@ def _request_consumables(self, :param facility_info: The facility_info from which the request for consumables originates :param item_codes: dict of the form {: } for the items requested + :param optional_item_codes: dict of the form {: } for the optional items requested :param to_log: whether the request is logged. :param treatment_id: the TREATMENT_ID of the HSI (which is entered to the log, if provided). :return: dict of the form {: } indicating the availability of each item requested. """ + # If optional_item_codes is None, treat it as an empty dictionary + optional_item_codes = optional_item_codes or {} + _all_item_codes = {**essential_item_codes, **optional_item_codes} # Issue warning if any item_code is not recognised. - not_recognised_item_codes = item_codes.keys() - self.item_codes + not_recognised_item_codes = _all_item_codes.keys() - self.item_codes if len(not_recognised_item_codes) > 0: self._not_recognised_item_codes[treatment_id] |= not_recognised_item_codes # Look-up whether each of these items is available in this facility currently: - available = self._lookup_availability_of_consumables(item_codes=item_codes, facility_info=facility_info) + available = self._lookup_availability_of_consumables(item_codes=_all_item_codes, facility_info=facility_info) # Log the request and the outcome: if to_log: - items_available = {k: v for k, v in item_codes.items() if available[k]} - items_not_available = {k: v for k, v in item_codes.items() if not available[k]} - logger.info(key='Consumables', - data={ - 'TREATMENT_ID': (treatment_id if treatment_id is not None else ""), - 'Item_Available': str(items_available), - 'Item_NotAvailable': str(items_not_available), - }, - # NB. Casting the data to strings because logger complains with dict of varying sizes/keys - description="Record of each consumable item that is requested." - ) - - self._summary_counter.record_availability(items_available=items_available, - items_not_available=items_not_available) + items_available = {k: v for k, v in _all_item_codes.items() if available[k]} + items_not_available = {k: v for k, v in _all_item_codes.items() if not available[k]} + + # Log items used if all essential items are available + items_used = items_available if all(available.get(k, False) for k in essential_item_codes) else {} + + logger.info( + key='Consumables', + data={ + 'TREATMENT_ID': treatment_id or "", + 'Item_Available': str(items_available), + 'Item_NotAvailable': str(items_not_available), + 'Item_Used': str(items_used), + }, + description="Record of requested and used consumable items." + ) + self._summary_counter.record_availability( + items_available=items_available, + items_not_available=items_not_available, + items_used=items_used, + ) # Return the result of the check on availability return available def _lookup_availability_of_consumables(self, - facility_info: 'FacilityInfo', # noqa: F821 + facility_info: 'FacilityInfo', # noqa: F821 item_codes: dict ) -> dict: """Lookup whether a particular item_code is in the set of available items for that facility (in @@ -267,12 +279,12 @@ def _lookup_availability_of_consumables(self, def on_simulation_end(self): """Do tasks at the end of the simulation. - + Raise warnings and enter to log about item_codes not recognised. """ if len(self._not_recognised_item_codes) > 0: not_recognised_item_codes = { - treatment_id if treatment_id is not None else "": sorted(codes) + treatment_id if treatment_id is not None else "": sorted(codes) for treatment_id, codes in self._not_recognised_item_codes.items() } warnings.warn( @@ -363,10 +375,11 @@ def _reset_internal_stores(self) -> None: self._items = { 'Available': defaultdict(int), - 'NotAvailable': defaultdict(int) + 'NotAvailable': defaultdict(int), + 'Used': defaultdict(int), } - def record_availability(self, items_available: dict, items_not_available: dict) -> None: + def record_availability(self, items_available: dict, items_not_available: dict, items_used: dict) -> None: """Add information about the availability of requested items to the running summaries.""" # Record items that were available @@ -377,6 +390,10 @@ def record_availability(self, items_available: dict, items_not_available: dict) for _item, _num in items_not_available.items(): self._items['NotAvailable'][_item] += _num + # Record items that were used + for _item, _num in items_used.items(): + self._items['Used'][_item] += _num + def write_to_log_and_reset_counters(self): """Log summary statistics and reset the data structures.""" @@ -387,6 +404,7 @@ def write_to_log_and_reset_counters(self): data={ "Item_Available": self._items['Available'], "Item_NotAvailable": self._items['NotAvailable'], + "Item_Used": self._items['Used'], }, ) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index ab6c633f4c..09cb394804 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1146,12 +1146,12 @@ def __init__(self, module, person_id, new_contraceptive): self.TREATMENT_ID = "Contraception_Routine" self.ACCEPTED_FACILITY_LEVEL = _facility_level - - @property - def EXPECTED_APPT_FOOTPRINT(self): - """Return the expected appt footprint based on contraception method and whether the HSI has been rescheduled.""" - person_id = self.target current_method = self.sim.population.props.loc[person_id].co_contraception + self.EXPECTED_APPT_FOOTPRINT = self._get_appt_footprint(current_method) + + + def _get_appt_footprint(self, current_method: str): + """Return the appointment footprint based on contraception method and whether the HSI has been rescheduled.""" if self._number_of_times_run > 0: # if it is to re-schedule due to unavailable consumables return self.make_appt_footprint({}) # if to switch to a method @@ -1165,9 +1165,11 @@ def EXPECTED_APPT_FOOTPRINT(self): elif self.new_contraceptive in ['male_condom', 'other_modern', 'pill']: return self.make_appt_footprint({'PharmDispensing': 1}) else: + warnings.warn( + "Assumed empty footprint for Contraception Routine appt because couldn't find actual case.", + stacklevel=3, + ) return self.make_appt_footprint({}) - warnings.warn(UserWarning("Assumed empty footprint for Contraception Routine appt because couldn't find" - "actual case.")) def apply(self, person_id, squeeze_factor): """If the relevant consumable is available, do change in contraception and log it""" @@ -1266,6 +1268,8 @@ def apply(self, person_id, squeeze_factor): ): self.reschedule() + return self._get_appt_footprint(current_method) + def post_apply_hook(self): self._number_of_times_run += 1 diff --git a/src/tlo/methods/epi.py b/src/tlo/methods/epi.py index 1cdf8a1612..0ad0c75c1f 100644 --- a/src/tlo/methods/epi.py +++ b/src/tlo/methods/epi.py @@ -182,7 +182,10 @@ def initialise_simulation(self, sim): sim.schedule_event(EpiLoggingEvent(self), sim.date + DateOffset(years=1)) # HPV vaccine given from 2018 onwards - sim.schedule_event(HpvScheduleEvent(self), Date(2018, 1, 1)) + if self.sim.date.year < 2018: + sim.schedule_event(HpvScheduleEvent(self), Date(2018, 1, 1)) + else: + sim.schedule_event(HpvScheduleEvent(self), Date(self.sim.date.year, 1, 1)) # Look up item codes for consumables self.get_item_codes() diff --git a/src/tlo/methods/epilepsy.py b/src/tlo/methods/epilepsy.py index 5645d55e34..cc8c0f8cca 100644 --- a/src/tlo/methods/epilepsy.py +++ b/src/tlo/methods/epilepsy.py @@ -100,6 +100,15 @@ def __init__(self, name=None, resourcefilepath=None): 'daly_wt_epilepsy_seizure_free': Parameter( Types.REAL, 'disability weight for less severe epilepsy' 'controlled phase - code 862' ), + 'prob_start_anti_epilep_when_seizures_detected_in_generic_first_appt': Parameter( + Types.REAL, 'probability that someone who has had a seizure is started on anti-epileptics. This is ' + 'calibrated to induce the correct proportion of persons with epilepsy currently receiving ' + 'anti-epileptics.' + ), + 'max_num_of_failed_attempts_before_defaulting': Parameter( + Types.INT, 'maximum number of time an HSI can be repeated if the relevant essential consumables are not ' + 'available.' + ), } """ @@ -406,8 +415,14 @@ def do_at_generic_first_appt_emergency( **kwargs, ) -> None: if "seizures" in symptoms: - event = HSI_Epilepsy_Start_Anti_Epileptic(person_id=person_id, module=self) - schedule_hsi_event(event, priority=0, topen=self.sim.date) + # Determine if treatment will start - depends on probability of prescribing, which is calibrated to + # induce the right proportion of persons with epilepsy receiving treatment. + + prob_start = self.parameters['prob_start_anti_epilep_when_seizures_detected_in_generic_first_appt'] + + if self.rng.random_sample() < prob_start: + event = HSI_Epilepsy_Start_Anti_Epileptic(person_id=person_id, module=self) + schedule_hsi_event(event, priority=0, topen=self.sim.date) class EpilepsyEvent(RegularEvent, PopulationScopeEventMixin): @@ -576,12 +591,17 @@ def apply(self, population): cum_deaths = (~df.is_alive).sum() + # Proportion of those with infrequent or frequent seizures currently on anti-epileptics + prop_freq_or_infreq_seiz_on_antiep = status_groups[2:].ep_antiep.sum() / status_groups[2:].is_alive.sum() \ + if status_groups[2:].is_alive.sum() > 0 else 0 + logger.info(key='epilepsy_logging', data={ 'prop_seiz_stat_0': status_groups['prop_seiz_stats'].iloc[0], 'prop_seiz_stat_1': status_groups['prop_seiz_stats'].iloc[1], 'prop_seiz_stat_2': status_groups['prop_seiz_stats'].iloc[2], 'prop_seiz_stat_3': status_groups['prop_seiz_stats'].iloc[3], + 'prop_freq_or_infreq_seiz_on_antiep': prop_freq_or_infreq_seiz_on_antiep, 'prop_antiepilep_seiz_stat_0': status_groups['prop_seiz_stat_on_anti_ep'].iloc[0], 'prop_antiepilep_seiz_stat_1': status_groups['prop_seiz_stat_on_anti_ep'].iloc[1], 'prop_antiepilep_seiz_stat_2': status_groups['prop_seiz_stat_on_anti_ep'].iloc[2], @@ -608,6 +628,9 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self._MAX_NUMBER_OF_FAILED_ATTEMPTS_BEFORE_DEFAULTING = module.parameters['max_num_of_failed_attempts_before_defaulting'] + self._counter_of_failed_attempts_due_to_unavailable_medicines = 0 + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -639,8 +662,12 @@ def apply(self, person_id, squeeze_factor): priority=0 ) - else: + elif ( + self._counter_of_failed_attempts_due_to_unavailable_medicines + < self._MAX_NUMBER_OF_FAILED_ATTEMPTS_BEFORE_DEFAULTING + ): # If no medicine is available, run this HSI again next month + self._counter_of_failed_attempts_due_to_unavailable_medicines += 1 self.module.sim.modules['HealthSystem'].schedule_hsi_event(hsi_event=self, topen=self.sim.date + pd.DateOffset(months=1), tclose=None, @@ -652,7 +679,7 @@ class HSI_Epilepsy_Follow_Up(HSI_Event, IndividualScopeEventMixin): def __init__(self, module, person_id): super().__init__(module, person_id=person_id) - self._MAX_NUMBER_OF_FAILED_ATTEMPTS_BEFORE_DEFAULTING = 2 + self._MAX_NUMBER_OF_FAILED_ATTEMPTS_BEFORE_DEFAULTING = module.parameters['max_num_of_failed_attempts_before_defaulting'] self._DEFAULT_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self._REPEATED_APPT_FOOTPRINT = self.make_appt_footprint({'PharmDispensing': 1}) diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index 391cf587a8..8487eaa467 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -1510,20 +1510,20 @@ def per_capita_testing_rate(self): df = self.sim.population.props - # get number of tests performed in last time period - if self.sim.date.year == 2011: - number_tests_new = df.hv_number_tests.sum() + if not self.stored_test_numbers: + # If it's the first year, set previous_test_numbers to 0 previous_test_numbers = 0 - else: + # For subsequent years, retrieve the last stored number previous_test_numbers = self.stored_test_numbers[-1] - # calculate number of tests now performed - cumulative, include those who have died - number_tests_new = df.hv_number_tests.sum() + # Calculate number of tests now performed - cumulative, include those who have died + number_tests_new = df.hv_number_tests.sum() + # Store the number of tests performed in this year for future reference self.stored_test_numbers.append(number_tests_new) - # number of tests performed in last time period + # Number of tests performed in the last time period number_tests_in_last_period = number_tests_new - previous_test_numbers # per-capita testing rate diff --git a/src/tlo/methods/hsi_event.py b/src/tlo/methods/hsi_event.py index bdf597fba4..f267181b56 100644 --- a/src/tlo/methods/hsi_event.py +++ b/src/tlo/methods/hsi_event.py @@ -364,7 +364,8 @@ def get_consumables( # Checking the availability and logging: rtn = self.healthcare_system.consumables._request_consumables( - item_codes={**_item_codes, **_optional_item_codes}, + essential_item_codes=_item_codes, + optional_item_codes=_optional_item_codes, to_log=_to_log, facility_info=self.facility_info, treatment_id=self.TREATMENT_ID, diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 1ca2749af7..c79b26314d 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -2325,24 +2325,24 @@ def look_up_consumable_item_codes(self): # fractures.) } self.cons_item_codes['open_fracture_treatment'] = { - get_item_codes('Ceftriaxone 1g, PFR_each_CMST'): 2000, - get_item_codes('Cetrimide 15% + chlorhexidine 1.5% solution.for dilution _5_CMST'): 500, + get_item_codes('Ceftriaxone 1g, PFR_each_CMST'): 2, + get_item_codes('Cetrimide 15% + chlorhexidine 1.5% solution.for dilution _5_CMST'): 100, get_item_codes("Gauze, absorbent 90cm x 40m_each_CMST"): 100, get_item_codes('Suture pack'): 1, } self.cons_item_codes["open_fracture_treatment_additional_if_contaminated"] = { - get_item_codes('Metronidazole, injection, 500 mg in 100 ml vial'): 1500 + get_item_codes('Metronidazole, injection, 500 mg in 100 ml vial'): 3 } self.cons_item_codes['laceration_treatment_suture_pack'] = { - get_item_codes('Suture pack'): 0, + get_item_codes('Suture pack'): 1, } self.cons_item_codes['laceration_treatment_cetrimide_chlorhexidine'] = { - get_item_codes('Cetrimide 15% + chlorhexidine 1.5% solution.for dilution _5_CMST'): 500, + get_item_codes('Cetrimide 15% + chlorhexidine 1.5% solution.for dilution _5_CMST'): 100, } self.cons_item_codes['burn_treatment_per_burn'] = { - get_item_codes("Gauze, absorbent 90cm x 40m_each_CMST"): 0, - get_item_codes('Cetrimide 15% + chlorhexidine 1.5% solution.for dilution _5_CMST'): 0, + get_item_codes("Gauze, absorbent 90cm x 40m_each_CMST"): 100, + get_item_codes('Cetrimide 15% + chlorhexidine 1.5% solution.for dilution _5_CMST'): 100, } self.cons_item_codes['ringers lactate for multiple burns'] = { get_item_codes("ringer's lactate (Hartmann's solution), 1000 ml_12_IDA"): 4000 @@ -2353,16 +2353,16 @@ def look_up_consumable_item_codes(self): get_item_codes("diclofenac sodium 25 mg, enteric coated_1000_IDA"): 300 } self.cons_item_codes['pain_management_moderate'] = { - get_item_codes("tramadol HCl 100 mg/2 ml, for injection_100_IDA"): 300 + get_item_codes("tramadol HCl 100 mg/2 ml, for injection_100_IDA"): 3 } self.cons_item_codes['pain_management_severe'] = { - get_item_codes("morphine sulphate 10 mg/ml, 1 ml, injection (nt)_10_IDA"): 120 + get_item_codes("morphine sulphate 10 mg/ml, 1 ml, injection (nt)_10_IDA"): 12 } self.cons_item_codes['major_surgery'] = { # request a general anaesthetic get_item_codes("Halothane (fluothane)_250ml_CMST"): 100, # clean the site of the surgery - get_item_codes("Chlorhexidine 1.5% solution_5_CMST"): 500, + get_item_codes("Chlorhexidine 1.5% solution_5_CMST"): 600, # tools to begin surgery get_item_codes("Scalpel blade size 22 (individually wrapped)_100_CMST"): 1, # administer an IV @@ -2375,7 +2375,7 @@ def look_up_consumable_item_codes(self): # administer pain killer get_item_codes('Pethidine, 50 mg/ml, 2 ml ampoule'): 6, # administer antibiotic - get_item_codes("Ampicillin injection 500mg, PFR_each_CMST"): 1000, + get_item_codes("Ampicillin injection 500mg, PFR_each_CMST"): 2, # equipment used by surgeon, gloves and facemask get_item_codes('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_codes('surgical face mask, disp., with metal nose piece_50_IDA'): 1, @@ -2386,7 +2386,7 @@ def look_up_consumable_item_codes(self): # request a local anaesthetic get_item_codes("Halothane (fluothane)_250ml_CMST"): 100, # clean the site of the surgery - get_item_codes("Chlorhexidine 1.5% solution_5_CMST"): 500, + get_item_codes("Chlorhexidine 1.5% solution_5_CMST"): 300, # tools to begin surgery get_item_codes("Scalpel blade size 22 (individually wrapped)_100_CMST"): 1, # administer an IV @@ -2399,7 +2399,7 @@ def look_up_consumable_item_codes(self): # administer pain killer get_item_codes('Pethidine, 50 mg/ml, 2 ml ampoule'): 6, # administer antibiotic - get_item_codes("Ampicillin injection 500mg, PFR_each_CMST"): 1000, + get_item_codes("Ampicillin injection 500mg, PFR_each_CMST"): 2, # equipment used by surgeon, gloves and facemask get_item_codes('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_codes('surgical face mask, disp., with metal nose piece_50_IDA'): 1, diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index 9dc05ff301..33edeb63c8 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -681,8 +681,13 @@ def get_consumables_for_dx_and_tx(self): # TB Sputum smear test # assume that if smear-positive, sputum smear test is 100% specific and sensitive - self.item_codes_for_consumables_required['sputum_test'] = \ - hs.get_item_codes_from_package_name("Microscopy Test") + self.item_codes_for_consumables_required['sputum_test'] = hs.get_item_code_from_item_name("ZN Stain") + self.item_codes_for_consumables_required['sputum_container'] = hs.get_item_code_from_item_name( + "Sputum container") + self.item_codes_for_consumables_required['slides'] = hs.get_item_code_from_item_name( + "Microscope slides, lime-soda-glass, pack of 50") + self.item_codes_for_consumables_required['gloves'] = hs.get_item_code_from_item_name( + "Gloves, exam, latex, disposable, pair") self.sim.modules['HealthSystem'].dx_manager.register_dx_test( tb_sputum_test_smear_positive=DxTest( @@ -690,7 +695,10 @@ def get_consumables_for_dx_and_tx(self): target_categories=["active"], sensitivity=p["sens_sputum_smear_positive"], specificity=p["spec_sputum_smear_positive"], - item_codes=self.item_codes_for_consumables_required['sputum_test'] + item_codes=self.item_codes_for_consumables_required['sputum_test'], + optional_item_codes=[self.item_codes_for_consumables_required['sputum_container'], + self.item_codes_for_consumables_required['slides'], + self.item_codes_for_consumables_required['gloves']] ) ) self.sim.modules['HealthSystem'].dx_manager.register_dx_test( @@ -699,13 +707,16 @@ def get_consumables_for_dx_and_tx(self): target_categories=["active"], sensitivity=0.0, specificity=1.0, - item_codes=self.item_codes_for_consumables_required['sputum_test'] + item_codes=self.item_codes_for_consumables_required['sputum_test'], + optional_item_codes=[self.item_codes_for_consumables_required['sputum_container'], + self.item_codes_for_consumables_required['slides'], + self.item_codes_for_consumables_required['gloves']] ) ) # TB GeneXpert self.item_codes_for_consumables_required['xpert_test'] = \ - hs.get_item_codes_from_package_name("Xpert test") + hs.get_item_code_from_item_name("Xpert") # sensitivity/specificity set for smear status of cases self.sim.modules["HealthSystem"].dx_manager.register_dx_test( @@ -714,7 +725,10 @@ def get_consumables_for_dx_and_tx(self): target_categories=["active"], sensitivity=p["sens_xpert_smear_positive"], specificity=p["spec_xpert_smear_positive"], - item_codes=self.item_codes_for_consumables_required['xpert_test'] + item_codes=self.item_codes_for_consumables_required['xpert_test'], + optional_item_codes=[self.item_codes_for_consumables_required['sputum_container'], + self.item_codes_for_consumables_required['slides'], + self.item_codes_for_consumables_required['gloves']] ) ) self.sim.modules["HealthSystem"].dx_manager.register_dx_test( @@ -723,13 +737,17 @@ def get_consumables_for_dx_and_tx(self): target_categories=["active"], sensitivity=p["sens_xpert_smear_negative"], specificity=p["spec_xpert_smear_negative"], - item_codes=self.item_codes_for_consumables_required['xpert_test'] + item_codes=self.item_codes_for_consumables_required['xpert_test'], + optional_item_codes=[self.item_codes_for_consumables_required['sputum_container'], + self.item_codes_for_consumables_required['slides'], + self.item_codes_for_consumables_required['gloves']] ) ) # TB Chest x-ray - self.item_codes_for_consumables_required['chest_xray'] = { - hs.get_item_code_from_item_name("X-ray"): 1} + self.item_codes_for_consumables_required['chest_xray'] = hs.get_item_code_from_item_name("X-ray") + self.item_codes_for_consumables_required['lead_apron'] = hs.get_item_code_from_item_name( + "Lead rubber x-ray protective aprons up to 150kVp 0.50mm_each_CMST") # sensitivity/specificity set for smear status of cases self.sim.modules["HealthSystem"].dx_manager.register_dx_test( @@ -738,7 +756,8 @@ def get_consumables_for_dx_and_tx(self): target_categories=["active"], sensitivity=p["sens_xray_smear_positive"], specificity=p["spec_xray_smear_positive"], - item_codes=self.item_codes_for_consumables_required['chest_xray'] + item_codes=self.item_codes_for_consumables_required['chest_xray'], + optional_item_codes=self.item_codes_for_consumables_required['lead_apron'] ) ) self.sim.modules["HealthSystem"].dx_manager.register_dx_test( @@ -747,7 +766,8 @@ def get_consumables_for_dx_and_tx(self): target_categories=["active"], sensitivity=p["sens_xray_smear_negative"], specificity=p["spec_xray_smear_negative"], - item_codes=self.item_codes_for_consumables_required['chest_xray'] + item_codes=self.item_codes_for_consumables_required['chest_xray'], + optional_item_codes=self.item_codes_for_consumables_required['lead_apron'] ) ) @@ -1849,7 +1869,7 @@ def apply(self, person_id, squeeze_factor): if self.facility_level == "1a": self.sim.modules["HealthSystem"].schedule_hsi_event( hsi_event=HSI_Tb_ScreeningAndRefer( - person_id=person_id, module=self.module, facility_level="2" + person_id=person_id, module=self.module, facility_level="1b" ), topen=self.sim.date + DateOffset(days=1), tclose=None, diff --git a/src/tlo/util.py b/src/tlo/util.py index f8dc67d471..e246fcf05b 100644 --- a/src/tlo/util.py +++ b/src/tlo/util.py @@ -7,6 +7,7 @@ import numpy as np import pandas as pd from pandas import DataFrame, DateOffset +from pandas._typing import DtypeArg from tlo import Population, Property, Types @@ -475,7 +476,9 @@ def convert_excel_files_to_csv(folder: Path, files: Optional[list[str]] = None, Path(folder/excel_file_path).unlink() -def read_csv_files(folder: Path, files: Optional[list[str]] = None) -> DataFrame | dict[str, DataFrame]: +def read_csv_files(folder: Path, + dtype: DtypeArg | dict[str, DtypeArg] | None = None, + files: str | int | list[str] | None = 0) -> DataFrame | dict[str, DataFrame]: """ A function to read CSV files in a similar way pandas reads Excel files (:py:func:`pandas.read_excel`). @@ -485,8 +488,20 @@ def read_csv_files(folder: Path, files: Optional[list[str]] = None) -> DataFrame :py:func:`pandas.drop`. :param folder: Path to folder containing CSV files to read. + :param dtype: allows passing in a dictionary of datatypes in cases where you want different datatypes per column :param files: preferred csv file name(s). This is the same as sheet names in Excel file. Note that if None(no files - selected) then all files in the containing folder will be loaded + selected) then all csv files in the containing folder will be read + + Please take note of the following behaviours: + ----------------------------------------------- + - if files argument is initialised to zero(default) and the folder contains one or multiple files, + this method will return a dataframe. If the folder contain multiple files, it is good to + specify file names or initialise files argument with None to ensure correct files are selected + - if files argument is initialised to None and the folder contains one or multiple files, this method + will return a dataframe dictionary + - if the folder contains multiple files and files argument is initialised with one file name this + method will return a dataframe. it will return a dataframe dictionary when files argument is + initialised with a list of multiple file names """ all_data: dict[str, DataFrame] = {} # dataframes dictionary @@ -499,15 +514,21 @@ def clean_dataframe(dataframes_dict: dict[str, DataFrame]) -> None: for _key, dataframe in dataframes_dict.items(): all_data[_key] = dataframe.drop(dataframe.filter(like='Unnamed'), axis=1) # filter and drop Unnamed columns - if files is None: - for f_name in folder.rglob("*.csv"): - all_data[f_name.stem] = pd.read_csv(f_name) - + return_dict = False # a flag that will determine whether the output should be a dictionary or a DatFrame + if isinstance(files, list): + return_dict = True + elif isinstance(files, int) or files is None: + return_dict = files is None + files = [f_name.stem for f_name in folder.glob("*.csv")] + elif isinstance(files, str): + files = [files] else: - for f_name in files: - all_data[f_name] = pd.read_csv((folder / f_name).with_suffix(".csv")) + raise TypeError(f"Value passed for files argument {files} is not one of expected types.") + + for f_name in files: + all_data[f_name] = pd.read_csv((folder / f_name).with_suffix(".csv"), dtype=dtype) # clean and return the dataframe dictionary clean_dataframe(all_data) - # If only one file loaded return dataframe directly rather than dict - return next(iter(all_data.values())) if len(all_data) == 1 else all_data + # return a dictionary if return_dict flag is set to True else return a dataframe + return all_data if return_dict else next(iter(all_data.values())) diff --git a/tests/bitset_handler/conftest.py b/tests/bitset_handler/conftest.py index 20b6ae59f0..41b6ab3e6f 100644 --- a/tests/bitset_handler/conftest.py +++ b/tests/bitset_handler/conftest.py @@ -11,7 +11,6 @@ import numpy as np import pytest -from numpy.dtypes import BytesDType # pylint: disable=E0611 from numpy.random import PCG64, Generator from numpy.typing import NDArray @@ -89,7 +88,7 @@ def data_for_twos(dtype: BitsetDtype) -> None: @pytest.fixture -def data_missing(dtype: BitsetDtype) -> np.ndarray[BytesDType]: +def data_missing(dtype: BitsetDtype) -> np.ndarray: data = np.zeros((2,), dtype=dtype.np_array_dtype) data[0] = dtype.na_value data[1] = dtype.as_bytes({"a"}) diff --git a/tests/test_consumables.py b/tests/test_consumables.py index 6ddf3b3a28..c45f1532ed 100644 --- a/tests/test_consumables.py +++ b/tests/test_consumables.py @@ -61,7 +61,7 @@ def test_using_recognised_item_codes(seed): # Make requests for consumables (which would normally come from an instance of `HSI_Event`). rtn = cons._request_consumables( - item_codes={0: 1, 1: 1}, + essential_item_codes={0: 1, 1: 1}, facility_info=facility_info_0 ) @@ -88,7 +88,7 @@ def test_unrecognised_item_code_is_recorded(seed): # Make requests for consumables (which would normally come from an instance of `HSI_Event`). rtn = cons._request_consumables( - item_codes={99: 1}, + essential_item_codes={99: 1}, facility_info=facility_info_0 ) @@ -128,7 +128,8 @@ def test_consumables_availability_options(seed): cons.on_start_of_day(date=date) assert _expected_result == cons._request_consumables( - item_codes={_item_code: 1 for _item_code in all_items_request}, to_log=False, facility_info=facility_info_0 + essential_item_codes={_item_code: 1 for _item_code in all_items_request}, + to_log=False, facility_info=facility_info_0 ) @@ -153,7 +154,8 @@ def request_item(cons, item_code: Union[list, int]): item_code = [item_code] return all(cons._request_consumables( - item_codes={_i: 1 for _i in item_code}, to_log=False, facility_info=facility_info_0 + essential_item_codes={_i: 1 for _i in item_code}, + to_log=False, facility_info=facility_info_0 ).values()) rng = get_rng(seed) @@ -250,7 +252,7 @@ def test_consumables_available_at_right_frequency(seed): for _ in range(n_trials): cons.on_start_of_day(date=date) rtn = cons._request_consumables( - item_codes=requested_items, + essential_item_codes=requested_items, facility_info=facility_info_0, ) for _i in requested_items: @@ -273,6 +275,47 @@ def is_obs_frequency_consistent_with_expected_probability(n_obs, n_trials, p): p=average_availability_of_known_items) +@pytest.mark.parametrize("p_known_items, expected_items_used", [ + # Test 1 + ({0: 0.0, 1: 1.0, 2: 1.0, 3: 1.0}, {}), + # Test 2 + ({0: 1.0, 1: 1.0, 2: 0.0, 3: 1.0}, {0: 5, 1: 10, 3: 2}) +]) +def test_items_used_includes_only_available_items(seed, p_known_items, expected_items_used): + """ + Test that 'items_used' includes only items that are available. + Items should only be logged if the essential items are ALL available + If essential items are available, then optional items can be logged as items_used if available + Test 1: should not have any items_used as essential item 0 is not available + Test 2: should have essential items logged as items_used, but optional item 2 is not available + """ + + data = create_dummy_data_for_cons_availability( + intrinsic_availability=p_known_items, + months=[1], + facility_ids=[0] + ) + rng = get_rng(seed) + date = datetime.datetime(2010, 1, 1) + + cons = Consumables(availability_data=data, rng=rng) + + # Define essential and optional item codes + essential_item_codes = {0: 5, 1: 10} # these must match parameters above + optional_item_codes = {2: 7, 3: 2} + + cons.on_start_of_day(date=date) + cons._request_consumables( + essential_item_codes=essential_item_codes, + optional_item_codes=optional_item_codes, + facility_info=facility_info_0, + ) + + # Access items used from the Consumables summary counter + items_used = getattr(cons._summary_counter, '_items', {}).get('Used') + assert items_used == expected_items_used, f"Expected items_used to be {expected_items_used}, but got {items_used}" + + def get_sim_with_dummy_module_registered(tmpdir=None, run=True, data=None): """Return an initialised simulation object with a Dummy Module registered. If the `data` argument is provided, the parameter in HealthSystem that holds the data on consumables availability is over-written.""" diff --git a/tests/test_healthsystem.py b/tests/test_healthsystem.py index ca26316758..6eeabc4995 100644 --- a/tests/test_healthsystem.py +++ b/tests/test_healthsystem.py @@ -952,7 +952,7 @@ def apply(self, person_id, squeeze_factor): } == set(detailed_hsi_event.columns) assert {'date', 'Frac_Time_Used_Overall', 'Frac_Time_Used_By_Facility_ID', 'Frac_Time_Used_By_OfficerType', } == set(detailed_capacity.columns) - assert {'date', 'TREATMENT_ID', 'Item_Available', 'Item_NotAvailable' + assert {'date', 'TREATMENT_ID', 'Item_Available', 'Item_NotAvailable', 'Item_Used' } == set(detailed_consumables.columns) bed_types = sim.modules['HealthSystem'].bed_days.bed_types @@ -1019,6 +1019,9 @@ def dict_all_close(dict_1, dict_2): assert summary_consumables['Item_NotAvailable'].apply(pd.Series).sum().to_dict() == \ detailed_consumables['Item_NotAvailable'].apply( lambda x: {f'{k}': v for k, v in eval(x).items()}).apply(pd.Series).sum().to_dict() + assert summary_consumables['Item_Used'].apply(pd.Series).sum().to_dict() == \ + detailed_consumables['Item_Used'].apply( + lambda x: {f'{k}': v for k, v in eval(x).items()}).apply(pd.Series).sum().to_dict() # - Bed-Days (bed-type by bed-type and year by year) for _bed_type in bed_types: diff --git a/tests/test_utils.py b/tests/test_utils.py index 1022c95010..0e6b13d83b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -332,11 +332,46 @@ def copy_files_to_temporal_directory_and_return_path(tmpdir): return tmpdir_resource_filepath -def test_read_csv_method_with_no_file(tmpdir): - """ read csv method when no file name is supplied - i) should return dictionary. - ii) dictionary keys should match csv file names in resource folder - iii) all dictionary values should be dataframes +def test_pass_datatypes_to_read_csv_method(tmpdir): + """ test passing column datatypes to read csv method. Final column datatype should change to what has been passed """ + # copy and get resource files path in the temporal directory + path_to_tmpdir = Path(tmpdir) + sample_data = pd.DataFrame(data={'numbers1': [5,6,8,4,9,6], 'numbers2': [19,27,53,49,75,56]}, dtype=int) + sample_data.to_csv(tmpdir/'sample_data.csv', index=False) + # read from the sample data file + read_sample_data = read_csv_files(path_to_tmpdir, files='sample_data') + # confirm column datatype is what was assigned + assert read_sample_data.numbers1.dtype == 'int' and read_sample_data.numbers2.dtype == 'int' + # define new datatypes + datatype = {'numbers1': int, 'numbers2': float} + # pass the new datatypes to read csv method and confirm datatype has changed to what has been declared now + assign_dtype = read_csv_files(path_to_tmpdir, files='sample_data', dtype=datatype) + assert assign_dtype.numbers1.dtype == 'int' and assign_dtype.numbers2.dtype == 'float' + + +def test_read_csv_file_method_passing_none_to_files_argument(tmpdir): + """ test reading csv files with one file in the target resource file and setting to None the files argument + + Expectations + 1. should return a dictionary + 2. the dictionary key name should match file name + """ + # copy and get resource files path in the temporal directory + tmpdir_resource_filepath = copy_files_to_temporal_directory_and_return_path(tmpdir) + # choose an Excel file with one sheet in it and convert it to csv file + convert_excel_files_to_csv(tmpdir_resource_filepath, files=['ResourceFile_load-parameters.xlsx']) + # get the folder containing the newly converted csv file and check the expected behavior + this_csv_resource_folder = tmpdir_resource_filepath/"ResourceFile_load-parameters" + file_names = [csv_file_path.stem for csv_file_path in this_csv_resource_folder.rglob("*.csv")] + one_csv_file_in_folder_dict = read_csv_files(this_csv_resource_folder, files=None) + assert isinstance(one_csv_file_in_folder_dict, dict) + assert set(one_csv_file_in_folder_dict.keys()) == set(file_names) + + +def test_read_csv_method_with_default_value_for_files_argument(tmpdir): + """ read csv method when no file name(s) is supplied to the files argument + i) should return a dataframe of the first csv file in the folder. Similar to pd.read_excel returning + a dataframe of first sheet in the file. :param tmpdir: path to a temporal directory @@ -344,18 +379,18 @@ def test_read_csv_method_with_no_file(tmpdir): tmpdir_resource_filepath = copy_files_to_temporal_directory_and_return_path(tmpdir) file_names = [csv_file_path.stem for csv_file_path in tmpdir_resource_filepath.rglob("*.csv")] df_no_files = read_csv_files(tmpdir_resource_filepath) - assert isinstance(df_no_files, dict) - assert set(df_no_files.keys()) == set(file_names) - assert all(isinstance(value, pd.DataFrame) for value in df_no_files.values()) + fist_file_in_folder_df = read_csv_files(tmpdir_resource_filepath, files=file_names[0]) + assert isinstance(df_no_files, pd.DataFrame) + pd.testing.assert_frame_equal(fist_file_in_folder_df, df_no_files) def test_read_csv_method_with_one_file(tmpdir): - """ test read csv method when one file name is supplied. should return a dataframe + """ test read csv method when one file name is supplied to files argument. should return a dataframe :param tmpdir: path to a temporal directory """ tmpdir_resource_filepath = copy_files_to_temporal_directory_and_return_path(tmpdir) - df = read_csv_files(tmpdir_resource_filepath, files=['df_at_healthcareseeking']) + df = read_csv_files(tmpdir_resource_filepath, files='df_at_healthcareseeking') assert isinstance(df, pd.DataFrame) diff --git a/tox.ini b/tox.ini index 5d42fe3252..d25f446a25 100644 --- a/tox.ini +++ b/tox.ini @@ -66,13 +66,15 @@ deps = ; require setuptools_scm for getting version info setuptools_scm commands = - sphinx-apidoc -e -f -o {toxinidir}/docs/reference {toxinidir}/src/tlo + sphinx-apidoc -e -o {toxinidir}/docs/reference {toxinidir}/src/tlo ; Generate API documentation for TLO methods python docs/tlo_methods_rst.py ; Generate data sources page python docs/tlo_data_sources.py ; Generate contributors page python docs/tlo_contributors.py + ; Generate publications page + python docs/tlo_publications.py ; Generate resources files page python docs/tlo_resources.py ; Generate HSI events listing @@ -80,8 +82,7 @@ commands = python src/tlo/analysis/hsi_events.py --output-file docs/hsi_events.csv --output-format csv ; Generate parameters listing python docs/tlo_parameters.py {toxinidir}{/}resources {toxinidir}{/}docs{/}parameters.rst - sphinx-build {posargs:-E} -b html docs dist/docs - -sphinx-build -b linkcheck docs dist/docs + sphinx-build -b html docs dist/docs [testenv:check] deps = @@ -139,6 +140,13 @@ commands = python {toxinidir}/src/scripts/automation/update_citation.py skip_install = true deps = pyyaml +[testenv:update-publications] +commands = python {toxinidir}/docs/tlo_publications.py --update-from-zotero +skip_install = true +deps = + pybtex + requests + [testenv:requirements] commands = pip-compile --output-file {toxinidir}/requirements/base.txt