Repository: hartwigmedical/actin
Pull request #1029: ACTIN-1673: Implement logic for CURRENTLY_GETS_CHEMORADIOTHERAPY_OF_TYPE_X_CHEMOTHERAPY_AND_AT_LEAST_Y_CYCLES
Author: login: zoehehartwig
Created: 2025-03-13T18:30:26Z | Merged: 2025-03-26T14:04:03Z

Hi, I still need some details on:
when it would resolve to undetermined and 
if these treatments need to be ongoing (since this is named currently and not has had)


Diff:
diff --git a/algo/README.md b/algo/README.md
index 972430fabc..5b609a2055 100644
--- a/algo/README.md
+++ b/algo/README.md
@@ -175,6 +175,7 @@ The following criterion algorithms ('rules') are available:
 
 | Rule                                                                                       | When does a patient pass evaluation?                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Note                                                                                                                                                                                                                                                              |
 |--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| CURRENTLY_GETS_CHEMORADIOTHERAPY_OF_TYPE_X_CHEMOTHERAPY_AND_AT_LEAST_Y_CYCLES              | Patient has a treatment history entry with categories of both chemotherapy and radiotherapy of type X and of at least Y cycles that is ongoing as of the reference date. Excludes treatments that appear to be stopped before the evaluation date based on stop date or the presence of later treatments.                                                                                                                                                                                                                                                          | Resolves to `UNDETERMINED` if any data is unknown but present data does not explicitly `FAIL`.                                                                                                                                                                    |
 | IS_NOT_ELIGIBLE_FOR_TREATMENT_WITH_CURATIVE_INTENT                                         | Currently resolves to `NOT_EVALUATED`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |                                                                                                                                                                                                                                                                   |
 | IS_ELIGIBLE_FOR_ON_LABEL_TREATMENT_X                                                       | CRC patients are evaluated using CrcDecisionTree and will resolve to `UNDETERMINED` if patient is eligible for treatment X. Osimertinib is evaluated for NSCLC patients and will `PASS` in EGFR activating mutation (except for EGFR exon20 insertion) or EGFR T790M with prior TKI gen 1/2 treatment and the patient did not have progression on Osimertinib and did not receive Osimertinib in the past 6 months. `WARN` in case the patient has already received treatment X (except when X is Osimertinib). Will resolve to `UNDETERMINED` in all other cases. |                                                                                                                                                                                                                                                                   |
 | IS_ELIGIBLE_FOR_RADIOTHERAPY                                                               | Currently resolves to `UNDETERMINED`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |                                                                                                                                                                                                                                                                   |
@@ -598,7 +599,6 @@ Notes:
 |-----------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
 | CURRENTLY_GETS_NAME_X_MEDICATION                                                  | Medication > name like %X% and status is active                                                                                                                                                                                                | Resolve to `WARN` in case planned medication with name X                                                     |
 | CURRENTLY_GETS_CATEGORY_X_MEDICATION                                              | Medication > atc.<atcLevel>.code equal to <category> where <atcLevel> is a level defined below in 3] and <category> is X OR a member of the set of ATC codes derived from X using the table below in 4]; and status is active                  | Resolve to `WARN` in case planned medication with category X                                                 |
-| CURRENTLY_GETS_CHEMORADIOTHERAPY_OF_TYPE_X_CHEMOTHERAPY_AND_AT_LEAST_Y_CYCLES     | Currently resolves to `UNDETERMINED`                                                                                                                                                                                                           |                                                                                                              |
 | HAS_RECEIVED_CATEGORY_X_MEDICATION_ WITHIN_Y_WEEKS                                | Medication > atc.<atcLevel>.code equal to <category> where <atcLevel> is a level defined below in 3] and <category> is X OR a member of the set of ATC codes derived from X using the table below in 4]; and active OR stopDate within Y weeks | `UNDETERMINED` in case Y would require stop dates to be prior to ACTIN registration date.                    |
 | CURRENTLY_GETS_POTENTIALLY_QT_PROLONGATING_MEDICATION                             | Medication > qtProlongatingRisk contains type of "Known", "Potential" or "Conditional" and status is active                                                                                                                                    | Resolve to `WARN` in case planned QT prolongating medication                                                 |
 | CURRENTLY_GETS_MEDICATION_INDUCING_ANY_CYP                                        | Medication > cypInteractions.type is "Inducer" and status is active                                                                                                                                                                            | Resolve to `WARN` in case planned CYP inducing medication                                                    |                                                                                           
diff --git a/algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/medication/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt b/algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/medication/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
deleted file mode 100644
index 635c48dc73..0000000000
--- a/algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/medication/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.hartwig.actin.algo.evaluation.medication
-
-import com.hartwig.actin.algo.evaluation.EvaluationFactory
-import com.hartwig.actin.algo.evaluation.EvaluationFunction
-import com.hartwig.actin.datamodel.PatientRecord
-import com.hartwig.actin.datamodel.algo.Evaluation
-import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
-
-class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
-    private val selector: MedicationSelector,
-    private val type: TreatmentType,
-    private val minCycles: Int
-) : EvaluationFunction {
-
-    override fun evaluate(record: PatientRecord): Evaluation {
-        return EvaluationFactory.undetermined(
-            "Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles"
-        )
-    }
-}
\ No newline at end of file
diff --git a/algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/medication/MedicationRuleMapper.kt b/algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/medication/MedicationRuleMapper.kt
index 42d243e43f..63d7d5929b 100644
--- a/algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/medication/MedicationRuleMapper.kt
+++ b/algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/medication/MedicationRuleMapper.kt
@@ -19,8 +19,6 @@ class MedicationRuleMapper(resources: RuleMappingResources) : RuleMapper(resourc
         return mapOf(
             EligibilityRule.CURRENTLY_GETS_NAME_X_MEDICATION to getsActiveMedicationWithConfiguredNameCreator(),
             EligibilityRule.CURRENTLY_GETS_CATEGORY_X_MEDICATION to getsActiveMedicationWithCategoryCreator(),
-            EligibilityRule.CURRENTLY_GETS_CHEMORADIOTHERAPY_OF_TYPE_X_CHEMOTHERAPY_AND_AT_LEAST_Y_CYCLES to
-                    getsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesCreator(),
             EligibilityRule.HAS_RECEIVED_CATEGORY_X_MEDICATION_WITHIN_Y_WEEKS to hasRecentlyReceivedMedicationOfAtcLevelCreator(),
             EligibilityRule.CURRENTLY_GETS_POTENTIALLY_QT_PROLONGATING_MEDICATION to getsQTProlongatingMedicationCreator(),
             EligibilityRule.CURRENTLY_GETS_MEDICATION_INDUCING_ANY_CYP to getsAnyCYPInducingMedicationCreator(),
@@ -53,13 +51,6 @@ class MedicationRuleMapper(resources: RuleMappingResources) : RuleMapper(resourc
         }
     }
 
-    private fun getsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesCreator(): FunctionCreator {
-        return { function: EligibilityFunction ->
-            val (chemotherapyType, minCycles) = functionInputResolver().createOneTreatmentTypeOneIntegerInput(function)
-            CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(selector, chemotherapyType, minCycles)
-        }
-    }
-
     private fun hasRecentlyReceivedMedicationOfAtcLevelCreator(): FunctionCreator {
         return { function: EligibilityFunction ->
             val (categoryInput, integerInput) = functionInputResolver().createOneMedicationCategoryOneIntegerInput(function)
diff --git a/algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt b/algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
new file mode 100644
index 0000000000..aa3bc09a6a
--- /dev/null
+++ b/algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
@@ -0,0 +1,59 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.algo.evaluation.util.DateComparison
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryEntry
+import java.time.LocalDate
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int,
+    private val referenceDate: LocalDate
+) : EvaluationFunction {
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val latestStart = record.oncologicalHistory.map { LocalDate.of(it.startYear ?: 0, it.startMonth ?: 1, 1) }
+            .sortedDescending()
+            .firstOrNull { !it.isAfter(referenceDate) }
+
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            val matchingCategories = it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY))
+
+            val enoughCyclesAndOngoingTreatment = enoughCyclesAndOngoingTreatment(it, latestStart)
+
+            when {
+                matchingCategories && it.isOfType(type) == true && enoughCyclesAndOngoingTreatment == true -> true
+                !matchingCategories && it.categories().isNotEmpty() ||
+                        it.isOfType(type) == false || enoughCyclesAndOngoingTreatment == false -> false
+
+                else -> null
+            }
+        }
+
+        return when {
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("The patient doesn't currently get chemoradiotherapy with $type chemo")
+            true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            else -> EvaluationFactory.fail("No chemoradiotherapy with $type with at least $minCycles is currently received")
+        }
+    }
+
+    private fun enoughCyclesAndOngoingTreatment(treatmentHistoryEntry: TreatmentHistoryEntry, latestStart: LocalDate?): Boolean? {
+        val treatmentHistoryDetails = treatmentHistoryEntry.treatmentHistoryDetails
+        return treatmentHistoryDetails?.cycles?.let { cycles ->
+            val appearsOngoing = with(treatmentHistoryDetails) {
+                val treatmentNotStopped = DateComparison.isAfterDate(referenceDate, stopYear, stopMonth)
+                treatmentNotStopped == true ||
+                    (treatmentNotStopped == null && latestStart?.let {
+                        DateComparison.isAfterDate(latestStart, treatmentHistoryEntry.startYear, treatmentHistoryEntry.startMonth)
+                    } != false)
+            }
+            cycles >= minCycles && appearsOngoing
+        }
+    }
+}
\ No newline at end of file
diff --git a/algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/TreatmentRuleMapper.kt b/algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/TreatmentRuleMapper.kt
index f8552e6416..1f26c36b72 100644
--- a/algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/TreatmentRuleMapper.kt
+++ b/algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/TreatmentRuleMapper.kt
@@ -19,6 +19,8 @@ class TreatmentRuleMapper(resources: RuleMappingResources) : RuleMapper(resource
 
     override fun createMappings(): Map<EligibilityRule, FunctionCreator> {
         return mapOf(
+            EligibilityRule.CURRENTLY_GETS_CHEMORADIOTHERAPY_OF_TYPE_X_CHEMOTHERAPY_AND_AT_LEAST_Y_CYCLES to
+                    getsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesCreator(),
             EligibilityRule.IS_NOT_ELIGIBLE_FOR_TREATMENT_WITH_CURATIVE_INTENT to { IsNotEligibleForCurativeTreatment() },
             EligibilityRule.IS_ELIGIBLE_FOR_ON_LABEL_TREATMENT_X to isEligibleForOnLabelTreatmentCreator(),
             EligibilityRule.IS_ELIGIBLE_FOR_RADIOTHERAPY to { IsEligibleForRadiotherapy() },
@@ -121,6 +123,13 @@ class TreatmentRuleMapper(resources: RuleMappingResources) : RuleMapper(resource
         )
     }
 
+    private fun getsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesCreator(): FunctionCreator {
+        return { function: EligibilityFunction ->
+            val (chemotherapyType, minCycles) = functionInputResolver().createOneTreatmentTypeOneIntegerInput(function)
+            CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(chemotherapyType, minCycles, referenceDate)
+        }
+    }
+
     private fun isEligibleForOnLabelTreatmentCreator(): FunctionCreator {
         return { function: EligibilityFunction ->
             val treatmentName = functionInputResolver().createOneSpecificTreatmentInput(function)
diff --git a/algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt b/algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
new file mode 100644
index 0000000000..e2d6af92e9
--- /dev/null
+++ b/algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
@@ -0,0 +1,179 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationAssert
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.EvaluationResult
+import com.hartwig.actin.datamodel.clinical.TreatmentTestFactory
+import com.hartwig.actin.datamodel.clinical.treatment.DrugType
+import com.hartwig.actin.datamodel.clinical.treatment.Radiotherapy
+import com.hartwig.actin.datamodel.clinical.treatment.RadiotherapyType
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryEntry
+import java.time.LocalDate
+import org.junit.Test
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest {
+    private val MIN_CYCLES = 5
+    private val MATCHING_TYPE = DrugType.ALK_INHIBITOR
+    private val RADIOTHERAPY = Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+    private val CHEMOTHERAPY = TreatmentTestFactory.drugTreatment("Alk inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(MATCHING_TYPE))
+    private val REFERENCE_YEAR = 2024
+
+    @Test
+    fun `Should fail if there are no treatments`() {
+        val record = TreatmentTestFactory.withTreatmentHistory(emptyList())
+        assertResultForPatient(EvaluationResult.FAIL, MATCHING_TYPE, record)
+    }
+
+    @Test
+    fun `Should pass if there is a chemotherapy of matching type with sufficient cycles`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(CHEMOTHERAPY, RADIOTHERAPY),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = MIN_CYCLES)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.PASS, MATCHING_TYPE, record)
+    }
+
+    @Test
+    fun `Should fail if there is only radiotherapy`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                RADIOTHERAPY
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = MIN_CYCLES)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, RadiotherapyType.CYBERKNIFE, record)
+    }
+
+    @Test
+    fun `Should be undetermined for current chemoradiotherapy with matching cycles and unknown chemo type`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Null type", TreatmentCategory.CHEMOTHERAPY, emptySet()),
+                RADIOTHERAPY
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = MIN_CYCLES)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.UNDETERMINED, MATCHING_TYPE, record)
+    }
+
+    @Test
+    fun `Should fail for matching treatment with insufficient cycles`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                CHEMOTHERAPY,
+                RADIOTHERAPY
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = MIN_CYCLES - 1)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, MATCHING_TYPE, record)
+    }
+
+    @Test
+    fun `Should fail if the end date is null but there is a newer treatment`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                CHEMOTHERAPY,
+                RADIOTHERAPY
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = REFERENCE_YEAR - 1
+        )
+        val newerTreatment = TreatmentHistoryEntry(
+            treatments = setOf(TreatmentTestFactory.drugTreatment("Ablation", TreatmentCategory.ABLATION)),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = REFERENCE_YEAR
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment, newerTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, MATCHING_TYPE, record)
+    }
+
+    @Test
+    fun `Should pass if the end date is null but there is another treatment with an unknown start date`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                CHEMOTHERAPY,
+                RADIOTHERAPY
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = MIN_CYCLES),
+            startYear = REFERENCE_YEAR - 1
+        )
+        val treatmentUnknownStartDate = TreatmentHistoryEntry(
+            treatments = setOf(TreatmentTestFactory.drugTreatment("Ablation", TreatmentCategory.ABLATION)),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = MIN_CYCLES)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment, treatmentUnknownStartDate))
+        assertResultForPatient(EvaluationResult.PASS, MATCHING_TYPE, record)
+    }
+
+    @Test
+    fun `Should be undetermined if there is a matching treatment with unknown cycles`() {
+        val matchingTreatmentNullCycles = TreatmentHistoryEntry(
+            treatments = setOf(
+                CHEMOTHERAPY,
+                RADIOTHERAPY
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatmentNullCycles))
+        assertResultForPatient(EvaluationResult.UNDETERMINED, MATCHING_TYPE, record)
+    }
+
+    @Test
+    fun `Should fail for matching treatment with an end date before the reference date`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                CHEMOTHERAPY,
+                RADIOTHERAPY
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = REFERENCE_YEAR - 2, cycles = MIN_CYCLES)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, MATCHING_TYPE, record)
+    }
+
+    @Test
+    fun `Should fail if the category matches but the type is wrong`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Abl inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ABL_INHIBITOR)),
+                RADIOTHERAPY
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = MIN_CYCLES)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, MATCHING_TYPE, record)
+    }
+
+    @Test
+    fun `Should pass if there is a matching treatment with a null end date and a newer treatment that hasn't started as of the reference date`() {
+        val matchingTreatmentNullEndDate = TreatmentHistoryEntry(
+            treatments = setOf(
+                CHEMOTHERAPY,
+                RADIOTHERAPY
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = MIN_CYCLES),
+            startYear = REFERENCE_YEAR - 1
+        )
+        val treatmentStartDateAfterReferenceDate = TreatmentHistoryEntry(
+            treatments = setOf(TreatmentTestFactory.drugTreatment("Ablation", TreatmentCategory.ABLATION)),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = MIN_CYCLES),
+            startYear = REFERENCE_YEAR + 1
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatmentNullEndDate, treatmentStartDateAfterReferenceDate))
+        assertResultForPatient(EvaluationResult.PASS, MATCHING_TYPE, record)
+    }
+
+    private fun assertResultForPatient(evaluationResult: EvaluationResult, type: TreatmentType, record: PatientRecord) {
+        val evaluation = CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+            type, MIN_CYCLES, LocalDate.of(REFERENCE_YEAR, 1, 1)
+        ).evaluate(record)
+        return EvaluationAssert.assertEvaluation(evaluationResult, evaluation)
+    }
+}
\ No newline at end of file
diff --git a/datamodel/src/main/kotlin/com/hartwig/actin/datamodel/trial/EligibilityRule.kt b/datamodel/src/main/kotlin/com/hartwig/actin/datamodel/trial/EligibilityRule.kt
index fff65245e9..5fe4afa82a 100644
--- a/datamodel/src/main/kotlin/com/hartwig/actin/datamodel/trial/EligibilityRule.kt
+++ b/datamodel/src/main/kotlin/com/hartwig/actin/datamodel/trial/EligibilityRule.kt
@@ -97,6 +97,7 @@ enum class EligibilityRule(val input: FunctionInput?, val ihcProtein: String? =
     HAS_SYMPTOMS_OF_PRIMARY_TUMOR_IN_SITU(FunctionInput.NONE),
 
     // Rules related to previous cancer treatments
+    CURRENTLY_GETS_CHEMORADIOTHERAPY_OF_TYPE_X_CHEMOTHERAPY_AND_AT_LEAST_Y_CYCLES(FunctionInput.ONE_TREATMENT_TYPE_ONE_INTEGER),
     IS_NOT_ELIGIBLE_FOR_TREATMENT_WITH_CURATIVE_INTENT(FunctionInput.NONE),
     IS_ELIGIBLE_FOR_ON_LABEL_TREATMENT_X(FunctionInput.ONE_SPECIFIC_TREATMENT),
     IS_ELIGIBLE_FOR_RADIOTHERAPY(FunctionInput.NONE),
@@ -430,7 +431,6 @@ enum class EligibilityRule(val input: FunctionInput?, val ihcProtein: String? =
     // Rules depending on current medication
     CURRENTLY_GETS_NAME_X_MEDICATION(FunctionInput.ONE_STRING),
     CURRENTLY_GETS_CATEGORY_X_MEDICATION(FunctionInput.ONE_MEDICATION_CATEGORY),
-    CURRENTLY_GETS_CHEMORADIOTHERAPY_OF_TYPE_X_CHEMOTHERAPY_AND_AT_LEAST_Y_CYCLES(FunctionInput.ONE_TREATMENT_TYPE_ONE_INTEGER),
     HAS_RECEIVED_CATEGORY_X_MEDICATION_WITHIN_Y_WEEKS(FunctionInput.ONE_MEDICATION_CATEGORY_ONE_INTEGER),
     CURRENTLY_GETS_POTENTIALLY_QT_PROLONGATING_MEDICATION(FunctionInput.NONE),
     CURRENTLY_GETS_MEDICATION_INDUCING_ANY_CYP(FunctionInput.NONE),


Reviews:
user: login: jbartletthmf
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T19:56:01Z
user: login: spdeleeuw
body: Hi Zoe, as mentioned in Slack as well: 
We can assume that if it's the most recent line and does not have an end date before the current date (or no end date at all) that the patient is still using it (i.e. ongoing).

All undetermined cases would basically be the cases where you do not have all required data to do a clear evaluation. For instance, when cycles is null, or when you have multiple entries with start and end date null (it's not expected that a patient uses two treatment regimens at the same time, so without any dates you wouldn't know which of those is currently being used and which was used before).
Or when you know it's treatmentCategory Chemotherapy but you don't know the chemotherapy drugType.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T11:03:26Z
user: login: spdeleeuw
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T09:37:08Z
user: login: jbartletthmf
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T13:34:57Z
user: login: jbartletthmf
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T14:13:18Z
user: login: jbartletthmf
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T20:39:59Z
user: login: jbartletthmf
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T15:37:50Z
user: login: jbartletthmf
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T16:38:15Z
user: login: zoehehartwig
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T14:16:25Z
user: login: jbartletthmf
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T14:22:41Z
user: login: jbartletthmf
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T14:41:58Z
user: login: zoehehartwig
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T15:51:02Z
user: login: zoehehartwig
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T16:02:51Z
user: login: jbartletthmf
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T19:50:13Z
user: login: jbartletthmf
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T21:23:48Z
user: login: jbartletthmf
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T19:57:29Z
user: login: jbartletthmf
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T20:12:48Z
user: login: spdeleeuw
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T09:11:41Z

Review comments:
diff_hunk: @@ -0,0 +1,38 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationAssert
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.EvaluationResult
+import com.hartwig.actin.datamodel.clinical.TreatmentTestFactory
+import com.hartwig.actin.datamodel.clinical.treatment.DrugType
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryEntry
+import org.junit.Test
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest {
+    @Test
+    fun `Fail if there is no treatments`() {
+        val record = TreatmentTestFactory.withTreatmentHistory(emptyList())
+        assert(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, 10, record)
+    }
+
+    @Test
+    fun `Pass if there is a match` () {
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: Our convention is for all test names to start with `Should`.
created_at: 2025-03-13T19:43:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -33,7 +33,7 @@ import org.junit.Test
 private const val PATIENT = "ACTN01029999"
 private val EXPECTED_CLINICAL_RECORD = "${resourceOnClasspath("clinical_record")}/$PATIENT.clinical.json"
 
-class ClinicalIngestionFeedAdapterTest {
+class S {
path: clinical/src/test/kotlin/com/hartwig/actin/clinical/ClinicalIngestionFeedAdapterTest.kt
user: login: jbartletthmf
body: Accident?
created_at: 2025-03-13T19:44:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -170,6 +170,11 @@ class ClinicalIngestionFeedAdapterTest {
             )
     }
 
+    @Test
+    fun `Should contain validation warning if there is no questionnaire` () {
+
+    }
path: clinical/src/test/kotlin/com/hartwig/actin/clinical/ClinicalIngestionFeedAdapterTest.kt
user: login: jbartletthmf
body: This looks related to another ticket. Sometimes it helps to focus on one task at a time 🙂 
created_at: 2025-03-13T19:44:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,30 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+
+        val matchingTreatments = record.oncologicalHistory.filter {
+            it.categories().intersect(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)).isNotEmpty() &&
+                    it.isOfType(type) == true &&
+                    it.treatmentHistoryDetails?.cycles != null &&
+                    it.treatmentHistoryDetails?.cycles!! > minCycles
+        }
+
+        return when {
+            matchingTreatments.isNotEmpty() -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            else -> EvaluationFactory.fail("No chemo radio therapy with $type with at least $minCycles is currently received")
+            //else -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles" )
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: I suspect we'll want to return undetermined when cycles, category, or type is unknown.
created_at: 2025-03-13T19:49:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,30 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+
+        val matchingTreatments = record.oncologicalHistory.filter {
+            it.categories().intersect(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)).isNotEmpty() &&
+                    it.isOfType(type) == true &&
+                    it.treatmentHistoryDetails?.cycles != null &&
+                    it.treatmentHistoryDetails?.cycles!! > minCycles
+        }
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: I don't think we use the `isOngoing` field (it's hard to rely on this since treatments could finish after the record is updated) but we could check that the treatment end date is either unknown or after the reference date.
created_at: 2025-03-13T19:51:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,30 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+
+        val matchingTreatments = record.oncologicalHistory.filter {
+            it.categories().intersect(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)).isNotEmpty() &&
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: This will allow all treatments that are chemo AND/OR radiotherapy. I think you should check that both are present here, e.g. `it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY))`.
created_at: 2025-03-13T19:55:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -175,6 +175,7 @@ The following criterion algorithms ('rules') are available:
 
 | Rule                                                                                       | When does a patient pass evaluation?                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | Note                                                                                                                                                                                                                                                              |
 |--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| CURRENTLY_GETS_CHEMORADIOTHERAPY_OF_TYPE_X_CHEMOTHERAPY_AND_AT_LEAST_Y_CYCLES              | Currently resolves to `UNDETERMINED`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |                                                                                                                                                                                                                                                                   |
path: algo/README.md
user: login: spdeleeuw
body: Okay for now since still WIP, but could you update the README before merging this PR?
created_at: 2025-03-14T10:55:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    private val now = java.time.LocalDate.now()
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: spdeleeuw
body: To make sure you could also run ACTIN historically, I think you should add the referenceDate as an argument to the class using the `referenceDateProvider()` from `RuleMapper`, instead of declaring this `now` val inside the class.

Correct @jbartletthmf ?
created_at: 2025-03-17T08:35:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    private val now = java.time.LocalDate.now()
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            when {
+                (it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == true) -> {
+                    true
+                }
+                !(it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) ||
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: spdeleeuw
body: I think you are negating rows 26-28 as a whole here because of the placement of the parentheses. I assume you only intended to negate row 26 here? Since negating `it.isOfType(type) == false` and `enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == false` results in the same statements as row 22 and 23
created_at: 2025-03-17T08:43:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    private val now = java.time.LocalDate.now()
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            when {
+                (it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == true) -> {
+                    true
+                }
+                !(it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) ||
+                        it.isOfType(type) == false ||
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == false) -> {
+                    false
+                }
+                else -> {
+                    null
+                }
+            }
+        }
+
+        return when {
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("No treatments received")
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: spdeleeuw
body: In an ideal message, the corresponding rule you are evaluating is immediately clear to the reader. I'd suggest always describing what you've evaluated as much as possible, without ending up with a too long message (in this case something like the patient doesn't currently get chemoradiotherapy with type X chemo (something like you did in the else branch). 
created_at: 2025-03-17T09:24:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    private val now = java.time.LocalDate.now()
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            when {
+                (it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == true) -> {
+                    true
+                }
+                !(it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) ||
+                        it.isOfType(type) == false ||
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == false) -> {
+                    false
+                }
+                else -> {
+                    null
+                }
+            }
+        }
+
+        return when {
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("No treatments received")
+            true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            else -> EvaluationFactory.fail("No chemo radio therapy with $type with at least $minCycles is currently received")
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: spdeleeuw
body: chemoradiotherapy
created_at: 2025-03-17T09:25:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    private val now = java.time.LocalDate.now()
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            when {
+                (it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == true) -> {
+                    true
+                }
+                !(it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) ||
+                        it.isOfType(type) == false ||
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == false) -> {
+                    false
+                }
+                else -> {
+                    null
+                }
+            }
+        }
+
+        return when {
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("No treatments received")
+            true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            else -> EvaluationFactory.fail("No chemo radio therapy with $type with at least $minCycles is currently received")
+        }
+    }
+
+    fun enoughCyclesAndOngoingTreatment(treatmentHistoryDetails: TreatmentHistoryDetails?): Boolean?{
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: spdeleeuw
body: function could be private and missing space between ? and {
created_at: 2025-03-17T09:26:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    private val now = java.time.LocalDate.now()
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            when {
+                (it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == true) -> {
+                    true
+                }
+                !(it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) ||
+                        it.isOfType(type) == false ||
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == false) -> {
+                    false
+                }
+                else -> {
+                    null
+                }
+            }
+        }
+
+        return when {
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("No treatments received")
+            true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            else -> EvaluationFactory.fail("No chemo radio therapy with $type with at least $minCycles is currently received")
+        }
+    }
+
+    fun enoughCyclesAndOngoingTreatment(treatmentHistoryDetails: TreatmentHistoryDetails?): Boolean?{
+        if (treatmentHistoryDetails == null)
+            return null
+
+        val cycles = treatmentHistoryDetails.cycles
+        if(cycles == null)
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: spdeleeuw
body: missing space between if and (
created_at: 2025-03-17T09:26:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    private val now = java.time.LocalDate.now()
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            when {
+                (it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == true) -> {
+                    true
+                }
+                !(it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) ||
+                        it.isOfType(type) == false ||
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == false) -> {
+                    false
+                }
+                else -> {
+                    null
+                }
+            }
+        }
+
+        return when {
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("No treatments received")
+            true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            else -> EvaluationFactory.fail("No chemo radio therapy with $type with at least $minCycles is currently received")
+        }
+    }
+
+    fun enoughCyclesAndOngoingTreatment(treatmentHistoryDetails: TreatmentHistoryDetails?): Boolean?{
+        if (treatmentHistoryDetails == null)
+            return null
+
+        val cycles = treatmentHistoryDetails.cycles
+        if(cycles == null)
+            return null
+        else if(cycles < minCycles)
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: spdeleeuw
body: missing space after if
created_at: 2025-03-17T09:26:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    private val now = java.time.LocalDate.now()
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            when {
+                (it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == true) -> {
+                    true
+                }
+                !(it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) ||
+                        it.isOfType(type) == false ||
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == false) -> {
+                    false
+                }
+                else -> {
+                    null
+                }
+            }
+        }
+
+        return when {
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("No treatments received")
+            true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            else -> EvaluationFactory.fail("No chemo radio therapy with $type with at least $minCycles is currently received")
+        }
+    }
+
+    fun enoughCyclesAndOngoingTreatment(treatmentHistoryDetails: TreatmentHistoryDetails?): Boolean?{
+        if (treatmentHistoryDetails == null)
+            return null
+
+        val cycles = treatmentHistoryDetails.cycles
+        if(cycles == null)
+            return null
+        else if(cycles < minCycles)
+            return false
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: spdeleeuw
body: Alternative for rows 46-53:
```
 val cycles = treatmentHistoryDetails?.cycles ?: return null
 if (cycles < minCycles) return false
```

created_at: 2025-03-17T09:34:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    private val now = java.time.LocalDate.now()
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            when {
+                (it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == true) -> {
+                    true
+                }
+                !(it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) ||
+                        it.isOfType(type) == false ||
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == false) -> {
+                    false
+                }
+                else -> {
+                    null
+                }
+            }
+        }
+
+        return when {
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("No treatments received")
+            true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            else -> EvaluationFactory.fail("No chemo radio therapy with $type with at least $minCycles is currently received")
+        }
+    }
+
+    fun enoughCyclesAndOngoingTreatment(treatmentHistoryDetails: TreatmentHistoryDetails?): Boolean?{
+        if (treatmentHistoryDetails == null)
+            return null
+
+        val cycles = treatmentHistoryDetails.cycles
+        if(cycles == null)
+            return null
+        else if(cycles < minCycles)
+            return false
+
+        val stopYear = treatmentHistoryDetails.stopYear
+        val stopMonth = treatmentHistoryDetails.stopMonth
+
+        return when {
+            stopYear == null -> null
+            now.year < stopYear -> true
+            now.year > stopYear -> false
+            stopMonth == null -> null
+            now.monthValue <= stopMonth -> true
+            else -> false
+        }
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: spdeleeuw
body: I think having no stop date (i.e. stopYear and stopMonth null) should be considered as ongoing. With the current implementation stopdate null always leads to undetermined.
created_at: 2025-03-17T09:36:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    private val now = java.time.LocalDate.now()
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: Correct!

@zoehehartwig, we create a date provider when we start the `TreatmentMatcherApplication` according to the arguments passed in from the command line: `com/hartwig/actin/algo/TreatmentMatcherApplication.kt:62`:
```kotlin
        val referenceDateProvider = ReferenceDateProviderFactory.create(patient, config.runHistorically)
```
This allows us to show results relative to the patient's registration date instead of the current date, which can be useful when re-evaluating old patients.
created_at: 2025-03-17T13:34:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    private val now = java.time.LocalDate.now()
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            when {
+                (it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == true) -> {
+                    true
+                }
+                !(it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) ||
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: I agree about the placement. Should we also check that `categories()` is not empty here? If an entry has no categories, it's probably an unknown trial treatment.
created_at: 2025-03-17T13:39:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -6,25 +6,62 @@ import com.hartwig.actin.datamodel.PatientRecord
 import com.hartwig.actin.datamodel.algo.Evaluation
 import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
 import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
 
 class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
     private val type: TreatmentType,
     private val minCycles: Int
 ) : EvaluationFunction {
 
-    override fun evaluate(record: PatientRecord): Evaluation {
+    private val now = java.time.LocalDate.now()
 
-        val matchingTreatments = record.oncologicalHistory.filter {
-            it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
-                    it.isOfType(type) == true &&
-                    it.treatmentHistoryDetails?.cycles != null &&
-                    it.treatmentHistoryDetails?.cycles!! > minCycles
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            when {
+                (it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == true) -> {
+                    true
+                }
+                !(it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) ||
+                        it.isOfType(type) == false ||
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == false) -> {
+                    false
+                }
+                else -> {
+                    null
+                }
+            }
         }
 
         return when {
-            matchingTreatments.isNotEmpty() -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("No treatments received")
+            true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
             else -> EvaluationFactory.fail("No chemo radio therapy with $type with at least $minCycles is currently received")
-            //else -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles" )
+        }
+    }
+
+    fun enoughCyclesAndOngoingTreatment(treatmentHistoryDetails: TreatmentHistoryDetails?): Boolean?{
+        if (treatmentHistoryDetails == null)
+            return null
+
+        val cycles = treatmentHistoryDetails.cycles
+        if(cycles == null)
+            return null
+        else if(cycles < minCycles)
+            return false
+
+        val stopYear = treatmentHistoryDetails.stopYear
+        val stopMonth = treatmentHistoryDetails.stopMonth
+
+        return when {
+            stopYear == null -> null
+            now.year < stopYear -> true
+            now.year > stopYear -> false
+            stopMonth == null -> null
+            now.monthValue <= stopMonth -> true
+            else -> false
         }
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: I try to avoid putting multiple `return` statements in a single code block because it can make code harder to follow. It's not that different from using `break` or even `goto`. There are also some useful date comparison functions you can use here:
```suggestion
        return treatmentHistoryDetails?.cycles?.let { cycles ->
            val appearsOngoing = with (treatmentHistoryDetails) {
                DateComparison.isAfterDate(referenceDate, stopYear, stopMonth) != false
            }
            cycles >= minCycles && appearsOngoing
        }
```

created_at: 2025-03-17T14:00:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,65 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationAssert
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.EvaluationResult
+import com.hartwig.actin.datamodel.clinical.TreatmentTestFactory
+import com.hartwig.actin.datamodel.clinical.treatment.DrugType
+import com.hartwig.actin.datamodel.clinical.treatment.Radiotherapy
+import com.hartwig.actin.datamodel.clinical.treatment.RadiotherapyType
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryEntry
+import org.junit.Test
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest {
+    @Test
+    fun `Fail if there is no treatments`() {
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: `Should fail if there are no treatments`
created_at: 2025-03-17T14:03:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,65 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationAssert
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.EvaluationResult
+import com.hartwig.actin.datamodel.clinical.TreatmentTestFactory
+import com.hartwig.actin.datamodel.clinical.treatment.DrugType
+import com.hartwig.actin.datamodel.clinical.treatment.Radiotherapy
+import com.hartwig.actin.datamodel.clinical.treatment.RadiotherapyType
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryEntry
+import org.junit.Test
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest {
+    @Test
+    fun `Fail if there is no treatments`() {
+        val record = TreatmentTestFactory.withTreatmentHistory(emptyList())
+        assert(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, 10, record)
+    }
+
+    @Test
+    fun `Should pass if there is a match` () {
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: `Should pass if there is a chemotherapy of matching type with sufficient cycles`
created_at: 2025-03-17T14:04:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    private val now = java.time.LocalDate.now()
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: I'm not sure how best to consider whether another line is started after a treatment with unknown stop date. One option would be to first find the maximum start year/month of all treatment entries, and then pass that in to `enoughCyclesAndOngoingTreatment` below. Kotlin has some useful functions for this, like https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/max-of-or-null.html.
created_at: 2025-03-17T14:08:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -13,55 +15,47 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
     private val minCycles: Int
 ) : EvaluationFunction {
 
-    private val now = java.time.LocalDate.now()
-
     override fun evaluate(record: PatientRecord): Evaluation {
         val treatmentMatches = record.oncologicalHistory.groupBy {
             when {
                 (it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
                         it.isOfType(type) == true &&
-                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == true) -> {
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails, record) == true) -> {
                     true
                 }
-                !(it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) ||
+
+                (!it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
+                        it.categories().isNotEmpty() ||
                         it.isOfType(type) == false ||
-                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == false) -> {
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails, record) == false) -> {
                     false
                 }
+
                 else -> {
                     null
                 }
             }
         }
 
         return when {
-            treatmentMatches.isEmpty() -> EvaluationFactory.fail("No treatments received")
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("The patient doesn't currently get chemoradiotherapy with $type chemo")
             true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
             null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
-            else -> EvaluationFactory.fail("No chemo radio therapy with $type with at least $minCycles is currently received")
+            else -> EvaluationFactory.fail("No chemoradio therapy with $type with at least $minCycles is currently received")
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: Should be "chemoradiotherapy'
created_at: 2025-03-18T20:18:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -13,55 +15,47 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
     private val minCycles: Int
 ) : EvaluationFunction {
 
-    private val now = java.time.LocalDate.now()
-
     override fun evaluate(record: PatientRecord): Evaluation {
         val treatmentMatches = record.oncologicalHistory.groupBy {
             when {
                 (it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
                         it.isOfType(type) == true &&
-                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == true) -> {
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails, record) == true) -> {
                     true
                 }
-                !(it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) ||
+
+                (!it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
+                        it.categories().isNotEmpty() ||
                         it.isOfType(type) == false ||
-                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == false) -> {
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails, record) == false) -> {
                     false
                 }
+
                 else -> {
                     null
                 }
             }
         }
 
         return when {
-            treatmentMatches.isEmpty() -> EvaluationFactory.fail("No treatments received")
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("The patient doesn't currently get chemoradiotherapy with $type chemo")
             true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
             null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
-            else -> EvaluationFactory.fail("No chemo radio therapy with $type with at least $minCycles is currently received")
+            else -> EvaluationFactory.fail("No chemoradio therapy with $type with at least $minCycles is currently received")
         }
     }
 
-    fun enoughCyclesAndOngoingTreatment(treatmentHistoryDetails: TreatmentHistoryDetails?): Boolean?{
-        if (treatmentHistoryDetails == null)
-            return null
+    private fun enoughCyclesAndOngoingTreatment(treatmentHistoryDetails: TreatmentHistoryDetails?, record: PatientRecord): Boolean? {
+        val referenceDateProvider = ReferenceDateProviderFactory.create(record, true).date()
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: Within this class, we don't know if the user is running against current or historic patients (nor should we). Instead, we should pass in `referenceDate` to its constructor in the `TreatmentRuleMapper`.
created_at: 2025-03-18T20:23:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -13,55 +15,47 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
     private val minCycles: Int
 ) : EvaluationFunction {
 
-    private val now = java.time.LocalDate.now()
-
     override fun evaluate(record: PatientRecord): Evaluation {
         val treatmentMatches = record.oncologicalHistory.groupBy {
             when {
                 (it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
                         it.isOfType(type) == true &&
-                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == true) -> {
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails, record) == true) -> {
                     true
                 }
-                !(it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) ||
+
+                (!it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
+                        it.categories().isNotEmpty() ||
                         it.isOfType(type) == false ||
-                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails) == false) -> {
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails, record) == false) -> {
                     false
                 }
+
                 else -> {
                     null
                 }
             }
         }
 
         return when {
-            treatmentMatches.isEmpty() -> EvaluationFactory.fail("No treatments received")
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("The patient doesn't currently get chemoradiotherapy with $type chemo")
             true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
             null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
-            else -> EvaluationFactory.fail("No chemo radio therapy with $type with at least $minCycles is currently received")
+            else -> EvaluationFactory.fail("No chemoradio therapy with $type with at least $minCycles is currently received")
         }
     }
 
-    fun enoughCyclesAndOngoingTreatment(treatmentHistoryDetails: TreatmentHistoryDetails?): Boolean?{
-        if (treatmentHistoryDetails == null)
-            return null
+    private fun enoughCyclesAndOngoingTreatment(treatmentHistoryDetails: TreatmentHistoryDetails?, record: PatientRecord): Boolean? {
+        val referenceDateProvider = ReferenceDateProviderFactory.create(record, true).date()
 
-        val cycles = treatmentHistoryDetails.cycles
-        if(cycles == null)
+        if (treatmentHistoryDetails == null)
             return null
-        else if(cycles < minCycles)
-            return false
-
-        val stopYear = treatmentHistoryDetails.stopYear
-        val stopMonth = treatmentHistoryDetails.stopMonth
 
-        return when {
-            stopYear == null -> null
-            now.year < stopYear -> true
-            now.year > stopYear -> false
-            stopMonth == null -> null
-            now.monthValue <= stopMonth -> true
-            else -> false
+        return treatmentHistoryDetails.cycles?.let { cycles ->
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: If you use a null-safe call here (`treatmentHistoryDetails?.cycles`), you could drop the extra null check for `treatmentHistoryDetails` above.
created_at: 2025-03-18T20:25:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    private val now = java.time.LocalDate.now()
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: This hasn't been addressed yet. Could we use the latest start year/month from the treatment history to potentially eliminate some previous treatments that presumably aren't ongoing? e.g.
```kotlin
val latestStart = record.oncologicalHistory.maxOfOrNull { LocalDate.of(it.startYear ?: 0, it.startMonth ?: 1, 1) }
```
Then, when evaluating `appearsOngoing`, we can add:
```kotlin
DateComparison.isAfterDate(latestStart, startYear, startMonth) != false
```
created_at: 2025-03-18T20:36:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,61 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.calendar.ReferenceDateProviderFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.algo.evaluation.util.DateComparison
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int
+) : EvaluationFunction {
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            when {
+                (it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY)) &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails, record) == true) -> {
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: You could define a `val` for `enoughCyclesAndOngoingTreatment` since it's referenced in 2 places. Same for `categories`.
created_at: 2025-03-18T20:38:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -41,19 +43,14 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
             treatmentMatches.isEmpty() -> EvaluationFactory.fail("The patient doesn't currently get chemoradiotherapy with $type chemo")
             true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
             null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
-            else -> EvaluationFactory.fail("No chemoradio therapy with $type with at least $minCycles is currently received")
+            else -> EvaluationFactory.fail("No chemoradiotherapy with $type with at least $minCycles is currently received")
         }
     }
 
-    private fun enoughCyclesAndOngoingTreatment(treatmentHistoryDetails: TreatmentHistoryDetails?, record: PatientRecord): Boolean? {
-        val referenceDateProvider = ReferenceDateProviderFactory.create(record, true).date()
-
-        if (treatmentHistoryDetails == null)
-            return null
-
-        return treatmentHistoryDetails.cycles?.let { cycles ->
+    private fun enoughCyclesAndOngoingTreatment(treatmentHistoryDetails: TreatmentHistoryDetails?): Boolean? {
+        return treatmentHistoryDetails?.cycles?.let { cycles ->
             val appearsOngoing = with(treatmentHistoryDetails) {
-                DateComparison.isAfterDate(referenceDateProvider, stopYear, stopMonth) != false
+                DateComparison.isAfterDate(referenceDate, stopYear ?: 2200, stopMonth ?: 12) != false
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: You shouldn't use `?:` here because `isAfterDate` is null-safe by design. It's meant to compare years first if available, then months if available and necessary. If it can't make a clear determination, it will return null, which is handled by your `!= false` comparison.
created_at: 2025-03-19T15:30:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -59,7 +60,11 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
     }
 
     private fun assert(evaluationResult: EvaluationResult, type: TreatmentType, minCycles: Int, record: PatientRecord) {
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: We shouldn't override a stdlib function here (https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/assert.html), and the name could be more descriptive. How about `assertResultForPatient`?
created_at: 2025-03-19T15:34:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -59,7 +60,11 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
     }
 
     private fun assert(evaluationResult: EvaluationResult, type: TreatmentType, minCycles: Int, record: PatientRecord) {
-        val evaluation = CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(type, minCycles).evaluate(record)
+        val evaluation = CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+            type,
+            minCycles,
+            ReferenceDateProviderFactory.create(record, true).date()
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: We typically just pass in a static `LocalDate` for predictable testing instead of going through the provider.
created_at: 2025-03-19T15:35:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -12,22 +11,25 @@ import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDe
 
 class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
     private val type: TreatmentType,
-    private val minCycles: Int
+    private val minCycles: Int,
+    private val referenceDate: java.time.LocalDate
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: Can we `import java.time.LocalDate` at the top so we can just specify `LocalDate` here?
created_at: 2025-03-19T15:37:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -8,17 +8,18 @@ import com.hartwig.actin.datamodel.algo.Evaluation
 import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
 import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
 import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+import java.time.LocalDate
 
 class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
     private val type: TreatmentType,
     private val minCycles: Int,
-    private val referenceDate: java.time.LocalDate
+    private val referenceDate: LocalDate
 ) : EvaluationFunction {
 
     override fun evaluate(record: PatientRecord): Evaluation {
         val treatmentMatches = record.oncologicalHistory.groupBy {
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: The latest start should be calculated once and then referenced below in `enoughCyclesAndOngoingTreatment` instead of being recalculated each time.
created_at: 2025-03-19T16:19:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,64 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.algo.evaluation.util.DateComparison
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+import java.time.LocalDate
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int,
+    private val referenceDate: LocalDate
+) : EvaluationFunction {
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            val matchingCategories = it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY))
+            val enoughCyclesAndOngoingTreatment = enoughCyclesAndOngoingTreatment(it.treatmentHistoryDetails, record)
+            when {
+                (matchingCategories &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment == true) -> {
+                    true
+                }
+
+                (!matchingCategories &&
+                        it.categories().isNotEmpty() ||
+                        it.isOfType(type) == false ||
+                        enoughCyclesAndOngoingTreatment == false) -> {
+                    false
+                }
+
+                else -> {
+                    null
+                }
+            }
+        }
+
+        return when {
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("The patient doesn't currently get chemoradiotherapy with $type chemo")
+            true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            else -> EvaluationFactory.fail("No chemoradiotherapy with $type with at least $minCycles is currently received")
+        }
+    }
+
+    private fun enoughCyclesAndOngoingTreatment(treatmentHistoryDetails: TreatmentHistoryDetails?, record: PatientRecord): Boolean? {
+        return treatmentHistoryDetails?.cycles?.let { cycles ->
+            val appearsOngoing = with(treatmentHistoryDetails) {
+                val startYear = record.oncologicalHistory.maxOfOrNull { it.startYear ?: 0 }
+                val startMonth = record.oncologicalHistory.maxOfOrNull { it.startMonth ?: 1 }
+                val latestStart = LocalDate.of(startYear ?: 0, startMonth ?: 0, 1)
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: We don't want the max overall month, we want the max year-month. This should look more like `val latestStart = record.oncologicalHistory.maxOfOrNull { LocalDate.of(it.startYear ?: 0, it.startMonth ?: 1, 1) }` as I proposed earlier.
created_at: 2025-03-19T16:38:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,65 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.algo.evaluation.util.DateComparison
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryEntry
+import java.time.LocalDate
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int,
+    private val referenceDate: LocalDate
+) : EvaluationFunction {
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            val matchingCategories = it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY))
+
+            val latestStart = record.oncologicalHistory.maxOfOrNull { LocalDate.of(it.startYear ?: 0, it.startMonth ?: 1, 1) }
+            val enoughCyclesAndOngoingTreatment = enoughCyclesAndOngoingTreatment(it, latestStart)
+
+            when {
+                (matchingCategories &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment == true) -> {
+                    true
+                }
+
+                (!matchingCategories &&
+                        it.categories().isNotEmpty() ||
+                        it.isOfType(type) == false ||
+                        enoughCyclesAndOngoingTreatment == false) -> {
+                    false
+                }
+
+                else -> {
+                    null
+                }
+            }
+        }
+
+        return when {
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("The patient doesn't currently get chemoradiotherapy with $type chemo")
+            true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            else -> EvaluationFactory.fail("No chemoradiotherapy with $type with at least $minCycles is currently received")
+        }
+    }
+
+    private fun enoughCyclesAndOngoingTreatment(treatmentHistoryEntry: TreatmentHistoryEntry, latestStart: LocalDate?): Boolean? {
+        val treatmentHistoryDetails = treatmentHistoryEntry.treatmentHistoryDetails
+        return treatmentHistoryDetails?.cycles?.let { cycles ->
+            val appearsOngoing = with(treatmentHistoryDetails) {
+
+                DateComparison.isAfterDate(referenceDate, stopYear, stopMonth) != false &&
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: zoehehartwig
body: In regards to the &&, would there be a need to check that this is the most recent treatment history entry if we know the stop date and it is not before the reference date?
created_at: 2025-03-24T14:16:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,65 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.algo.evaluation.util.DateComparison
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryEntry
+import java.time.LocalDate
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int,
+    private val referenceDate: LocalDate
+) : EvaluationFunction {
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            val matchingCategories = it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY))
+
+            val latestStart = record.oncologicalHistory.maxOfOrNull { LocalDate.of(it.startYear ?: 0, it.startMonth ?: 1, 1) }
+            val enoughCyclesAndOngoingTreatment = enoughCyclesAndOngoingTreatment(it, latestStart)
+
+            when {
+                (matchingCategories &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment == true) -> {
+                    true
+                }
+
+                (!matchingCategories &&
+                        it.categories().isNotEmpty() ||
+                        it.isOfType(type) == false ||
+                        enoughCyclesAndOngoingTreatment == false) -> {
+                    false
+                }
+
+                else -> {
+                    null
+                }
+            }
+        }
+
+        return when {
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("The patient doesn't currently get chemoradiotherapy with $type chemo")
+            true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            else -> EvaluationFactory.fail("No chemoradiotherapy with $type with at least $minCycles is currently received")
+        }
+    }
+
+    private fun enoughCyclesAndOngoingTreatment(treatmentHistoryEntry: TreatmentHistoryEntry, latestStart: LocalDate?): Boolean? {
+        val treatmentHistoryDetails = treatmentHistoryEntry.treatmentHistoryDetails
+        return treatmentHistoryDetails?.cycles?.let { cycles ->
+            val appearsOngoing = with(treatmentHistoryDetails) {
+
+                DateComparison.isAfterDate(referenceDate, stopYear, stopMonth) != false &&
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: No, if the treatment stops after the reference date, we know it's ongoing.
created_at: 2025-03-24T14:22:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -175,6 +175,7 @@ The following criterion algorithms ('rules') are available:
 
 | Rule                                                                                       | When does a patient pass evaluation?                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Note                                                                                                                                                                                                                                                              |
 |--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| CURRENTLY_GETS_CHEMORADIOTHERAPY_OF_TYPE_X_CHEMOTHERAPY_AND_AT_LEAST_Y_CYCLES              | Patient currently receives chemotherapy and radiotherapy of type x and of at least a Y cycles                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | Resolves to `UNDETERMINED` if any data is unknown but present data does not explicitly `FAIL`. Checks if the treatment is currently expired and if there are more recent treatments to determine if it is currently being administered.                           |
path: algo/README.md
user: login: jbartletthmf
body: > Patient currently receives chemotherapy and radiotherapy of type x and of at least a Y cycles

This needs some proofreading. Also, the "pass" criteria in the README should do more than just restate the name of the rule. In this case, it would be helpful to indicate how we determine which treatments are current.
created_at: 2025-03-24T14:26:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,65 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.algo.evaluation.util.DateComparison
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryEntry
+import java.time.LocalDate
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int,
+    private val referenceDate: LocalDate
+) : EvaluationFunction {
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            val matchingCategories = it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY))
+
+            val latestStart = record.oncologicalHistory.maxOfOrNull { LocalDate.of(it.startYear ?: 0, it.startMonth ?: 1, 1) }
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: The key selector function passed into [groupBy](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/group-by.html) is applied to each element, so it's inefficient to do any calculations in this block that don't depend on the `TreatmentHistoryEntry` being classified (`it`). This line needs to be pulled outside of the `groupBy` block so it's only evaluated once.
created_at: 2025-03-24T14:31:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,65 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.algo.evaluation.util.DateComparison
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryEntry
+import java.time.LocalDate
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int,
+    private val referenceDate: LocalDate
+) : EvaluationFunction {
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            val matchingCategories = it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY))
+
+            val latestStart = record.oncologicalHistory.maxOfOrNull { LocalDate.of(it.startYear ?: 0, it.startMonth ?: 1, 1) }
+            val enoughCyclesAndOngoingTreatment = enoughCyclesAndOngoingTreatment(it, latestStart)
+
+            when {
+                (matchingCategories &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment == true) -> {
+                    true
+                }
+
+                (!matchingCategories &&
+                        it.categories().isNotEmpty() ||
+                        it.isOfType(type) == false ||
+                        enoughCyclesAndOngoingTreatment == false) -> {
+                    false
+                }
+
+                else -> {
+                    null
+                }
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: The `when` conditions don't need parentheses, and we could drop the braces around the simple return values if they fit on the same line:
```suggestion
                matchingCategories && it.isOfType(type) == true && enoughCyclesAndOngoingTreatment == true -> true

                !matchingCategories && it.categories().isNotEmpty() || it.isOfType(type) == false || enoughCyclesAndOngoingTreatment == false -> false

                else -> null
```
created_at: 2025-03-24T14:34:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,65 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.algo.evaluation.util.DateComparison
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryEntry
+import java.time.LocalDate
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int,
+    private val referenceDate: LocalDate
+) : EvaluationFunction {
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            val matchingCategories = it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY))
+
+            val latestStart = record.oncologicalHistory.maxOfOrNull { LocalDate.of(it.startYear ?: 0, it.startMonth ?: 1, 1) }
+            val enoughCyclesAndOngoingTreatment = enoughCyclesAndOngoingTreatment(it, latestStart)
+
+            when {
+                (matchingCategories &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment == true) -> {
+                    true
+                }
+
+                (!matchingCategories &&
+                        it.categories().isNotEmpty() ||
+                        it.isOfType(type) == false ||
+                        enoughCyclesAndOngoingTreatment == false) -> {
+                    false
+                }
+
+                else -> {
+                    null
+                }
+            }
+        }
+
+        return when {
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("The patient doesn't currently get chemoradiotherapy with $type chemo")
+            true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            else -> EvaluationFactory.fail("No chemoradiotherapy with $type with at least $minCycles is currently received")
+        }
+    }
+
+    private fun enoughCyclesAndOngoingTreatment(treatmentHistoryEntry: TreatmentHistoryEntry, latestStart: LocalDate?): Boolean? {
+        val treatmentHistoryDetails = treatmentHistoryEntry.treatmentHistoryDetails
+        return treatmentHistoryDetails?.cycles?.let { cycles ->
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: Could be inlined:
```suggestion
        return treatmentHistoryEntry.treatmentHistoryDetails?.cycles?.let { cycles ->
```
created_at: 2025-03-24T14:35:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationAssert
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.EvaluationResult
+import com.hartwig.actin.datamodel.clinical.TreatmentTestFactory
+import com.hartwig.actin.datamodel.clinical.treatment.DrugType
+import com.hartwig.actin.datamodel.clinical.treatment.Radiotherapy
+import com.hartwig.actin.datamodel.clinical.treatment.RadiotherapyType
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryEntry
+import java.time.LocalDate
+import org.junit.Test
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest {
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: We're missing a number of test cases here. How can we verify that all scenarios are handled correctly? For example:

1. matching treatment with insufficient cycles
2. matching treatment with future end date
3. matching treatment with unknown end date with other treatment line starting later
4. matching treatment with unknown end date with other treatment line with unknown start date
5. matching category with unknown type
6. ...
created_at: 2025-03-24T14:41:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,65 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationFactory
+import com.hartwig.actin.algo.evaluation.EvaluationFunction
+import com.hartwig.actin.algo.evaluation.util.DateComparison
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.Evaluation
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryEntry
+import java.time.LocalDate
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
+    private val type: TreatmentType,
+    private val minCycles: Int,
+    private val referenceDate: LocalDate
+) : EvaluationFunction {
+
+    override fun evaluate(record: PatientRecord): Evaluation {
+        val treatmentMatches = record.oncologicalHistory.groupBy {
+            val matchingCategories = it.categories().containsAll(setOf(TreatmentCategory.CHEMOTHERAPY, TreatmentCategory.RADIOTHERAPY))
+
+            val latestStart = record.oncologicalHistory.maxOfOrNull { LocalDate.of(it.startYear ?: 0, it.startMonth ?: 1, 1) }
+            val enoughCyclesAndOngoingTreatment = enoughCyclesAndOngoingTreatment(it, latestStart)
+
+            when {
+                (matchingCategories &&
+                        it.isOfType(type) == true &&
+                        enoughCyclesAndOngoingTreatment == true) -> {
+                    true
+                }
+
+                (!matchingCategories &&
+                        it.categories().isNotEmpty() ||
+                        it.isOfType(type) == false ||
+                        enoughCyclesAndOngoingTreatment == false) -> {
+                    false
+                }
+
+                else -> {
+                    null
+                }
+            }
+        }
+
+        return when {
+            treatmentMatches.isEmpty() -> EvaluationFactory.fail("The patient doesn't currently get chemoradiotherapy with $type chemo")
+            true in treatmentMatches -> EvaluationFactory.pass("Patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            null in treatmentMatches -> EvaluationFactory.undetermined("Undetermined if patient is currently getting chemoradiotherapy with $type chemotherapy and at least $minCycles cycles")
+            else -> EvaluationFactory.fail("No chemoradiotherapy with $type with at least $minCycles is currently received")
+        }
+    }
+
+    private fun enoughCyclesAndOngoingTreatment(treatmentHistoryEntry: TreatmentHistoryEntry, latestStart: LocalDate?): Boolean? {
+        val treatmentHistoryDetails = treatmentHistoryEntry.treatmentHistoryDetails
+        return treatmentHistoryDetails?.cycles?.let { cycles ->
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: zoehehartwig
body: I don't think that would work because treatmentHistoryDetails is also used below replacing that will give a null error
created_at: 2025-03-24T15:51:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationAssert
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.EvaluationResult
+import com.hartwig.actin.datamodel.clinical.TreatmentTestFactory
+import com.hartwig.actin.datamodel.clinical.treatment.DrugType
+import com.hartwig.actin.datamodel.clinical.treatment.Radiotherapy
+import com.hartwig.actin.datamodel.clinical.treatment.RadiotherapyType
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryEntry
+import java.time.LocalDate
+import org.junit.Test
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest {
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: zoehehartwig
body: I think the 2nd one is already handled by the default PASS test case
created_at: 2025-03-24T16:02:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -175,7 +175,7 @@ The following criterion algorithms ('rules') are available:
 
 | Rule                                                                                       | When does a patient pass evaluation?                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Note                                                                                                                                                                                                                                                              |
 |--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| CURRENTLY_GETS_CHEMORADIOTHERAPY_OF_TYPE_X_CHEMOTHERAPY_AND_AT_LEAST_Y_CYCLES              | Patient currently receives chemotherapy and radiotherapy of type x and of at least a Y cycles                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | Resolves to `UNDETERMINED` if any data is unknown but present data does not explicitly `FAIL`. Checks if the treatment is currently expired and if there are more recent treatments to determine if it is currently being administered.                           |
+| CURRENTLY_GETS_CHEMORADIOTHERAPY_OF_TYPE_X_CHEMOTHERAPY_AND_AT_LEAST_Y_CYCLES              | Patient has a treatment history entry with categories of both chemotherapy and radiotherapy of type x and of at least a Y cycles that is ongoing as of the reference date. Checks whether the treatment is ongoing by if the treatment is past the reference date and if there is no reference date, then if there are more recent treatments to determine if it is currently being administered.                                                                                                                                                                  | Resolves to `UNDETERMINED` if any data is unknown but present data does not explicitly `FAIL`.                                                                                                                                                                    |
path: algo/README.md
user: login: jbartletthmf
body: "of at least ~a~ Y cycles" -> "of at least Y cycles"

Instead of "Checks whether the treatment is ongoing..." you could say, "Excludes treatments that appear to be stopped before the evaluation date based on stop date or the presence of later treatments."
created_at: 2025-03-24T19:13:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -55,9 +44,11 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
         val treatmentHistoryDetails = treatmentHistoryEntry.treatmentHistoryDetails
         return treatmentHistoryDetails?.cycles?.let { cycles ->
             val appearsOngoing = with(treatmentHistoryDetails) {
-
-                DateComparison.isAfterDate(referenceDate, stopYear, stopMonth) != false &&
-                        (latestStart?.let { DateComparison.isAfterDate(latestStart, treatmentHistoryEntry.startYear, treatmentHistoryEntry.startYear) }) != false
+                val treatmentNotStopped = DateComparison.isAfterDate(referenceDate, stopYear, stopMonth)
+                treatmentNotStopped == true ||
+                    (treatmentNotStopped == null && (latestStart?.let {
+                        DateComparison.isAfterDate(latestStart, treatmentHistoryEntry.startYear, treatmentHistoryEntry.startMonth)
+                    }) != false)
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: You can drop the parentheses around `(latestStart?.let {})`.
created_at: 2025-03-24T19:20:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,29 +17,18 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
 ) : EvaluationFunction {
 
     override fun evaluate(record: PatientRecord): Evaluation {
+        val latestStart = record.oncologicalHistory.maxOfOrNull { LocalDate.of(it.startYear ?: 0, it.startMonth ?: 1, 1) }
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: Strictly speaking, I think we need to exclude any dates that are greater than the reference date.
created_at: 2025-03-24T19:22:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -59,6 +59,70 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
         assertResultForPatient(EvaluationResult.UNDETERMINED, DrugType.ALK_INHIBITOR, 1, record)
     }
 
+    @Test
+    fun `Should FAIL for matching treatment with insufficient cycles`() {
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: We don't usually capitalize the expected results in test names.
created_at: 2025-03-24T19:23:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -59,6 +59,70 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
         assertResultForPatient(EvaluationResult.UNDETERMINED, DrugType.ALK_INHIBITOR, 1, record)
     }
 
+    @Test
+    fun `Should FAIL for matching treatment with insufficient cycles`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = 10)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, 20, record)
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: I think it would be more intuitive if you held the `minCycles` consistent, at least for most tests, and varied the details of the patients being tested. For example, you could define `private const val MIN_CYCLES = 10` and then create patients with e.g. `cycles = MIN_CYCLES + 1`. This would allow you to drop the `minCycles` parameter on the helper function.
created_at: 2025-03-24T19:28:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -59,6 +59,70 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
         assertResultForPatient(EvaluationResult.UNDETERMINED, DrugType.ALK_INHIBITOR, 1, record)
     }
 
+    @Test
+    fun `Should FAIL for matching treatment with insufficient cycles`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: Is the `radioType` relevant for any of these tests? I could see an argument for `Should fail if there is only radiotherapy`, but the required type is supposed to be a drug type (`OF_TYPE_X_CHEMOTHERAPY`) so it doesn't really apply there either. It should suffice to leave it out in most cases. You could also define a `private val radiotherapy` to define it in one place.
created_at: 2025-03-24T19:33:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -59,6 +59,70 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
         assertResultForPatient(EvaluationResult.UNDETERMINED, DrugType.ALK_INHIBITOR, 1, record)
     }
 
+    @Test
+    fun `Should FAIL for matching treatment with insufficient cycles`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = 10)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, 20, record)
+    }
+
+    @Test
+    fun `Should FAIL if the end date is null but there is a newer treatment`() {
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: `Should fail`
created_at: 2025-03-24T19:34:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -59,6 +59,70 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
         assertResultForPatient(EvaluationResult.UNDETERMINED, DrugType.ALK_INHIBITOR, 1, record)
     }
 
+    @Test
+    fun `Should FAIL for matching treatment with insufficient cycles`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = 10)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, 20, record)
+    }
+
+    @Test
+    fun `Should FAIL if the end date is null but there is a newer treatment`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = 2020
+        )
+        val newerTreatment = TreatmentHistoryEntry(
+            treatments = setOf(TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.ABLATION)),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = 2022
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment, newerTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, 1, record)
+    }
+
+    @Test
+    fun `Should PASS if the end date is null but there is another treatment with an unknown start date`() {
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: `Should pass`
created_at: 2025-03-24T19:34:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -59,6 +59,70 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
         assertResultForPatient(EvaluationResult.UNDETERMINED, DrugType.ALK_INHIBITOR, 1, record)
     }
 
