Skip to content

Commit

Permalink
working Aidbox/Health Samurai integration. We can now convert CCDA re…
Browse files Browse the repository at this point in the history
…cords to FHIR.
  • Loading branch information
AnalogJ committed Jan 24, 2024
1 parent 27297f8 commit 0b98758
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 38 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,4 @@ I'd also like to thank the following Corporate Sponsors:

<a href="https://depot.dev/"><img src="https://raw.githubusercontent.com/fastenhealth/docs/main/img/sponsors/depot.png" height="100px" /></a>
<a style="padding-left:5px" href="https://www.macminivault.com/"><img src="https://raw.githubusercontent.com/fastenhealth/docs/main/img/sponsors/macminivault.png" height="100px" /></a>
<a style="padding-left:5px" href="https://www.health-samurai.io/"><img src="https://raw.githubusercontent.com/fastenhealth/docs/main/img/sponsors/health-samurai-logo.png" height="100px" /></a>
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ <h2 class="az-content-title mg-t-40">Medical Record Sources</h2>

<div class="row row-sm">
<div class="col-lg">
<ngx-dropzone [multiple]="false" (change)="uploadSourceBundleHandler($event)" accept=".json,.phr,.ndjson,.jsonl">
<ngx-dropzone-label>Select your EMR/EHR bundle. Must be in JSON format</ngx-dropzone-label>
<ngx-dropzone [multiple]="false" (change)="uploadSourceBundleHandler($event)" accept=".json,.phr,.ndjson,.jsonl,.xml,.ccda,.cda">
<ngx-dropzone-label>Select your EMR/EHR bundle. Must be in JSON or XML format</ngx-dropzone-label>
<ngx-dropzone-preview *ngFor="let f of uploadedFile" [removable]="false">
<ngx-dropzone-label>{{ f.name }} ({{ f.type }})</ngx-dropzone-label>
</ngx-dropzone-preview>
Expand Down Expand Up @@ -130,42 +130,41 @@ <h5 class="mb-1">
</div>
</ng-container>
</div>
</div>
</ng-template>


<ng-template #ccdaWarningModalRef let-modal>

<div class="modal-header">
<h6 class="modal-title">Convert your records?</h6>
<button type="button" class="btn close" aria-label="Close" (click)="modal.dismiss('cancel')">
<span aria-hidden="true">×</span>
</button>
</div>

<div class="modal-body">
<h6>Fasten Does not natively support <a href="https://en.wikipedia.org/wiki/Consolidated_Clinical_Document_Architecture" externalLink >CCDA</a> Health Records</h6>

<p>However we can convert it automatically using software generously donated by the team at <a href="https://www.health-samurai.io/" externalLink>Health Samurai</a><br/><br/>
This converter is hosted by <a href="https://www.fastenhealth.com/" externalLink>Fasten Health, Inc.</a> and is subject to our <a href="https://policy.fastenhealth.com/privacy_policy.html" externalLink>Privacy Policy</a>.
Your data will <strong>NEVER</strong> be shared with any third parties.
</p>

<div class="d-flex h-100">
<div class="mx-auto my-auto">
<a href="https://www.health-samurai.io/" externalLink>
<img style="height:50px" src="assets/images/health-samurai-logo.png">
</a>
</div>
</div>

<!-- <ng-container *ngIf="modalSelectedSourceListItem?.metadata?.category?.length > 0 || modalSelectedSourceListItem?.metadata?.patient_access_description">-->
<!-- <hr/>-->
<!-- <ng-container *ngIf="modalSelectedSourceListItem?.metadata?.patient_access_description">-->
<!-- <h6>About this Source</h6>-->
<!-- <p >{{modalSelectedSourceListItem?.metadata?.patient_access_description}}</p>-->
<!-- </ng-container>-->
<!-- <ng-container *ngIf="modalSelectedSourceListItem?.metadata.aliases?.length > 0">-->
<!-- <h6>Aliases</h6>-->
<!-- <ul>-->
<!-- <li *ngFor="let alias of modalSelectedSourceListItem?.metadata?.aliases">{{alias}}</li>-->
<!-- </ul>-->
<!-- </ng-container>-->
<!-- <ng-container *ngIf="modalSelectedSourceListItem?.metadata.platform_type">-->
<!-- <h6>Platform Type</h6>-->
<!-- <p>{{modalSelectedSourceListItem?.metadata.platform_type}}</p>-->
<!-- </ng-container>-->
<!-- <ng-container *ngIf="modalSelectedSourceListItem?.metadata?.category?.length > 0">-->
<!-- <h6>Categories</h6>-->
<!-- <ul>-->
<!-- <li *ngFor="let cat of modalSelectedSourceListItem?.metadata?.category">{{cat | medicalSourcesCategoryLookup}}</li>-->
<!-- </ul>-->
<!-- </ng-container>-->
<!-- </ng-container>-->
</div>
<div class="modal-footer">
<button (click)="modal.close('convert')" type="button" class="btn btn-indigo">Convert</button>
<button (click)="modal.dismiss('cancel')" type="button" class="btn btn-outline-light">Cancel</button>
</div>

