Walking a concept tree
A vocabulary with thirty thousand entries will not fit in a dropdown. It is a tree, and the list an endpoint hands you often starts partway down it, so the first thing to learn is how to climb.
Everything below elaborates this sentence.
What goes wrong if you treat it as a list
Small coded lists are flat. Identifier types are eleven entries with no structure: every concept
comes back at depth: 0, and a dropdown is the correct interface.
Interventions are not that. There are tens of thousands of them, arranged in layers: benefits, then categories, then interventions, then services, with protocols and drugs beneath those. Three things follow, and each one bites a different integration:
| Call | What it does | What you keep |
|---|---|---|
You cannot render it all | A single fetch of every concept is slow, and far too large to use as a picker. Users need to narrow down, which means the interface has to open one level at a time. | roots first, children on demand |
The list may start partway down | Some endpoints return interventions at the sub-intervention level rather than at the top. What you receive is a set of leaves rather than a set of roots, so a naive renderer shows you the bottom of the tree with no idea what it belongs to. | climb with /parents/ |
Grouping nodes are not selectable | A category is a heading. If you render every concept as an option, a user can pick a heading, and the claim that results is rejected for a code that was never billable. | concept_class |
The second one costs the most, because nothing in the response tells you it happened. You get a well-formed list of concepts, each with a real code, and it looks complete.
The two directions
Everything here is one of two moves: down, to offer choices, or up, to work out what a leaf belongs to.
What lazy rendering looks like at the desk. Only the opened branch has been fetched.
GET /search/typeahead/?q=echoINT-CARD-ECHO-TTEINT-CARD-ECHO-TOEThe calls
One thing to settle before the calls. Code systems, value sets and concept maps are all addressable
by their short code, so /code-systems/IDENTIFIER-TYPES/ works exactly as /code-systems/1251/ does.
Concepts are the exception: a concept code is unique only inside its own system, so the concept
routes below take a numeric id. Resolve it once from the list read and keep it beside the code you
store:
The list read is addressed by short code; the id on the row it returns is what the tree calls
below need. Storing the id next to the code saves this lookup on every subsequent walk, and unlike
a display string, an id is safe to cache because it never changes.
id, alongside the codecurl -sS "https://ilm-dev.dha.go.ke/dev/ts/api/v2/concepts/?system=IDENTIFIER-TYPES&search=NATIONAL_ID&version=serving" \
-H "authorization: Bearer $TS_TOKEN"Down the tree, one level at a time:
The top layer of a vocabulary. Start here for anything you cannot render whole.
the root codesThe direct children of one node. Called when a user opens a branch, and cached so reopening it does not re-fetch.
Up the tree, from a leaf you were handed:
One level up. Note the plural: a concept may have more than one parent, and a breadcrumb that renders a single path silently picks one of them.
Every ancestor, transitively. This is what turns a list of leaves into a tree you can render.
And the whole subtree, when you genuinely need it:
Everything beneath a node, transitively. Useful for a rule that applies to a whole branch, and for checking whether a billed code falls under an approved one.
Is this code under that one?
A common question at Bill: the preauthorisation approved a category, and a specific service was performed. Does the service fall under what was approved?
Do not answer this by walking descendants and searching the list. There is one call:
How two codes relate: equivalent, subsumes, subsumed-by or not-subsumed.
outcomesubsumes means codeA is the broader one, so an approved category subsuming a billed service is
the answer you want. not-subsumed means the two are in unrelated branches, which is the case
worth surfacing to an operator before submission rather than after rejection.
Building it so it stays fast
- 01Fetch roots once, at start-upThe top layer of a vocabulary changes rarely. Load it with the rest of your terminology sync rather than when a patient is waiting.
- 02Cache each branch by node idA user who collapses and reopens Cardiology should not cost you a round-trip. Cache the children keyed on the node, and invalidate the whole cache on a version change rather than per node.
- 03Search server-side, do not filter your cacheA search box that filters only the branches already fetched will confidently report no results for a code that exists. Send the term to the service.
- 04Use concept_class to decide what is selectableGrouping nodes render as headings. Do not infer this from child_count, because a leaf category with nothing under it yet is still not billable.
- 05Store the code and the version, never the displayA display string you stored is a claim you cannot defend once the wording changes. The code is what was actually chosen, and the label is only one way of showing it.
What breaks here
| Where it surfaces | What the desk sees | What actually went wrong |
|---|---|---|
| First render of a large vocabulary | "The picker takes twelve seconds to open" | Every concept fetched up front. Fetch roots, then children on demand, since the user only ever sees one branch at a time anyway. |
| A list that arrives partway down | "We are showing sub-services with no idea what they belong to" | Leaves rendered as though they were roots. Resolve ancestors and group under them. |
| Submission | "The code was rejected and it is definitely in the list" | A grouping node was offered as an option. Read concept_class before rendering something selectable. |
| Search | "Searching finds nothing, but browsing finds it" | The search box is filtering the local cache rather than calling the service, so it can only find branches already opened. |
| Weeks after a terminology release | "Our tree shows a parent that no longer exists" | A client-assembled hierarchy, cached and never invalidated. Read ancestors from the service and key the cache on the version. |
Next: Validating a code · Pinning to a release · or the whole surface at the terminology service

