Docs: FLAT & OPT Validation
Validating FLAT Compositions & OPT Templates
Most errors from an openEHR CDR come from a composition that doesn't match its template, or from a template that was broken to begin with. oehrpy's validators catch both locally: FlatValidator checks FLAT compositions against the Web Template, and OPTValidator checks OPT 1.4 XML before you upload it or generate code from it.
FLAT Format Validator
The FlatValidator validates FLAT format compositions against Web Template definitions before submission to a CDR. It catches invalid paths, wrong suffixes, missing required fields, and provides "did you mean?" suggestions for renamed nodes.
Try it in the browser: The FLAT Validator web tool runs entirely client-side — paste your Web Template and FLAT composition and validate instantly.
Python API
from oehrpy.validation import FlatValidator
# Initialize with a Web Template JSON dict
validator = FlatValidator.from_web_template(web_template, platform="ehrbase")
# Validate a FLAT composition
result = validator.validate(flat_composition)
if not result.is_valid:
for error in result.errors:
print(f" {error.path}: {error.message}")
if error.suggestion:
print(f" Did you mean: {error.suggestion}")Fetching from EHRBase
# Or fetch the Web Template directly from EHRBase
validator = await FlatValidator.from_ehrbase(
client=ehrbase_client,
template_id="IDCR - Adverse Reaction List.v1"
)
result = validator.validate(flat_data)What It Catches
- Unknown paths — paths not in the Web Template, with fuzzy "did you mean?" suggestions
- Renamed nodes — detects when a template renames a node (e.g.,
substance→causative_agent) and suggests the correct path - Wrong suffixes — e.g.,
|valueon aDV_QUANTITYinstead of|magnitude - Index notation mismatch — EHRBase 2.x doesn't use
:0for single-occurrence items - Missing required fields —
category,language,territory,composer,context/start_time,context/setting
Platform Support
Pass platform="ehrbase" or platform="better" to match your CDR's FLAT format dialect:
- EHRBase 2.x — no
:0indexing on single-occurrence items, no/any_event/nodes,tree.idprefix - Better — may include
:0indexing on tree-based nodes (the validator and tests verify that some leaf paths carry:0);/any_event:0/andtemplate_idprefix support are defined in the dialect configuration but not yet implemented in the path enumerator
Validation Result
# The result contains all details
result.is_valid # bool
result.errors # list[ValidationError] - invalid paths
result.warnings # list[ValidationError] - missing required fields
result.platform # "ehrbase" or "better"
result.template_id # template ID from the Web Template
result.valid_path_count # total valid paths in the template
result.checked_path_count # paths checked in the composition
# Each error has:
error.path # the invalid path
error.error_type # "unknown_path", "wrong_suffix", "missing_required", "index_mismatch"
error.message # human-readable explanation
error.suggestion # suggested fix (if available)
error.valid_alternatives # list of alternative valid pathsExploring Valid Paths
# List all valid FLAT paths for a template
validator = FlatValidator.from_web_template(wt, platform="ehrbase")
for path in validator.valid_paths:
print(path)OPT Validator
The OPTValidator validates OPT 1.4 (Operational Template) XML files before parsing or code generation. It checks well-formedness, semantic integrity, and structural quality — catching issues that would otherwise surface as cryptic errors downstream. It also provides informational hints about potential FLAT path implications (though authoritative FLAT paths come from the Web Template, per ADR-0005).
Basic Usage
from oehrpy.validation.opt import OPTValidator
validator = OPTValidator()
# Validate from a file
result = validator.validate_file("vital_signs.opt")
# Or validate an XML string
result = validator.validate_string(xml_content)
if result.is_valid:
print(f"Valid: {result.template_id} ({result.archetype_count} archetypes)")
else:
for issue in result.errors:
print(f" [{issue.code}] {issue.message}")
if issue.suggestion:
print(f" Fix: {issue.suggestion}")Integrated Validation
Both parse_opt() and generate_builder_from_opt() accept a validate=True flag for fail-fast validation:
from oehrpy.templates import parse_opt, generate_builder_from_opt
from oehrpy.validation.opt import OPTValidationError
# Validate before parsing
try:
template = parse_opt("template.opt", validate=True)
except OPTValidationError as e:
print(f"Invalid: {e.result.error_count} errors")
for issue in e.result.errors:
print(f" {issue.message}")
# Validate before generating a builder skeleton
try:
code = generate_builder_from_opt("template.opt", validate=True)
except OPTValidationError as e:
print(f"Cannot generate: {e.result.error_count} errors")CLI
Validate OPT files from the command line:
# Text output (default)
python -m oehrpy.validate_opt_cli template.opt
# JSON output (for CI/CD pipelines)
python -m oehrpy.validate_opt_cli template.opt --output json
# Treat warnings as errors
python -m oehrpy.validate_opt_cli template.opt --strict
# Include FLAT path impact hints (informational, see ADR-0005)
python -m oehrpy.validate_opt_cli template.opt --show-flat-pathsExit code 0 means valid, 1 means errors were found (or warnings in --strict mode).
Validation Categories
The validator runs four categories of checks in order:
- Well-formedness (errors) — XML validity, correct namespace, required fields (
template_id,concept,language), valid archetype IDs, RM type names against the 1.1.0 registry, occurrence constraints, and duplicate node IDs - Semantic integrity (errors) — missing term definitions for referenced
node_ids, orphaned terminology bindings, mandatory nodes without resolvable names - Structural warnings — draft lifecycle state, unstable archetype versions (
v0), prohibited nodes still in the tree, unconstrained archetype slots, special characters in concept names or term names, overuse of the same archetype - FLAT path impact (info) — informational hints about renamed nodes and potential path collisions. Note: authoritative FLAT paths come from the Web Template JSON, not OPT analysis (ADR-0005)
Validation Result
# OPTValidationResult fields
result.is_valid # bool - True only if zero errors
result.template_id # str | None - extracted template ID
result.concept # str | None - extracted concept name
result.node_count # int - total nodes parsed
result.archetype_count # int - distinct archetypes found
result.error_count # int - number of errors
result.warning_count # int - number of warnings
result.errors # list[OPTValidationIssue] - error-severity issues
result.warnings # list[OPTValidationIssue] - warning-severity issues
# Each issue has:
issue.severity # "error", "warning", or "info"
issue.category # "wellformedness", "semantic", "structural", "flat_impact"
issue.code # e.g., "MISSING_TERM_DEF", "INVALID_RM_TYPE"
issue.message # human-readable description
issue.xpath # XPath to the offending element (if applicable)
issue.node_id # at-code (if applicable)
issue.archetype_id # archetype ID (if applicable)
issue.suggestion # recommended fix (if available)
# Serialize for reporting
result.to_dict() # dict
result.to_json(indent=2) # JSON stringFiltering Issues
# Filter by category
semantic = [i for i in result.issues if i.category == "semantic"]
# Filter by specific code
missing_terms = [i for i in result.issues if i.code == "MISSING_TERM_DEF"]
# Get FLAT path impact analysis
flat_issues = [i for i in result.issues if i.category == "flat_impact"]