From 3b69301447623946fcb392faebbca3c84be4b383 Mon Sep 17 00:00:00 2001 From: makombe Date: Thu, 24 Oct 2024 20:05:55 +0300 Subject: [PATCH 1/3] KHP3-6851:Due for Pre-ART viral load test flag --- .../hiv/NeedsViralLoadTestCalculation.java | 84 ++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/org/openmrs/module/kenyaemr/calculation/library/hiv/NeedsViralLoadTestCalculation.java b/api/src/main/java/org/openmrs/module/kenyaemr/calculation/library/hiv/NeedsViralLoadTestCalculation.java index 10c1092d46..36a45fe149 100755 --- a/api/src/main/java/org/openmrs/module/kenyaemr/calculation/library/hiv/NeedsViralLoadTestCalculation.java +++ b/api/src/main/java/org/openmrs/module/kenyaemr/calculation/library/hiv/NeedsViralLoadTestCalculation.java @@ -12,9 +12,13 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.Concept; +import org.openmrs.Encounter; +import org.openmrs.EncounterType; +import org.openmrs.Form; import org.openmrs.Obs; import org.openmrs.Patient; import org.openmrs.Program; +import org.openmrs.api.ConceptService; import org.openmrs.api.PatientService; import org.openmrs.api.context.Context; import org.openmrs.calculation.patient.PatientCalculationContext; @@ -28,6 +32,9 @@ import org.openmrs.module.kenyaemr.calculation.library.hiv.art.LastViralLoadResultCalculation; import org.openmrs.module.kenyaemr.calculation.library.hiv.art.OnArtCalculation; import org.openmrs.module.kenyaemr.metadata.HivMetadata; +import org.openmrs.module.kenyaemr.metadata.MchMetadata; +import org.openmrs.module.kenyaemr.util.EmrUtils; +import org.openmrs.module.kenyaemr.util.HtsConstants; import org.openmrs.module.metadatadeploy.MetadataUtils; import org.openmrs.ui.framework.SimpleObject; @@ -41,6 +48,14 @@ public class NeedsViralLoadTestCalculation extends AbstractPatientCalculation implements PatientFlagCalculation { protected static final Log log = LogFactory.getLog(StablePatientsCalculation.class); + String flagMessage = null; + public static final Integer HTS_PMTCT_ANC_ENTRY_POINT_CONCEPT_ID = 160538; + public static final Integer HTS_PMTCT_MAT_ENTRY_POINT_CONCEPT_ID = 160456; + public static final Integer HTS_PMTCT_PNC_ENTRY_POINT_CONCEPT_ID = 1623; + public static final Integer HTSENTRYPOINT_QUESTION_CONCEPT_ID = 160540; + + + /** * Needs vl test calculation criteria: New EMR guidelines March 2023 @@ -54,13 +69,20 @@ public class NeedsViralLoadTestCalculation extends AbstractPatientCalculation im */ @Override public String getFlagMessage() { - return "Due for Viral Load"; + return flagMessage; } @Override public CalculationResultMap evaluate(Collection cohort, Map parameterValues, PatientCalculationContext context) { Program hivProgram = MetadataUtils.existing(Program.class, HivMetadata._Program.HIV); PatientService patientService = Context.getPatientService(); + ConceptService cs = Context.getConceptService(); + EncounterType mchEncType = MetadataUtils.existing(EncounterType.class, MchMetadata._EncounterType.MCHCS_CONSULTATION); + Form mchAncForm = MetadataUtils.existing(Form.class, MchMetadata._Form.MCHMS_ANTENATAL_VISIT); + Form mchDeliveryForm = MetadataUtils.existing(Form.class, MchMetadata._Form.MCHMS_DELIVERY); + Form mchPostnatalForm = MetadataUtils.existing(Form.class, MchMetadata._Form.MCHMS_POSTNATAL_VISIT); + + Set alive = Filters.alive(cohort, context); Set inHivProgram = Filters.inProgram(hivProgram, alive, context); @@ -129,10 +151,12 @@ public CalculationResultMap evaluate(Collection cohort, Map200 cps/ml) else if (lastVlResultValue != null && lastVLResultDate != null && daysSince(lastVLResultDate, context) >= 92 && lastVlResultValue > 200) { needsViralLoadTest = true; + flagMessage = "Due for Viral Load"; } //After 3 Months: New positives with no previous VL else if (artStartDate != null && daysSince(artStartDate, context) >= 92 && lastVLResultDate == null) { @@ -144,10 +168,68 @@ else if (artStartDate != null && daysSince(artStartDate, context) >= 92 && lastV else if (((lastPregStartDate != null && lastVLResultDate != null && lastPregStartDate.before(lastVLResultDate)) || (lastBFStartDate != null && lastVLResultDate != null && lastBFStartDate.before(lastVLResultDate)) || (obsPregStatusDate != null && lastVLResultDate != null && obsPregStatusDate.before(lastVLResultDate)) || (obsBFStatusDate != null && lastVLResultDate != null && obsBFStatusDate.before(lastVLResultDate)) || patient.getAge() <= 24) && (lastVLResultDate != null && daysSince(lastVLResultDate, context) >= 183 && (lastVlResultLDL != null || (lastVlResultValue != null && lastVlResultValue < 200)))) { needsViralLoadTest = true; + flagMessage = "Due for Viral Load"; } //After 12 Months: > 25 years old with suppressed VL or LDL else if (lastVLResultDate != null && daysSince(lastVLResultDate, context) >= 365 && patient.getAge() >= 25) { needsViralLoadTest = true; + flagMessage = "Due for Viral Load"; + } + + + ret.put(ptId, new BooleanResult(needsViralLoadTest, this)); + } + + /*Due for Pre-ART viral load test flag + * Criteria: In MCH program, Not on art and no previous vl test/Results,confirmed positive while in MCH + * + * + */ + if(activeInMCH.contains(ptId)) { + Encounter lastHtsInitialEnc = EmrUtils.lastEncounter(patient, HtsConstants.htsEncType, HtsConstants.htsInitialForm); + Encounter lastHtsRetestEnc = EmrUtils.lastEncounter(patient, HtsConstants.htsEncType, HtsConstants.htsRetestForm); + Encounter lastAncEnc = EmrUtils.lastEncounter(patient, mchEncType, mchAncForm); + Encounter lastMchDeliveryEnc = EmrUtils.lastEncounter(patient, mchEncType, mchDeliveryForm); + Encounter lastMchPostnatalEnc = EmrUtils.lastEncounter(patient, mchEncType, mchPostnatalForm); + Concept htsFinalTestQuestion = cs.getConcept(HtsConstants.HTS_FINAL_TEST_CONCEPT_ID); + Concept htsPositiveResult = cs.getConcept(HtsConstants.HTS_POSITIVE_RESULT_CONCEPT_ID); + Concept htsEntryPointQuestion = cs.getConcept(HTSENTRYPOINT_QUESTION_CONCEPT_ID); + Concept htsPmtctAncEntryPoint = cs.getConcept(HTS_PMTCT_ANC_ENTRY_POINT_CONCEPT_ID); + Concept htsPmtctMatEntryPoint = cs.getConcept(HTS_PMTCT_MAT_ENTRY_POINT_CONCEPT_ID); + Concept htsPmtctPncEntryPoint = cs.getConcept(HTS_PMTCT_PNC_ENTRY_POINT_CONCEPT_ID); + Encounter lastHtsEnc = null; + if (lastHtsInitialEnc != null && lastHtsRetestEnc == null) { + lastHtsEnc = lastHtsInitialEnc; + } else if (lastHtsInitialEnc == null && lastHtsRetestEnc != null) { + lastHtsEnc = lastHtsRetestEnc; + } else if (lastHtsInitialEnc != null && lastHtsRetestEnc != null) { + if (lastHtsInitialEnc.getEncounterDatetime().after(lastHtsRetestEnc.getEncounterDatetime())) { + lastHtsEnc = lastHtsInitialEnc; + } else { + lastHtsEnc = lastHtsRetestEnc; + } + } + boolean isConfirmedPositiveAtAnc = lastAncEnc != null ? EmrUtils.encounterThatPassCodedAnswer(lastAncEnc, htsFinalTestQuestion, htsPositiveResult) : false; + boolean isConfirmedPositiveAtPostnatal = lastMchPostnatalEnc != null ? EmrUtils.encounterThatPassCodedAnswer(lastMchPostnatalEnc, htsFinalTestQuestion, htsPositiveResult) : false; + boolean isConfirmedPositiveAtDelivery = lastMchDeliveryEnc != null ? EmrUtils.encounterThatPassCodedAnswer(lastMchDeliveryEnc, htsFinalTestQuestion, htsPositiveResult) : false; + + if ( !pendingVlResults.contains(ptId) && artStartDate == null && lastVLResultDate == null && + (isConfirmedPositiveAtAnc || isConfirmedPositiveAtPostnatal || isConfirmedPositiveAtDelivery)) { + needsViralLoadTest = true; + flagMessage = "Due for Pre-ART Viral Load"; + } + + if(lastHtsEnc != null) { + boolean isPositiveTestResult = lastHtsEnc != null ? EmrUtils.encounterThatPassCodedAnswer(lastHtsEnc, htsFinalTestQuestion, htsPositiveResult) : false; + if(isPositiveTestResult && !pendingVlResults.contains(ptId) && artStartDate == null && lastVLResultDate == null) { + for (Obs obs : lastHtsEnc.getObs()) { + if(obs.getConcept().equals(htsEntryPointQuestion) && (obs.getValueCoded().equals(htsPmtctAncEntryPoint) || + obs.getValueCoded().equals(htsPmtctMatEntryPoint) || obs.getValueCoded().equals(htsPmtctPncEntryPoint))) { + needsViralLoadTest = true; + flagMessage = "Due for Pre-ART Viral Load"; + } + } + } } ret.put(ptId, new BooleanResult(needsViralLoadTest, this)); } From 46b697ead50c891804b116c2ef5ba135f48baf0a Mon Sep 17 00:00:00 2001 From: makombe Date: Sun, 27 Oct 2024 21:28:48 +0300 Subject: [PATCH 2/3] Check for vl orders --- .../hiv/NeedsViralLoadTestCalculation.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/openmrs/module/kenyaemr/calculation/library/hiv/NeedsViralLoadTestCalculation.java b/api/src/main/java/org/openmrs/module/kenyaemr/calculation/library/hiv/NeedsViralLoadTestCalculation.java index 36a45fe149..8206952102 100755 --- a/api/src/main/java/org/openmrs/module/kenyaemr/calculation/library/hiv/NeedsViralLoadTestCalculation.java +++ b/api/src/main/java/org/openmrs/module/kenyaemr/calculation/library/hiv/NeedsViralLoadTestCalculation.java @@ -16,9 +16,11 @@ import org.openmrs.EncounterType; import org.openmrs.Form; import org.openmrs.Obs; +import org.openmrs.Order; import org.openmrs.Patient; import org.openmrs.Program; import org.openmrs.api.ConceptService; +import org.openmrs.api.OrderService; import org.openmrs.api.PatientService; import org.openmrs.api.context.Context; import org.openmrs.calculation.patient.PatientCalculationContext; @@ -36,10 +38,13 @@ import org.openmrs.module.kenyaemr.util.EmrUtils; import org.openmrs.module.kenyaemr.util.HtsConstants; import org.openmrs.module.metadatadeploy.MetadataUtils; +import org.openmrs.parameter.OrderSearchCriteria; import org.openmrs.ui.framework.SimpleObject; +import java.util.Arrays; import java.util.Collection; import java.util.Date; +import java.util.List; import java.util.Map; import java.util.Set; @@ -77,6 +82,7 @@ public CalculationResultMap evaluate(Collection cohort, Map cohort, Map= 365 Concept htsPmtctAncEntryPoint = cs.getConcept(HTS_PMTCT_ANC_ENTRY_POINT_CONCEPT_ID); Concept htsPmtctMatEntryPoint = cs.getConcept(HTS_PMTCT_MAT_ENTRY_POINT_CONCEPT_ID); Concept htsPmtctPncEntryPoint = cs.getConcept(HTS_PMTCT_PNC_ENTRY_POINT_CONCEPT_ID); - Encounter lastHtsEnc = null; + Encounter lastHtsEnc = null; + List vlOrderConcepts = Arrays.asList(cs.getConceptByUuid(Dictionary.HIV_VIRAL_LOAD), cs.getConceptByUuid(Dictionary.HIV_VIRAL_LOAD_QUALITATIVE)); + OrderSearchCriteria OrderSearchCriteria = new OrderSearchCriteria( patient, null, vlOrderConcepts, null, null, null, + null, null, false, null, null, null, null, true, true, false, false); + List vlOrders = orderService.getOrders(OrderSearchCriteria); + if (lastHtsInitialEnc != null && lastHtsRetestEnc == null) { lastHtsEnc = lastHtsInitialEnc; } else if (lastHtsInitialEnc == null && lastHtsRetestEnc != null) { @@ -213,7 +225,7 @@ else if (lastVLResultDate != null && daysSince(lastVLResultDate, context) >= 365 boolean isConfirmedPositiveAtPostnatal = lastMchPostnatalEnc != null ? EmrUtils.encounterThatPassCodedAnswer(lastMchPostnatalEnc, htsFinalTestQuestion, htsPositiveResult) : false; boolean isConfirmedPositiveAtDelivery = lastMchDeliveryEnc != null ? EmrUtils.encounterThatPassCodedAnswer(lastMchDeliveryEnc, htsFinalTestQuestion, htsPositiveResult) : false; - if ( !pendingVlResults.contains(ptId) && artStartDate == null && lastVLResultDate == null && + if ( vlOrders.size() == 0 && artStartDate == null && (isConfirmedPositiveAtAnc || isConfirmedPositiveAtPostnatal || isConfirmedPositiveAtDelivery)) { needsViralLoadTest = true; flagMessage = "Due for Pre-ART Viral Load"; @@ -221,7 +233,7 @@ else if (lastVLResultDate != null && daysSince(lastVLResultDate, context) >= 365 if(lastHtsEnc != null) { boolean isPositiveTestResult = lastHtsEnc != null ? EmrUtils.encounterThatPassCodedAnswer(lastHtsEnc, htsFinalTestQuestion, htsPositiveResult) : false; - if(isPositiveTestResult && !pendingVlResults.contains(ptId) && artStartDate == null && lastVLResultDate == null) { + if(isPositiveTestResult && vlOrders.size() == 0 && artStartDate == null) { for (Obs obs : lastHtsEnc.getObs()) { if(obs.getConcept().equals(htsEntryPointQuestion) && (obs.getValueCoded().equals(htsPmtctAncEntryPoint) || obs.getValueCoded().equals(htsPmtctMatEntryPoint) || obs.getValueCoded().equals(htsPmtctPncEntryPoint))) { From 121f80a4243595ba7f62401a3dd8722bf87699a7 Mon Sep 17 00:00:00 2001 From: makombe Date: Tue, 29 Oct 2024 14:47:39 +0300 Subject: [PATCH 3/3] For postnatal only show flag if tested postive within 24 months --- .../hiv/NeedsViralLoadTestCalculation.java | 61 +++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/org/openmrs/module/kenyaemr/calculation/library/hiv/NeedsViralLoadTestCalculation.java b/api/src/main/java/org/openmrs/module/kenyaemr/calculation/library/hiv/NeedsViralLoadTestCalculation.java index 8206952102..25229ea989 100755 --- a/api/src/main/java/org/openmrs/module/kenyaemr/calculation/library/hiv/NeedsViralLoadTestCalculation.java +++ b/api/src/main/java/org/openmrs/module/kenyaemr/calculation/library/hiv/NeedsViralLoadTestCalculation.java @@ -11,6 +11,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.joda.time.DateTime; +import org.joda.time.Days; import org.openmrs.Concept; import org.openmrs.Encounter; import org.openmrs.EncounterType; @@ -41,7 +43,13 @@ import org.openmrs.parameter.OrderSearchCriteria; import org.openmrs.ui.framework.SimpleObject; +import liquibase.pro.packaged.b; +import liquibase.pro.packaged.bo; +import liquibase.pro.packaged.d; +import liquibase.pro.packaged.e; + import java.util.Arrays; +import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.List; @@ -204,7 +212,10 @@ else if (lastVLResultDate != null && daysSince(lastVLResultDate, context) >= 365 Concept htsPmtctAncEntryPoint = cs.getConcept(HTS_PMTCT_ANC_ENTRY_POINT_CONCEPT_ID); Concept htsPmtctMatEntryPoint = cs.getConcept(HTS_PMTCT_MAT_ENTRY_POINT_CONCEPT_ID); Concept htsPmtctPncEntryPoint = cs.getConcept(HTS_PMTCT_PNC_ENTRY_POINT_CONCEPT_ID); - Encounter lastHtsEnc = null; + Concept dateOfDelivery = cs.getConcept(Dictionary.DATE_OF_CONFINEMENT); //Though named date of confinement, same concept used for delivery date + Encounter lastHtsEnc = null; + Date dateOfDeliveryValue = null; + Date dateTestedPositiveAtHts = null; List vlOrderConcepts = Arrays.asList(cs.getConceptByUuid(Dictionary.HIV_VIRAL_LOAD), cs.getConceptByUuid(Dictionary.HIV_VIRAL_LOAD_QUALITATIVE)); OrderSearchCriteria OrderSearchCriteria = new OrderSearchCriteria( patient, null, vlOrderConcepts, null, null, null, null, null, false, null, null, null, null, true, true, false, false); @@ -224,28 +235,70 @@ else if (lastVLResultDate != null && daysSince(lastVLResultDate, context) >= 365 boolean isConfirmedPositiveAtAnc = lastAncEnc != null ? EmrUtils.encounterThatPassCodedAnswer(lastAncEnc, htsFinalTestQuestion, htsPositiveResult) : false; boolean isConfirmedPositiveAtPostnatal = lastMchPostnatalEnc != null ? EmrUtils.encounterThatPassCodedAnswer(lastMchPostnatalEnc, htsFinalTestQuestion, htsPositiveResult) : false; boolean isConfirmedPositiveAtDelivery = lastMchDeliveryEnc != null ? EmrUtils.encounterThatPassCodedAnswer(lastMchDeliveryEnc, htsFinalTestQuestion, htsPositiveResult) : false; + boolean testedPositiveWithin24MonthsInPostnatal = false; + + if(lastMchPostnatalEnc != null) { + + Date dateTestedPositivePnc = null; + for (Obs obs : lastMchPostnatalEnc.getObs()) { + if(obs.getConcept().equals(dateOfDelivery)) { + dateOfDeliveryValue = obs.getValueDate(); + } else if(obs.getConcept().equals(htsFinalTestQuestion) && obs.getValueCoded().equals(htsPositiveResult)) { + dateTestedPositivePnc = obs.getObsDatetime(); + } + } + if(dateOfDeliveryValue != null && dateTestedPositivePnc != null) { + testedPositiveWithin24MonthsInPostnatal = testedPositiveWithin24MonthsInPnc(dateOfDeliveryValue, dateTestedPositivePnc); + } + } - if ( vlOrders.size() == 0 && artStartDate == null && - (isConfirmedPositiveAtAnc || isConfirmedPositiveAtPostnatal || isConfirmedPositiveAtDelivery)) { + if ( vlOrders.size() == 0 && artStartDate == null && + (isConfirmedPositiveAtAnc || (isConfirmedPositiveAtPostnatal && testedPositiveWithin24MonthsInPostnatal) || isConfirmedPositiveAtDelivery)) { needsViralLoadTest = true; flagMessage = "Due for Pre-ART Viral Load"; } if(lastHtsEnc != null) { boolean isPositiveTestResult = lastHtsEnc != null ? EmrUtils.encounterThatPassCodedAnswer(lastHtsEnc, htsFinalTestQuestion, htsPositiveResult) : false; + if(isPositiveTestResult) { + dateTestedPositiveAtHts = lastHtsEnc.getEncounterDatetime(); + } if(isPositiveTestResult && vlOrders.size() == 0 && artStartDate == null) { for (Obs obs : lastHtsEnc.getObs()) { if(obs.getConcept().equals(htsEntryPointQuestion) && (obs.getValueCoded().equals(htsPmtctAncEntryPoint) || - obs.getValueCoded().equals(htsPmtctMatEntryPoint) || obs.getValueCoded().equals(htsPmtctPncEntryPoint))) { + obs.getValueCoded().equals(htsPmtctMatEntryPoint))) { needsViralLoadTest = true; flagMessage = "Due for Pre-ART Viral Load"; } } } } + // check if confirmed positive using hts form and still within 24 months at postnatal + if(dateTestedPositiveAtHts != null && dateOfDeliveryValue != null && dateTestedPositiveAtHts.after(dateOfDeliveryValue) && vlOrders.size() == 0 && artStartDate == null) { + testedPositiveWithin24MonthsInPostnatal = testedPositiveWithin24MonthsInPnc(dateOfDeliveryValue, dateTestedPositiveAtHts); + if(testedPositiveWithin24MonthsInPostnatal) { + needsViralLoadTest = true; + flagMessage = "Due for Pre-ART Viral Load"; + } + } + ret.put(ptId, new BooleanResult(needsViralLoadTest, this)); } } return ret; } + private boolean testedPositiveWithin24MonthsInPnc(Date deliveryDate ,Date dateTestedPositive) { + boolean isTestedPositiveWithin24Months = false; + Calendar startCalendar = Calendar.getInstance(); + startCalendar.setTime(deliveryDate); + Calendar endCalendar = Calendar.getInstance(); + endCalendar.setTime(dateTestedPositive); + int yearDiff = endCalendar.get(Calendar.YEAR) - startCalendar.get(Calendar.YEAR); + int monthDiff = yearDiff * 12 + endCalendar.get(Calendar.MONTH) - startCalendar.get(Calendar.MONTH); + + if (monthDiff <= 24) { + isTestedPositiveWithin24Months = true; + } + return isTestedPositiveWithin24Months; + } }