reflex-icd11ect
A Reflex custom component for the WHO ICD-11 Embedded Classification Tools (ECT): the Embedded Coding Tool and the Embedded Browser, powered by the ICD-API.
It wraps @whoicd/icd11ect
1.8 and turns its imperative, page-global JavaScript API into ordinary Reflex
components, props and event handlers.
import reflex as rx
from reflex_icd11ect import icd11ect
class State(rx.State):
code: str = ""
title: str = ""
@rx.event
def on_select(self, entity: dict[str, str]):
self.code = entity["code"]
self.title = entity["title"]
def index() -> rx.Component:
return rx.vstack(
rx.heading(f"{State.code} {State.title}"),
icd11ect.coding_tool(
api_server_url="http://localhost", # your ICD-API deployment
placeholder="Search the ICD-11...",
on_select=State.on_select,
),
)
Install
pip install reflex-icd11ect
# or
uv add reflex-icd11ect
The npm package and its stylesheet are installed into the frontend by Reflex;
there is nothing to add to rxconfig.py.
What you get
| Component | Renders |
|---|---|
icd11ect.coding_tool(...) |
Search box + result window, configured and bound |
icd11ect.browser(...) |
The Embedded Browser |
icd11ect.controller(...) |
Lifecycle only, renders nothing |
icd11ect.provider(...) |
The controller used as page-wide settings |
icd11ect.search_input(ino) |
The bare input.ctw-input element |
icd11ect.result_window(ino) |
The bare div.ctw-window element |
icd11ect.browser_window(ino) |
The bare div.ctw-eb-window element |
Plus reflex_icd11ect.handler (the imperative ECT.Handler API as Reflex
events), IcdTokenProvider (OAUTH 2.0 client credentials, backend side),
SelectedEntity / BrowserContent (typed event payloads) and the constants
(MMS_CHAPTERS, LANGUAGES, SOURCES, ...).
The classes are available too: Icd11ectCodingTool, Icd11ectBrowser,
Icd11ectController.
Choosing a server
api_server_url is the only required setting.
| Deployment | Settings |
|---|---|
| Local ICD-API (Docker, Windows or systemd service) | api_server_url="http://localhost", api_secured=False |
| WHO cloud API | api_server_url="https://id.who.int", api_secured=True + a token |
| WHO developer test server (development only) | api_server_url=WHO_DEVELOPER_TEST_API, api_secured=False |
# a local deployment, no credentials, no data leaving your network
docker run -p 80:80 -e acceptLicense=true -e saveAnalytics=true whoicd/icd-api
Events
| Prop | ECT callback | Payload |
|---|---|---|
on_select |
selectedEntityFunction |
code, title, uri, linearization_uri, foundation_uri, selected_text, search_query, i_no |
on_search_start |
searchStartedFunction |
none |
on_search_end |
searchEndedFunction |
none |
on_browser_load |
browserLoadedFunction |
none |
on_browser_change |
browserChangedFunction |
code, uri, i_no |
on_token_request |
getNewTokenFunction |
none |
Payload keys are snake_case; wrap them for attribute access:
from reflex_icd11ect import SelectedEntity
@rx.event
def on_select(self, entity: dict[str, str]):
selected = SelectedEntity.from_payload(entity)
if selected.is_postcoordinated:
...
on_search_start / on_search_end carry no instance id in ECT, so with several
instances on a page they are delivered to the instance whose search box has
focus, falling back to the one that last emitted an event.
Driving the tool from the backend
from reflex_icd11ect import handler
class State(rx.State):
@rx.event
def code_fever(self):
return handler.search("1", "fever")
@rx.event
def show_tuberculosis(self):
return handler.set_browser_code("browser", "1B11")
@rx.event
def spanish(self):
return handler.change_language("1", "es")
search, clear, set_browser_code, set_browser_uri, change_language,
change_source, change_minor_version, overwrite_configuration, bind and
set_token are available. They run entirely on the client, with no extra round
trip.
OAUTH 2.0 with the WHO cloud API
Register at https://icd.who.int/icdapi for a client id and secret, then keep the secret on the backend:
from reflex_icd11ect import IcdTokenProvider, icd11ect
provider = IcdTokenProvider.from_env() # ICD_CLIENT_ID / ICD_CLIENT_SECRET
class Auth(rx.State):
token: str = ""
@rx.event(background=True)
async def refresh_token(self):
token = await provider.async_token()
async with self:
self.token = token
icd11ect.coding_tool(
api_server_url="https://id.who.int",
api_secured=True,
token=Auth.token,
on_token_request=Auth.refresh_token, # fired when the token expires
on_select=State.on_select,
)
IcdTokenProvider caches the token and refreshes it five minutes before it
expires. Only the short-lived token reaches the browser.
If you prefer the pattern WHO documents, where the browser fetches the token
itself, set token_endpoint="/api/icd/token" (and token_field when the JSON
field is not named token) instead of token.
Custom layouts
coding_tool is a controller with the two elements as children. Split them
when the search box and the results belong in different places:
rx.fragment(
icd11ect.controller(ino="dx", api_server_url=..., on_select=State.on_select),
rx.hstack(
rx.text("Diagnosis"),
icd11ect.search_input("dx", placeholder="..."),
),
rx.card(icd11ect.result_window("dx")),
)
The search box must stay uncontrolled: ECT writes into its value and disables it
while it loads, so do not bind value to your state.
Several instances
ino is ECT's data-ctw-ino; it identifies an instance and must be unique on
the page. ECT keeps a single configuration per page, so the component merges the
settings of every mounted instance and applies what an instance sets differently
through ECT.Handler.overwriteConfiguration:
icd11ect.coding_tool(ino="a", language="en", chapters_filter="")
icd11ect.coding_tool(ino="b", language="es", chapters_filter="06")
icd11ect.browser(ino="c", enable_select_button="categories")
Only part of ECT's settings can differ per instance: api_server_url,
api_secured, source, minor_version, language, popup_mode,
simplified_mode, disable_hierarchy, words_available, chapters_available,
chapters_filter, subtrees_filter, flexisearch_available,
search_by_code_or_uri, hierarchy_title and height
(reflex_icd11ect.OVERWRITABLE_SETTINGS). The rest is page-wide and takes the
value of the last instance that set it.
Settings
Every ECT setting is a prop, snake_case instead of camelCase. Props you do not set are not sent, so ECT keeps its own defaults; an empty string is treated the same way, which makes state-driven props easy.
Shared — api_server_url, api_secured, source ("mms", "icf",
"foundation"), minor_version, language, source_app, height,
hierarchy_title, hierarchy_resizable, other_postcoordination,
enable_keyboard, include_diagnostic_criteria, verbose.
Coding Tool — popup_mode, simplified_mode, disable_hierarchy,
words_available, chapters_available, chapters_filter, subtrees_filter,
flexisearch_available, search_by_code_or_uri, medical_coding_mode,
view_selected_uri.
Embedded Browser — enable_select_button ("none", "categories",
"all", "allButRoot"), browser_search_available,
browser_advanced_search_available, browser_hierarchy_available,
browser_hierarchy_root_uris, browser_uri,
display_other_foundation_children.
Every component accepts every setting: ECT keeps a single configuration per page, so a Coding Tool setting passed to a browser would still take effect on the page's Coding Tools, and hiding it would only mislead.
Bridge-only props: token, token_endpoint, token_field,
token_timeout_ms, ino, plus placeholder, input_props and window_props
for styling the elements.
docs/settings.md has the full table with ECT names and per-instance support.
How it works
ECT is not a React component: it renders itself into DOM nodes carrying
data-ctw-ino, and keeps one global configuration and one set of callbacks for
the whole page. The component therefore:
- renders the markup ECT looks for, with your
ino; - emits a page-level runtime (
window.__reflexIcd11ect) that owns the ECT configuration and routes ECT's global callbacks to the right instance; - configures ECT with
autoBind: falseand callsECT.Handler.bind(ino)from auseEffect, because ECT's auto-binding waits forwindow.onload, an event that has already fired by the time a single-page app renders a route; - re-applies settings and rebinds whenever your props change.
docs/architecture.md goes into detail.
Demo app
git clone https://github.com/ecrespo/reflex-icd11ect
cd reflex-icd11ect
uv pip install -e .
cd icd11ect_demo && uv run reflex run
Six pages: the Coding Tool with every setting live, the Embedded Browser, several instances side by side, custom layouts, server and OAUTH 2.0 handling, and a generated API reference.
Documentation
docs/settings.md— every prop, its ECT name and whether it can differ per instancedocs/architecture.md— how the bridge worksdocs/troubleshooting.md— what to check when nothing renders- ECT documentation — WHO
- ICD-API documentation — WHO
CONTRIBUTING.md— how to set up, test and releaseSECURITY.md— reporting a vulnerability, and how to keep the ICD-API credentials out of the browser
Requirements
- Python >= 3.10, Reflex >= 0.9
- An ICD-API server (local deployment or the WHO cloud API)
Licence
MIT for this component. The ICD-11 and the Embedded Classification Tools are published by the World Health Organization under their own licence: see https://icd.who.int/en/docs/icd11-license.pdf and the ICD-API terms.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file reflex_icd11ect-0.1.0.tar.gz.
File metadata
- Download URL: reflex_icd11ect-0.1.0.tar.gz
- Upload date:
- Size: 37.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fe5310727fd16655543e8103c0652ca5fca0d8669d100de94a3553c7087eabf6
|
|
| MD5 |
e3852f02f4b5ec632217a6426693ea97
|
|
| BLAKE2b-256 |
debeee637ff34c8c4940db1eb8dba81bdaab57c5ef4ec1a968efb4969af91393
|
Provenance
The following attestation bundles were made for reflex_icd11ect-0.1.0.tar.gz:
Publisher:
release.yml on ecrespo/reflex-icd11ect
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
reflex_icd11ect-0.1.0.tar.gz -
Subject digest:
fe5310727fd16655543e8103c0652ca5fca0d8669d100de94a3553c7087eabf6 - Sigstore transparency entry: 2881732267
- Sigstore integration time:
-
Permalink:
ecrespo/reflex-icd11ect@31a5063341f965bdbe608de208cf082eb282f6e2 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ecrespo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@31a5063341f965bdbe608de208cf082eb282f6e2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file reflex_icd11ect-0.1.0-py3-none-any.whl.
File metadata
- Download URL: reflex_icd11ect-0.1.0-py3-none-any.whl
- Upload date:
- Size: 35.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
85648f2890d061f9480701430bf010cd7e179f5693be0578eed62d63a26f5407
|
|
| MD5 |
90d226edac9e7ecc92ea5a83dfe2c467
|
|
| BLAKE2b-256 |
049f774f7c885bd2f0708b5cd4026b42bcc278ae77756e1a08bf38bc478ae435
|
Provenance
The following attestation bundles were made for reflex_icd11ect-0.1.0-py3-none-any.whl:
Publisher:
release.yml on ecrespo/reflex-icd11ect
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
reflex_icd11ect-0.1.0-py3-none-any.whl -
Subject digest:
85648f2890d061f9480701430bf010cd7e179f5693be0578eed62d63a26f5407 - Sigstore transparency entry: 2881732334
- Sigstore integration time:
-
Permalink:
ecrespo/reflex-icd11ect@31a5063341f965bdbe608de208cf082eb282f6e2 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ecrespo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@31a5063341f965bdbe608de208cf082eb282f6e2 -
Trigger Event:
push
-
Statement type: