Project

hln_ice

0.0
The project is in a healthy, maintained state
Evaluates a patient's immunization history against the HLN ICE forecasting rules and returns both the raw recommendations and a simplified status mapping.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 0.2
~> 0.21
~> 1.15
 Project Readme

HlnIce

A Ruby client for the HLN ICE (Immunization Calculation Engine), an OpenCDS clinical decision-support service for immunization forecasting.

Given a patient's demographics and immunization history, HlnIce builds the VMR (Virtual Medical Record) request, calls the ICE evaluation endpoint, and parses the response into:

  • a full list of recommendations (one per vaccine group, with status, reasons, administration intervals, and, when the ICE server outputs them, schedule authorities, series, season, and doses remaining),
  • the evaluations of each administered dose (per vaccine group, with dose number, validity, and reasons), and
  • a simplified status map keyed by vaccine group — e.g. { ipv_opv: "overdue", mmr: "compliant" }.

The gem has no Rails or ActiveSupport dependency; it takes an injected logger so it can run in any Ruby application.

Installation

Add the gem to your application's Gemfile:

gem "hln_ice"

Then run:

bundle install

Or install it directly:

gem install hln_ice

Usage

Creating a client

client = HlnIce::Client.new(base_url: "https://ice.example.com")

All options:

Option Default Description
base_url: (required) Base URL of the ICE OpenCDS service.
timeout: 30 HTTP request timeout, in seconds.
max_retries: 3 Number of retries on a failed evaluation request.
retry_delay: 1 Seconds to wait between retries.
logger: Logger.new($stdout) Any Logger-compatible object. In Rails, pass Rails.logger.
log_payloads: false When true, logs the ICE input data, generated request XML, and raw service response. These contain patient PHI, so keep this off outside local debugging.
# In a Rails app, reuse the application logger:
client = HlnIce::Client.new(base_url: ENV["ICE_URL"], logger: Rails.logger)

Checking availability

client.available? # => true / false

Evaluating immunizations

Pass a patient hash with their demographics and (optionally) prior immunization events:

patient_data = {
  patient: {
    id: "12345",
    date_of_birth: "2020-01-01", # parseable date string
    gender: "M"                  # "M", "F", or blank (defaults to "U")
  },
  immunizations: [
    {
      clinicalStatements: {
        substanceAdministrationEvents: [
          {
            id: "1", # echoed back as event_id on evaluations; a UUID is generated if omitted
            substance: {
              substanceCode: { code: "10", displayName: "Polio" }
            },
            administrationTimeInterval: { low: "2020-03-01", high: "2020-03-01" }
          }
        ]
      }
    }
  ]
}

result = client.evaluate_immunizations(patient_data)

The result

On success:

{
  success: true,
  data: {
    raw_xml: "<...the decoded ICE response XML...>",
    patient: { id: "12345", birth_date: "2020-01-01", gender: "M" },
    recommendations: [
      {
        vaccine: { code: "10", code_system: "...", name: "Polio Vaccine Group" },
        status:  { code: "RECOMMENDED", name: "Recommended" },
        reasons: [{ code: "DUE_NOW", name: "Due Now" }],
        intervals: { proposed: { low: "2020-03-01", high: "2020-04-01" } },
        # Only present when the ICE server has outputScheduleAuthorities enabled
        schedule_authorities: [
          { code: "ACIP_CDC", name: "Advisory Committee on Immunization Practices / Centers for Disease Control and Prevention" },
          { code: "AAP", name: "American Academy of Pediatrics" }
        ],
        # Only present when the ICE server has ICE_OUTPUTSERIESINFORMATION enabled.
        # selection is "unambiguous", "best_guess", or "alternative".
        series: { code: "...", name: "...", selection: "best_guess" }, # e.g. HEP_B_ADULT_3_DOSE_SERIES
        # Only present for seasonal vaccines (e.g. influenza, COVID-19) with series output on
        # starts_on/ends_on are read from the end of ICE's season name, e.g.
        # "2026-2027 COVID-19 Season (08/28/2026 - 01/01/2100)", because ICE sends
        # no structured season dates. They are nil if the name is not in that format.
        # ICE uses an end of 2100-01-01 for an open-ended season. For a season newer
        # than its newest coded one (for influenza, 20152016_INFLUENZA_SEASON in ICE's
        # supportedSeasons.yml), ICE sends that code and label with the actual season's
        # dates; the dates are the ones to rely on.
        season: {
          code: "COVID_19_AUG_2026_SEASON",
          name: "2026-2027 COVID-19 Season (08/28/2026 - 01/01/2100)",
          starts_on: "2026-08-28",
          ends_on: "2100-01-01"
        },
        # Only present when ICE_OUTPUTNUMBEROFDOSESREMAINING is also enabled. An Integer
        # for a plain count, otherwise ICE's text, e.g. "Recurring".
        doses_remaining: 3
      }
    ],
    # One entry per administered dose and vaccine group it counts toward, so a
    # combination vaccine has several. Codes are passed through from ICE.
    evaluations: [
      {
        event_id: "1",
        cvx: "10",
        administered_on: "2020-03-01",
        vaccine_group: { code: "400", name: "Polio Vaccine Group" },
        dose_number: 1, # nil if ICE omits it
        valid: true,
        status: { code: "VALID", name: "Valid Dose" }, # e.g. VALID, INVALID, ACCEPTED
        reasons: [], # e.g. [{ code: "BELOW_MINIMUM_INTERVAL", name: "Below Minimum Interval" }]
        # The series the dose was evaluated against. Only present with series output on.
        series: { code: "...", name: "...", selection: "unambiguous" } # e.g. MMR_2_DOSE_SERIES
      }
    ],
    simplified_status: { ipv_opv: "overdue" }
  }
}

On failure (service error, timeout after retries, or unparseable response):

{ success: false, error: "ICE service error: 400 - ..." }

Always branch on result[:success] before reading result[:data].

Optional keys (intervals, schedule_authorities, series, season, doses_remaining) are left out when ICE does not send them, so reading one returns nil.

Simplified status values

simplified_status maps each recognized vaccine group to one of:

Value Meaning
"compliant" Up to date (ICE FUTURE_RECOMMENDED / NOT_RECOMMENDED).
"overdue" A dose is recommended now (ICE RECOMMENDED).
"conditional" Conditionally recommended (ICE CONDITIONAL).
"medically_exempt" Conditional status with a medical-exemption reason.

The recognized vaccine-group keys are: :dtap_tdap, :hep_a, :hep_b, :hib, :hpv, :mcv4, :mmr, :pcv, :ipv_opv, and :var.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/PrimaryDotHealth/hln_ice.

License

The gem is available as open source under the terms of the MIT License.