Cervical Cancer Screening CDS for OpenMRS
0.1.0 - ci-build International flag

Cervical Cancer Screening CDS for OpenMRS, published by Hopena Health. This guide is not an authorized publication; it is the continuous build for version 0.1.0 built by the FHIR (HL7® FHIR® Standard) CI Build. This version is based on the current content of https://github.com/dhes/cervical-cancer-cds/tree/main and changes regularly. See the Directory of published versions

Library: Cervical Cancer Screening Common Definitions (Experimental)

Official URL: https://hopenahealth.com/fhir/cervical-cancer-cds/Library/CervicalCancerScreeningCommon Version: 0.1.0
Draft as of 2026-04-21 Computable Name: CervicalCancerScreeningCommon
Title: Cervical Cancer Screening Common Definitions
Id: CervicalCancerScreeningCommon
Version: 0.1.0
Url: Cervical Cancer Screening Common Definitions
Status: draft
Experimental: true
Type:

system: http://terminology.hl7.org/CodeSystem/library-type

code: logic-library

Date: 2026-04-21 19:55:49+0000
Publisher: Hopena Health
Jurisdiction: 001
Related Artifacts:

Dependencies

Parameters:
NameTypeMinMaxIn/Out
PatientPatient01Out
Is Femaleboolean01Out
Age In Yearsinteger01Out
Active HIV ConditionsCondition0*Out
Is WLHIVboolean01Out
Minimum Screening Ageinteger01Out
Maximum Screening Ageinteger01Out
Screening Interval Yearsinteger01Out
Post Triage Negative Retest Monthsinteger01Out
Post Treatment Retest Monthsinteger01Out
HPV DNA Test ResultsObservation0*Out
Most Recent HPV DNA TestObservation01Out
Most Recent HPV Test Is Positiveboolean01Out
Date Of Most Recent HPV Testdate01Out
Has Ever Been Screened With HPVboolean01Out
VIA Screening ResultsObservation0*Out
Most Recent VIA ResultObservation01Out
Most Recent VIA Is Positiveboolean01Out
Date Of Most Recent VIAdate01Out
Cervical TreatmentsObservation0*Out
Most Recent TreatmentObservation01Out
Has Been Treatedboolean01Out
Date Of Most Recent Treatmentdate01Out
Months Since Treatmentinteger01Out
Has Cervical Cancer Diagnosisboolean01Out
Data Requirements:
TypeProfileMSCode Filter
Condition http://hl7.org/fhir/StructureDefinition/Condition
Observation http://hl7.org/fhir/StructureDefinition/Observation
Observation http://hl7.org/fhir/StructureDefinition/Observation
Observation http://hl7.org/fhir/StructureDefinition/Observation
Condition http://hl7.org/fhir/StructureDefinition/Condition
Content: text/cql
/*
 * CervicalCancerScreeningCommon
 *
 * Shared definitions for cervical cancer screening CDS, implementing
 * WHO guideline for screening and treatment of cervical pre-cancer lesions
 * for cervical cancer prevention, 2nd edition (2021).
 *
 * This library provides:
 *   - Value set definitions (inline, using CIEL terminology)
 *   - Patient demographic expressions
 *   - HIV status determination
 *   - Screening history retrieval
 *   - Treatment history retrieval
 *   - Shared helper functions
 *
 * @author  Dan Heslinga / Hopena Health
 * @version 0.1.0
 * @date    2026-03-08
 *
 * Code system note:
 *   OpenMRS FHIR2 emits SNOMED CT with a trailing slash (http://snomed.info/sct/)
 *   which is non-standard. We use CIEL (https://cielterminology.org) as the
 *   primary code system for value sets, since every OpenMRS concept has a CIEL
 *   mapping and the URI is consistent.
 */

library CervicalCancerScreeningCommon version '0.1.0'

using FHIR version '4.0.1'

include FHIRHelpers version '4.0.1'

/*
 * =============================================================================
 * CODE SYSTEMS
 * =============================================================================
 */

