Skip to content

Commit

Permalink
Logical model update
Browse files Browse the repository at this point in the history
  • Loading branch information
pmanko committed Jun 15, 2024
1 parent 154ddd4 commit e01c4ae
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 124 deletions.
Binary file removed .DS_Store
Binary file not shown.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -163,5 +163,6 @@ l3-data/

output/*
tests/output/*
tests/temp*

.DS_Store
58 changes: 32 additions & 26 deletions tests/data/example_cql_HIV27.cql
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@

/*
* Library: HIV.IND.27 Logic
* Short Name: People living with HIV on ART
*
* People living with HIV on ART
* Number and % of people on ART among all people living with HIV at the end of the reporting period
*
* Numerator: Number of people on ART at the end of the reporting period (HIV patient monitoring data from, for example, ART registers, patient records or EMRs). For key populations survey data may be required.
* Numerator Calculation: COUNT of clients with "HIV status"='HIV-positive' AND "On ART"=True at reporting period end date
*
* Numerator: COUNT of clients with "HIV status"='HIV-positive' AND "On ART"=True at reporting period end date
* Numerator Exclusions: Clients with an "HIV treatment outcome" IN 'Lost to follow up', 'Transferred out', 'Death (documented)' at the end of the reporting period
*
* Denominator: 1. To determine treatment coverage: estimated number of people living with HIV (from models, such as Spectrum AIM) | 2. To gauge progress toward the second 95 target: number of people living with HIV who know their HIV status (from surveys or models)
* Denominator Calculation: For treatment coverage: *Estimated number of people living with HIV | | For progress towards 2nd 95 target: *Estimated number of people living with HIV who know their status
* Denominator Exclusions: Clients with an "HIV treatment outcome" IN 'Lost to follow up', 'Transferred out', 'Death (documented)' at the end of the reporting period
* Definition: Number of people on ART at the end of the reporting period (HIV patient monitoring data from, for example, ART registers, patient records or EMRs). For key populations survey data may be required.
*
* Disaggregations:
* • Gender (female, male, other*) | • Age (0–4, 5–9, 10–14, 15–19, 20–24, 25–29, 30–34, 35–39, 40–44, 45–49, 50+ years)** | • Key populations (men who have sex with men, people living in prisons and other closed settings, people who inject drugs, sex workers, trans and gender diverse people)*** | • Cities and other administrative regions of epidemiologic importance
* Disaggregation Elements: Gender | Age | Key population member type |
*
* Numerator and Denominator Elements:
* HIV status | On ART
*
* Reference: Consolidated guidelines on person-centred HIV strategic information: strengthening routine data for impact. Geneva: World Health Organization; 2022
* Denominator: For treatment coverage: *Estimated number of people living with HIV
* For progress towards 2nd 95 target: *Estimated number of people living with HIV who know their status
* Denominator exclusions: Clients with an "HIV treatment outcome" IN 'Lost to follow up', 'Transferred out', 'Death (documented)' at the end of the reporting period
* Definition: 1. To determine treatment coverage: estimated number of people living with HIV (from models, such as Spectrum AIM)
* 2. To gauge progress toward the second 95 target: number of people living with HIV who know their HIV status (from surveys or models)
*
* Disaggregation:
* - Gender
* - Age
* - Key population member type
* Description:
* - Gender (female, male, other*)
* - Age (0–4, 5–9, 10–14, 15–19, 20–24, 25–29, 30–34, 35–39, 40–44, 45–49, 50+ years)**
* - Key populations (men who have sex with men, people living in prisons and other closed settings, people who inject drugs, sex workers, trans and gender diverse people)***
* - Cities and other administrative regions of epidemiologic importance
*
* * The category of other includes trans and gender diverse people who choose an identity other than male or female.
* ** Recommended in settings with robust electronic health information systems.
* *** Where feasible and data security and confidentiality can be ensured (see monitoring considerations in section 3.8 of WHO's 2022 HIV SI Guidelines on key
* populations for further guidance and section 6.4 on maintaining data privacy, security and confidential).
*
* Data Elements:
* - HIV status
* - On ART
*
* Additional Context
* - what it measures: Measures progress towards providing ART to all people living with HIV, that is, treatment coverage, taking into account total attrition during the reporting period.
* - rationale: • WHO currently recommends treatment for all people living with HIV to achieve viral suppression. | • This indicator is central to accountability for national health sector strategic plans, effective programme management and donor programming. | • This indicator is essential to measurement of the second 95 target: that 95% of the people who know their HIV-positive status are accessing ART by 2025.
* - method: For the numerator: Generated by determining the number of people living with HIV on ART at the end of the last reporting period plus the number of people living with HIV initiated on ART during the current reporting period, taking into account retention/attrition status by the end of the reporting period. Retention and attrition analysis should be conducted as part of reporting on this indicator. The numerator should NOT INCLUDE people who have stopped treatment, died or were otherwise lost to follow-up during this period. Consistent with methods for defining the total attrition from ART indicator (see ART.2), these status classification categories should be reported separately to the national level and used to calculate the number of people living with HIV who are on ART. | | For the denominator: Epidemiological models such as Spectrum AIM are the preferred source for estimating the number of people living with HIV. Denominator 2 should be consistent with the numerator used for indicator HTS.1 People living with HIV who know their HIV status (first 95 target). The recommended maximum reporting frequency is 12 months. Shorter reporting intervals, for example, three months, are recommended where feasible.
* Reference: Consolidated guidelines on person-centred HIV strategic information: strengthening routine data for impact. Geneva: World Health Organization; 2022
*/




library HIVIND27Logic

