1 of 20

Complex maps through $translate

SNOMED ON FHIR · 18 OCTOBER 2026

Interpreting SNOMED CT Complex and Extended Maps, including the ICD-10 map, with FHIR

Jim Steel, Michael Lawley

CSIRO Australian e-Health Research Centre

OFFICIAL

OFFICIAL

2 of 20

Why this, and why here

CONTEXT

Where it came from

Ontoserver work on the SNOMED CT to ICD-10 map, written up as an ePoster for SNOMED CT Expo 2026 in Sydney next week.

What it is

A terminology server that interprets mapRules inside a standard $translate, and returns the map’s structure when it can’t decide.

Why this group

It picks up the group’s standing item on complex maps in $translate, building on the IG’s implicit ConceptMaps.

Complex maps through $translate · CSIRO AEHRC · 2

OFFICIAL

OFFICIAL

3 of 20

A complex map row is a rule plus a target

RECAP

Bronchitis 32398004 in the SNOMED CT to ICD-10 Extended Map 447562003

Row

mapRule

ICD-10 target

mapCategoryId

1 / 1

IFA 445518008 |Age at onset of clinical finding| <= 15.0 years

J20.9 Acute bronchitis, unspecified

context dependent

1 / 2

OTHERWISE TRUE

J40 Bronchitis, not specified as acute or chronic

properly classified

Columns: mapGroup, mapPriority, mapRule, mapAdvice, mapTarget, correlationId, plus mapCategoryId for Extended Maps. Within a group the first satisfied row wins; every group adds a code.

Complex maps through $translate · CSIRO AEHRC · 3

OFFICIAL

OFFICIAL

4 of 20

How these maps get used today

TODAY

Offline tools and tables

Generated from the RF2 files and run locally, e.g. IHTSDO’s snomed-to-icd10-mapper: concept, sex and age in, ICD-10 codes out.

$translate, rules as text

Implicit ConceptMap ?fhir_cm=447562003. Servers such as Snowstorm return every row with its mapRule and mapAdvice as strings.

So each client interprets

Either way the rule grammar is evaluated in each consumer, so the same governed map can give different answers in different places.

Complex maps through $translate · CSIRO AEHRC · 4

OFFICIAL

OFFICIAL

5 of 20

We’ve talked about this before

PREVIOUSLY, IN THIS GROUP

Mar 2023

R5 ConceptMap finalised: property, dependsOn and product clarified, with implicit maps from complex maps in mind.

Jun 2023

Concept Map in R5 page: mapRule and mapAdvice as properties. Left open: how should age and sex be passed in?

Feb 2024

Should the server apply the rules? DK: let the client choose. A Swedish vendor went proprietary for lack of a FHIR standard.

Mar 2024

DK: match.product per extra item, a URI per refset column. KK: R4 products are flat; rules arrive grouped.

Sources: SNOMED on FHIR meeting notes, item “Post R5 Concept Map – support for SNOMED Map features”; “Concept Map in R5” page · 5

OFFICIAL

OFFICIAL

6 of 20

What was raised, and how this work handles it

PICKING UP THE THREADS

Raised in the group

In this work

How should age and sex be passed in?

dependency: a finding as a Coding, or an observable with a typed value

The client should be able to apply the rules itself (DK)

Every candidate comes back with a status for each predicate

match.product per extra item, a URI per refset column (DK)

One product per extra mapGroup; columns as property URIs

R4 products are flat; rules come back grouped as text (KK)

Structured per row and per group; R4 carried with cross-version extensions

R4 can carry R5 elements in Parameters (ML)

That is how the R4 response works

Complex maps through $translate · CSIRO AEHRC · 6

OFFICIAL

OFFICIAL

7 of 20

Let $translate interpret the rules, and hand back the structure when it can’t decide.

THE IDEA

The same request and response shape work whether or not the server can evaluate the mapRule language.

OFFICIAL

OFFICIAL

8 of 20

Open the demo now and follow the examples

PLAY ALONG

  1. Pick a concept: Bronchitis, Infertile, Firework burn of skin
  2. Add context: age at onset, sex
  3. Watch the rule rows narrow
  4. Scroll down to see the $translate request and response

Captured mode evaluates real ICD-10 map rows in your browser. Live mode sends the same request to a FHIR terminology server you choose.

Complex maps through $translate · CSIRO AEHRC · 8

OFFICIAL

OFFICIAL

9 of 20