codesystem "CIEL": 'https://cielterminology.org'
codesystem "SNOMED": 'http://snomed.info/sct/'   // Note trailing slash (OpenMRS quirk)
codesystem "LOINC": 'http://loinc.org'
codesystem "ConditionClinicalStatusCodes": 'http://terminology.hl7.org/CodeSystem/condition-clinical'

/*
 * =============================================================================
 * CODES — Individual concept codes referenced directly in expressions
 * =============================================================================
 */

// Observation answer codes
code "Positive": '703' from "CIEL" display 'Positive'
code "Negative": '664' from "CIEL" display 'Negative'

// Condition clinical status
code "active": 'active' from "ConditionClinicalStatusCodes"

/*
 * =============================================================================
 * VALUE SETS — Inline definitions using CIEL codes
 * =============================================================================
 *
 * Note: These are defined inline rather than as external FHIR ValueSet resources.
 * When we package as an IG (Phase 4), these will be promoted to proper ValueSets
 * authored in FSH.
 */

// --- Screening Tests ---

/*
 * @description HPV DNA / NAT test observation codes
 *   CIEL 170145: Human papillomavirus (HPV) DNA detection (SNOMED 35904009)
 *   CIEL 159859: PCR, human papilloma virus, qualitative (legacy, SNOMED 9718006)
 */
valueset "HPV DNA Test Codes": 'urn:oid:cervical-cds:vs:hpv-dna-test'
// Expanded inline as concept list since we don't have a terminology server:
// 170145, 159859

/*
 * @description VIA / cervical screening observation codes
 *   CIEL 151185: Screening for malignant neoplasm of cervix (SNOMED 243877001)
 */
valueset "VIA Test Codes": 'urn:oid:cervical-cds:vs:via-test'
// Expanded inline: 151185

// --- Diagnoses ---

/*
 * @description HIV diagnosis codes for identifying WLHIV
 *   CIEL 138405: Human immunodeficiency virus (HIV) disease (SNOMED 86406008)
 */
valueset "HIV Diagnosis Codes": 'urn:oid:cervical-cds:vs:hiv-diagnosis'
// Expanded inline: 138405

/*
 * @description CIN diagnosis codes (all grades)
 *   CIEL 145809: CIN grade 1 (SNOMED 285836003)
 *   CIEL 145807: CIN grade 2 (SNOMED 285838002)
 *   CIEL 145806: CIN grade 3 (SNOMED 285636001)
 *   CIEL 120780: CIN general (SNOMED 285636001)
 */
valueset "CIN Diagnosis Codes": 'urn:oid:cervical-cds:vs:cin-diagnosis'
// Expanded inline: 145809, 145807, 145806, 120780

/*
 * @description Cervical cancer diagnosis
 *   CIEL 116023: Malignant neoplasm of cervix uteri (SNOMED 363354003)
 */
valueset "Cervical Cancer Diagnosis Codes": 'urn:oid:cervical-cds:vs:cervical-cancer'
// Expanded inline: 116023

// --- Treatments (recorded as Observations, since OpenMRS lacks Procedure resource) ---

/*
 * @description Treatment procedure codes (thermal ablation, cryotherapy, LEEP)
 *   CIEL 166706: Thermocauterization of cervix (SNOMED 1287592000)
 *   CIEL 162812: Cryosurgery of lesion of cervix (SNOMED 78203001)
 *   CIEL 165084: LEEP of cervix (SNOMED 23140002)
 *   CIEL 162810: Loop electrosurgical excision procedure (SNOMED 36899001)
 */
valueset "Cervical Treatment Codes": 'urn:oid:cervical-cds:vs:cervical-treatment'
// Expanded inline: 166706, 162812, 165084, 162810

/*
 * =============================================================================
 * CONCEPT DEFINITIONS — For direct code matching in retrieves
 * =============================================================================
 *
 * Since we're using inline value sets that aren't backed by a terminology
 * server, we define individual codes and use them in expressions with
 * explicit code matching.
 */

// Screening test codes
code "HPV DNA Detection Code": '170145' from "CIEL" display 'HPV DNA detection'
code "HPV PCR Qualitative Code": '159859' from "CIEL" display 'HPV PCR qualitative'
code "Cervical Screening Code": '151185' from "CIEL" display 'Screening for malignant neoplasm of cervix'

// Diagnosis codes
code "HIV Disease Code": '138405' from "CIEL" display 'HIV disease'
code "Cervical Cancer Code": '116023' from "CIEL" display 'Malignant neoplasm of cervix uteri'
code "CIN1 Code": '145809' from "CIEL" display 'CIN grade 1'
code "CIN2 Code": '145807' from "CIEL" display 'CIN grade 2'
code "CIN3 Code": '145806' from "CIEL" display 'CIN grade 3'

// Treatment codes
code "Thermal Ablation Code": '166706' from "CIEL" display 'Thermocauterization of cervix'
code "Cryotherapy Code": '162812' from "CIEL" display 'Cryosurgery of lesion of cervix'
code "LEEP Code": '165084' from "CIEL" display 'LEEP of cervix'
code "LEEP Generic Code": '162810' from "CIEL" display 'Loop electrosurgical excision procedure'

/*
 * =============================================================================
 * PATIENT DEMOGRAPHICS
 * =============================================================================
 */

context Patient

/*
 * @input Patient.gender
 * @pseudocode Patient is female
 */
define "Is Female":
  Patient.gender = 'female'

/*
 * @input Patient.birthDate
 * @pseudocode Patient age in years at time of evaluation
 */
define "Age In Years":
  AgeInYears()

/*
 * =============================================================================
 * HIV STATUS
 * =============================================================================
 *
 * @guidance WHO Recommendations 21-34 apply to WLHIV. HIV status determines:
 *   - Screening start age (25 vs 30)
 *   - Screening interval (3-5 years vs 5-10 years)
 *   - Triage recommendation (screen-triage-treat preferred for WLHIV)
 *   - Follow-up intervals (12 months vs 24 months post-triage-negative)
 *   - Post-treatment follow-up (double follow-up for WLHIV)
 */

/*
 * @input Condition where code in HIV Diagnosis Codes and clinicalStatus = active
 * @pseudocode Active HIV conditions for this patient
 */
define "Active HIV Conditions":
  [Condition] C
    where C.code.coding.code contains '138405'
      and C.clinicalStatus.coding.code contains 'active'

/*
 * @output Boolean — true if patient has active HIV diagnosis
 * @pseudocode Patient is a woman living with HIV
 */
define "Is WLHIV":
  exists("Active HIV Conditions")

/*
 * =============================================================================
 * SCREENING PARAMETERS — Population-specific thresholds
 * =============================================================================
 */

/*
 * @guidance WHO Recommendation 5 (general): start at age 30
 *           WHO Recommendation 25 (WLHIV): start at age 25
 */
define "Minimum Screening Age":
  if "Is WLHIV" then 25 else 30

/*
 * @guidance WHO Recommendation 6/26: stop after age 50 with 2 consecutive negatives
 */
define "Maximum Screening Age":
  49

/*
 * @guidance WHO Recommendation 8 (general): every 5-10 years
 *           WHO Recommendation 28 (WLHIV): every 3-5 years
 *           Using lower bound for "due" calculation (most conservative)
 */
define "Screening Interval Years":
  if "Is WLHIV" then 3 else 5

/*
 * @guidance WHO Recommendation 11 (general): retest at 24 months post-triage-negative
 *           WHO Recommendation 31 (WLHIV): retest at 12 months post-triage-negative
 */
define "Post Triage Negative Retest Months":
  if "Is WLHIV" then 12 else 24

/*
 * @guidance WHO Recommendation 13/33: retest at 12 months post-treatment (both populations)
 */
define "Post Treatment Retest Months":
  12

/*
 * =============================================================================
 * SCREENING HISTORY
 * =============================================================================
 */

/*
 * @input Observation where code matches HPV DNA test codes
 * @pseudocode All HPV DNA test results for this patient, any time
 */
define "HPV DNA Test Results":
  [Observation] O
    where O.code.coding.code contains '170145'
       or O.code.coding.code contains '159859'

/*
 * @pseudocode Most recent HPV DNA test result, by effective date
 */
define "Most Recent HPV DNA Test":
  Last(
    "HPV DNA Test Results" O
      sort by Coalesce(
        (effective as FHIR.dateTime).value,
        (effective as FHIR.Period)."start".value
      )
  )

/*
 * @output Boolean — true if most recent HPV DNA test was positive
 */
define "Most Recent HPV Test Is Positive":
  "Most Recent HPV DNA Test".value.coding.code contains '703'

/*
 * @output Date of most recent HPV DNA test
 */
define "Date Of Most Recent HPV Test":
  date from ("Most Recent HPV DNA Test".effective as FHIR.dateTime).value

/*
 * @pseudocode True if patient has ever had any HPV DNA test
 */
define "Has Ever Been Screened With HPV":
  exists("HPV DNA Test Results")

/*
 * =============================================================================
 * VIA / TRIAGE HISTORY
 * =============================================================================
 */

/*
 * @input Observation where code matches VIA/cervical screening codes
 * @pseudocode All VIA screening results for this patient
 */
define "VIA Screening Results":
  [Observation] O
    where O.code.coding.code contains '151185'

/*
 * @pseudocode Most recent VIA screening result
 */
define "Most Recent VIA Result":
  Last(
    "VIA Screening Results" O
      sort by Coalesce(
        (effective as FHIR.dateTime).value,
        (effective as FHIR.Period)."start".value
      )
  )

/*
 * @output Boolean — true if most recent VIA was positive
 */
define "Most Recent VIA Is Positive":
  "Most Recent VIA Result".value.coding.code contains '703'

/*
 * @output Date of most recent VIA
 */
define "Date Of Most Recent VIA":
  date from ("Most Recent VIA Result".effective as FHIR.dateTime).value

/*
 * =============================================================================
 * TREATMENT HISTORY
 * =============================================================================
 *
 * Treatments are stored as Observations (OpenMRS FHIR2 has no Procedure resource).
 * The Observation.code is the treatment SNOMED/CIEL code.
 */

/*
 * @input Observation where code matches cervical treatment codes
 * @pseudocode All cervical treatment events for this patient
 */
define "Cervical Treatments":
  [Observation] O
    where O.code.coding.code contains '166706'   // Thermal ablation
       or O.code.coding.code contains '162812'   // Cryotherapy
       or O.code.coding.code contains '165084'   // LEEP (cervix)
       or O.code.coding.code contains '162810'   // LEEP (generic)

/*
 * @pseudocode Most recent cervical treatment event
 */
define "Most Recent Treatment":
  Last(
    "Cervical Treatments" O
      sort by Coalesce(
        (effective as FHIR.dateTime).value,
        (effective as FHIR.Period)."start".value
      )
  )

/*
 * @output Boolean — true if patient has ever received cervical treatment
 */
define "Has Been Treated":
  exists("Cervical Treatments")

/*
 * @output Date of most recent treatment
 */
define "Date Of Most Recent Treatment":
  date from ("Most Recent Treatment".effective as FHIR.dateTime).value

/*
 * @output Number of months since most recent treatment
 */
define "Months Since Treatment":
  if "Has Been Treated"
  then months between "Date Of Most Recent Treatment" and Today()
  else null

/*
 * =============================================================================
 * CERVICAL CANCER DIAGNOSIS
 * =============================================================================
 */

/*
 * @input Condition where code matches cervical cancer codes
 * @pseudocode True if patient has a cervical cancer diagnosis
 * @guidance Patients with known cervical cancer should be referred for
 *           oncology management, not managed through screening pathway
 */
define "Has Cervical Cancer Diagnosis":
  exists(
    [Condition] C
      where C.code.coding.code contains '116023'
        and C.clinicalStatus.coding.code contains 'active'
  )

/*
 * =============================================================================
 * HELPER FUNCTIONS
 * =============================================================================
 */

/*
 * @pseudocode True if a coded value contains CIEL code 703 (Positive)
 */
define function IsPositive(value FHIR.CodeableConcept):
  value.coding.code contains '703'

/*
 * @pseudocode True if a coded value contains CIEL code 664 (Negative)
 */
define function IsNegative(value FHIR.CodeableConcept):
  value.coding.code contains '664'
Content: application/elm+json
Encoded data (169676 characters)