using FHIR version '4.0.1'
Expand Down Expand Up @@ -74,9 +83,6 @@ define "denominator":
and not exists (HIC."Patient Deceased before end of Measurement Period")
and not exists (HIC."Stopped ART at Facility during the measurement period")




/*
* Disaggregators
*/
Expand Down
Binary file added tests/data/l2/test_dd.xlsx
Binary file not shown.
20 changes: 10 additions & 10 deletions tests/test_cql_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
)
import pandas as pd
import unittest

import stringcase

class TestCqlScaffold(unittest.TestCase):
def test_generate_cql_file_headers(self):
Expand Down Expand Up @@ -43,7 +43,7 @@ def test_generate_cql_template(self):
class TestCqlResourceGenerator(unittest.TestCase):
def setUp(self):
# Load example CQL from data directory
cql_file_path = "tests/data/example_cql_HIV20.cql"
cql_file_path = "tests/data/example_cql_HIV27.cql"
indicator_file_path = "tests/data/indicator_dak_input_MINI.xlsx"

# Load content and close file
Expand All @@ -55,15 +55,15 @@ def setUp(self):
indicator_file_path, sheet_name="Indicator definitions"
)

self.indicator_row = indicator_file.iloc[1]
self.indicator_row = indicator_file.iloc[2]

self.generator = CQLResourceGenerator(self.indicator_row, self.cql_content)

def test_parse_cql_with_valid_content(self):
parsed_cql = self.generator.parse_cql()

self.assertIsNotNone(parsed_cql)
self.assertEqual(parsed_cql["library_name"], "HIV.IND.20")
self.assertEqual(parsed_cql["library_name"], "HIV.IND.27")
self.assertIn("stratifiers", parsed_cql.keys())
self.assertIn("denominator", parsed_cql.keys())
self.assertIn("numerator", parsed_cql.keys())
Expand All @@ -74,17 +74,17 @@ def test_parse_cql_with_valid_content(self):
self.assertIsNotNone(parsed_cql["numerator"])

def test_generate_library_fsh(self):
self.generator.parse_cql()
p = self.generator.parse_cql()
library_fsh = self.generator.generate_library_fsh()

output_file = "tests/output/fsh/HIV20_library.fsh"
output_file = "tests/output/fsh/HIV27_library.fsh"

if os.path.exists(output_file):
os.remove(output_file)
with open(output_file, "w") as f:
f.write(library_fsh)

expected_lib_file = "tests/data/example_fsh/HIV20_library.fsh"
expected_lib_file = f"tests/data/example_fsh/{stringcase.alphanumcase(p["library_name"])}_library.fsh"
expected_lib_file = open(expected_lib_file, "r")

expected_library_fsh = expected_lib_file.read()
Expand All @@ -94,17 +94,17 @@ def test_generate_library_fsh(self):
self.assertEqual(library_fsh.strip(), expected_library_fsh.strip())

def test_generate_measure_fsh(self):
self.generator.parse_cql()
p = self.generator.parse_cql()
measure_fsh = self.generator.generate_measure_fsh()

output_file = "tests/output/fsh/HIV20_measure.fsh"
output_file = f"tests/output/fsh/{stringcase.alphanumcase(p["library_name"])}_measure.fsh"

if os.path.exists(output_file):
os.remove(output_file)
with open(output_file, "w") as f:
f.write(measure_fsh)

expected_measure_file = "tests/data/example_fsh/HIV20_measure.fsh"
expected_measure_file = "tests/data/example_fsh/HIV27_measure.fsh"
expected_measure_file = open(expected_measure_file, "r")

expected_measure_fsh = expected_measure_file.read()
Expand Down
25 changes: 17 additions & 8 deletions tests/test_logical_model_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
import pandas as pd
import sys
from who_l3_smart_tools.core.logical_models.logical_model_generator import (
generate_fsh_from_excel,
LogicalModelGenerator,
)


@unittest.skip("Skip for now")
class TestLogicalModelGenerator(unittest.TestCase):
def setUp(self):
self.input_file = "../l3-data/test-data.xlsx"
self.output_dir = "../l3-data/output"
self.input_file = "tests/tmp/l2/test_dd.xlsx"
self.output_dir = "tests/tmp/fsh/models"

def tearDown(self):
os.remove(self.input_file)
Expand All @@ -30,14 +29,13 @@ def test_generate_fsh_from_excel(self):
df = pd.DataFrame(test_data)
df.to_excel(self.input_file, index=False)

# Call the function under test
generate_fsh_from_excel(self.input_file, self.output_dir)
g = LogicalModelGenerator(self.input_file, self.output_dir)

g.generate_fsh_from_excel()

# Check if the output file is generated
output_file = os.path.join(self.output_dir, "HIV.X.fsh")
self.assertTrue(os.path.exists(output_file))

# Check the content of the output file
with open(output_file, "r") as f:
fsh_artifact = f.read()

Expand All @@ -59,5 +57,16 @@ def test_generate_fsh_from_excel(self):
self.assertEqual(fsh_artifact.strip(), expected_fsh_artifact.strip())


class TestFullLogicalModelGeneration(unittest.TestCase):
def setUp(self) -> None:
self.input_file = os.path.join("tests", "data", "l2", "test_dd.xlsx")
self.output_dir = os.path.join("tests", "output", "fsh", "models")

def test_full_data_dictionary(self):
generator = LogicalModelGenerator(self.input_file, self.output_dir)

generator.generate_fsh_from_excel()


if __name__ == "__main__":
unittest.main()
Loading

0 comments on commit e01c4ae

Please sign in to comment.