Skip to content

Commit

Permalink
refactor: move search patient logic to infra layer
Browse files Browse the repository at this point in the history
  • Loading branch information
Salaton committed Jan 30, 2023
1 parent 8160cad commit f10811c
Show file tree
Hide file tree
Showing 8 changed files with 369 additions and 400 deletions.
111 changes: 109 additions & 2 deletions pkg/clinical/infrastructure/datastore/cloudhealthcare/fhir.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ import (
dataset "github.com/savannahghi/clinical/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhirdataset"
"github.com/savannahghi/clinical/pkg/clinical/repository"
"github.com/savannahghi/converterandformatter"
"github.com/savannahghi/firebasetools"
"github.com/savannahghi/scalarutils"
)

// constants and defaults
const (
internalError = "an error occurred on our end. Please try again later"
timeFormatStr = "2006-01-02T15:04:05+03:00"
internalError = "an error occurred on our end. Please try again later"
timeFormatStr = "2006-01-02T15:04:05+03:00"
notFoundWithSearchParams = "could not find a patient with the provided parameters"
)

var (
Expand Down Expand Up @@ -1884,6 +1886,111 @@ func (fh *StoreImpl) UpdateFHIRResource(resourceType, fhirResourceID string, pay
return fh.Dataset.UpdateFHIRResource(resourceType, fhirResourceID, payload)
}

// SearchFHIRPatient searches for a FHIR patient
func (fh *StoreImpl) SearchFHIRPatient(ctx context.Context, searchParams string) (*domain.PatientConnection, error) {
params := url.Values{}
params.Add("_content", searchParams)

bs, err := fh.Dataset.POSTRequest(patientResourceType, "_search", params, nil)
if err != nil {
return nil, fmt.Errorf("unable to search for patient: %w", err)
}

respMap := make(map[string]interface{})
err = json.Unmarshal(bs, &respMap)
if err != nil {
utils.ReportErrorToSentry(err)
log.Errorf("unable to unmarshal FHIR search response: %v", err)
return nil, fmt.Errorf(notFoundWithSearchParams)
}

mandatoryKeys := []string{"resourceType", "type", "total", "link"}
for _, k := range mandatoryKeys {
_, found := respMap[k]
if !found {
log.Errorf("search response does not have key '%s'", k)
return nil, fmt.Errorf(notFoundWithSearchParams)
}
}
resourceType, ok := respMap["resourceType"].(string)
if !ok {
return nil, fmt.Errorf("search: the resourceType is not a string")
}
if resourceType != "Bundle" {
log.Errorf("Search: the resourceType value is not 'Bundle' as expected")
return nil, fmt.Errorf(notFoundWithSearchParams)
}

resultType, ok := respMap["type"].(string)
if !ok {
return nil, fmt.Errorf("search: the type is not a string")
}
if resultType != "searchset" {
log.Errorf("Search: the type value is not 'searchset' as expected")
return nil, fmt.Errorf(notFoundWithSearchParams)
}

respEntries := respMap["entry"]
if respEntries == nil {
return &domain.PatientConnection{
Edges: []*domain.PatientEdge{},
PageInfo: &firebasetools.PageInfo{},
}, nil
}
entries, ok := respEntries.([]interface{})
if !ok {
log.Errorf("Search: entries is not a list of maps, it is: %T", respEntries)
return nil, fmt.Errorf(notFoundWithSearchParams)
}

output := domain.PatientConnection{}
for _, en := range entries {
entry, ok := en.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("expected each entry to be map, they are %T instead", en)
}
expectedKeys := []string{"fullUrl", "resource", "search"}
for _, k := range expectedKeys {
_, found := entry[k]
if !found {
log.Errorf("search entry does not have key '%s'", k)
return nil, fmt.Errorf(notFoundWithSearchParams)
}
}

resource := entry["resource"].(map[string]interface{})

resource = birthdateMapper(resource)
resource = identifierMapper(resource)
resource = nameMapper(resource)
resource = telecomMapper(resource)
resource = addressMapper(resource)
resource = photoMapper(resource)
resource = contactMapper(resource)

var patient domain.FHIRPatient

err := mapstructure.Decode(resource, &patient)
if err != nil {
utils.ReportErrorToSentry(err)
log.Errorf("unable to map decode resource: %v", err)
return nil, fmt.Errorf(internalError)
}

hasOpenEpisodes, err := fh.HasOpenEpisode(ctx, patient)
if err != nil {
utils.ReportErrorToSentry(err)
log.Errorf("error while checking if hasOpenEpisodes: %v", err)
return nil, fmt.Errorf(internalError)
}
output.Edges = append(output.Edges, &domain.PatientEdge{
Node: &patient,
HasOpenEpisodes: hasOpenEpisodes,
})
}
return &output, nil
}

// POSTRequest is used to manually compose POST requests to the FHIR service
func (fh *StoreImpl) POSTRequest(resourceName string, path string, params url.Values, body io.Reader) ([]byte, error) {
return fh.Dataset.POSTRequest(resourceName, path, params, body)
Expand Down
242 changes: 242 additions & 0 deletions pkg/clinical/infrastructure/datastore/cloudhealthcare/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"net/url"

"github.com/labstack/gommon/log"
"github.com/savannahghi/clinical/pkg/clinical/application/common/helpers"
"github.com/savannahghi/scalarutils"
)

