adding more services.

This commit is contained in:
Jason Kulatunga 2022-09-18 19:46:57 -07:00
parent cc485a0615
commit 080379eabe
24 changed files with 185 additions and 55 deletions

View File

@ -7,11 +7,24 @@ type SourceType string
const ( const (
SourceTypeManual SourceType = "manual" SourceTypeManual SourceType = "manual"
SourceTypeAetna SourceType = "aetna" SourceTypeAetna SourceType = "aetna"
SourceTypeAnthem SourceType = "anthem" SourceTypeAnthem SourceType = "anthem"
SourceTypeCigna SourceType = "cigna" SourceTypeCedarSinai SourceType = "cedarssinai"
SourceTypeHumana SourceType = "humana" SourceTypeCigna SourceType = "cigna"
SourceTypeKaiser SourceType = "kaiser" SourceTypeCommonSpirit SourceType = "commonspirit"
SourceTypeUnitedHealthcare SourceType = "unitedhealthcare" SourceTypeDeltaDental SourceType = "deltadental"
SourceTypeLogica SourceType = "logica" SourceTypeDignityHealth SourceType = "dignityhealth"
SourceTypeHCAHealthcare SourceType = "hcahealthcare"
SourceTypeHumana SourceType = "humana"
SourceTypeKaiser SourceType = "kaiser"
SourceTypeLogica SourceType = "logica"
SourceTypeMetlife SourceType = "metlife"
SourceTypeProvidence SourceType = "providence"
SourceTypeStanford SourceType = "stanford"
SourceTypeSutter SourceType = "sutter"
SourceTypeTrinity SourceType = "trinity"
SourceTypeUCSF SourceType = "ucsf"
SourceTypeUnitedHealthcare SourceType = "unitedhealthcare"
SourceTypeVeteransHealthAdministration SourceType = "va"
SourceTypeVerity SourceType = "verity"
) )

View File

@ -0,0 +1,13 @@
package models
import (
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
)
type MetadataSource struct {
SourceType pkg.SourceType `json:"source_type"`
Display string `json:"display"`
Category []string `json:"category"`
Supported bool `json:"enabled"`
}

View File