The client decides how much context to send

TWO MODES, ONE MECHANISM

Server resolves

Bronchitis with age at onset = 10 years

→ one match, J20.9, with its age predicate marked satisfied.

Every FHIR client gets the same answer from the same map.

Client decides

Bronchitis with no context

→ two matches, J20.9 and J40, with the age predicate marked unknown.

The client can see that age at onset would settle it.

OFFICIAL

OFFICIAL

10 of 20

Context goes in as dependency parameters

REQUEST

A finding is present

Satisfies IFA 248152002 |Female|

{"name": "dependency", "part": [

  {"name": "attribute", "valueUri":

     "http://snomed.info/id/404684003"},

  {"name": "value", "valueCoding": {

     "system": "http://snomed.info/sct",

     "code": "248152002",

     "display": "Female"}}

]}

An observable has a value

Satisfies IFA 445518008 |Age at onset| <= 15.0 years

{"name": "dependency", "part": [

  {"name": "attribute", "valueUri":

     "http://snomed.info/id/445518008"},

  {"name": "value", "valueQuantity": {

     "value": 10,

     "system": "http://unitsofmeasure.org",

     "code": "a"}}

]}

Attribute 404684003 |Clinical finding| marks a finding. A supplied finding satisfies IFA X if it is X or a descendant (sex is matched exactly). Units are normalised before comparison.

OFFICIAL

OFFICIAL

11 of 20

Each predicate has one of three statuses

EVALUATION

satisfied

unsatisfied

unknown

Unknown is not false

Context that wasn’t supplied leaves a predicate unknown. It never rules a row out.

Only a contradiction removes a row

A row drops out only when one of its predicates is unsatisfied.

Only a satisfied row shadows

Within a group, a satisfied row hides lower-priority rows. An unknown row hides nothing.

Groups combine

Each extra mapGroup adds a product. The matches are the combinations of surviving rows.

OFFICIAL

OFFICIAL

12 of 20

Each match carries the reasoning with it

RESPONSE

match

  relationship  related-to

  concept       ICD-10 J20.9

  dependsOn

    attribute   …/id/445518008

    value       <= 15 years

    status      unknown

  property      mapRule

  property      mapAdvice

  property      mapCategoryId

  product       additional-code#2

relationship + concept

The primary group’s target, from mapTarget and correlationId.

dependsOn + status

Every predicate the rows refer to, in the request’s own encoding, with its status.

property

The row’s mapRule, mapAdvice and mapCategoryId, each under its own URI.

product

One for each extra mapGroup: its code, relationship and its own row’s properties.

OFFICIAL

OFFICIAL

13 of 20

Bronchitis, with nothing else known

EXAMPLE · NO CONTEXT

  • Two matches come back
  • J20.9: the age rule is unknown, so it stays
  • J40: OTHERWISE TRUE, satisfied
  • Row 1/1 is unknown, so it shadows nothing

OFFICIAL

OFFICIAL

14 of 20

Bronchitis, no context: the J20.9 match

ON THE WIRE · A MATCH

{"name": "match", "part": [

  {"name": "relationship", "valueCode": "related-to"},

  {"name": "concept", "valueCoding": {"system": "http://hl7.org/fhir/sid/icd-10", "code": "J20.9", …}},

  {"name": "dependsOn", "part": [

    {"name": "attribute", "valueUri": "http://snomed.info/id/445518008"},

    {"name": "value", "valueQuantity": {"value": 15, "comparator": "<=", "unit": "years"}},

    {"name": "status", "valueCode": "unknown"}]},

  {"name": "property", "part": [{"name": "uri", "valueUri": "http://snomed.info/sct/property/mapRule"},

    {"name": "value", "valueString": "IFA 445518008 | Age at onset of … | <= 15.0 years"}]},

  {"name": "property", "part": [{"name": "uri", "valueUri": "http://snomed.info/sct/property/mapAdvice"},

    {"name": "value", "valueString": "IF AGE AT ONSET … CHOOSE J20.9 | MAP … IS CONTEXT DEPENDENT | …"}]},

  {"name": "property", "part": [{"name": "uri", "valueUri": "http://snomed.info/sct/property/mapCategoryId"},

    {"name": "value", "valueCoding": {"system": "http://snomed.info/sct", "code": "447639009", …}}]}

]}

The second match is J40 (OTHERWISE TRUE, no dependsOn). “…” marks shortened text.

OFFICIAL

OFFICIAL

