Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to the Health Intersections Node Server will be documented i
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [v0.5.6] - 2026-02-26

### Changed
- Added content to TerminologyCapabilities.codeSystem
- fix LOINC list filter handling
- Improve Diagnostic Logging
- Add icd-9-cm parser

### Tx Conformance Statement

FHIRsmith 0.5.5 passed all 1382 HL7 terminology service tests (modes tx.fhir.org,omop,general,snomed, tests v1.9.0, runner v6.8.1)

## [v0.5.5] - 2026-02-26

### Changed
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fhirsmith",
"version": "0.5.5",
"version": "0.5.6",
"description": "A Node.js server that provides a collection of tools to serve the FHIR ecosystem",
"main": "server.js",
"engines": {
Expand Down
4 changes: 4 additions & 0 deletions tx/cs/cs-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,10 @@ class CodeSystemFactoryProvider {
*/
version() { throw new Error("Must override"); }

content() {
return "complete";
}

getPartialVersion() {
let ver = this.version();
if (ver && VersionUtilities.isSemVer(ver)) {
Expand Down
2 changes: 1 addition & 1 deletion tx/cs/cs-loinc.js
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ class LoincServices extends BaseCSServices {
sql = `SELECT DISTINCT TargetKey as Key FROM Relationships
WHERE RelationshipTypeKey = ${this.relationships.get('Answer')}
AND SourceKey IN (SELECT CodeKey FROM Codes WHERE Code = '${this.#sqlWrapString(value)}')
ORDER BY SourceKey ASC`;
ORDER BY TargetKey ASC`;
lsql = `SELECT COUNT(DISTINCT TargetKey) FROM Relationships
WHERE RelationshipTypeKey = ${this.relationships.get('Answer')}
AND SourceKey IN (SELECT CodeKey FROM Codes WHERE Code = '${this.#sqlWrapString(value)}')
Expand Down
33 changes: 18 additions & 15 deletions tx/library/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,17 @@ class Renderer {
displayCoded(...args) {
if (args.length === 1) {
const arg = args[0];
if (arg.systemUri !== undefined && arg.version !== undefined && arg.code !== undefined && arg.display !== undefined) {
if (arg instanceof CodeSystemProvider) {
return arg.system() + "|" + arg.version();
} else if (arg.system !== undefined && arg.version !== undefined && arg.code !== undefined && arg.display !== undefined) {
// It's a Coding
return this.displayCodedCoding(arg);
} else if (arg.coding !== undefined || arg.text) {
// It's a CodeableConcept
return this.displayCodedCodeableConcept(arg);
} else if (arg.systemUri !== undefined && arg.version !== undefined) {
} else if (arg.system !== undefined && arg.version !== undefined) {
// It's a CodeSystemProvider
return this.displayCodedProvider(arg);
} else if (arg instanceof CodeSystemProvider) {
let cs = arg;
return cs.system() + "|" + cs.version();
}
} else if (args.length === 2) {
return this.displayCodedSystemVersion(args[0], args[1]);
Expand All @@ -46,7 +45,7 @@ class Renderer {
}

displayCodedProvider(system) {
let result = system.systemUri + '|' + system.version;
let result = system.system + '|' + system.version;
if (system.sourcePackage) {
result = result + ' (from ' + system.sourcePackage + ')';
}
Expand All @@ -70,7 +69,7 @@ class Renderer {
}

displayCodedCoding(code) {
return this.displayCodedSystemVersionCodeDisplay(code.systemUri, code.version, code.code, code.display);
return this.displayCodedSystemVersionCodeDisplay(code.system, code.version, code.code, code.display);
}

displayCodedCodeableConcept(code) {
Expand All @@ -90,12 +89,12 @@ class Renderer {

displayValueSetInclude(inc) {
let result;
if (inc.systemUri) {
result = '(' + inc.systemUri + ')';
if (inc.hasConcepts) {
if (inc.system) {
result = '(' + inc.system + ')';
if (inc.concept) {
result = result + '(';
let first = true;
for (const cc of inc.concepts) {
for (const cc of inc.concept) {
if (first) {
first = false;
} else {
Expand All @@ -105,23 +104,23 @@ class Renderer {
}
result = result + ')';
}
if (inc.hasFilters) {
if (inc.filter) {
result = result + '(';
let first = true;
for (const ci of inc.filters) {
for (const ci of inc.filter) {
if (first) {
first = false;
} else {
result = result + ',';
}
result = result + ci.prop + ci.op + ci.value;
result = result + ci.property + ci.op + ci.value;
}
result = result + ')';
}
} else {
result = '(';
let first = true;
for (const s of inc.valueSets || []) {
for (const s of inc.valueSet || []) {
if (first) {
first = false;
} else {
Expand Down Expand Up @@ -1557,6 +1556,10 @@ class Renderer {
// No versions specified
await this.renderLink(li, cs.uri);
}
let content = cs.content || Extensions.readString(cs, "http://hl7.org/fhir/4.0/StructureDefinition/extension-TerminologyCapabilities.codeSystem.content");
if (content && content != "complete") {
li.tx(" (" + content + ")");
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion tx/workers/expand.js
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ class ValueSetExpander {
} else if (cs.contentMode() === 'supplement') {
throw new Issue('error', 'business-rule', null, null, 'The code system definition for ' + cset.system + ' defines a supplement, so this expansion cannot be performed', 'invalid');
} else {
this.addParamUri(exp, cs.contentMode(), cs.system + '|' + cs.version);
this.addParamUri(exp, cs.contentMode(), cs.system() + '|' + cs.version());
Extensions.addString(exp, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed",
"This extension is based on a fragment of the code system " + cset.system);
}
Expand Down
13 changes: 10 additions & 3 deletions tx/workers/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,10 @@ class MetadataHandler {
for (const cs of provider.codeSystems.values()) {
const url = cs.url || (cs.jsonObj && cs.jsonObj.url);
const version = cs.version || (cs.jsonObj && cs.jsonObj.version);
const content = cs.content || cs.jsonObj.content;

if (url) {
this.addCodeSystemEntry(seenSystems, url, version);
this.addCodeSystemEntry(seenSystems, url, version, content);
}
}
}
Expand All @@ -339,7 +340,7 @@ class MetadataHandler {
const version = factory.version();

if (url) {
this.addCodeSystemEntry(seenSystems, url, version);
this.addCodeSystemEntry(seenSystems, url, version, factory.content());
}
}
}
Expand All @@ -357,20 +358,26 @@ class MetadataHandler {
* @param {string} url - Code system URL
* @param {string} version - Code system version (may be null)
*/
addCodeSystemEntry(seenSystems, url, version) {
addCodeSystemEntry(seenSystems, url, version, content) {
if (!seenSystems.has(url)) {
// Create new entry
const entry = { uri: url };
if (version) {
entry.version = [{ code: version }];
}
if (content) {
entry.content = content;
}
seenSystems.set(url, entry);
} else if (version) {
// Add version to existing entry
const entry = seenSystems.get(url);
if (!entry.version) {
entry.version = [];
}
if (content) {
entry.content = content;
}
// Check if version already exists
if (!entry.version.some(v => v.code === version)) {
entry.version.push({ code: version });
Expand Down
7 changes: 4 additions & 3 deletions tx/workers/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class ValueSetChecker {
valueSet;
params;
others = new Map();
indentCount = 0;

constructor(worker, valueSet, params) {
validateParameter(worker, "worker", TerminologyWorker);
Expand Down Expand Up @@ -302,8 +303,8 @@ class ValueSetChecker {
this.seeValueSet();
this.worker.opContext.addNote(this.valueSet, 'Analysing ' + this.valueSet.vurl + ' for validation purposes', this.indentCount);
if (this.indentCount === 0) {
this.worker.opContext.addNote(this.valueSet, 'Parameters: ' + this.params.summary, this.indentCount);
let vrs = this.params.verSummary;
this.worker.opContext.addNote(this.valueSet, 'Parameters: ' + this.params.summary(), this.indentCount);
let vrs = this.params.verSummary();
if (vrs) {
this.worker.opContext.addNote(this.valueSet, 'Version Rules: ' + vrs, this.indentCount);
}
Expand Down Expand Up @@ -1731,7 +1732,7 @@ class ValueSetChecker {
let list = [];
for (let filter of cset.filter) {
let s = cset.filter.length > 1 ? "(" : "";
s = filter.prop+" "+filter.op+" "+filter.value;
s = filter.property+" "+filter.op+" "+filter.value;
s = s + (cset.filter.length > 1 ? ")" : "");
list.push(s)
}
Expand Down
2 changes: 1 addition & 1 deletion tx/workers/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ class TerminologyWorker {
for (const ext of supplementExtensions) {
const supplementUrl = ext.valueString || ext.valueUri;
if (supplementUrl && !cs.hasSupplement(this.opContext, supplementUrl)) {
throw new TerminologyError(`ValueSet depends on supplement '${supplementUrl}' on ${cs.systemUri} that is not known`);
throw new TerminologyError(`ValueSet depends on supplement '${supplementUrl}' on ${cs.system} that is not known`);
}
}
}
Expand Down
22 changes: 21 additions & 1 deletion tx/xversion/xv-terminologyCapabilities.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const {VersionUtilities} = require("../../library/version-utilities");
const {Extensions} = require("../library/extensions");

/**
* Converts input TerminologyCapabilities to R5 format (modifies input object for performance)
Expand All @@ -14,7 +15,17 @@ function terminologyCapabilitiesToR5(jsonObj, sourceVersion) {
}

if (VersionUtilities.isR4Ver(sourceVersion)) {
// R4 to R5: No major structural changes needed for TerminologyCapabilities
for (const cs of jsonObj.codeSystem || []) {
if (cs.content) {
let cnt = Extensions.readString("http://hl7.org/fhir/5.0/StructureDefinition/extension-TerminologyCapabilities.codeSystem.content");
if (cnt) {
delete cs.extensions;
cs.content = cnt;
}
}
}


return jsonObj;
}

Expand Down Expand Up @@ -125,6 +136,15 @@ function terminologyCapabilitiesR5ToR4(r5Obj) {
if (r5Obj.versionAlgorithmCoding) {
delete r5Obj.versionAlgorithmCoding;
}
for (const cs of r5Obj.codeSystem || []) {
if (cs.content) {
if (!cs.extension) {
cs.extension = [];
}
cs.extension.push({"url" : "http://hl7.org/fhir/5.0/StructureDefinition/extension-TerminologyCapabilities.codeSystem.content", valueCode : cs.content});
delete cs.content;
}
}

return r5Obj;
}
Expand Down
Loading
Loading