func validateSearchParams(params map[string]interface{}) (url.Values, error) {
Expand Down Expand Up @@ -111,3 +113,243 @@ func (fh *StoreImpl) searchFilterHelper(
}
return results, nil
}

func birthdateMapper(resource map[string]interface{}) map[string]interface{} {

resourceCopy := resource

parsedDate := helpers.ParseDate(resourceCopy["birthDate"].(string))

dateMap := make(map[string]interface{})

dateMap["year"] = parsedDate.Year()
dateMap["month"] = parsedDate.Month()
dateMap["day"] = parsedDate.Day()

resourceCopy["birthDate"] = dateMap

return resourceCopy
}

func periodMapper(period map[string]interface{}) map[string]interface{} {

periodCopy := period

parsedStartDate := helpers.ParseDate(periodCopy["start"].(string))

periodCopy["start"] = scalarutils.DateTime(parsedStartDate.Format(timeFormatStr))

parsedEndDate := helpers.ParseDate(periodCopy["end"].(string))

periodCopy["end"] = scalarutils.DateTime(parsedEndDate.Format(timeFormatStr))

return periodCopy
}

func identifierMapper(resource map[string]interface{}) map[string]interface{} {

resourceCopy := resource

if _, ok := resource["identifier"]; ok {

newIdentifiers := []map[string]interface{}{}

for _, identifier := range resource["identifier"].([]interface{}) {

identifier := identifier.(map[string]interface{})

if _, ok := identifier["period"]; ok {

period := identifier["period"].(map[string]interface{})
newPeriod := periodMapper(period)

identifier["period"] = newPeriod
}

newIdentifiers = append(newIdentifiers, identifier)
}

resourceCopy["identifier"] = newIdentifiers
}

return resourceCopy
}

func nameMapper(resource map[string]interface{}) map[string]interface{} {

resourceCopy := resource

newNames := []map[string]interface{}{}

if _, ok := resource["name"]; ok {

for _, name := range resource["name"].([]interface{}) {

name := name.(map[string]interface{})

if _, ok := name["period"]; ok {

period := name["period"].(map[string]interface{})
newPeriod := periodMapper(period)

name["period"] = newPeriod
}

newNames = append(newNames, name)
}

}

resourceCopy["name"] = newNames

return resourceCopy
}

func telecomMapper(resource map[string]interface{}) map[string]interface{} {

resourceCopy := resource

newTelecoms := []map[string]interface{}{}

if _, ok := resource["telecom"]; ok {

for _, telecom := range resource["telecom"].([]interface{}) {

telecom := telecom.(map[string]interface{})

if _, ok := telecom["period"]; ok {

period := telecom["period"].(map[string]interface{})
newPeriod := periodMapper(period)

telecom["period"] = newPeriod
}

newTelecoms = append(newTelecoms, telecom)
}

}

resourceCopy["telecom"] = newTelecoms

return resourceCopy
}

func addressMapper(resource map[string]interface{}) map[string]interface{} {

resourceCopy := resource

newAddresses := []map[string]interface{}{}

if _, ok := resource["address"]; ok {

for _, address := range resource["address"].([]interface{}) {

address := address.(map[string]interface{})

if _, ok := address["period"]; ok {

period := address["period"].(map[string]interface{})
newPeriod := periodMapper(period)

address["period"] = newPeriod
}

newAddresses = append(newAddresses, address)
}
}

resourceCopy["address"] = newAddresses

return resourceCopy
}

func photoMapper(resource map[string]interface{}) map[string]interface{} {

resourceCopy := resource

newPhotos := []map[string]interface{}{}

if _, ok := resource["photo"]; ok {

for _, photo := range resource["photo"].([]interface{}) {

photo := photo.(map[string]interface{})

parsedDate := helpers.ParseDate(photo["creation"].(string))

photo["creation"] = scalarutils.DateTime(parsedDate.Format(timeFormatStr))

newPhotos = append(newPhotos, photo)
}
}

resourceCopy["photo"] = newPhotos

return resourceCopy
}

func contactMapper(resource map[string]interface{}) map[string]interface{} {

resourceCopy := resource

newContacts := []map[string]interface{}{}

if _, ok := resource["contact"]; ok {

for _, contact := range resource["contact"].([]interface{}) {

contact := contact.(map[string]interface{})

if _, ok := contact["name"]; ok {

name := contact["name"].(map[string]interface{})
if _, ok := name["period"]; ok {

period := name["period"].(map[string]interface{})
newPeriod := periodMapper(period)

name["period"] = newPeriod
}

contact["name"] = name
}

if _, ok := contact["telecom"]; ok {

newTelecoms := []map[string]interface{}{}

for _, telecom := range contact["telecom"].([]interface{}) {

telecom := telecom.(map[string]interface{})

if _, ok := telecom["period"]; ok {

period := telecom["period"].(map[string]interface{})
newPeriod := periodMapper(period)

telecom["period"] = newPeriod
}

newTelecoms = append(newTelecoms, telecom)
}

contact["telecom"] = newTelecoms
}

if _, ok := contact["period"]; ok {

period := contact["period"].(map[string]interface{})
newPeriod := periodMapper(period)

contact["period"] = newPeriod
}

newContacts = append(newContacts, contact)
}
}

resourceCopy["contact"] = newContacts

return resourceCopy
}
Loading

0 comments on commit f10811c

Please sign in to comment.