fhirsmith 0.18.1 → 1.0.0
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.
- package/ACCESSIBILITY.md +119 -0
- package/CHANGELOG.md +190 -9
- package/CONFIGURATION.md +305 -0
- package/LICENSE +29 -0
- package/README.md +29 -122
- package/cluster.js +1220 -0
- package/cluster.md +707 -0
- package/config-template.json +77 -8
- package/extension-tracker/extension-tracker-template.html +5 -8
- package/extension-tracker/extension-tracker.js +0 -2
- package/folder/folder.js +7 -6
- package/ips/analysis.js +560 -0
- package/ips/ips-template.html +5 -8
- package/ips/ips.js +331 -33
- package/ips/jurisdictions.js +65 -0
- package/ips/nationality.js +120 -0
- package/ips/pipeline.js +131 -17
- package/ips/presenter.js +1 -1
- package/ips/profile-editor.js +283 -4
- package/ips/readme.md +170 -20
- package/ips/render.js +769 -109
- package/ips/scripts.js +46 -0
- package/ips/store.js +123 -11
- package/ips/structure.js +247 -0
- package/ips/terminology.js +1 -1
- package/library/body-limit.js +22 -0
- package/library/cache-generation.js +15 -0
- package/library/cgroup-memory.js +133 -1
- package/library/cluster-counts.js +121 -0
- package/library/cluster-events.js +153 -0
- package/library/cluster-metrics.js +193 -0
- package/library/cluster-status.js +468 -0
- package/library/config-reader.js +96 -0
- package/library/daily-report.js +100 -2
- package/library/html-server.js +27 -1
- package/library/i18nsupport.js +23 -1
- package/library/keep-alive.js +18 -0
- package/library/languages.js +17 -3
- package/library/logger.js +31 -0
- package/library/memory-guard.js +428 -0
- package/library/native-memory.js +52 -0
- package/library/npm-audit.js +51 -25
- package/library/openapi-doc.js +7 -5
- package/library/package-manager.js +8 -0
- package/library/role.js +197 -0
- package/library/server-credentials.js +169 -0
- package/library/sqlite-utilities.js +59 -0
- package/library/ssrf-guard.js +100 -0
- package/library/static-response-stats.js +16 -0
- package/library/version-utilities.js +50 -0
- package/library/worker-count.js +192 -0
- package/nginx.md +30 -11
- package/npmprojector/indexer.js +18 -10
- package/package.json +11 -8
- package/packages/feed-check-page.js +117 -0
- package/packages/feed-check.js +279 -0
- package/packages/npm-check.js +57 -3
- package/packages/npm-signing.js +219 -0
- package/packages/openapi.yaml +207 -1
- package/packages/package-crawler.js +322 -41
- package/packages/packages-template.html +6 -8
- package/packages/packages.js +604 -57
- package/packages/readme.md +113 -1
- package/publisher/publisher-template.html +2 -5
- package/publisher/publisher.js +121 -110
- package/publisher/readme.md +75 -0
- package/registry/api.js +179 -1
- package/registry/crawler.js +86 -108
- package/registry/documentation/api.md +68 -2
- package/registry/documentation/configuration.md +34 -0
- package/registry/documentation/internals.md +4 -1
- package/registry/documentation/readme.md +3 -2
- package/registry/model.js +8 -0
- package/registry/openapi.yaml +84 -0
- package/registry/registry-template.html +5 -8
- package/registry/registry.js +58 -6
- package/root-bare-template.html +7 -4
- package/root-template.html +5 -8
- package/server.js +398 -198
- package/shl/shl.js +38 -37
- package/static/a11y/a11y-check.js +832 -0
- package/static/a11y/axe.min.js +12 -0
- package/static/assets/css/bootstrap-fhir.css +15 -15
- package/static/assets/images/flags/au.svg +18 -0
- package/static/assets/images/flags/unknown.svg +11 -0
- package/static/assets/images/flags/us.svg +63 -0
- package/static/assets/images/icon_audit.png +0 -0
- package/static/assets/images/icon_clinical.png +0 -0
- package/static/assets/images/icon_document.png +0 -0
- package/static/assets/images/icon_external.png +0 -0
- package/static/assets/images/icon_medication.png +0 -0
- package/static/assets/images/icon_organization.png +0 -0
- package/static/assets/images/icon_other.png +0 -0
- package/static/assets/images/icon_person.png +0 -0
- package/static/assets/images/icon_section.png +0 -0
- package/stats-db.js +45 -0
- package/stats.js +197 -10
- package/testing/testing-template.html +5 -8
- package/token/nginx_passport_setup.md +1 -1
- package/token/security_guide.md +1 -1
- package/token/token-template.html +2 -5
- package/token/token.js +40 -36
- package/translations/Messages.properties +19 -0
- package/tx/README.md +158 -4
- package/tx/closure/closure-store.js +126 -3
- package/tx/cm/cm-database.js +69 -57
- package/tx/cm/cm-package.js +50 -16
- package/tx/cs/cs-api.js +284 -19
- package/tx/cs/cs-api.md +41 -4
- package/tx/cs/cs-areacode.js +1 -1
- package/tx/cs/cs-country.js +17 -5
- package/tx/cs/cs-cpt.js +33 -16
- package/tx/cs/cs-cs.js +199 -24
- package/tx/cs/cs-currency.js +1 -1
- package/tx/cs/cs-icd11.js +21 -13
- package/tx/cs/cs-icf.js +22 -3
- package/tx/cs/cs-lang.js +2 -2
- package/tx/cs/cs-loinc.js +193 -67
- package/tx/cs/cs-mimetypes.js +12 -2
- package/tx/cs/cs-ndc.js +40 -25
- package/tx/cs/cs-omop.js +54 -29
- package/tx/cs/cs-rxnorm.js +300 -196
- package/tx/cs/cs-snomed.js +861 -95
- package/tx/cs/cs-ucum.js +14 -4
- package/tx/cs/cs-unii.js +18 -14
- package/tx/cs/cs-virtual.js +210 -0
- package/tx/ecosystem-resolver.js +413 -0
- package/tx/html/cluster-dash-metrics.liquid +26 -0
- package/tx/html/cluster-home-metrics.liquid +31 -0
- package/tx/html/cluster-metrics-script.liquid +251 -0
- package/tx/html/codesystem-operations.liquid +3 -3
- package/tx/html/ecl-panel.liquid +1 -1
- package/tx/html/external-metrics.liquid +17 -0
- package/tx/html/operations-form.liquid +17 -17
- package/tx/html/search-form.liquid +11 -11
- package/tx/html/status-line.liquid +58 -0
- package/tx/html/status-panel.liquid +140 -0
- package/tx/html/status-tasks.liquid +25 -0
- package/tx/html/tx-template.html +2 -5
- package/tx/importers/atc-to-fhir.js +17 -16
- package/tx/importers/export-icd11-codesystem.js +3 -2
- package/tx/importers/import-icd11.js +4 -3
- package/tx/importers/import-loinc.module.js +118 -56
- package/tx/importers/import-ndc.module.js +56 -44
- package/tx/importers/import-rxnorm.module.js +118 -79
- package/tx/importers/import-sct.module.js +125 -61
- package/tx/importers/import-unii.module.js +37 -36
- package/tx/importers/subset-loinc.module.js +49 -48
- package/tx/importers/subset-rxnorm.module.js +29 -28
- package/tx/importers/terminal.js +18 -0
- package/tx/importers/tx-import-base.js +13 -12
- package/tx/importers/tx-import-settings.js +5 -4
- package/tx/importers/tx-import.js +53 -52
- package/tx/library/canonical-resource.js +49 -0
- package/tx/library/codesystem.js +94 -5
- package/tx/library/designations.js +45 -2
- package/tx/library/extensions.js +13 -7
- package/tx/library/implicit-valuesets.js +66 -0
- package/tx/library/renderer.js +77 -21
- package/tx/library/sqlite-utilities.js +2 -0
- package/tx/library/tho-precedence.js +40 -0
- package/tx/library/ucum-parsers.js +11 -1
- package/tx/library/wait-for-cache.js +43 -0
- package/tx/library.js +321 -22
- package/tx/openapi-operations.js +6 -4
- package/tx/operation-context.js +460 -99
- package/tx/params.js +25 -0
- package/tx/problems.js +3 -5
- package/tx/provider.js +314 -67
- package/tx/resource-cache-sqlite.js +1050 -0
- package/tx/sct/documentation.md +45 -21
- package/tx/sct/ecl.js +81 -17
- package/tx/sct/editions.js +39 -0
- package/tx/sct/expressions.js +10 -1
- package/tx/sct/load-config.js +107 -0
- package/tx/sct/mmap.js +109 -0
- package/tx/sct/structures.js +175 -15
- package/tx/sct/text-index.js +5 -1
- package/tx/syndication/syndication-feed.js +2 -2
- package/tx/tx-html.js +44 -8
- package/tx/tx.fhir.org.yml +8 -8
- package/tx/tx.js +282 -80
- package/tx/usage-tracker.js +39 -11
- package/tx/vs/vs-api.js +19 -0
- package/tx/vs/vs-database.js +386 -62
- package/tx/vs/vs-package.js +44 -17
- package/tx/vs/vs-vsac.js +649 -101
- package/tx/workers/batch-validate.js +37 -10
- package/tx/workers/batch.js +6 -1
- package/tx/workers/cache-control.js +15 -4
- package/tx/workers/closure.js +156 -13
- package/tx/workers/compare.js +8 -5
- package/tx/workers/expand.js +1173 -289
- package/tx/workers/metadata.js +35 -0
- package/tx/workers/search.js +53 -1
- package/tx/workers/supplement-filters.js +145 -0
- package/tx/workers/translate.js +169 -4
- package/tx/workers/validate.js +223 -119
- package/tx/workers/worker.js +123 -31
- package/tx/xversion/xv-valueset.js +7 -4
- package/utilities/submit-test-report.js +3 -1
- package/utilities/tx-stress-test.js +482 -0
- package/vcl/readme.md +37 -0
- package/vcl/vcl-parser.js +15 -0
- package/xig/readme.md +52 -0
- package/xig/xig-template.html +5 -8
- package/xig/xig.js +238 -422
- package/changelog-template.md +0 -22
- package/registry/test-runner.js +0 -237
- package/release.sh +0 -80
- package/test-scripts/repro-re2-wasm-leak.js +0 -34
- package/tsconfig.eslint.json +0 -9
- package/tx/html/dash-metrics.liquid +0 -147
- package/tx/html/home-metrics.liquid +0 -225
package/ACCESSIBILITY.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Accessibility
|
|
2
|
+
|
|
3
|
+
## Project Information
|
|
4
|
+
|
|
5
|
+
| Field | Value |
|
|
6
|
+
|---|---|
|
|
7
|
+
| Project | FHIRsmith |
|
|
8
|
+
| Owner | Health Intersections |
|
|
9
|
+
| Issue tracker | https://github.com/HealthIntersections/FHIRsmith/issues |
|
|
10
|
+
| Target standard | WCAG 2.0 Level AA (at least) |
|
|
11
|
+
| Conformance status | Target only - not yet evaluated |
|
|
12
|
+
| Last reviewed | 2026-11-05 |
|
|
13
|
+
|
|
14
|
+
## Commitment
|
|
15
|
+
|
|
16
|
+
FHIRsmith aims to be usable by everyone, including people who use assistive
|
|
17
|
+
technologies such as screen readers, screen magnifiers, voice control and
|
|
18
|
+
keyboard-only navigation.
|
|
19
|
+
|
|
20
|
+
We aspire to ensure that the FHIRsmith web UI meets the
|
|
21
|
+
[Web Content Accessibility Guidelines (WCAG) 2.0](https://www.w3.org/TR/WCAG20/)
|
|
22
|
+
at Level AA, at least.
|
|
23
|
+
|
|
24
|
+
Our approach is to make the default HTML accessible wherever we can do so
|
|
25
|
+
without significantly degrading the usual user experience (e.g. semantic
|
|
26
|
+
markup, table headers, form labels, keyboard access, not relying on colour).
|
|
27
|
+
|
|
28
|
+
Where meeting the guidelines would make the usual experience significantly
|
|
29
|
+
worse (e.g. dense tables or interactive tree views), FHIRsmith will provide
|
|
30
|
+
an option (a checkbox in the UI) that switches the generated HTML to a form
|
|
31
|
+
that does meet them. The setting applies to all pages for the remainder of
|
|
32
|
+
the user's session.
|
|
33
|
+
|
|
34
|
+
## Scope
|
|
35
|
+
|
|
36
|
+
### In scope
|
|
37
|
+
|
|
38
|
+
The **web user interface** that FHIRsmith presents in a browser: the home
|
|
39
|
+
page, the module pages (tx, packages, registry, xig, publisher, ips, shl,
|
|
40
|
+
testing etc.), and the HTML renderings of resources.
|
|
41
|
+
|
|
42
|
+
### Not in scope
|
|
43
|
+
|
|
44
|
+
FHIRsmith is primarily a server. Most of what it does is provide FHIR APIs
|
|
45
|
+
(terminology, packages, registry, etc.) that are consumed by other software,
|
|
46
|
+
not by people directly. Those APIs return machine-readable content (JSON, XML,
|
|
47
|
+
npm packages), and their accessibility is the responsibility of the client
|
|
48
|
+
applications that present that content to users.
|
|
49
|
+
|
|
50
|
+
### Third-party content
|
|
51
|
+
|
|
52
|
+
Some HTML content shown by FHIRsmith (e.g. resource narratives) is authored
|
|
53
|
+
by third parties and published in packages. FHIRsmith presents that content
|
|
54
|
+
as provided and cannot guarantee its accessibility. Accessibility problems in
|
|
55
|
+
that content should be reported to the publisher of the package concerned.
|
|
56
|
+
|
|
57
|
+
## Conformance Status
|
|
58
|
+
|
|
59
|
+
**Target only.** Conformance has not yet been formally assessed, and no
|
|
60
|
+
claim of conformance is made.
|
|
61
|
+
|
|
62
|
+
## Known Limitations
|
|
63
|
+
|
|
64
|
+
Pages have not yet been reviewed. Areas that are likely to present
|
|
65
|
+
difficulties include:
|
|
66
|
+
|
|
67
|
+
* Large tables (e.g. value set expansions, package listings, search results)
|
|
68
|
+
* Interactive tree views and editors
|
|
69
|
+
* Colour used to convey status
|
|
70
|
+
|
|
71
|
+
This section will be updated as pages are reviewed.
|
|
72
|
+
|
|
73
|
+
## Checking Pages
|
|
74
|
+
|
|
75
|
+
Setting `accessibility.checkPanel` to `true` in the server's config adds an
|
|
76
|
+
accessibility check panel to the bottom of every page. Pressing its button
|
|
77
|
+
checks the page as it is in the browser at that moment:
|
|
78
|
+
|
|
79
|
+
* Against WCAG 2.0 A and AA, using [axe-core](https://github.com/dequelabs/axe-core)
|
|
80
|
+
* For heading structure (exactly one top level heading, which comes first,
|
|
81
|
+
and no skipped levels), labels that point at nothing, accesskeys,
|
|
82
|
+
placeholder alt text, generic link text, and click handlers on elements
|
|
83
|
+
that can't be reached from the keyboard
|
|
84
|
+
* For links distinguished from the surrounding text by colour alone
|
|
85
|
+
|
|
86
|
+
Problems are reported by HHS Section 508 checklist item, and marked on the
|
|
87
|
+
page. axe-core is only loaded when the button is pressed. Like all automated
|
|
88
|
+
checks, these find only some problems.
|
|
89
|
+
|
|
90
|
+
## Reporting an Accessibility Barrier
|
|
91
|
+
|
|
92
|
+
If you find part of the FHIRsmith web UI that is difficult or impossible to
|
|
93
|
+
use with assistive technology, please
|
|
94
|
+
[create an issue on GitHub](https://github.com/HealthIntersections/FHIRsmith/issues)
|
|
95
|
+
and include "Accessibility" in the title. Helpful details include:
|
|
96
|
+
|
|
97
|
+
* The URL of the page
|
|
98
|
+
* What you were trying to do
|
|
99
|
+
* The browser and assistive technology (and versions) you were using
|
|
100
|
+
* What happened, and what you expected
|
|
101
|
+
|
|
102
|
+
Accessibility issues are treated as bugs, not feature requests.
|
|
103
|
+
|
|
104
|
+
## Contributor Requirements
|
|
105
|
+
|
|
106
|
+
Contributions that improve accessibility are welcome. When contributing
|
|
107
|
+
changes to the web UI, please:
|
|
108
|
+
|
|
109
|
+
* Use semantic HTML (headings, lists, `<table>` with `<th>`/`scope`, `<label>` for form controls)
|
|
110
|
+
* Make sure everything works with the keyboard alone
|
|
111
|
+
* Provide text alternatives for images and icons
|
|
112
|
+
* Don't rely on colour alone to convey meaning
|
|
113
|
+
* If the page has an accessible-mode variant, check that it still works
|
|
114
|
+
|
|
115
|
+
## References
|
|
116
|
+
|
|
117
|
+
* [WCAG 2.0](https://www.w3.org/TR/WCAG20/)
|
|
118
|
+
* [ACCESSIBILITY.md template](https://mgifford.github.io/ACCESSIBILITY.md/)
|
|
119
|
+
* [Open Source Accessibility Framework](https://open-source-accessibility.github.io/Open-Source-Accessibility-Framework/foundational)
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,196 @@ All notable changes to Health Intersections FHIRsmith will be documented in this
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.0] - 2026-11-12
|
|
9
|
+
|
|
10
|
+
**Upgrading**: a big upgrade - every operator should check their settings, though some might not need to change any. See [Upgrading to 1.0.0](CONFIGURATION.md#upgrading-to-100) for what to check.
|
|
11
|
+
|
|
12
|
+
Major new features:
|
|
13
|
+
|
|
14
|
+
- Cluster mode: a master and several tx-workers on one machine and data directory, so terminology can use more than one CPU core (`node cluster.js`, see [cluster.md](cluster.md))
|
|
15
|
+
- Memory protection: a memory guard that refuses new requests (503) when memory is short and aborts running operations near the limit, caps on the caches that could grow without bound, VSAC value sets read on demand, and SNOMED CT caches optionally memory-mapped
|
|
16
|
+
- Lots of performance improvements, particularly in `$expand` paging, `$validate-code` and `$batch-validate-code`
|
|
17
|
+
- Lots of bug fixes around terminology handling (SNOMED CT, LOINC, RxNorm, supplements, filters, limits)
|
|
18
|
+
- Ecosystem resolution: `$validate-code` for code systems this server doesn't have, by asking other servers in the terminology ecosystem
|
|
19
|
+
- Package signatures: npm registry signatures, and publishers' signatures checked by the crawler
|
|
20
|
+
- IPS module
|
|
21
|
+
|
|
22
|
+
### Security
|
|
23
|
+
|
|
24
|
+
- Publisher: the administrator (`admin`) was created with a fixed password, `admin123`, and there is no page to change a password. Its password is now the module's `adminPassword` setting, set at every start; without one, the administrator gets a random password (a UUID), which is logged. An administrator that still has `admin123` is given a random one at startup
|
|
25
|
+
- Parsers have depth and size limits: ECL, VCL, SNOMED CT expressions and UCUM units nested past the limit were a stack overflow (RangeError) rather than a parse error, and an unterminated VCL filter list looped until memory ran out
|
|
26
|
+
- SNOMED CT ECL `wild:` terms are matched with re2js: as native regular expressions, a term with many `*`s took exponential time
|
|
27
|
+
- Token module: the session cookie is `Secure` when the request came over https (`req.secure`, which follows nginx's `X-Forwarded-Proto` with `server.trustProxy`; `cookieSecure` overrides). It depended on `NODE_ENV`, which nothing set, so it was never Secure
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- `config.json` may have comments: `//` to the end of the line, and `/* ... */` (`library/config-reader.js`). A syntax error is reported at its line and column in the file
|
|
32
|
+
- A LICENSE file (BSD 3-Clause); the package's licence is now the SPDX id `BSD-3-Clause` (was `BSD-3`)
|
|
33
|
+
- Memory guard (`server.memory`): above `rejectFraction` (0.85) of the memory limit (the cgroup's - systemd `MemoryMax` - or else the machine's RAM) new requests get 503 + `Retry-After`; above `abortFraction` (0.92) running operations are aborted (422 too-costly). While it refuses, it forces a GC (so start node with `--expose-gc`, as `npm start` and the Docker image now do), and, with `trimAllocator` (on by default only with a role), returns the C allocator's free memory. A client that disconnects, or a memory abort, now stops the whole operation, `$batch` and `$batch-validate-code` included
|
|
34
|
+
- Accessibility: [ACCESSIBILITY.md](ACCESSIBILITY.md) (the target is WCAG 2.0 Level AA, at least), and `accessibility.checkPanel`, a button at the bottom of every page that checks the page against WCAG 2.0 A/AA (axe-core, loaded only when pressed) and its heading structure
|
|
35
|
+
- The largest request body the server accepts is a setting, `server.maxBodyMB` (default 50, as before; about 36x the largest IG Publisher request seen on build.fhir.org, 1.39 MB)
|
|
36
|
+
- Packages
|
|
37
|
+
- `/packages/feed-check` - enter a package feed's URL, and it is read the way the crawler reads it, with nothing stored: for each item, the package (downloaded and checked as the crawler checks it), whether the registry's package-restrictions allow it from that feed, its signature, and whether the registry already holds it (and if it's the same bytes); or what's wrong with the feed itself. The feed's signing keys can be given as JSON (its package-feeds.json `signing` block), to test a feed before its keys are registered. Runs in the background (the report page reloads until done), one at a time, SSRF-guarded, http(s) only (`feedCheck` settings)
|
|
38
|
+
- publishers' signatures. A package can be signed by its publisher (the IG Publisher does, with a key configured in `fhir-settings.json`): a partner file, `{package}.tgz.sigstore.json`, linked from the feed item by `fhir:signature`, checked against the keys `package-feeds.json` lists for the feed (`feeds[].signing`). `crawler.signatures`: `ignore`, `check` (the default: a signature that fails is logged and the package stored anyway, unless the feed says `signing.required`) or `require` (unsigned or badly signed packages are refused, and feeds that list no keys aren't read). Packages already held are checked against their stored bytes when their feed first carries a signature. Each signature file is kept, as checked, in a new `PackageSignatures` table, and the HTML package lists show each version's signature as confirmed, rejected or absent. The package document has `dist.fhir-signature` (`url`, `status`, `keyid`) for each version with a signature, and `/packages/-/fhir/v1/signatures/{name}@{version}` serves the signature as fetched. Not `dist.attestations`, which npm would fail to verify (see the packages readme)
|
|
39
|
+
- npm registry signatures, so `npm audit signatures` can check packages installed from the package server. Each version in a package document now has `dist.integrity` (sha512 SRI) and `dist.signatures` (ECDSA P-256 over `{id}@{version}:{integrity}`, as the npm registry signs), and `/packages/-/npm/v1/keys` lists the key. The signature is the registry's, so every version is signed, wherever it was crawled from. On by default (`npmSigning.enabled`); the key is `npmSigning.keyFile`, default `npm-signing-key.pem` next to the database, made at startup if it isn't there. Versions are signed when stored; existing ones are signed in the background at startup (progress in `/packages/status`), and the keys endpoint is a 404 until they all are. Integrity and signatures are in a new `PackageDist` table. `/packages/npm-check` checks signatures too, when the registry lists keys
|
|
40
|
+
- Terminology Ecosystem
|
|
41
|
+
- OAuth2 client credentials (`oauth`, by server code) for servers that need a token to be scanned (NZHTS); such endpoints are reported as `security: oauth`
|
|
42
|
+
- Terminology
|
|
43
|
+
- A `fhirsmith-cluster` command (`cluster.js`), alongside `fhirsmith` (`server.js`), for a global npm install
|
|
44
|
+
- Cluster: the daily report has a Cluster paragraph: when the launcher started; for each process that exited or was recycled, how many times and why, how long it was unavailable, and the requests lost to it (504, 502, cache-ids that came while it restarted); and the requests the launcher refused itself (503) and the connections it closed for write timeouts. The launcher sends the master what it saw over IPC, and the master keeps it in `stats.db` (`cluster_events`)
|
|
45
|
+
- Cluster: `server.js --role=master|tx-worker` (`--instance-code`, `--port`), and a launcher, `cluster.js`, that starts a master and tx-workers (by default, as many as the CPU cores and the memory limit allow) and sends each request to the right process. The master runs every module; the tx-workers answer terminology requests, opening the master's databases and caches read-only, and never building or downloading them. Settings are in `server.cluster` (see [cluster.md](cluster.md)): with a role a process listens on 127.0.0.1 (`bindAddress`); options `sharedCache` (the `$cache-control` caches in one SQLite file, `databases/resource-cache.db`, at most `sharedCacheMaxMB`, default 256, so any process can answer any cache-id) and `sharedClosure` (every process answers `$closure`), both off by default. The home page and dashboard graph every process, and count every process's requests. With no role, nothing changes
|
|
46
|
+
- SNOMED CT: how each edition's cache file is loaded is set in `modules.tx.snomed`: `load` = `read` (the default, as before) or `mmap` (memory-mapped, so pages are only resident when used and can be dropped under memory pressure; needs the optional dependency koffi, and falls back to read where mapping isn't available), `prewarm` (editions mapped and read through once at startup, in the background) and `read` (editions always read into memory). The memory guard leaves out the file-backed pages of mapped editions
|
|
47
|
+
- LOINC: the `answer-list` filter (`answer-list = [LA code]`: the answer lists the answer is in), as agreed with LOINC (UP-645) and tested by the tx-ecosystem `loinc-*-filter-answer-list` tests. `answers-for` is the defined filter for the answers in a list; `LIST` is still accepted, for backwards compatibility only
|
|
48
|
+
- Extension-codes supplements: a code in a code space the code system reserves for users (`codesystem-reservedCodeSpace`, a regular expression) is a valid code, whether or not anything defines it; with nothing defining it, its display is the code space's `default-display`, or 'User-assigned', and (as a placeholder) another display only gets an information issue. An `extension-codes` supplement in scope defines such codes, with their displays: a global one as if the code system defined them (a different display is an error, or a warning with lenient display validation), one that is referenced as a display that isn't the only correct one (a different display is a warning). Codes from supplements are only in an expansion when the value set lists them. ISO 3166's user-assigned codes work the same way: an extension-codes supplement's display replaces the 'User-assigned' placeholder. So do ICF's qualified (post-coordinated) codes and SNOMED CT post-coordinated expressions (matched as written, or in their minimal rendering): an extension-codes supplement's display is the code's display, and the generated one stays valid
|
|
49
|
+
- Reserved code spaces: an expansion of all of a code system that reserves code spaces (`codesystem-reservedCodeSpace`) is marked unclosed (`valueset-unclosed`, with the reason), since `$validate-code` accepts codes in those spaces that the expansion can't list
|
|
50
|
+
- Terminology: `$cache-control` mode=start also returns the issued cache-id in the `X-Cache-Id` response header (the body is unchanged), so a request log taken at the proxy (nginx `$sent_http_x_cache_id`) can tie each cache-id to the request that created it, for replay
|
|
51
|
+
- Terminology: `$validate-code` (CodeSystem and ValueSet) warns when a coding has no version but its code system says `versionNeeded = true` (`CODESYSTEM_VERSION_NEEDED`); a version pinned by the value set or by the parameters doesn't count, since it's specific to that context and doesn't travel with the coding
|
|
52
|
+
- Ecosystem resolution (`modules.tx.ecosystem`, off by default): for a code system (or version) this server doesn't have, `$validate-code` against a value set asks the ecosystem registry which server has it, and asks that server (a `$lookup` per code, a `$validate-code` per filter). Servers can be excluded, as can code system patterns; API keys and OAuth client credentials can be given per server; requests are SSRF-guarded; answers are cached for an hour. Requests that had to ask another server are counted and graphed separately (External requests). See tx/README.md
|
|
53
|
+
- `$translate`: `targetScope` - with a `targetSystem`, a code system's own targets are held to the value set; without one, the maps into the code systems the value set draws on are used (how a national SNOMED CT edition is translated to the International Edition a value set is built on), and what they give is held to the value set. A source version that is a SNOMED CT edition with no date is the release it resolves to
|
|
54
|
+
- TerminologyCapabilities marks the version of each code system that a request naming no version gets (`isDefault`)
|
|
55
|
+
- Terminology: Resource pages link codes to their code system's browser: SNOMED CT (to the edition, when the browser has it), LOINC (codes, parts and answers), RxNorm (RxNav), the NCI Thesaurus (EVS Explore) and ICD-11 (the WHO browser; a postcoordinated expression links to its stem)
|
|
56
|
+
- SNOMED CT: the `display` `regex` filter (VSAC uses it): every concept with an active description (not only the preferred term) that the pattern matches, whole and case-sensitively, in one language - the request's, else the edition's default. The scan is cached as for ECL
|
|
57
|
+
- SNOMED CT: `concept` filters `descendent-leaf` and `is-not-a`, and `code` is accepted as the same property as `concept` (value sets in production use it)
|
|
58
|
+
- SNOMED CT `$lookup`: `sufficientlyDefined`, `semanticTag` and concrete values (typed by the MRCM range); the module is `moduleId` (`module` still works); the normal forms are returned when asked for by name
|
|
59
|
+
- CodeSystem resources: the `descendent-leaf` filter
|
|
60
|
+
- `modules.tx.pruneTerminologyCache` (default false): once the library and the endpoints' core packages have loaded, what in `terminology-cache` wasn't used is deleted - the `.db` and `.cache` files (with their SQLite side files) and the package folders (old versions, packages no longer in the library, core packages of FHIR versions no endpoint has, failed downloads) - and what was freed is logged. Not other files or folders, not anything changed in the last day, and in a cluster only the master
|
|
61
|
+
- IPS
|
|
62
|
+
- a Structure view (after Narrative) showing a document's logical structure as an expandable tree: what the Composition refers to, then its sections, each with its entries and what else it refers to, and each resource referred to expanding into its own references. A reference that doesn't resolve in the document is `not-in-document`; a resource already shown isn't expanded again
|
|
63
|
+
- a nationality step (the first), which decides the document's nation from what it claims (US and AU so far), shown as a flag on the document page and a Nation column on the project page
|
|
64
|
+
- an analysis step and view - for each resource and section: its extensions (with their definitions, found through the validator), modifier extensions and `implicitRules`, inactive, entered-in-error and unknown statuses, a subject that isn't the document's, dates in the future where one isn't expected, and negation patterns (no known allergies and the like, US Core and IPS styles)
|
|
65
|
+
- the Narrative view switches between the narrative as provided and the one the server generates; the daily report has an IPS section
|
|
66
|
+
- `modules.ips.validator.autoLoadPackages` (default true): the validator finds and loads the package that defines an extension it doesn't have
|
|
67
|
+
- READMEs for the publisher, xig and vcl modules, from their settings
|
|
68
|
+
|
|
69
|
+
### Changed
|
|
70
|
+
|
|
71
|
+
- The server keeps idle connections open for 65 s (`keepAliveTimeout`; `headersTimeout` 66 s), longer than nginx's 60 s upstream keep-alive: Node's 5 s meant nginx sometimes reused a connection as Node closed it, and a POST then got a 502
|
|
72
|
+
- `node server.js` with `--port` or `--instance-code` but no `--role` stops with an error (they were ignored)
|
|
73
|
+
- `NODE_ENV` defaults to `production` when it isn't set (`npm run dev`: `development`), so Express's error pages no longer include stack traces on a server where nobody set it
|
|
74
|
+
- `npm start` (and the Docker image) run `node --expose-gc server.js`, for the memory guard
|
|
75
|
+
- Dependencies: re2js, koffi (optional, for memory-mapped SNOMED CT), fhir-validator-wrapper 1.7.0, mongoose 9.12.1; rimraf is gone
|
|
76
|
+
- Dashboard: the FHIRsmith release and npm audit's result are one line above "More", with low disk space (under 10% free, still also a problem) in red on the same line (on the home page too, which has no npm audit line); the graphs' key is under "More"
|
|
77
|
+
- Status page (`/` and `/dashboard`): one line (requests, uptime, heap, and problems only when there are any), with the detail - a panel of tiles, a memory meter against the memory guard's thresholds, the caches - under "More"; the graphs are one component for every server, with a line per process in a cluster
|
|
78
|
+
- `config-template.json` has every module turned off: set `"enabled": true` on the ones you run
|
|
79
|
+
- The npm package for FHIRsmith leaves out the release scripts and test reports, and what git ignores (npm doesn't read `.gitignore` when there is a `.npmignore`, so a local `npm publish` included, for one, `private/`)
|
|
80
|
+
- Terminology
|
|
81
|
+
- `$expand` limits: the defaults are now 10000 codes returned (`externalLimit`, was 3000) and 100000 built (`internalLimit`, was 10000). The three expansion limits (`externalLimit`, `internalLimit`, `walkLimit`) are checked at startup - whole numbers above 0, external <= internal <= walk - and logged. The tx-ecosystem test runner (User-Agent `Tools/Java`) is no longer held to the old defaults: it gets the same limits as any client, and the tests that depend on a limit set `X-TOO-COSTLY-THRESHOLD`
|
|
82
|
+
- `$expand`: a page (`count` and `offset`) can lie past the expansion limit. Only the page is built; the codes before it are passed keeping only their keys, so `offset + count` is limited by a new, larger `walkLimit` (default 10 x `internalLimit`), and `count` by the expansion limit as before. Over the walk limit: 422 too-costly, `VALUESET_TOO_COSTLY_PAGE`. Value sets with an exclude are paged too (see below); a request with a text filter is as before. Any page, the first too, builds only its own codes: the rest are counted, by key, for the total (reported, as before, only when the whole expansion is within the limit), so a `count=20` first page of a large value set no longer builds codes up to the limit
|
|
83
|
+
- `$expand` of a code system that caps how many of its codes an expansion may list (`expandLimitation`: CPT, 1000, by agreement with the AMA): no page has codes past the cap, but the total is all the codes (as for `count=0`), and the walk stops at the cap. When the cap keeps codes off the page asked for (e.g. `count=1100`), the expansion says why: a new `expansion-issue` extension refers to a contained OperationOutcome with a business-rule warning (`VALUESET_EXPANSION_CODESYSTEM_LIMITED`). (`addExpansionIssue` is the general mechanism, for other things users of an expansion should know)
|
|
84
|
+
- `$expand`: every expansion is held to the limits. A value set with an exclude was exempt from the response limit, and an imported value set's codes didn't count against it, so a value set with an exclude or an import came back whole whatever its size (and an imported value set with an exclude escaped the internal limit). Now they are limited, and paged, like any other: an expansion that used to succeed may now be a 422 too-costly, and should be paged (`count` and `offset`). `count=0` still only counts
|
|
85
|
+
- Global supplements: a code system supplement is used without being referenced when it is a `lang-pack` or `extension-codes` supplement (`codesystem-supplement-type`) marked with `codesystem-supplement-global` = `true`. The `global-lang-pack` code (0.18.1) is gone, and a `lang-pack` supplement that isn't marked global is only used when it is referenced, like any other supplement
|
|
86
|
+
- VSAC value sets are read from the database when asked for, with the recently used ones kept parsed (`vsacCfg.valueSetCacheMB`, default 64), instead of all ~40,000 being held in memory (about 1 GB); a search reads only the value sets on the page returned
|
|
87
|
+
- SNOMED CT: a loaded edition uses its cache file's sections in place, rather than copies, so it takes its file size in memory, not twice that
|
|
88
|
+
- `$expand` builds only what it returns: a page's codes (the codes before it are only walked, the codes after it counted, or totalled by the provider), and only the codes it returns from an imported value set
|
|
89
|
+
- Performance: supplements are indexed by the code system they supplement (scanning every CodeSystem was 18-24% of CPU under IG Publisher traffic); a code system provider is built once per operation, not once per include or code; a code not in an enumerated include is located once; a response is serialised once; each cache-id's resources are hashed once; long operations give other requests a turn every 10 ms of compute (was 25), as do `$batch-validate-code` between entries and the usage tracker's startup scan; `$batch-validate-code` releases each entry as it is answered; RxNorm has an index on `RXNATOMARCHIVE(RXCUI, SAB)`, added to an existing database at load (an unknown code scanned ~372k rows); `$closure` can use a provider's ancestor sets (`ancestorsOf`)
|
|
90
|
+
- `x-too-costly-threshold` can't be more than `internalLimit` (a bigger value is cut down to it), and a value that isn't a positive whole number is ignored: there is always a limit (`0` meant no limit)
|
|
91
|
+
- `$cache-control`: caches are never carried over a restart. With an `instanceCode`, a cache-id carries the process's start, so a request with a cache-id from before a restart is told the cache was lost to a restart (404 `cache-id-unknown`)
|
|
92
|
+
- ValueSet pages list the first 500 codes of an include or exclude, with "Show all" (`_showAll=true`); VSAC's Medication Brand Name was 33,341 rows, each looked up
|
|
93
|
+
- The code system provider API (`tx/cs/cs-api.js`) has optional hooks for providers: `handlesSelecting`/`processSelection` (now called, with the value set as context, and may refuse as too costly), `prefetchCodes`, and `ancestorsOf`
|
|
94
|
+
- LOINC importer: every linguistic variant's language is loaded, and the variants' axis columns become `PartName` designations on the Parts (for some languages, e.g. ko-KR, the only translations LOINC has); relationships the source files repeat are stored once; the import runs as one transaction (much faster)
|
|
95
|
+
- `$expand` `designation` parameter: a bare code (or `|code`) matches a language, or a use code from any system; `system|code` works as before
|
|
96
|
+
- IPS
|
|
97
|
+
- on a document's page the actions (Edit, Share, Process again, Delete) are on their own line below the processing pipeline, and Edit is a button like the others
|
|
98
|
+
- `fhir-validator-wrapper` 1.7.0; the validator is no longer passed `checkIpsCodes` (validator 7.0.4 refuses it, failing every validation)
|
|
99
|
+
|
|
100
|
+
### Fixed
|
|
101
|
+
|
|
102
|
+
- A `config.json` with no `server` block failed at startup; every setting in it has a default
|
|
103
|
+
- The home page's folder module entry wasn't closed (`</li>`)
|
|
104
|
+
- Lint: the errors in the project's own code are fixed, and the vendored jQuery files are no longer linted, so `npm run lint` has no errors
|
|
105
|
+
- The release workflow's test job has the heap size that `ci.yml` gives the tests (the suites outgrow Node's default)
|
|
106
|
+
- Terminology
|
|
107
|
+
- SNOMED CT importer: effectiveTimes were stored a day early when a cache was built in a time zone ahead of UTC (the day count to the 1899 epoch used local dates), so `$lookup` reported e.g. 2002-01-30 for 20020131. Caches built in such a zone need rebuilding
|
|
108
|
+
- `CodeSystem/$batch-validate-code`: a top-level `url` (and `version`) is the code system for every entry, as for `CodeSystem/$validate-code`, and every entry is validated against a code system - an entry with its own `url` was validated as a value set
|
|
109
|
+
- `regex` filters match the whole value, for every code system: the pattern is `^(?:pattern)$`. Code systems from CodeSystem resources, ISO 3166 and filters on supplement properties anchored it as `^pattern$`, so an alternation went wrong (`code2|code3` matched every code starting with code2); LOINC matched anywhere in the value (a CLASS regex of `CYTO` matched PANEL.HL7.CYTOGEN). LOINC regex filters may now select fewer codes
|
|
110
|
+
- ValueSet `$validate-code` with `activeOnly=true`: an inactive code was only rejected when the value set includes all of its code system; when the value set lists the code, or selects it with a filter, it was valid (with an inactive warning). It is now not in the value set whatever the include, for every code system
|
|
111
|
+
- Only one version of a supplement applies: when the server has one version and the request passes another in `tx-resource` (or a value set names one and the request another), the version the request asks for by version (`useSupplement url|version`, or the value set's `valueset-supplement`) is used - else the request's own, else the latest - and `used-supplement` reports that exact version. Both were applied before, and the server's version was the one reported (and whose displays won)
|
|
112
|
+
- ValueSet `$validate-code`: a code found through a value set filter didn't report a status the code has while active (`deprecated`: no `status` parameter and no `DEPRECATED_CONCEPT_FOUND` warning), as it does when the value set lists the code
|
|
113
|
+
- `$expand` text filters (`filter`) now search one language: the first language the request asks for (`displayLanguage`, else `Accept-Language`) that the code system has text in, else the code system's own language - the same rule as the SNOMED CT display filter. All the displays and designations in that language take part, including those from supplements in use; designations in other languages no longer match. LOINC searches its names in other languages (`filter=Volumen&displayLanguage=de` finds Glucose [Masse/Volumen] in Serum oder Plasma) and supplement designations (Uzbek); in English it still searches the displays. SNOMED CT builds a text index per language on editions with more than one (the first search in a language takes a second or two), and a national edition with no language asked for searches its default language
|
|
114
|
+
- A value set filter on a property that a supplement in use defines (`catalog-group = renal-diabetes` on LOINC or SNOMED CT, with a supplement giving codes a `catalog-group`) failed (500 for LOINC and SNOMED CT, 'not understood' for code systems from CodeSystem resources). Such filters are now evaluated from the supplement, for any code system, in `$expand` (includes and excludes) and `$validate-code`, alone or with the code system's own filters (=, in, regex and exists = true can select codes on their own; not-in and exists = false only narrow other filters). A filter the code system does itself still goes to the code system
|
|
115
|
+
- Supplements pinned to a version of their code system (`supplements` = `http://loinc.org|2.80`) were applied to whichever version a request without a version resolved to (LOINC 2.83), and reported as used; a value set requiring one didn't fail. Which supplements apply is now decided by the version actually used: one pinned to another version is not applied (so requiring it fails with `VALUESET_SUPPLEMENT_MISSING`), and one from the server's own content pinned to that version now applies when the request doesn't name a version
|
|
116
|
+
- `$expand` returned no properties for codes from LOINC, SNOMED CT, RxNorm, UCUM, OMOP, CPT or NDC, whether asked for with `property`, `ValueSet.compose.property` or `*` (`$lookup` returned them): these code systems now report the requested properties on each code, as `$lookup` does, and only compute them when some are asked for. UCUM's `canonical` property is returned when asked for (`$lookup` never returned it). An R4 expansion declared a property with no uri with an empty uri extension. OMOP codes from a filter were JSON numbers in an expansion
|
|
117
|
+
- LOINC: `$lookup` of a claims attachment term (CLASSTYPE 3: 18719-5, 19005-8, 26436-6... - 1,161 of them) failed with a 500 ('valueString must be a string, not a undefined'), and a `CLASSTYPE = 3` filter selected nothing: the importer stores the class type as 'Claims attachment', and the provider only knew 'Claims attachments'. CLASSTYPE is now reported as 3 with the description 'Claims attachment', and the filter selects those terms
|
|
118
|
+
- The too-costly error (`VALUESET_TOO_COSTLY`) for a value set passed in the request with no url named it 'undefined' or '??'; it now names it by its id, or says it's the value set in the request
|
|
119
|
+
- A value set with a modifier extension the server doesn't know (on the value set, its compose, an include or exclude, a filter or a listed concept - e.g. VSAC's `vsacOpModifier` on a filter) was refused with a 500. It's a problem with the value set, not the server: `$expand` and `$validate-code` now refuse it with a 422 (`VALUESET_MODIFIER_EXTENSION_UNKNOWN`, `vs-invalid`) naming the extension. A resource with implicit rules is refused with a 422 too
|
|
120
|
+
- `$expand`: a value set that carries an expansion (one sent as a `tx-resource`, or from a package), whether expanded itself or imported by another, was answered from that expansion, even when it was made from other code system versions than the request asks for (`force-system-version` etc.) or the server has - the opioid-cds IG's value sets got 1,733 codes from stale LOINC 2.82 expansions instead of 2,221 from 2.83. A value set with a compose is now expanded from its compose, as the Java expander does. Its stored expansion is only used when it has no compose, or when a code system the compose includes isn't available here (not loaded, or not the version the include names), so the compose can't be expanded
|
|
121
|
+
- Search (`CodeSystem?`, `ValueSet?`, `ConceptMap?`): the `version` parameter matched any version containing it (`version=0` found 3.0.0). It is now matched version-aware: exactly, or with wildcards for a semver version (`3.0.x`, `3.x.x`), or as a prefix for others (`2024-01` finds 2024-01-15), using the resource's versionAlgorithm when it has one
|
|
122
|
+
- Search: the `url` parameter is a left match (the url starts with the value, case-sensitive) for ValueSets and ConceptMaps. A ValueSet url had to match exactly, and a ConceptMap url matched anywhere in the url
|
|
123
|
+
- VSAC: a value set asked for at a version VSAC doesn't have (`valueSetVersion=19990101`) was answered from its latest version; it is now not found, as a value set from a package is (#94). `VSACValueSetProvider.VERSION_FALLBACK_TO_LATEST` brings the old behaviour back
|
|
124
|
+
- The expansion caches could fill the heap under steady load (a replay of IG Publisher traffic ran a server with node's default heap out of memory in 7 minutes: 1,000-code pages of whole-SNOMED CT expansions, about 0.8 MB each). The memory threshold (`expansionCacheMemoryThreshold`) was only checked every 5 minutes. The caches of all the endpoints now share a budget of codes (`expansionCacheMaxCodes`; default a quarter of the heap limit at 1 KB a code, about a million codes with a 4 GB heap): each expansion is counted as it's cached, and the least recently used ones are dropped, from whichever endpoint, to stay under it. An expansion bigger than the whole budget isn't cached. The caches also drop entries in least recently used order without sorting
|
|
125
|
+
- ValueSet `$validate-code` with `inferSystem`: when the value set includes a code system that isn't known, the request failed with a 500 ('Exception expanding value set in order to infer system') if the value set also imports a value set, and otherwise reported that the system couldn't be inferred. The system can't be inferred when the code might be in a code system that isn't known, so it is now a 422 naming that code system (`UNABLE_TO_INFER_CODESYSTEM_UNKNOWN_SYSTEM`). The same applies to the value sets it imports
|
|
126
|
+
- ValueSet `$validate-code` with `inferSystem`: a value set that imports another value set was expanded to find the code's system, which failed with a 500 when the expansion was too big (an import of all of UCUM: 'too many codes to produce (>10000)'), and cost as much as the value set was big when it wasn't. The system is now found by checking the code against each include, and each include of the value sets it imports, less the value set's excludes, without expanding anything
|
|
127
|
+
- `$expand` of a value set that lists its codes (as VSAC value sets do), when the codes are more than the expansion limit: `count=0` (the total only) was refused as too costly (422), and a full expansion was refused only after the codes up to the limit had been built (displays, properties) - 0.1 to 3 s for some VSAC value sets. With `count=0` the listed codes are now counted without being built, when every include lists its codes, so the total is returned whatever the limit; otherwise the listed codes are counted first, and an expansion they would take over the limit is refused before any is built (a page that fits within the limit is still served)
|
|
128
|
+
- Client side caches (`$cache-control`) had no size limit: only idle ones were dropped, so a cache in use could grow without bound, and 500 caches of a 20,000-concept code system ran the server out of memory. A cache may now hold at most `cacheMaxConcepts` concepts (default 250,000, about 10x the largest IG Publisher session measured on build.fhir.org, 24,048): a request that would take it over is not performed: the cache is dropped and the request gets a 404 with one issue coded `cache-id-unknown` (`CACHE_ID_DISCARDED_TOO_BIG`: the cache grew too large and was dropped; start a new cache), as for any cache that is gone, so the client starts a new cache and sends the request again (a later request with that cache-id gets the same 404, saying why it was dropped). A request whose own resources hold more than `cacheMaxConcepts`, which no cache could take, is refused (422 too-costly) and the cache is kept. An endpoint's caches hold at most `cacheMaxTotalConcepts` between them (default 1,000,000, 4 full caches), and the least recently used caches are dropped to stay under it; a request with a dropped cache-id is told so (404 `cache-id-unknown`). 0 = no limit, for either
|
|
129
|
+
- The usage tracker (behind the `usage-count` expansion property) kept every system and code a `$validate-code` was asked about, forever: validating unique made-up codes grew the heap by 10 MB a second. It now records only valid codes, at most `usageTrackerMaxCodes` of them (default 1,000,000, 2x the about 500,000 a full library's value sets list), none longer than `usageTrackerMaxCodeLength` characters (default 100, 2.5x the longest of those, 40). 0 = no limit
|
|
130
|
+
- The cache of parsed language tags kept every valid tag forever, and private use tags (`en-x-...`) of any length are valid: unique 8 KB tags in requests grew the heap by 46 KB each. It now keeps the `languageTagCacheSize` tags used most recently (default 1,000: at most about 46 MB of 8 KB tags). 0 = no limit
|
|
131
|
+
- The tx module logs its memory limits at startup, on one line (`Limits: operationTimeLimit=20s, cacheTimeout=30min, cacheMaxConcepts=250000, ...`)
|
|
132
|
+
- VSAC: a sync cut short when the server stopped left the cache partly filled until the next scheduled refresh (24 h), since a sync only ran at startup when the cache was empty. At startup the server now carries on with a sync that was interrupted or failed: a sync keeps its work list and how far it has got, and the next one does the value sets that weren't finished first (including one that was part way through its versions, which a fresh listing wouldn't find), then whatever else has changed. A run left marked running is recorded as interrupted. A failed startup sync no longer stops the refresh timer from starting
|
|
133
|
+
- A tx endpoint answered a request body over the size limit with Express's HTML error page; it now answers with a 413 OperationOutcome (too-costly), in the format the client asked for
|
|
134
|
+
- SNOMED CT: inactive relationship rows are ignored (agreed with the SNOMED leads; they are still imported) - property filters, ECL refinements, is-a and subsumption, and `$lookup`'s properties, parents and children used them
|
|
135
|
+
- SNOMED CT: `concept in` a list of plain concepts selected nothing (only reference sets in the list worked); spaces after commas and concepts named twice are handled too
|
|
136
|
+
- SNOMED CT implicit value sets: the generic form (`http://snomed.info/sct?fhir_vs...`) is resolved against the edition the request asks for (`system-version` and the like), checking the named concept or reference set there, not the default edition; an edition or version named in the url still wins
|
|
137
|
+
- SNOMED CT `$lookup` `property=*`: every property but the normal forms (and ancestors and descendants), which have to be asked for by name
|
|
138
|
+
- RxNorm: filters in one include are ANDed (two of the same kind both apply); results and totals count concepts, not atoms; the display is the concept's main atom; `concept in` lists codes, and `code` is accepted as `concept`; relationship filters with `in`; a concept whose display atom is obsolete (`SUPPRESS = O`) is inactive; `$lookup` returns TTY, STY and SAB by default, and RxNorm's own relationships (SAB = RXNORM) with `*` or by name, valued with the bare RXCUI
|
|
139
|
+
- `$expand` of a value set that includes one version of a code system and excludes another whole version of it: an included code is excluded when the excluded version has it (the excluded version's codes aren't listed, since most providers can't list theirs)
|
|
140
|
+
- `$expand` from a code system with no content (`not-present`, e.g. THO's time-period-ranges) is a 422, not a 500
|
|
141
|
+
- A cached expansion made under one request's `x-too-costly-threshold` could be served to a request with another: the limit is now part of the cache key
|
|
142
|
+
- `CodeSystem/$validate-code` never answered (the connection was held until the client gave up) when an issue was raised before validation started, such as an unknown cache-id
|
|
143
|
+
- `$validate-code` of a CodeableConcept: the display reported is the reported coding's own, not one from a coding before it; when the code is in no include, the code systems' reasons (an error from another server, say) are reported
|
|
144
|
+
- An exception in a SQLite callback left the request hanging with no response; now it fails the request, in every module
|
|
145
|
+
- SQLite connections leaked: `CodeSystem/$validate-code` (and batch entries with no value set) never closed its provider, nor did a request that failed `check-system-version`
|
|
146
|
+
- A supplement's designations matched a text filter when they had any of its words; they need every word, as the code system's own do
|
|
147
|
+
- LOINC importer: each externally defined answer list (65 in 2.83) added an Answer with a blank code, shared by all of them
|
|
148
|
+
- VSAC: a failed sync stopped the refresh timer for good (it now retries after 1, 5 and 30 minutes); after a restart the next sync is due an interval after the last one, not after the restart (a server restarted more than daily never synced); a `refreshIntervalHours` too big for a timer ran syncs back to back
|
|
149
|
+
- Packages crawler: mirror files are written to a temporary name and renamed into place (nginx could serve a half-written file), before the registry advertises the package; each package is stored in one transaction
|
|
150
|
+
- Statistics: requests per minute is divided by the real time since the last sample (a late timer showed a higher rate), and only responses the static file handlers send count as static hits
|
|
151
|
+
- Startup: logging starts before `config.json` is read, so a server that fails to start says why in the log as well as on stderr
|
|
152
|
+
|
|
153
|
+
### Tx Conformance Statement
|
|
154
|
+
|
|
155
|
+
FHIRsmith v1.0.0 passed all 5230 HL7 terminology service tests (modes tx.fhir.org+omop+general+snomed+mimetypes+icd-11+icf+closure+loinc, tests v1.11.1-snapshot, runner v7.0.5)
|
|
156
|
+
|
|
157
|
+
## [0.18.2] - 2026-10-29
|
|
158
|
+
|
|
159
|
+
### Added
|
|
160
|
+
|
|
161
|
+
- IPS: national profiles - the profile on Bundle that a national IG defines for an IPS document: its jurisdiction (an ISO 3166 country code, or a UN M49 region), the IG's package id and version, and the profile's url. They are shared across all projects at `/ips/national-profiles`, where everyone can see them and only the administrator adds, edits and deletes them (also as JSON). A new processing step, after validation, validates each document against each national profile - loading the IG's package into the validator, once - and keeps the outcome and whether the document passed (no errors). The document's new National Profiles view lists them, and shows each one's validation as the Validation view does
|
|
162
|
+
- IPS: `modules.ips.validator.jar` - a validator jar of the module's own (a local build of org.hl7.fhir.core, say), used as it is, never downloaded or updated. Don't put such a jar at `bin/validator_cli.jar`: the wrapper takes that jar's version from the `.version` file it wrote when it downloaded the release, so a different jar there is started as if it were that release, and is replaced by the next one
|
|
163
|
+
- Registry: `GET /tx-reg/choose-server` - given the code systems a request involves and the client's default server, which one server to send it to, and a log of why. The same choice the Java tooling makes, for clients that aren't Java
|
|
164
|
+
- SNOMED CT ECL: member filters (`^ refset {{ M ... }}`) - `effectiveTime` (with a time value or set), `active`, `moduleId` (any sub expression) and the reference set's own fields (`targetComponentId = << X`, `mapTarget = "..."`, `mapGroup >= #2`, ...), comma-separated and repeatable. A member filter anywhere other than directly after a memberOf constraint is a syntax error (`INVALID_ECL`)
|
|
165
|
+
- SNOMED CT ECL: concept filters (`{{ C ... }}`) - `active`, `definitionStatus` (primitive/defined), `definitionStatusId`, `moduleId` (a concept, a bracketed concept list or any sub expression) and `effectiveTime`
|
|
166
|
+
- SNOMED CT ECL: description filters (`{{ D ... }}`) - `term` (match and `wild:` search terms, single or a set), `language`, `typeId` and `type` (syn/fsn/def), `dialectId` and `dialect` (en-us, en-gb and the other aliases in the ECL specification) with acceptability sets, `moduleId`, `effectiveTime`, `active` and `id`. The filters in one block apply to the same description; only active descriptions are considered unless `active` is given
|
|
167
|
+
- SNOMED CT ECL: history supplements (`{{ + HISTORY }}`, `HISTORY-MIN` / `-MOD` / `-MAX`, `HISTORY (*)`, `HISTORY (refset expression)`) - the inactive concepts that the selected historical association reference sets associate with the result are added to it
|
|
168
|
+
- SNOMED CT ECL: `AND` and `OR` between refinements - between attributes (`: A = x AND B = y`, `: A = x OR B = y`, with brackets for nesting), within an attribute group (`{ A = x OR B = y }`) and between groups (`{ ... } OR { ... }`). Mixing AND and OR without brackets is rejected, as it is for compound expressions. Previously only `,` (conjunction) was accepted
|
|
169
|
+
- SNOMED CT ECL: concrete values in refinements (`= #500`, `>= #1.5`, `!= #-1`, `= "text"`, `= true`). The importer now reads `sct2_RelationshipConcreteValues_*` into a new cache section (cache version 19, after 18's alternate identifiers); editions imported before this report a concrete-value refinement as unsupported. The test edition cache is now `sct_test_20250909e.cache`
|
|
170
|
+
- SNOMED CT ECL: `!=` in refinements (`: A != X` - at least one A whose value is not in X; `[0..0] A != X` - every A is in X), reversed attributes (`: R A = X` - the concepts that are the value of A on a concept in X), and `,` as a conjunction between expressions as well as between refinements
|
|
171
|
+
- SNOMED CT ECL: comments (`/* ... */`), top and bottom of set (`!!> (...)`, `!!< (...)`), and attribute names that are expressions rather than a single concept (`: << 762705008 = ...`, `: (A OR B) = ...`, `: ^ refset = ...`, `: * = ...`, and the same after `.` in dotted expressions)
|
|
172
|
+
- SNOMED CT ECL: alternate identifiers (`LOINC#14394-1`, `"LOINC#14394-1"`): the scheme alias names an identifier scheme (LOINC = 30051010000102 |LOINC code ID|, as imported from the LOINC Extension) and the code resolves to the concept(s) carrying it, which then take hierarchy operators like any set. An unknown scheme, or a code that no concept carries, is `INVALID_ECL`, as an unknown concept is
|
|
173
|
+
- SNOMED CT ECL: reference set field selection on member-of (`^ [targetComponentId] refset`) is parsed, and reported as `UNSUPPORTED_ECL` (the selected fields, and many of the referenced components, are descriptions) rather than as a syntax error
|
|
174
|
+
|
|
175
|
+
### Changed
|
|
176
|
+
|
|
177
|
+
- IPS: a clinical profile's page is the same for everyone. The required sections and entries are a table of Expectation, Section and Entry (`Code is system|code`, or `Code is in` a value set, with `[m..n]` when the cardinality isn't 1..1). The rules about resources are a table of Expectation, Resource Set (`Conditions where Code is ...`) and Rule, in words for `.exists()`, `.x.exists()` and `.x.y.exists()`, `.all(sameSubject())`, `.conformsTo(url)` and `.category.hasCoding(system, code)`. The administrator adds, edits and deletes the rules on that page one at a time, in a dialog, each saved by itself; the full editor is still there as Edit all
|
|
178
|
+
- IPS: `fhir-validator-wrapper` 1.6.1 (was 1.5.0) - it sends `/loadIG` the package in the request body (1.5.0 sent it in the query, which the validator ignored, so no national profile's package loaded), and starts validator 7.0.4 and later with `-txTests`, which the tx tests need
|
|
179
|
+
- Terminology server: code systems, value sets and concept maps that both hl7.terminology and an endpoint's FHIR core package provide. THO's is still always the default for the url, but the core versions are no longer dropped: a reference that names a core version (`url|5.0.0`) gets it, where THO doesn't have that version. Previously they weren't available at all, so a reference to one failed - including THO's own value sets that pin a core version (`ValueSet/concept-property-type` 6.0.1 includes `concept-property-type` 5.0.0) - and `valueSetVersion=5.0.0` found nothing
|
|
180
|
+
- Registry: the "Register your own server" and "Documentation" links on the main page come from the master registration file (`registration` and `ecosystem-documentation`), so each ecosystem has its own, and a link the file doesn't give is left out. They were hard-coded to the HL7 ecosystem's
|
|
181
|
+
- Tx tests: the `loinc` mode (the LOINC suite) is run
|
|
182
|
+
|
|
183
|
+
### Fixed
|
|
184
|
+
|
|
185
|
+
- VSAC: the cache folder `terminology-cache/vsac` is created if it doesn't exist. Only its parent was being created (the folder name was passed as if it were a file name), so on a fresh install the VSAC database could not be created (`SQLITE_CANTOPEN`) and every VSAC value set failed
|
|
186
|
+
- CodeSystem resources: the `notSelectable` property is recognised as the tx-ecosystem requirements say - by the `concept-properties#notSelectable` uri, whatever local code the code system gives it, and by the code `notSelectable` only where the code system doesn't declare that code with a different uri. A code system that used `notSelectable` for a property of its own had its concepts marked `abstract` in expansions and treated as not selectable (`notSelectable-unprop-all`, `notSelectable-unprop-true`). A filter on that code is still a filter on the code system's own property
|
|
187
|
+
- LOINC: `$lookup` returns `CLASS` as a string, the class name (e.g. `CHEM`), with the Part code for the class as `partCode`; it returned the Part code as a code
|
|
188
|
+
- SNOMED CT ECL: a hierarchy operator on a bracketed expression was silently dropped - `< (A OR B)` returned A and B themselves rather than their descendants. It now applies to every member of the set, as do operators on `*` and on `^ refset`, which were previously rejected as unsupported
|
|
189
|
+
- SNOMED CT ECL: `^` on a reference set whose members are descriptions (a language reference set) returned an empty expansion. It is now reported as `UNSUPPORTED_ECL` when the reference set is named, and skipped when it merely falls inside a computed operand such as `^ *`
|
|
190
|
+
- SNOMED CT ECL: an attribute name with no comparison (`: 363698007` with nothing after it) was accepted by the parser and failed later as unsupported; it is a syntax error
|
|
191
|
+
- SNOMED CT ECL: an evaluation error is now `UNSUPPORTED_ECL` unless it is about a concept that does not exist (`INVALID_ECL`). It was classified by matching the error text, which filed `= true` as a syntax error. A concept id that is not 6-18 digits is rejected by the parser
|
|
192
|
+
- IPS: the pages link the module's browser scripts with their version (`?v=`), so a browser picks up a changed script as soon as the server is restarted with it, instead of using its cached copy for up to an hour
|
|
193
|
+
|
|
194
|
+
### Tx Conformance Statement
|
|
195
|
+
|
|
196
|
+
FHIRsmith v0.18.2 passed all 4565 HL7 terminology service tests (modes tx.fhir.org+omop+general+snomed+mimetypes+icd-11+icf+closure+loinc, tests v1.10.1-snapshot, runner v7.0.3)
|
|
197
|
+
|
|
8
198
|
## [0.18.1] - 2026-10-25
|
|
9
199
|
|
|
10
200
|
|
|
@@ -79,12 +269,6 @@ Note: We are heading towards a 1.0.0 release.
|
|
|
79
269
|
- `$lookup`: a warning when the `displayLanguage` asked for has no display for the code, and the display is in the default language (`LOOKUP_DISPLAY_LANGUAGE_FALLBACK`)
|
|
80
270
|
- ICD-11: the `classificationCode` extension (provisional url `http://id.who.int/icd/schema/extension/classificationCode`) on a Coding carries the short code for an entity id, saving a lookup. When present it is checked in `$validate-code` (CodeSystem and ValueSet): a wrong value, or the extension on a code with no short code, on a short code, or on a system other than MMS, is an error
|
|
81
271
|
- ICD-11 `$lookup`: postcoordination is reported as one `postcoordination` property per axis (subproperties `axis` and `value` Codings, a `/` member's value with its own nested postcoordination), replacing the WHO `postcoordinationScale` shape. With the new parameter `post-coordination-metadata=true` every declared axis is listed with `required`, `allowMultipleValues` and its value set; otherwise only the valued axes. `source` (the Foundation entity) is a Coding, and asking for `source.details` adds what a `$lookup` on it would return, designations included. A single-valued axis with several values gets a message in `$validate-code`
|
|
82
|
-
- SNOMED CT ECL: member filters (`^ refset {{ M ... }}`) - `effectiveTime` (with a time value or set), `active`, `moduleId` (any sub expression) and the reference set's own fields (`targetComponentId = << X`, `mapTarget = "..."`, `mapGroup >= #2`, ...), comma-separated and repeatable. A member filter anywhere other than directly after a memberOf constraint is a syntax error (`INVALID_ECL`). Description filters (`{{ D }}`), concept filters (`{{ C }}`) and history supplements (`{{ + }}`) are now parsed and rejected as `UNSUPPORTED_ECL` rather than failing as a lexer error
|
|
83
|
-
- SNOMED CT ECL: concept filters (`{{ C ... }}`) - `active`, `definitionStatus` (primitive/defined), `definitionStatusId`, `moduleId` (a concept, a bracketed concept list or any sub expression) and `effectiveTime`
|
|
84
|
-
- SNOMED CT ECL: `AND` and `OR` between refinements - between attributes (`: A = x AND B = y`, `: A = x OR B = y`, with brackets for nesting), within an attribute group (`{ A = x OR B = y }`) and between groups (`{ ... } OR { ... }`). Mixing AND and OR without brackets is rejected, as for compound expressions. Previously only `,` (conjunction) was accepted
|
|
85
|
-
- SNOMED CT ECL: description filters (`{{ D ... }}`) - `term` (match and `wild:` search terms, single or a set), `language`, `typeId` and `type` (syn/fsn/def), `dialectId` and `dialect` (en-us, en-gb and the other aliases in the ECL specification) with acceptability sets, `moduleId`, `effectiveTime`, `active` and `id`. The filters in one block apply to the same description; only active descriptions are considered unless `active` is given
|
|
86
|
-
- SNOMED CT ECL: comments (`/* ... */`), top and bottom of set (`!!> (...)`, `!!< (...)`), and attribute names that are expressions rather than a single concept (`: << 762705008 = ...`, `: (A OR B) = ...`, `: ^ refset = ...`, `: * = ...`, and the same after `.` in dotted expressions)
|
|
87
|
-
- SNOMED CT ECL: concrete values in refinements (`= #5`, `!= "x"`, `= match:"x"`, `= true`, ...) and alternate identifiers (`"ICD10#A01.1"`, `SCTID#123`) and reference set field selection on member-of (`^ [targetComponentId] refset`) are now parsed, so they are reported as `UNSUPPORTED_ECL` with a message saying what is missing (concrete value relationships / alternate identifier reference sets are not loaded) rather than as a syntax error
|
|
88
272
|
- SNOMED CT: `tx/sct/documentation.md` - what the server supports for SNOMED CT: editions, ECL, expressions and the MRCM, implicit concept maps and translation
|
|
89
273
|
- `utilities/make-sct-test-extension.js`: builds the extension concepts (module TEST1000003) in the SNOMED CT test edition used by the tx tests - one of each shape (fully defined ungrouped, fully defined role-grouped, equivalent to an International concept, primitive), plus inactive ones with and without historical associations
|
|
90
274
|
- README: each module's status - in production; in production but still under development; under development / prototyping; or on the back-burner
|
|
@@ -106,9 +290,6 @@ Note: We are heading towards a 1.0.0 release.
|
|
|
106
290
|
- ValueSet `$validate-code`: an include with more than one filter only checked the first. The other filters' checks return a message when the code fails them, and a message was taken as a pass
|
|
107
291
|
- LOINC: the `exists` filter ignored its value. On a relationship property (`METHOD_TYP`, `COMPONENT`, `document-kind`, ...) the value was looked up as a code or display, so `exists true` and `exists false` both returned every code with the relationship (because a code displayed as "True" and one as "False" exist), and on a property (`CLASSTYPE`, `ORDER_OBS`, ...) `exists false` meant `exists true`. `true` now returns the codes that have the property, `false` every other code in the code system - Parts, AnswerLists and Answers included, so add `CLASSTYPE exists true` to restrict to LOINC terms (e.g. LOINC terms with no method: `METHOD_TYP exists false` + `CLASSTYPE exists true`). Any other value is not supported
|
|
108
292
|
- Packages: a package download or page request was counted twice in the statistics (once by the validation step, once by the handler), and tarball downloads were not told from requests for a version's JSON document; downloads are now counted as `download`, version documents as `version`, each once
|
|
109
|
-
- SNOMED CT ECL: a hierarchy operator on a bracketed expression was silently dropped - `< (A OR B)` returned A and B themselves rather than their descendants. It now applies to every member of the set, as do operators on `*` and on `^ refset`, which were previously rejected as unsupported
|
|
110
|
-
- SNOMED CT ECL: `^` on a reference set whose members are descriptions (a language reference set) returned an empty expansion; it is now reported as `UNSUPPORTED_ECL`
|
|
111
|
-
- SNOMED CT ECL: an evaluation error is now `UNSUPPORTED_ECL` unless it is about a concept that does not exist (`INVALID_ECL`); it was classified by matching the error text, which filed `= true` as a syntax error. A concept id that is not 6-18 digits is rejected by the parser
|
|
112
293
|
- SNOMED CT expressions: the MRCM content type taken as "postcoordinated" was 723594008, which is *all precoordinated content* (postcoordinated is 723595009). So the body-structure part-of attributes (|Regional part of| etc., precoordinated only) were allowed in expressions, |Has ingredient| (postcoordinated only) was not checked, and for the attributes with a precoordinated-specific range that range was used in place of the general one
|
|
113
294
|
|
|
114
295
|
- SHL: every access to a live link (`/shl/access/{uuid}`) hung without a response - the access-logging callback was a plain `function`, so `this.db` was undefined in it and the request was never answered (since 0.3.0). A `GET` on a link is now the U flag (direct file) access the SMART Health Links spec defines: a link with one file returns it as a JWE (`application/jose`), and `recipient` is read from the query and logged (it was always `anonymous`); a link with more than one file, or a VHL, still gets the manifest / VHL bundle. The manifest now has `status` (`finalized`), file `location`s are always https (they followed `req.secure`, so http behind a proxy without trust proxy), and `embedded` is the JWE itself (it was the base64 of it, as stored)
|