fasten-onprem/backend/pkg/hub/internal/fhir/manual/client.go

165 lines
5.5 KiB
Go

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