+    @Test
+    fun `Should FAIL for matching treatment with insufficient cycles`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = 10)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, 20, record)
+    }
+
+    @Test
+    fun `Should FAIL if the end date is null but there is a newer treatment`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = 2020
+        )
+        val newerTreatment = TreatmentHistoryEntry(
+            treatments = setOf(TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.ABLATION)),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = 2022
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment, newerTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, 1, record)
+    }
+
+    @Test
+    fun `Should PASS if the end date is null but there is another treatment with an unknown start date`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = 2020
+        )
+        val newerTreatment = TreatmentHistoryEntry(
+            treatments = setOf(TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.ABLATION)),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = null
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment, newerTreatment))
+        assertResultForPatient(EvaluationResult.PASS, DrugType.ALK_INHIBITOR, 1, record)
+    }
+
+    @Test
+    fun `Should be UNDETERMINED if there is a chemotherapy of unknown type with sufficient cycles`() {
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: `Should be undetermined`
created_at: 2025-03-24T19:34:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -59,6 +59,70 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
         assertResultForPatient(EvaluationResult.UNDETERMINED, DrugType.ALK_INHIBITOR, 1, record)
     }
 
+    @Test
+    fun `Should FAIL for matching treatment with insufficient cycles`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = 10)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, 20, record)
+    }
+
+    @Test
+    fun `Should FAIL if the end date is null but there is a newer treatment`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = 2020
+        )
+        val newerTreatment = TreatmentHistoryEntry(
+            treatments = setOf(TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.ABLATION)),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = 2022
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment, newerTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, 1, record)
+    }
+
+    @Test
+    fun `Should PASS if the end date is null but there is another treatment with an unknown start date`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = 2020
+        )
+        val newerTreatment = TreatmentHistoryEntry(
+            treatments = setOf(TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.ABLATION)),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = null
+        )
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: You could consider using `TreatmentTestFactory.treatmentHistoryEntry` here. Either way, `startYear` is null by default and doesn't have to be provided.
created_at: 2025-03-24T19:39:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -59,6 +59,70 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
         assertResultForPatient(EvaluationResult.UNDETERMINED, DrugType.ALK_INHIBITOR, 1, record)
     }
 