15 of 20

Add age at onset, and the map decides

EXAMPLE · WITH CONTEXT

  • Age at onset 10 years
  • Row 1/1 is satisfied, so it wins
  • J40 is shadowed by it
  • One match: J20.9

OFFICIAL

OFFICIAL

16 of 20

Firework burn: a second code as a product

ON THE WIRE · SEVERAL MAP GROUPS

{"name": "match", "part": [

  {"name": "relationship", "valueCode": "related-to"},

  {"name": "concept", "valueCoding": {"system": "http://hl7.org/fhir/sid/icd-10", "code": "T30.0", …}},

  {"name": "property", … mapRule "TRUE", mapAdvice "ALWAYS T30.0", mapCategoryId 447637006 …},

  {"name": "product", "part": [

    {"name": "attribute", "valueUri": "http://snomed.info/sct/property/additional-code#2"},

    {"name": "value", "valueCoding": {"system": "http://hl7.org/fhir/sid/icd-10", "code": "W39", …}},

    {"name": "relationship", "valueCode": "related-to"},

    {"name": "property", "part": [{"name": "uri", "valueUri": "http://snomed.info/sct/property/mapRule"},

      {"name": "value", "valueString": "TRUE"}]},

    {"name": "property", "part": [{"name": "uri", "valueUri": "http://snomed.info/sct/property/mapAdvice"},

      {"name": "value", "valueString": "ALWAYS W39 | POSSIBLE REQUIREMENT FOR PLACE OF OCCURRENCE"}]},

    {"name": "property", … mapCategoryId 447637006 …}]}

]}

Base $translate doesn’t define relationship or property on a product; they’re this convention’s additions. When a group has several surviving rows, there’s one match per combination.

OFFICIAL

OFFICIAL

17 of 20

Infertile, intersex: an explicit no map

ON THE WIRE · AN EXPLICIT NO MAP

{"name": "result", "valueBoolean": false},

{"name": "match", "part": [

  {"name": "noMap", "valueBoolean": true},

  {"name": "property", "part": [

    {"name": "uri", "valueUri": "…/property/mapRule"},

    {"name": "value", "valueString": "OTHERWISE TRUE"}]},

  {"name": "property", "part": [

    {"name": "uri", "valueUri": "…/property/mapAdvice"},

    {"name": "value", "valueString":

      "MAP SOURCE CONCEPT CANNOT BE CLASSIFIED

       WITH AVAILABLE DATA"}]},

  {"name": "property", "part": [

    {"name": "uri", "valueUri": "…/property/mapCategoryId"},

    {"name": "value", "valueCoding": {"code": "447638001", …}}]}

]}

Intersex rules out the Female and Male rows, so OTHERWISE TRUE applies, and it has no target.

That comes back as a match with noMap and no concept (FHIR-58697), keeping the rule and reason.

A concept with no rows in the map returns no match at all.

OFFICIAL

OFFICIAL

18 of 20

Implemented in Ontoserver, across FHIR versions

STATUS

Version

How the response is carried

R5

Natively: match.property, dependsOn and product. Only status and the product’s children are additions.

R4

Cross-version extensions; correlation uses R4 equivalence codes. Same structure, same evaluation.

R6

additionalAttribute declares the dependsOn and product attributes. Status would still need an extension.

Ontoserver

Complex and Extended map refsets. Available from [release / date TBC].

Not covered (yet)

Reverse translate, the UK’s mapBlock, and free-text mapAdvice.

OFFICIAL

OFFICIAL

19 of 20

Questions I’d welcome views on

FOR DISCUSSION

Who owns the URIs?

Property URIs for refset columns and a status code system: the URI standard, this IG, or HL7?

Is product the right fit?

It gains relationship and property parts that base $translate doesn’t define. Keep them, or use extensions?

Status in core?

Should per-predicate status be proposed for R6, rather than staying an extension?

Other servers and editions

Would Snowstorm or others align on this shape? What would the UK’s mapBlock need?

OFFICIAL

OFFICIAL

20 of 20

Thank you

Try the demo: ontoserver.csiro.au/demo/complex-maps

See the ePoster: SNOMED CT Expo 2026, Sydney, 21–23 October

Follow noMap: jira.hl7.org/browse/FHIR-58697

Get in touch: jim.steel@csiro.au

Jim Steel, Michael Lawley · CSIRO Australian e-Health Research Centre

OFFICIAL

OFFICIAL