diff --git a/pkg/clinical/application/dto/observation_output.go b/pkg/clinical/application/dto/observation_output.go index 003038eb..2ac6281b 100644 --- a/pkg/clinical/application/dto/observation_output.go +++ b/pkg/clinical/application/dto/observation_output.go @@ -2,12 +2,13 @@ package dto // Observation is a minimal representation of a fhir Observation type Observation struct { - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Value string `json:"value,omitempty"` - Status ObservationStatus `json:"status,omitempty"` - PatientID string `json:"patientID,omitempty"` - EncounterID string `json:"encounterID,omitempty"` + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` + Status ObservationStatus `json:"status,omitempty"` + PatientID string `json:"patientID,omitempty"` + EncounterID string `json:"encounterID,omitempty"` + TimeRecorded string `json:"timeRecorded,omitempty"` } // ObservationEdge is an observation edge diff --git a/pkg/clinical/infrastructure/datastore/cloudhealthcare/mock/fhir_mock.go b/pkg/clinical/infrastructure/datastore/cloudhealthcare/mock/fhir_mock.go index e880bbf6..52df28c6 100644 --- a/pkg/clinical/infrastructure/datastore/cloudhealthcare/mock/fhir_mock.go +++ b/pkg/clinical/infrastructure/datastore/cloudhealthcare/mock/fhir_mock.go @@ -816,6 +816,7 @@ func NewFHIRMock() *FHIRMock { }, MockCreateFHIRObservationFn: func(ctx context.Context, input domain.FHIRObservationInput) (*domain.FHIRObservation, error) { uuid := uuid.New().String() + instant := gofakeit.TimeZone() finalStatus := domain.ObservationStatusEnumFinal return &domain.FHIRObservation{ ID: new(string), @@ -848,6 +849,7 @@ func NewFHIRMock() *FHIRMock { EffectiveDateTime: &scalarutils.Date{}, EffectivePeriod: &domain.FHIRPeriod{}, EffectiveTiming: &domain.FHIRTiming{}, + EffectiveInstant: (*scalarutils.Instant)(&instant), Performer: []*domain.FHIRReference{}, ValueQuantity: &domain.FHIRQuantity{}, ValueCodeableConcept: (*scalarutils.Code)(&uuid), @@ -1042,6 +1044,7 @@ func NewFHIRMock() *FHIRMock { }, MockSearchPatientObservationsFn: func(ctx context.Context, patientReference, conceptID string, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRObservations, error) { uuid := uuid.New().String() + instant := gofakeit.TimeZone() finalStatus := domain.ObservationStatusEnumFinal return &domain.PagedFHIRObservations{ Observations: []domain.FHIRObservation{ @@ -1067,6 +1070,7 @@ func NewFHIRMock() *FHIRMock { }, Text: "", }, + EffectiveInstant: (*scalarutils.Instant)(&instant), }, }, HasNextPage: true, diff --git a/pkg/clinical/presentation/graph/generated/generated.go b/pkg/clinical/presentation/graph/generated/generated.go index 524b2409..5f31cc74 100644 --- a/pkg/clinical/presentation/graph/generated/generated.go +++ b/pkg/clinical/presentation/graph/generated/generated.go @@ -185,12 +185,13 @@ type ComplexityRoot struct { } Observation struct { - EncounterID func(childComplexity int) int - ID func(childComplexity int) int - Name func(childComplexity int) int - PatientID func(childComplexity int) int - Status func(childComplexity int) int - Value func(childComplexity int) int + EncounterID func(childComplexity int) int + ID func(childComplexity int) int + Name func(childComplexity int) int + PatientID func(childComplexity int) int + Status func(childComplexity int) int + TimeRecorded func(childComplexity int) int + Value func(childComplexity int) int } ObservationConnection struct { @@ -1069,6 +1070,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Observation.Status(childComplexity), true + case "Observation.timeRecorded": + if e.complexity.Observation.TimeRecorded == nil { + break + } + + return e.complexity.Observation.TimeRecorded(childComplexity), true + case "Observation.value": if e.complexity.Observation.Value == nil { break @@ -1949,6 +1957,7 @@ type Observation { encounterID: String! name: String! value: String! + timeRecorded: String! } type Medication { @@ -5386,6 +5395,8 @@ func (ec *executionContext) fieldContext_MedicalData_weight(ctx context.Context, return ec.fieldContext_Observation_name(ctx, field) case "value": return ec.fieldContext_Observation_value(ctx, field) + case "timeRecorded": + return ec.fieldContext_Observation_timeRecorded(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Observation", field.Name) }, @@ -5441,6 +5452,8 @@ func (ec *executionContext) fieldContext_MedicalData_bmi(ctx context.Context, fi return ec.fieldContext_Observation_name(ctx, field) case "value": return ec.fieldContext_Observation_value(ctx, field) + case "timeRecorded": + return ec.fieldContext_Observation_timeRecorded(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Observation", field.Name) }, @@ -5496,6 +5509,8 @@ func (ec *executionContext) fieldContext_MedicalData_viralLoad(ctx context.Conte return ec.fieldContext_Observation_name(ctx, field) case "value": return ec.fieldContext_Observation_value(ctx, field) + case "timeRecorded": + return ec.fieldContext_Observation_timeRecorded(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Observation", field.Name) }, @@ -5551,6 +5566,8 @@ func (ec *executionContext) fieldContext_MedicalData_cd4Count(ctx context.Contex return ec.fieldContext_Observation_name(ctx, field) case "value": return ec.fieldContext_Observation_value(ctx, field) + case "timeRecorded": + return ec.fieldContext_Observation_timeRecorded(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Observation", field.Name) }, @@ -6233,6 +6250,8 @@ func (ec *executionContext) fieldContext_Mutation_recordTemperature(ctx context. return ec.fieldContext_Observation_name(ctx, field) case "value": return ec.fieldContext_Observation_value(ctx, field) + case "timeRecorded": + return ec.fieldContext_Observation_timeRecorded(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Observation", field.Name) }, @@ -6302,6 +6321,8 @@ func (ec *executionContext) fieldContext_Mutation_recordHeight(ctx context.Conte return ec.fieldContext_Observation_name(ctx, field) case "value": return ec.fieldContext_Observation_value(ctx, field) + case "timeRecorded": + return ec.fieldContext_Observation_timeRecorded(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Observation", field.Name) }, @@ -6371,6 +6392,8 @@ func (ec *executionContext) fieldContext_Mutation_recordWeight(ctx context.Conte return ec.fieldContext_Observation_name(ctx, field) case "value": return ec.fieldContext_Observation_value(ctx, field) + case "timeRecorded": + return ec.fieldContext_Observation_timeRecorded(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Observation", field.Name) }, @@ -6440,6 +6463,8 @@ func (ec *executionContext) fieldContext_Mutation_recordRespiratoryRate(ctx cont return ec.fieldContext_Observation_name(ctx, field) case "value": return ec.fieldContext_Observation_value(ctx, field) + case "timeRecorded": + return ec.fieldContext_Observation_timeRecorded(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Observation", field.Name) }, @@ -6509,6 +6534,8 @@ func (ec *executionContext) fieldContext_Mutation_recordPulseRate(ctx context.Co return ec.fieldContext_Observation_name(ctx, field) case "value": return ec.fieldContext_Observation_value(ctx, field) + case "timeRecorded": + return ec.fieldContext_Observation_timeRecorded(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Observation", field.Name) }, @@ -6578,6 +6605,8 @@ func (ec *executionContext) fieldContext_Mutation_recordBloodPressure(ctx contex return ec.fieldContext_Observation_name(ctx, field) case "value": return ec.fieldContext_Observation_value(ctx, field) + case "timeRecorded": + return ec.fieldContext_Observation_timeRecorded(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Observation", field.Name) }, @@ -6647,6 +6676,8 @@ func (ec *executionContext) fieldContext_Mutation_recordBMI(ctx context.Context, return ec.fieldContext_Observation_name(ctx, field) case "value": return ec.fieldContext_Observation_value(ctx, field) + case "timeRecorded": + return ec.fieldContext_Observation_timeRecorded(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Observation", field.Name) }, @@ -6716,6 +6747,8 @@ func (ec *executionContext) fieldContext_Mutation_recordViralLoad(ctx context.Co return ec.fieldContext_Observation_name(ctx, field) case "value": return ec.fieldContext_Observation_value(ctx, field) + case "timeRecorded": + return ec.fieldContext_Observation_timeRecorded(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Observation", field.Name) }, @@ -6785,6 +6818,8 @@ func (ec *executionContext) fieldContext_Mutation_recordMUAC(ctx context.Context return ec.fieldContext_Observation_name(ctx, field) case "value": return ec.fieldContext_Observation_value(ctx, field) + case "timeRecorded": + return ec.fieldContext_Observation_timeRecorded(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Observation", field.Name) }, @@ -6854,6 +6889,8 @@ func (ec *executionContext) fieldContext_Mutation_recordOxygenSaturation(ctx con return ec.fieldContext_Observation_name(ctx, field) case "value": return ec.fieldContext_Observation_value(ctx, field) + case "timeRecorded": + return ec.fieldContext_Observation_timeRecorded(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Observation", field.Name) }, @@ -7474,6 +7511,50 @@ func (ec *executionContext) fieldContext_Observation_value(ctx context.Context, return fc, nil } +func (ec *executionContext) _Observation_timeRecorded(ctx context.Context, field graphql.CollectedField, obj *dto.Observation) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Observation_timeRecorded(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.TimeRecorded, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Observation_timeRecorded(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Observation", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _ObservationConnection_totalCount(ctx context.Context, field graphql.CollectedField, obj *dto.ObservationConnection) (ret graphql.Marshaler) { fc, err := ec.fieldContext_ObservationConnection_totalCount(ctx, field) if err != nil { @@ -7661,6 +7742,8 @@ func (ec *executionContext) fieldContext_ObservationEdge_node(ctx context.Contex return ec.fieldContext_Observation_name(ctx, field) case "value": return ec.fieldContext_Observation_value(ctx, field) + case "timeRecorded": + return ec.fieldContext_Observation_timeRecorded(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Observation", field.Name) }, @@ -13617,6 +13700,13 @@ func (ec *executionContext) _Observation(ctx context.Context, sel ast.SelectionS out.Values[i] = ec._Observation_value(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "timeRecorded": + + out.Values[i] = ec._Observation_timeRecorded(ctx, field, obj) + if out.Values[i] == graphql.Null { invalids++ } diff --git a/pkg/clinical/presentation/graph/types.graphql b/pkg/clinical/presentation/graph/types.graphql index 48ce81df..4fd61eb7 100644 --- a/pkg/clinical/presentation/graph/types.graphql +++ b/pkg/clinical/presentation/graph/types.graphql @@ -22,6 +22,7 @@ type Observation { encounterID: String! name: String! value: String! + timeRecorded: String! } type Medication { diff --git a/pkg/clinical/usecases/clinical/observation_test.go b/pkg/clinical/usecases/clinical/observation_test.go index 0e17a1b7..b2f773ab 100644 --- a/pkg/clinical/usecases/clinical/observation_test.go +++ b/pkg/clinical/usecases/clinical/observation_test.go @@ -1714,17 +1714,6 @@ func TestUseCasesClinicalImpl_GetPatientObservations(t *testing.T) { }, wantErr: false, }, - { - name: "Sad Case - Fail to search observation - nil encounter id", - args: args{ - ctx: context.Background(), - patientID: gofakeit.UUID(), - pagination: &dto.Pagination{ - First: &first, - }, - }, - wantErr: false, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1976,6 +1965,7 @@ func TestUseCasesClinicalImpl_GetPatientObservations(t *testing.T) { if tt.name == "Sad Case - Fail to search observation - nil encounter" { fakeFHIR.MockSearchPatientObservationsFn = func(ctx context.Context, patientReference, conceptID string, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRObservations, error) { status := dto.ObservationStatusFinal + instant := gofakeit.TimeZone() valueConcept := "222" UUID := gofakeit.UUID() return &domain.PagedFHIRObservations{ @@ -2000,78 +1990,7 @@ func TestUseCasesClinicalImpl_GetPatientObservations(t *testing.T) { ValueString: new(string), ValueBoolean: new(bool), ValueInteger: new(string), - ValueRange: &domain.FHIRRange{ - Low: domain.FHIRQuantity{ - Value: 100, - Unit: "cm", - }, - High: domain.FHIRQuantity{ - Value: 100, - Unit: "cm", - }, - }, - ValueRatio: &domain.FHIRRatio{ - Numerator: domain.FHIRQuantity{ - Value: 100, - Unit: "cm", - }, - Denominator: domain.FHIRQuantity{ - Value: 100, - Unit: "cm", - }, - }, - ValueSampledData: &domain.FHIRSampledData{ - ID: &UUID, - }, - ValueTime: &time.Time{}, - ValueDateTime: &scalarutils.Date{ - Year: 2000, - Month: 1, - Day: 1, - }, - ValuePeriod: &domain.FHIRPeriod{ - Start: scalarutils.DateTime(time.Wednesday.String()), - End: scalarutils.DateTime(time.Thursday.String()), - }, - }, - }, - HasNextPage: false, - NextCursor: "", - HasPreviousPage: false, - PreviousCursor: "", - TotalCount: 0, - }, nil - } - } - - if tt.name == "Sad Case - Fail to search observation - nil encounter id" { - fakeFHIR.MockSearchPatientObservationsFn = func(ctx context.Context, patientReference, conceptID string, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRObservations, error) { - status := dto.ObservationStatusFinal - valueConcept := "222" - UUID := gofakeit.UUID() - return &domain.PagedFHIRObservations{ - Observations: []domain.FHIRObservation{ - { - ID: new(string), - Status: (*domain.ObservationStatusEnum)(&status), - Code: domain.FHIRCodeableConcept{ - ID: new(string), - Coding: []*domain.FHIRCoding{{ - Display: gofakeit.BS(), - }}, - }, - Subject: &domain.FHIRReference{ - ID: new(string), - }, - Encounter: &domain.FHIRReference{}, - ValueQuantity: &domain.FHIRQuantity{ - Value: 100, - Unit: "cm", - }, - ValueCodeableConcept: (*scalarutils.Code)(&valueConcept), - ValueString: new(string), - ValueBoolean: new(bool), - ValueInteger: new(string), + EffectiveInstant: (*scalarutils.Instant)(&instant), ValueRange: &domain.FHIRRange{ Low: domain.FHIRQuantity{ Value: 100, diff --git a/pkg/clinical/usecases/clinical/patient.go b/pkg/clinical/usecases/clinical/patient.go index 9aeee7a3..a3804bc7 100644 --- a/pkg/clinical/usecases/clinical/patient.go +++ b/pkg/clinical/usecases/clinical/patient.go @@ -312,11 +312,12 @@ func mapFHIRObservationToObservationDTO(fhirObservation domain.FHIRObservation) } obs := &dto.Observation{ - ID: *fhirObservation.ID, - Status: dto.ObservationStatus(*fhirObservation.Status), - Name: fhirObservation.Code.Coding[0].Display, - Value: value, - PatientID: *fhirObservation.Subject.ID, + ID: *fhirObservation.ID, + Status: dto.ObservationStatus(*fhirObservation.Status), + Name: fhirObservation.Code.Coding[0].Display, + Value: value, + PatientID: *fhirObservation.Subject.ID, + TimeRecorded: string(*fhirObservation.EffectiveInstant), } if fhirObservation.Encounter != nil && fhirObservation.Encounter.ID != nil { diff --git a/pkg/clinical/usecases/clinical/patient_unit_test.go b/pkg/clinical/usecases/clinical/patient_unit_test.go index 24b14de3..9b2324ed 100644 --- a/pkg/clinical/usecases/clinical/patient_unit_test.go +++ b/pkg/clinical/usecases/clinical/patient_unit_test.go @@ -853,6 +853,7 @@ func TestClinicalUseCaseImpl_GetMedicalData(t *testing.T) { if tt.name == "Happy Case - Successfully search observation" { fakeFHIR.MockSearchFHIRObservationFn = func(ctx context.Context, params map[string]interface{}, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRObservations, error) { status := dto.ObservationStatusFinal + instant := gofakeit.TimeZone() valueConcept := "222" UUID := gofakeit.UUID() return &domain.PagedFHIRObservations{ @@ -880,6 +881,7 @@ func TestClinicalUseCaseImpl_GetMedicalData(t *testing.T) { ValueString: new(string), ValueBoolean: new(bool), ValueInteger: new(string), + EffectiveInstant: (*scalarutils.Instant)(&instant), ValueRange: &domain.FHIRRange{ Low: domain.FHIRQuantity{ Value: 100, @@ -1070,6 +1072,7 @@ func TestClinicalUseCaseImpl_GetMedicalData(t *testing.T) { if tt.name == "Sad Case - Fail to search observation - nil status" { fakeFHIR.MockSearchFHIRObservationFn = func(ctx context.Context, params map[string]interface{}, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRObservations, error) { valueConcept := "222" + instant := gofakeit.TimeZone() UUID := gofakeit.UUID() return &domain.PagedFHIRObservations{ Observations: []domain.FHIRObservation{ @@ -1092,6 +1095,7 @@ func TestClinicalUseCaseImpl_GetMedicalData(t *testing.T) { ValueString: new(string), ValueBoolean: new(bool), ValueInteger: new(string), + EffectiveInstant: (*scalarutils.Instant)(&instant), ValueRange: &domain.FHIRRange{ Low: domain.FHIRQuantity{ Value: 100, @@ -1282,6 +1286,7 @@ func TestClinicalUseCaseImpl_GetMedicalData(t *testing.T) { if tt.name == "Sad Case - Fail to search observation - nil encounter" { fakeFHIR.MockSearchFHIRObservationFn = func(ctx context.Context, params map[string]interface{}, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRObservations, error) { status := dto.ObservationStatusFinal + instant := gofakeit.TimeZone() valueConcept := "222" UUID := gofakeit.UUID() return &domain.PagedFHIRObservations{ @@ -1306,6 +1311,7 @@ func TestClinicalUseCaseImpl_GetMedicalData(t *testing.T) { ValueString: new(string), ValueBoolean: new(bool), ValueInteger: new(string), + EffectiveInstant: (*scalarutils.Instant)(&instant), ValueRange: &domain.FHIRRange{ Low: domain.FHIRQuantity{ Value: 100, @@ -1354,6 +1360,7 @@ func TestClinicalUseCaseImpl_GetMedicalData(t *testing.T) { fakeFHIR.MockSearchFHIRObservationFn = func(ctx context.Context, params map[string]interface{}, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRObservations, error) { status := dto.ObservationStatusFinal valueConcept := "222" + instant := gofakeit.TimeZone() UUID := gofakeit.UUID() return &domain.PagedFHIRObservations{ Observations: []domain.FHIRObservation{ @@ -1378,6 +1385,7 @@ func TestClinicalUseCaseImpl_GetMedicalData(t *testing.T) { ValueString: new(string), ValueBoolean: new(bool), ValueInteger: new(string), + EffectiveInstant: (*scalarutils.Instant)(&instant), ValueRange: &domain.FHIRRange{ Low: domain.FHIRQuantity{ Value: 100,