+    @Test
+    fun `Should FAIL for matching treatment with insufficient cycles`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = 10)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, 20, record)
+    }
+
+    @Test
+    fun `Should FAIL if the end date is null but there is a newer treatment`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = 2020
+        )
+        val newerTreatment = TreatmentHistoryEntry(
+            treatments = setOf(TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.ABLATION)),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = 2022
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment, newerTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, 1, record)
+    }
+
+    @Test
+    fun `Should PASS if the end date is null but there is another treatment with an unknown start date`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = 2020
+        )
+        val newerTreatment = TreatmentHistoryEntry(
+            treatments = setOf(TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.ABLATION)),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = null
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment, newerTreatment))
+        assertResultForPatient(EvaluationResult.PASS, DrugType.ALK_INHIBITOR, 1, record)
+    }
+
+    @Test
+    fun `Should be UNDETERMINED if there is a chemotherapy of unknown type with sufficient cycles`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: This appears to be a chemotherapy of known type...
created_at: 2025-03-24T19:40:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -59,6 +59,70 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
         assertResultForPatient(EvaluationResult.UNDETERMINED, DrugType.ALK_INHIBITOR, 1, record)
     }
 
+    @Test
+    fun `Should FAIL for matching treatment with insufficient cycles`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = 10)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, 20, record)
+    }
+
+    @Test
+    fun `Should FAIL if the end date is null but there is a newer treatment`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = 2020
+        )
+        val newerTreatment = TreatmentHistoryEntry(
+            treatments = setOf(TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.ABLATION)),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = 2022
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment, newerTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, 1, record)
+    }
+
+    @Test
+    fun `Should PASS if the end date is null but there is another treatment with an unknown start date`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = 2020
+        )
+        val newerTreatment = TreatmentHistoryEntry(
+            treatments = setOf(TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.ABLATION)),
+            treatmentHistoryDetails = TreatmentHistoryDetails(cycles = 10),
+            startYear = null
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment, newerTreatment))
+        assertResultForPatient(EvaluationResult.PASS, DrugType.ALK_INHIBITOR, 1, record)
+    }
+
+    @Test
+    fun `Should be UNDETERMINED if there is a chemotherapy of unknown type with sufficient cycles`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = null)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = 10)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.UNDETERMINED, DrugType.ALK_INHIBITOR, 1, record)
+    }
+
     private fun assertResultForPatient(evaluationResult: EvaluationResult, type: TreatmentType, minCycles: Int, record: PatientRecord) {
         val evaluation = CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles(
             type, minCycles, LocalDate.of(1900, 1, 1)
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: Rather than using an unrealistically old date, you could set a constant for e.g. `REFERENCE_YEAR` and use it here, then use e.g. ``REFERENCE_YEAR + 1` above when you want to use a later date.
created_at: 2025-03-24T19:42:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,132 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationAssert
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.EvaluationResult
+import com.hartwig.actin.datamodel.clinical.TreatmentTestFactory
+import com.hartwig.actin.datamodel.clinical.treatment.DrugType
+import com.hartwig.actin.datamodel.clinical.treatment.Radiotherapy
+import com.hartwig.actin.datamodel.clinical.treatment.RadiotherapyType
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryEntry
+import java.time.LocalDate
+import org.junit.Test
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest {
+    @Test
+    fun `Should fail if there are no treatments`() {
+        val record = TreatmentTestFactory.withTreatmentHistory(emptyList())
+        assertResultForPatient(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, 10, record)
+    }
+
+    @Test
+    fun `Should pass if there is a chemotherapy of matching type with sufficient cycles`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = 10)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.PASS, DrugType.ALK_INHIBITOR, 1, record)
+    }
+
+    @Test
+    fun `Should fail if there is only radiotherapy`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                Radiotherapy("Radiotherapy", radioType = RadiotherapyType.CYBERKNIFE)
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = 10)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, RadiotherapyType.CYBERKNIFE, 1, record)
+    }
+
+    @Test
+    fun `Should be undetermined if there are no explicit fails`() {
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: Can this name be made more descriptive? e.g. it appears that the chemotherapy type is unknown.
created_at: 2025-03-24T19:46:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+package com.hartwig.actin.algo.evaluation.treatment
+
+import com.hartwig.actin.algo.evaluation.EvaluationAssert
+import com.hartwig.actin.datamodel.PatientRecord
+import com.hartwig.actin.datamodel.algo.EvaluationResult
+import com.hartwig.actin.datamodel.clinical.TreatmentTestFactory
+import com.hartwig.actin.datamodel.clinical.treatment.DrugType
+import com.hartwig.actin.datamodel.clinical.treatment.Radiotherapy
+import com.hartwig.actin.datamodel.clinical.treatment.RadiotherapyType
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentCategory
+import com.hartwig.actin.datamodel.clinical.treatment.TreatmentType
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryDetails
+import com.hartwig.actin.datamodel.clinical.treatment.history.TreatmentHistoryEntry
+import java.time.LocalDate
+import org.junit.Test
+
+class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest {
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: I wasn't intending that to be an exhaustive list, I was just naming some scenarios off the top of my head. Can you think of any others?

6. matching treatment with unknown cycles
7. matching treatment with earlier end date
8. matching category with wrong type
9. ...
created_at: 2025-03-24T19:50:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -175,7 +175,7 @@ The following criterion algorithms ('rules') are available:
 
 | Rule                                                                                       | When does a patient pass evaluation?                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Note                                                                                                                                                                                                                                                              |
 |--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| CURRENTLY_GETS_CHEMORADIOTHERAPY_OF_TYPE_X_CHEMOTHERAPY_AND_AT_LEAST_Y_CYCLES              | Patient has a treatment history entry with categories of both chemotherapy and radiotherapy of type x and of at least a Y cycles that is ongoing as of the reference date. Checks whether the treatment is ongoing by if the treatment is past the reference date and if there is no reference date, then if there are more recent treatments to determine if it is currently being administered.                                                                                                                                                                  | Resolves to `UNDETERMINED` if any data is unknown but present data does not explicitly `FAIL`.                                                                                                                                                                    |
+| CURRENTLY_GETS_CHEMORADIOTHERAPY_OF_TYPE_X_CHEMOTHERAPY_AND_AT_LEAST_Y_CYCLES              | Patient has a treatment history entry with categories of both chemotherapy and radiotherapy of type x and of at least y cycles that is ongoing as of the reference date. Excludes treatments that appear to be stopped before the evaluation date based on stop date or the presence of later treatments.                                                                                                                                                                                                                                                          | Resolves to `UNDETERMINED` if any data is unknown but present data does not explicitly `FAIL`.                                                                                                                                                                    |
path: algo/README.md
user: login: jbartletthmf
body: The convention in the README is to use uppercase X and Y.
created_at: 2025-03-24T21:12:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,7 +17,14 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
 ) : EvaluationFunction {
 
     override fun evaluate(record: PatientRecord): Evaluation {
-        val latestStart = record.oncologicalHistory.maxOfOrNull { LocalDate.of(it.startYear ?: 0, it.startMonth ?: 1, 1) }
+        val latestStartAll = record.oncologicalHistory.maxOfOrNull { LocalDate.of(it.startYear ?: 0, it.startMonth ?: 1, 1) }
+        val latestStart = if (latestStartAll?.let { DateComparison.isAfterDate(it, referenceDate.year, referenceDate.monthValue) } == true){
+            referenceDate
+        }
+        else{
+            latestStartAll
+        }
path: algo/src/main/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles.kt
user: login: jbartletthmf
body: I don't see why we'd want to set the latest start date to the reference date. How about:
```suggestion
        val latestStartAll = record.oncologicalHistory.map { LocalDate.of(it.startYear ?: 0, it.startMonth ?: 1, 1) }
            .sortedDescending()
            .firstOrNull { !it.isAfter(referenceDate) }
```
created_at: 2025-03-24T21:20:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -15,10 +15,11 @@ import java.time.LocalDate
 import org.junit.Test
 
 class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest {
+    private val MIN_CYCLES = 5
     @Test
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: There should be a blank line before the first test.
created_at: 2025-03-24T21:21:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -48,7 +51,7 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
     }
 
     @Test
-    fun `Should be undetermined if there are no explicit fails`() {
+    fun `Should be undetermined if there are no explicit fails but the types is undetermined`() {
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: What does it mean that "there are no explicit fails"? To me, this should be named something like `Should be undetermined for current chemoradiotherapy with matching cycles and unknown chemo type`.
created_at: 2025-03-25T19:40:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,9 +126,67 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
         assertResultForPatient(EvaluationResult.UNDETERMINED, DrugType.ALK_INHIBITOR, record)
     }
 
+    @Test
+    fun `Should be undetermined if there is a matching treatment with unknown cycles`() {
+        val matchingTreatmentNullCycles = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                RADIOTHERAPY
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatmentNullCycles))
+        assertResultForPatient(EvaluationResult.UNDETERMINED, DrugType.ALK_INHIBITOR, record)
+    }
+
+    @Test
+    fun `Should fail for matching treatment with an end date before the reference date`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                RADIOTHERAPY
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2022, cycles = MIN_CYCLES)
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: It would help if you define these years with respect to `REFERENCE_YEAR` so we can easily see how these entries compare.
created_at: 2025-03-25T19:50:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,9 +126,67 @@ class CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCycles
         assertResultForPatient(EvaluationResult.UNDETERMINED, DrugType.ALK_INHIBITOR, record)
     }
 
+    @Test
+    fun `Should be undetermined if there is a matching treatment with unknown cycles`() {
+        val matchingTreatmentNullCycles = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                RADIOTHERAPY
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatmentNullCycles))
+        assertResultForPatient(EvaluationResult.UNDETERMINED, DrugType.ALK_INHIBITOR, record)
+    }
+
+    @Test
+    fun `Should fail for matching treatment with an end date before the reference date`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                RADIOTHERAPY
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2022, cycles = MIN_CYCLES)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, DrugType.ALK_INHIBITOR, record)
+    }
+
+    @Test
+    fun `Should fail if the category matches but the type is wrong`() {
+        val matchingTreatment = TreatmentHistoryEntry(
+            treatments = setOf(
+                TreatmentTestFactory.drugTreatment("Alk Inhibitor", TreatmentCategory.CHEMOTHERAPY, setOf(DrugType.ALK_INHIBITOR)),
+                RADIOTHERAPY
+            ),
+            treatmentHistoryDetails = TreatmentHistoryDetails(stopYear = 2030, cycles = MIN_CYCLES)
+        )
+        val record = TreatmentTestFactory.withTreatmentHistory(listOf(matchingTreatment))
+        assertResultForPatient(EvaluationResult.FAIL, DrugType.ABL_INHIBITOR, record)
path: algo/src/test/kotlin/com/hartwig/actin/algo/evaluation/treatment/CurrentlyGetsChemoradiotherapyWithSpecificChemotherapyTypeAndMinimumCyclesTest.kt
user: login: jbartletthmf
body: I think it would be easier to follow if the function criteria were kept consistent (i.e. `ALK_INHIBITOR`) and the patient/treatment details varied instead. You could also define a `MATCHING_TYPE` constant to make this even more obvious.
created_at: 2025-03-25T19:53:05Z
author_association: CONTRIBUTOR

PR comments:
user: login: zoehehartwig
created_at: 2025-03-20T18:24:40Z
body: Should I update the readme?
author_association: CONTRIBUTOR
user: login: jbartletthmf
created_at: 2025-03-24T12:50:14Z
body: You'll need to address the conflicts before you can merge:
```
git checkout master
git pull
git checkout ACTIN-1673
git merge master
```
then resolve conflicts and commit + push.
author_association: CONTRIBUTOR