Packages
hl7v2
3.3.1
3.10.1
3.9.0
3.8.0
3.7.0
3.6.0
3.5.0
3.4.0
3.3.6
3.3.5
3.3.4
3.3.3
3.3.2
3.3.1
3.3.0
3.2.0
3.1.1
3.1.0
3.0.2
3.0.1
3.0.0
2.11.0
2.10.0
2.9.1
2.9.0
2.8.2
2.8.1
2.8.0
2.7.1
2.7.0
2.6.0
2.5.0
2.4.0
2.3.0
2.2.0
2.1.3
2.1.2
2.1.1
2.1.0
1.4.6
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.0
1.2.0
1.1.0
1.0.0
0.6.0
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.1.0
Pure Elixir HL7 v2.x toolkit — schema-driven parsing, typed segments, message builder, MLLP transport
Current section
Files
Jump to
Current section
Files
lib/hl7v2/standard/coverage.ex
defmodule HL7v2.Standard.Coverage do
@moduledoc """
Computes library coverage against the HL7 v2.5.1 standard.
Reports which segments, types, and structures are typed, raw, or unsupported,
and identifies specific fields that fall back to `:raw` within typed segments.
Fields listed in `@runtime_dispatched` are intentionally declared as `:raw`
because their concrete type is resolved at runtime (e.g. OBX-5 whose type
depends on OBX-2 Value Type). These are reported separately from true raw
gaps so coverage reports reflect genuine implementation work remaining.
"""
alias HL7v2.Standard
# Fields intentionally typed as `:raw` because their concrete type is
# determined at runtime rather than statically from the segment definition.
@runtime_dispatched [{"OBX", :observation_value, 5}]
@doc """
Returns all typed segment IDs.
## Examples
iex> "PID" in HL7v2.Standard.Coverage.typed_segments()
true
"""
@spec typed_segments() :: [binary()]
def typed_segments, do: Standard.typed_segment_ids()
@doc """
Returns per-segment field completeness: `{segment_id, typed_count, total_count, pct}`.
Segments with all fields typed show 100.0%. Segments with `:raw` holes show lower.
"""
@spec segment_completeness() :: [{binary(), non_neg_integer(), non_neg_integer(), float()}]
def segment_completeness do
Standard.typed_segment_ids()
|> Enum.map(fn seg_id ->
module = Standard.segment_module(seg_id)
fields = module.fields()
total = length(fields)
typed = Enum.count(fields, fn {_, _, type, _, _} -> type != :raw end)
pct = if total > 0, do: Float.round(typed / total * 100, 1), else: 100.0
{seg_id, typed, total, pct}
end)
|> Enum.sort_by(fn {_, _, _, pct} -> pct end)
end
@doc "Returns segments where all fields are typed (no `:raw` holes)."
@spec fully_typed_segments() :: [binary()]
def fully_typed_segments do
segment_completeness()
|> Enum.filter(fn {_, _, _, pct} -> pct == 100.0 end)
|> Enum.map(fn {id, _, _, _} -> id end)
end
@doc "Returns segments with at least one `:raw` hole."
@spec partially_typed_segments() :: [binary()]
def partially_typed_segments do
segment_completeness()
|> Enum.filter(fn {_, _, _, pct} -> pct < 100.0 end)
|> Enum.map(fn {id, _, _, _} -> id end)
end
@doc """
Returns segments that exist in the standard but are not typed.
These segments are preserved as raw tuples during typed parsing.
"""
@spec unsupported_segments() :: [binary()]
def unsupported_segments do
Standard.segment_ids()
|> Enum.filter(&(Standard.segment_tier(&1) == :unsupported))
end
@doc """
Returns data types with full parse/encode modules.
"""
@spec typed_types() :: [binary()]
def typed_types, do: Standard.typed_type_codes()
@doc """
Returns data types that exist in the standard but are not implemented.
"""
@spec unsupported_types() :: [binary()]
def unsupported_types do
Standard.type_codes()
|> Enum.filter(&(Standard.type_tier(&1) == :unsupported))
end
@doc """
Returns specific segment fields that are typed as `:raw` within
otherwise typed segments — true raw gaps that need implementation.
Fields listed in `@runtime_dispatched` (e.g. OBX-5) are excluded because
their `:raw` typing is intentional. Use `runtime_dispatched/0` to inspect those.
"""
@spec raw_holes() :: [{binary(), atom(), pos_integer()}]
def raw_holes do
all_raw_holes()
|> Enum.reject(fn hole -> hole in @runtime_dispatched end)
end
@doc """
Returns fields that are intentionally declared as `:raw` because their
concrete type is resolved at runtime (e.g. OBX-5 whose type depends on
OBX-2 Value Type).
Only fields present in both `@runtime_dispatched` and the actual segment
definitions are returned (i.e. the allowlist is intersected with reality).
"""
@spec runtime_dispatched() :: [{binary(), atom(), pos_integer()}]
def runtime_dispatched do
all = MapSet.new(all_raw_holes())
Enum.filter(@runtime_dispatched, fn entry -> MapSet.member?(all, entry) end)
end
# Returns every `:raw` field across typed segments, without filtering.
@spec all_raw_holes() :: [{binary(), atom(), pos_integer()}]
defp all_raw_holes do
Standard.typed_segment_ids()
|> Enum.flat_map(fn seg_id ->
module = Standard.segment_module(seg_id)
module.fields()
|> Enum.filter(fn {_seq, _name, type, _opt, _max} -> type == :raw end)
|> Enum.map(fn {seq, name, _type, _opt, _max} -> {seg_id, name, seq} end)
end)
end
@doc """
Returns a summary map of coverage statistics.
## Examples
iex> summary = HL7v2.Standard.Coverage.coverage_summary()
iex> is_integer(summary.typed_segment_count)
true
"""
@spec coverage_summary() :: map()
def coverage_summary do
typed_segs = typed_segments()
all_segs = Standard.segment_ids()
typed_typs = typed_types()
all_typs = Standard.type_codes()
holes = raw_holes()
dispatched = runtime_dispatched()
total_fields =
typed_segs
|> Enum.map(&Standard.segment_module/1)
|> Enum.map(&length(&1.fields()))
|> Enum.sum()
%{
typed_segment_count: length(typed_segs),
total_segment_count: length(all_segs),
segment_coverage_pct: Float.round(length(typed_segs) / length(all_segs) * 100, 1),
typed_type_count: length(typed_typs),
total_type_count: length(all_typs),
type_coverage_pct: Float.round(length(typed_typs) / length(all_typs) * 100, 1),
total_typed_fields: total_fields,
raw_hole_count: length(holes),
raw_holes: holes,
runtime_dispatched_count: length(dispatched),
runtime_dispatched: dispatched
}
end
end