Skip to content

Commit

Permalink
release: staging to production (#296)
Browse files Browse the repository at this point in the history
* add conditions to patient timeline (#276)

* fix: fix listing of patient conditions (#278)

* fix: check encounter finished status when creating allergies (#279)

* feat: add record blood sugar api (#280)

* feat: add api to fetch a patient's blood sugar observation (#281)

* add last menstrual period vital (#283)

* add diastolic blood pressure observation (#284)

* adds fetching patient's diastolic blood pressure (#286)

* feat: token caching (#287)

* feat: add composition resource (#282)

* list patient's compositions (#289)

* query observations with encounterID filter (#290)

* query observations with encounterID filter

* updates

* query conditions with encounterID (#291)

* query compositions with encounterID (#292)

* feat: add date filter query on observations (#293)

- rebased - has observations date filter changes ONLY

* feat: add date filter on conditions (#294)

- adds date filter as a search param for conditions

---------

Co-authored-by: EspiraMarvin <espiramarvin@gmail.com>
Co-authored-by: Salaton <nairouasalaton@gmail.com>
Co-authored-by: Charles Muchogo <dev@muchogo.com>
  • Loading branch information
4 people committed Dec 14, 2023
1 parent f093351 commit 582f29e
Show file tree
Hide file tree
Showing 37 changed files with 5,970 additions and 872 deletions.
23 changes: 14 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/ahmetb/go-linq/v3 v3.2.0
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/brianvoe/gofakeit v3.18.0+incompatible
github.com/chenyahui/gin-cache v1.8.1
github.com/getsentry/sentry-go v0.23.0
github.com/gin-contrib/cors v1.4.0
github.com/gin-gonic/gin v1.9.1
Expand Down Expand Up @@ -52,20 +53,23 @@ require (
firebase.google.com/go v3.13.0+incompatible // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/aws/aws-sdk-go v1.44.328 // indirect
github.com/bytedance/sonic v1.10.0 // indirect
github.com/bytedance/sonic v1.10.1 // indirect
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.1 // indirect
github.com/go-playground/validator/v10 v10.15.5 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
Expand All @@ -79,6 +83,7 @@ require (
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
github.com/imroc/req v0.3.2 // indirect
github.com/jellydator/ttlcache/v2 v2.11.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
Expand All @@ -87,7 +92,7 @@ require (
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/prometheus v0.46.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
Expand All @@ -109,13 +114,13 @@ require (
go.opentelemetry.io/otel/metric v1.16.0 // indirect
go.opentelemetry.io/otel/sdk v1.16.0 // indirect
go.opentelemetry.io/otel/trace v1.16.0 // indirect
golang.org/x/arch v0.4.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/arch v0.5.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/net v0.16.0 // indirect
golang.org/x/sync v0.4.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.12.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
Expand Down
84 changes: 66 additions & 18 deletions go.sum

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions pkg/clinical/application/common/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ const (
// MuacCIELTerminologyCode is the terminology code for mid-upper arm circumference
MuacCIELTerminologyCode = "1343"

// BloodSugarCIELTerminologyCode is the terminology code for blood sugar (Serum glucose)
BloodSugarCIELTerminologyCode = "887"

// DiastolicBloodPressureTerminologyCode is the terminology code for diastolic blood pressure
DiastolicBloodPressureCIELTerminologyCode = "5086"

// LastMenstrualPeriodCIELTerminologyCode is the terminology code for last menstrual period
LastMenstrualPeriodCIELTerminologyCode = "1427"

// Spoc2CIELTerminologyCode is the terminology code oxygen saturation
OxygenSaturationCIELTerminologyCode = "5092"

Expand Down Expand Up @@ -97,6 +106,12 @@ const (

// AddFHIRIDToProgram is the topic where details to update a program's fhir ID will be published to
AddFHIRIDToProgram = "program.fhirid.update"

// LOINCProgressNoteCode defines LOINC progress note terminology code
LOINCProgressNoteCode = "81216-4"

// LOINCAssessmentPlanCode defines LOINC assessment plan note terminology code
LOINCAssessmentPlanCode = "51847-2"
)

// DefaultIdentifier assigns a patient a code to function as their
Expand Down
2 changes: 1 addition & 1 deletion pkg/clinical/application/dto/allergy_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type AllergyConnection struct {
PageInfo PageInfo
}

// CreateConditionConnection creates a connection that follows the GraphQl Cursor Connection Specification
// CreateAllergyConnection creates a connection that follows the GraphQl Cursor Connection Specification
func CreateAllergyConnection(allergies []*Allergy, pageInfo PageInfo, total int) AllergyConnection {
connection := AllergyConnection{
TotalCount: total,
Expand Down
51 changes: 51 additions & 0 deletions pkg/clinical/application/dto/composition_output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package dto

import (
"github.com/savannahghi/scalarutils"
)

// Composition is a minimal representation of a fhir Composition
type Composition struct {
ID string `json:"id,omitempty"`
Text string `json:"text,omitempty"`
Type CompositionType `json:"type,omitempty"`
Category CompositionCategory `json:"category,omitempty"`
Status CompositionStatusEnum `json:"status,omitempty"`
PatientID string `json:"patientID,omitempty"`
EncounterID string `json:"encounterID,omitempty"`
Date *scalarutils.Date `json:"date"`
Author string `json:"author,omitempty"`
}

// CompositionEdge is a composition edge
type CompositionEdge struct {
Node Composition
Cursor string
}

// CompositionConnection is a Composition Connection Type
type CompositionConnection struct {
TotalCount int
Edges []CompositionEdge
PageInfo PageInfo
}

// CreateCompositionConnection creates a connection that follows the GraphQl Cursor Connection Specification
func CreateCompositionConnection(compositions []Composition, pageInfo PageInfo, total int) CompositionConnection {
connection := CompositionConnection{
TotalCount: total,
Edges: []CompositionEdge{},
PageInfo: pageInfo,
}

for _, composition := range compositions {
edge := CompositionEdge{
Node: composition,
Cursor: composition.ID,
}

connection.Edges = append(connection.Edges, edge)
}

return connection
}
2 changes: 1 addition & 1 deletion pkg/clinical/application/dto/condition_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type Condition struct {
Category ConditionCategory `json:"category"`

OnsetDate *scalarutils.Date `json:"onsetDate"`
RecordedDate scalarutils.Date `json:"recordedDate"`
RecordedDate *scalarutils.Date `json:"recordedDate"`

Note string `json:"note"`

Expand Down
34 changes: 33 additions & 1 deletion pkg/clinical/application/dto/enums.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ const (
ConditionStatusUnknown ConditionStatus = "UNKNOWN"
)

// ConditionStatus represents status of a FHIR condition
// ConditionCategory represents status of a FHIR condition
type ConditionCategory string

const (
Expand All @@ -133,3 +133,35 @@ const (
TerminologySourceSNOMEDCT TerminologySource = "SNOMED_CT"
TerminologySourceLOINC TerminologySource = "LOINC"
)

// LOINCCodes represents LOINC assessment codes
type LOINCCodes string

const (
LOINCPlanOfCareCode LOINCCodes = "18776-5"
LOINCAssessmentPlanCode LOINCCodes = "51847-2"
)

// CompositionCategory enum represents category composition attribute
type CompositionCategory string

const (
AssessmentAndPlan CompositionCategory = "ASSESSMENT_PLAN"
)

// Type enum represents type composition attribute
type CompositionType string

const (
ProgressNote CompositionType = "PROGRESS_NOTE"
)

// CompositionStatus enum represents status composition attribute
type CompositionStatusEnum string

const (
CompositionStatuEnumPreliminary CompositionStatusEnum = "PRELIMINARY"
CompositionStatuEnumFinal CompositionStatusEnum = "FINAL"
CompositionStatuEnumAmended CompositionStatusEnum = "AMENDED"
CompositionStatuEnumEnteredInErrorPreliminary CompositionStatusEnum = "ENTERED_IN_ERROR"
)
9 changes: 9 additions & 0 deletions pkg/clinical/application/dto/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,12 @@ type MediaInput struct {
EncounterID string `json:"encounterID"`
File map[string][]*multipart.FileHeader `form:"file" json:"file"`
}

// CompositionInput models the composition input
type CompositionInput struct {
EncounterID string `json:"encounterID"`
Type CompositionType `json:"type"`
Category CompositionCategory `json:"category"`
Status CompositionStatusEnum `json:"status"`
Note string `json:"note"`
}
2 changes: 1 addition & 1 deletion pkg/clinical/application/dto/observation_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type ObservationConnection struct {
PageInfo PageInfo
}

// CreateConditionConnection creates a connection that follows the GraphQl Cursor Connection Specification
// CreateObservationConnection creates a connection that follows the GraphQl Cursor Connection Specification
func CreateObservationConnection(observations []*Observation, pageInfo PageInfo, total int) ObservationConnection {
connection := ObservationConnection{
TotalCount: total,
Expand Down
1 change: 1 addition & 0 deletions pkg/clinical/domain/complex_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2099,6 +2099,7 @@ type CompositionStatusEnum string
const (
// CompositionStatusEnumPreliminary ...
CompositionStatusEnumPreliminary CompositionStatusEnum = "preliminary"
CompositionStatusEnumRegistered CompositionStatusEnum = "registered"
// CompositionStatusEnumFinal ...
CompositionStatusEnumFinal CompositionStatusEnum = "final"
// CompositionStatusEnumAmended ...
Expand Down
14 changes: 12 additions & 2 deletions pkg/clinical/domain/composition.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ type FHIRCompositionSection struct {
Title *string `json:"title,omitempty"`

// A code identifying the kind of content contained within the section. This must be consistent with the section title.
Code *scalarutils.Code `json:"code,omitempty"`
Code *FHIRCodeableConceptInput `json:"code,omitempty"`

// Identifies who is responsible for the information in this section, not necessarily who typed it in.
Author []*FHIRReference `json:"author,omitempty"`
Expand Down Expand Up @@ -259,7 +259,7 @@ type FHIRCompositionSectionInput struct {
Title *string `json:"title,omitempty"`

// A code identifying the kind of content contained within the section. This must be consistent with the section title.
Code *scalarutils.Code `json:"code,omitempty"`
Code *FHIRCodeableConceptInput `json:"code,omitempty"`

// Identifies who is responsible for the information in this section, not necessarily who typed it in.
Author []*FHIRReferenceInput `json:"author,omitempty"`
Expand Down Expand Up @@ -300,6 +300,16 @@ type FHIRCompositionRelayEdge struct {
Node *FHIRComposition `json:"node,omitempty"`
}

// PagedFHIRComposition ...
type PagedFHIRComposition struct {
Compositions []FHIRComposition
HasNextPage bool
NextCursor string
HasPreviousPage bool
PreviousCursor string
TotalCount int
}

// FHIRCompositionRelayPayload is used to return single instances of Composition
type FHIRCompositionRelayPayload struct {
Resource *FHIRComposition `json:"resource,omitempty"`
Expand Down
27 changes: 13 additions & 14 deletions pkg/clinical/infrastructure/datastore/cloudhealthcare/fhir.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,11 @@ func NewFHIRStoreImpl(
// SearchPatientObservations fetches all observations that belong to a specific patient
func (fh StoreImpl) SearchPatientObservations(
_ context.Context,
patientReference string,
observationCode string,
searchParameters map[string]interface{},
tenant dto.TenantIdentifiers,
pagination dto.Pagination,
) (*domain.PagedFHIRObservations, error) {
params := map[string]interface{}{
"patient": patientReference,
"code": observationCode,
}

observations, err := fh.Dataset.SearchFHIRResource(observationResourceType, params, tenant, pagination)
observations, err := fh.Dataset.SearchFHIRResource(observationResourceType, searchParameters, tenant, pagination)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -813,14 +807,21 @@ func (fh StoreImpl) UpdateFHIRAllergyIntolerance(_ context.Context, input domain
}

// SearchFHIRComposition provides a search API for FHIRComposition
func (fh StoreImpl) SearchFHIRComposition(_ context.Context, params map[string]interface{}, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.FHIRCompositionRelayConnection, error) {
output := domain.FHIRCompositionRelayConnection{}

func (fh StoreImpl) SearchFHIRComposition(_ context.Context, params map[string]interface{}, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRComposition, error) {
resources, err := fh.Dataset.SearchFHIRResource(compositionResourceType, params, tenant, pagination)
if err != nil {
return nil, err
}

output := domain.PagedFHIRComposition{
Compositions: []domain.FHIRComposition{},
HasNextPage: resources.HasNextPage,
NextCursor: resources.NextCursor,
HasPreviousPage: resources.HasPreviousPage,
PreviousCursor: resources.PreviousCursor,
TotalCount: resources.TotalCount,
}

for _, result := range resources.Resources {
var resource domain.FHIRComposition

Expand All @@ -835,9 +836,7 @@ func (fh StoreImpl) SearchFHIRComposition(_ context.Context, params map[string]i
"server error: Unable to unmarshal %s: %w", compositionResourceType, err)
}

output.Edges = append(output.Edges, &domain.FHIRCompositionRelayEdge{
Node: &resource,
})
output.Compositions = append(output.Compositions, resource)
}

return &output, nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4053,11 +4053,10 @@ func TestStoreImpl_EndEpisode(t *testing.T) {
func TestStoreImpl_SearchPatientObservations(t *testing.T) {
ctx := context.Background()
type args struct {
ctx context.Context
patientReference string
observationCode string
tenant dto.TenantIdentifiers
pagination dto.Pagination
ctx context.Context
params map[string]interface{}
tenant dto.TenantIdentifiers
pagination dto.Pagination
}
tests := []struct {
name string
Expand All @@ -4067,18 +4066,24 @@ func TestStoreImpl_SearchPatientObservations(t *testing.T) {
{
name: "Happy Case - Successfully search patient observation",
args: args{
ctx: ctx,
patientReference: fmt.Sprintf("Patient/%s", gofakeit.UUID()),
observationCode: "5088",
ctx: ctx,
params: map[string]interface{}{
"patient": fmt.Sprintf("Patient/%s", gofakeit.UUID()),
"encounter": fmt.Sprintf("Encounter/%s", gofakeit.UUID()),
"observationCode": "5088",
},
},
wantErr: false,
},
{
name: "Sad Case - fail to search fhir resource",
args: args{
ctx: ctx,
patientReference: fmt.Sprintf("Patient/%s", gofakeit.UUID()),
observationCode: "5088",
ctx: ctx,
params: map[string]interface{}{
"patient": fmt.Sprintf("Patient/%s", gofakeit.UUID()),
"encounter": fmt.Sprintf("Encounter/%s", gofakeit.UUID()),
"observationCode": "5088",
},
},
wantErr: true,
},
Expand All @@ -4093,8 +4098,7 @@ func TestStoreImpl_SearchPatientObservations(t *testing.T) {
return nil, fmt.Errorf("failed to search observation resource")
}
}

got, err := fh.SearchPatientObservations(tt.args.ctx, tt.args.patientReference, tt.args.observationCode, tt.args.tenant, tt.args.pagination)
got, err := fh.SearchPatientObservations(tt.args.ctx, tt.args.params, tt.args.tenant, tt.args.pagination)
if (err != nil) != tt.wantErr {
t.Errorf("StoreImpl.SearchPatientObservations() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down
Loading

0 comments on commit 582f29e

Please sign in to comment.