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 installOr install it directly:
gem install hln_iceUsage
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 / falseEvaluating 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.