fixing composite unique index (when embedded the index is not unique).

creating a lookup table for retrieving Resource references (and generated UUIDs).
Adding organization processing.
This commit is contained in:
Jason Kulatunga 2022-08-30 21:36:40 -07:00
parent 9902daa75d
commit 79755d8e8b
7 changed files with 136 additions and 14 deletions

View File

@ -9,8 +9,9 @@ type DatabaseRepository interface {
Close() error
GetCurrentUser() models.User
UpsertProfile(ctx context.Context, profile models.Profile) error
UpsertProfile(context.Context, *models.Profile) error
UpsertOrganziation(context.Context, *models.Organization) error
CreateSource(ctx context.Context, providerCreds *models.Source) error
GetSources(ctx context.Context) ([]models.Source, error)
CreateSource(context.Context, *models.Source) error
GetSources(context.Context) ([]models.Source, error)
}

View File

@ -47,6 +47,7 @@ func NewRepository(appConfig config.Interface, globalLogger logrus.FieldLogger)
&models.User{},
&models.Source{},
&models.Profile{},
&models.Organization{},
)
if err != nil {
return nil, fmt.Errorf("Failed to automigrate! - %v", err)
@ -86,15 +87,28 @@ func (sr *sqliteRepository) GetCurrentUser() models.User {
}
// UpsertSourceResource Create or Update record in database
func (sr *sqliteRepository) UpsertProfile(ctx context.Context, profile models.Profile) error {
if sr.gormClient.Debug().WithContext(ctx).Model(&profile).
func (sr *sqliteRepository) UpsertProfile(ctx context.Context, profile *models.Profile) error {
if sr.gormClient.Debug().WithContext(ctx).Model(profile).
Where(models.OriginBase{
SourceID: profile.GetSourceID(),
SourceResourceID: profile.GetSourceResourceID(),
SourceResourceType: profile.GetSourceResourceType(), //TODO: and UpdatedAt > old UpdatedAt
}).Updates(&profile).RowsAffected == 0 {
}).Updates(profile).RowsAffected == 0 {
sr.logger.Infof("profile does not exist, creating: %s %s %s", profile.GetSourceID(), profile.GetSourceResourceID(), profile.GetSourceResourceType())
return sr.gormClient.Debug().Create(&profile).Error
return sr.gormClient.Debug().Create(profile).Error
}
return nil
}
func (sr *sqliteRepository) UpsertOrganziation(ctx context.Context, org *models.Organization) error {
if sr.gormClient.Debug().WithContext(ctx).Model(org).
Where(models.OriginBase{
SourceID: org.GetSourceID(),
SourceResourceID: org.GetSourceResourceID(),
SourceResourceType: org.GetSourceResourceType(), //TODO: and UpdatedAt > old UpdatedAt
}).Updates(org).RowsAffected == 0 {
sr.logger.Infof("org does not exist, creating: %s %s %s", org.GetSourceID(), org.GetSourceResourceID(), org.GetSourceResourceType())
return sr.gormClient.Debug().Create(org).Error
}
return nil
}

View File

@ -107,3 +107,39 @@ func (c *FHIR401Client) ProcessPatients(patients []fhir401.Patient) ([]models.Pr
return profiles, nil
}
func (c *FHIR401Client) ProcessOrganizations(orgs []fhir401.Organization) ([]models.Organization, error) {
apiOrganizations := []models.Organization{}
for _, org := range orgs {
apiOrganization := models.Organization{
OriginBase: models.OriginBase{
ModelBase: models.ModelBase{},
UserID: c.Source.UserID,
SourceID: c.Source.ID,
SourceResourceID: *org.Id,
SourceResourceType: fhir401.ResourceTypeOrganization.Code(),
},
Address: models.Address{},
}
if org.Meta != nil && org.Meta.LastUpdated != nil {
if parsed, err := time.Parse(time.RFC3339, *org.Meta.LastUpdated); err == nil {
apiOrganization.UpdatedAt = parsed
}
}
if org.Address != nil && len(org.Address) > 0 {
itemAddress := org.Address[0]
apiOrganization.Address.City = itemAddress.City
apiOrganization.Address.Country = itemAddress.Country
apiOrganization.Address.State = itemAddress.State
apiOrganization.Address.Street = itemAddress.Line
apiOrganization.Address.Zip = itemAddress.PostalCode
}
apiOrganization.Name = org.Name
apiOrganization.Active = org.Active
apiOrganizations = append(apiOrganizations, apiOrganization)
}
return apiOrganizations, nil
}

View File

@ -2,12 +2,14 @@ package cigna
import (
"context"
"fmt"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/base"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/fastenhealth/gofhir-models/fhir401"
fhirutils "github.com/fastenhealth/gofhir-models/fhir401/utils"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"net/http"
)
@ -36,6 +38,8 @@ func (c CignaClient) SyncAll(db database.DatabaseRepository) error {
resources = append(resources, resource)
}
resourceRefLookup := map[string]uuid.UUID{}
//////////////////////////////////////////////////////////////////////
// Patient
//////////////////////////////////////////////////////////////////////
@ -45,17 +49,35 @@ func (c CignaClient) SyncAll(db database.DatabaseRepository) error {
patientResources = append(patientResources, patient)
}
}
patientProfiles, err := c.ProcessPatients(patientResources)
for _, profile := range patientProfiles {
err = db.UpsertProfile(context.Background(), profile)
apiProfiles, err := c.ProcessPatients(patientResources)
for _, profile := range apiProfiles {
err = db.UpsertProfile(context.Background(), &profile)
if err != nil {
return err
}
//add upserted resource uuids to lookup
resourceRefLookup[fmt.Sprintf("%s/%s", profile.SourceResourceType, profile.SourceResourceID)] = profile.ID
}
//////////////////////////////////////////////////////////////////////
// Patient
// Organization
//////////////////////////////////////////////////////////////////////
organizations := []fhir401.Organization{}
for _, resource := range resources {
if org, isOrganization := resource.(fhir401.Organization); isOrganization {
organizations = append(organizations, org)
}
}
apiOrgs, err := c.ProcessOrganizations(organizations)
for _, apiOrg := range apiOrgs {
err = db.UpsertOrganziation(context.Background(), &apiOrg)
if err != nil {
return err
}
//add upserted resource uuids to lookup
resourceRefLookup[fmt.Sprintf("%s/%s", apiOrg.SourceResourceType, apiOrg.SourceResourceID)] = apiOrg.ID
}
return nil
}

View File

@ -26,10 +26,10 @@ type OriginBase struct {
UserID uuid.UUID `json:"user_id"`
Source Source `json:"source" gorm:"-"`
SourceID uuid.UUID `json:"source_id" gorm:"not null;uniqueIndex:idx_source_resource_id"`
SourceID uuid.UUID `json:"source_id" gorm:"not null;index:,unique,composite:source_resource_id"`
SourceResourceType string `json:"source_resource_type" gorm:"not null;uniqueIndex:idx_source_resource_id"`
SourceResourceID string `json:"source_resource_id" gorm:"not null;uniqueIndex:idx_source_resource_id"`
SourceResourceType string `json:"source_resource_type" gorm:"not null;index:,unique,composite:source_resource_id"`
SourceResourceID string `json:"source_resource_id" gorm:"not null;index:,unique,composite:source_resource_id"`
}
func (o OriginBase) GetSourceID() uuid.UUID {

View File

@ -0,0 +1,40 @@
package models
import "time"
// https://reference.humanapi.co/reference/encounters
type Encounter struct {
OriginBase
DateTime time.Time `json:"time,omitempty"` //dateTime Date (optional) The date of the encounter
VisitType *string `json:"visitType,omitempty"` // visitType String (optional) The type of visit
Provider Provider `json:"provider,omitempty" gorm:"serializer:json;default:'{}'"` // provider Object (optional) The provider for the encounter (see provider object)
//Prescriptions []Prescription `json:"prescriptions,omitempty"` // prescriptions Array[Object] (optional) A list of prescriptions provided during the encounter (see link object)
//Diagnoses []Diagnosis `json:"diagnoses,omitempty"` // diagnoses Array[Object] (optional) A list of diagnoses for the encounter where object contains a "name" field e.g. *[{"name": "Sacroiliac dysfunction"}, {"name": "Bilateral hand pain"}]
//vitals Object (optional) Vitals captured during the encounter (e.g. {"temperature" : 95.2 [degF]","weight" : 180 [lb_av]","height" : "69 [in_us]"})
//vitalSigns Array[Object] (optional) A list of vital signs from the encounter (see link object)
Reasons []string `json:"reasons,omitempty"` // reasons Array[String] (optional) A list of reasons for the encounter (e.g. [Follow-up, 'Consult, 'DYSPHONIA', 'Back Pain])
Orders []Order `json:"orders,omitempty" gorm:"serializer:json;default:'{}'"` // orders Array[Object] (optional) A list of medication orders for the patient (see orders object)
//testResults Array[Object] (optional) A list of test results for the patient (see link object)
//plansOfCare Array[Object] (optional) A list of plans of care from the encounter (see link object)
//medications Array[Object] (optional) A list of medications used by the patient. Objects in array can have some or many of the properties of medications. Common properties are "name", "productName", "startDate", "endDate", "instructions".
FollowUpInstructions *string `json:"followUpInstructions,omitempty"` // followUpInstructions String (optional) Follow-up instructions
//Organization Organization `json:"organization,omitempty"` // organization Object (optional) (See organizations object)
//codes Array[Object] (optional) (See codes)
}
type Order struct {
Name *string `json:"name,omitempty"` // name String (optional) The name of the order
CodeType *string `json:"codeType,omitempty"` // codeType String (optional) The code type of the order (e.g. “CPT( R )”, “Custom”)
ExpectedDate time.Time `json:"expectedDate,omitempty"` // expectedDate Date (optional) The date the order is expected
ExpireDate time.Time `json:"expireDate,omitempty"` // expireDate Date (optional) The date the order expires
ProcedureCode *string `json:"procedureCode,omitempty"` // procedureCode String (optional) The procedure code of the order
OrderType *string `json:"type,omitempty"` // type String (optional) The type of the order (e.g. “Lab”, “Procedures”)
//Name string `codes:"codes,omitempty"` // codes Array[Object] (optional) (See codes)
}
type Provider struct {
Name *string `json:"name,omitempty"` //name String (optional) Name of the provider
DepartmentName *string `json:"departmentName,omitempty"` //departmentName String (optional) Name of the provider department
Address *string `json:"address,omitempty"` //address String (optional) Address of the provider
}

View File

@ -0,0 +1,9 @@
package models
type Organization struct {
OriginBase
Name *string `json:"name,omitempty"` //name String (optional) The name of the organization
Address Address `json:"address,omitempty" gorm:"serializer:json;default:'{}'"` //address String (optional) Address of the provider
Active *bool `json:"active,omitempty"`
}