reflex-icd11ect 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. reflex_icd11ect-0.1.0/LICENSE +21 -0
  2. reflex_icd11ect-0.1.0/PKG-INFO +341 -0
  3. reflex_icd11ect-0.1.0/README.md +303 -0
  4. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect/__init__.py +105 -0
  5. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect/_runtime.py +291 -0
  6. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect/constants.py +194 -0
  7. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect/handler.py +242 -0
  8. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect/icd11ect.py +552 -0
  9. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect/icd11ect.pyi +512 -0
  10. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect/namespace.py +49 -0
  11. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect/namespace.pyi +193 -0
  12. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect/py.typed +0 -0
  13. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect/token.py +224 -0
  14. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect/types.py +164 -0
  15. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect.egg-info/PKG-INFO +341 -0
  16. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect.egg-info/SOURCES.txt +25 -0
  17. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect.egg-info/dependency_links.txt +1 -0
  18. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect.egg-info/requires.txt +14 -0
  19. reflex_icd11ect-0.1.0/custom_components/reflex_icd11ect.egg-info/top_level.txt +1 -0
  20. reflex_icd11ect-0.1.0/pyproject.toml +95 -0
  21. reflex_icd11ect-0.1.0/setup.cfg +4 -0
  22. reflex_icd11ect-0.1.0/tests/test_components.py +246 -0
  23. reflex_icd11ect-0.1.0/tests/test_handler.py +70 -0
  24. reflex_icd11ect-0.1.0/tests/test_packaging.py +70 -0
  25. reflex_icd11ect-0.1.0/tests/test_runtime.py +193 -0
  26. reflex_icd11ect-0.1.0/tests/test_token.py +180 -0
  27. reflex_icd11ect-0.1.0/tests/test_types.py +38 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ernesto Crespo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,341 @@