@ -0,0 +1,40 @@
package handler
import (
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/gin-gonic/gin"
"net/http"
)
func GetMetadataSource(c *gin.Context) {
metadataSource := map[string]models.MetadataSource{
string(pkg.SourceTypeLogica): {Display: "Logica (Sandbox)", SourceType: pkg.SourceTypeLogica, Category: []string{"Sandbox"}, Supported: true},
// enabled
string(pkg.SourceTypeAetna): {Display: "Aetna", SourceType: pkg.SourceTypeAetna, Category: []string{"Insurance"}, Supported: true},
string(pkg.SourceTypeCigna): {Display: "Cigna", SourceType: pkg.SourceTypeCigna, Category: []string{"Insurance", "Hospital"}, Supported: true},
// pending
string(pkg.SourceTypeAnthem): {Display: "Anthem", SourceType: pkg.SourceTypeAnthem, Category: []string{"Insurance"}},
string(pkg.SourceTypeCedarSinai): {Display: "Cedar Sinai", SourceType: pkg.SourceTypeCedarSinai, Category: []string{"Hospital"}},
string(pkg.SourceTypeCommonSpirit): {Display: "Common Spirit", SourceType: pkg.SourceTypeCommonSpirit, Category: []string{"Hospital"}},
string(pkg.SourceTypeDeltaDental): {Display: "Delta Dental", SourceType: pkg.SourceTypeDeltaDental, Category: []string{"Insurance"}},
string(pkg.SourceTypeDignityHealth): {Display: "Dignity Health", SourceType: pkg.SourceTypeDignityHealth, Category: []string{"Hospital"}},
string(pkg.SourceTypeHCAHealthcare): {Display: "HCA Healthcare", SourceType: pkg.SourceTypeHCAHealthcare, Category: []string{"Insurance"}},
string(pkg.SourceTypeHumana): {Display: "Humana", SourceType: pkg.SourceTypeHumana, Category: []string{"Insurance"}},
string(pkg.SourceTypeKaiser): {Display: "Kaiser", SourceType: pkg.SourceTypeKaiser, Category: []string{"Hospital", "Insurance"}},
string(pkg.SourceTypeMetlife): {Display: "Metlife", SourceType: pkg.SourceTypeMetlife, Category: []string{"Insurance"}},
string(pkg.SourceTypeProvidence): {Display: "Providence", SourceType: pkg.SourceTypeProvidence, Category: []string{"Hospital"}},
string(pkg.SourceTypeStanford): {Display: "Stanford Healthcare", SourceType: pkg.SourceTypeStanford, Category: []string{"Hospital"}},
string(pkg.SourceTypeSutter): {Display: "Sutter", SourceType: pkg.SourceTypeSutter, Category: []string{"Hospital"}},
string(pkg.SourceTypeTrinity): {Display: "Trinity", SourceType: pkg.SourceTypeTrinity, Category: []string{"Hospital"}},
string(pkg.SourceTypeUCSF): {Display: "UCSF", SourceType: pkg.SourceTypeUCSF, Category: []string{"Hospital"}},
string(pkg.SourceTypeUnitedHealthcare): {Display: "United Healthcare", SourceType: pkg.SourceTypeUnitedHealthcare, Category: []string{"Insurance"}},
string(pkg.SourceTypeVeteransHealthAdministration): {Display: "Veterans Health (VA)", SourceType: pkg.SourceTypeVeteransHealthAdministration, Category: []string{"Hospital"}},
string(pkg.SourceTypeVerity): {Display: "Verity", SourceType: pkg.SourceTypeVerity, Category: []string{"Hospital"}},
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": metadataSource})
}

View File

@ -56,6 +56,8 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
secure.GET("/resource/fhir/:resourceId", handler.GetResourceFhir) secure.GET("/resource/fhir/:resourceId", handler.GetResourceFhir)
} }
api.GET("/metadata/source", handler.GetMetadataSource)
if ae.Config.GetString("log.level") == "DEBUG" { if ae.Config.GetString("log.level") == "DEBUG" {
//in debug mode, this endpoint lets us request data directly from the source api //in debug mode, this endpoint lets us request data directly from the source api
ae.Logger.Warningf("***INSECURE*** ***INSECURE*** DEBUG mode enables developer functionality, including unauthenticated raw api requests") ae.Logger.Warningf("***INSECURE*** ***INSECURE*** DEBUG mode enables developer functionality, including unauthenticated raw api requests")
@ -77,7 +79,7 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
//catch-all, serve index page. //catch-all, serve index page.
r.NoRoute(func(c *gin.Context) { r.NoRoute(func(c *gin.Context) {
path := c.Request.URL.Path path := c.Request.URL.Path
if strings.HasPrefix(path, "/api") { if strings.HasPrefix(path, "/api") || strings.HasPrefix(path, "/api") {
c.JSON(http.StatusNotFound, gin.H{"success": false, "error": "404 endpoint not found"}) c.JSON(http.StatusNotFound, gin.H{"success": false, "error": "404 endpoint not found"})
} else { } else {
c.File(fmt.Sprintf("%s/index.html", ae.Config.GetString("web.src.frontend.path"))) c.File(fmt.Sprintf("%s/index.html", ae.Config.GetString("web.src.frontend.path")))

View File

@ -0,0 +1,6 @@
export class MetadataSource {
source_type: string
display: string
category: string[]
enabled: boolean
}

View File

@ -152,7 +152,7 @@
class="mr-3" class="mr-3"
style="width:100px;"> style="width:100px;">
<div class="media-body"> <div class="media-body">
<h5>{{source.source_type}}</h5> <h5>{{metadataSource[source.source_type]?.display}}</h5>
<p> <p>
{{getPatientSummary(patientForSource[source.id]?.payload)}} {{getPatientSummary(patientForSource[source.id]?.payload)}}
</p> </p>
@ -160,7 +160,7 @@
</div> </div>
</div> </div>
</td> </td>
<td class="align-middle"><p><small class="tx-gray-600">status:</small><br/> active</p></td> <td class="align-middle"><p><small class="tx-gray-600">status:</small><br/> {{isActive(source)}}</p></td>
<td class="align-middle"><p><small class="tx-gray-600">last updated:</small><br/> {{source.updated_at | date}}</p></td> <td class="align-middle"><p><small class="tx-gray-600">last updated:</small><br/> {{source.updated_at | date}}</p></td>
<td class="align-middle"><p><small class="tx-gray-600">expires:</small><br/> {{source.expires_at}}</p></td> <td class="align-middle"><p><small class="tx-gray-600">expires:</small><br/> {{source.expires_at}}</p></td>
<td class="align-middle"><p><i class="fas fa-chevron-right"></i></td> <td class="align-middle"><p><i class="fas fa-chevron-right"></i></td>

View File

@ -6,6 +6,8 @@ import {Router} from '@angular/router';
import {Summary} from '../../models/fasten/summary'; import {Summary} from '../../models/fasten/summary';
import {ResourceTypeCounts} from '../../models/fasten/source-summary'; import {ResourceTypeCounts} from '../../models/fasten/source-summary';
import {ResourceFhir} from '../../models/fasten/resource_fhir'; import {ResourceFhir} from '../../models/fasten/resource_fhir';
import {forkJoin} from 'rxjs';
import {MetadataSource} from '../../models/fasten/metadata-source';
@Component({ @Component({
selector: 'app-dashboard', selector: 'app-dashboard',
@ -19,27 +21,63 @@ export class DashboardComponent implements OnInit {
recordsCount: number = 0 recordsCount: number = 0
patientForSource: {[name: string]: ResourceFhir} = {} patientForSource: {[name: string]: ResourceFhir} = {}
metadataSource: { [name: string]: MetadataSource }
constructor(private fastenApi: FastenApiService, private router: Router) { } constructor(private fastenApi: FastenApiService, private router: Router) { }
ngOnInit() { ngOnInit() {
this.fastenApi.getSummary() // this.fastenApi.getSummary()
.subscribe( (summary) => { // .subscribe( (summary) => {
console.log(summary); // console.log(summary);
this.sources = summary.sources // this.sources = summary.sources
//
// //calculate the number of records
// summary.resource_type_counts.forEach((resourceTypeInfo) => {
// this.recordsCount += resourceTypeInfo.count
// if(resourceTypeInfo.resource_type == "Encounter"){
// this.encounterCount = resourceTypeInfo.count
// }
// })
//
// summary.patients.forEach((resourceFhir) => {
// this.patientForSource[resourceFhir.source_id] = resourceFhir
// })
// })
//calculate the number of records forkJoin([this.fastenApi.getSummary(), this.fastenApi.getMetadataSources()]).subscribe(results => {
summary.resource_type_counts.forEach((resourceTypeInfo) => { let summary = results[0] as Summary
this.recordsCount += resourceTypeInfo.count let metadataSource = results[1] as { [name: string]: MetadataSource }
if(resourceTypeInfo.resource_type == "Encounter"){
this.encounterCount = resourceTypeInfo.count
}
})
summary.patients.forEach((resourceFhir) => { //process
this.patientForSource[resourceFhir.source_id] = resourceFhir console.log(summary);
}) this.sources = summary.sources
this.metadataSource = metadataSource
this.metadataSource["manual"] = {
"source_type": "manual",
"display": "Manual",
"category": ["Manual"],
"enabled": true,
}
//calculate the number of records
summary.resource_type_counts.forEach((resourceTypeInfo) => {
this.recordsCount += resourceTypeInfo.count
if(resourceTypeInfo.resource_type == "Encounter"){
this.encounterCount = resourceTypeInfo.count
}
}) })
summary.patients.forEach((resourceFhir) => {
this.patientForSource[resourceFhir.source_id] = resourceFhir
})
});
} }
selectSource(selectedSource: Source){ selectSource(selectedSource: Source){
@ -55,6 +93,15 @@ export class DashboardComponent implements OnInit {
return '' return ''
} }
isActive(source: Source){
if(source.source_type == "manual"){
return '--'
}
let expiresDate = new Date(source.expires_at);
let currentDate = new Date()
return expiresDate < currentDate ? 'active' : 'expired'
}
pageViewChartData = [{ pageViewChartData = [{
label: 'This week', label: 'This week',
data: [36.57, 38.9, 42.3, 41.8, 37.4, 32.5, 28.1, 24.7, 23.4, 20.4, 16.5, 12.1, 9.2, 5.1, 9.6, 10.8, 13.2, 18.2, 13.9, 18.7, 13.7, 11.3, 13.7, 15.8, 12.9, 17.5, 21.9, 18.2, 14.3, 18.2, 14.8, 13.01, 14.5, 15.4, 16.6, 19.4, 14.5, 17.7, 13.8, 9.4, 11.9, 9.7, 6.1, 1.4, 2.3, 2.3, 4.5, 3.7, 5.7, 5.08, 1.9, 8.2, data: [36.57, 38.9, 42.3, 41.8, 37.4, 32.5, 28.1, 24.7, 23.4, 20.4, 16.5, 12.1, 9.2, 5.1, 9.6, 10.8, 13.2, 18.2, 13.9, 18.7, 13.7, 11.3, 13.7, 15.8, 12.9, 17.5, 21.9, 18.2, 14.3, 18.2, 14.8, 13.01, 14.5, 15.4, 16.6, 19.4, 14.5, 17.7, 13.8, 9.4, 11.9, 9.7, 6.1, 1.4, 2.3, 2.3, 4.5, 3.7, 5.7, 5.08, 1.9, 8.2,

View File

@ -18,16 +18,15 @@
</div> </div>
</div> </div>
<h2 class="az-content-title mg-t-40">Medical Sources</h2> <h2 class="az-content-title mg-t-40">Medical Record Sources</h2>
<div class="az-content-label mg-b-5">Manual Upload</div> <div class="az-content-label mg-b-5">Manual Upload</div>
<p class="mg-b-20">The following medical insurance companies have API's which Fasten can use to retrieve your medical history. <p class="mg-b-20">Fasten also supports manually uploaded electronic medical records (EMR). Click below to upload.</p>
Please click the logos below to initiate the connection.</p>
<div class="row row-sm"> <div class="row row-sm">
<div class="col-lg"> <div class="col-lg">
<ngx-dropzone [multiple]="false" (change)="uploadSourceBundle($event)"> <ngx-dropzone [multiple]="false" (change)="uploadSourceBundle($event)">
<ngx-dropzone-label>Drop it, baby!</ngx-dropzone-label> <ngx-dropzone-label>Select your EMR bundle</ngx-dropzone-label>
<ngx-dropzone-preview *ngFor="let f of uploadedFile" [removable]="false"> <ngx-dropzone-preview *ngFor="let f of uploadedFile" [removable]="false">
<ngx-dropzone-label>{{ f.name }} ({{ f.type }})</ngx-dropzone-label> <ngx-dropzone-label>{{ f.name }} ({{ f.type }})</ngx-dropzone-label>
</ngx-dropzone-preview> </ngx-dropzone-preview>
@ -37,8 +36,8 @@
<hr class="mg-y-30"> <hr class="mg-y-30">
<div class="az-content-label mg-b-5">Insurance Companies</div> <div class="az-content-label mg-b-5">Healthcare Companies</div>
<p class="mg-b-20">The following medical insurance companies have API's which Fasten can use to retrieve your medical history. <p class="mg-b-20">The following medical companies have API's which Fasten can use to retrieve your medical history.
Please click the logos below to initiate the connection.</p> Please click the logos below to initiate the connection.</p>
<div class="row row-sm"> <div class="row row-sm">
@ -47,7 +46,7 @@
<div *ngFor="let sourceData of availableSourceList" (click)="connect($event, sourceData['source_type'])" class="col-sm-3 mg-b-20 px-3"> <div *ngFor="let sourceData of availableSourceList" (click)="connect($event, sourceData['source_type'])" class="col-sm-3 mg-b-20 px-3">
<div class="card h-100 d-flex align-items-center justify-content-center p-3 rounded-0"> <div class="card h-100 d-flex align-items-center justify-content-center p-3 rounded-0">
<div class="card-body"> <div class="card-body">
<img [src]="'assets/sources/'+sourceData['source_type']+'.png'" alt="client" class="img-fluid"> <img [src]="'assets/sources/'+sourceData['source_type']+'.png'" [alt]="metadataSources[sourceData['source_type']].display" class="img-fluid">
<div *ngIf="status[sourceData['source_type']]" class="progress"> <div *ngIf="status[sourceData['source_type']]" class="progress">
<div [style.width]="status[sourceData['source_type']] == 'authorize' ? '33%' : '66%'" class="bg-indigo progress-bar progress-bar-striped progress-bar-animated" role="progressbar"></div> <div [style.width]="status[sourceData['source_type']] == 'authorize' ? '33%' : '66%'" class="bg-indigo progress-bar progress-bar-striped progress-bar-animated" role="progressbar"></div>

View File

@ -10,6 +10,7 @@ import BrowserAdapter from 'fhirclient/lib/adapters/BrowserAdapter';
import {Observable, of, throwError} from 'rxjs'; import {Observable, of, throwError} from 'rxjs';
import {concatMap, delay, retryWhen} from 'rxjs/operators'; import {concatMap, delay, retryWhen} from 'rxjs/operators';
import * as FHIR from "fhirclient" import * as FHIR from "fhirclient"
import {MetadataSource} from '../../models/fasten/metadata-source';
export const retryCount = 24; //wait 2 minutes (5 * 24 = 120) export const retryCount = 24; //wait 2 minutes (5 * 24 = 120)
export const retryWaitMilliSeconds = 5000; //wait 5 seconds export const retryWaitMilliSeconds = 5000; //wait 5 seconds
@ -27,15 +28,7 @@ export class MedicalSourcesComponent implements OnInit {
) { } ) { }
status: { [name: string]: string } = {} status: { [name: string]: string } = {}
sourceLookup = { metadataSources: {[name:string]: MetadataSource} = {}
"aetna": {"display": "Aetna"},
"anthem": {"display": "Anthem"},
"cigna": {"display": "Cigna"},
"humana": {"display": "Humana"},
"kaiser": {"display": "Kaiser"},
"unitedhealthcare": {"display": "United Healthcare"},
"logica": {"display": "Logica Sandbox"},
}
connectedSourceList = [] connectedSourceList = []
availableSourceList = [] availableSourceList = []
@ -43,26 +36,32 @@ export class MedicalSourcesComponent implements OnInit {
uploadedFile: File[] = [] uploadedFile: File[] = []
ngOnInit(): void { ngOnInit(): void {
this.fastenApi.getSources() this.fastenApi.getMetadataSources().subscribe((metadataSources: {[name:string]: MetadataSource}) => {
.subscribe((sourceList: Source[]) => { this.metadataSources = metadataSources
for (const sourceType in this.sourceLookup) { this.fastenApi.getSources()
let isConnected = false .subscribe((sourceList: Source[]) => {
for(const connectedSource of sourceList){
if(connectedSource.source_type == sourceType){ for (const sourceType in this.metadataSources) {
this.connectedSourceList.push({"source_type": sourceType, "display": this.sourceLookup[sourceType]["display"]}) let isConnected = false
isConnected = true for(const connectedSource of sourceList){
break if(connectedSource.source_type == sourceType){
this.connectedSourceList.push({"source_type": sourceType, "display": this.metadataSources[sourceType]["display"]})
isConnected = true
break
}
}
if(!isConnected){
//this source has not been found in the connected list, lets add it to the available list.
this.availableSourceList.push({"source_type": sourceType, "display": this.metadataSources[sourceType]["display"]})
} }
} }
if(!isConnected){ })
//this source has not been found in the connected list, lets add it to the available list. })
this.availableSourceList.push({"source_type": sourceType, "display": this.sourceLookup[sourceType]["display"]})
}
}
})
} }
connect($event: MouseEvent, sourceType: string) { connect($event: MouseEvent, sourceType: string) {

View File

@ -11,6 +11,7 @@ import {User} from '../models/fasten/user';
import {ResourceFhir} from '../models/fasten/resource_fhir'; import {ResourceFhir} from '../models/fasten/resource_fhir';
import {SourceSummary} from '../models/fasten/source-summary'; import {SourceSummary} from '../models/fasten/source-summary';
import {Summary} from '../models/fasten/summary'; import {Summary} from '../models/fasten/summary';
import {MetadataSource} from '../models/fasten/metadata-source';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -148,4 +149,14 @@ export class FastenApiService {
}) })
); );
} }
getMetadataSources(): Observable<{[name: string]: MetadataSource}> {
return this._httpClient.get<any>(`${this.getBasePath()}/api/metadata/source`)
.pipe(
map((response: ResponseWrapper) => {
console.log("Metadata RESPONSE", response)
return response.data as {[name: string]: MetadataSource}
})
);
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB