Skip to content

Validator does not handle choice groups correctly #151

@lqbach

Description

@lqbach

Description

hl7apy.validation.Validator does not differentiate between sequence and choice group types. Both fall into the same branch in _check_known_element, which iterates all child references and calls _check_repetitions on each individually. For choice groups, this is incorrect because only one child needs to be present, not all of them.

Steps to Reproduce

from hl7apy.parser import parse_message
from hl7apy.validation import Validator, VALIDATION_LEVEL

msg_text = (
    "MSH|^~\\&|SND|SND|RCV|RCV|20260101120000||ORM^O01|MSG001|P|2.3\r"
    "PID|1||12345||DOE^JOHN|||19800101|M\r"
    "ORC|NW||||||1^^^20260101120000|20260101120000\r"
    "OBR|1|||SPEC-001|PANEL|||20260101090000|||||||CSF||||||||||||1^^^20260101120000\r"
)

msg = parse_message(msg_text, find_groups=True)
Validator(VALIDATION_LEVEL.TOLERANT).validate(msg)

Expected

Validation should pass because OBR satisfies ORM_O01_CHOICE (a choice group). See ORM_O01 message structure

Actual

hl7apy.exceptions.ValidationError: Missing required child ORM_O01_CHOICE.RQD

Root Cause

hl7apy/hl7apy/validation.py

Lines 151 to 176 in 0297526

if ref[0] in ('sequence', 'choice'):
element_children = {c.name for c in el.children if not c.is_z_element()}
valid_children, valid_children_refs = _get_valid_children_info(ref)
# check that the children are all allowed children
if not element_children <= valid_children:
errs.append(ValidationError("Invalid children detected for {}: {}".
format(el, list(element_children - valid_children))))
# iterates the valid children
for child_ref in valid_children_refs:
# it gets the structure of the children to check
child_name, cardinality = _get_child_reference_info(child_ref)
try:
# it gets all the occurrences of the children of a type
children = el.children.get(child_name)
except Exception:
# TODO: it is due to the lack of element in the official reference files... should
# we raise an exception here?
pass
else:
_check_repetitions(el, children, cardinality, child_name, errs)
# calls validation for every children
for c in children:
_is_valid(c, child_ref[1], errs, warns)

sequence and choice share the same code path. For ORM_O01_CHOICE, valid_children_refs contains OBR, RQD, RQ1, RXO, ODS, ODT each with cardinality (1, 1). The loop enforces all six as required. The correct semantics for choice is exactly one of the options must be present.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions