adding support for Composition resource type (Custom grouping) (#20)

This commit is contained in:
Jason Kulatunga 2023-01-10 19:23:47 -08:00 committed by GitHub
parent ba333fce42
commit f903c38b55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 18420 additions and 876 deletions

2
.gitignore vendored
View File

@ -58,3 +58,5 @@ vendor
fasten.db
test.go
/.couchdb
config.dev.yaml

View File

@ -1 +1,12 @@
package pkg
const (
ContextKeyTypeConfig string = "CONFIG"
ContextKeyTypeDatabase string = "REPOSITORY"
ContextKeyTypeLogger string = "LOGGER"
ContextKeyTypeAuthUsername string = "AUTH_USERNAME"
ContextKeyTypeAuthToken string = "AUTH_TOKEN"
FhirResourceTypeComposition string = "Composition"
)

View File

@ -25,6 +25,7 @@ type DatabaseRepository interface {
AddResourceAssociation(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string, relatedSource *models.SourceCredential, relatedResourceType string, relatedResourceId string) error
RemoveResourceAssociation(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string, relatedSource *models.SourceCredential, relatedResourceType string, relatedResourceId string) error
GetFlattenedResourceGraph(ctx context.Context) ([]*models.ResourceFhir, []*models.ResourceFhir, error)
AddResourceComposition(ctx context.Context, compositionTitle string, resources []*models.ResourceFhir) error
//UpsertProfile(context.Context, *models.Profile) error
//UpsertOrganziation(context.Context, *models.Organization) error

View File

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// SourceCredential: interface.go
// Source: interface.go
// Package mock_database is a generated GoMock package.
package mock_database
@ -8,7 +8,8 @@ import (
context "context"
reflect "reflect"
models "github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
models "github.com/fastenhealth/fasten-sources/clients/models"
models0 "github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
gomock "github.com/golang/mock/gomock"
)
@ -35,6 +36,34 @@ func (m *MockDatabaseRepository) EXPECT() *MockDatabaseRepositoryMockRecorder {
return m.recorder
}
// AddResourceAssociation mocks base method.
func (m *MockDatabaseRepository) AddResourceAssociation(ctx context.Context, source *models0.SourceCredential, resourceType, resourceId string, relatedSource *models0.SourceCredential, relatedResourceType, relatedResourceId string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddResourceAssociation", ctx, source, resourceType, resourceId, relatedSource, relatedResourceType, relatedResourceId)
ret0, _ := ret[0].(error)
return ret0
}
// AddResourceAssociation indicates an expected call of AddResourceAssociation.
func (mr *MockDatabaseRepositoryMockRecorder) AddResourceAssociation(ctx, source, resourceType, resourceId, relatedSource, relatedResourceType, relatedResourceId interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddResourceAssociation", reflect.TypeOf((*MockDatabaseRepository)(nil).AddResourceAssociation), ctx, source, resourceType, resourceId, relatedSource, relatedResourceType, relatedResourceId)
}
// AddResourceComposition mocks base method.
func (m *MockDatabaseRepository) AddResourceComposition(ctx context.Context, compositionTitle string, resources []*models0.ResourceFhir) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddResourceComposition", ctx, compositionTitle, resources)
ret0, _ := ret[0].(error)
return ret0
}
// AddResourceComposition indicates an expected call of AddResourceComposition.
func (mr *MockDatabaseRepositoryMockRecorder) AddResourceComposition(ctx, compositionTitle, resources interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddResourceComposition", reflect.TypeOf((*MockDatabaseRepository)(nil).AddResourceComposition), ctx, compositionTitle, resources)
}
// Close mocks base method.
func (m *MockDatabaseRepository) Close() error {
m.ctrl.T.Helper()
@ -49,8 +78,22 @@ func (mr *MockDatabaseRepositoryMockRecorder) Close() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockDatabaseRepository)(nil).Close))
}
// CreateSource mocks base method.
func (m *MockDatabaseRepository) CreateSource(arg0 context.Context, arg1 *models0.SourceCredential) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateSource", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// CreateSource indicates an expected call of CreateSource.
func (mr *MockDatabaseRepositoryMockRecorder) CreateSource(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSource", reflect.TypeOf((*MockDatabaseRepository)(nil).CreateSource), arg0, arg1)
}
// CreateUser mocks base method.
func (m *MockDatabaseRepository) CreateUser(arg0 context.Context, arg1 *models.User) error {
func (m *MockDatabaseRepository) CreateUser(arg0 context.Context, arg1 *models0.User) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateUser", arg0, arg1)
ret0, _ := ret[0].(error)
@ -62,3 +105,211 @@ func (mr *MockDatabaseRepositoryMockRecorder) CreateUser(arg0, arg1 interface{})
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUser", reflect.TypeOf((*MockDatabaseRepository)(nil).CreateUser), arg0, arg1)
}
// GetCurrentUser mocks base method.
func (m *MockDatabaseRepository) GetCurrentUser(arg0 context.Context) *models0.User {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetCurrentUser", arg0)
ret0, _ := ret[0].(*models0.User)
return ret0
}
// GetCurrentUser indicates an expected call of GetCurrentUser.
func (mr *MockDatabaseRepositoryMockRecorder) GetCurrentUser(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentUser", reflect.TypeOf((*MockDatabaseRepository)(nil).GetCurrentUser), arg0)
}
// GetFlattenedResourceGraph mocks base method.
func (m *MockDatabaseRepository) GetFlattenedResourceGraph(ctx context.Context) ([]*models0.ResourceFhir, []*models0.ResourceFhir, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetFlattenedResourceGraph", ctx)
ret0, _ := ret[0].([]*models0.ResourceFhir)
ret1, _ := ret[1].([]*models0.ResourceFhir)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// GetFlattenedResourceGraph indicates an expected call of GetFlattenedResourceGraph.
func (mr *MockDatabaseRepositoryMockRecorder) GetFlattenedResourceGraph(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFlattenedResourceGraph", reflect.TypeOf((*MockDatabaseRepository)(nil).GetFlattenedResourceGraph), ctx)
}
// GetPatientForSources mocks base method.
func (m *MockDatabaseRepository) GetPatientForSources(ctx context.Context) ([]models0.ResourceFhir, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPatientForSources", ctx)
ret0, _ := ret[0].([]models0.ResourceFhir)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPatientForSources indicates an expected call of GetPatientForSources.
func (mr *MockDatabaseRepositoryMockRecorder) GetPatientForSources(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPatientForSources", reflect.TypeOf((*MockDatabaseRepository)(nil).GetPatientForSources), ctx)
}
// GetResourceBySourceId mocks base method.
func (m *MockDatabaseRepository) GetResourceBySourceId(arg0 context.Context, arg1, arg2 string) (*models0.ResourceFhir, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetResourceBySourceId", arg0, arg1, arg2)
ret0, _ := ret[0].(*models0.ResourceFhir)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetResourceBySourceId indicates an expected call of GetResourceBySourceId.
func (mr *MockDatabaseRepositoryMockRecorder) GetResourceBySourceId(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResourceBySourceId", reflect.TypeOf((*MockDatabaseRepository)(nil).GetResourceBySourceId), arg0, arg1, arg2)
}
// GetResourceBySourceType mocks base method.
func (m *MockDatabaseRepository) GetResourceBySourceType(arg0 context.Context, arg1, arg2 string) (*models0.ResourceFhir, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetResourceBySourceType", arg0, arg1, arg2)
ret0, _ := ret[0].(*models0.ResourceFhir)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetResourceBySourceType indicates an expected call of GetResourceBySourceType.
func (mr *MockDatabaseRepositoryMockRecorder) GetResourceBySourceType(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResourceBySourceType", reflect.TypeOf((*MockDatabaseRepository)(nil).GetResourceBySourceType), arg0, arg1, arg2)
}
// GetSource mocks base method.
func (m *MockDatabaseRepository) GetSource(arg0 context.Context, arg1 string) (*models0.SourceCredential, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSource", arg0, arg1)
ret0, _ := ret[0].(*models0.SourceCredential)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSource indicates an expected call of GetSource.
func (mr *MockDatabaseRepositoryMockRecorder) GetSource(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSource", reflect.TypeOf((*MockDatabaseRepository)(nil).GetSource), arg0, arg1)
}
// GetSourceSummary mocks base method.
func (m *MockDatabaseRepository) GetSourceSummary(arg0 context.Context, arg1 string) (*models0.SourceSummary, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSourceSummary", arg0, arg1)
ret0, _ := ret[0].(*models0.SourceSummary)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSourceSummary indicates an expected call of GetSourceSummary.
func (mr *MockDatabaseRepositoryMockRecorder) GetSourceSummary(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSourceSummary", reflect.TypeOf((*MockDatabaseRepository)(nil).GetSourceSummary), arg0, arg1)
}
// GetSources mocks base method.
func (m *MockDatabaseRepository) GetSources(arg0 context.Context) ([]models0.SourceCredential, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSources", arg0)
ret0, _ := ret[0].([]models0.SourceCredential)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSources indicates an expected call of GetSources.
func (mr *MockDatabaseRepositoryMockRecorder) GetSources(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSources", reflect.TypeOf((*MockDatabaseRepository)(nil).GetSources), arg0)
}
// GetSummary mocks base method.
func (m *MockDatabaseRepository) GetSummary(ctx context.Context) (*models0.Summary, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSummary", ctx)
ret0, _ := ret[0].(*models0.Summary)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSummary indicates an expected call of GetSummary.
func (mr *MockDatabaseRepositoryMockRecorder) GetSummary(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSummary", reflect.TypeOf((*MockDatabaseRepository)(nil).GetSummary), ctx)
}
// GetUserByUsername mocks base method.
func (m *MockDatabaseRepository) GetUserByUsername(arg0 context.Context, arg1 string) (*models0.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserByUsername", arg0, arg1)
ret0, _ := ret[0].(*models0.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUserByUsername indicates an expected call of GetUserByUsername.
func (mr *MockDatabaseRepositoryMockRecorder) GetUserByUsername(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByUsername", reflect.TypeOf((*MockDatabaseRepository)(nil).GetUserByUsername), arg0, arg1)
}
// ListResources mocks base method.
func (m *MockDatabaseRepository) ListResources(arg0 context.Context, arg1 models0.ListResourceQueryOptions) ([]models0.ResourceFhir, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListResources", arg0, arg1)
ret0, _ := ret[0].([]models0.ResourceFhir)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListResources indicates an expected call of ListResources.
func (mr *MockDatabaseRepositoryMockRecorder) ListResources(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListResources", reflect.TypeOf((*MockDatabaseRepository)(nil).ListResources), arg0, arg1)
}
// Migrate mocks base method.
func (m *MockDatabaseRepository) Migrate() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Migrate")
ret0, _ := ret[0].(error)
return ret0
}
// Migrate indicates an expected call of Migrate.
func (mr *MockDatabaseRepositoryMockRecorder) Migrate() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Migrate", reflect.TypeOf((*MockDatabaseRepository)(nil).Migrate))
}
// RemoveResourceAssociation mocks base method.
func (m *MockDatabaseRepository) RemoveResourceAssociation(ctx context.Context, source *models0.SourceCredential, resourceType, resourceId string, relatedSource *models0.SourceCredential, relatedResourceType, relatedResourceId string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RemoveResourceAssociation", ctx, source, resourceType, resourceId, relatedSource, relatedResourceType, relatedResourceId)
ret0, _ := ret[0].(error)
return ret0
}
// RemoveResourceAssociation indicates an expected call of RemoveResourceAssociation.
func (mr *MockDatabaseRepositoryMockRecorder) RemoveResourceAssociation(ctx, source, resourceType, resourceId, relatedSource, relatedResourceType, relatedResourceId interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveResourceAssociation", reflect.TypeOf((*MockDatabaseRepository)(nil).RemoveResourceAssociation), ctx, source, resourceType, resourceId, relatedSource, relatedResourceType, relatedResourceId)
}
// UpsertRawResource mocks base method.
func (m *MockDatabaseRepository) UpsertRawResource(ctx context.Context, sourceCredentials models.SourceCredential, rawResource models.RawResourceFhir) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpsertRawResource", ctx, sourceCredentials, rawResource)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpsertRawResource indicates an expected call of UpsertRawResource.
func (mr *MockDatabaseRepositoryMockRecorder) UpsertRawResource(ctx, sourceCredentials, rawResource interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertRawResource", reflect.TypeOf((*MockDatabaseRepository)(nil).UpsertRawResource), ctx, sourceCredentials, rawResource)
}

View File

@ -6,8 +6,10 @@ import (
"fmt"
"github.com/dominikbraun/graph"
sourceModel "github.com/fastenhealth/fasten-sources/clients/models"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/utils"
"github.com/gin-gonic/gin"
"github.com/glebarez/sqlite"
"github.com/google/uuid"
@ -121,10 +123,10 @@ func (sr *SqliteRepository) GetUserByUsername(ctx context.Context, username stri
}
func (sr *SqliteRepository) GetCurrentUser(ctx context.Context) *models.User {
username := ctx.Value("AUTH_USERNAME")
username := ctx.Value(pkg.ContextKeyTypeAuthUsername)
if username == nil {
ginCtx := ctx.(*gin.Context)
username = ginCtx.MustGet("AUTH_USERNAME")
username = ginCtx.MustGet(pkg.ContextKeyTypeAuthUsername)
}
var currentUser models.User
@ -134,7 +136,7 @@ func (sr *SqliteRepository) GetCurrentUser(ctx context.Context) *models.User {
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// User
// Summary
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func (sr *SqliteRepository) GetSummary(ctx context.Context) (*models.Summary, error) {
@ -205,7 +207,7 @@ func (sr *SqliteRepository) UpsertRawResource(ctx context.Context, sourceCredent
//create associations
//note: we create the association in the related_resources table **before** the model actually exists.
if rawResource.ReferencedResources != nil {
if rawResource.ReferencedResources != nil && len(rawResource.ReferencedResources) > 0 {
for _, referencedResource := range rawResource.ReferencedResources {
parts := strings.Split(referencedResource, "/")
if len(parts) != 2 {
@ -227,7 +229,17 @@ func (sr *SqliteRepository) UpsertRawResource(ctx context.Context, sourceCredent
}
}
sr.Logger.Infof("insert/update (%v) %v", rawResource.SourceResourceType, rawResource.SourceResourceID)
return sr.UpsertResource(ctx, wrappedResourceModel)
}
//this method will upsert a resource, however it will not create associations.
func (sr *SqliteRepository) UpsertResource(ctx context.Context, wrappedResourceModel *models.ResourceFhir) (bool, error) {
wrappedResourceModel.UserID = sr.GetCurrentUser(ctx).ID
wrappedResourceModel.RelatedResourceFhir = nil
cachedResourceRaw := wrappedResourceModel.ResourceRaw
sr.Logger.Infof("insert/update (%v) %v", wrappedResourceModel.SourceResourceType, wrappedResourceModel.SourceResourceID)
createResult := sr.GormClient.WithContext(ctx).Where(models.OriginBase{
SourceID: wrappedResourceModel.GetSourceID(),
@ -240,7 +252,7 @@ func (sr *SqliteRepository) UpsertRawResource(ctx context.Context, sourceCredent
} else if createResult.RowsAffected == 0 {
//at this point, wrappedResourceModel contains the data found in the database.
// check if the database resource matches the new resource.
if wrappedResourceModel.ResourceRaw.String() != string(rawResource.ResourceRaw) {
if wrappedResourceModel.ResourceRaw.String() != string(cachedResourceRaw) {
updateResult := createResult.Omit("RelatedResourceFhir.*").Updates(wrappedResourceModel)
return updateResult.RowsAffected > 0, updateResult.Error
} else {
@ -251,34 +263,6 @@ func (sr *SqliteRepository) UpsertRawResource(ctx context.Context, sourceCredent
//resource was created
return createResult.RowsAffected > 0, createResult.Error
}
//return results.RowsAffected > 0, results.Error
//if sr.GormClient.Debug().WithContext(ctx).
// Where(models.OriginBase{
// SourceID: wrappedResourceModel.GetSourceID(),
// SourceResourceID: wrappedResourceModel.GetSourceResourceID(),
// SourceResourceType: wrappedResourceModel.GetSourceResourceType(), //TODO: and UpdatedAt > old UpdatedAt
// }).Updates(wrappedResourceModel).RowsAffected == 0 {
// sr.Logger.Infof("resource does not exist, creating: %s %s %s", wrappedResourceModel.GetSourceID(), wrappedResourceModel.GetSourceResourceID(), wrappedResourceModel.GetSourceResourceType())
// return sr.GormClient.Debug().Create(wrappedResourceModel).Error
//}
//return nil
}
func (sr *SqliteRepository) UpsertResource(ctx context.Context, resourceModel *models.ResourceFhir) error {
sr.Logger.Infof("insert/update (%T) %v", resourceModel, resourceModel)
if sr.GormClient.WithContext(ctx).
Where(models.OriginBase{
SourceID: resourceModel.GetSourceID(),
SourceResourceID: resourceModel.GetSourceResourceID(),
SourceResourceType: resourceModel.GetSourceResourceType(), //TODO: and UpdatedAt > old UpdatedAt
}).Updates(resourceModel).RowsAffected == 0 {
sr.Logger.Infof("resource does not exist, creating: %s %s %s", resourceModel.GetSourceID(), resourceModel.GetSourceResourceID(), resourceModel.GetSourceResourceType())
return sr.GormClient.Create(resourceModel).Error
}
return nil
}
func (sr *SqliteRepository) ListResources(ctx context.Context, queryOptions models.ListResourceQueryOptions) ([]models.ResourceFhir, error) {
@ -407,6 +391,7 @@ func (sr *SqliteRepository) GetFlattenedResourceGraph(ctx context.Context) ([]*m
}
//Generate Graph
// TODO optimization: eventually cache the graph in a database/storage, and update when new resources are added.
g := graph.New(resourceVertexId, graph.Directed(), graph.Acyclic(), graph.Rooted())
//add vertices to the graph (must be done first)
@ -477,7 +462,7 @@ func (sr *SqliteRepository) GetFlattenedResourceGraph(ctx context.Context) ([]*m
continue
}
if strings.ToLower(resource.SourceResourceType) == "condition" {
if strings.ToLower(resource.SourceResourceType) == "condition" || strings.ToLower(resource.SourceResourceType) == strings.ToLower(pkg.FhirResourceTypeComposition) {
conditionList = append(conditionList, resource)
} else if strings.ToLower(resource.SourceResourceType) == "encounter" {
encounterList = append(encounterList, resource)
@ -507,6 +492,10 @@ func (sr *SqliteRepository) GetFlattenedResourceGraph(ctx context.Context) ([]*m
for ndx, _ := range encounterList {
// this is a "root" encounter, which is not related to any condition, we should add it to the Unknown encounters list
flattenRelatedResourcesFn(encounterList[ndx])
//sort all related resources (by date, desc)
encounterList[ndx].RelatedResourceFhir = utils.SortResourcePtrListByDate(encounterList[ndx].RelatedResourceFhir)
}
// Step 4: find all encounters referenced by the root conditions, populate them, then add them to the condition as RelatedResourceFhir
@ -520,10 +509,13 @@ func (sr *SqliteRepository) GetFlattenedResourceGraph(ctx context.Context) ([]*m
flattenRelatedResourcesFn(relatedResourceFhir)
conditionList[ndx].RelatedResourceFhir = append(conditionList[ndx].RelatedResourceFhir, relatedResourceFhir)
}
//sort all related resources (by date, desc)
conditionList[ndx].RelatedResourceFhir = utils.SortResourcePtrListByDate(conditionList[ndx].RelatedResourceFhir)
}
//TODO: sort conditionList by date
conditionList = utils.SortResourcePtrListByDate(conditionList)
encounterList = utils.SortResourcePtrListByDate(encounterList)
return conditionList, encounterList, nil
}
@ -674,6 +666,155 @@ func (sr *SqliteRepository) RemoveResourceAssociation(ctx context.Context, sourc
}).Error
}
// AddResourceComposition
// this will group resources together into a "Composition" -- primarily to group related Encounters & Conditions into one semantic root.
// algorithm:
// - find source for each resource
// - (validate) ensure the current user and the source for each resource matches
// - check if there is a Composition resource Type already.
// - if Composition type already exists:
// - update "relatesTo" field with additional data.
// - else:
// - Create a Composition resource type (populated with "relatesTo" references to all provided Resources)
// - add AddResourceAssociation for all resources linked to the Composition resource
// - store the Composition resource
func (sr *SqliteRepository) AddResourceComposition(ctx context.Context, compositionTitle string, resources []*models.ResourceFhir) error {
currentUser := sr.GetCurrentUser(ctx)
//generate placeholder source
placeholderSource := models.SourceCredential{UserID: currentUser.ID, SourceType: "manual", ModelBase: models.ModelBase{ID: uuid.MustParse("00000000-0000-0000-0000-000000000000")}}
existingCompositionResources := []*models.ResourceFhir{}
rawResourceLookupTable := map[string]*models.ResourceFhir{}
//find the source for each resource we'd like to merge. (for ownership verification)
sourceLookup := map[uuid.UUID]*models.SourceCredential{}
for _, resource := range resources {
if resource.SourceResourceType == pkg.FhirResourceTypeComposition {
//skip, Composition resources don't have a valid SourceCredential
existingCompositionResources = append(existingCompositionResources, resource)
//compositions may include existing resources, make sure we handle these
for _, related := range resource.RelatedResourceFhir {
rawResourceLookupTable[fmt.Sprintf("%s/%s", related.SourceResourceType, related.SourceResourceID)] = related
}
continue
}
if _, sourceOk := sourceLookup[resource.SourceID]; !sourceOk {
//source has not been added yet, lets query for it.
sourceCred, err := sr.GetSource(ctx, resource.SourceID.String())
if err != nil {
return fmt.Errorf("could not find source %s", resource.SourceID.String())
}
sourceLookup[resource.SourceID] = sourceCred
}
rawResourceLookupTable[fmt.Sprintf("%s/%s", resource.SourceResourceType, resource.SourceResourceID)] = resource
}
// (validate) ensure the current user and the source for each resource matches
for _, source := range sourceLookup {
if source.UserID != currentUser.ID {
return fmt.Errorf("source must be owned by the current user: %s vs %s", source.UserID, currentUser.ID)
}
}
// - check if there is a Composition resource Type already.
var compositionResource *models.ResourceFhir
if len(existingCompositionResources) > 0 {
//- if Composition type already exists in this set
// - update "relatesTo" field with additional data.
compositionResource = existingCompositionResources[0]
//unassociated all existing composition resources.
for _, existingCompositionResource := range existingCompositionResources[1:] {
for _, relatedResource := range existingCompositionResource.RelatedResourceFhir {
if err := sr.RemoveResourceAssociation(
ctx,
&placeholderSource,
existingCompositionResource.SourceResourceType,
existingCompositionResource.SourceResourceID,
sourceLookup[relatedResource.SourceID],
relatedResource.SourceResourceType,
relatedResource.SourceResourceID,
); err != nil {
//ignoring errors, could be due to duplicate edges
return fmt.Errorf("an error occurred while removing resource association: %v", err)
}
}
//remove this resource
err := sr.GormClient.WithContext(ctx).Delete(existingCompositionResource)
if err.Error != nil {
return fmt.Errorf("an error occurred while removing resource: %v", err)
}
}
} else {
//- else:
// - Create a Composition resource type (populated with "relatesTo" references to all provided Resources)
compositionResource = &models.ResourceFhir{
OriginBase: models.OriginBase{
UserID: placeholderSource.UserID, //
SourceID: placeholderSource.ID, //Empty SourceID expected ("0000-0000-0000-0000")
SourceResourceType: pkg.FhirResourceTypeComposition,
SourceResourceID: uuid.New().String(),
},
SortDate: nil, //TOOD: figoure out the sortDate by looking for the earliest sort date for all nested resources
SortTitle: &compositionTitle,
RelatedResourceFhir: resources,
}
}
// - Generate an "updated" RawResource json blob
rawCompositionResource := models.ResourceComposition{
Title: compositionTitle,
RelatesTo: []models.ResourceCompositionRelatesTo{},
}
for relatedResourceKey, _ := range rawResourceLookupTable {
rawCompositionResource.RelatesTo = append(rawCompositionResource.RelatesTo, models.ResourceCompositionRelatesTo{
Target: models.ResourceCompositionRelatesToTarget{
TargetReference: models.ResourceCompositionRelatesToTargetReference{
Reference: relatedResourceKey,
},
},
})
}
rawResourceJson, err := json.Marshal(rawCompositionResource)
if err != nil {
return err
}
compositionResource.ResourceRaw = rawResourceJson
//store the Composition resource
_, err = sr.UpsertResource(ctx, compositionResource)
if err != nil {
return err
}
// - add AddResourceAssociation for all resources linked to the Composition resource
for _, resource := range rawResourceLookupTable {
if err := sr.AddResourceAssociation(
ctx,
&placeholderSource,
compositionResource.SourceResourceType,
compositionResource.SourceResourceID,
sourceLookup[resource.SourceID],
resource.SourceResourceType,
resource.SourceResourceID,
); err != nil {
//ignoring errors, could be due to duplicate edges
sr.Logger.Warnf("an error occurred while creating resource association: %v", err)
}
}
return nil
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SourceCredential
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -1,8 +1,21 @@
package database
import (
"fmt"
sourceModels "github.com/fastenhealth/fasten-sources/clients/models"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
mock_config "github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config/mock"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/gin-gonic/gin"
"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"golang.org/x/net/context"
"io/ioutil"
"log"
"os"
"testing"
)
@ -15,3 +28,233 @@ func TestSourceCredentialInterface(t *testing.T) {
require.Implements(t, (*sourceModels.DatabaseRepository)(nil), repo, "should implement the DatabaseRepository interface from fasten-sources")
require.Implements(t, (*DatabaseRepository)(nil), repo, "should implement the DatabaseRepository interface")
}
// Define the suite, and absorb the built-in basic suite
// functionality from testify - including a T() method which
// returns the current testing context
type RepositoryTestSuite struct {
suite.Suite
MockCtrl *gomock.Controller
TestDatabase *os.File
}
// BeforeTest has a function to be executed right before the test starts and receives the suite and test names as input
func (suite *RepositoryTestSuite) BeforeTest(suiteName, testName string) {
suite.MockCtrl = gomock.NewController(suite.T())
dbFile, err := ioutil.TempFile("", fmt.Sprintf("%s.*.db", testName))
if err != nil {
log.Fatal(err)
}
suite.TestDatabase = dbFile
}
// AfterTest has a function to be executed right after the test finishes and receives the suite and test names as input
func (suite *RepositoryTestSuite) AfterTest(suiteName, testName string) {
suite.MockCtrl.Finish()
os.Remove(suite.TestDatabase.Name())
}
// In order for 'go test' to run this suite, we need to create
// a normal test function and pass our suite to suite.Run
func TestRepositoryTestSuite(t *testing.T) {
suite.Run(t, new(RepositoryTestSuite))
}
func (suite *RepositoryTestSuite) TestNewRepository() {
//setup
fakeConfig := mock_config.NewMockInterface(suite.MockCtrl)
fakeConfig.EXPECT().GetString("database.location").Return(suite.TestDatabase.Name()).AnyTimes()
fakeConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
//test
_, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()))
//assert
require.NoError(suite.T(), err)
}
func (suite *RepositoryTestSuite) TestCreateUser() {
//setup
fakeConfig := mock_config.NewMockInterface(suite.MockCtrl)
fakeConfig.EXPECT().GetString("database.location").Return(suite.TestDatabase.Name()).AnyTimes()
fakeConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
dbRepo, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()))
require.NoError(suite.T(), err)
//test
userModel := &models.User{
Username: "test_username",
Password: "testpassword",
Email: "test@test.com",
}
dbRepo.CreateUser(context.Background(), userModel)
//assert
require.NotEmpty(suite.T(), userModel.ID)
}
func (suite *RepositoryTestSuite) TestCreateUser_WithExitingUser_ShouldFail() {
//setup
fakeConfig := mock_config.NewMockInterface(suite.MockCtrl)
fakeConfig.EXPECT().GetString("database.location").Return(suite.TestDatabase.Name()).AnyTimes()
fakeConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
dbRepo, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()))
require.NoError(suite.T(), err)
//test
userModel := &models.User{
Username: "test_username",
Password: "testpassword",
Email: "test@test.com",
}
err = dbRepo.CreateUser(context.Background(), userModel)
require.NoError(suite.T(), err)
userModel2 := &models.User{
Username: "test_username",
Password: "testpassword",
Email: "test@test.com",
}
err = dbRepo.CreateUser(context.Background(), userModel2)
//assert
require.Error(suite.T(), err)
}
//TODO: ensure user's cannot specify the ID when creating a user.
func (suite *RepositoryTestSuite) TestCreateUser_WithUserProvidedId_ShouldBeReplaced() {
//setup
fakeConfig := mock_config.NewMockInterface(suite.MockCtrl)
fakeConfig.EXPECT().GetString("database.location").Return(suite.TestDatabase.Name()).AnyTimes()
fakeConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
dbRepo, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()))
require.NoError(suite.T(), err)
//test
userProvidedId := uuid.New()
userModel := &models.User{
ModelBase: models.ModelBase{
ID: userProvidedId,
},
Username: "test_username",
Password: "testpassword",
Email: "test@test.com",
}
dbRepo.CreateUser(context.Background(), userModel)
//assert
require.NotEmpty(suite.T(), userModel.ID)
require.NotEqual(suite.T(), userProvidedId.String(), userModel.ID.String())
}
func (suite *RepositoryTestSuite) TestGetUserByUsername() {
//setup
fakeConfig := mock_config.NewMockInterface(suite.MockCtrl)
fakeConfig.EXPECT().GetString("database.location").Return(suite.TestDatabase.Name()).AnyTimes()
fakeConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
dbRepo, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()))
require.NoError(suite.T(), err)
userModel := &models.User{
Username: "test_username",
Password: "testpassword",
Email: "test@test.com",
}
err = dbRepo.CreateUser(context.Background(), userModel)
require.NoError(suite.T(), err)
//test
userModelResult, err := dbRepo.GetUserByUsername(context.Background(), "test_username")
//assert
require.NoError(suite.T(), err)
require.Equal(suite.T(), userModel.ID, userModelResult.ID)
}
func (suite *RepositoryTestSuite) TestGetUserByUsername_WithInvalidUsername() {
//setup
fakeConfig := mock_config.NewMockInterface(suite.MockCtrl)
fakeConfig.EXPECT().GetString("database.location").Return(suite.TestDatabase.Name()).AnyTimes()
fakeConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
dbRepo, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()))
require.NoError(suite.T(), err)
userModel := &models.User{
Username: "test_username",
Password: "testpassword",
Email: "test@test.com",
}
err = dbRepo.CreateUser(context.Background(), userModel)
require.NoError(suite.T(), err)
//test
_, err = dbRepo.GetUserByUsername(context.Background(), "userdoesntexist")
//assert
require.Error(suite.T(), err)
}
func (suite *RepositoryTestSuite) TestGetCurrentUser_WithContextBackgroundAuthUser() {
//setup
fakeConfig := mock_config.NewMockInterface(suite.MockCtrl)
fakeConfig.EXPECT().GetString("database.location").Return(suite.TestDatabase.Name()).AnyTimes()
fakeConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
dbRepo, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()))
require.NoError(suite.T(), err)
userModel := &models.User{
Username: "test_username",
Password: "testpassword",
Email: "test@test.com",
}
err = dbRepo.CreateUser(context.Background(), userModel)
require.NoError(suite.T(), err)
//test
userModelResult := dbRepo.GetCurrentUser(context.WithValue(context.Background(), pkg.ContextKeyTypeAuthUsername, "test_username"))
//assert
require.NotNil(suite.T(), userModelResult)
require.Equal(suite.T(), userModelResult.Username, "test_username")
}
func (suite *RepositoryTestSuite) TestGetCurrentUser_WithGinContextBackgroundAuthUser() {
//setup
fakeConfig := mock_config.NewMockInterface(suite.MockCtrl)
fakeConfig.EXPECT().GetString("database.location").Return(suite.TestDatabase.Name()).AnyTimes()
fakeConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
dbRepo, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()))
require.NoError(suite.T(), err)
userModel := &models.User{
Username: "test_username",
Password: "testpassword",
Email: "test@test.com",
}
err = dbRepo.CreateUser(context.Background(), userModel)
require.NoError(suite.T(), err)
//test
ginContext := gin.Context{}
ginContext.Set(pkg.ContextKeyTypeAuthUsername, "test_username")
userModelResult := dbRepo.GetCurrentUser(&ginContext)
//assert
require.NotNil(suite.T(), userModelResult)
require.Equal(suite.T(), userModelResult.Username, "test_username")
}
func (suite *RepositoryTestSuite) TestGetCurrentUser_WithContextBackgroundAuthUserAndNoUserExists_ShouldThrowError() {
//setup
fakeConfig := mock_config.NewMockInterface(suite.MockCtrl)
fakeConfig.EXPECT().GetString("database.location").Return(suite.TestDatabase.Name()).AnyTimes()
fakeConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
dbRepo, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()))
require.NoError(suite.T(), err)
//test
userModelResult := dbRepo.GetCurrentUser(context.WithValue(context.Background(), pkg.ContextKeyTypeAuthUsername, "test_username"))
//assert
require.Nil(suite.T(), userModelResult)
}
//TODO - merging multiple Compositions is broken

View File

@ -0,0 +1,19 @@
package models
//this model is based on FHIR401 Resource Composition - https://github.com/fastenhealth/gofhir-models/blob/main/fhir401/composition.go
type ResourceComposition struct {
Title string `bson:"title" json:"title"`
RelatesTo []ResourceCompositionRelatesTo `bson:"relatesTo,omitempty" json:"relatesTo,omitempty"`
}
type ResourceCompositionRelatesTo struct {
Target ResourceCompositionRelatesToTarget `bson:"target,omitempty" json:"target,omitempty"`
}
type ResourceCompositionRelatesToTarget struct {
TargetReference ResourceCompositionRelatesToTargetReference `bson:"targetReference,omitempty" json:"targetReference,omitempty"`
}
type ResourceCompositionRelatesToTargetReference struct {
Reference string `bson:"reference,omitempty" json:"reference,omitempty"`
}

View File

@ -8,8 +8,8 @@ import (
type ResourceFhir struct {
OriginBase
SortDate time.Time `json:"sort_date" gorm:"sort_date"`
SortTitle string `json:"sort_title" gorm:"sort_title"`
SortDate *time.Time `json:"sort_date" gorm:"sort_date"`
SortTitle *string `json:"sort_title" gorm:"sort_title"`
//embedded data
ResourceRaw datatypes.JSON `json:"resource_raw" gorm:"resource_raw"`

60
backend/pkg/utils/sort.go Normal file
View File

@ -0,0 +1,60 @@
package utils
import (
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"sort"
)
//default sort ASC (a, b, c, d, e...)
func SortResourcePtrListByTitle(resourceList []*models.ResourceFhir) []*models.ResourceFhir {
sort.SliceStable(resourceList, func(i, j int) bool {
if resourceList[i].SortTitle != nil && resourceList[j].SortTitle != nil {
return (*resourceList[i].SortTitle) < (*resourceList[j].SortTitle)
} else if resourceList[i].SortTitle != nil {
return true
} else {
return false
}
})
return resourceList
}
func SortResourceListByTitle(resourceList []models.ResourceFhir) []models.ResourceFhir {
sort.SliceStable(resourceList, func(i, j int) bool {
if resourceList[i].SortTitle != nil && resourceList[j].SortTitle != nil {
return (*resourceList[i].SortTitle) < (*resourceList[j].SortTitle)
} else if resourceList[i].SortTitle != nil {
return true
} else {
return false
}
})
return resourceList
}
//default sort DESC (today, yesterday, 2 days ago, 3 days ago...)
func SortResourcePtrListByDate(resourceList []*models.ResourceFhir) []*models.ResourceFhir {
sort.SliceStable(resourceList, func(i, j int) bool {
if resourceList[i].SortDate != nil && resourceList[j].SortDate != nil {
return (*resourceList[i].SortDate).After(*resourceList[j].SortDate)
} else if resourceList[i].SortDate != nil {
return true
} else {
return false
}
})
return resourceList
}
func SortResourceListByDate(resourceList []models.ResourceFhir) []models.ResourceFhir {
sort.SliceStable(resourceList, func(i, j int) bool {
if resourceList[i].SortDate != nil && resourceList[j].SortDate != nil {
return (*resourceList[i].SortDate).After(*resourceList[j].SortDate)
} else if resourceList[i].SortDate != nil {
return true
} else {
return false
}
})
return resourceList
}

View File

@ -0,0 +1,70 @@
package utils
import (
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/stretchr/testify/require"
"testing"
"time"
)
func TestSortResourceListByTitle(t *testing.T) {
//setup
a := "a"
g := "g"
p := "p"
z := "z"
resources := []*models.ResourceFhir{
{
SortTitle: &a,
},
{
SortTitle: &g,
},
{
SortTitle: &z,
},
{
SortTitle: &p,
},
}
//test
resources = SortResourcePtrListByTitle(resources)
//assert
require.Equal(t, "a", *resources[0].SortTitle)
require.Equal(t, "g", *resources[1].SortTitle)
require.Equal(t, "p", *resources[2].SortTitle)
require.Equal(t, "z", *resources[3].SortTitle)
}
func TestSortResourceListByDate(t *testing.T) {
//setup
a := time.Now().Add(time.Hour * -24)
g := time.Now().Add(time.Hour * -48)
p := time.Now().Add(time.Hour * -72)
z := time.Now().Add(time.Hour * -100)
resources := []*models.ResourceFhir{
{
SortDate: &a,
},
{
SortDate: &g,
},
{
SortDate: &z,
},
{
SortDate: &p,
},
}
//test
resources = SortResourcePtrListByDate(resources)
//assert
require.Equal(t, a, *resources[0].SortDate)
require.Equal(t, g, *resources[1].SortDate)
require.Equal(t, p, *resources[2].SortDate)
require.Equal(t, z, *resources[3].SortDate)
}

View File

@ -2,6 +2,7 @@ package handler
import (
"fmt"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/auth"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
@ -11,8 +12,8 @@ import (
)
func AuthSignup(c *gin.Context) {
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
appConfig := c.MustGet("CONFIG").(config.Interface)
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
appConfig := c.MustGet(pkg.ContextKeyTypeConfig).(config.Interface)
var user models.User
if err := c.ShouldBindJSON(&user); err != nil {
@ -32,8 +33,8 @@ func AuthSignup(c *gin.Context) {
}
func AuthSignin(c *gin.Context) {
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
appConfig := c.MustGet("CONFIG").(config.Interface)
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
appConfig := c.MustGet(pkg.ContextKeyTypeConfig).(config.Interface)
var user models.User
if err := c.ShouldBindJSON(&user); err != nil {

View File

@ -1,8 +1,10 @@
package handler
import (
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/utils"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"net/http"
@ -10,8 +12,8 @@ import (
)
func ListResourceFhir(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
listResourceQueryOptions := models.ListResourceQueryOptions{}
if len(c.Query("sourceResourceType")) > 0 {
@ -29,6 +31,12 @@ func ListResourceFhir(c *gin.Context) {
wrappedResourceModels, err := databaseRepo.ListResources(c, listResourceQueryOptions)
if c.Query("sortBy") == "title" {
wrappedResourceModels = utils.SortResourceListByTitle(wrappedResourceModels)
} else {
wrappedResourceModels = utils.SortResourceListByDate(wrappedResourceModels)
}
if err != nil {
logger.Errorln("An error occurred while retrieving resources", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
@ -40,8 +48,8 @@ func ListResourceFhir(c *gin.Context) {
//this endpoint retrieves a specific resource by its ID
func GetResourceFhir(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
resourceId := strings.Trim(c.Param("resourceId"), "/")
sourceId := strings.Trim(c.Param("sourceId"), "/")
@ -56,55 +64,27 @@ func GetResourceFhir(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true, "data": wrappedResourceModel})
}
func ReplaceResourceAssociation(c *gin.Context) {
func CreateResourceComposition(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
resourceAssociation := models.ResourceAssociation{}
if err := c.ShouldBindJSON(&resourceAssociation); err != nil {
logger.Errorln("An error occurred while parsing posted resource association data", err)
type jsonPayload struct {
Resources []*models.ResourceFhir `json:"resources"`
Title string `json:"title"`
}
var payload jsonPayload
if err := c.ShouldBindJSON(&payload); err != nil {
logger.Errorln("An error occurred while parsing posted resources & title", err)
c.JSON(http.StatusBadRequest, gin.H{"success": false})
return
}
sourceCred, err := databaseRepo.GetSource(c, resourceAssociation.SourceID)
err := databaseRepo.AddResourceComposition(c, payload.Title, payload.Resources)
if err != nil {
logger.Errorln("An error occurred while retrieving source", err)
logger.Errorln("An error occurred while creating resource group (composition)", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
return
}
if len(resourceAssociation.OldRelatedSourceID) > 0 {
oldRelatedSourceCred, err := databaseRepo.GetSource(c, resourceAssociation.OldRelatedSourceID)
if err != nil {
logger.Errorln("An error occurred while retrieving old related source", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
return
}
err = databaseRepo.RemoveResourceAssociation(c, sourceCred, resourceAssociation.SourceResourceType, resourceAssociation.SourceResourceID, oldRelatedSourceCred, resourceAssociation.OldRelatedSourceResourceType, resourceAssociation.OldRelatedSourceResourceID)
if err != nil {
logger.Errorln("An error occurred while deleting resource association", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
return
}
}
newRelatedSourceCred, err := databaseRepo.GetSource(c, resourceAssociation.NewRelatedSourceID)
if err != nil {
logger.Errorln("An error occurred while retrieving new related source", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
return
}
err = databaseRepo.AddResourceAssociation(c, sourceCred, resourceAssociation.SourceResourceType, resourceAssociation.SourceResourceID, newRelatedSourceCred, resourceAssociation.NewRelatedSourceResourceType, resourceAssociation.NewRelatedSourceResourceID)
if err != nil {
logger.Errorln("An error occurred while associating resource", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
return
}
c.JSON(http.StatusOK, gin.H{"success": true})
}
@ -114,8 +94,8 @@ func ReplaceResourceAssociation(c *gin.Context) {
// find the PredecessorMap
// - filter to only vertices that are "Condition" or "Encounter" and are "root" nodes (have no edges directed to this node)
func GetResourceFhirGraph(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
conditionResourceList, encounterResourceList, err := databaseRepo.GetFlattenedResourceGraph(c)
if err != nil {

View File

@ -6,6 +6,7 @@ import (
"github.com/fastenhealth/fasten-sources/clients/factory"
sourceModels "github.com/fastenhealth/fasten-sources/clients/models"
sourcePkg "github.com/fastenhealth/fasten-sources/pkg"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/gin-gonic/gin"
@ -15,8 +16,8 @@ import (
)
func CreateSource(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
sourceCred := models.SourceCredential{}
if err := c.ShouldBindJSON(&sourceCred); err != nil {
@ -45,8 +46,8 @@ func CreateSource(c *gin.Context) {
}
func SourceSync(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
logger.Infof("Get SourceCredential Credentials: %v", c.Param("sourceId"))
@ -67,8 +68,8 @@ func SourceSync(c *gin.Context) {
}
func CreateManualSource(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
// single file
file, err := c.FormFile("file")
@ -135,12 +136,12 @@ func CreateManualSource(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": summary})
c.JSON(http.StatusOK, gin.H{"success": true, "data": summary, "source": manualSourceCredential})
}
func GetSource(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
sourceCred, err := databaseRepo.GetSource(c, c.Param("sourceId"))
if err != nil {
@ -152,8 +153,8 @@ func GetSource(c *gin.Context) {
}
func GetSourceSummary(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
sourceSummary, err := databaseRepo.GetSourceSummary(c, c.Param("sourceId"))
if err != nil {
@ -165,8 +166,8 @@ func GetSourceSummary(c *gin.Context) {
}
func ListSource(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
sourceCreds, err := databaseRepo.GetSources(c)
if err != nil {

View File

@ -0,0 +1,130 @@
package handler
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
mock_config "github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config/mock"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/gin-gonic/gin"
"github.com/golang/mock/gomock"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
)
// Define the suite, and absorb the built-in basic suite
// functionality from testify - including a T() method which
// returns the current testing context
type SourceHandlerTestSuite struct {
suite.Suite
MockCtrl *gomock.Controller
TestDatabase *os.File
AppConfig *mock_config.MockInterface
AppRepository database.DatabaseRepository
}
// BeforeTest has a function to be executed right before the test starts and receives the suite and test names as input
func (suite *SourceHandlerTestSuite) BeforeTest(suiteName, testName string) {
suite.MockCtrl = gomock.NewController(suite.T())
dbFile, err := ioutil.TempFile("", fmt.Sprintf("%s.*.db", testName))
if err != nil {
log.Fatal(err)
}
suite.TestDatabase = dbFile
appConfig := mock_config.NewMockInterface(suite.MockCtrl)
appConfig.EXPECT().GetString("database.location").Return(suite.TestDatabase.Name()).AnyTimes()
appConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
suite.AppConfig = appConfig
appRepo, err := database.NewRepository(suite.AppConfig, logrus.WithField("test", suite.T().Name()))
suite.AppRepository = appRepo
appRepo.CreateUser(context.Background(), &models.User{
Username: "test_username",
Password: "test",
})
}
// AfterTest has a function to be executed right after the test finishes and receives the suite and test names as input
func (suite *SourceHandlerTestSuite) AfterTest(suiteName, testName string) {
suite.MockCtrl.Finish()
os.Remove(suite.TestDatabase.Name())
}
// In order for 'go test' to run this suite, we need to create
// a normal test function and pass our suite to suite.Run
func TestSourceHandlerTestSuite(t *testing.T) {
suite.Run(t, new(SourceHandlerTestSuite))
}
func (suite *SourceHandlerTestSuite) TestCreateManualSourceHandler() {
//setup
w := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(w)
ctx.Set(pkg.ContextKeyTypeLogger, logrus.WithField("test", suite.T().Name()))
ctx.Set(pkg.ContextKeyTypeDatabase, suite.AppRepository)
ctx.Set(pkg.ContextKeyTypeConfig, suite.AppConfig)
ctx.Set(pkg.ContextKeyTypeAuthUsername, "test_username")
//test
file, err := os.Open("testdata/Tania553_Harris789_545c2380-b77f-4919-ab5d-0f615f877250.json")
require.NoError(suite.T(), err)
defer file.Close()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, _ := writer.CreateFormFile("file", filepath.Base(file.Name()))
io.Copy(part, file)
writer.Close()
req, err := http.NewRequest("POST", "/source/manual", body)
require.NoError(suite.T(), err)
req.Header.Add("Content-Type", writer.FormDataContentType())
ctx.Request = req
CreateManualSource(ctx)
//assert
require.Equal(suite.T(), http.StatusOK, w.Code)
type ResponseWrapper struct {
Data struct {
UpdatedResources []string `json:"UpdatedResources"`
TotalResources int `json:"TotalResources"`
} `json:"data"`
Success bool `json:"success"`
Source models.SourceCredential `json:"source"`
}
var respWrapper ResponseWrapper
err = json.Unmarshal(w.Body.Bytes(), &respWrapper)
require.NoError(suite.T(), err)
require.Equal(suite.T(), true, respWrapper.Success)
require.Equal(suite.T(), "manual", string(respWrapper.Source.SourceType))
require.Equal(suite.T(), 196, respWrapper.Data.TotalResources)
summary, err := suite.AppRepository.GetSourceSummary(ctx, respWrapper.Source.ID.String())
require.NoError(suite.T(), err)
require.Equal(suite.T(), map[string]interface{}{
"count": int64(5),
"resource_type": "Condition",
"source_id": respWrapper.Source.ID.String(),
}, summary.ResourceTypeCounts[3])
}

View File

@ -1,6 +1,7 @@
package handler
import (
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
@ -8,8 +9,8 @@ import (
)
func GetSummary(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
summary, err := databaseRepo.GetSummary(c)
if err != nil {

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ package handler
import (
"github.com/fastenhealth/fasten-sources/clients/factory"
sourcePkg "github.com/fastenhealth/fasten-sources/pkg"
"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/gin-gonic/gin"
@ -17,9 +18,10 @@ These Endpoints are only available when Fasten is deployed with allow_unsafe_end
*/
func UnsafeRequestSource(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
appConfig := c.MustGet("CONFIG").(config.Interface)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
appConfig := c.MustGet(pkg.ContextKeyTypeConfig).(config.Interface)
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
//safety check incase this function is called in another way
if !appConfig.GetBool("web.allow_unsafe_endpoints") {
c.JSON(http.StatusServiceUnavailable, gin.H{"success": false})
@ -79,3 +81,18 @@ func UnsafeRequestSource(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": resp})
}
func UnsafeResourceGraph(c *gin.Context) {
appConfig := c.MustGet(pkg.ContextKeyTypeConfig).(config.Interface)
//safety check incase this function is called in another way
if !appConfig.GetBool("web.allow_unsafe_endpoints") {
c.JSON(http.StatusServiceUnavailable, gin.H{"success": false})
return
}
//!!!!!!INSECURE!!!!!!S
//We're setting the username to a user provided value, this is insecure, but required for calling databaseRepo fns
c.Set(pkg.ContextKeyTypeAuthUsername, c.Param("username"))
GetResourceFhirGraph(c)
}

View File

@ -1,13 +1,14 @@
package middleware
import (
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
"github.com/gin-gonic/gin"
)
func ConfigMiddleware(appConfig config.Interface) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("CONFIG", appConfig)
c.Set(pkg.ContextKeyTypeConfig, appConfig)
c.Next()
}
}

View File

@ -1,6 +1,7 @@
package middleware
import (
"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/gin-gonic/gin"
@ -16,7 +17,7 @@ func RepositoryMiddleware(appConfig config.Interface, globalLogger logrus.FieldL
//TODO: determine where we can call defer deviceRepo.Close()
return func(c *gin.Context) {
c.Set("REPOSITORY", deviceRepo)
c.Set(pkg.ContextKeyTypeDatabase, deviceRepo)
c.Next()
}
}

View File

@ -3,6 +3,7 @@ package middleware
import (
"bytes"
"fmt"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"io"
@ -51,7 +52,7 @@ func LoggerMiddleware(logger *logrus.Entry) gin.HandlerFunc {
path := c.Request.URL.Path
blw := &responseBodyLogWriter{body: &bytes.Buffer{}, ResponseWriter: c.Writer}
c.Writer = blw
c.Set("LOGGER", logger)
c.Set(pkg.ContextKeyTypeLogger, logger)
start := time.Now()
c.Next()
stop := time.Since(start)

View File

@ -1,6 +1,7 @@
package middleware
import (
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/auth"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
"github.com/gin-gonic/gin"
@ -11,7 +12,7 @@ import (
func RequireAuth() gin.HandlerFunc {
return func(c *gin.Context) {
appConfig := c.MustGet("CONFIG").(config.Interface)
appConfig := c.MustGet(pkg.ContextKeyTypeConfig).(config.Interface)
authHeader := c.GetHeader("Authorization")
authHeaderParts := strings.Split(authHeader, " ")
@ -38,8 +39,8 @@ func RequireAuth() gin.HandlerFunc {
}
//todo, is this shared between all sessions??
c.Set("AUTH_TOKEN", tokenString)
c.Set("AUTH_USERNAME", claim.Subject)
c.Set(pkg.ContextKeyTypeAuthToken, tokenString)
c.Set(pkg.ContextKeyTypeAuthUsername, claim.Subject)
c.Next()
}

View File

@ -60,7 +60,7 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
secure.GET("/resource/fhir", handler.ListResourceFhir)
secure.GET("/resource/graph", handler.GetResourceFhirGraph)
secure.GET("/resource/fhir/:sourceId/:resourceId", handler.GetResourceFhir)
secure.POST("/resource/association", handler.ReplaceResourceAssociation)
secure.POST("/resource/composition", handler.CreateResourceComposition)
}
if ae.Config.GetBool("web.allow_unsafe_endpoints") {
@ -80,6 +80,7 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
{
//http://localhost:9090/api/raw/test@test.com/436d7277-ad56-41ce-9823-44e353d1b3f6/Patient/smart-1288992
unsafe.GET("/:username/:sourceId/*path", handler.UnsafeRequestSource)
unsafe.GET("/:username/graph", handler.UnsafeResourceGraph)
}
}

View File

@ -24,6 +24,7 @@ const routes: Routes = [
{ path: 'dashboard', component: DashboardComponent, canActivate: [ IsAuthenticatedAuthGuard] },
{ path: 'source/:source_id', component: SourceDetailComponent, canActivate: [ IsAuthenticatedAuthGuard] },
{ path: 'source/:source_id/resource/:resource_id', component: ResourceDetailComponent, canActivate: [ IsAuthenticatedAuthGuard] },
{ path: 'source/:source_id/resource/:resource_type/:resource_id', component: ResourceDetailComponent, canActivate: [ IsAuthenticatedAuthGuard] },
{ path: 'sources', component: MedicalSourcesComponent, canActivate: [ IsAuthenticatedAuthGuard] },
{ path: 'sources/callback/:source_type', component: MedicalSourcesComponent, canActivate: [ IsAuthenticatedAuthGuard] },

View File

@ -160,6 +160,10 @@ export class ReportLabsObservationComponent implements OnInit {
let referenceRanges = []
//sort observations
this.observations = this.observations.sort((a, b) => a.sort_date > b.sort_date ? -1 : a.sort_date < b.sort_date ? 1 : 0)
for(let observation of this.observations){
//get label
this.barChartLabels.push(

View File

@ -1,12 +1,12 @@
<div class="card card-dashboard-seven mb-3">
<div class="card-header tx-medium">
<div class="row cursor-pointer" routerLink="/source/{{condition?.source_id}}/resource/{{condition?.source_resource_id}}">
<div class="row cursor-pointer" routerLink="/source/{{conditionDisplayModel?.source_id}}/resource/{{conditionDisplayModel?.source_resource_id}}">
<!-- Condition Header -->
<div class="col-6">
{{condition | fhirPath: "Condition.code.text.first()":"Condition.code.coding.display.first()"}}
{{conditionDisplayModel?.sort_title ? conditionDisplayModel?.sort_title : (conditionGroup | fhirPath: "Condition.code.text.first()":"Condition.code.coding.display.first()")}}
</div>
<div class="col-6">
{{condition | fhirPath: "Condition.onsetPeriod.start":"Condition.onsetDateTime" | date }} - {{condition | fhirPath: "Condition.onsetPeriod.end" | date}}
{{conditionGroup | fhirPath: "Condition.onsetPeriod.start":"Condition.onsetDateTime" | date }} - {{conditionGroup | fhirPath: "Condition.onsetPeriod.end" | date}}
</div>
</div>
</div><!-- card-header -->
@ -14,29 +14,21 @@
<div class="row">
<!-- Condition Details -->
<!-- {{conditionDisplayModel | json}}-->
<div class="col-6 mb-2">
<div class="row pl-3">
<div *ngIf="involvedInCare.length > 0" class="row pl-3">
<div class="col-12 mt-3 mb-2 tx-indigo">
<p>Involved in Care</p>
</div>
<ng-container *ngFor="let careTeamEntry of careTeams | keyvalue">
<div class="col-6">
<strong>{{careTeamEntry.value | fhirPath: "CareTeam.participant.member.display"}}</strong>
</div>
<div class="col-6">
{{careTeamEntry.value | fhirPath: "CareTeam.participant.role.text"}}
</div>
</ng-container>
<ng-container *ngFor="let practitionerEntry of practitioners | keyvalue">
<div class="col-6">
<strong>{{practitionerEntry.value | fhirPath: "Practitioner.name.family"}}, {{practitionerEntry.value | fhirPath: "Practitioner.name.given"}}</strong>
</div>
<div class="col-6">
{{practitionerEntry.value | fhirPath: "Practitioner.name.prefix"}}
</div>
<ng-container *ngFor="let practitioner of involvedInCare">
<div class="col-6">
<strong>{{practitioner.displayName}}</strong>
</div>
<div class="col-6">
{{practitioner.role}}
<!-- TODO: add email address link here -->
</div>
</ng-container>
@ -48,12 +40,12 @@
<!-- </div>-->
</div>
<ng-container *ngIf="condition.related_resources.length > 0">
<ng-container *ngIf="conditionGroup.related_resources.length > 0">
<a class="cursor-pointer tx-indigo" (click)="collapse.toggle()">show all</a>
<div #collapse="ngbCollapse" [ngbCollapse]="true">
<ul>
<li class="cursor-pointer tx-indigo" *ngFor="let resource of condition.related_resources" routerLink="/source/{{resource?.source_id}}/resource/{{resource?.source_resource_id}}">Resource: {{resource.source_resource_type}}/{{resource.source_resource_id}}</li>
<li class="cursor-pointer tx-indigo" *ngFor="let resourceEntry of resourcesLookup | keyvalue" [routerLink]="resourceEntry.key">{{resourceEntry.value.source_resource_type}} {{resourceEntry.value.sort_title ? '- '+resourceEntry.value.sort_title : '' }} </li>
</ul>
</div>
</ng-container>
@ -62,50 +54,53 @@
</div>
<div class="col-6 bg-gray-100">
<div class="row">
<ng-container *ngFor="let encounter of condition.related_resources | filter:'source_resource_type':'Encounter'">
<ng-container *ngFor="let encounter of encounters">
<div routerLink="/source/{{encounter?.source_id}}/resource/{{encounter?.source_resource_id}}" class="col-6 mt-3 mb-2 tx-indigo">
<strong>{{encounter | fhirPath: "Encounter.period.start" | date}}</strong>
<strong>{{encounter.period_start | date}}</strong>
</div>
<div routerLink="/source/{{encounter?.source_id}}/resource/{{encounter?.source_resource_id}}" class="col-6 mt-3 mb-2 tx-indigo">
<small>{{encounter | fhirPath: "Encounter.location.first().location.display"}}</small>
<small>{{encounter.location_display }}</small>
</div>
<div *ngIf="encounter.related_resources | filter:'source_resource_type':'MedicationRequest' as medications" class="col-12 mt-2 mb-2">
<div *ngIf="encounter?.related_resources?.MedicationRequest || encounter?.related_resources?.Medication" class="col-12 mt-2 mb-2">
<strong>Medications:</strong>
<ul>
<li routerLink="/source/{{medication?.source_id}}/resource/{{medication?.source_resource_id}}" *ngFor="let medication of medications">
{{medication | fhirPath: "MedicationRequest.medicationReference.display":"MedicationRequest.medicationCodeableConcept.text"}}
<li routerLink="/source/{{medication?.source_id}}/resource/{{medication?.source_resource_id}}" *ngFor="let medication of encounter?.related_resources?.MedicationRequest">
{{medication.display }}
</li>
<li routerLink="/source/{{medication?.source_id}}/resource/{{medication?.source_resource_id}}" *ngFor="let medication of encounter?.related_resources?.Medication">
{{medication.title}}
</li>
</ul>
</div>
<div *ngIf="encounter.related_resources | filter:'source_resource_type':'Procedure' as procedures" class="col-12 mt-2 mb-2">
<div *ngIf="encounter?.related_resources?.Procedure as procedures" class="col-12 mt-2 mb-2">
<strong>Procedures:</strong>
<ul>
<li routerLink="/source/{{procedure?.source_id}}/resource/{{procedure?.source_resource_id}}" *ngFor="let procedure of procedures">
{{procedure | fhirPath: "Procedure.code.text"}}
{{procedure.display}}
</li>
</ul>
</div>
<div *ngIf="encounter.related_resources | filter:'source_resource_type':'DiagnosticReport' as diagnosticReports" class="col-12 mt-2 mb-2">
<div *ngIf="encounter?.related_resources?.DiagnosticReport as diagnosticReports" class="col-12 mt-2 mb-2">
<strong>Tests and Examinations:</strong>
<ul>
<li routerLink="/source/{{diagnosticReport?.source_id}}/resource/{{diagnosticReport?.source_resource_id}}" *ngFor="let diagnosticReport of diagnosticReports">
{{diagnosticReport | fhirPath: "DiagnosticReport.code.text":"DiagnosticReport.code.coding.display"}}
{{diagnosticReport.title}}
</li>
</ul>
</div>
<div *ngIf="encounter.related_resources | filter:'source_resource_type':'Device' as devices" class="col-12 mt-2 mb-2">
<div *ngIf="encounter?.related_resources?.Device as devices" class="col-12 mt-2 mb-2">
<strong>Device:</strong>
<ul>
<li routerLink="/source/{{device?.source_id}}/resource/{{device?.source_resource_id}}" *ngFor="let device of devices">
{{device | fhirPath: "Device.code.text"}}
{{device.model}}
</li>
</ul>
</div>

View File

@ -1,52 +1,137 @@
import {Component, Input, OnInit} from '@angular/core';
import {ResourceFhir} from '../../models/fasten/resource_fhir';
import {CareTeamModel} from '../../../lib/models/resources/care-team-model';
import {PractitionerModel} from '../../../lib/models/resources/practitioner-model';
import {EncounterModel} from '../../../lib/models/resources/encounter-model';
import {fhirModelFactory} from '../../../lib/models/factory';
import {fhirVersions, ResourceType} from '../../../lib/models/constants';
import {MedicationModel} from '../../../lib/models/resources/medication-model';
import {ProcedureModel} from '../../../lib/models/resources/procedure-model';
import {DeviceModel} from '../../../lib/models/resources/device-model';
import {DiagnosticReportModel} from '../../../lib/models/resources/diagnostic-report-model';
import {FastenDisplayModel} from '../../../lib/models/fasten/fasten-display-model';
@Component({
selector: 'app-report-medical-history-condition',
templateUrl: './report-medical-history-condition.component.html',
styleUrls: ['./report-medical-history-condition.component.scss']
})
export class ReportMedicalHistoryConditionComponent implements OnInit {
@Input() condition: ResourceFhir
/*
* conditionGroup is either a Condition or Composite object
*
* Condition
related_resources
Encounter1
Location
Encounter2
Observation
Observation
Location
*
* or
* Composite
related_resources
Condition2
Condition
related_resources
Encounter1
Location
Encounter2
Observation
Observation
Location
careTeams: {[careTeamId: string]: ResourceFhir} = {}
practitioners: {[practitionerId: string]: ResourceFhir} = {}
encounters: {[encounterId: string]: ResourceFhir} = {}
*
* */
@Input() conditionGroup: ResourceFhir
conditionDisplayModel: FastenDisplayModel
//lookup table for all resources
resourcesLookup: {[name:string]: FastenDisplayModel} = {}
involvedInCare: {displayName: string, role?: string, email?: string}[] = []
encounters: EncounterModel[] = []
// medications: {[encounterResourceId: string]: MedicationModel[]} = {}
// procedures: {[encounterResourceId: string]: ProcedureModel[]} = {}
// diagnosticReports: {[encounterResourceId: string]: DiagnosticReportModel[]} = {}
// device: {[encounterResourceId: string]: DeviceModel[]} = {}
constructor() { }
ngOnInit(): void {
for(let resource of this.condition.related_resources){
this.recExtractResources(resource)
}
//add resources to the lookup table, ensure uniqueness.
this.conditionDisplayModel = this.recExtractResources(this.conditionGroup)
// console.log("EXTRACTED CARETEAM", this.careTeams)
// console.log("EXTRACTED practitioners", this.practitioners)
// console.log("EXTRACTED encounters", this.encounters)
//loop though all resources, process display data
for(let resourceId in this.resourcesLookup){
let resource = this.resourcesLookup[resourceId]
switch(resource.source_resource_type){
case ResourceType.CareTeam:
for(let participant of (resource as CareTeamModel).participants){
this.involvedInCare.push({
displayName: participant.display,
role: participant.role
})
}
break
case ResourceType.Practitioner:
this.involvedInCare.push({
displayName: `${(resource as PractitionerModel).name?.family }, ${(resource as PractitionerModel).name?.given}`,
role: `${(resource as PractitionerModel).name?.prefix || (resource as PractitionerModel).name?.suffix}`
})
break
case ResourceType.Encounter:
this.encounters.push(resource as EncounterModel)
break
}
}
}
recExtractResources(resource: ResourceFhir){
if(resource.source_resource_type == "CareTeam"){
this.careTeams[this.genResourceId(resource)] = resource
} else if (resource.source_resource_type == "Practitioner"){
this.practitioners[this.genResourceId(resource)] = resource
} else if (resource.source_resource_type == "Encounter"){
this.encounters[this.genResourceId(resource)] = resource
/*
This function flattens all resources
*/
recExtractResources(resource: ResourceFhir): FastenDisplayModel{
let resourceId = this.genResourceId(resource)
let resourceDisplayModel: FastenDisplayModel = this.resourcesLookup[resourceId]
//ensure display model is populated
if(!resourceDisplayModel){
try{
resourceDisplayModel = fhirModelFactory(resource.source_resource_type as ResourceType, resource)
this.resourcesLookup[resourceId] = resourceDisplayModel
}catch(e){
console.error(e) //failed to parse a model
return null
}
}
if(!resource.related_resources){
return
}
for(let relatedResource of resource.related_resources){
this.recExtractResources(relatedResource)
return resourceDisplayModel
} else {
for(let relatedResource of resource.related_resources){
resourceDisplayModel.related_resources[relatedResource.source_resource_type] = resourceDisplayModel.related_resources[relatedResource.source_resource_type] || []
let relatedResourceDisplayModel = this.recExtractResources(relatedResource)
if(relatedResourceDisplayModel){
resourceDisplayModel.related_resources[relatedResource.source_resource_type].push(relatedResourceDisplayModel)
}
}
}
return resourceDisplayModel
}
genResourceId(relatedResource: ResourceFhir): string {
return `${relatedResource.source_id}/${relatedResource.source_resource_type}/${relatedResource.source_resource_id}`
return `/source/${relatedResource.source_id}/resource/${relatedResource.source_resource_type}/${relatedResource.source_resource_id}`
}
}

View File

@ -3,9 +3,53 @@
<button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross click')"><span aria-hidden="true">×</span></button>
</div>
<div class="modal-body">
<tree-root [nodes]="nodes" [options]="options" (moveNode)="onResourceMoved($event)"></tree-root>
<div class="row">
<div class="col-12">
<div class="alert alert-info" role="alert">
<strong>Create Group</strong> If you have Conditions & Encounters from multiple healthcare providers that are related,
you can group them together by using the checkboxes below. You can also provide a new name for this new group.
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">Group Title</span>
</div>
<input type="text" class="form-control" placeholder="provide a name for your new group" [(ngModel)]="compositionTitle" [disabled]="(selectedResources | keyvalue)?.length == 0" >
</div>
</div>
</div>
<tree-root [nodes]="nodes" [options]="options">
<ng-template #treeNodeWrapperTemplate let-node let-index="index">
<div class="node-wrapper" [style.padding-left]="node.getNodePadding()">
<tree-node-checkbox *ngIf="node.data.show_checkbox"
(click)="onResourceCheckboxClick($event, node)"
[node]="node"></tree-node-checkbox>
<tree-node-expander [node]="node"></tree-node-expander>
<div class="node-content-wrapper"
[class.node-content-wrapper-active]="node.isActive"
[class.node-content-wrapper-focused]="node.isFocused"
(click)="node.mouseAction('click', $event)"
(dblclick)="node.mouseAction('dblClick', $event)"
(contextmenu)="node.mouseAction('contextMenu', $event)"
(treeDrop)="node.onDrop($event)"
[treeAllowDrop]="node.allowDrop"
[treeDrag]="node"
[treeDragEnabled]="node.allowDrag()">
<tree-node-content [node]="node" [index]="index"></tree-node-content>
</div>
</div>
</ng-template>
</tree-root>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-indigo" [disabled]="!compositionTitle" (click)="onMergeResourcesClick()">Merge Selected</button>
<button type="button" class="btn btn-outline-dark" (click)="activeModal.close('Close click')">Close</button>
</div>

View File

@ -3,14 +3,16 @@ import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap';
import {ResourceFhir} from '../../models/fasten/resource_fhir';
import {FastenApiService} from '../../services/fasten-api.service';
import * as fhirpath from 'fhirpath';
import {ITreeOptions} from '@circlon/angular-tree-component';
class RelatedNode {
name: string
resourceType: string
resourceId: string
sourceId: string
draggable: boolean
source_resource_type: string
source_resource_id: string
source_id: string
children: RelatedNode[]
show_checkbox: boolean
resource: ResourceFhir
}
@Component({
@ -21,9 +23,8 @@ class RelatedNode {
export class ReportMedicalHistoryEditorComponent implements OnInit {
@Input() conditions: ResourceFhir[] = []
@Input() encounters: ResourceFhir[] = []
assignedEncounters: {[name: string]: ResourceFhir} = {}
resourceLookup: {[name: string]: ResourceFhir} = {}
compositionTitle: string = ""
nodes = [
// {
@ -49,70 +50,62 @@ export class ReportMedicalHistoryEditorComponent implements OnInit {
// ]
// }
];
options = {
allowDrag: (node) => {return node.data.draggable},
allowDrop: (element, { parent, index }) => {
// return true / false based on element, to.parent, to.index. e.g.
return parent.data.resourceType == "Condition";
},
options: ITreeOptions = {
allowDrag: false,
allowDrop: false,
}
selectedResources:{ [id:string]: ResourceFhir} = {}
constructor(
public activeModal: NgbActiveModal,
private fastenApi: FastenApiService,
) { }
ngOnInit(): void {
console.log("ngOnInit STATUS", this.conditions)
this.nodes = this.generateNodes(this.conditions)
}
onResourceMoved($event) {
onResourceCheckboxClick($event, node:{data:RelatedNode}){
let key = `${node.data.source_id}/${node.data.source_resource_type}/${node.data.source_resource_id}`
if($event.target.checked){
this.selectedResources[key] = node.data.resource
if(!this.compositionTitle){
this.compositionTitle = node.data.resource.sort_title
}
} else {
//delete this key (unselected)
delete this.selectedResources[key]
}
console.log("selected resources", this.selectedResources)
}
this.fastenApi.replaceResourceAssociation({
onMergeResourcesClick() {
source_id: $event.to.parent.sourceId,
source_resource_type: $event.to.parent.resourceType,
source_resource_id: $event.to.parent.resourceId,
let resources: ResourceFhir[] = []
for(let key in this.selectedResources){
resources.push(this.selectedResources[key])
}
new_related_source_id: $event.node.sourceId,
new_related_source_resource_type: $event.node.resourceType,
new_related_source_resource_id: $event.node.resourceId,
}).subscribe(results => {
this.fastenApi.createResourceComposition(this.compositionTitle, resources).subscribe(results => {
console.log(results)
})
this.activeModal.close()
},(err) => {})
}
generateNodes(resouceFhirList: ResourceFhir[]): RelatedNode[] {
let relatedNodes = resouceFhirList.map((resourceFhir) => { return this.recGenerateNode(resourceFhir) })
//create an unassigned encounters "condition"
if(this.encounters.length > 0){
let unassignedCondition = {
name: "[Unassigned Encounters]",
resourceType: "Condition",
resourceId: "UNASSIGNED",
sourceId: "UNASSIGNED",
draggable: false,
children: []
}
for(let encounter of this.encounters){
let encounterId = `${encounter.source_id}/${encounter.source_resource_type}/${encounter.source_resource_id}`
if(!this.assignedEncounters[encounterId]){
this.assignedEncounters[encounterId] = encounter
unassignedCondition.children.push(this.recGenerateNode(encounter))
for(let relatedNode of relatedNodes){
if(relatedNode.source_id == 'UNASSIGNED' && relatedNode.source_resource_type == 'Condition' && relatedNode.source_resource_id == 'UNASSIGNED'){
//this is a placeholder for the Unassigned resources. This resource cannot be merged, but all child resources can be, so lets set them to true
for(let unassignedEncounters of relatedNode.children){
unassignedEncounters.show_checkbox = true
}
}
if(unassignedCondition.children.length > 0){
//only add the unassigned condition block if the subchildren list is populated.
relatedNodes.push(unassignedCondition)
} else {
relatedNode.show_checkbox = true
}
}
@ -122,39 +115,48 @@ export class ReportMedicalHistoryEditorComponent implements OnInit {
recGenerateNode(resourceFhir: ResourceFhir): RelatedNode {
let relatedNode = {
sourceId: resourceFhir.source_id,
name: `[${resourceFhir.source_resource_type}/${resourceFhir.source_resource_id}]`,
resourceId: resourceFhir.source_resource_id,
resourceType: resourceFhir.source_resource_type,
draggable: resourceFhir.source_resource_type == "Encounter" || resourceFhir.source_resource_type == "Condition",
show_checkbox: false,
source_id: resourceFhir.source_id,
name: `[${resourceFhir.source_resource_type}/${resourceFhir.source_resource_id.length > 10 ? resourceFhir.source_resource_id.substring(0, 10)+ '...' : resourceFhir.source_resource_id}] `,
source_resource_id: resourceFhir.source_resource_id,
source_resource_type: resourceFhir.source_resource_type,
children: [],
resource: resourceFhir
}
switch (resourceFhir.source_resource_type) {
case "Condition":
relatedNode.name += ` ${fhirpath.evaluate(resourceFhir.resource_raw, "Condition.onsetPeriod.start")} ${fhirpath.evaluate(resourceFhir.resource_raw, "Condition.code.text.first()")}`
relatedNode.name += resourceFhir.sort_title || ` ${fhirpath.evaluate(resourceFhir.resource_raw, "Condition.onsetPeriod.start")} ${fhirpath.evaluate(resourceFhir.resource_raw, "Condition.code.text.first()")}`
if(resourceFhir.sort_date){
relatedNode.name += ` - ${new Date(resourceFhir.sort_date).toLocaleDateString("en-US")}`
}
break
case "Encounter":
relatedNode.name += ` ${fhirpath.evaluate(resourceFhir.resource_raw, "Encounter.period.start")} ${fhirpath.evaluate(resourceFhir.resource_raw, "Encounter.location.first().location.display")}`
relatedNode.name += resourceFhir.sort_title ||` ${fhirpath.evaluate(resourceFhir.resource_raw, "Encounter.period.start")} ${fhirpath.evaluate(resourceFhir.resource_raw, "Encounter.location.first().location.display")}`
if(resourceFhir.sort_date){
relatedNode.name += ` - ${new Date(resourceFhir.sort_date).toLocaleDateString("en-US")}`
}
break
case "CareTeam":
relatedNode.name += ` ${fhirpath.evaluate(resourceFhir.resource_raw, "CareTeam.participant.member.display")}`
relatedNode.name += resourceFhir.sort_title || ` ${fhirpath.evaluate(resourceFhir.resource_raw, "CareTeam.participant.member.display")}`
break
case "Location":
relatedNode.name += ` ${fhirpath.evaluate(resourceFhir.resource_raw, "Location.name")}`
relatedNode.name += resourceFhir.sort_title || ` ${fhirpath.evaluate(resourceFhir.resource_raw, "Location.name")}`
break
case "Organization":
relatedNode.name += ` ${fhirpath.evaluate(resourceFhir.resource_raw, "Organization.name")}`
relatedNode.name += resourceFhir.sort_title || ` ${fhirpath.evaluate(resourceFhir.resource_raw, "Organization.name")}`
break
case "Practitioner":
relatedNode.name += ` ${fhirpath.evaluate(resourceFhir.resource_raw, "Practitioner.name.family")}`
relatedNode.name += resourceFhir.sort_title || ` ${fhirpath.evaluate(resourceFhir.resource_raw, "Practitioner.name.family")}`
break
case "MedicationRequest":
relatedNode.name += ` ${fhirpath.evaluate(resourceFhir.resource_raw, "MedicationRequest.medicationReference.display")}`
relatedNode.name += resourceFhir.sort_title || ` ${fhirpath.evaluate(resourceFhir.resource_raw, "MedicationRequest.medicationReference.display")}`
break
default:
relatedNode.name += resourceFhir.sort_title
}
this.assignedEncounters[`${resourceFhir.source_id}/${resourceFhir.source_resource_type}/${resourceFhir.source_resource_id}`] = resourceFhir
this.resourceLookup[`${resourceFhir.source_id}/${resourceFhir.source_resource_type}/${resourceFhir.source_resource_id}`] = resourceFhir
if(!resourceFhir.related_resources){
return relatedNode

View File

@ -43,6 +43,7 @@ import { ReportMedicalHistoryConditionComponent } from './report-medical-history
import { ReportLabsObservationComponent } from './report-labs-observation/report-labs-observation.component';
import { ChartsModule } from 'ng2-charts';
import { LoadingSpinnerComponent } from './loading-spinner/loading-spinner.component';
import {FormsModule} from '@angular/forms';
@NgModule({
imports: [
@ -50,6 +51,7 @@ import { LoadingSpinnerComponent } from './loading-spinner/loading-spinner.compo
BrowserModule,
NgxDatatableModule,
NgbModule,
FormsModule,
MomentModule,
TreeModule,
ChartsModule

View File

@ -8,6 +8,9 @@ export class ResourceFhir {
resource_raw: IResourceRaw
related_resources?: ResourceFhir[] = []
sort_title: string = ""
sort_date: Date = null
constructor(object?: any) {
return Object.assign(this, object)
}

View File

@ -29,7 +29,7 @@
</div>
<!-- Condition List -->
<app-report-medical-history-condition *ngFor="let condition of conditions; let i = index" [condition]="condition"></app-report-medical-history-condition>
<app-report-medical-history-condition *ngFor="let condition of conditions; let i = index" [conditionGroup]="condition"></app-report-medical-history-condition>
</ng-template>
<ng-template #emptyReport>

View File

@ -52,7 +52,7 @@ export class MedicalHistoryComponent implements OnInit {
},
source_id: 'UNASSIGNED',
source_resource_id: 'UNASSIGNED',
source_resource_type: 'UNASSIGNED',
source_resource_type: 'Condition',
related_resources: this.unassigned_encounters
} as any)
}
@ -65,9 +65,10 @@ export class MedicalHistoryComponent implements OnInit {
}
openEditorRelated(): void {
const modalRef = this.modalService.open(ReportMedicalHistoryEditorComponent);
const modalRef = this.modalService.open(ReportMedicalHistoryEditorComponent, {
size: 'xl',
});
modalRef.componentInstance.conditions = this.conditions;
modalRef.componentInstance.encounters = this.unassigned_encounters;
}

View File

@ -3,6 +3,7 @@ import {FastenApiService} from '../../services/fasten-api.service';
import {ActivatedRoute, Router} from '@angular/router';
import {ResourceFhir} from '../../models/fasten/resource_fhir';
import {fhirModelFactory} from '../../../lib/models/factory';
import {ResourceType} from '../../../lib/models/constants';
@Component({
selector: 'app-resource-detail',
@ -29,7 +30,7 @@ export class ResourceDetailComponent implements OnInit {
this.sourceName = "unknown" //TODO popualte this
try{
let parsed = fhirModelFactory(resourceFhir["source_resource_type"], resourceFhir["resource_raw"])
let parsed = fhirModelFactory(resourceFhir["source_resource_type"] as ResourceType, resourceFhir)
console.log("Successfully parsed model", parsed)
} catch (e) {
console.log("FAILED TO PARSE", resourceFhir)

View File

@ -148,8 +148,12 @@ export class FastenApiService {
);
}
replaceResourceAssociation(resourceAssociation: ResourceAssociation): Observable<any> {
return this._httpClient.post<any>(`${GetEndpointAbsolutePath(globalThis.location, environment.fasten_api_endpoint_base)}/secure/resource/association`, resourceAssociation)
//this method allows a user to manually group related FHIR resources together (conditions, encounters, etc).
createResourceComposition(title: string, resources: ResourceFhir[]){
return this._httpClient.post<any>(`${GetEndpointAbsolutePath(globalThis.location, environment.fasten_api_endpoint_base)}/secure/resource/composition`, {
"resources": resources,
"title": title,
})
.pipe(
map((response: ResponseWrapper) => {
console.log("RESPONSE", response)

View File

@ -1,4 +1,37 @@
export enum ResourceType {
AdverseEvent = "AdverseEvent",
AllergyIntolerance = "AllergyIntolerance",
Appointment = "Appointment",
Binary = "Binary",
CarePlan = "CarePlan",
CareTeam = "CareTeam",
Condition = "Condition",
Composition = "Composition",
Coverage = "Coverage",
Device = "Device",
DiagnosticReport = "DiagnosticReport",
DocumentReference = "DocumentReference",
Encounter = "Encounter",
Goal = "Goal",
Immunization = "Immunization",
Location = "Location",
Medication = "Medication",
MedicationDispense = "MedicationDispense",
MedicationRequest = "MedicationRequest",
Observation = "Observation",
Organization = "Organization",
Patient = "Patient",
Practitioner = "Practitioner",
PractitionerRole = "PractitionerRole",
Procedure = "Procedure",
Provenance = "Provenance",
RelatedPerson = "RelatedPerson",
ResearchStudy = "ResearchStudy",
ServiceRequest = "ServiceRequest",
Specimen = "Specimen",
}
export enum fhirVersions{
DSTU2 = "DSTU2",

View File

@ -1,4 +1,4 @@
import {fhirVersions} from './constants';
import {fhirVersions, ResourceType} from './constants';
import {AdverseEventModel} from './resources/adverse-event-model';
import {AllergyIntoleranceModel} from './resources/allergy-intolerance-model';
import {AppointmentModel} from './resources/appointment-model';
@ -22,126 +22,144 @@ import {PractitionerRoleModel} from './resources/practitioner-role-model';
import {ProcedureModel} from './resources/procedure-model';
import {RelatedPersonModel} from './resources/related-person-model';
import {ResearchStudyModel} from './resources/research-study-model';
import {BinaryModel} from './resources/binary-model';
import {FastenOptions} from './fasten/fasten-options';
import {FastenDisplayModel} from './fasten/fasten-display-model';
import {MedicationRequestModel} from './resources/medication-request-model';
export function fhirModelFactory(modelName: string, fhirResource: any, fhirVersion: fhirVersions = fhirVersions.R4):any {
switch (modelName) {
case "AdverseEvent": {
return new AdverseEventModel(fhirResource, fhirVersion)
}
case "AllergyIntolerance": {
return new AllergyIntoleranceModel(fhirResource, fhirVersion)
}
case "Appointment": {
return new AppointmentModel(fhirResource, fhirVersion)
}
case "Binary": {
return new BinaryModel(fhirResource, fhirVersion)
}
case "CarePlan": {
return new CarePlanModel(fhirResource, fhirVersion)
}
case "CareTeam": {
return new CareTeamModel(fhirResource, fhirVersion)
}
// case "Claim": {
// return new ClaimModel(fhirResource, fhirVersion)
// }
// case "ClaimResponse": {
// return new ClaimResponseModel(fhirResource, fhirVersion)
// }
case "Condition": {
return new ConditionModel(fhirResource, fhirVersion)
}
// case "Coverage": {
// return new CoverageModel(fhirResource, fhirVersion)
// }
case "Device": {
return new DeviceModel(fhirResource, fhirVersion)
}
case "DiagnosticReport": {
return new DiagnosticReportModel(fhirResource, fhirVersion)
}
case "DocumentReference": {
return new DocumentReferenceModel(fhirResource, fhirVersion)
}
case "Encounter": {
return new EncounterModel(fhirResource, fhirVersion)
}
// case "ExplanationOfBenefit": {
// return new ExplanationOfBenefitModel(fhirResource, fhirVersion)
// }
// case "FamilyMemberHistory": {
// return new FamilyMemberHistoryModel(fhirResource, fhirVersion)
// }
case "Goal": {
return new GoalModel(fhirResource, fhirVersion)
}
case "Immunization": {
return new ImmunizationModel(fhirResource, fhirVersion)
}
case "Location": {
return new LocationModel(fhirResource, fhirVersion)
}
case "Medication": {
return new MedicationModel(fhirResource, fhirVersion)
}
// case "MedicationAdministration": {
// return new MedicationAdministrationModel(fhirResource, fhirVersion)
// }
case "MedicationDispense": {
return new MedicationDispenseModel(fhirResource, fhirVersion)
}
// case "MedicationKnowledge": {
// return new MedicationKnowledgeModel(fhirResource, fhirVersion)
// }
// case "MedicationOrder": {
// return new MedicationOrderModel(fhirResource, fhirVersion)
// }
// case "MedicationRequest": {
// return new MedicationRequestModel(fhirResource, fhirVersion)
// }
// case "MedicationStatement": {
// return new MedicationStatementModel(fhirResource, fhirVersion)
// }
case "Observation": {
return new ObservationModel(fhirResource, fhirVersion)
}
case "Organization": {
return new OrganizationModel(fhirResource, fhirVersion)
}
case "Patient": {
return new PatientModel(fhirResource, fhirVersion)
}
case "Practitioner": {
return new PractitionerModel(fhirResource, fhirVersion)
}
case "PractitionerRole": {
return new PractitionerRoleModel(fhirResource, fhirVersion)
}
case "Procedure": {
return new ProcedureModel(fhirResource, fhirVersion)
}
// case "Questionnaire": {
// return new QuestionnaireModel(fhirResource, fhirVersion)
// }
// case "QuestionnaireResponse": {
// return new QuestionnaireResponseModel(fhirResource, fhirVersion)
// }
// case "ReferralRequest": {
// return new ReferralRequestModel(fhirResource, fhirVersion)
// }
case "RelatedPerson": {
return new RelatedPersonModel(fhirResource, fhirVersion)
}
case "ResearchStudy": {
return new ResearchStudyModel(fhirResource, fhirVersion)
}
// case "ResourceCategory": {
// return new ResourceCategoryModel(fhirResource, fhirVersion)
// }
default: {
throw new Error("Unknown resource data structure")
}
}
// import {BinaryModel} from './resources/binary-model';
export function fhirModelFactory(modelResourceType: ResourceType, fhirResourceWrapper: any, fhirVersion: fhirVersions = fhirVersions.R4, fastenOptions?: FastenOptions): FastenDisplayModel {
let resourceModel: FastenDisplayModel
switch (modelResourceType) {
case "AdverseEvent":
resourceModel = new AdverseEventModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "AllergyIntolerance":
resourceModel = new AllergyIntoleranceModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "Appointment":
resourceModel = new AppointmentModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
// case "Binary": {
// resourceModel = new BinaryModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
// }
case "CarePlan":
resourceModel = new CarePlanModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "CareTeam":
resourceModel = new CareTeamModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
// case "Claim":
// resourceModel = new ClaimModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
// break
// case "ClaimResponse":
// resourceModel = new ClaimResponseModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
// break
case "Condition":
resourceModel = new ConditionModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "Composition":
resourceModel = new ConditionModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
// case "Coverage":
// resourceModel = new CoverageModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
// break
case "Device":
resourceModel = new DeviceModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "DiagnosticReport":
resourceModel = new DiagnosticReportModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "DocumentReference":
resourceModel = new DocumentReferenceModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "Encounter":
resourceModel = new EncounterModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
// case "ExplanationOfBenefit":
// resourceModel = new ExplanationOfBenefitModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
// break
// case "FamilyMemberHistory":
// resourceModel = new FamilyMemberHistoryModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
//break
case "Goal":
resourceModel = new GoalModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "Immunization":
resourceModel = new ImmunizationModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "Location":
resourceModel = new LocationModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "Medication":
resourceModel = new MedicationModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
// case "MedicationAdministration":
// resourceModel = new MedicationAdministrationModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
// break
case "MedicationDispense":
resourceModel = new MedicationDispenseModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
// case "MedicationKnowledge":
// resourceModel = new MedicationKnowledgeModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
// break
// case "MedicationOrder":
// resourceModel = new MedicationOrderModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
// break
case "MedicationRequest":
resourceModel = new MedicationRequestModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
// case "MedicationStatement":
// resourceModel = new MedicationStatementModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
// break
case "Observation":
resourceModel = new ObservationModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "Organization":
resourceModel = new OrganizationModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "Patient":
resourceModel = new PatientModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "Practitioner":
resourceModel = new PractitionerModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "PractitionerRole":
resourceModel = new PractitionerRoleModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "Procedure":
resourceModel = new ProcedureModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
// case "Questionnaire":
// resourceModel = new QuestionnaireModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
// break
// case "QuestionnaireResponse":
// resourceModel = new QuestionnaireResponseModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
// break
// case "ReferralRequest":
// resourceModel = new ReferralRequestModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
// break
case "RelatedPerson":
resourceModel = new RelatedPersonModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
case "ResearchStudy":
resourceModel = new ResearchStudyModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
break
// case "ResourceCategory":
// resourceModel = new ResourceCategoryModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
// break
default: {
throw new Error("Ignoring Unknown resource data structure:" + modelResourceType)
}
}
//transfer data from wrapper to the display model.
resourceModel.source_resource_id = fhirResourceWrapper.source_resource_id
resourceModel.source_id = fhirResourceWrapper.source_id
resourceModel.sort_title = fhirResourceWrapper.sort_title
resourceModel.sort_date = fhirResourceWrapper.sort_date
return resourceModel
}

View File

@ -0,0 +1,7 @@
import { FastenDisplayModel } from './fasten-display-model';
describe('FastenDisplayModel', () => {
it('should create an instance', () => {
expect(new FastenDisplayModel()).toBeTruthy();
});
});

View File

@ -0,0 +1,14 @@
import {FastenOptions} from './fasten-options';
import {ResourceType} from '../constants';
export class FastenDisplayModel {
source_resource_type: ResourceType | undefined
source_resource_id: string | undefined
source_id: string | undefined
sort_title: string | undefined
sort_date: Date | undefined
related_resources: {[ modelResourceType: string]: FastenDisplayModel[]} = {}
constructor(options?: FastenOptions) {}
}

View File

@ -0,0 +1,7 @@
import { FastenOptions } from './fasten-options';
describe('FastenOptions', () => {
it('should create an instance', () => {
expect(new FastenOptions()).toBeTruthy();
});
});

View File

@ -0,0 +1,2 @@
export class FastenOptions {
}

View File

@ -20,7 +20,7 @@ describe('AdverseEventModel', () => {
// expected.hasEventType = true
expected.date = "2017-01-29T12:34:56+00:00"
expected.seriousness = new CodableConceptModel({ coding: [ Object({ system: 'http://terminology.hl7.org/CodeSystem/adverse-event-seriousness', code: 'Non-serious', display: 'Non-serious' }) ] })
expected.hasSeriousness = true
expected.has_seriousness = true
expected.actuality = 'actual'
expected.event = new CodableConceptModel({
"coding": [
@ -32,7 +32,7 @@ describe('AdverseEventModel', () => {
],
"text": "This was a mild rash on the left forearm"
})
expected.hasEvent = true
expected.has_event = true
expect(new AdverseEventModel(fixture)).toEqual(expected);
});

View File

@ -1,21 +1,25 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class AdverseEventModel {
export class AdverseEventModel extends FastenDisplayModel {
subject: ReferenceModel | undefined
description: string | undefined
eventType: string | undefined
hasEventType: boolean | undefined
event_type: string | undefined
has_event_type: boolean | undefined
date: string | undefined
seriousness: CodableConceptModel | undefined
hasSeriousness: boolean | undefined
has_seriousness: boolean | undefined
actuality: string | undefined
event: CodableConceptModel | undefined
hasEvent: boolean | undefined
has_event: boolean | undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.AdverseEvent
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
@ -25,20 +29,20 @@ export class AdverseEventModel {
this.date = _.get(fhirResource, 'date');
let seriousness = _.get(fhirResource, 'seriousness', [])
this.seriousness = new CodableConceptModel(seriousness);
this.hasSeriousness = hasValue(seriousness);
this.has_seriousness = hasValue(seriousness);
};
stu3DTO(fhirResource:any){
this.description = _.get(fhirResource, 'description');
this.eventType = _.get(fhirResource, 'type', []);
this.hasEventType = hasValue(this.eventType);
this.event_type = _.get(fhirResource, 'type', []);
this.has_event_type = hasValue(this.event_type);
};
r4DTO(fhirResource:any){
this.actuality = _.get(fhirResource, 'actuality');
let event = _.get(fhirResource, 'event', [])
this.event = new CodableConceptModel(event);
this.hasEvent = hasValue(event);
this.has_event = hasValue(event);
};
resourceDTO(fhirResource: any, fhirVersion: fhirVersions){

View File

@ -13,8 +13,8 @@ describe('AllergyIntoleranceModel', () => {
let expected = new AllergyIntoleranceModel({})
expected.title = 'Cashew nuts'
expected.status = 'Confirmed'
expected.recordedDate = '2014-10-09T14:58:00+11:00'
expected.substanceCoding = [
expected.recorded_date = '2014-10-09T14:58:00+11:00'
expected.substance_coding = [
{
"system": "http://www.nlm.nih.gov/research/umls/rxnorm",
"code": "1160593",
@ -35,8 +35,8 @@ describe('AllergyIntoleranceModel', () => {
let expected = new AllergyIntoleranceModel({})
expected.title = 'Penicillin G'
expected.status = 'Unconfirmed'
expected.recordedDate = '2010-03-01'
expected.substanceCoding = []
expected.recorded_date = '2010-03-01'
expected.substance_coding = []
// expected.asserter = { reference: 'Patient/example' }
// expected.note = [{ text: 'The criticality is high becasue of the observed anaphylactic reaction when challenged with cashew extract.' }]
// expected.type = ''
@ -51,8 +51,8 @@ describe('AllergyIntoleranceModel', () => {
let expected = new AllergyIntoleranceModel({})
expected.title = 'No Known Allergy (situation)'
expected.status = 'Confirmed'
expected.recordedDate = '2015-08-06T15:37:31-06:00'
expected.substanceCoding = []
expected.recorded_date = '2015-08-06T15:37:31-06:00'
expected.substance_coding = []
// expected.asserter = { reference: 'Patient/example' }
// expected.note = [{ text: 'The criticality is high becasue of the observed anaphylactic reaction when challenged with cashew extract.' }]
// expected.type = 'allergy'
@ -69,8 +69,8 @@ describe('AllergyIntoleranceModel', () => {
let expected = new AllergyIntoleranceModel({})
expected.title = "ALLERGENIC EXTRACT, PENICILLIN"
expected.status = 'unconfirmed'
expected.recordedDate = "2010-03-01"
expected.substanceCoding = [
expected.recorded_date = "2010-03-01"
expected.substance_coding = [
{
"system": "http://www.nlm.nih.gov/research/umls/rxnorm",
"code": "314422",
@ -91,8 +91,8 @@ describe('AllergyIntoleranceModel', () => {
let expected = new AllergyIntoleranceModel({})
expected.title = 'PENICILLINS'
expected.status = 'confirmed'
expected.recordedDate = '2008-02-22T06:00:00.000Z'
expected.substanceCoding = [
expected.recorded_date = '2008-02-22T06:00:00.000Z'
expected.substance_coding = [
{
"system": 'http://hl7.org/fhir/ndfrt' ,
"code": 'N0000005840' ,
@ -116,8 +116,8 @@ describe('AllergyIntoleranceModel', () => {
let expected = new AllergyIntoleranceModel({})
expected.title = 'Cashew nuts'
expected.status = 'confirmed'
expected.recordedDate = '2014-10-09T14:58:00+11:00'
expected.substanceCoding = [
expected.recorded_date = '2014-10-09T14:58:00+11:00'
expected.substance_coding = [
{
"system": "http://www.nlm.nih.gov/research/umls/rxnorm",
"code": "1160593",
@ -138,8 +138,8 @@ describe('AllergyIntoleranceModel', () => {
let expected = new AllergyIntoleranceModel({})
expected.title = 'Fish - dietary (substance)'
expected.status = 'confirmed'
expected.recordedDate = '2015-08-06T15:37:31-06:00'
expected.substanceCoding = []
expected.recorded_date = '2015-08-06T15:37:31-06:00'
expected.substance_coding = []
// expected.asserter = {reference: 'Patient/example'}
// expected.note = []
// expected.type = 'allergy'

View File

@ -1,14 +1,16 @@
import {CodingModel} from '../datatypes/coding-model';
import * as _ from "lodash";
import {fhirVersions} from '../constants'
import {fhirVersions, ResourceType} from '../constants'
import {ReferenceModel} from '../datatypes/reference-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class AllergyIntoleranceModel {
export class AllergyIntoleranceModel extends FastenDisplayModel {
title: string | undefined
status: string | undefined
recordedDate: string | undefined
substanceCoding: CodingModel[] | undefined
recorded_date: string | undefined
substance_coding: CodingModel[] | undefined
// reaction: string | undefined
asserter: ReferenceModel | undefined
note: { text: string }[] | undefined
@ -16,7 +18,9 @@ export class AllergyIntoleranceModel {
category: string[] | undefined
patient: ReferenceModel | undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.AllergyIntolerance
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
@ -34,8 +38,8 @@ export class AllergyIntoleranceModel {
_.get(fhirResource, 'substance.coding[0].display') ||
_.get(fhirResource, 'substance.text', '');
this.status = _.get(fhirResource, 'status', '');
this.recordedDate = _.get(fhirResource, 'recordedDate');
this.substanceCoding = _.get(fhirResource, 'substance.coding', []);
this.recorded_date = _.get(fhirResource, 'recordedDate');
this.substance_coding = _.get(fhirResource, 'substance.coding', []);
this.asserter = _.get(fhirResource, 'reporter');
this.note = []
this.category = _.get(fhirResource, 'category') ? [_.get(fhirResource, 'category')] : [];
@ -48,11 +52,11 @@ export class AllergyIntoleranceModel {
stu3DTO(fhirResource: any) {
this.title = _.get(fhirResource, 'code.coding.0.display');
this.status = _.get(fhirResource, 'verificationStatus');
this.recordedDate = _.get(fhirResource, 'assertedDate');
this.recorded_date = _.get(fhirResource, 'assertedDate');
let substanceCoding = _.get(fhirResource, 'reaction', []).filter((item: any) =>
_.get(item, 'substance.coding'),
);
this.substanceCoding = _.get(substanceCoding, '0.substance.coding', []);
this.substance_coding = _.get(substanceCoding, '0.substance.coding', []);
this.note = _.get(fhirResource, 'note');
};
@ -60,11 +64,11 @@ export class AllergyIntoleranceModel {
r4DTO(fhirResource: any) {
this.title = _.get(fhirResource, 'code.coding.0.display');
this.status = _.get(fhirResource, 'verificationStatus.coding[0].display');
this.recordedDate = _.get(fhirResource, 'recordedDate');
this.recorded_date = _.get(fhirResource, 'recordedDate');
let substanceCoding = _.get(fhirResource, 'reaction', []).filter((item: any) =>
_.get(item, 'substance.coding'),
);
this.substanceCoding = _.get(substanceCoding, '0.substance.coding', []);
this.substance_coding = _.get(substanceCoding, '0.substance.coding', []);
this.note = _.get(fhirResource, 'note');
};

View File

@ -1,25 +1,30 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class AppointmentModel extends FastenDisplayModel {
export class AppointmentModel {
description: string|undefined
status: string|undefined
start: string|undefined
typeCoding: string|undefined
type_coding: string|undefined
comment: string|undefined
participant: string|undefined
participantPatient: string|undefined
participantPractitioner: string|undefined
participantLocation: string|undefined
minutesDuration: string|undefined
participant_patient: string|undefined
participant_practitioner: string|undefined
participant_location: string|undefined
minutes_duration: string|undefined
reason: string|undefined
cancelationReason: string|undefined
serviceCategory: string|undefined
cancelation_reason: string|undefined
service_category: string|undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.Appointment
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
@ -28,7 +33,7 @@ export class AppointmentModel {
this.description = _.get(fhirResource, 'description');
this.status = _.get(fhirResource, 'status');
this.start = _.get(fhirResource, 'start');
this.typeCoding = _.get(fhirResource, 'type.coding');
this.type_coding = _.get(fhirResource, 'type.coding');
this.comment = _.get(fhirResource, 'comment');
this.participant = _.get(fhirResource, 'participant');
// const {
@ -36,20 +41,20 @@ export class AppointmentModel {
// participantPractitioner,
// participantLocation,
// } = prepareParticipantData(participant);
this.minutesDuration = _.get(fhirResource, 'minutesDuration');
this.minutes_duration = _.get(fhirResource, 'minutesDuration');
this.reason = _.get(fhirResource, 'reason', []);
};
stu3DTO(fhirResource:any) {
this.serviceCategory = _.get(fhirResource, 'serviceCategory', []);
this.typeCoding = _.get(fhirResource, 'appointmentType.coding');
this.service_category = _.get(fhirResource, 'serviceCategory', []);
this.type_coding = _.get(fhirResource, 'appointmentType.coding');
};
r4DTO(fhirResource:any) {
this.reason = _.get(fhirResource, 'reasonCode', []);
this.cancelationReason = _.get(fhirResource, 'cancelationReason', []);
this.serviceCategory = _.get(fhirResource, 'serviceCategory', []);
this.typeCoding = _.get(fhirResource, 'appointmentType.coding');
this.cancelation_reason = _.get(fhirResource, 'cancelationReason', []);
this.service_category = _.get(fhirResource, 'serviceCategory', []);
this.type_coding = _.get(fhirResource, 'appointmentType.coding');
};
resourceDTO(fhirResource:any, fhirVersion:fhirVersions){

View File

@ -14,9 +14,9 @@ describe('CarePlanModel', () => {
// expected.expiry = "completed"
// expected.category = "completed"
expected.goals = [ { reference: '#goal' } ]
expected.hasGoals = true
expected.has_goals = true
expected.addresses = [ { reference: "Condition/f201", display: '?????' } ]
expected.hasAddresses = true
expected.has_addresses = true
expected.activity = [
{
title: '64915003',
@ -26,10 +26,10 @@ describe('CarePlanModel', () => {
]
}
]
expected.hasActivity = true
expected.has_activity = true
expected.subject = {"reference": "Patient/f001", display: 'P. van de Heuvel'}
expected.periodStart = "2011-06-26"
expected.periodEnd = "2011-06-27"
expected.period_start = "2011-06-26"
expected.period_end = "2011-06-27"
// expected.basedOn
// expected.title = 'Cashew nuts'
@ -58,19 +58,19 @@ describe('CarePlanModel', () => {
// expected.expiry = "completed"
// expected.category = "completed"
expected.goals = [ { reference: '#goal' } ]
expected.hasGoals = true
expected.has_goals = true
expected.addresses = [ { reference: '#p1', "display": "pregnancy" } ]
expected.hasAddresses = true
expected.has_addresses = true
expected.activity = [
{ title: undefined, hasCategories: false, categories: [ ] },
{ title: 'First Antenatal encounter', hasCategories: true, categories: [ { system: 'http://example.org/mySystem', code: '1an' } ] },
{ title: 'Follow-up Antenatal encounter', hasCategories: true, categories: [ { system: 'http://example.org/mySystem', code: 'an' } ] },
{ title: 'Delivery', hasCategories: true, categories: [ { system: 'http://example.org/mySystem', code: 'del' } ] }
]
expected.hasActivity = true
expected.has_activity = true
expected.subject = {display: 'Eve Everywoman', reference: "Patient/1"}
expected.periodStart = '2013-01-01'
expected.periodEnd = '2013-10-01'
expected.period_start = '2013-01-01'
expected.period_end = '2013-10-01'
expect(new CarePlanModel(fixture)).toEqual(expected);
});
@ -82,20 +82,20 @@ describe('CarePlanModel', () => {
// expected.expiry = "completed"
expected.category = [{ text: 'Weight management plan' }]
expected.goals = [ { reference: 'Goal/example' } ]
expected.hasGoals = true
expected.has_goals = true
expected.addresses = [
{ reference: '#p1', display: 'obesity' }
]
expected.hasAddresses = true
expected.has_addresses = true
expected.activity = [
{ title: '3141-9', hasCategories: true, categories: [ Object({ system: 'http://loinc.org', code: '3141-9', display: 'Weight Measured' }), Object({ system: 'http://snomed.info/sct', code: '27113001', display: 'Body weight' }) ] }
]
expected.hasActivity = true
expected.has_activity = true
expected.description = 'Manage obesity and weight loss'
expected.subject = {display: 'Peter James Chalmers', reference: 'Patient/example'}
// expected.periodStart = '2013-01-01'
expected.periodEnd = '2017-06-01'
expected.period_end = '2017-06-01'
expected.author = { reference: 'Practitioner/example', display: 'Dr Adam Careful' }
expect(new CarePlanModel(fixture)).toEqual(expected);
});

View File

@ -1,29 +1,33 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {ReferenceModel} from '../datatypes/reference-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class CarePlanModel {
export class CarePlanModel extends FastenDisplayModel {
status: string | undefined
expiry: string | undefined
category: any[] | undefined
hasCategory: boolean | undefined
has_category: boolean | undefined
goals: ReferenceModel[] | undefined
hasGoals: boolean | undefined
has_goals: boolean | undefined
addresses: ReferenceModel[] | undefined
hasAddresses: boolean | undefined
has_addresses: boolean | undefined
activity: any
hasActivity: boolean | undefined
basedOn: string | undefined
partOf: string | undefined
has_activity: boolean | undefined
based_on: string | undefined
part_of: string | undefined
intent: string | undefined
description: string | undefined
subject: ReferenceModel | undefined
periodStart: string | undefined
periodEnd: string | undefined
period_start: string | undefined
period_end: string | undefined
author: ReferenceModel | undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.CarePlan
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
@ -31,22 +35,22 @@ export class CarePlanModel {
this.status = _.get(fhirResource, 'status', '');
this.expiry = _.get(fhirResource, 'expiry');
this.category = _.get(fhirResource, 'category');
this.hasCategory = Array.isArray(_.get(fhirResource, 'category.0.coding'));
this.has_category = Array.isArray(_.get(fhirResource, 'category.0.coding'));
this.goals = _.get(fhirResource, 'goal');
this.hasGoals = Array.isArray(this.goals);
this.has_goals = Array.isArray(this.goals);
this.addresses = _.get(fhirResource, 'addresses')
this.hasAddresses = Array.isArray(this.addresses);
this.has_addresses = Array.isArray(this.addresses);
this.description = _.get(fhirResource, 'description');
this.subject = _.get(fhirResource, 'subject');
this.periodStart = _.get(fhirResource, 'period.start');
this.periodEnd = _.get(fhirResource, 'period.end');
this.period_start = _.get(fhirResource, 'period.start');
this.period_end = _.get(fhirResource, 'period.end');
this.author = _.get(fhirResource, 'author');
};
dstu2DTO(fhirResource: any){
this.activity = _.get(fhirResource, 'activity');
this.hasActivity = Array.isArray(this.activity);
this.activity = !this.hasActivity
this.has_activity = Array.isArray(this.activity);
this.activity = !this.has_activity
? this.activity
: this.activity.map((item: any) => {
const categories = _.get(item, 'detail.category.coding');
@ -62,8 +66,8 @@ export class CarePlanModel {
stu3DTO(fhirResource: any) {
let activity = _.get(fhirResource, 'activity');
this.hasActivity = Array.isArray(activity);
this.activity = !this.hasActivity
this.has_activity = Array.isArray(activity);
this.activity = !this.has_activity
? activity
: activity.map((item: any) => {
const categories = [
@ -78,15 +82,15 @@ export class CarePlanModel {
categories,
};
});
this.basedOn = _.get(fhirResource, 'basedOn', []);
this.partOf = _.get(fhirResource, 'partOf', []);
this.based_on = _.get(fhirResource, 'basedOn', []);
this.part_of = _.get(fhirResource, 'partOf', []);
this.intent = _.get(fhirResource, 'intent', []);
};
r4DTO(fhirResource: any) {
this.activity = _.get(fhirResource, 'activity');
this.hasActivity = Array.isArray(this.activity);
this.activity = !this.hasActivity
this.has_activity = Array.isArray(this.activity);
this.activity = !this.has_activity
? this.activity
: this.activity.map((item: any) => {
const categories = [

View File

@ -15,7 +15,7 @@ describe('CareTeamModel', () => {
expected.name = "Peter James Charlmers Care Plan for Inpatient Encounter"
expected.status = "active"
// expected.periodStart
expected.periodEnd = "2013-01-01"
expected.period_end = "2013-01-01"
// expected.participants
expected.category = [
{ coding: [
@ -30,7 +30,7 @@ describe('CareTeamModel', () => {
expected.encounter = {
"reference": "Encounter/example"
}
expected.managingOrganization = { reference: 'Organization/f001' }
expected.managing_organization = { reference: 'Organization/f001' }
expected.participants = [
{ display: 'Peter James Chalmers', role: undefined, periodStart: undefined, periodEnd: undefined },
{ display: 'Dorothy Dietition', role: undefined, periodStart: undefined, periodEnd: '2013-01-01' }

View File

@ -1,20 +1,25 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {ReferenceModel} from '../datatypes/reference-model';
import {CodableConceptModel} from '../datatypes/codable-concept-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class CareTeamModel extends FastenDisplayModel {
export class CareTeamModel {
name: string | undefined
status: string | undefined
periodStart: string | undefined
periodEnd: string | undefined
period_start: string | undefined
period_end: string | undefined
participants: any[] | undefined
category: CodableConceptModel[] | undefined
subject: ReferenceModel | undefined
encounter: ReferenceModel | undefined
managingOrganization: ReferenceModel | undefined
managing_organization: ReferenceModel | undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.CareTeam
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4)
}
@ -22,17 +27,17 @@ export class CareTeamModel {
// Default value for title - "Care team"
this.name = _.get(fhirResource, 'name', 'Care team');
this.status = _.get(fhirResource, 'status');
this.periodStart = _.get(fhirResource, 'period.start');
this.periodEnd = _.get(fhirResource, 'period.end');
this.period_start = _.get(fhirResource, 'period.start');
this.period_end = _.get(fhirResource, 'period.end');
this.category = _.get(fhirResource, 'category');
this.subject = _.get(fhirResource, 'subject');
this.managingOrganization =
this.managing_organization =
_.get(fhirResource, 'managingOrganization[0]') ||
_.get(fhirResource, 'managingOrganization');
this.participants = _.get(fhirResource, 'participant', []).map((item: any) => {
const display = _.get(item, 'member.display');
const role = _.get(item, 'role.text') || _.get(item, 'role.coding.0.display');
const role = _.get(item, 'role.text') || _.get(item, 'role[0].text') || _.get(item, 'role.coding.0.display');
const periodStart = _.get(item, 'period.start');
const periodEnd = _.get(item, 'period.end');

View File

@ -0,0 +1,7 @@
import { CompositionModel } from './composition-model';
describe('CompositionModel', () => {
it('should create an instance', () => {
expect(new CompositionModel()).toBeTruthy();
});
});

View File

@ -0,0 +1,22 @@
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodableConceptModel} from '../datatypes/codable-concept-model';
import {fhirVersions, ResourceType} from '../constants';
import {FastenOptions} from '../fasten/fasten-options';
import * as _ from "lodash";
export class CompositionModel extends FastenDisplayModel {
title: string | undefined
relates_to: string | undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.Composition
this.title = _.get(fhirResource, 'title')
this.relates_to = _.get(fhirResource, 'relatesTo', []).map((relatesTo) => {
return relatesTo.target?.targetReference?.reference
})
}
}

View File

@ -11,12 +11,12 @@ describe('ConditionModel', () => {
it('should parse example1.json', () => {
let fixture = require("../../fixtures/r4/resources/condition/example1.json")
let expected = new ConditionModel({})
expected.codeText = 'Burn of ear'
expected.severityText = 'Severe'
expected.code_text = 'Burn of ear'
expected.severity_text = 'Severe'
// expected.hasAsserter: boolean | undefined
// expected.asserter: string | undefined
expected.hasBodySite = true
expected.bodySite = [new CodableConceptModel({
expected.has_body_site = true
expected.body_site = [new CodableConceptModel({
"coding": [
{
"system": "http://snomed.info/sct",
@ -26,22 +26,22 @@ describe('ConditionModel', () => {
],
"text": "Left Ear"
})]
expected.clinicalStatus = 'active'
expected.clinical_status = 'active'
// expected.dateRecorded: string | undefined
expected.onsetDateTime = '2012-05-24'
expected.onset_datetime = '2012-05-24'
expect(new ConditionModel(fixture)).toEqual(expected);
});
it('should parse example2.json', () => {
let fixture = require("../../fixtures/r4/resources/condition/example2.json")
let expected = new ConditionModel({})
expected.codeText = 'Asthma'
expected.severityText = 'Mild'
expected.code_text = 'Asthma'
expected.severity_text = 'Mild'
// expected.hasAsserter: boolean | undefined
// expected.asserter: string | undefined
expected.hasBodySite = false
expected.has_body_site = false
// expected.bodySite
expected.clinicalStatus = 'active'
expected.clinical_status = 'active'
// expected.dateRecorded: string | undefined
// expected.onsetDateTime = '2012-05-24'
@ -50,15 +50,15 @@ describe('ConditionModel', () => {
it('should parse example3.json', () => {
let fixture = require("../../fixtures/r4/resources/condition/example3.json")
let expected = new ConditionModel({})
expected.codeText = 'Fever'
expected.severityText = 'Mild'
expected.hasAsserter = true
expected.code_text = 'Fever'
expected.severity_text = 'Mild'
expected.has_asserter = true
expected.asserter = { reference: 'Practitioner/f201' }
expected.hasBodySite = true
expected.bodySite = [new CodableConceptModel({ text: '', coding: [ Object({ system: 'http://snomed.info/sct', code: '38266002', display: 'Entire body as a whole' }) ] })]
expected.clinicalStatus = 'resolved'
expected.dateRecorded = '2013-04-04'
expected.onsetDateTime = '2013-04-02'
expected.has_body_site = true
expected.body_site = [new CodableConceptModel({ text: '', coding: [ Object({ system: 'http://snomed.info/sct', code: '38266002', display: 'Entire body as a whole' }) ] })]
expected.clinical_status = 'resolved'
expected.date_recorded = '2013-04-04'
expected.onset_datetime = '2013-04-02'
expect(new ConditionModel(fixture)).toEqual(expected);
});

View File

@ -1,54 +1,59 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class ConditionModel {
codeText: string | undefined
severityText: string | undefined
hasAsserter: boolean | undefined
export class ConditionModel extends FastenDisplayModel {
code_text: string | undefined
severity_text: string | undefined
has_asserter: boolean | undefined
asserter: ReferenceModel | undefined
hasBodySite: boolean | undefined
bodySite: CodableConceptModel[] | undefined
clinicalStatus: string | undefined
dateRecorded: string | undefined
onsetDateTime: string | undefined
has_body_site: boolean | undefined
body_site: CodableConceptModel[] | undefined
clinical_status: string | undefined
date_recorded: string | undefined
onset_datetime: string | undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.Condition
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
commonDTO(fhirResource:any){
this.codeText =
this.code_text =
_.get(fhirResource, 'code.coding.0.display') ||
_.get(fhirResource, 'code.text') ||
_.get(fhirResource, 'code.coding.0.code');
this.severityText =
this.severity_text =
_.get(fhirResource, 'severity.coding.0.display') ||
_.get(fhirResource, 'severity.text');
this.onsetDateTime = _.get(fhirResource, 'onsetDateTime');
this.hasAsserter = _.has(fhirResource, 'asserter');
this.onset_datetime = _.get(fhirResource, 'onsetDateTime');
this.has_asserter = _.has(fhirResource, 'asserter');
this.asserter = _.get(fhirResource, 'asserter');
this.hasBodySite = !!_.get(fhirResource, 'bodySite.0.coding.0.display');
this.has_body_site = !!_.get(fhirResource, 'bodySite.0.coding.0.display');
let bodySite = _.get(fhirResource, 'bodySite')
if(bodySite){
this.bodySite = bodySite.map((body:any) => new CodableConceptModel(body))
this.body_site = bodySite.map((body:any) => new CodableConceptModel(body))
}
};
dstu2DTO(fhirResource:any){
this.clinicalStatus = _.get(fhirResource, 'clinicalStatus');
this.dateRecorded = _.get(fhirResource, 'dateRecorded');
this.clinical_status = _.get(fhirResource, 'clinicalStatus');
this.date_recorded = _.get(fhirResource, 'dateRecorded');
};
stu3DTO(fhirResource:any){
this.clinicalStatus = _.get(fhirResource, 'clinicalStatus');
this.dateRecorded = _.get(fhirResource, 'assertedDate');
this.clinical_status = _.get(fhirResource, 'clinicalStatus');
this.date_recorded = _.get(fhirResource, 'assertedDate');
};
r4DTO(fhirResource:any){
this.clinicalStatus = _.get(fhirResource, 'clinicalStatus.coding.0.code');
this.dateRecorded = _.get(fhirResource, 'recordedDate');
this.clinical_status = _.get(fhirResource, 'clinicalStatus.coding.0.code');
this.date_recorded = _.get(fhirResource, 'recordedDate');
};
resourceDTO(fhirResource:any, fhirVersion:fhirVersions){

View File

@ -1,55 +1,60 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class DeviceModel extends FastenDisplayModel {
export class DeviceModel {
model: string | undefined
status: string | undefined
hasExpiry: boolean | undefined
getExpiry: string | undefined
getTypeCoding: string | undefined
hasTypeCoding: boolean | undefined
getUdi: string | undefined
udiCarrierAIDC: string | undefined
udiCarrierHRF: string | undefined
has_expiry: boolean | undefined
get_expiry: string | undefined
get_type_coding: string | undefined
has_type_coding: boolean | undefined
get_udi: string | undefined
udi_carrier_aidc: string | undefined
udi_carrier_hrf: string | undefined
safety: string | undefined
hasSafety: boolean | undefined
has_safety: boolean | undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.Device
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
commonDTO(fhirResource:any){
this.model = _.get(fhirResource, 'model', 'Device');
this.model = _.get(fhirResource, 'model') || _.get(fhirResource,"code.text", 'Device');
this.status = _.get(fhirResource, 'status', '');
this.getTypeCoding = _.get(fhirResource, 'type.coding');
this.hasTypeCoding = Array.isArray(this.getTypeCoding);
this.get_type_coding = _.get(fhirResource, 'type.coding');
this.has_type_coding = Array.isArray(this.get_type_coding);
};
dstu2DTO(fhirResource:any){
this.getUdi = _.get(fhirResource, 'udi');
this.hasExpiry = _.has(fhirResource, 'expiry');
this.getExpiry = _.get(fhirResource, 'expiry');
this.get_udi = _.get(fhirResource, 'udi');
this.has_expiry = _.has(fhirResource, 'expiry');
this.get_expiry = _.get(fhirResource, 'expiry');
};
stu3DTO(fhirResource:any){
this.getUdi = _.get(fhirResource, 'udi.name');
this.hasExpiry = _.has(fhirResource, 'expirationDate');
this.getExpiry = _.get(fhirResource, 'expirationDate');
this.get_udi = _.get(fhirResource, 'udi.name');
this.has_expiry = _.has(fhirResource, 'expirationDate');
this.get_expiry = _.get(fhirResource, 'expirationDate');
this.safety = _.get(fhirResource, 'safety', []);
this.hasSafety = hasValue(this.safety);
this.has_safety = hasValue(this.safety);
};
r4DTO(fhirResource:any){
this.getUdi = _.get(fhirResource, 'udiCarrier.deviceIdentifier');
this.hasExpiry = _.has(fhirResource, 'expirationDate');
this.getExpiry = _.get(fhirResource, 'expirationDate');
this.udiCarrierAIDC = _.get(fhirResource, 'udiCarrier.carrierAIDC');
this.udiCarrierHRF = _.get(fhirResource, 'udiCarrier.carrierHRF');
this.get_udi = _.get(fhirResource, 'udiCarrier.deviceIdentifier');
this.has_expiry = _.has(fhirResource, 'expirationDate');
this.get_expiry = _.get(fhirResource, 'expirationDate');
this.udi_carrier_aidc = _.get(fhirResource, 'udiCarrier.carrierAIDC');
this.udi_carrier_hrf = _.get(fhirResource, 'udiCarrier.carrierHRF');
this.safety = _.get(fhirResource, 'safety', []);
this.hasSafety = hasValue(this.safety);
this.has_safety = hasValue(this.safety);
};
resourceDTO(fhirResource:any, fhirVersion:fhirVersions){

View File

@ -16,12 +16,12 @@ describe('DiagnosticReportModel', () => {
expected.title = 'Complete blood count (hemogram) panel - Blood by Automated count'
expected.status = 'final'
// expected.effectiveDateTime: string | undefined
expected.categoryCoding = [
expected.category_coding = [
{ system: 'http://snomed.info/sct', code: '252275004', display: 'Haematology test' },
{ system: 'http://hl7.org/fhir/v2/0074', code: 'HM' }
]
expected.hasCategoryCoding = true
expected.hasPerformer = true
expected.has_category_coding = true
expected.has_performer = true
expected.conclusion = 'Core lab'
expected.performer = { reference: 'Organization/f001', display: 'Burgers University Medical Centre' }
expected.issued = '2013-05-15T19:32:52+01:00'

View File

@ -1,21 +1,26 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class DiagnosticReportModel extends FastenDisplayModel {
export class DiagnosticReportModel {
title: string | undefined
status: string | undefined
effectiveDateTime: string | undefined
categoryCoding: CodingModel[] | undefined
hasCategoryCoding: boolean | undefined
hasPerformer: boolean | undefined
effective_datetime: string | undefined
category_coding: CodingModel[] | undefined
has_category_coding: boolean | undefined
has_performer: boolean | undefined
conclusion: string | undefined
performer: ReferenceModel | undefined
issued: string | undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.DiagnosticReport
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
@ -25,19 +30,19 @@ export class DiagnosticReportModel {
_.get(fhirResource, 'code.display') ||
_.get(fhirResource, 'code.coding.0.display', null);
this.status = _.get(fhirResource, 'status', '');
this.effectiveDateTime = _.get(fhirResource, 'effectiveDateTime');
this.categoryCoding = _.get(fhirResource, 'category.coding');
this.hasCategoryCoding = Array.isArray(this.categoryCoding);
this.effective_datetime = _.get(fhirResource, 'effectiveDateTime');
this.category_coding = _.get(fhirResource, 'category.coding');
this.has_category_coding = Array.isArray(this.category_coding);
this.conclusion = _.get(fhirResource, 'conclusion');
this.issued = _.get(fhirResource, 'issued');
};
dstu2DTO(fhirResource:any){
this.hasPerformer = _.has(fhirResource, 'performer');
this.has_performer = _.has(fhirResource, 'performer');
this.performer = _.get(fhirResource, 'performer');
};
stu3DTO(fhirResource:any){
this.hasPerformer = _.has(fhirResource, 'performer.0.actor.display');
this.has_performer = _.has(fhirResource, 'performer.0.actor.display');
this.performer = _.get(fhirResource, 'performer.0.actor');
};
@ -46,9 +51,9 @@ export class DiagnosticReportModel {
if (!this.performer) {
this.performer = _.get(fhirResource, 'performer.0');
}
this.hasPerformer = !!this.performer;
this.categoryCoding = _.get(fhirResource, 'category.coding');
this.hasCategoryCoding = Array.isArray(this.categoryCoding);
this.has_performer = !!this.performer;
this.category_coding = _.get(fhirResource, 'category.coding');
this.has_category_coding = Array.isArray(this.category_coding);
};
resourceDTO(fhirResource:any, fhirVersion: fhirVersions){

View File

@ -15,10 +15,10 @@ describe('DocumentReferenceModel', () => {
expected.description = 'Physical'
expected.status = 'current'
// expected.docStatus: string | undefined
expected.typeCoding = { system: 'http://loinc.org', code: '34108-1', display: 'Outpatient Note' }
expected.type_coding = { system: 'http://loinc.org', code: '34108-1', display: 'Outpatient Note' }
// expected.classCoding: string | undefined
expected.createdAt = '2005-12-24T09:43:41+11:00'
expected.securityLabelCoding = { system: 'http://terminology.hl7.org/CodeSystem/v3-Confidentiality', code: 'V', display: 'very restricted' }
expected.created_at = '2005-12-24T09:43:41+11:00'
expected.security_label_coding = { system: 'http://terminology.hl7.org/CodeSystem/v3-Confidentiality', code: 'V', display: 'very restricted' }
expected.context = {
eventCoding: { system: 'http://ihe.net/xds/connectathon/eventCodes', code: 'T-D8200', display: 'Arm' },
facilityTypeCoding: { system: 'http://www.ihe.net/xds/connectathon/healthcareFacilityTypeCodes', code: 'Outpatient', display: 'Outpatient' },

View File

@ -1,17 +1,20 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class DocumentReferenceModel extends FastenDisplayModel {
export class DocumentReferenceModel {
description: string | undefined
status: string | undefined
docStatus: string | undefined
typeCoding: CodingModel | undefined
classCoding: CodingModel | undefined
createdAt: string | undefined
securityLabelCoding: CodingModel | undefined
doc_status: string | undefined
type_coding: CodingModel | undefined
class_coding: CodingModel | undefined
created_at: string | undefined
security_label_coding: CodingModel | undefined
content: {
url: string
isUrlBinaryResourceReference: boolean
@ -26,7 +29,9 @@ export class DocumentReferenceModel {
periodEnd: string
} | undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.DocumentReference
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
@ -34,10 +39,10 @@ export class DocumentReferenceModel {
commonDTO(fhirResource:any){
this.description = _.get(fhirResource, 'description');
this.status = _.get(fhirResource, 'status');
this.typeCoding = _.get(fhirResource, 'type.coding[0]');
this.classCoding = _.get(fhirResource, 'class.coding[0]');
this.createdAt = _.get(fhirResource, 'created');
this.securityLabelCoding = _.get(fhirResource, 'securityLabel[0].coding[0]');
this.type_coding = _.get(fhirResource, 'type.coding[0]');
this.class_coding = _.get(fhirResource, 'class.coding[0]');
this.created_at = _.get(fhirResource, 'created');
this.security_label_coding = _.get(fhirResource, 'securityLabel[0].coding[0]');
const eventCoding = _.get(fhirResource, 'context.event[0].coding[0]');
const facilityTypeCoding = _.get(
fhirResource,
@ -59,18 +64,18 @@ export class DocumentReferenceModel {
};
dstu2DTO(fhirResource:any) {
this.docStatus =
this.doc_status =
_.get(fhirResource, 'docStatus.coding[0].display') ||
_.get(fhirResource, 'docStatus.coding[0].code');
};
stu3DTO(fhirResource:any){
this.docStatus = _.get(fhirResource, 'docStatus');
this.doc_status = _.get(fhirResource, 'docStatus');
};
r4DTO(fhirResource:any){
this.classCoding = _.get(fhirResource, 'category.coding[0]');
this.createdAt = _.get(fhirResource, 'date');
this.class_coding = _.get(fhirResource, 'category.coding[0]');
this.created_at = _.get(fhirResource, 'date');
};
contentDTO(fhirResource: any, fhirVersion: fhirVersions){

View File

@ -15,8 +15,8 @@ describe('EncounterModel', () => {
// hasParticipant: boolean | undefined
// locationDisplay: string | undefined
// encounterType: string | undefined
expected.resourceClass = 'inpatient encounter'
expected.resourceStatus = 'in-progress'
expected.resource_class = 'inpatient encounter'
expected.resource_status = 'in-progress'
// participant
expect(new EncounterModel(fixture)).toEqual(expected);
@ -25,13 +25,13 @@ describe('EncounterModel', () => {
it('should parse example2.json', () => {
let fixture = require("../../fixtures/r4/resources/encounter/example2.json")
let expected = new EncounterModel({})
expected.periodEnd = '2015-01-17T16:30:00+10:00'
expected.periodStart = '2015-01-17T16:00:00+10:00'
expected.hasParticipant = true
expected.locationDisplay = 'Client\'s home'
expected.period_end = '2015-01-17T16:30:00+10:00'
expected.period_start = '2015-01-17T16:00:00+10:00'
expected.has_participant = true
expected.location_display = 'Client\'s home'
// encounterType: string | undefined
expected.resourceClass = 'home health'
expected.resourceStatus = 'finished'
expected.resource_class = 'home health'
expected.resource_status = 'finished'
expected.participant = [
{
display: undefined,
@ -49,11 +49,11 @@ describe('EncounterModel', () => {
let expected = new EncounterModel({})
// expected.periodEnd = '2015-01-17T16:30:00+10:00'
// expected.periodStart = '2015-01-17T16:00:00+10:00'
expected.hasParticipant = true
expected.locationDisplay = 'Encounter'
expected.encounterType = [ { coding: [ Object({ system: 'http://snomed.info/sct', code: '11429006', display: 'Consultation' }) ] } ]
expected.resourceClass = 'ambulatory'
expected.resourceStatus = 'finished'
expected.has_participant = true
expected.location_display = 'Encounter'
expected.encounter_type = [ { coding: [ Object({ system: 'http://snomed.info/sct', code: '11429006', display: 'Consultation' }) ] } ]
expected.resource_class = 'ambulatory'
expected.resource_status = 'finished'
expected.participant = [
{ display: undefined, reference: Object({ reference: 'Practitioner/f201' }), text: undefined, periodStart: undefined }
]

View File

@ -1,17 +1,20 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class EncounterModel {
periodEnd: string | undefined
periodStart: string | undefined
hasParticipant: boolean | undefined
locationDisplay: string | undefined
encounterType: CodableConceptModel[] | undefined
resourceClass: string | undefined
resourceStatus: string | undefined
export class EncounterModel extends FastenDisplayModel {
period_end: string | undefined
period_start: string | undefined
has_participant: boolean | undefined
location_display: string | undefined
encounter_type: CodableConceptModel[] | undefined
resource_class: string | undefined
resource_status: string | undefined
participant: {
display?: string,
reference: ReferenceModel,
@ -19,25 +22,27 @@ export class EncounterModel {
periodStart?:string
}[] | undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.Encounter
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
commonDTO(fhirResource:any){
this.resourceStatus = _.get(fhirResource, 'status');
this.locationDisplay = _.get(
this.resource_status = _.get(fhirResource, 'status');
this.location_display = _.get(
fhirResource,
'location[0].location.display',
'Encounter',
);
this.encounterType = _.get(fhirResource, 'type');
this.hasParticipant = _.has(fhirResource, 'participant');
this.encounter_type = _.get(fhirResource, 'type');
this.has_participant = _.has(fhirResource, 'participant');
};
dstu2DTO(fhirResource:any){
this.periodEnd = _.get(fhirResource, 'period.end');
this.periodStart = _.get(fhirResource, 'period.start');
this.resourceClass = _.get(fhirResource, 'class');
this.period_end = _.get(fhirResource, 'period.end');
this.period_start = _.get(fhirResource, 'period.start');
this.resource_class = _.get(fhirResource, 'class');
this.participant = _.get(fhirResource, 'participant', []).map((item: any) => {
let periodStart = _.get(item, 'period.start');
periodStart = new Date(periodStart).toLocaleString();
@ -52,11 +57,11 @@ export class EncounterModel {
};
stu3DTO(fhirResource:any){
this.periodEnd = _.get(fhirResource, 'period.end');
this.periodStart = _.get(fhirResource, 'period.start');
this.period_end = _.get(fhirResource, 'period.end');
this.period_start = _.get(fhirResource, 'period.start');
this.resourceClass = _.get(fhirResource, 'class.display');
this.resource_class = _.get(fhirResource, 'class.display');
this.participant = _.get(fhirResource, 'participant', []).map((item: any) => {
let periodStart = _.get(item, 'period.start');
const reference = _.get(item, 'individual', {});
@ -70,10 +75,10 @@ export class EncounterModel {
};
r4DTO(fhirResource:any){
this.periodEnd = _.get(fhirResource, 'period.end');
this.periodStart = _.get(fhirResource, 'period.start');
this.period_end = _.get(fhirResource, 'period.end');
this.period_start = _.get(fhirResource, 'period.start');
this.resourceClass = _.get(fhirResource, 'class.display');
this.resource_class = _.get(fhirResource, 'class.display');
this.participant = _.get(fhirResource, 'participant', []).map((item: any) => {
let periodStart = _.get(item, 'period.start');
const reference = _.get(item, 'individual', {});

View File

@ -15,23 +15,23 @@ describe('GoalModel', () => {
// expected.title: string | undefined
expected.status = 'on-hold'
expected.hasStatus = true
expected.startDate = '2015-04-05'
expected.hasCategory = true
expected.has_status = true
expected.start_date = '2015-04-05'
expected.has_category = true
expected.category = [
new CodableConceptModel({ coding: [ Object({ system: 'http://terminology.hl7.org/CodeSystem/goal-category', code: 'dietary' }) ] })
]
// expected.hasUdi: boolean | undefined
// expected.udi: string | undefined
expected.addresses = [{ display: 'obesity condition' }]
expected.hasAddresses = true
expected.has_addresses = true
// expected.author: string | undefined
expected.description = 'Target weight is 160 to 180 lbs.'
// expected.outcomeReference: string | undefined
// expected.achievementStatus: string | undefined
expected.priority = { system: 'http://terminology.hl7.org/CodeSystem/goal-priority', code: 'high-priority', display: 'High Priority' }
expected.subject = { reference: 'Patient/example', display: 'Peter James Chalmers' }
expected.statusDate = '2016-02-14'
expected.status_date = '2016-02-14'
expect(new GoalModel(fixture)).toEqual(expected);
});
@ -42,17 +42,17 @@ describe('GoalModel', () => {
// expected.title: string | undefined
expected.status = 'completed'
expected.hasStatus = true
expected.startDate = '2015-04-05'
expected.hasCategory = false
expected.has_status = true
expected.start_date = '2015-04-05'
expected.has_category = false
// expected.hasUdi: boolean | undefined
// expected.udi: string | undefined
// expected.addresses = [{ display: 'obesity condition' }]
expected.hasAddresses = false
expected.has_addresses = false
// expected.author: string | undefined
expected.description = 'Stop smoking'
// expected.outcomeReference: string | undefined
expected.achievementStatus = { system: 'http://terminology.hl7.org/CodeSystem/goal-achievement', code: 'achieved', display: 'Achieved' }
expected.achievement_status = { system: 'http://terminology.hl7.org/CodeSystem/goal-achievement', code: 'achieved', display: 'Achieved' }
// expected.priority = { system: 'http://terminology.hl7.org/CodeSystem/goal-priority', code: 'high-priority', display: 'High Priority' }
expected.subject = { reference: 'Patient/example', display: 'Peter James Chalmers' }
// expected.statusDate = '2016-02-14'

View File

@ -1,51 +1,55 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class GoalModel {
export class GoalModel extends FastenDisplayModel {
title: string | undefined
status: string | undefined
hasStatus: boolean | undefined
startDate: string | undefined
hasCategory: boolean | undefined
has_status: boolean | undefined
start_date: string | undefined
has_category: boolean | undefined
category: CodableConceptModel[] | undefined
hasUdi: boolean | undefined
has_udi: boolean | undefined
udi: string | undefined
addresses: any[] | undefined
hasAddresses: boolean | undefined
has_addresses: boolean | undefined
author: string | undefined
description: string | undefined
outcomeReference: string | undefined
achievementStatus: CodingModel | undefined
outcome_reference: string | undefined
achievement_status: CodingModel | undefined
priority: CodingModel | undefined
subject: ReferenceModel | undefined
statusDate: string | undefined
status_date: string | undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.Goal
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
commonDTO(fhirResource: any){
this.title = _.get(fhirResource, 'note[0].text', 'Goal');
this.status = _.get(fhirResource, 'status', '');
this.hasStatus = _.has(fhirResource, 'status');
this.startDate = _.get(fhirResource, 'startDate');
this.has_status = _.has(fhirResource, 'status');
this.start_date = _.get(fhirResource, 'startDate');
let category = _.get(fhirResource, 'category');
if(category){
this.category = category.map((cat:any) => new CodableConceptModel(cat))
}
this.hasCategory = Array.isArray(this.category);
this.hasUdi = _.has(fhirResource, 'udi');
this.has_category = Array.isArray(this.category);
this.has_udi = _.has(fhirResource, 'udi');
this.udi = _.get(fhirResource, 'udi');
this.addresses = _.get(fhirResource, 'addresses');
this.hasAddresses = Array.isArray(this.addresses);
this.has_addresses = Array.isArray(this.addresses);
this.author = _.get(fhirResource, 'author');
this.priority = _.get(fhirResource, 'priority.coding[0]');
this.subject = _.get(fhirResource, 'subject');
this.statusDate = _.get(fhirResource, 'statusDate');
this.status_date = _.get(fhirResource, 'statusDate');
};
dstu2DTO(fhirResource:any){
this.description = _.get(fhirResource, 'description');
@ -53,13 +57,13 @@ export class GoalModel {
stu3DTO(fhirResource:any){
this.description = _.get(fhirResource, 'description.text', null);
this.title = _.get(fhirResource, 'statusReason');
this.outcomeReference = _.get(fhirResource, 'outcomeReference');
this.outcome_reference = _.get(fhirResource, 'outcomeReference');
};
r4DTO(fhirResource:any){
this.description = _.get(fhirResource, 'description.text', null);
this.status = _.get(fhirResource, 'lifecycleStatus', '');
this.hasStatus = _.has(fhirResource, 'lifecycleStatus');
this.achievementStatus = _.get(fhirResource, 'achievementStatus.coding[0]');
this.has_status = _.has(fhirResource, 'lifecycleStatus');
this.achievement_status = _.get(fhirResource, 'achievementStatus.coding[0]');
};
resourceDTO(fhirResource: any, fhirVersion:fhirVersions){

View File

@ -12,20 +12,20 @@ describe('ImmunizationModel', () => {
expected.title = 'Fluvax (Influenza)'
expected.status = 'completed'
expected.providedDate = '2013-01-10'
expected.provided_date = '2013-01-10'
// manufacturerText: string | undefined
expected.hasLotNumber = true
expected.lotNumber = 'AAJN11K'
expected.lotNumberExpirationDate = '2015-02-15'
expected.hasDoseQuantity = true
expected.doseQuantity = { value: 5, system: 'http://unitsofmeasure.org', code: 'mg' }
expected.has_lot_number = true
expected.lot_number = 'AAJN11K'
expected.lot_number_expiration_date = '2015-02-15'
expected.has_dose_quantity = true
expected.dose_quantity = { value: 5, system: 'http://unitsofmeasure.org', code: 'mg' }
// requester: string | undefined
// reported: string | undefined
// performer: string | undefined
expected.route = [ { system: 'http://terminology.hl7.org/CodeSystem/v3-RouteOfAdministration', code: 'IM', display: 'Injection, intramuscular' }]
expected.hasRoute = true
expected.has_route = true
expected.site = [{ system: 'http://terminology.hl7.org/CodeSystem/v3-ActSite', code: 'LA', display: 'left arm' } ]
expected.hasSite = true
expected.has_site = true
expected.patient = { reference: 'Patient/example' }
expected.note = [ { text: 'Notes on adminstration of vaccine' } ]

View File

@ -1,31 +1,35 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class ImmunizationModel {
export class ImmunizationModel extends FastenDisplayModel {
title: string | undefined
status: string | undefined
providedDate: string | undefined
manufacturerText: string | undefined
hasLotNumber: boolean | undefined
lotNumber: string | undefined
lotNumberExpirationDate: string | undefined
hasDoseQuantity: boolean | undefined
doseQuantity: CodingModel | undefined
provided_date: string | undefined
manufacturer_text: string | undefined
has_lot_number: boolean | undefined
lot_number: string | undefined
lot_number_expiration_date: string | undefined
has_dose_quantity: boolean | undefined
dose_quantity: CodingModel | undefined
requester: string | undefined
reported: string | undefined
performer: string | undefined
route: CodingModel[] | undefined
hasRoute: boolean | undefined
has_route: boolean | undefined
site: CodingModel[] | undefined
hasSite: boolean | undefined
has_site: boolean | undefined
patient: ReferenceModel | undefined
note: any[] | undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.Immunization
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
@ -35,19 +39,19 @@ export class ImmunizationModel {
_.get(fhirResource, 'vaccineCode.text') ||
_.get(fhirResource, 'vaccineCode.coding[0].display', 'Immunization');
this.status = _.get(fhirResource, 'status', null);
this.providedDate = _.get(fhirResource, 'date', null);
this.provided_date = _.get(fhirResource, 'date', null);
this.reported = _.get(fhirResource, 'reported') && ' - self reported';
this.manufacturerText = _.get(fhirResource, 'manufacturer.display');
this.hasLotNumber = _.has(fhirResource, 'lotNumber');
this.lotNumber = _.get(fhirResource, 'lotNumber');
this.lotNumberExpirationDate = _.get(fhirResource, 'expirationDate');
this.hasDoseQuantity = _.has(fhirResource, 'doseQuantity');
this.doseQuantity = _.get(fhirResource, 'doseQuantity');
this.manufacturer_text = _.get(fhirResource, 'manufacturer.display');
this.has_lot_number = _.has(fhirResource, 'lotNumber');
this.lot_number = _.get(fhirResource, 'lotNumber');
this.lot_number_expiration_date = _.get(fhirResource, 'expirationDate');
this.has_dose_quantity = _.has(fhirResource, 'doseQuantity');
this.dose_quantity = _.get(fhirResource, 'doseQuantity');
this.requester = _.get(fhirResource, 'requester');
this.route = _.get(fhirResource, 'route.coding');
this.hasRoute = Array.isArray(this.route);
this.has_route = Array.isArray(this.route);
this.site = _.get(fhirResource, 'site.coding');
this.hasSite = Array.isArray(this.site);
this.has_site = Array.isArray(this.site);
this.patient = _.get(fhirResource, 'patient');
this.note = _.get(fhirResource, 'note');
};
@ -60,7 +64,7 @@ export class ImmunizationModel {
r4DTO(fhirResource:any){
this.performer = _.get(fhirResource, 'performer.actor');
this.providedDate =
this.provided_date =
_.get(fhirResource, 'occurrenceDateTime') ||
_.get(fhirResource, 'occurrenceString');
};

View File

@ -17,7 +17,7 @@ describe('LocationModel', () => {
expectedLocation.status = 'active'
expectedLocation.description = 'Second floor of the Old South Wing, formerly in use by Psychiatry'
expectedLocation.mode = "instance"
expectedLocation.managingOrganization = {"reference": "Organization/f001"}
expectedLocation.managing_organization = {"reference": "Organization/f001"}
expectedLocation.address = new AddressModel({
city: "Den Burg",
line: ['Galapagosweg 91, Building A'],
@ -31,7 +31,7 @@ describe('LocationModel', () => {
{system: 'url', value: 'http://sampleorg.com/southwing', use: 'work'}
]
expectedLocation.type = []
expectedLocation.physicalType = new CodableConceptModel({
expectedLocation.physical_type = new CodableConceptModel({
text: '',
coding: [
{
@ -52,7 +52,7 @@ describe('LocationModel', () => {
expectedLocation.status = 'active'
expectedLocation.description = "Ambulance provided by Burgers University Medical Center"
expectedLocation.mode = "kind"
expectedLocation.managingOrganization = {"reference": "Organization/f001"}
expectedLocation.managing_organization = {"reference": "Organization/f001"}
expectedLocation.telecom = [
{system: 'phone', value: '2329', use: 'mobile'}
]
@ -66,7 +66,7 @@ describe('LocationModel', () => {
}
]
})]
expectedLocation.physicalType = new CodableConceptModel({
expectedLocation.physical_type = new CodableConceptModel({
text: '',
coding: [
{
@ -90,7 +90,7 @@ describe('LocationModel', () => {
expectedLocation.status = 'active'
expectedLocation.description = "Second floor of the Old South Wing, formerly in use by Psychiatry"
expectedLocation.mode = "instance"
expectedLocation.managingOrganization = {"reference": "Organization/f001"}
expectedLocation.managing_organization = {"reference": "Organization/f001"}
expectedLocation.telecom = [
{
"system": "phone",
@ -121,7 +121,7 @@ describe('LocationModel', () => {
"postalCode": "9105 PZ",
"country": "NLD"
})
expectedLocation.physicalType = new CodableConceptModel({
expectedLocation.physical_type = new CodableConceptModel({
text: '',
coding: [
{

View File

@ -3,20 +3,25 @@ import {AddressModel} from '../datatypes/address-model';
import {TelecomModel} from '../datatypes/telecom-model';
import {CodableConceptModel} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class LocationModel extends FastenDisplayModel {
export class LocationModel {
name: string
status: string
description: string
address: AddressModel
telecom: TelecomModel[]
type: CodableConceptModel[]
physicalType: CodableConceptModel
physical_type: CodableConceptModel
mode: string
managingOrganization: ReferenceModel
managing_organization: ReferenceModel
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.Location
this.name = _.get(fhirResource, 'name');
this.status = _.get(fhirResource, 'status');
@ -24,9 +29,9 @@ export class LocationModel {
this.address = new AddressModel(_.get(fhirResource, 'address'));
this.telecom = _.get(fhirResource, 'telecom');
this.type = (_.get(fhirResource, 'type') || []).map((_type: any) => new CodableConceptModel(_type));
this.physicalType = new CodableConceptModel(_.get(fhirResource, 'physicalType'));
this.physical_type = new CodableConceptModel(_.get(fhirResource, 'physicalType'));
this.mode = _.get(fhirResource, 'mode');
this.managingOrganization = _.get(fhirResource, 'managingOrganization');
this.managing_organization = _.get(fhirResource, 'managingOrganization');
}
}

View File

@ -1,26 +1,31 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class MedicationDispenseModel {
medicationTitle: string|undefined
medicationCoding: string|undefined
typeCoding: string|undefined
hasDosageInstruction: boolean|undefined
dosageInstruction: any[] | undefined
dosageInstructionData: any[]|undefined
whenPrepared: string|undefined
export class MedicationDispenseModel extends FastenDisplayModel {
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
medication_title: string|undefined
medication_coding: string|undefined
type_coding: string|undefined
has_dosage_instruction: boolean|undefined
dosage_instruction: any[] | undefined
dosage_instruction_data: any[]|undefined
when_prepared: string|undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.MedicationDispense
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
commonDTO(fhirResource:any){
this.typeCoding = _.get(fhirResource, 'type.coding.0');
this.whenPrepared = _.get(fhirResource, 'whenPrepared');
this.type_coding = _.get(fhirResource, 'type.coding.0');
this.when_prepared = _.get(fhirResource, 'whenPrepared');
};
dstu2DTO(fhirResource:any){
@ -46,10 +51,10 @@ export class MedicationDispenseModel {
});
};
this.medicationTitle = _.get(fhirResource, 'medicationReference.display');
this.dosageInstruction = _.get(fhirResource, 'dosageInstruction', []);
this.hasDosageInstruction = Array.isArray(this.dosageInstruction) && this.dosageInstruction.length > 0;
this.dosageInstructionData = prepareDosageInstructionData(this.dosageInstruction || []);
this.medication_title = _.get(fhirResource, 'medicationReference.display');
this.dosage_instruction = _.get(fhirResource, 'dosageInstruction', []);
this.has_dosage_instruction = Array.isArray(this.dosage_instruction) && this.dosage_instruction.length > 0;
this.dosage_instruction_data = prepareDosageInstructionData(this.dosage_instruction || []);
};
stu3DTO(fhirResource:any){
@ -74,14 +79,14 @@ export class MedicationDispenseModel {
return data;
});
};
this.medicationTitle =
this.medication_title =
_.get(fhirResource, 'medicationReference.display') ||
_.get(fhirResource, 'contained[0].code.coding[0].display');
this.medicationCoding = _.get(fhirResource, 'contained[0].code.coding[0]');
this.dosageInstruction = _.get(fhirResource, 'dosageInstruction', []);
this.hasDosageInstruction =
Array.isArray(this.dosageInstruction) && this.dosageInstruction.length > 0;
this.dosageInstructionData = prepareDosageInstructionData(this.dosageInstruction);
this.medication_coding = _.get(fhirResource, 'contained[0].code.coding[0]');
this.dosage_instruction = _.get(fhirResource, 'dosageInstruction', []);
this.has_dosage_instruction =
Array.isArray(this.dosage_instruction) && this.dosage_instruction.length > 0;
this.dosage_instruction_data = prepareDosageInstructionData(this.dosage_instruction);
};
r4DTO(fhirResource:any){
@ -106,14 +111,14 @@ export class MedicationDispenseModel {
return data;
});
};
this.medicationTitle =
this.medication_title =
_.get(fhirResource, 'medicationReference.display') ||
_.get(fhirResource, 'contained[0].code.coding[0].display');
this.medicationCoding = _.get(fhirResource, 'contained[0].code.coding[0]');
this.dosageInstruction = _.get(fhirResource, 'dosageInstruction', []);
this.hasDosageInstruction =
Array.isArray(this.dosageInstruction) && this.dosageInstruction.length > 0;
this.dosageInstructionData = prepareDosageInstructionData(this.dosageInstruction);
this.medication_coding = _.get(fhirResource, 'contained[0].code.coding[0]');
this.dosage_instruction = _.get(fhirResource, 'dosageInstruction', []);
this.has_dosage_instruction =
Array.isArray(this.dosage_instruction) && this.dosage_instruction.length > 0;
this.dosage_instruction_data = prepareDosageInstructionData(this.dosage_instruction);
};
resourceDTO(fhirResource:any, fhirVersion: fhirVersions){

View File

@ -1,25 +1,30 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class MedicationModel extends FastenDisplayModel {
export class MedicationModel {
title: CodingModel|undefined
isBrand: string|undefined
is_brand: string|undefined
manufacturer: string|undefined
hasProductForm:boolean|undefined
productForm: string|undefined
productIngredient: string|undefined
hasProductIngredient: boolean|undefined
hasProduct: boolean|undefined
packageCoding: string|undefined
hasPackageCoding: boolean|undefined
hasImages: boolean|undefined
has_product_form:boolean|undefined
product_form: string|undefined
product_ingredient: string|undefined
has_product_ingredient: boolean|undefined
has_product: boolean|undefined
package_coding: string|undefined
has_package_coding: boolean|undefined
has_images: boolean|undefined
images: string|undefined
status: string|undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.Medication
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
@ -32,46 +37,46 @@ export class MedicationModel {
};
dstu2DTO(fhirResource:any){
this.productForm = _.get(fhirResource, 'product.form.coding', []);
this.hasProductForm = Array.isArray(this.productForm) && this.productForm.length > 0;
this.productIngredient = _.get(fhirResource, 'product.ingredient', []);
this.hasProductIngredient =
Array.isArray(this.productIngredient) && this.productIngredient.length > 0;
this.hasProduct = this.hasProductForm || this.hasProductIngredient;
this.packageCoding = _.get(fhirResource, 'package.container.coding', []);
this.hasPackageCoding =
Array.isArray(this.packageCoding) && this.packageCoding.length > 0;
this.isBrand = _.get(fhirResource, 'isBrand');
this.product_form = _.get(fhirResource, 'product.form.coding', []);
this.has_product_form = Array.isArray(this.product_form) && this.product_form.length > 0;
this.product_ingredient = _.get(fhirResource, 'product.ingredient', []);
this.has_product_ingredient =
Array.isArray(this.product_ingredient) && this.product_ingredient.length > 0;
this.has_product = this.has_product_form || this.has_product_ingredient;
this.package_coding = _.get(fhirResource, 'package.container.coding', []);
this.has_package_coding =
Array.isArray(this.package_coding) && this.package_coding.length > 0;
this.is_brand = _.get(fhirResource, 'isBrand');
};
stu3DTO(fhirResource:any){
this.productForm = _.get(fhirResource, 'form.coding', []);
this.hasProductForm = Array.isArray(this.productForm) && this.productForm.length > 0;
this.productIngredient = _.get(fhirResource, 'ingredient', []);
this.hasProductIngredient = Array.isArray(this.productIngredient) && this.productIngredient.length > 0;
this. hasProduct = this.hasProductForm || this.hasProductIngredient;
this. packageCoding = _.get(fhirResource, 'package.container.coding', []);
this. hasPackageCoding =
Array.isArray(this.packageCoding) && this.packageCoding.length > 0;
this.product_form = _.get(fhirResource, 'form.coding', []);
this.has_product_form = Array.isArray(this.product_form) && this.product_form.length > 0;
this.product_ingredient = _.get(fhirResource, 'ingredient', []);
this.has_product_ingredient = Array.isArray(this.product_ingredient) && this.product_ingredient.length > 0;
this. has_product = this.has_product_form || this.has_product_ingredient;
this. package_coding = _.get(fhirResource, 'package.container.coding', []);
this. has_package_coding =
Array.isArray(this.package_coding) && this.package_coding.length > 0;
this. images = _.get(fhirResource, 'image', []);
this. hasImages = true;
this. has_images = true;
if (
!Array.isArray(this.images) ||
this.images.filter(item => !!item.url).length === 0
) {
this.hasImages = false;
this.has_images = false;
}
this. isBrand = _.get(fhirResource, 'isBrand');
this. is_brand = _.get(fhirResource, 'isBrand');
};
r4DTO(fhirResource:any){
this.productForm = _.get(fhirResource, 'form.coding', []);
this.hasProductForm = Array.isArray(this.productForm) && this.productForm.length > 0;
this.productIngredient = _.get(fhirResource, 'ingredient', []);
this.hasProductIngredient =
Array.isArray(this.productIngredient) && this.productIngredient.length > 0;
this.product_form = _.get(fhirResource, 'form.coding', []);
this.has_product_form = Array.isArray(this.product_form) && this.product_form.length > 0;
this.product_ingredient = _.get(fhirResource, 'ingredient', []);
this.has_product_ingredient =
Array.isArray(this.product_ingredient) && this.product_ingredient.length > 0;
this.status = _.get(fhirResource, 'status');
this.hasProduct = this.hasProductForm || this.hasProductIngredient;
this.has_product = this.has_product_form || this.has_product_ingredient;
};
resourceDTO(fhirResource:any, fhirVersion: fhirVersions){

View File

@ -0,0 +1,7 @@
import { MedicationRequestModel } from './medication-request-model';
describe('MedicationRequestModel', () => {
it('should create an instance', () => {
expect(new MedicationRequestModel()).toBeTruthy();
});
});

View File

@ -0,0 +1,40 @@
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {CodingModel} from '../datatypes/coding-model';
import {fhirVersions, ResourceType} from '../constants';
import {FastenOptions} from '../fasten/fasten-options';
import * as _ from "lodash";
import {ReferenceModel} from '../datatypes/reference-model';
export class MedicationRequestModel extends FastenDisplayModel {
display: string|undefined
medication_reference: ReferenceModel|undefined
medication_codeable_concept: CodingModel|undefined
reason_code: string|undefined
dosage_instruction: string|undefined
has_dosage_instruction: boolean|undefined
requester: string|undefined
created: string|undefined
intent: string|undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.MedicationRequest
this.medication_reference = _.get(fhirResource, 'medicationReference');
this.medication_codeable_concept = _.get(
fhirResource,
'medicationCodeableConcept.coding.0',
);
this.reason_code = _.get(fhirResource, 'reasonCode');
this.dosage_instruction = _.get(fhirResource, 'dosageInstruction');
this.has_dosage_instruction =
Array.isArray(this.dosage_instruction) && this.dosage_instruction.length > 0;
this.requester =
_.get(fhirResource, 'requester.agent') || _.get(fhirResource, 'requester');
this.created = _.get(fhirResource, 'authoredOn');
this.intent = _.get(fhirResource, 'intent');
this.display = _.get(fhirResource, 'medicationCodeableConcept.text') || this.medication_codeable_concept?.display || _.get(fhirResource, 'medicationCodeableConcept.text') || this.medication_reference?.display
}
}

View File

@ -1,47 +1,51 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class ObservationModel {
export class ObservationModel extends FastenDisplayModel {
effectiveDate: string
codeCodingDisplay: string
codeText: string
valueQuantityValue: string
valueQuantityUnit: string
effective_date: string
code_coding_display: string
code_text: string
value_quantity_value: string
value_quantity_unit: string
status: string
valueCodeableConceptText: string
valueCodeableConceptCodingDisplay: string
valueCodeableConceptCoding: string
valueQuantityValueNumber: string
value_codeable_concept_text: string
value_codeable_concept_coding_display: string
value_codeable_concept_coding: string
value_quantity_value_number: string
subject: string
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
this.effectiveDate = _.get(fhirResource, 'effectiveDateTime');
this.codeCodingDisplay = _.get(fhirResource, 'code.coding.0.display');
this.codeText = _.get(fhirResource, 'code.text', '');
this.valueQuantityValue = _.get(fhirResource, 'valueQuantity.value', '');
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.Observation
this.effective_date = _.get(fhirResource, 'effectiveDateTime');
this.code_coding_display = _.get(fhirResource, 'code.coding.0.display');
this.code_text = _.get(fhirResource, 'code.text', '');
this.value_quantity_value = _.get(fhirResource, 'valueQuantity.value', '');
// const issued = _.get(fhirResource, 'issued', '');
this.valueQuantityUnit = _.get(fhirResource, 'valueQuantity.unit', '');
this.value_quantity_unit = _.get(fhirResource, 'valueQuantity.unit', '');
this.status = _.get(fhirResource, 'status', '');
this.valueCodeableConceptText = _.get(
this.value_codeable_concept_text = _.get(
fhirResource,
'valueCodeableConcept.text',
);
this.valueCodeableConceptCodingDisplay = _.get(
this.value_codeable_concept_coding_display = _.get(
fhirResource,
'valueCodeableConcept.coding[0].display',
);
this.valueCodeableConceptCoding = _.get(
this.value_codeable_concept_coding = _.get(
fhirResource,
'valueCodeableConcept.coding',
[],
);
this.valueQuantityValueNumber = this.valueQuantityValue;
this.value_quantity_value_number = this.value_quantity_value;
this.subject = _.get(fhirResource, 'subject');
}

View File

@ -1,17 +1,22 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class OrganizationModel extends FastenDisplayModel {
export class OrganizationModel {
identifier: string|undefined
name: string|undefined
addresses: string|undefined
telecom: string|undefined
typeCodings: any[]|undefined
type_codings: any[]|undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.Organization
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
@ -23,11 +28,11 @@ export class OrganizationModel {
this.telecom = _.get(fhirResource, 'telecom');
};
dstu2DTO(fhirResource:any){
this.typeCodings = _.get(fhirResource, 'type.coding');
this.type_codings = _.get(fhirResource, 'type.coding');
};
stu3DTO(fhirResource:any){
let typeCodings = _.get(fhirResource, 'type', []).map((type: { coding: any; }) => type.coding);
this.typeCodings = _.flatten(typeCodings)
this.type_codings = _.flatten(typeCodings)
};
resourceDTO(fhirResource:any, fhirVersion:fhirVersions){

View File

@ -1,46 +1,52 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class PatientModel extends FastenDisplayModel {
export class PatientModel {
id: string|undefined
patientName: string|undefined
patientBirthDate: string|undefined
patientGender: string|undefined
patientContact: string|undefined
patientAddress: string|undefined
patientPhones: string|undefined
communicationLanguage: string|undefined
hasCommunicationLanguage: boolean|undefined
hasPatientPhones: boolean|undefined
patient_name: string|undefined
patient_birthdate: string|undefined
patient_gender: string|undefined
patient_contact: string|undefined
patient_address: string|undefined
patient_phones: string|undefined
communication_language: string|undefined
has_communication_language: boolean|undefined
has_patient_phones: boolean|undefined
active: string|undefined
activeStatus: string|undefined
isDeceased: boolean|undefined
deceasedDate: string|undefined
active_status: string|undefined
is_deceased: boolean|undefined
deceased_date: string|undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.Patient
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
this.id = this.getId(fhirResource);
this.patientName = this.getNames(fhirResource);
this.patientBirthDate = this.getBirthDate(fhirResource);
this.patientGender = this.getGender(fhirResource);
this.patientContact = _.get(fhirResource, 'contact[0]');
this.patientAddress = _.get(fhirResource, 'address[0]');
this.patientPhones = _.get(fhirResource, 'telecom', []).filter(
this.patient_name = this.getNames(fhirResource);
this.patient_birthdate = this.getBirthDate(fhirResource);
this.patient_gender = this.getGender(fhirResource);
this.patient_contact = _.get(fhirResource, 'contact[0]');
this.patient_address = _.get(fhirResource, 'address[0]');
this.patient_phones = _.get(fhirResource, 'telecom', []).filter(
(telecom: any) => telecom.system === 'phone',
);
let communicationLanguage = _.get(fhirResource, 'communication', [])
.filter((item: any) => Boolean(_.get(item, 'language.coding', null)))
.map((item: any) => item.language.coding);
this.communicationLanguage = _.get(communicationLanguage, '0', []);
this.hasCommunicationLanguage = !_.isEmpty(communicationLanguage);
this.hasPatientPhones = !_.isEmpty(this.patientPhones);
this.communication_language = _.get(communicationLanguage, '0', []);
this.has_communication_language = !_.isEmpty(communicationLanguage);
this.has_patient_phones = !_.isEmpty(this.patient_phones);
this.active = _.get(fhirResource, 'active', false);
this.activeStatus = this.active ? 'active' : 'inactive';
this.active_status = this.active ? 'active' : 'inactive';
let deceasedBoolean = _.get(fhirResource, 'deceasedBoolean', false);
this.deceasedDate = _.get(fhirResource, 'deceasedDateTime');
this.isDeceased = deceasedBoolean || this.deceasedDate;
this.deceased_date = _.get(fhirResource, 'deceasedDateTime');
this.is_deceased = deceasedBoolean || this.deceased_date;
}
getId(fhirResource: any) {

View File

@ -1,24 +1,29 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class PractitionerModel extends FastenDisplayModel {
export class PractitionerModel {
identifier: string|undefined
name: string|undefined
name: any|undefined
gender: string|undefined
status: string|undefined
isContactData: boolean|undefined
is_contact_data: boolean|undefined
contactData: {
name: string,
relationship: string
}|undefined
telecom: string|undefined
address: string|undefined
birthDate: string|undefined
birthdate: string|undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.Practitioner
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
@ -28,8 +33,8 @@ export class PractitionerModel {
this.identifier = _.get(fhirResource, 'identifier', '');
this.gender = _.get(fhirResource, 'gender', '');
this.status = _.get(fhirResource, 'active') === true ? 'active' : '';
this.isContactData = _.has(fhirResource, 'contact[0]');
this.birthDate = _.get(fhirResource, 'birthDate');
this.is_contact_data = _.has(fhirResource, 'contact[0]');
this.birthdate = _.get(fhirResource, 'birthDate');
this.contactData = {
name: _.get(fhirResource, 'contact[0].name'),
relationship: _.get(fhirResource, 'contact[0].relationship[0].text'),

View File

@ -1,17 +1,23 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class PractitionerRoleModel extends FastenDisplayModel {
export class PractitionerRoleModel {
codes: string|undefined
status: string|undefined
specialties: string|undefined
organization: string|undefined
practitioner: string|undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.PractitionerRole
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}

View File

@ -1,48 +1,54 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class ProcedureModel extends FastenDisplayModel {
export class ProcedureModel {
display: string|undefined;
status: string|undefined;
hasPerformedDateTime: boolean|undefined;
performedDateTime: string|undefined;
performedPeriodStart: string|undefined;
performedPeriodEnd: string|undefined;
hasPerformedPeriod: boolean|undefined;
hasCoding: boolean|undefined;
has_performed_datetime: boolean|undefined;
performed_datetime: string|undefined;
performed_period_start: string|undefined;
performed_period_end: string|undefined;
has_performed_period: boolean|undefined;
has_coding: boolean|undefined;
coding: string|undefined;
category: string|undefined;
locationReference: string|undefined;
hasPerformerData: boolean|undefined;
location_reference: string|undefined;
has_performer_data: boolean|undefined;
performer: string|undefined;
hasReasonCode: boolean|undefined;
reasonCode: string|undefined;
hasNote: boolean|undefined;
has_reason_code: boolean|undefined;
reason_code: string|undefined;
has_note: boolean|undefined;
note: string|undefined;
outcome: string|undefined;
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.Procedure
this.display =
_.get(fhirResource, 'code.coding[0].display') ||
_.get(fhirResource, 'code.text');
this.status = _.get(fhirResource, 'status', '');
this.hasPerformedDateTime = _.has(fhirResource, 'performedDateTime');
this.performedDateTime = _.get(fhirResource, 'performedDateTime');
this.performedPeriodStart = _.get(fhirResource, 'performedPeriod.start');
this.performedPeriodEnd = _.get(fhirResource, 'performedPeriod.end');
this.hasPerformedPeriod = !!(this.performedPeriodStart || this.performedPeriodEnd);
this.hasCoding = _.has(fhirResource, 'code.coding');
this.has_performed_datetime = _.has(fhirResource, 'performedDateTime');
this.performed_datetime = _.get(fhirResource, 'performedDateTime');
this.performed_period_start = _.get(fhirResource, 'performedPeriod.start');
this.performed_period_end = _.get(fhirResource, 'performedPeriod.end');
this.has_performed_period = !!(this.performed_period_start || this.performed_period_end);
this.has_coding = _.has(fhirResource, 'code.coding');
this.coding = _.get(fhirResource, 'code.coding', []);
this.category = _.get(fhirResource, 'category.coding[0]');
this.locationReference = _.get(fhirResource, 'location');
this.hasPerformerData = _.has(fhirResource, 'performer.0.actor.display');
this.location_reference = _.get(fhirResource, 'location');
this.has_performer_data = _.has(fhirResource, 'performer.0.actor.display');
this.performer = _.get(fhirResource, 'performer', []);
this.hasReasonCode = _.has(fhirResource, 'reasonCode');
this.reasonCode = _.get(fhirResource, 'reasonCode', []);
this.hasNote = _.has(fhirResource, 'note');
this.has_reason_code = _.has(fhirResource, 'reasonCode');
this.reason_code = _.get(fhirResource, 'reasonCode', []);
this.has_note = _.has(fhirResource, 'note');
this.note = _.get(fhirResource, 'note', []);
this.outcome = _.get(fhirResource, 'outcome');

View File

@ -1,28 +1,34 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class RelatedPersonModel extends FastenDisplayModel {
export class RelatedPersonModel {
patient: string|undefined
name: string|undefined
birthDate: string|undefined
birthdate: string|undefined
gender: string|undefined
address: string|undefined
relatedPersonTelecom: string|undefined
related_person_telecom: string|undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.RelatedPerson
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
commonDTO(fhirResource:any){
this.patient = _.get(fhirResource, 'patient');
this.birthDate = _.get(fhirResource, 'birthDate');
this.birthdate = _.get(fhirResource, 'birthDate');
this.gender = _.get(fhirResource, 'gender');
this.address = _.get(fhirResource, 'address[0]');
this.relatedPersonTelecom = _.get(fhirResource, 'telecom', []).filter(
this.related_person_telecom = _.get(fhirResource, 'telecom', []).filter(
(telecom: any) => telecom.system === 'phone',
);
};

View File

@ -1,30 +1,36 @@
import {fhirVersions} from '../constants';
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
export class ResearchStudyModel extends FastenDisplayModel {
export class ResearchStudyModel {
title:string|undefined
status:string|undefined
categoryCoding:string|undefined
focusCoding:string|undefined
protocolReference:string|undefined
partOfReference:string|undefined
category_coding:string|undefined
focus_coding:string|undefined
protocol_reference:string|undefined
part_of_reference:string|undefined
contacts:string|undefined
keywordConcepts:string|undefined
keyword_concepts:string|undefined
period:string|undefined
enrollmentReferences:string|undefined
sponsorReference:string|undefined
principalInvestigatorReference:string|undefined
siteReferences:string|undefined
enrollment_references:string|undefined
sponsor_reference:string|undefined
principal_investigator_reference:string|undefined
site_references:string|undefined
comments:string|undefined
description:string|undefined
arms:string|undefined
location:string|undefined
primaryPurposeType:string|undefined
primary_purpose_type:string|undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.ResearchStudy
constructor(fhirResource: any, fhirVersion?: fhirVersions) {
this.resourceDTO(fhirResource, fhirVersion || fhirVersions.R4);
}
@ -32,24 +38,24 @@ export class ResearchStudyModel {
commonDTO(fhirResource:any) {
this.title = _.get(fhirResource, 'title', 'Research Study');
this.status = _.get(fhirResource, 'status');
this.categoryCoding = _.get(fhirResource, 'category[0].coding[0]');
this.focusCoding = _.get(fhirResource, 'focus[0].coding[0]');
this.protocolReference = _.get(fhirResource, 'protocol');
this.partOfReference = _.get(fhirResource, 'partOf');
this.category_coding = _.get(fhirResource, 'category[0].coding[0]');
this.focus_coding = _.get(fhirResource, 'focus[0].coding[0]');
this.protocol_reference = _.get(fhirResource, 'protocol');
this.part_of_reference = _.get(fhirResource, 'partOf');
this.contacts = _.get(fhirResource, 'contact', []).map((contact: any) => {
const name = _.get(contact, 'name');
const telecoms = _.get(contact, 'telecom');
return { name, telecoms };
});
this.keywordConcepts = _.get(fhirResource, 'keyword', []);
this.keyword_concepts = _.get(fhirResource, 'keyword', []);
this.period = _.get(fhirResource, 'period', {});
this.enrollmentReferences = _.get(fhirResource, 'enrollment', []);
this.sponsorReference = _.get(fhirResource, 'sponsor');
this.principalInvestigatorReference = _.get(
this.enrollment_references = _.get(fhirResource, 'enrollment', []);
this.sponsor_reference = _.get(fhirResource, 'sponsor');
this.principal_investigator_reference = _.get(
fhirResource,
'principalInvestigator',
);
this.siteReferences = _.get(fhirResource, 'site', []);
this.site_references = _.get(fhirResource, 'site', []);
this.comments = _.get(fhirResource, 'note', []);
this.description = _.get(fhirResource, 'description');
this.arms = _.get(fhirResource, 'arm', []).map((arm: any) => {
@ -62,7 +68,7 @@ export class ResearchStudyModel {
r4DTO(fhirResource:any) {
this.location = _.get(fhirResource, 'location');
this.primaryPurposeType = _.get(fhirResource, 'primaryPurposeType');
this.primary_purpose_type = _.get(fhirResource, 'primaryPurposeType');
};

4
go.mod
View File

@ -5,7 +5,7 @@ go 1.18
require (
github.com/analogj/go-util v0.0.0-20210417161720-39b497cca03b
github.com/dominikbraun/graph v0.15.0
github.com/fastenhealth/fasten-sources v0.0.11
github.com/fastenhealth/fasten-sources v0.0.14
github.com/gin-gonic/gin v1.8.1
github.com/glebarez/sqlite v1.5.0
github.com/golang-jwt/jwt/v4 v4.4.2
@ -17,6 +17,7 @@ require (
github.com/urfave/cli/v2 v2.11.2
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17
golang.org/x/net v0.2.0
gorm.io/datatypes v1.0.7
gorm.io/gorm v1.24.1
)
@ -62,7 +63,6 @@ require (
github.com/subosito/gotenv v1.3.0 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/oauth2 v0.2.0 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/term v0.2.0 // indirect

4
go.sum
View File

@ -74,8 +74,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fastenhealth/fasten-sources v0.0.11 h1:GuU/fEs7yMtFY+Pe0j9z4XzJQtEZ3Yx3pDNqzsAWnOk=
github.com/fastenhealth/fasten-sources v0.0.11/go.mod h1:NBh0n2OBwJC10CHqkIx4WYQcDBRywJU4TAR8rSfR2Ts=
github.com/fastenhealth/fasten-sources v0.0.14 h1:+SGn/gzhC9MU2OzZy8hn9giiuOs6lJgIKPReY8STe5U=
github.com/fastenhealth/fasten-sources v0.0.14/go.mod h1:NBh0n2OBwJC10CHqkIx4WYQcDBRywJU4TAR8rSfR2Ts=
github.com/fastenhealth/gofhir-models v0.0.4 h1:Q//StwNXGfK+WAS2DckGBHAP1R4cHMRZEF/sLGgmR04=
github.com/fastenhealth/gofhir-models v0.0.4/go.mod h1:xB8ikGxu3bUq2b1JYV+CZpHqBaLXpOizFR0eFBCunis=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=