1
+ Metadata-Version: 2.4
2
+ Name: reflex-icd11ect
3
+ Version: 0.1.0
4
+ Summary: Reflex custom component for the WHO ICD-11 Embedded Classification Tools (Embedded Coding Tool and Embedded Browser)
5
+ Author-email: Ernesto Crespo <ecrespo@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ecrespo/reflex-icd11ect
8
+ Project-URL: Source, https://github.com/ecrespo/reflex-icd11ect
9
+ Project-URL: Issues, https://github.com/ecrespo/reflex-icd11ect/issues
10
+ Project-URL: ECT documentation, https://icd.who.int/docs/icd-api/icd11ect/
11
+ Keywords: reflex,reflex-custom-components,icd11,icd-11,icd11ect,who,medical-coding,healthcare,terminology
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Healthcare Industry
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
21
+ Classifier: Topic :: Software Development :: User Interfaces
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: reflex>=0.9.0
26
+ Requires-Dist: httpx>=0.27
27
+ Provides-Extra: dev
28
+ Requires-Dist: build; extra == "dev"
29
+ Requires-Dist: twine; extra == "dev"
30
+ Requires-Dist: pytest>=8; extra == "dev"
31
+ Requires-Dist: pytest-cov; extra == "dev"
32
+ Requires-Dist: ruff; extra == "dev"
33
+ Requires-Dist: trove-classifiers; extra == "dev"
34
+ Provides-Extra: security
35
+ Requires-Dist: bandit[toml]; extra == "security"
36
+ Requires-Dist: pip-audit; extra == "security"
37
+ Dynamic: license-file
38
+
39
+ # reflex-icd11ect
40
+
41
+ [![tests](https://github.com/ecrespo/reflex-icd11ect/actions/workflows/tests.yml/badge.svg)](https://github.com/ecrespo/reflex-icd11ect/actions/workflows/tests.yml)
42
+ [![quality](https://github.com/ecrespo/reflex-icd11ect/actions/workflows/quality.yml/badge.svg)](https://github.com/ecrespo/reflex-icd11ect/actions/workflows/quality.yml)
43
+ [![security](https://github.com/ecrespo/reflex-icd11ect/actions/workflows/security.yml/badge.svg)](https://github.com/ecrespo/reflex-icd11ect/actions/workflows/security.yml)
44
+ [![PyPI](https://img.shields.io/pypi/v/reflex-icd11ect.svg)](https://pypi.org/project/reflex-icd11ect/)
45
+ [![Python](https://img.shields.io/pypi/pyversions/reflex-icd11ect.svg)](https://pypi.org/project/reflex-icd11ect/)
46
+ [![Licence: MIT](https://img.shields.io/badge/licence-MIT-blue.svg)](LICENSE)
47
+
48
+ A [Reflex](https://reflex.dev) custom component for the WHO **ICD-11 Embedded
49
+ Classification Tools** ([ECT](https://icd.who.int/docs/icd-api/icd11ect/)): the
50
+ *Embedded Coding Tool* and the *Embedded Browser*, powered by the ICD-API.
51
+
52
+ It wraps [`@whoicd/icd11ect`](https://www.npmjs.com/package/@whoicd/icd11ect)
53
+ 1.8 and turns its imperative, page-global JavaScript API into ordinary Reflex
54
+ components, props and event handlers.
55
+
56
+ ```python
57
+ import reflex as rx
58
+ from reflex_icd11ect import icd11ect
59
+
60
+
61
+ class State(rx.State):
62
+ code: str = ""
63
+ title: str = ""
64
+
65
+ @rx.event
66
+ def on_select(self, entity: dict[str, str]):
67
+ self.code = entity["code"]
68
+ self.title = entity["title"]
69
+
70
+
71
+ def index() -> rx.Component:
72
+ return rx.vstack(
73
+ rx.heading(f"{State.code} {State.title}"),
74
+ icd11ect.coding_tool(
75
+ api_server_url="http://localhost", # your ICD-API deployment
76
+ placeholder="Search the ICD-11...",
77
+ on_select=State.on_select,
78
+ ),
79
+ )
80
+ ```
81
+
82
+ ## Install
83
+
84
+ ```bash
85
+ pip install reflex-icd11ect
86
+ # or
87
+ uv add reflex-icd11ect
88
+ ```
89
+
90
+ The npm package and its stylesheet are installed into the frontend by Reflex;
91
+ there is nothing to add to `rxconfig.py`.
92
+
93
+ ## What you get
94
+
95
+ | Component | Renders |
96
+ | --- | --- |
97
+ | `icd11ect.coding_tool(...)` | Search box + result window, configured and bound |
98
+ | `icd11ect.browser(...)` | The Embedded Browser |
99
+ | `icd11ect.controller(...)` | Lifecycle only, renders nothing |
100
+ | `icd11ect.provider(...)` | The controller used as page-wide settings |
101
+ | `icd11ect.search_input(ino)` | The bare `input.ctw-input` element |
102
+ | `icd11ect.result_window(ino)` | The bare `div.ctw-window` element |
103
+ | `icd11ect.browser_window(ino)` | The bare `div.ctw-eb-window` element |
104
+
105
+ Plus `reflex_icd11ect.handler` (the imperative `ECT.Handler` API as Reflex
106
+ events), `IcdTokenProvider` (OAUTH 2.0 client credentials, backend side),
107
+ `SelectedEntity` / `BrowserContent` (typed event payloads) and the constants
108
+ (`MMS_CHAPTERS`, `LANGUAGES`, `SOURCES`, ...).
109
+
110
+ The classes are available too: `Icd11ectCodingTool`, `Icd11ectBrowser`,
111
+ `Icd11ectController`.
112
+
113
+ ## Choosing a server
114
+
115
+ `api_server_url` is the only required setting.
116
+
117
+ | Deployment | Settings |
118
+ | --- | --- |
119
+ | Local ICD-API (Docker, Windows or systemd service) | `api_server_url="http://localhost"`, `api_secured=False` |
120
+ | WHO cloud API | `api_server_url="https://id.who.int"`, `api_secured=True` + a token |
121
+ | WHO developer test server (development only) | `api_server_url=WHO_DEVELOPER_TEST_API`, `api_secured=False` |
122
+
123
+ ```bash
124
+ # a local deployment, no credentials, no data leaving your network
125
+ docker run -p 80:80 -e acceptLicense=true -e saveAnalytics=true whoicd/icd-api
126
+ ```
127
+
128
+ ## Events
129
+
130
+ | Prop | ECT callback | Payload |
131
+ | --- | --- | --- |
132
+ | `on_select` | `selectedEntityFunction` | `code`, `title`, `uri`, `linearization_uri`, `foundation_uri`, `selected_text`, `search_query`, `i_no` |
133
+ | `on_search_start` | `searchStartedFunction` | none |
134
+ | `on_search_end` | `searchEndedFunction` | none |
135
+ | `on_browser_load` | `browserLoadedFunction` | none |
136
+ | `on_browser_change` | `browserChangedFunction` | `code`, `uri`, `i_no` |
137
+ | `on_token_request` | `getNewTokenFunction` | none |
138
+
139
+ Payload keys are snake_case; wrap them for attribute access:
140
+
141
+ ```python
142
+ from reflex_icd11ect import SelectedEntity
143
+
144
+ @rx.event
145
+ def on_select(self, entity: dict[str, str]):
146
+ selected = SelectedEntity.from_payload(entity)
147
+ if selected.is_postcoordinated:
148
+ ...
149
+ ```
150
+
151
+ `on_search_start` / `on_search_end` carry no instance id in ECT, so with several
152
+ instances on a page they are delivered to the instance whose search box has
153
+ focus, falling back to the one that last emitted an event.
154
+
155
+ ## Driving the tool from the backend
156
+
157
+ ```python
158
+ from reflex_icd11ect import handler
159
+
160
+
161
+ class State(rx.State):
162
+ @rx.event
163
+ def code_fever(self):
164
+ return handler.search("1", "fever")
165
+
166
+ @rx.event
167
+ def show_tuberculosis(self):
168
+ return handler.set_browser_code("browser", "1B11")
169
+
170
+ @rx.event
171
+ def spanish(self):
172
+ return handler.change_language("1", "es")
173
+ ```
174
+
175
+ `search`, `clear`, `set_browser_code`, `set_browser_uri`, `change_language`,
176
+ `change_source`, `change_minor_version`, `overwrite_configuration`, `bind` and
177
+ `set_token` are available. They run entirely on the client, with no extra round
178
+ trip.
179
+
180
+ ## OAUTH 2.0 with the WHO cloud API
181
+
182
+ Register at <https://icd.who.int/icdapi> for a client id and secret, then keep
183
+ the secret on the backend:
184
+
185
+ ```python
186
+ from reflex_icd11ect import IcdTokenProvider, icd11ect
187
+
188
+ provider = IcdTokenProvider.from_env() # ICD_CLIENT_ID / ICD_CLIENT_SECRET
189
+
190
+
191
+ class Auth(rx.State):
192
+ token: str = ""
193
+
194
+ @rx.event(background=True)
195
+ async def refresh_token(self):
196
+ token = await provider.async_token()
197
+ async with self:
198
+ self.token = token
199
+
200
+
201
+ icd11ect.coding_tool(
202
+ api_server_url="https://id.who.int",
203
+ api_secured=True,
204
+ token=Auth.token,
205
+ on_token_request=Auth.refresh_token, # fired when the token expires
206
+ on_select=State.on_select,
207
+ )
208
+ ```
209
+
210
+ `IcdTokenProvider` caches the token and refreshes it five minutes before it
211
+ expires. Only the short-lived token reaches the browser.
212
+
213
+ If you prefer the pattern WHO documents, where the browser fetches the token
214
+ itself, set `token_endpoint="/api/icd/token"` (and `token_field` when the JSON
215
+ field is not named `token`) instead of `token`.
216
+
217
+ ## Custom layouts
218
+
219
+ `coding_tool` is a `controller` with the two elements as children. Split them
220
+ when the search box and the results belong in different places:
221
+
222
+ ```python
223
+ rx.fragment(
224
+ icd11ect.controller(ino="dx", api_server_url=..., on_select=State.on_select),
225
+ rx.hstack(
226
+ rx.text("Diagnosis"),
227
+ icd11ect.search_input("dx", placeholder="..."),
228
+ ),
229
+ rx.card(icd11ect.result_window("dx")),
230
+ )
231
+ ```
232
+
233
+ The search box must stay uncontrolled: ECT writes into its value and disables it
234
+ while it loads, so do not bind `value` to your state.
235
+
236
+ ## Several instances
237
+
238
+ `ino` is ECT's `data-ctw-ino`; it identifies an instance and must be unique on
239
+ the page. ECT keeps a single configuration per page, so the component merges the
240
+ settings of every mounted instance and applies what an instance sets differently
241
+ through `ECT.Handler.overwriteConfiguration`:
242
+
243
+ ```python
244
+ icd11ect.coding_tool(ino="a", language="en", chapters_filter="")
245
+ icd11ect.coding_tool(ino="b", language="es", chapters_filter="06")
246
+ icd11ect.browser(ino="c", enable_select_button="categories")
247
+ ```
248
+
249
+ Only part of ECT's settings can differ per instance: `api_server_url`,
250
+ `api_secured`, `source`, `minor_version`, `language`, `popup_mode`,
251
+ `simplified_mode`, `disable_hierarchy`, `words_available`, `chapters_available`,
252
+ `chapters_filter`, `subtrees_filter`, `flexisearch_available`,
253
+ `search_by_code_or_uri`, `hierarchy_title` and `height`
254
+ (`reflex_icd11ect.OVERWRITABLE_SETTINGS`). The rest is page-wide and takes the
255
+ value of the last instance that set it.
256
+
257
+ ## Settings
258
+
259
+ Every ECT setting is a prop, snake_case instead of camelCase. Props you do not
260
+ set are not sent, so ECT keeps its own defaults; an empty string is treated the
261
+ same way, which makes state-driven props easy.
262
+
263
+ **Shared** — `api_server_url`, `api_secured`, `source` (`"mms"`, `"icf"`,
264
+ `"foundation"`), `minor_version`, `language`, `source_app`, `height`,
265
+ `hierarchy_title`, `hierarchy_resizable`, `other_postcoordination`,
266
+ `enable_keyboard`, `include_diagnostic_criteria`, `verbose`.
267
+
268
+ **Coding Tool** — `popup_mode`, `simplified_mode`, `disable_hierarchy`,
269
+ `words_available`, `chapters_available`, `chapters_filter`, `subtrees_filter`,
270
+ `flexisearch_available`, `search_by_code_or_uri`, `medical_coding_mode`,
271
+ `view_selected_uri`.
272
+
273
+ **Embedded Browser** — `enable_select_button` (`"none"`, `"categories"`,
274
+ `"all"`, `"allButRoot"`), `browser_search_available`,
275
+ `browser_advanced_search_available`, `browser_hierarchy_available`,
276
+ `browser_hierarchy_root_uris`, `browser_uri`,
277
+ `display_other_foundation_children`.
278
+
279
+ Every component accepts every setting: ECT keeps a single configuration per
280
+ page, so a Coding Tool setting passed to a browser would still take effect on
281
+ the page's Coding Tools, and hiding it would only mislead.
282
+
283
+ Bridge-only props: `token`, `token_endpoint`, `token_field`,
284
+ `token_timeout_ms`, `ino`, plus `placeholder`, `input_props` and `window_props`
285
+ for styling the elements.
286
+
287
+ `docs/settings.md` has the full table with ECT names and per-instance support.
288
+
289
+ ## How it works
290
+
291
+ ECT is not a React component: it renders itself into DOM nodes carrying
292
+ `data-ctw-ino`, and keeps one global configuration and one set of callbacks for
293
+ the whole page. The component therefore:
294
+
295
+ * renders the markup ECT looks for, with your `ino`;
296
+ * emits a page-level runtime (`window.__reflexIcd11ect`) that owns the ECT
297
+ configuration and routes ECT's global callbacks to the right instance;
298
+ * configures ECT with `autoBind: false` and calls `ECT.Handler.bind(ino)` from a
299
+ `useEffect`, because ECT's auto-binding waits for `window.onload`, an event
300
+ that has already fired by the time a single-page app renders a route;
301
+ * re-applies settings and rebinds whenever your props change.
302
+
303
+ `docs/architecture.md` goes into detail.
304
+
305
+ ## Demo app
306
+
307
+ ```bash
308
+ git clone https://github.com/ecrespo/reflex-icd11ect
309
+ cd reflex-icd11ect
310
+ uv pip install -e .
311
+ cd icd11ect_demo && uv run reflex run
312
+ ```
313
+
314
+ Six pages: the Coding Tool with every setting live, the Embedded Browser,
315
+ several instances side by side, custom layouts, server and OAUTH 2.0 handling,
316
+ and a generated API reference.
317
+
318
+ ## Documentation
319
+
320
+ * [`docs/settings.md`](docs/settings.md) — every prop, its ECT name and whether
321
+ it can differ per instance
322
+ * [`docs/architecture.md`](docs/architecture.md) — how the bridge works
323
+ * [`docs/troubleshooting.md`](docs/troubleshooting.md) — what to check when
324
+ nothing renders
325
+ * [ECT documentation](https://icd.who.int/docs/icd-api/icd11ect/) — WHO
326
+ * [ICD-API documentation](https://icd.who.int/docs/icd-api/) — WHO
327
+ * [`CONTRIBUTING.md`](CONTRIBUTING.md) — how to set up, test and release
328
+ * [`SECURITY.md`](SECURITY.md) — reporting a vulnerability, and how to keep
329
+ the ICD-API credentials out of the browser
330
+
331
+ ## Requirements
332
+
333
+ * Python >= 3.10, Reflex >= 0.9
334
+ * An ICD-API server (local deployment or the WHO cloud API)
335
+
336
+ ## Licence
337
+
338
+ MIT for this component. The ICD-11 and the Embedded Classification Tools are
339
+ published by the World Health Organization under their own licence: see
340
+ <https://icd.who.int/en/docs/icd11-license.pdf> and the
341
+ [ICD-API terms](https://icd.who.int/icdapi).
@@ -0,0 +1,303 @@
1
+ # reflex-icd11ect
2
+
3
+ [![tests](https://github.com/ecrespo/reflex-icd11ect/actions/workflows/tests.yml/badge.svg)](https://github.com/ecrespo/reflex-icd11ect/actions/workflows/tests.yml)
4
+ [![quality](https://github.com/ecrespo/reflex-icd11ect/actions/workflows/quality.yml/badge.svg)](https://github.com/ecrespo/reflex-icd11ect/actions/workflows/quality.yml)
5
+ [![security](https://github.com/ecrespo/reflex-icd11ect/actions/workflows/security.yml/badge.svg)](https://github.com/ecrespo/reflex-icd11ect/actions/workflows/security.yml)
6
+ [![PyPI](https://img.shields.io/pypi/v/reflex-icd11ect.svg)](https://pypi.org/project/reflex-icd11ect/)
7
+ [![Python](https://img.shields.io/pypi/pyversions/reflex-icd11ect.svg)](https://pypi.org/project/reflex-icd11ect/)
8
+ [![Licence: MIT](https://img.shields.io/badge/licence-MIT-blue.svg)](LICENSE)
9
+
10
+ A [Reflex](https://reflex.dev) custom component for the WHO **ICD-11 Embedded
11
+ Classification Tools** ([ECT](https://icd.who.int/docs/icd-api/icd11ect/)): the
12
+ *Embedded Coding Tool* and the *Embedded Browser*, powered by the ICD-API.
13
+
14
+ It wraps [`@whoicd/icd11ect`](https://www.npmjs.com/package/@whoicd/icd11ect)
15
+ 1.8 and turns its imperative, page-global JavaScript API into ordinary Reflex
16
+ components, props and event handlers.
17
+
18
+ ```python
19
+ import reflex as rx
20
+ from reflex_icd11ect import icd11ect
21
+
22
+
23
+ class State(rx.State):
24
+ code: str = ""
25
+ title: str = ""
26
+
27
+ @rx.event
28
+ def on_select(self, entity: dict[str, str]):
29
+ self.code = entity["code"]
30
+ self.title = entity["title"]
31
+
32
+
33
+ def index() -> rx.Component:
34
+ return rx.vstack(
35
+ rx.heading(f"{State.code} {State.title}"),
36
+ icd11ect.coding_tool(
37
+ api_server_url="http://localhost", # your ICD-API deployment
38
+ placeholder="Search the ICD-11...",
39
+ on_select=State.on_select,
40
+ ),
41
+ )
42
+ ```
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ pip install reflex-icd11ect
48
+ # or
49
+ uv add reflex-icd11ect
50
+ ```
51
+
52
+ The npm package and its stylesheet are installed into the frontend by Reflex;
53
+ there is nothing to add to `rxconfig.py`.
54
+
55
+ ## What you get
56
+
57
+ | Component | Renders |
58
+ | --- | --- |
59
+ | `icd11ect.coding_tool(...)` | Search box + result window, configured and bound |
60
+ | `icd11ect.browser(...)` | The Embedded Browser |
61
+ | `icd11ect.controller(...)` | Lifecycle only, renders nothing |
62
+ | `icd11ect.provider(...)` | The controller used as page-wide settings |
63
+ | `icd11ect.search_input(ino)` | The bare `input.ctw-input` element |
64
+ | `icd11ect.result_window(ino)` | The bare `div.ctw-window` element |
65
+ | `icd11ect.browser_window(ino)` | The bare `div.ctw-eb-window` element |
66
+
67
+ Plus `reflex_icd11ect.handler` (the imperative `ECT.Handler` API as Reflex
68
+ events), `IcdTokenProvider` (OAUTH 2.0 client credentials, backend side),
69
+ `SelectedEntity` / `BrowserContent` (typed event payloads) and the constants
70
+ (`MMS_CHAPTERS`, `LANGUAGES`, `SOURCES`, ...).
71
+
72
+ The classes are available too: `Icd11ectCodingTool`, `Icd11ectBrowser`,
73
+ `Icd11ectController`.
74
+
75
+ ## Choosing a server
76
+
77
+ `api_server_url` is the only required setting.
78
+
79
+ | Deployment | Settings |
80
+ | --- | --- |
81
+ | Local ICD-API (Docker, Windows or systemd service) | `api_server_url="http://localhost"`, `api_secured=False` |
82
+ | WHO cloud API | `api_server_url="https://id.who.int"`, `api_secured=True` + a token |
83
+ | WHO developer test server (development only) | `api_server_url=WHO_DEVELOPER_TEST_API`, `api_secured=False` |
84
+
85
+ ```bash
86
+ # a local deployment, no credentials, no data leaving your network
87
+ docker run -p 80:80 -e acceptLicense=true -e saveAnalytics=true whoicd/icd-api
88
+ ```
89
+
90
+ ## Events
91
+
92
+ | Prop | ECT callback | Payload |
93
+ | --- | --- | --- |
94
+ | `on_select` | `selectedEntityFunction` | `code`, `title`, `uri`, `linearization_uri`, `foundation_uri`, `selected_text`, `search_query`, `i_no` |
95
+ | `on_search_start` | `searchStartedFunction` | none |
96
+ | `on_search_end` | `searchEndedFunction` | none |
97
+ | `on_browser_load` | `browserLoadedFunction` | none |
98
+ | `on_browser_change` | `browserChangedFunction` | `code`, `uri`, `i_no` |
99
+ | `on_token_request` | `getNewTokenFunction` | none |
100
+
101
+ Payload keys are snake_case; wrap them for attribute access:
102
+
103
+ ```python
104
+ from reflex_icd11ect import SelectedEntity
105
+
106
+ @rx.event
107
+ def on_select(self, entity: dict[str, str]):
108
+ selected = SelectedEntity.from_payload(entity)
109
+ if selected.is_postcoordinated:
110
+ ...
111
+ ```
112
+
113
+ `on_search_start` / `on_search_end` carry no instance id in ECT, so with several
114
+ instances on a page they are delivered to the instance whose search box has
115
+ focus, falling back to the one that last emitted an event.
116
+
117
+ ## Driving the tool from the backend
118
+
119
+ ```python
120
+ from reflex_icd11ect import handler
121
+
122
+
123
+ class State(rx.State):
124
+ @rx.event
125
+ def code_fever(self):
126
+ return handler.search("1", "fever")
127
+
128
+ @rx.event
129
+ def show_tuberculosis(self):
130
+ return handler.set_browser_code("browser", "1B11")
131
+
132
+ @rx.event
133
+ def spanish(self):
134
+ return handler.change_language("1", "es")
135
+ ```
136
+
137
+ `search`, `clear`, `set_browser_code`, `set_browser_uri`, `change_language`,
138
+ `change_source`, `change_minor_version`, `overwrite_configuration`, `bind` and
139
+ `set_token` are available. They run entirely on the client, with no extra round
140
+ trip.
141
+
142
+ ## OAUTH 2.0 with the WHO cloud API
143
+
144
+ Register at <https://icd.who.int/icdapi> for a client id and secret, then keep
145
+ the secret on the backend:
146
+
147
+ ```python
148
+ from reflex_icd11ect import IcdTokenProvider, icd11ect
149
+
150
+ provider = IcdTokenProvider.from_env() # ICD_CLIENT_ID / ICD_CLIENT_SECRET
151
+
152
+
153
+ class Auth(rx.State):
154
+ token: str = ""
155
+
156
+ @rx.event(background=True)
157
+ async def refresh_token(self):
158
+ token = await provider.async_token()
159
+ async with self:
160
+ self.token = token
161
+
162
+
163
+ icd11ect.coding_tool(
164
+ api_server_url="https://id.who.int",
165
+ api_secured=True,
166
+ token=Auth.token,
167
+ on_token_request=Auth.refresh_token, # fired when the token expires
168
+ on_select=State.on_select,
169
+ )
170
+ ```
171
+
172
+ `IcdTokenProvider` caches the token and refreshes it five minutes before it
173
+ expires. Only the short-lived token reaches the browser.
174
+
175
+ If you prefer the pattern WHO documents, where the browser fetches the token
176
+ itself, set `token_endpoint="/api/icd/token"` (and `token_field` when the JSON
177
+ field is not named `token`) instead of `token`.
178
+
179
+ ## Custom layouts
180
+
181
+ `coding_tool` is a `controller` with the two elements as children. Split them
182
+ when the search box and the results belong in different places:
183
+
184
+ ```python
185
+ rx.fragment(
186
+ icd11ect.controller(ino="dx", api_server_url=..., on_select=State.on_select),
187
+ rx.hstack(
188
+ rx.text("Diagnosis"),
189
+ icd11ect.search_input("dx", placeholder="..."),
190
+ ),
191
+ rx.card(icd11ect.result_window("dx")),
192
+ )
193
+ ```
194
+
195
+ The search box must stay uncontrolled: ECT writes into its value and disables it
196
+ while it loads, so do not bind `value` to your state.
197
+
198
+ ## Several instances
199
+
200
+ `ino` is ECT's `data-ctw-ino`; it identifies an instance and must be unique on
201
+ the page. ECT keeps a single configuration per page, so the component merges the
202
+ settings of every mounted instance and applies what an instance sets differently
203
+ through `ECT.Handler.overwriteConfiguration`:
204
+
205
+ ```python
206
+ icd11ect.coding_tool(ino="a", language="en", chapters_filter="")
207
+ icd11ect.coding_tool(ino="b", language="es", chapters_filter="06")
208
+ icd11ect.browser(ino="c", enable_select_button="categories")
209
+ ```
210
+
211
+ Only part of ECT's settings can differ per instance: `api_server_url`,
212
+ `api_secured`, `source`, `minor_version`, `language`, `popup_mode`,
213
+ `simplified_mode`, `disable_hierarchy`, `words_available`, `chapters_available`,
214
+ `chapters_filter`, `subtrees_filter`, `flexisearch_available`,
215
+ `search_by_code_or_uri`, `hierarchy_title` and `height`
216
+ (`reflex_icd11ect.OVERWRITABLE_SETTINGS`). The rest is page-wide and takes the
217
+ value of the last instance that set it.
218
+
219
+ ## Settings
220
+
221
+ Every ECT setting is a prop, snake_case instead of camelCase. Props you do not
222
+ set are not sent, so ECT keeps its own defaults; an empty string is treated the
223
+ same way, which makes state-driven props easy.
224
+
225
+ **Shared** — `api_server_url`, `api_secured`, `source` (`"mms"`, `"icf"`,
226
+ `"foundation"`), `minor_version`, `language`, `source_app`, `height`,
227
+ `hierarchy_title`, `hierarchy_resizable`, `other_postcoordination`,
228
+ `enable_keyboard`, `include_diagnostic_criteria`, `verbose`.
229
+
230
+ **Coding Tool** — `popup_mode`, `simplified_mode`, `disable_hierarchy`,
231
+ `words_available`, `chapters_available`, `chapters_filter`, `subtrees_filter`,
232
+ `flexisearch_available`, `search_by_code_or_uri`, `medical_coding_mode`,
233
+ `view_selected_uri`.
234
+
235
+ **Embedded Browser** — `enable_select_button` (`"none"`, `"categories"`,
236
+ `"all"`, `"allButRoot"`), `browser_search_available`,
237
+ `browser_advanced_search_available`, `browser_hierarchy_available`,
238
+ `browser_hierarchy_root_uris`, `browser_uri`,
239
+ `display_other_foundation_children`.
240
+
241
+ Every component accepts every setting: ECT keeps a single configuration per
242
+ page, so a Coding Tool setting passed to a browser would still take effect on
243
+ the page's Coding Tools, and hiding it would only mislead.
244
+
245
+ Bridge-only props: `token`, `token_endpoint`, `token_field`,
246
+ `token_timeout_ms`, `ino`, plus `placeholder`, `input_props` and `window_props`
247
+ for styling the elements.
248
+
249
+ `docs/settings.md` has the full table with ECT names and per-instance support.
250
+
251
+ ## How it works
252
+
253
+ ECT is not a React component: it renders itself into DOM nodes carrying
254
+ `data-ctw-ino`, and keeps one global configuration and one set of callbacks for
255
+ the whole page. The component therefore:
256
+
257
+ * renders the markup ECT looks for, with your `ino`;
258
+ * emits a page-level runtime (`window.__reflexIcd11ect`) that owns the ECT
259
+ configuration and routes ECT's global callbacks to the right instance;
260
+ * configures ECT with `autoBind: false` and calls `ECT.Handler.bind(ino)` from a
261
+ `useEffect`, because ECT's auto-binding waits for `window.onload`, an event
262
+ that has already fired by the time a single-page app renders a route;
263
+ * re-applies settings and rebinds whenever your props change.
264
+
265
+ `docs/architecture.md` goes into detail.
266
+
267
+ ## Demo app
268
+
269
+ ```bash
270
+ git clone https://github.com/ecrespo/reflex-icd11ect
271
+ cd reflex-icd11ect
272
+ uv pip install -e .
273
+ cd icd11ect_demo && uv run reflex run
274
+ ```
275
+
276
+ Six pages: the Coding Tool with every setting live, the Embedded Browser,
277
+ several instances side by side, custom layouts, server and OAUTH 2.0 handling,
278
+ and a generated API reference.
279
+
280
+ ## Documentation
281
+
282
+ * [`docs/settings.md`](docs/settings.md) — every prop, its ECT name and whether
283
+ it can differ per instance
284
+ * [`docs/architecture.md`](docs/architecture.md) — how the bridge works
285
+ * [`docs/troubleshooting.md`](docs/troubleshooting.md) — what to check when
286
+ nothing renders
287
+ * [ECT documentation](https://icd.who.int/docs/icd-api/icd11ect/) — WHO
288
+ * [ICD-API documentation](https://icd.who.int/docs/icd-api/) — WHO
289
+ * [`CONTRIBUTING.md`](CONTRIBUTING.md) — how to set up, test and release
290
+ * [`SECURITY.md`](SECURITY.md) — reporting a vulnerability, and how to keep
291
+ the ICD-API credentials out of the browser
292
+
293
+ ## Requirements
294
+
295
+ * Python >= 3.10, Reflex >= 0.9
296
+ * An ICD-API server (local deployment or the WHO cloud API)
297
+
298
+ ## Licence
299
+
300
+ MIT for this component. The ICD-11 and the Embedded Classification Tools are
301
+ published by the World Health Organization under their own licence: see
302
+ <https://icd.who.int/en/docs/icd11-license.pdf> and the
303
+ [ICD-API terms](https://icd.who.int/icdapi).