Skip to content
Open
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ To run these demos locally:
`ng serve`

Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.

## Regional specific configuration

Some regions may have specific requirements and additional configuration - this configuration needs to be specified when running `ng serve`

These configuration files are at at `src/environments` and applies to various components within the SCT Demonstrator application.

Run:
`ng serve --configuration=some-region`

Note: Adding new regional config must have an accompanying configuration section for both `build` and `serve` in `angular.json`.

31 changes: 31 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,33 @@
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
},
"australia": {
"baseHref": "/",
"budgets": [
{
"type": "initial",
"maximumWarning": "10mb",
"maximumError": "10mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "500kb",
"maximumError": "999kb"
}
],
"outputHashing": "all",
"optimization": {
"scripts": true,
"styles": true,
"fonts": false
},
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.australia.ts"
}
]
}
},
"defaultConfiguration": "production"
Expand All @@ -101,9 +128,13 @@
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "sct-implementation-demonstrator:build:production"
},
"development": {
"buildTarget": "sct-implementation-demonstrator:build:development"
},
"australia": {
"buildTarget": "sct-implementation-demonstrator:build:australia"
}
},
"defaultConfiguration": "development"
Expand Down
17 changes: 17 additions & 0 deletions docs/assets/language/national-language-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,23 @@
"languageDialects": "sv-X-83461000052100,sv-X-46011000052107,en-X-900000000000509007"
}
]
},
{
"title": "Australian Edition",
"moduleUri": "http://snomed.info/sct/32506021000036107",
"languageCodes": [
"en"
],
"languageRefsets": [
"32570271000036106"
],
"contexts": [
{
"name": "Australian English",
"languageDialects": "en-X-sctlang-32570271-00003610-6"
}

]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,22 @@
<mat-option *ngFor="let option of severityOptions" [value]="option" (click)="reactionSeveritySelected(reaction, option)">{{option.display}}</mat-option>
</mat-select>
</mat-form-field>
<div class="row-centered">
<div class="small-form-field">
<app-autocomplete-binding [binding]="routeBinding" (selectionChange)="reactionRouteSelected(reaction, $event)"></app-autocomplete-binding>

<div *ngIf="showExposureRoute">
<div class="row-centered">
<div class="small-form-field">
<app-autocomplete-binding [binding]="routeBinding" (selectionChange)="reactionRouteSelected(reaction, $event)"></app-autocomplete-binding>
</div>
<button mat-icon-button color="primary" [matMenuTriggerFor]="routePopoverMenu">
<mat-icon>info</mat-icon>
</button>
<mat-menu #routePopoverMenu="matMenu" >
<span mat-menu-item [disableRipple]="true" (click)="$event.stopPropagation()">
<p>Terminology binding:</p>
<pre>{{ routeBinding.ecl }}</pre>
</span>
</mat-menu>
</div>
<button mat-icon-button color="primary" [matMenuTriggerFor]="routePopoverMenu">
<mat-icon>info</mat-icon>
</button>
<mat-menu #routePopoverMenu="matMenu" >
<span mat-menu-item [disableRipple]="true" (click)="$event.stopPropagation()">
<p>Terminology binding:</p>
<pre>{{ routeBinding.ecl }}</pre>
</span>
</mat-menu>
</div>
</div>
<div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Component, EventEmitter, Input, Output, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

import { environment } from '../../../../environments/environment';

@Component({
selector: 'app-allergies-allergy-list-reaction',
templateUrl: './allergies-allergy-list-reaction.component.html',
Expand All @@ -21,14 +23,17 @@ export class AllergiesAllergyListReactionComponent implements ControlValueAccess
// add emitter for new problem
@Output() newManifestation = new EventEmitter<any>();

//config
showExposureRoute = environment.allergyList.enableExposureRoute;

severityOptions = [
{ code: 'mild', display: 'Mild', sctCode: '255604002', sctDisplay: 'Mild (qualifier value)' },
{ code: 'moderate', display: 'Moderate', sctCode: '6736007', sctDisplay: 'Moderate (qualifier value)' },
{ code: 'severe', display: 'Severe', sctCode: '24484000', sctDisplay: 'Severe (qualifier value)' }
];
selectedSeverity: any = {};
reactionManifestationBinding = { ecl: '<<404684003 |Clinical finding|', title: 'Reaction Manifestation' };
routeBinding = { ecl: '<<284009009 |Route of administration value|', title: 'Exposure Route' };
reactionManifestationBinding = environment.allergyList.reactionManifestationBinding;
routeBinding = environment.allergyList.routeBinding;

reaction: any = {};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,16 @@ <h4>Allergy list</h4>
</mat-menu>
</h4>
<button mat-flat-button color="accent" (click)="clear()" id="clear">Clear</button>

<div *ngIf="showPatient">
<mat-form-field class="form-field">
<mat-label>Patient Reference</mat-label>
<input matInput [(ngModel)]="patientReference" placeholder="Patient/example" (blur)="updateAllergyStr()">
</mat-form-field>
</div>

<div class="row-centered">
<div class="smallest-form-field">
<div [ngClass]="showPropensity ? 'smallest-form-field' : 'small-form-field'">
<app-autocomplete-binding [binding]="codeBinding" (selectionChange)="codeSelected($event)" [term]="selectedCodeTerm"></app-autocomplete-binding>
</div>
<button mat-icon-button color="primary" [matMenuTriggerFor]="codePopoverMenu">
Expand All @@ -49,7 +57,7 @@ <h4>Allergy list</h4>
<pre>{{ codeBinding.ecl }}</pre>
</span>
</mat-menu>
<mat-slide-toggle [(ngModel)]="recordPropensity" (click)="propensityRecordChanged()" matTooltip="Defines wether to use a propensity based model or a substance based model. Defines which one is recorded in the code element of the AllergyIntolerance resource.">
<mat-slide-toggle *ngIf="showPropensity" [(ngModel)]="recordPropensity" (click)="propensityRecordChanged()" matTooltip="Defines wether to use a propensity based model or a substance based model. Defines which one is recorded in the code element of the AllergyIntolerance resource.">
Propensity based model
</mat-slide-toggle>
</div>
Expand Down Expand Up @@ -106,6 +114,14 @@ <h4>Allergy list</h4>
</mat-form-field>
</div>
<app-allergies-allergy-list-reaction [(ngModel)]="selectedReactions" (ngModelChange)="onReactionsChange($event)" (newManifestation)="newProblem.emit($event)"></app-allergies-allergy-list-reaction>

<div *ngIf="showNotes">
<mat-form-field class="form-field">
<mat-label>Notes</mat-label>
<textarea matInput [(ngModel)]="noteText" rows="5" placeholder="Enter any relevant notes here..." (blur)="updateAllergyStr()"></textarea>
</mat-form-field>
</div>

<button mat-flat-button color="accent" (click)="addToProblemsList()">Save</button>
</div>
<div class="column">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { saveAs } from 'file-saver';
import { Clipboard } from '@angular/cdk/clipboard';
import { MatSnackBar } from '@angular/material/snack-bar';
import { SnackAlertComponent } from 'src/app/alerts/snack-alert';

import { environment } from '../../../environments/environment';

@Component({
selector: 'app-allergies-allergy-list',
templateUrl: './allergies-allergy-list.component.html',
Expand All @@ -17,6 +20,11 @@ export class AllergiesAllergyListComponent implements OnInit {

@Output() newProblem = new EventEmitter<any>();

//config
showNotes = environment.allergyList.enableNotes;
showPropensity = environment.allergyList.enablePropensity;
showPatient = environment.allergyList.enablePatient;

clinicalStatusOptions = [
{ system: "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical", code: 'active', display: 'Active' },
{ system: "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical", code: 'inactive', display: 'Inactive' },
Expand Down Expand Up @@ -62,13 +70,13 @@ export class AllergiesAllergyListComponent implements OnInit {
];
selectedSeverity: any = {};

codeBinding = { ecl: '<<418038007 |Propensity to adverse reactions to substance| OR <<420134006 |Propensity to adverse reaction (finding)|', title: 'Allergy/Intolerance by propensity' };
codeBinding = environment.allergyList.codeBinding;
selectedCode: any = null;
selectedCodeTerm = "";
recordPropensity = false;

substanceBinding = { ecl: '<<105590001 | Substance (substance) | OR <<373873005 | Pharmaceutical / biologic product (product) |', title: 'Allergy/Intolerance substance or product' };
refinedSubstanceBinding = { ecl: '<<105590001 | Substance (substance) |', title: 'Allergy/Intolerance substance based on propensity' };
substanceBinding = environment.allergyList.substanceBinding;
refinedSubstanceBinding = environment.allergyList.refinedSubstanceBinding;
selectedSubstanceTerm = "";
selectedSubstance: any = null;

Expand All @@ -81,13 +89,17 @@ export class AllergiesAllergyListComponent implements OnInit {
}
];

reactionManifestationBinding = { ecl: '<<404684003 |Clinical finding|', title: 'Reaction Manifestation' };
selectedReactionManifestation: any = null;
selectedReactionManifestationTerm = "";
routeBinding = { ecl: '<<284009009 |Route of administration value|', title: 'Exposure Route' };

selectedRoute: any = null;
selectedRouteTerm = "";

notes: any[] = [];
noteText: string = "";

patientReference: string = "";

outputAllergyBase: any = {
"resourceType" : "AllergyIntolerance",
"id" : "medication",
Expand Down Expand Up @@ -117,7 +129,7 @@ export class AllergiesAllergyListComponent implements OnInit {
"severity" : ""
}],
"patient" : {
"reference" : "Patient/example"
"reference" : ""
},
"recordedDate" : "2010-03-01",
"participant" : [{
Expand All @@ -131,7 +143,11 @@ export class AllergiesAllergyListComponent implements OnInit {
"actor" : {
"reference" : "Practitioner/example"
}
}]
}],
"note": [{
"text": ""
}
],
}
outputAllergy: any = JSON.parse(JSON.stringify(this.outputAllergyBase));

Expand Down Expand Up @@ -173,6 +189,9 @@ export class AllergiesAllergyListComponent implements OnInit {
route: {}
}
];
this.notes = [];
this.noteText = "";
this.patientReference
this.outputAllergy = JSON.parse(JSON.stringify(this.outputAllergyBase));
this.updateAllergyStr();
setTimeout(() => {
Expand All @@ -190,8 +209,8 @@ export class AllergiesAllergyListComponent implements OnInit {
this.outputAllergy.criticality = (this.selectedCriticality?.code) ? [this.selectedCriticality.code] : {};
this.outputAllergy.reaction = [];
this.selectedReactions.forEach((reaction: any) => {
if (reaction.manifestation.code) { reaction.manifestation.system = 'http://snomed.info/sct'; }
if (reaction.route.code) { reaction.route.system = 'http://snomed.info/sct'; }
if (reaction.manifestation && reaction.manifestation.code) { reaction.manifestation.system = 'http://snomed.info/sct'; }
if (reaction.route && reaction.route.code) { reaction.route.system = 'http://snomed.info/sct'; }
const newReaction = {
substance: [{
coding: [this.selectedSubstance]
Expand All @@ -206,6 +225,15 @@ export class AllergiesAllergyListComponent implements OnInit {
};
this.outputAllergy.reaction.push(newReaction);
});

// Update notes array from noteText
this.notes = this.noteText && this.noteText.trim() !== ''
? [{ text: this.noteText }]
: [];
this.outputAllergy.note = [...this.notes];

this.outputAllergy.patient.reference = this.patientReference.trim() ? this.patientReference : '';

setTimeout(() => {
this.outputAllergyStr = JSON.stringify(this.outputAllergy, null, 2);
}
Expand Down
26 changes: 16 additions & 10 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { LanguageConfigComponent } from './util/language-config/language-config.
import { catchError, of, skip, Subject, switchMap, tap } from 'rxjs';
import { HttpClient } from '@angular/common/http';

import { environment } from '../environments/environment';

declare let gtag: Function;

@Component({
Expand Down Expand Up @@ -41,7 +43,7 @@ export class AppComponent {
{ name: "SNOMED Dev 1", url: "https://dev-browser.ihtsdotools.org/fhir"},
{ name: "Implementation Demo", url: "https://implementation-demo.snomedtools.org/fhir"},
];
selectedServer = this.fhirServers[1];
selectedServer = this.fhirServers[environment.defaultFhirServerIndex];
embeddedMode: boolean = false;
demos: any[] = [];

Expand Down Expand Up @@ -163,18 +165,20 @@ export class AppComponent {
}
});

// Set default server from config
this.setFhirServer(this.fhirServers[environment.defaultFhirServerIndex]);

// now subscribe to changes
this.terminologyService.snowstormFhirBase$.subscribe(url => {
if (this.fhirServers?.length > 0) {
this.fhirServers.forEach(loopServer => {
if (loopServer.url === url) {
this.selectedServer = loopServer;
this.cdRef.detectChanges();
this.updateCodeSystemOptions()
}
});
const found = this.fhirServers.find(server => server.url === url);
if (found) {
this.selectedServer = found;
this.cdRef.detectChanges();
this.updateCodeSystemOptions();
}
}
});
this.setFhirServer(this.selectedServer);

// this.http.get('https://raw.githubusercontent.com/IHTSDO/snomedct-language-metadata/refs/heads/main/national-language-metadata.json').subscribe((data: any) => {
// this.languageMetadata = data;
Expand All @@ -188,7 +192,7 @@ export class AppComponent {

setupLanguageMetadata() {
let editionUri = this.terminologyService.getFhirUrlParam();
// Remve the verssion data from the editionUri (http://snomed.info/sct/900000000000207008/version/20250501 to http://snomed.info/sct/900000000000207008)
// Remove the version data from the editionUri (http://snomed.info/sct/900000000000207008/version/20250501 to http://snomed.info/sct/900000000000207008)
if (editionUri.includes('/version/')) {
const editionUriParts = editionUri.split('/');
editionUriParts.pop();
Expand All @@ -200,6 +204,8 @@ export class AppComponent {
if (!this.filteredLanguageMetadata) {
this.filteredLanguageMetadata = { contexts: [] };
}

//Fallback to US English if not found
this.filteredLanguageMetadata.contexts.push({ name: 'US English', languageDialects: 'en-X-900000000000509007' });
this.setContext(this.filteredLanguageMetadata.contexts[0]);

Expand Down
12 changes: 11 additions & 1 deletion src/app/services/terminology.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,20 @@ export class TerminologyService {
if (this.context) {
return this.context.languageDialects;
} else if (this.languageRefsetConcept) {
return this.lang + '-X-' + this.languageRefsetConcept.code
return this.toLanguageCode(this.lang, this.languageRefsetConcept.code);
} else return this.lang;
}

toLanguageCode(lang: string, langRefset: string) {
if (langRefset.length > 16) {
return `${lang}-x-sctlang-${langRefset.substring(0, 8)}-${langRefset.substring(8, 16)}-${langRefset.substring(16)}`;
} else if (langRefset.length > 8) {
return `${lang}-x-sctlang-${langRefset.substring(0, 8)}-${langRefset.substring(8)}`;
} else {
return `${lang}-x-sctlang-${langRefset}`;
}
}

getCodeSystems() {
let requestUrl = `${this.snowstormFhirBase}/CodeSystem`;
if (this.snowstormFhirBase.includes('ontoserver')) {
Expand Down
Loading