Skip to content

Commit

Permalink
feat: record mammography result
Browse files Browse the repository at this point in the history
- This API is used to record mammography results

Link to documentation: [https://savannahghi.atlassian.net/wiki/spaces/OHE/pages/468025506/API+Documentation#Record-Mammography-Result]

Signed-off-by: Kathurima Kimathi <kathurimakimathi415@gmail.com>
  • Loading branch information
KathurimaKimathi committed Feb 8, 2024
1 parent 10b5559 commit fad781b
Show file tree
Hide file tree
Showing 22 changed files with 1,561 additions and 47 deletions.
3 changes: 3 additions & 0 deletions gqlgen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ models:
ReferenceInput:
model:
- github.com/savannahghi/clinical/pkg/clinical/application/dto.Reference
MediaInput:
model:
- github.com/savannahghi/clinical/pkg/clinical/application/dto.Media
Date:
model:
- "github.com/savannahghi/scalarutils.Date"
6 changes: 6 additions & 0 deletions pkg/clinical/application/common/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ const (

// CIELTerminologySystem is the identity of ciel terminology system
CIELTerminologySystem = "https://CIELterminology.org"

// MammogramTerminologyCode is the terminology code used to represent mammogram results.
MammogramTerminologyCode = "163591"

// BenignNeoplasmOfBreastOfSkinTerminologyCode is the terminology code used to represent benign of skin results.
BenignNeoplasmOfBreastOfSkinTerminologyCode = "147661"
)

// DefaultIdentifier assigns a patient a code to function as their
Expand Down
11 changes: 9 additions & 2 deletions pkg/clinical/application/dto/diagnostic_report_output.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package dto

type DiagnosticReportOutput struct {
ID string `json:"id,omitempty"`
type DiagnosticReport struct {
ID string `json:"id,omitempty"`
Status ObservationStatus `json:"status,omitempty"`
PatientID string `json:"patientID,omitempty"`
EncounterID string `json:"encounterID,omitempty"`
Issued string `json:"issued,omitempty"`
Result []*Observation `json:"result,omitempty"`
Media []*Media `json:"media,omitempty"`
Conclusion string `json:"conclusion,omitempty"`
}
17 changes: 16 additions & 1 deletion pkg/clinical/application/dto/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ type EncounterInput struct {
type ObservationInput struct {
Status ObservationStatus `json:"status,omitempty" validate:"required"`
EncounterID string `json:"encounterID,omitempty" validate:"required"`
Value string `json:"value,omitempty" validate:"required"`
Note string `json:"note,omitempty"`
Value string `json:"value,omitempty"`
}

func (o ObservationInput) Validate() error {
Expand Down Expand Up @@ -223,3 +223,18 @@ type Expression struct {
Expression *string `json:"expression,omitempty"`
Reference *string `json:"reference,omitempty"`
}

// DiagnosticReportInput represents the data class used to provide diagnostic report information
type DiagnosticReportInput struct {
EncounterID string `json:"encounterID,omitempty" validate:"required"`
Note string `json:"note,omitempty"`
Media Media `json:"media" validate:"required"`
Findings string `json:"findings,omitempty" validate:"required"`
}

func (d DiagnosticReportInput) Validate() error {
v := validator.New()
err := v.Struct(d)

return err
}
1 change: 1 addition & 0 deletions pkg/clinical/application/extensions/mock/extension_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ func NewFakeBaseExtensionMock() *FakeBaseExtension {
MockGetTenantIdentifiersFn: func(ctx context.Context) (*dto.TenantIdentifiers, error) {
return &dto.TenantIdentifiers{
OrganizationID: uuid.New().String(),
FacilityID: uuid.New().String(),
}, nil
},
MockVerifyPubSubJWTAndDecodePayloadFn: func(w http.ResponseWriter, r *http.Request) (*pubsubtools.PubSubPayload, error) {
Expand Down
101 changes: 71 additions & 30 deletions pkg/clinical/domain/diagnostic_report.go
Original file line number Diff line number Diff line change
@@ -1,40 +1,81 @@
package domain

// DiagnosticReport is documented here http://hl7.org/fhir/StructureDefinition/DiagnosticReport
type DiagnosticReport struct {
ID *string `json:"id,omitempty"`
Meta *FHIRMeta `json:"meta,omitempty"`
ImplicitRules *string `json:"implicitRules,omitempty"`
Language *string `json:"language,omitempty"`
Text *FHIRNarrative `json:"text,omitempty"`
Extension []*Extension `json:"extension,omitempty"`
ModifierExtension []*Extension `json:"modifierExtension,omitempty"`
Identifier []*FHIRIdentifier `json:"identifier,omitempty"`
BasedOn []*FHIRReference `json:"basedOn,omitempty"`
Status DiagnosticReportStatusEnum `json:"status"`
Category []*FHIRCodeableConcept `json:"category,omitempty"`
Code FHIRCodeableConcept `json:"code"`
Subject *FHIRReference `json:"subject,omitempty"`
Encounter *FHIRReference `json:"encounter,omitempty"`
EffectiveDateTime *string `json:"effectiveDateTime,omitempty"`
EffectivePeriod *FHIRPeriod `json:"effectivePeriod,omitempty"`
Issued *string `json:"issued,omitempty"`
Performer []*FHIRReference `json:"performer,omitempty"`
ResultsInterpreter []*FHIRReference `json:"resultsInterpreter,omitempty"`
Specimen []*FHIRReference `json:"specimen,omitempty"`
Result []*FHIRReference `json:"result,omitempty"`
ImagingStudy []*FHIRReference `json:"imagingStudy,omitempty"`
Media []*DiagnosticReportMedia `json:"media,omitempty"`
Conclusion *string `json:"conclusion,omitempty"`
ConclusionCode []*FHIRCodeableConcept `json:"conclusionCode,omitempty"`
PresentedForm []*FHIRAttachment `json:"presentedForm,omitempty"`
import "github.com/savannahghi/scalarutils"

// FHIRDiagnosticReport is documented here http://hl7.org/fhir/StructureDefinition/DiagnosticReport
type FHIRDiagnosticReport struct {
ID *string `json:"id,omitempty"`
Meta *FHIRMeta `json:"meta,omitempty"`
ImplicitRules *string `json:"implicitRules,omitempty"`
Language *string `json:"language,omitempty"`
Text *FHIRNarrative `json:"text,omitempty"`
Extension []*Extension `json:"extension,omitempty"`
ModifierExtension []*Extension `json:"modifierExtension,omitempty"`
Identifier []*FHIRIdentifier `json:"identifier,omitempty"`
BasedOn []*FHIRReference `json:"basedOn,omitempty"`
Status DiagnosticReportStatusEnum `json:"status"`
Category []*FHIRCodeableConcept `json:"category,omitempty"`
Code FHIRCodeableConcept `json:"code"`
Subject *FHIRReference `json:"subject,omitempty"`
Encounter *FHIRReference `json:"encounter,omitempty"`
EffectiveDateTime *scalarutils.DateTime `json:"effectiveDateTime,omitempty"`
EffectivePeriod *FHIRPeriod `json:"effectivePeriod,omitempty"`
Issued *string `json:"issued,omitempty"`
Performer []*FHIRReference `json:"performer,omitempty"`
ResultsInterpreter []*FHIRReference `json:"resultsInterpreter,omitempty"`
Specimen []*FHIRReference `json:"specimen,omitempty"`
Result []*FHIRReference `json:"result,omitempty"`
ImagingStudy []*FHIRReference `json:"imagingStudy,omitempty"`
Media []*FHIRDiagnosticReportMedia `json:"media,omitempty"`
Conclusion *string `json:"conclusion,omitempty"`
ConclusionCode []*FHIRCodeableConcept `json:"conclusionCode,omitempty"`
PresentedForm []*FHIRAttachment `json:"presentedForm,omitempty"`
}

// DiagnosticReportMedia represents the key images associated with this report
type DiagnosticReportMedia struct {
// FHIRDiagnosticReportMedia represents the key images associated with this report
type FHIRDiagnosticReportMedia struct {
ID *string `json:"id,omitempty"`
Extension []*Extension `json:"extension,omitempty"`
ModifierExtension []*Extension `json:"modifierExtension,omitempty"`
Comment *string `json:"comment,omitempty"`
Link *FHIRReference `json:"link"`
}

// FHIRDiagnosticReportInput is documented here http://hl7.org/fhir/StructureDefinition/DiagnosticReport
type FHIRDiagnosticReportInput struct {
ID *string `json:"id,omitempty"`
Meta *FHIRMetaInput `json:"meta,omitempty"`
ImplicitRules *string `json:"implicitRules,omitempty"`
Language *string `json:"language,omitempty"`
Text *FHIRNarrativeInput `json:"text,omitempty"`
Extension []*FHIRExtension `json:"extension,omitempty"`
ModifierExtension []*FHIRExtension `json:"modifierExtension,omitempty"`
Identifier []*FHIRIdentifierInput `json:"identifier,omitempty"`
BasedOn []*FHIRReferenceInput `json:"basedOn,omitempty"`
Status DiagnosticReportStatusEnum `json:"status"`
Category []*FHIRCodeableConceptInput `json:"category,omitempty"`
Code FHIRCodeableConceptInput `json:"code"`
Subject *FHIRReferenceInput `json:"subject,omitempty"`
Encounter *FHIRReferenceInput `json:"encounter,omitempty"`
EffectiveDateTime *scalarutils.DateTime `json:"effectiveDateTime,omitempty"`
EffectivePeriod *FHIRPeriodInput `json:"effectivePeriod,omitempty"`
Issued *string `json:"issued,omitempty"`
Performer []*FHIRReferenceInput `json:"performer,omitempty"`
ResultsInterpreter []*FHIRReferenceInput `json:"resultsInterpreter,omitempty"`
Specimen []*FHIRReferenceInput `json:"specimen,omitempty"`
Result []*FHIRReferenceInput `json:"result,omitempty"`
ImagingStudy []*FHIRReferenceInput `json:"imagingStudy,omitempty"`
Media []*FHIRDiagnosticReportMediaInput `json:"media,omitempty"`
Conclusion *string `json:"conclusion,omitempty"`
ConclusionCode []*FHIRCodeableConceptInput `json:"conclusionCode,omitempty"`
PresentedForm []*FHIRAttachmentInput `json:"presentedForm,omitempty"`
}

// FHIRDiagnosticReportMediaInput represents the key images associated with this report
type FHIRDiagnosticReportMediaInput struct {
ID *string `json:"id,omitempty"`
Extension []*FHIRExtension `json:"extension,omitempty"`
ModifierExtension []*FHIRExtension `json:"modifierExtension,omitempty"`
Comment *string `json:"comment,omitempty"`
Link *FHIRReferenceInput `json:"link"`
}
18 changes: 18 additions & 0 deletions pkg/clinical/infrastructure/datastore/cloudhealthcare/fhir.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const (
consentResourceType = "Consent"
questionnaireResponseResourceType = "QuestionnaireResponse"
riskAssessmentResourceType = "RiskAssessment"
diagnosticReportResourceType = "DiagnosticReport"
)

// Dataset ...
Expand Down Expand Up @@ -1856,3 +1857,20 @@ func (fh StoreImpl) GetFHIRQuestionnaireResponse(_ context.Context, id string) (

return payload, nil
}

// CreateFHIRDiagnosticReport is used to create a diagnostic report resource for a patient
func (fh StoreImpl) CreateFHIRDiagnosticReport(_ context.Context, input *domain.FHIRDiagnosticReportInput) (*domain.FHIRDiagnosticReport, error) {
payload, err := converterandformatter.StructToMap(input)
if err != nil {
return nil, fmt.Errorf("unable to turn %s input into a map: %w", diagnosticReportResourceType, err)
}

resource := &domain.FHIRDiagnosticReport{}

err = fh.Dataset.CreateFHIRResource(diagnosticReportResourceType, payload, resource)
if err != nil {
return nil, fmt.Errorf("unable to create %s resource: %w", diagnosticReportResourceType, err)
}

return resource, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -4873,6 +4873,57 @@ func TestStoreImpl_GetFHIRQuestionnaire(t *testing.T) {
}
}

func TestStoreImpl_CreateFHIRDiagnosticReport(t *testing.T) {
ID := gofakeit.UUID()
type args struct {
in0 context.Context
input *domain.FHIRDiagnosticReportInput
}
tests := []struct {
name string
args args
want *domain.FHIRDiagnosticReport
wantErr bool
}{
{
name: "Happy case: create diagnostic report",
args: args{
input: &domain.FHIRDiagnosticReportInput{
ID: &ID,
},
},
wantErr: false,
},
{
name: "Sad case: unable to create diagnostic report",
args: args{
input: &domain.FHIRDiagnosticReportInput{
ID: &ID,
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fakeDataset := fakeDataset.NewFakeFHIRRepositoryMock()
fh := FHIR.NewFHIRStoreImpl(fakeDataset)

if tt.name == "Sad case: unable to create diagnostic report" {
fakeDataset.MockCreateFHIRResourceFn = func(resourceType string, payload map[string]interface{}, resource interface{}) error {
return fmt.Errorf("an error ocurred")
}
}

_, err := fh.CreateFHIRDiagnosticReport(tt.args.in0, tt.args.input)
if (err != nil) != tt.wantErr {
t.Errorf("StoreImpl.CreateFHIRDiagnosticReport() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}

func TestStoreImpl_SearchFHIRRiskAssessment(t *testing.T) {
ctx := context.Background()
type args struct {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ type FHIRMock struct {
MockGetFHIRQuestionnaireFn func(ctx context.Context, id string) (*domain.FHIRQuestionnaireRelayPayload, error)
MockSearchFHIRRiskAssessmentFn func(ctx context.Context, params map[string]interface{}, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.FHIRRiskAssessmentRelayConnection, error)
MockGetFHIRQuestionnaireResponseFn func(ctx context.Context, id string) (*domain.FHIRQuestionnaireResponseRelayPayload, error)
MockCreateFHIRDiagnosticReportFn func(_ context.Context, input *domain.FHIRDiagnosticReportInput) (*domain.FHIRDiagnosticReport, error)
}

// NewFHIRMock initializes a new instance of FHIR mock
Expand Down Expand Up @@ -1596,10 +1597,11 @@ func NewFHIRMock() *FHIRMock {
return &domain.FHIRMedicationRelayPayload{}, nil
},
MockCreateFHIRMediaFn: func(ctx context.Context, input domain.FHIRMedia) (*domain.FHIRMedia, error) {
id := uuid.New().String()
id := "1"
url := gofakeit.URL()
title := gofakeit.BeerName()
return &domain.FHIRMedia{
ID: &id,
Status: "",
Subject: &domain.FHIRReferenceInput{
ID: &id,
Expand Down Expand Up @@ -2016,6 +2018,40 @@ func NewFHIRMock() *FHIRMock {
}
return &domain.FHIRQuestionnaireRelayPayload{Resource: resource}, nil
},
MockCreateFHIRDiagnosticReportFn: func(_ context.Context, input *domain.FHIRDiagnosticReportInput) (*domain.FHIRDiagnosticReport, error) {
ID := "1234"
return &domain.FHIRDiagnosticReport{
ID: new(string),
Meta: &domain.FHIRMeta{},
ImplicitRules: new(string),
Language: new(string),
Text: &domain.FHIRNarrative{},
Extension: []*domain.Extension{},
ModifierExtension: []*domain.Extension{},
Identifier: []*domain.FHIRIdentifier{},
BasedOn: []*domain.FHIRReference{},
Status: "",
Category: []*domain.FHIRCodeableConcept{},
Code: domain.FHIRCodeableConcept{},
Subject: &domain.FHIRReference{
ID: &ID,
},
Encounter: &domain.FHIRReference{
ID: &ID,
},
EffectivePeriod: &domain.FHIRPeriod{},
Issued: new(string),
Performer: []*domain.FHIRReference{},
ResultsInterpreter: []*domain.FHIRReference{},
Specimen: []*domain.FHIRReference{},
Result: []*domain.FHIRReference{},
ImagingStudy: []*domain.FHIRReference{},
Media: []*domain.FHIRDiagnosticReportMedia{},
Conclusion: new(string),
ConclusionCode: []*domain.FHIRCodeableConcept{},
PresentedForm: []*domain.FHIRAttachment{},
}, nil
},
}
}

Expand Down Expand Up @@ -2358,3 +2394,8 @@ func (fh *FHIRMock) SearchFHIRRiskAssessment(ctx context.Context, params map[str
func (fh *FHIRMock) GetFHIRQuestionnaireResponse(ctx context.Context, id string) (*domain.FHIRQuestionnaireResponseRelayPayload, error) {
return fh.MockGetFHIRQuestionnaireResponseFn(ctx, id)
}

// CreateFHIRDiagnosticReport mocks the implementation of creating a diagnostic report.
func (fh *FHIRMock) CreateFHIRDiagnosticReport(ctx context.Context, input *domain.FHIRDiagnosticReportInput) (*domain.FHIRDiagnosticReport, error) {
return fh.MockCreateFHIRDiagnosticReportFn(ctx, input)
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ func (s Service) GetConcept(
if err != nil {
return nil, fmt.Errorf("OCL API request error: %w", err)
}

defer resp.Body.Close()

output := make(map[string]interface{})
Expand Down
3 changes: 3 additions & 0 deletions pkg/clinical/presentation/graph/clinical.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,7 @@ extend type Mutation {
encounterID: String!
input: QuestionnaireResponseInput!
): QuestionnaireResponse!

# Diagnostic Report
recordMammographyResult(input: DiagnosticReportInput!): DiagnosticReport!
}
5 changes: 5 additions & 0 deletions pkg/clinical/presentation/graph/clinical.resolvers.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit fad781b

Please sign in to comment.