<!-- <div class="modal-footer">-->
<!--&lt;!&ndash; <button (click)="sourceSyncHandler(modalSelectedSourceListItem.source)" type="button" class="btn btn-indigo">Sync</button>&ndash;&gt;-->
<!-- &lt;!&ndash; <button (click)="connectHandler($event, modalSelectedSourceListItem.source['source_type'])" type="button" class="btn btn-outline-light">Reconnect</button>&ndash;&gt;-->
<!-- <button type="button" (click)="connectHandler($event, modalSelectedSourceListItem)" class="btn btn-indigo">-->
<!-- <span *ngIf="status[modalSelectedSourceListItem?.metadata?.source_type] == 'authorize'" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>-->
<!-- Connect-->
<!-- </button>-->
<!-- <button (click)="modal.dismiss('Close click')" type="button" class="btn btn-outline-light">Close</button>-->
<!-- </div>-->
</ng-template>


Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Component, EventEmitter, OnInit, Optional, Output} from '@angular/core';
import {Component, EventEmitter, OnInit, Optional, Output, ViewChild} from '@angular/core';
import {LighthouseService} from '../../services/lighthouse.service';
import {FastenApiService} from '../../services/fasten-api.service';
import {LighthouseSourceMetadata} from '../../models/lighthouse/lighthouse-source-metadata';
Expand All @@ -17,6 +17,7 @@ import {MedicalSourcesFilter, MedicalSourcesFilterService} from '../../services/
import {FormControl, FormGroup} from '@angular/forms';
import * as _ from 'lodash';
import {PatientAccessBrand} from '../../models/patient-access-brands';
import {PlatformService} from '../../services/platform.service';

export const sourceConnectWindowTimeout = 24*5000 //wait 2 minutes (5 * 24 = 120)

Expand Down Expand Up @@ -72,9 +73,14 @@ export class MedicalSourcesComponent implements OnInit {
modalSelectedBrandListItem: LighthouseBrandListDisplayItem | PatientAccessBrand = null;
modalCloseResult = '';


// CCDA-FHIR modal
@ViewChild('ccdaWarningModalRef') ccdaWarningModalRef : any;

constructor(
private lighthouseApi: LighthouseService,
private fastenApi: FastenApiService,
private platformApi: PlatformService,
private activatedRoute: ActivatedRoute,
private filterService: MedicalSourcesFilterService,
private modalService: NgbModal,
Expand Down Expand Up @@ -315,10 +321,28 @@ export class MedicalSourcesComponent implements OnInit {
* this function is used to process manually "uploaded" FHIR bundle files, adding them to the database.
* @param event
*/
public uploadSourceBundleHandler(event) {
this.uploadedFile = [event.addedFiles[0]]
public async uploadSourceBundleHandler(event) {

let processingFile = event.addedFiles[0] as File
this.uploadedFile = [processingFile]

if(processingFile.type == "text/xml"){

let shouldConvert = await this.showCcdaWarningModal()
if(shouldConvert){
let convertedFile = await this.platformApi.convertCcdaToFhir(processingFile).toPromise()
console.log("converted file: ", convertedFile.name)
processingFile = convertedFile
} else {
console.log("removing file from list")
this.uploadedFile = []
return
}

}

//TODO: handle manual bundles.
this.fastenApi.createManualSource(event.addedFiles[0]).subscribe(
this.fastenApi.createManualSource(processingFile).subscribe(
(respData) => {
console.log("source manual source create response:", respData)
},
Expand All @@ -329,6 +353,21 @@ export class MedicalSourcesComponent implements OnInit {
)
}

showCcdaWarningModal(): Promise<boolean> {

console.log("SHOWING CCDA Warning MODAL")

return this.modalService.open(this.ccdaWarningModalRef).result.then<boolean>(
(result) => {
//convert button clicked, .close()
return true //convert from CCDA -> FHIR.
}
).catch((reason) => {
// x or cancel button clicked, .dismiss()
return false
})
}



}
16 changes: 16 additions & 0 deletions frontend/src/app/services/platform.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { PlatformService } from './platform.service';

describe('PlatformService', () => {
let service: PlatformService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(PlatformService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
35 changes: 35 additions & 0 deletions frontend/src/app/services/platform.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {Inject, Injectable} from '@angular/core';
import {HTTP_CLIENT_TOKEN} from '../dependency-injection';
import {HttpClient} from '@angular/common/http';
import {Router} from '@angular/router';
import {AuthService} from './auth.service';
import {MedicalSourcesFilter} from './medical-sources-filter.service';
import {Observable} from 'rxjs';
import {LighthouseSourceSearch} from '../models/lighthouse/lighthouse-source-search';
import {environment} from '../../environments/environment';
import {ResponseWrapper} from '../models/response-wrapper';
import {map} from 'rxjs/operators';

@Injectable({
providedIn: 'root'
})
export class PlatformService {

constructor(private _httpClient: HttpClient) {}

public convertCcdaToFhir(ccdaFile: File): Observable<File> {
if(!ccdaFile || ccdaFile.size === 0){
throw new Error("Invalid CCDA file")
}

const endpointUrl = new URL(`https://api.platform.fastenhealth.com/v1/app/ccda-to-fhir`);
return this._httpClient.post<string>(endpointUrl.toString(), ccdaFile, { headers: {'Content-Type': 'application/cda+xml'} })
.pipe(
map((responseJson: string) => {
console.log("Converter RESPONSE", responseJson)
return new File([JSON.stringify(responseJson)], ccdaFile.name + ".converted.json", {type: "application/json", lastModified: ccdaFile.lastModified || Date.now()})
})
);
}

}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 0b98758

Please sign in to comment.