adding more services.
|
@ -7,11 +7,24 @@ type SourceType string
|
|||
const (
|
||||
SourceTypeManual SourceType = "manual"
|
||||
|
||||
SourceTypeAetna SourceType = "aetna"
|
||||
SourceTypeAnthem SourceType = "anthem"
|
||||
SourceTypeCigna SourceType = "cigna"
|
||||
SourceTypeHumana SourceType = "humana"
|
||||
SourceTypeKaiser SourceType = "kaiser"
|
||||
SourceTypeUnitedHealthcare SourceType = "unitedhealthcare"
|
||||
SourceTypeLogica SourceType = "logica"
|
||||
SourceTypeAetna SourceType = "aetna"
|
||||
SourceTypeAnthem SourceType = "anthem"
|
||||
SourceTypeCedarSinai SourceType = "cedarssinai"
|
||||
SourceTypeCigna SourceType = "cigna"
|
||||
SourceTypeCommonSpirit SourceType = "commonspirit"
|
||||
SourceTypeDeltaDental SourceType = "deltadental"
|
||||
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"
|
||||
)
|
||||
|
|
|
@ -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"`
|
||||
}
|
|
@ -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})
|
||||
}
|
|
@ -56,6 +56,8 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
|
|||
secure.GET("/resource/fhir/:resourceId", handler.GetResourceFhir)
|
||||
}
|
||||
|
||||
api.GET("/metadata/source", handler.GetMetadataSource)
|
||||
|
||||
if ae.Config.GetString("log.level") == "DEBUG" {
|
||||
//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")
|
||||
|
@ -77,7 +79,7 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
|
|||
//catch-all, serve index page.
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
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"})
|
||||
} else {
|
||||
c.File(fmt.Sprintf("%s/index.html", ae.Config.GetString("web.src.frontend.path")))
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
export class MetadataSource {
|
||||
source_type: string
|
||||
display: string
|
||||
category: string[]
|
||||
enabled: boolean
|
||||
}
|
|
@ -152,7 +152,7 @@
|
|||
class="mr-3"
|
||||
style="width:100px;">
|
||||
<div class="media-body">
|
||||
<h5>{{source.source_type}}</h5>
|
||||
<h5>{{metadataSource[source.source_type]?.display}}</h5>
|
||||
<p>
|
||||
{{getPatientSummary(patientForSource[source.id]?.payload)}}
|
||||
</p>
|
||||
|
@ -160,7 +160,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</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">expires:</small><br/> {{source.expires_at}}</p></td>
|
||||
<td class="align-middle"><p><i class="fas fa-chevron-right"></i></td>
|
||||
|
|
|
@ -6,6 +6,8 @@ import {Router} from '@angular/router';
|
|||
import {Summary} from '../../models/fasten/summary';
|
||||
import {ResourceTypeCounts} from '../../models/fasten/source-summary';
|
||||
import {ResourceFhir} from '../../models/fasten/resource_fhir';
|
||||
import {forkJoin} from 'rxjs';
|
||||
import {MetadataSource} from '../../models/fasten/metadata-source';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
|
@ -19,27 +21,63 @@ export class DashboardComponent implements OnInit {
|
|||
recordsCount: number = 0
|
||||
patientForSource: {[name: string]: ResourceFhir} = {}
|
||||
|
||||
metadataSource: { [name: string]: MetadataSource }
|
||||
|
||||
constructor(private fastenApi: FastenApiService, private router: Router) { }
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
this.fastenApi.getSummary()
|
||||
.subscribe( (summary) => {
|
||||
console.log(summary);
|
||||
this.sources = summary.sources
|
||||
// this.fastenApi.getSummary()
|
||||
// .subscribe( (summary) => {
|
||||
// console.log(summary);
|
||||
// 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
|
||||
summary.resource_type_counts.forEach((resourceTypeInfo) => {
|
||||
this.recordsCount += resourceTypeInfo.count
|
||||
if(resourceTypeInfo.resource_type == "Encounter"){
|
||||
this.encounterCount = resourceTypeInfo.count
|
||||
}
|
||||
})
|
||||
forkJoin([this.fastenApi.getSummary(), this.fastenApi.getMetadataSources()]).subscribe(results => {
|
||||
let summary = results[0] as Summary
|
||||
let metadataSource = results[1] as { [name: string]: MetadataSource }
|
||||
|
||||
summary.patients.forEach((resourceFhir) => {
|
||||
this.patientForSource[resourceFhir.source_id] = resourceFhir
|
||||
})
|
||||
//process
|
||||
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){
|
||||
|
@ -55,6 +93,15 @@ export class DashboardComponent implements OnInit {
|
|||
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 = [{
|
||||
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,
|
||||
|
|
|
@ -18,16 +18,15 @@
|
|||
</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>
|
||||
<p class="mg-b-20">The following medical insurance companies have API's which Fasten can use to retrieve your medical history.
|
||||
Please click the logos below to initiate the connection.</p>
|
||||
<p class="mg-b-20">Fasten also supports manually uploaded electronic medical records (EMR). Click below to upload.</p>
|
||||
|
||||
<div class="row row-sm">
|
||||
<div class="col-lg">
|
||||
<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-label>{{ f.name }} ({{ f.type }})</ngx-dropzone-label>
|
||||
</ngx-dropzone-preview>
|
||||
|
@ -37,8 +36,8 @@
|
|||
|
||||
<hr class="mg-y-30">
|
||||
|
||||
<div class="az-content-label mg-b-5">Insurance Companies</div>
|
||||
<p class="mg-b-20">The following medical insurance companies have API's which Fasten can use to retrieve your medical history.
|
||||
<div class="az-content-label mg-b-5">Healthcare Companies</div>
|
||||
<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>
|
||||
|
||||
<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 class="card h-100 d-flex align-items-center justify-content-center p-3 rounded-0">
|
||||
<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 [style.width]="status[sourceData['source_type']] == 'authorize' ? '33%' : '66%'" class="bg-indigo progress-bar progress-bar-striped progress-bar-animated" role="progressbar"></div>
|
||||
|
|
|
@ -10,6 +10,7 @@ import BrowserAdapter from 'fhirclient/lib/adapters/BrowserAdapter';
|
|||
import {Observable, of, throwError} from 'rxjs';
|
||||
import {concatMap, delay, retryWhen} from 'rxjs/operators';
|
||||
import * as FHIR from "fhirclient"
|
||||
import {MetadataSource} from '../../models/fasten/metadata-source';
|
||||
|
||||
export const retryCount = 24; //wait 2 minutes (5 * 24 = 120)
|
||||
export const retryWaitMilliSeconds = 5000; //wait 5 seconds
|
||||
|
@ -27,15 +28,7 @@ export class MedicalSourcesComponent implements OnInit {
|
|||
) { }
|
||||
status: { [name: string]: string } = {}
|
||||
|
||||
sourceLookup = {
|
||||
"aetna": {"display": "Aetna"},
|
||||
"anthem": {"display": "Anthem"},
|
||||
"cigna": {"display": "Cigna"},
|
||||
"humana": {"display": "Humana"},
|
||||
"kaiser": {"display": "Kaiser"},
|
||||
"unitedhealthcare": {"display": "United Healthcare"},
|
||||
"logica": {"display": "Logica Sandbox"},
|
||||
}
|
||||
metadataSources: {[name:string]: MetadataSource} = {}
|
||||
|
||||
connectedSourceList = []
|
||||
availableSourceList = []
|
||||
|
@ -43,26 +36,32 @@ export class MedicalSourcesComponent implements OnInit {
|
|||
uploadedFile: File[] = []
|
||||
|
||||
ngOnInit(): void {
|
||||
this.fastenApi.getSources()
|
||||
.subscribe((sourceList: Source[]) => {
|
||||
this.fastenApi.getMetadataSources().subscribe((metadataSources: {[name:string]: MetadataSource}) => {
|
||||
this.metadataSources = metadataSources
|
||||
|
||||
for (const sourceType in this.sourceLookup) {
|
||||
let isConnected = false
|
||||
for(const connectedSource of sourceList){
|
||||
if(connectedSource.source_type == sourceType){
|
||||
this.connectedSourceList.push({"source_type": sourceType, "display": this.sourceLookup[sourceType]["display"]})
|
||||
isConnected = true
|
||||
break
|
||||
this.fastenApi.getSources()
|
||||
.subscribe((sourceList: Source[]) => {
|
||||
|
||||
for (const sourceType in this.metadataSources) {
|
||||
let isConnected = false
|
||||
for(const connectedSource of sourceList){
|
||||
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) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import {User} from '../models/fasten/user';
|
|||
import {ResourceFhir} from '../models/fasten/resource_fhir';
|
||||
import {SourceSummary} from '../models/fasten/source-summary';
|
||||
import {Summary} from '../models/fasten/summary';
|
||||
import {MetadataSource} from '../models/fasten/metadata-source';
|
||||
|
||||
@Injectable({
|
||||
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}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 231 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 73 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 11 KiB |