adding support for dropzone

adding support for manual  source
This commit is contained in:
Jason Kulatunga 2022-09-15 22:26:37 -07:00
parent 96a7a394ab
commit 5f71891970
31 changed files with 777 additions and 221 deletions

View File

@ -5,6 +5,8 @@ package pkg
type SourceType string
const (
SourceTypeManual SourceType = "manual"
SourceTypeAetna SourceType = "aetna"
SourceTypeAnthem SourceType = "anthem"
SourceTypeCigna SourceType = "cigna"

View File

@ -9,6 +9,7 @@ import (
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/aetna"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/base"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/cigna"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/manual"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/sirupsen/logrus"
"net/http"
@ -26,6 +27,8 @@ func NewClient(sourceType pkg.SourceType, ctx context.Context, appConfig config.
sourceClient, updatedSource, err = cigna.NewClient(ctx, appConfig, globalLogger, credentials, testHttpClient...)
case pkg.SourceTypeCigna:
sourceClient, updatedSource, err = cigna.NewClient(ctx, appConfig, globalLogger, credentials, testHttpClient...)
case pkg.SourceTypeManual:
sourceClient, updatedSource, err = manual.NewClient(ctx, appConfig, globalLogger, credentials, testHttpClient...)
default:
return nil, updatedSource, errors.New(fmt.Sprintf("Unknown Source Type: %s", sourceType))
}

View File

@ -5,10 +5,13 @@ import (
"encoding/json"
"fmt"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"io"
"net/http"
"os"
"strings"
"time"
)
@ -22,6 +25,10 @@ type BaseClient struct {
Source models.Source
}
func (c *BaseClient) SyncAllBundle(db database.DatabaseRepository, bundleFile *os.File) error {
panic("SyncAllBundle functionality is not available on this client")
}
func NewBaseClient(ctx context.Context, appConfig config.Interface, globalLogger logrus.FieldLogger, source models.Source, testHttpClient ...*http.Client) (*BaseClient, *models.Source, error) {
var httpClient *http.Client
var updatedSource *models.Source
@ -76,6 +83,7 @@ func NewBaseClient(ctx context.Context, appConfig config.Interface, globalLogger
}
httpClient.Timeout = 10 * time.Second
return &BaseClient{
Context: ctx,
AppConfig: appConfig,
@ -101,13 +109,19 @@ func (c *BaseClient) GetRequest(resourceSubpath string, decodeModelPtr interface
return fmt.Errorf("An error occurred during request %s - %d - %s", url, resp.StatusCode, resp.Status)
}
err = json.NewDecoder(resp.Body).Decode(decodeModelPtr)
if err != nil {
return err
}
err = ParseBundle(resp.Body, decodeModelPtr)
return err
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Helper Functions
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func ParseBundle(r io.Reader, decodeModelPtr interface{}) error {
decoder := json.NewDecoder(r)
//decoder.DisallowUnknownFields() //make sure we throw an error if unknown fields are present.
err := decoder.Decode(decodeModelPtr)
if err != nil {
return err
}
return err
}

View File

@ -80,63 +80,3 @@ func (c *FHIR401Client) ProcessBundle(bundle fhir401.Bundle) ([]models.ResourceF
})
return wrappedResourceModels, nil
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Process & Generate API/Database Models
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//func (c *FHIR401Client) ProcessPatient(item fhir401.Patient) (models.Profile, error) {
// c.Logger.Debugf("item %v", item)
// patientProfile := models.Profile{
// OriginBase: models.OriginBase{
// ModelBase: models.ModelBase{},
// UserID: c.Source.UserID,
// SourceID: c.Source.ID,
// SourceResourceID: *item.Id,
// SourceResourceType: fhir401.ResourceTypePatient.Code(),
// },
// Demographics: models.Demographics{
// Address: models.Address{},
// Name: models.Name{},
// },
// }
//
//
//
// if item.Address != nil && len(item.Address) > 0 {
// itemAddress := item.Address[0]
// patientProfile.Demographics.Address.City = itemAddress.City
// patientProfile.Demographics.Address.Country = itemAddress.Country
// patientProfile.Demographics.Address.State = itemAddress.State
// patientProfile.Demographics.Address.Street = itemAddress.Line
// patientProfile.Demographics.Address.Zip = itemAddress.PostalCode
//
// }
// patientProfile.Demographics.Dob = item.BirthDate
//
// if item.Gender != nil {
// itemGenderStr := item.Gender.String()
// itemGenderCode := item.Gender.Code()
// patientProfile.Demographics.Gender = &itemGenderStr
// patientProfile.Demographics.GenderCodes = &itemGenderCode
// }
// patientProfile.Demographics.Language = item.Language
//
// if item.MaritalStatus != nil {
// patientProfile.Demographics.MaritalStatus = item.MaritalStatus.Text
// if len(item.MaritalStatus.Coding) > 0 {
// patientProfile.Demographics.MaritalStatusCodes = item.MaritalStatus.Coding[0].Code
// }
// }
// if item.Name != nil && len(item.Name) > 0 {
// itemName := item.Name[0]
// if itemName.Prefix != nil && len(itemName.Prefix) > 0 {
// itemNamePrefix := itemName.Prefix[0]
// patientProfile.Demographics.Name.Prefix = &itemNamePrefix
// }
// patientProfile.Demographics.Name.Given = itemName.Given
// patientProfile.Demographics.Name.Family = itemName.Family
//
// }
//
// return patientProfile, nil
//}

View File

@ -6,7 +6,10 @@ import (
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/fastenhealth/gofhir-models/fhir430"
fhirutils "github.com/fastenhealth/gofhir-models/fhir430/utils"
"github.com/samber/lo"
"github.com/sirupsen/logrus"
"gorm.io/datatypes"
"net/http"
)
@ -37,5 +40,42 @@ func (c *FHIR430Client) GetPatient(patientId string) (*fhir430.Patient, error) {
patient := fhir430.Patient{}
err := c.GetRequest(fmt.Sprintf("Patient/%s", patientId), &patient)
return &patient, err
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Process Bundles
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func (c *FHIR430Client) ProcessBundle(bundle fhir430.Bundle) ([]models.ResourceFhir, error) {
//process each entry in bundle
wrappedResourceModels := lo.FilterMap[fhir430.BundleEntry, models.ResourceFhir](bundle.Entry, func(bundleEntry fhir430.BundleEntry, _ int) (models.ResourceFhir, bool) {
originalResource, _ := fhirutils.MapToResource(bundleEntry.Resource, false)
resourceType, resourceId := originalResource.(ResourceInterface).ResourceRef()
// TODO find a way to safely/consistently get the resource updated date (and other metadata) which shoudl be added to the model.
//if originalResource.Meta != nil && originalResource.Meta.LastUpdated != nil {
// if parsed, err := time.Parse(time.RFC3339Nano, *originalResource.Meta.LastUpdated); err == nil {
// patientProfile.UpdatedAt = parsed
// }
//}
if resourceId == nil {
//no resourceId present for this resource, we'll ignore it.
return models.ResourceFhir{}, false
}
wrappedResourceModel := models.ResourceFhir{
OriginBase: models.OriginBase{
ModelBase: models.ModelBase{},
UserID: c.Source.UserID,
SourceID: c.Source.ID,
SourceResourceID: *resourceId,
SourceResourceType: resourceType,
},
Payload: datatypes.JSON(bundleEntry.Resource),
}
return wrappedResourceModel, true
})
return wrappedResourceModels, nil
}

View File

@ -1,28 +1,17 @@
package base
import "github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
import (
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
"os"
)
//go:generate mockgen -source=interface.go -destination=mock/mock_client.go
type Client interface {
GetRequest(resourceSubpath string, decodeModelPtr interface{}) error
SyncAll(db database.DatabaseRepository) error
//PatientProfile() (models.PatientProfile, error)
//Allergies()
//Encounters()
//Immunizations()
//Instructions()
//Medications()
//Narratives()
//Organizations()
//PlansOfCare()
//Problems()
//Procedures()
//TestResults()
//Vitals()
//CCD()
//Demographics()
//SocialHistory()
//Manual client ONLY functions
SyncAllBundle(db database.DatabaseRepository, bundleFile *os.File) error
}
type ResourceInterface interface {

View File

@ -0,0 +1,164 @@
package manual
import (
"context"
"fmt"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/base"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/fastenhealth/gofhir-models/fhir401"
fhir401utils "github.com/fastenhealth/gofhir-models/fhir401/utils"
"github.com/fastenhealth/gofhir-models/fhir430"
fhir430utils "github.com/fastenhealth/gofhir-models/fhir430/utils"
"github.com/samber/lo"
"github.com/sirupsen/logrus"
"io"
"net/http"
"os"
"strings"
)
type ManualClient struct {
Context context.Context
AppConfig config.Interface
Logger logrus.FieldLogger
Source *models.Source
}
func (m ManualClient) GetRequest(resourceSubpath string, decodeModelPtr interface{}) error {
panic("implement me")
}
func (m ManualClient) SyncAll(db database.DatabaseRepository) error {
panic("implement me")
}
func (m ManualClient) SyncAllBundle(db database.DatabaseRepository, bundleFile *os.File) error {
// we need to find the (most populated) patient record
patientId, bundleType, err := m.ExtractPatientId(bundleFile)
if err != nil {
return fmt.Errorf("an error occurred while extracting patient id from bundle: %w", err)
}
// we need to add the patient id to the source
m.Source.PatientId = patientId
// we need to upsert Source
err = db.CreateSource(m.Context, m.Source)
if err != nil {
return fmt.Errorf("an error occurred while creating manual source: %w", err)
}
// we need to parse the bundle into resources (might need to try a couple of different times)
var resourceFhirList []models.ResourceFhir
switch bundleType {
case "fhir430":
bundle430Data := fhir430.Bundle{}
err := base.ParseBundle(bundleFile, &bundle430Data)
if err != nil {
return fmt.Errorf("an error occurred while parsing 4.3.0 bundle: %w", err)
}
client, _, err := base.NewFHIR430Client(m.Context, m.AppConfig, m.Logger, *m.Source, http.DefaultClient)
if err != nil {
return fmt.Errorf("an error occurred while creating 4.3.0 client: %w", err)
}
resourceFhirList, err = client.ProcessBundle(bundle430Data)
if err != nil {
return fmt.Errorf("an error occurred while processing 4.3.0 resources: %w", err)
}
case "fhir401":
bundle401Data := fhir401.Bundle{}
err := base.ParseBundle(bundleFile, &bundle401Data)
if err != nil {
return fmt.Errorf("an error occurred while parsing 4.0.1 bundle: %w", err)
}
client, _, err := base.NewFHIR401Client(m.Context, m.AppConfig, m.Logger, *m.Source, http.DefaultClient)
if err != nil {
return fmt.Errorf("an error occurred while creating 4.0.1 client: %w", err)
}
resourceFhirList, err = client.ProcessBundle(bundle401Data)
if err != nil {
return fmt.Errorf("an error occurred while processing 4.0.1 resources: %w", err)
}
}
// we need to upsert all resources (and make sure they are associated with new Source)
for _, apiModel := range resourceFhirList {
err = db.UpsertResource(context.Background(), apiModel)
if err != nil {
return fmt.Errorf("an error occurred while upserting resources: %w", err)
}
}
return nil
}
func (m ManualClient) ExtractPatientId(bundleFile *os.File) (string, string, error) {
// try from newest format to the oldest format
bundle430Data := fhir430.Bundle{}
bundle401Data := fhir401.Bundle{}
var patientIds []string
bundleType := "fhir430"
if err := base.ParseBundle(bundleFile, &bundle430Data); err == nil {
patientIds = lo.FilterMap[fhir430.BundleEntry, string](bundle430Data.Entry, func(bundleEntry fhir430.BundleEntry, _ int) (string, bool) {
parsedResource, err := fhir430utils.MapToResource(bundleEntry.Resource, false)
if err != nil {
return "", false
}
typedResource := parsedResource.(base.ResourceInterface)
resourceType, resourceId := typedResource.ResourceRef()
if resourceId == nil || len(*resourceId) == 0 {
return "", false
}
return *resourceId, resourceType == fhir430.ResourceTypePatient.String()
})
}
bundleFile.Seek(0, io.SeekStart)
//fallback
if patientIds == nil || len(patientIds) == 0 {
bundleType = "fhir401"
//try parsing the bundle as a 401 bundle
//TODO: find a better, more generic way to do this.
err := base.ParseBundle(bundleFile, &bundle401Data)
if err != nil {
return "", "", err
}
patientIds = lo.FilterMap[fhir401.BundleEntry, string](bundle401Data.Entry, func(bundleEntry fhir401.BundleEntry, _ int) (string, bool) {
parsedResource, err := fhir401utils.MapToResource(bundleEntry.Resource, false)
if err != nil {
return "", false
}
typedResource := parsedResource.(base.ResourceInterface)
resourceType, resourceId := typedResource.ResourceRef()
if resourceId == nil || len(*resourceId) == 0 {
return "", false
}
return *resourceId, resourceType == fhir430.ResourceTypePatient.String()
})
}
bundleFile.Seek(0, io.SeekStart)
if patientIds == nil || len(patientIds) == 0 {
return "", "", fmt.Errorf("could not determine patient id")
} else {
//reset reader
return strings.TrimLeft(patientIds[0], "Patient/"), bundleType, nil
}
}
func NewClient(ctx context.Context, appConfig config.Interface, globalLogger logrus.FieldLogger, source models.Source, testHttpClient ...*http.Client) (base.Client, *models.Source, error) {
return ManualClient{
Context: ctx,
AppConfig: appConfig,
Logger: globalLogger,
Source: &models.Source{
SourceType: pkg.SourceTypeManual,
},
}, nil, nil
}

View File

@ -1,12 +1,14 @@
package handler
import (
"fmt"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"io/ioutil"
"net/http"
"strings"
)
@ -57,6 +59,52 @@ func CreateSource(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true, "data": sourceCred})
}
func CreateManualSource(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
// single file
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "could not extract file from form"})
return
}
fmt.Printf("Uploaded filename: %s", file.Filename)
// create a temporary file to store this uploaded file
bundleFile, err := ioutil.TempFile("", file.Filename)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "could not create temp file"})
return
}
// Upload the file to specific bundleFile.
err = c.SaveUploadedFile(file, bundleFile.Name())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "could not save temp file"})
return
}
// We cannot save the "Source" object yet, as we do not know the patientID
// create a "manual" client, which we can use to parse the
manualSourceClient, _, err := hub.NewClient(pkg.SourceTypeManual, c, nil, logger, models.Source{})
if err != nil {
logger.Errorln("An error occurred while initializing hub client using manual source without credentials", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
return
}
err = manualSourceClient.SyncAllBundle(databaseRepo, bundleFile)
if err != nil {
logger.Errorln("An error occurred while processing bundle", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": fmt.Sprintf("'%s' uploaded!", file.Filename)})
}
func GetSource(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)

View File

@ -46,6 +46,7 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
secure := api.Group("/secure").Use(middleware.RequireAuth())
{
secure.POST("/source", handler.CreateSource)
secure.POST("/source/manual", handler.CreateManualSource)
secure.GET("/source", handler.ListSource)
secure.GET("/source/:sourceId", handler.GetSource)
//in debug mode, this endpoint lets us request data directly from the source api

View File

@ -24,10 +24,14 @@
"@fortawesome/free-solid-svg-icons": "^6.2.0",
"@ng-bootstrap/ng-bootstrap": "10.0.0",
"@panva/oauth4webapi": "^1.1.3",
"angular-datatables": "^14.0.0",
"bootstrap": "^4.4.1",
"chart.js": "2.9.4",
"fhirclient": "^2.5.1",
"humanize-duration": "^3.27.3",
"moment": "^2.29.4",
"ng2-charts": "^2.3.0",
"ngx-dropzone": "^3.1.0",
"rxjs": "~6.5.4",
"tslib": "^2.0.0",
"zone.js": "~0.11.8"
@ -3946,6 +3950,87 @@
"ajv": "^6.9.1"
}
},
"node_modules/angular-datatables": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/angular-datatables/-/angular-datatables-14.0.0.tgz",
"integrity": "sha512-IdJdS/IGAFKcWKCM3PrgHt8YIyCGxUKaPQGQom+5YvAcIMKuRwgB7aYT2H3+zUsJBNms2Yaeh95ABNT+yqOwLQ==",
"dependencies": {
"codelyzer": "^6.0.2"
},
"engines": {
"node": ">=8.9.0"
}
},
"node_modules/angular-datatables/node_modules/@angular/compiler": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz",
"integrity": "sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ==",
"peerDependencies": {
"tslib": "^1.10.0"
}
},
"node_modules/angular-datatables/node_modules/@angular/core": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz",
"integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==",
"peerDependencies": {
"rxjs": "^6.5.3",
"tslib": "^1.10.0",
"zone.js": "~0.10.2"
}
},
"node_modules/angular-datatables/node_modules/app-root-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz",
"integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==",
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/angular-datatables/node_modules/codelyzer": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-6.0.2.tgz",
"integrity": "sha512-v3+E0Ucu2xWJMOJ2fA/q9pDT/hlxHftHGPUay1/1cTgyPV5JTHFdO9hqo837Sx2s9vKBMTt5gO+lhF95PO6J+g==",
"dependencies": {
"@angular/compiler": "9.0.0",
"@angular/core": "9.0.0",
"app-root-path": "^3.0.0",
"aria-query": "^3.0.0",
"axobject-query": "2.0.2",
"css-selector-tokenizer": "^0.7.1",
"cssauron": "^1.4.0",
"damerau-levenshtein": "^1.0.4",
"rxjs": "^6.5.3",
"semver-dsl": "^1.0.1",
"source-map": "^0.5.7",
"sprintf-js": "^1.1.2",
"tslib": "^1.10.0",
"zone.js": "~0.10.3"
},
"peerDependencies": {
"@angular/compiler": ">=2.3.1 <13.0.0 || ^12.0.0-next || ^12.1.0-next || ^12.2.0-next",
"@angular/core": ">=2.3.1 <13.0.0 || ^12.0.0-next || ^12.1.0-next || ^12.2.0-next",
"tslint": "^5.0.0 || ^6.0.0"
}
},
"node_modules/angular-datatables/node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/angular-datatables/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/angular-datatables/node_modules/zone.js": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.10.3.tgz",
"integrity": "sha512-LXVLVEq0NNOqK/fLJo3d0kfzd4sxwn2/h67/02pjCjfKDxgx1i9QqpvtHD8CrBnSSwMw5+dy11O7FRX5mkO7Cg=="
},
"node_modules/ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
@ -4081,7 +4166,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz",
"integrity": "sha512-majUxHgLehQTeSA+hClx+DY09OVUqG3GtezWkF1krgLGNdlDu9l9V8DaqNMWbq4Eddc8wsyDA0hpDUtnYxQEXw==",
"dev": true,
"dependencies": {
"ast-types-flow": "0.0.7",
"commander": "^2.11.0"
@ -4150,8 +4234,7 @@
"node_modules/ast-types-flow": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
"integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==",
"dev": true
"integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag=="
},
"node_modules/asynckit": {
"version": "0.4.0",
@ -4232,7 +4315,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz",
"integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==",
"dev": true,
"dependencies": {
"ast-types-flow": "0.0.7"
}
@ -5052,8 +5134,7 @@
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"node_modules/commondir": {
"version": "1.0.1",
@ -5640,7 +5721,6 @@
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz",
"integrity": "sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
"fastparse": "^1.1.2"
@ -5671,7 +5751,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz",
"integrity": "sha512-Ht70DcFBh+/ekjVrYS2PlDMdSQEl3OFNmjK6lcn49HptBgilXf/Zwg4uFh9Xn0pX3Q8YOkSjIFOfK2osvdqpBw==",
"dev": true,
"dependencies": {
"through": "X.X.X"
}
@ -5690,7 +5769,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"bin": {
"cssesc": "bin/cssesc"
},
@ -5707,8 +5785,7 @@
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
"dev": true
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="
},
"node_modules/dashdash": {
"version": "1.14.1",
@ -7077,8 +7154,7 @@
"node_modules/fastparse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
"dev": true
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ=="
},
"node_modules/fastq": {
"version": "1.13.0",
@ -7842,6 +7918,11 @@
"node": ">=10.17.0"
}
},
"node_modules/humanize-duration": {
"version": "3.27.3",
"resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.3.tgz",
"integrity": "sha512-iimHkHPfIAQ8zCDQLgn08pRqSVioyWvnGfaQ8gond2wf7Jq2jJ+24ykmnRyiz3fIldcn4oUuQXpjqKLhSVR7lw=="
},
"node_modules/humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
@ -9809,6 +9890,14 @@
"rxjs": "^6.3.3"
}
},
"node_modules/ngx-dropzone": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/ngx-dropzone/-/ngx-dropzone-3.1.0.tgz",
"integrity": "sha512-5RBaEl07QUcY6sv/BBPyIxN6nbWY/KqTGheEKgbuGS0N1QPFY7NJUo8+X3fYUwQgLS+wjJeqPiR37dd0YNDtWA==",
"dependencies": {
"tslib": "^2.0.0"
}
},
"node_modules/nice-napi": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
@ -12491,7 +12580,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz",
"integrity": "sha512-e8BOaTo007E3dMuQQTnPdalbKTABKNS7UxoBIDnwOqRa+QwMrCPjynB8zAlPF6xlqUfdLPPLIJ13hJNmhtq8Ng==",
"dev": true,
"dependencies": {
"semver": "^5.3.0"
}
@ -12500,7 +12588,6 @@
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true,
"bin": {
"semver": "bin/semver"
}
@ -13005,8 +13092,7 @@
"node_modules/sprintf-js": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
"dev": true
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug=="
},
"node_modules/sshpk": {
"version": "1.17.0",
@ -13333,8 +13419,7 @@
"node_modules/through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
"dev": true
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="
},
"node_modules/thunky": {
"version": "1.1.0",
@ -17322,6 +17407,67 @@
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true
},
"angular-datatables": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/angular-datatables/-/angular-datatables-14.0.0.tgz",
"integrity": "sha512-IdJdS/IGAFKcWKCM3PrgHt8YIyCGxUKaPQGQom+5YvAcIMKuRwgB7aYT2H3+zUsJBNms2Yaeh95ABNT+yqOwLQ==",
"requires": {
"codelyzer": "^6.0.2"
},
"dependencies": {
"@angular/compiler": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz",
"integrity": "sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ=="
},
"@angular/core": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz",
"integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w=="
},
"app-root-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz",
"integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA=="
},
"codelyzer": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-6.0.2.tgz",
"integrity": "sha512-v3+E0Ucu2xWJMOJ2fA/q9pDT/hlxHftHGPUay1/1cTgyPV5JTHFdO9hqo837Sx2s9vKBMTt5gO+lhF95PO6J+g==",
"requires": {
"@angular/compiler": "9.0.0",
"@angular/core": "9.0.0",
"app-root-path": "^3.0.0",
"aria-query": "^3.0.0",
"axobject-query": "2.0.2",
"css-selector-tokenizer": "^0.7.1",
"cssauron": "^1.4.0",
"damerau-levenshtein": "^1.0.4",
"rxjs": "^6.5.3",
"semver-dsl": "^1.0.1",
"source-map": "^0.5.7",
"sprintf-js": "^1.1.2",
"tslib": "^1.10.0",
"zone.js": "~0.10.3"
}
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="
},
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"zone.js": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.10.3.tgz",
"integrity": "sha512-LXVLVEq0NNOqK/fLJo3d0kfzd4sxwn2/h67/02pjCjfKDxgx1i9QqpvtHD8CrBnSSwMw5+dy11O7FRX5mkO7Cg=="
}
}
},
"ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
@ -17428,7 +17574,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz",
"integrity": "sha512-majUxHgLehQTeSA+hClx+DY09OVUqG3GtezWkF1krgLGNdlDu9l9V8DaqNMWbq4Eddc8wsyDA0hpDUtnYxQEXw==",
"dev": true,
"requires": {
"ast-types-flow": "0.0.7",
"commander": "^2.11.0"
@ -17485,8 +17630,7 @@
"ast-types-flow": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
"integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==",
"dev": true
"integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag=="
},
"asynckit": {
"version": "0.4.0",
@ -17536,7 +17680,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz",
"integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==",
"dev": true,
"requires": {
"ast-types-flow": "0.0.7"
}
@ -18156,8 +18299,7 @@
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"commondir": {
"version": "1.0.1",
@ -18610,7 +18752,6 @@
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz",
"integrity": "sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
"fastparse": "^1.1.2"
@ -18626,7 +18767,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz",
"integrity": "sha512-Ht70DcFBh+/ekjVrYS2PlDMdSQEl3OFNmjK6lcn49HptBgilXf/Zwg4uFh9Xn0pX3Q8YOkSjIFOfK2osvdqpBw==",
"dev": true,
"requires": {
"through": "X.X.X"
}
@ -18640,8 +18780,7 @@
"cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
},
"custom-event": {
"version": "1.0.1",
@ -18652,8 +18791,7 @@
"damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
"dev": true
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="
},
"dashdash": {
"version": "1.14.1",
@ -19601,8 +19739,7 @@
"fastparse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
"dev": true
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ=="
},
"fastq": {
"version": "1.13.0",
@ -20186,6 +20323,11 @@
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"dev": true
},
"humanize-duration": {
"version": "3.27.3",
"resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.3.tgz",
"integrity": "sha512-iimHkHPfIAQ8zCDQLgn08pRqSVioyWvnGfaQ8gond2wf7Jq2jJ+24ykmnRyiz3fIldcn4oUuQXpjqKLhSVR7lw=="
},
"humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
@ -21684,6 +21826,14 @@
"tslib": "^2.0.0"
}
},
"ngx-dropzone": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/ngx-dropzone/-/ngx-dropzone-3.1.0.tgz",
"integrity": "sha512-5RBaEl07QUcY6sv/BBPyIxN6nbWY/KqTGheEKgbuGS0N1QPFY7NJUo8+X3fYUwQgLS+wjJeqPiR37dd0YNDtWA==",
"requires": {
"tslib": "^2.0.0"
}
},
"nice-napi": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
@ -23569,7 +23719,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz",
"integrity": "sha512-e8BOaTo007E3dMuQQTnPdalbKTABKNS7UxoBIDnwOqRa+QwMrCPjynB8zAlPF6xlqUfdLPPLIJ13hJNmhtq8Ng==",
"dev": true,
"requires": {
"semver": "^5.3.0"
},
@ -23577,8 +23726,7 @@
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
}
},
@ -24005,8 +24153,7 @@
"sprintf-js": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
"dev": true
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug=="
},
"sshpk": {
"version": "1.17.0",
@ -24232,8 +24379,7 @@
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
"dev": true
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="
},
"thunky": {
"version": "1.1.0",

View File

@ -28,10 +28,14 @@
"@fortawesome/free-solid-svg-icons": "^6.2.0",
"@ng-bootstrap/ng-bootstrap": "10.0.0",
"@panva/oauth4webapi": "^1.1.3",
"angular-datatables": "^14.0.0",
"bootstrap": "^4.4.1",
"chart.js": "2.9.4",
"fhirclient": "^2.5.1",
"humanize-duration": "^3.27.3",
"moment": "^2.29.4",
"ng2-charts": "^2.3.0",
"ngx-dropzone": "^3.1.0",
"rxjs": "~6.5.4",
"tslib": "^2.0.0",
"zone.js": "~0.11.8"

View File

@ -1,6 +1,5 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
@ -18,7 +17,7 @@ import { ResourceDetailComponent } from './pages/resource-detail/resource-detail
import { AuthSignupComponent } from './pages/auth-signup/auth-signup.component';
import { AuthSigninComponent } from './pages/auth-signin/auth-signin.component';
import { FormsModule } from '@angular/forms';
import { NgxDropzoneModule } from 'ngx-dropzone';
import { AuthInterceptorService } from './services/auth-interceptor.service';
import { CanActivateAuthGuard } from './services/can-activate.auth-guard';
import {FastenApiService} from './services/fasten-api.service';
@ -44,7 +43,8 @@ import { SourceDetailComponent } from './pages/source-detail/source-detail.compo
AppRoutingModule,
HttpClientModule,
NgbModule,
ChartsModule
ChartsModule,
NgxDropzoneModule
],
providers: [
{

View File

@ -1 +1,31 @@
<p>list-care-plan works!</p>
<p class="mg-b-20">A clinical condition, problem, diagnosis, or other event, situation, issue, or clinical concept that has risen to a level of concern.</p>
<div class="table-responsive">
<table class="table table-bordered mg-b-0">
<thead>
<tr>
<th>Category</th>
<th>Reason</th>
<th>Period</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let careplan of careplanList">
<td>
<b>{{careplan.category}}</b>
</td>
<td class="align-middle">
<div *ngFor="let reason of careplan.reason">
{{reason[0]}}
<span> - </span>
<span className="text-muted">
{{reason[1] || "no data"}}
</span>
</div>
</td>
<td class="align-middle">{{careplan.period?.start}} - {{careplan.period?.end}}</td>
<td class="align-middle">{{careplan.status}}</td>
</tr>
</tbody>
</table>
</div>

View File

@ -1,4 +1,6 @@
import { Component, OnInit } from '@angular/core';
import {Component, Input, OnInit} from '@angular/core';
import {ResourceFhir} from '../../models/fasten/resource_fhir';
import {CarePlan} from '../../models/display/care-plan';
@Component({
selector: 'app-list-care-plan',
@ -7,9 +9,17 @@ import { Component, OnInit } from '@angular/core';
})
export class ListCarePlanComponent implements OnInit {
@Input() resourceList: ResourceFhir[] = []
careplanList: CarePlan[] = []
constructor() { }
ngOnInit(): void {
let _careplanList = this.careplanList
this.resourceList.forEach((resource) => {
let careplan = new CarePlan(resource.payload)
_careplanList.push(careplan)
})
}
}

View File

@ -13,13 +13,13 @@
<tr *ngFor="let condition of conditionList">
<td>
{{condition.name}}
<small class="text-muted pull-right">
<small class="text-muted float-right">
{{ condition.nameCode}} {{ condition.nameCodeSystem }}
</small>
</td>
<td>{{condition.clinicalStatus}}</td>
<td>{{condition.verificationStatus}}</td>
<td>{{condition.onset}}</td>
<td class="align-middle">{{condition.clinicalStatus}}</td>
<td class="align-middle">{{condition.verificationStatus}}</td>
<td class="align-middle">{{condition.onset | date:'M/d/yy'}}</td>
</tr>
</tbody>
</table>

View File

@ -1,51 +1,6 @@
import {Component, Input, OnInit} from '@angular/core';
import {getCodeOrConcept} from '../../fhir/utils';
import {CODE_SYSTEMS} from '../../fhir/constants';
import {ResourceFhir} from '../../models/fasten/resource_fhir';
export class Condition {
name: string
nameCode: string
nameCodeSystem: string
clinicalStatus: string
verificationStatus: string
onset: string
constructor(resourcePayload: any) {
this.populateConditionName(resourcePayload)
this.clinicalStatus = getCodeOrConcept(resourcePayload.clinicalStatus)
this.verificationStatus = getCodeOrConcept(resourcePayload.verificationStatus)
}
populateConditionName(resourePayload: any){
if (resourePayload.code) {
if (resourePayload.code.text) {
this.name = resourePayload.code.text;
}
if (Array.isArray(resourePayload.code.coding) && resourePayload.code.coding.length) {
let c = resourePayload.code.coding[0]
this.nameCodeSystem = c.system
for (let key in CODE_SYSTEMS) {
if (CODE_SYSTEMS[key].url === c.system) {
this.nameCodeSystem = `(${key})`;
break;
}
}
if (c.display) {
this.name = c.display
}
if (c.code) {
this.nameCode = c.code
}
}
}
}
}
import {Condition} from '../../models/display/condition';
@Component({
selector: 'app-list-condition',
@ -55,21 +10,16 @@ export class Condition {
export class ListConditionComponent implements OnInit {
@Input() resourceList: ResourceFhir[] = []
conditionList: Condition[] = []
constructor() { }
ngOnInit(): void {
console.log("INSIDE LIST CONDIDITION", this.resourceList)
let _conditions = this.conditionList
this.resourceList.forEach((resource) => {
let cond = new Condition(resource.payload)
_conditions.push(cond)
console.log("PARSED CONDITITION", cond)
})
console.log("COMPLETED", this.conditionList)
}
}

View File

@ -1 +1,27 @@
<p>list-encounter works!</p>
<p class="mg-b-20">A clinical condition, problem, diagnosis, or other event, situation, issue, or clinical concept that has risen to a level of concern.</p>
<div class="table-responsive">
<table class="table table-bordered mg-b-0">
<thead>
<tr>
<th>Type</th>
<th>Reason</th>
<th>Class</th>
<th>Status</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let encounter of encounterList">
<td>
<b *ngIf="encounter.encounterType">{{encounter.encounterType}}</b>
<span *ngIf="!encounter.encounterType && encounter.encounterClass" class="text-muted">{{encounter.encounterClass}} encounter</span>
<small *ngIf="!encounter.encounterType && !encounter.encounterClass" class="text-muted">N/A</small>
</td>
<td class="align-middle">{{encounter.reason ? encounter.reason : 'N/A' }}</td>
<td class="align-middle">{{encounter.encounterClass ? encounter.encounterClass : 'N/A' }}</td>
<td class="align-middle">{{encounter.status ? encounter.status : 'N/A'}}</td>
<td class="align-middle">-</td>
</tr>
</tbody>
</table>
</div>

View File

@ -1,4 +1,7 @@
import { Component, OnInit } from '@angular/core';
import {Component, Input, OnInit} from '@angular/core';
import {ResourceFhir} from '../../models/fasten/resource_fhir';
import {Condition} from '../../models/display/condition';
import {Encounter} from '../../models/display/encounter';
@Component({
selector: 'app-list-encounter',
@ -7,9 +10,16 @@ import { Component, OnInit } from '@angular/core';
})
export class ListEncounterComponent implements OnInit {
@Input() resourceList: ResourceFhir[] = []
encounterList: Encounter[] = []
constructor() { }
ngOnInit(): void {
let _encounterList = this.encounterList
this.resourceList.forEach((resource) => {
let encounter = new Encounter(resource.payload)
_encounterList.push(encounter)
})
}
}

View File

@ -1 +1,19 @@
<p>list-immunization works!</p>
<p class="mg-b-20">A clinical condition, problem, diagnosis, or other event, situation, issue, or clinical concept that has risen to a level of concern.</p>
<div class="table-responsive">
<table class="table table-bordered mg-b-0">
<thead>
<tr>
<th>Type</th>
<th>Status</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let immunization of immunizationList">
<td>{{immunization.immunizationType}}</td>
<td class="align-middle">{{immunization.status || '-'}}</td>
<td class="align-middle">{{immunization.date }}</td>
</tr>
</tbody>
</table>
</div>

View File

@ -1,4 +1,7 @@
import { Component, OnInit } from '@angular/core';
import {Component, Input, OnInit} from '@angular/core';
import {ResourceFhir} from '../../models/fasten/resource_fhir';
import {Encounter} from '../../models/display/encounter';
import {Immunization} from '../../models/display/immunization';
@Component({
selector: 'app-list-immunization',
@ -7,9 +10,17 @@ import { Component, OnInit } from '@angular/core';
})
export class ListImmunizationComponent implements OnInit {
@Input() resourceList: ResourceFhir[] = []
immunizationList: Immunization[] = []
constructor() { }
ngOnInit(): void {
let _immunizationList = this.immunizationList
this.resourceList.forEach((resource) => {
let immunization = new Immunization(resource.payload)
_immunizationList.push(immunization)
})
}
}

View File

@ -10,6 +10,7 @@ import { ListEncounterComponent } from './list-encounter/list-encounter.componen
import { ListConditionComponent } from './list-condition/list-condition.component';
import { ListCarePlanComponent } from './list-care-plan/list-care-plan.component';
import {BrowserModule} from '@angular/platform-browser';
import { ListGenericResourceComponent } from './list-generic-resource/list-generic-resource.component';
@NgModule({
imports: [
@ -25,12 +26,19 @@ import {BrowserModule} from '@angular/platform-browser';
ListImmunizationComponent,
ListEncounterComponent,
ListConditionComponent,
ListCarePlanComponent
ListCarePlanComponent,
ListGenericResourceComponent
],
exports: [
ComponentsSidebarComponent,
UtilitiesSidebarComponent,
ListConditionComponent
ComponentsSidebarComponent,
UtilitiesSidebarComponent,
ListPatientComponent,
ListObservationComponent,
ListExplanationOfBenefitComponent,
ListImmunizationComponent,
ListEncounterComponent,
ListConditionComponent,
ListCarePlanComponent
]
})

View File

@ -0,0 +1,21 @@
import {getPath} from '../../fhir/utils';
export class CarePlan {
category: string
reason: string[][]
periodStart: string
periodEnd: string
status: string
constructor(resourcePayload: any) {
this.category = getPath(resourcePayload, "category.0.coding.0.display")
this.reason = (resourcePayload.activity || []).map((a, i) => {
let reason = getPath(a, "detail.code.coding.0.display") || ""
return reason ? [reason, getPath(a, "detail.status") || "no data"] : []
}).filter((arr) => {return arr.length > 0 })
this.periodStart = resourcePayload.period.start
this.periodEnd = resourcePayload.period.end
this.status = resourcePayload.status
}
}

View File

@ -0,0 +1,45 @@
import {getCodeOrConcept} from '../../fhir/utils';
import {CODE_SYSTEMS} from '../../fhir/constants';
export class Condition {
name: string
nameCode: string
nameCodeSystem: string
clinicalStatus: string
verificationStatus: string
onset: string
constructor(resourcePayload: any) {
this.populateConditionName(resourcePayload)
this.clinicalStatus = getCodeOrConcept(resourcePayload.clinicalStatus)
this.verificationStatus = getCodeOrConcept(resourcePayload.verificationStatus)
this.onset = resourcePayload.onsetDateTime
}
populateConditionName(resourePayload: any){
if (resourePayload.code) {
if (resourePayload.code.text) {
this.name = resourePayload.code.text;
}
if (Array.isArray(resourePayload.code.coding) && resourePayload.code.coding.length) {
let c = resourePayload.code.coding[0]
this.nameCodeSystem = c.system
for (let key in CODE_SYSTEMS) {
if (CODE_SYSTEMS[key].url === c.system) {
this.nameCodeSystem = `(${key})`;
break;
}
}
if (c.display) {
this.name = c.display
}
if (c.code) {
this.nameCode = c.code
}
}
}
}
}

View File

@ -0,0 +1,21 @@
import {getPath} from '../../fhir/utils';
export class Encounter {
encounterType: string
encounterClass: string
reason: string
status: string
constructor(resourcePayload: any) {
this.encounterType = getPath(resourcePayload, "type.0.text");
this.encounterClass = this.getEncounterClass(resourcePayload);
this.reason = getPath(resourcePayload, "reason.0.coding.0.display")
this.status = getPath(resourcePayload, "status")
}
getEncounterClass(encounter) {
return encounter.class && typeof encounter.class == "object" ?
getPath(encounter, "class.type.0.text") :
encounter.class;
}
}

View File

@ -0,0 +1,14 @@
import {getPath} from '../../fhir/utils';
import * as moment from 'moment'
export class Immunization {
immunizationType: string
status: string
date: moment.Moment
constructor(resourcePayload: any) {
this.immunizationType = getPath(resourcePayload, "vaccineCode.coding.0.display")
this.status = resourcePayload.status || "-"
this.date = moment(resourcePayload.date || resourcePayload.occurrenceDateTime || resourcePayload.occurrenceString)
}
}

View File

@ -0,0 +1,20 @@
import * as moment from 'moment';
import {getPath} from '../../fhir/utils';
export class Observation {
code: string
status: string
date: moment.Moment
// { title: 'Observation', versions: '*', format: 'code', getter: o => o.code.coding[0] },
// { title: 'Value', versions: '*', getter: o => obsValue(o) },
// { title: 'Effective', 'versions': '*', getter: o => attributeXTime(o,'effective') },
// { title: 'Issued Date', 'versions': '*', format: 'date', getter: o => o.issued },
// { title: 'ID', versions: '*', getter: o => o.id }
constructor(resourcePayload: any) {
this.code = getPath(resourcePayload, "code.coding[0].display")
this.status = resourcePayload.status || "-"
this.date = moment(resourcePayload.date || resourcePayload.occurrenceDateTime || resourcePayload.occurrenceString)
}
}

View File

@ -0,0 +1,12 @@
import * as moment from "moment"
export class Period {
from: moment.Moment
to: moment.Moment
constructor(startDate?: string, endDate?: string) {
this.from = startDate ? moment(startDate) : null
this.to = endDate ? moment(endDate) : null
}
}

View File

@ -20,19 +20,18 @@
<h2 class="az-content-title mg-t-40">Medical Sources</h2>
<div class="az-content-label mg-b-5">Hospitals & Medical Clinics</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.
Please click the logos below to initiate the connection.</p>
<div class="row row-sm">
<div class="col-lg">
<input class="form-control" placeholder="Input box" type="text">
</div><!-- col -->
<div class="col-lg mg-t-10 mg-lg-t-0">
<input class="form-control" placeholder="Input box (readonly)" readonly="" type="text">
</div><!-- col -->
<div class="col-lg mg-t-10 mg-lg-t-0">
<input class="form-control" placeholder="Input box (disabled)" disabled="" type="text">
<ngx-dropzone [multiple]="false" (change)="uploadSourceBundle($event)">
<ngx-dropzone-label>Drop it, baby!</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>
</ngx-dropzone>
</div><!-- col -->
</div><!-- row -->

View File

@ -39,8 +39,7 @@ export class MedicalSourcesComponent implements OnInit {
connectedSourceList = []
availableSourceList = []
uploadedFile: File[] = []
ngOnInit(): void {
this.fastenApi.getSources()
@ -193,6 +192,20 @@ export class MedicalSourcesComponent implements OnInit {
)
}
uploadSourceBundle(event) {
this.uploadedFile = [event.addedFiles[0]]
this.fastenApi.createManualSource(event.addedFiles[0]).subscribe(
(respData) => {
console.log("source manual source create response:", respData)
},
(err) => {console.log(err)},
() => {
this.uploadedFile = []
}
)
}
uuidV4(){
// @ts-ignore
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>

View File

@ -17,27 +17,10 @@
<h2 class="az-content-title mg-t-40">{{selectedResourceType}}</h2>
<app-list-condition *ngIf="selectedResourceType == 'Condition'" [resourceList]="selectedResources"></app-list-condition>
<app-list-care-plan *ngIf="selectedResourceType == 'CarePlan'" [resourceList]="selectedResources"></app-list-care-plan>
<app-list-encounter *ngIf="selectedResourceType == 'Encounter'" [resourceList]="selectedResources"></app-list-encounter>
<app-list-immunization *ngIf="selectedResourceType == 'Immunization'" [resourceList]="selectedResources"></app-list-immunization>
<div class="table-responsive">
<table class="table table-bordered mg-b-0">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Position</th>
<th>Salary</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let resource of selectedResources">
<th scope="row">1</th>
<td>Tiger Nixon</td>
<td>System Architect</td>
<td>$320,800</td>
</tr>
</tbody>
</table>
</div>
</div><!-- az-content-body -->
</div><!-- container -->

View File

@ -76,6 +76,20 @@ export class FastenApiService {
);
}
createManualSource(file: File): Observable<Source> {
const formData = new FormData();
formData.append('file', file);
return this._httpClient.post<any>(`${this.getBasePath()}/api/secure/source/manual`, formData)
.pipe(
map((response: ResponseWrapper) => {
console.log("MANUAL SOURCE RESPONSE", response)
return response.data as Source
})
);
}
getSources(): Observable<Source[]> {
return this._httpClient.get<any>(`${this.getBasePath()}/api/secure/source`)
.pipe(