Skip to content

Commit

Permalink
feat: persist patient referral form as FHIR document reference (#421)
Browse files Browse the repository at this point in the history
Signed-off-by: Kathurima Kimathi <kathurimakimathi415@gmail.com>
  • Loading branch information
KathurimaKimathi committed Apr 9, 2024
1 parent 56c3750 commit 98aa104
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 28 deletions.
3 changes: 0 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
# Copy the Go binary to the production image from the builder stage.
COPY --from=builder /app/server /server

# Ensure your templates directory is correctly copied into the Docker image.
COPY --from=builder /app/templates /app/templates

# Set the working directory to where your binary and templates are.
WORKDIR /app

Expand Down
3 changes: 3 additions & 0 deletions pkg/clinical/application/common/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ const (
// BreastExaminationCIELTerminologySystem is the terminology code used to represent breast examination concept.
// This is more a more general concept code.
BreastExaminationCIELTerminologySystem = "162825"

// ReferralNoteLOINCTerminologySystem is the system code used to represent referral note concept in loinc terminology system
ReferralNoteLOINCTerminologySystem = "57133-1"
)

// DefaultIdentifier assigns a patient a code to function as their
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
package utils

const ReferralFormTemplate = `
<!DOCTYPE html>
<html lang="en">
<head>
Expand Down Expand Up @@ -298,4 +301,4 @@ <h2>Referred by</h2>
{{end}}
</body>
</html>
`
152 changes: 152 additions & 0 deletions pkg/clinical/domain/complex_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4661,3 +4661,155 @@ func TestDocumentRelationshipTypeEnum_String(t *testing.T) {
})
}
}

func TestScreeningTypeEnum_IsValid(t *testing.T) {
tests := []struct {
name string
e ScreeningTypeEnum
want bool
}{
{
name: "Valid screening type - breast cancer",
e: BreastCancerScreeningTypeEnum,
want: true,
},
{
name: "Invalid screening type status",
e: "invalid",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.e.IsValid(); got != tt.want {
t.Errorf("ScreeningTypeEnum.IsValid() = %v, want %v", got, tt.want)
}
})
}
}

func TestScreeningTypeEnum_String(t *testing.T) {
tests := []struct {
name string
e ScreeningTypeEnum
want string
}{
{
name: "Breast Cancer Screening",
e: BreastCancerScreeningTypeEnum,
want: "BREAST_CANCER_SCREENING",
},
{
name: "Cervical Cancer Screening",
e: CervicalCancerScreeningTypeEnum,
want: "CERVICAL_CANCER_SCREENING",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.e.String(); got != tt.want {
t.Errorf("ScreeningTypeEnum.String() = %v, want %v", got, tt.want)
}
})
}
}

func TestScreeningTypeEnum_UnmarshalGQL(t *testing.T) {
value := BreastCancerScreeningTypeEnum
invalidType := ScreeningTypeEnum("invalid")
type args struct {
v interface{}
}
tests := []struct {
name string
e *ScreeningTypeEnum
args args
wantErr bool
}{
{
name: "valid type",
e: &value,
args: args{
v: "BREAST_CANCER_SCREENING",
},
wantErr: false,
},
{
name: "invalid type",
e: &invalidType,
args: args{
v: "this is not a valid type",
},
wantErr: true,
},
{
name: "non string type",
e: &invalidType,
args: args{
v: 1,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.e.UnmarshalGQL(tt.args.v); (err != nil) != tt.wantErr {
t.Errorf("ScreeningTypeEnum.UnmarshalGQL() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func TestMediaScreeningTypeEnum_MarshalGQL(t *testing.T) {
tests := []struct {
name string
e ScreeningTypeEnum
wantW string
}{
{
name: "BREAST_CANCER_SCREENING",
e: BreastCancerScreeningTypeEnum,
wantW: strconv.Quote("BREAST_CANCER_SCREENING"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := &bytes.Buffer{}
tt.e.MarshalGQL(w)
if gotW := w.String(); gotW != tt.wantW {
t.Errorf("ScreeningTypeEnum.MarshalGQL() = %v, want %v", gotW, tt.wantW)
}
})
}
}

func TestScreeningTypeEnum_Text(t *testing.T) {
tests := []struct {
name string
screeningType ScreeningTypeEnum
want string
}{
{
name: "Valid type - breast",
screeningType: BreastCancerScreeningTypeEnum,
want: "Breast Cancer Screening",
},
{
name: "Valid type - cervical",
screeningType: CervicalCancerScreeningTypeEnum,
want: "Cervical Cancer Screening",
},
{
name: "Invalid type",
screeningType: ScreeningTypeEnum("invalid"),
want: "unknown screening type",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.screeningType.Text(); got != tt.want {
t.Errorf("ScreeningTypeEnum.Text() = %v, want %v", got, tt.want)
}
})
}
}
4 changes: 3 additions & 1 deletion pkg/clinical/domain/document_reference.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package domain

import "github.com/savannahghi/scalarutils"

// FHIRDocumentReference represents a reference to a document of any kind for any purpose.
// It provides metadata about the document so that the document can be discovered and managed.
// The scope of a document is any seralized object with a mime-type, so includes formal patient centric documents (CDA), cliical notes, scanned paper, and non-patient centric documents like policy text.
Expand Down Expand Up @@ -78,7 +80,7 @@ type FHIRDocumentReferenceInput struct {
Type *FHIRCodeableConceptInput `json:"type,omitempty"`
Category []FHIRCodeableConceptInput `json:"category,omitempty"`
Subject *FHIRReferenceInput `json:"subject,omitempty"`
Date *string `json:"date,omitempty"`
Date *scalarutils.Instant `json:"date,omitempty"`
Author []FHIRReferenceInput `json:"author,omitempty"`
Authenticator *FHIRReferenceInput `json:"authenticator,omitempty"`
Custodian *FHIRReferenceInput `json:"custodian,omitempty"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ type FHIRMock struct {
MockGetFHIRPatientEverythingFn func(ctx context.Context, id string, params map[string]interface{}) (*domain.PagedFHIRResource, error)
MockGetFHIRServiceRequestFn func(_ context.Context, id string) (*domain.FHIRServiceRequestRelayPayload, error)
MockCreateFHIRSubscriptionFn func(_ context.Context, subscription *domain.FHIRSubscriptionInput) (*domain.FHIRSubscription, error)
MockCreateFHIRDocumentReferenceFn func(ctx context.Context, subscription *domain.FHIRSubscriptionInput) (*domain.FHIRSubscription, error)
MockPatchFHIRServiceRequestFn func(ctx context.Context, id string, input domain.FHIRServiceRequestInput) (*domain.FHIRServiceRequestRelayPayload, error)
MockCreateFHIRDocumentReferenceFn func(ctx context.Context, documentReference *domain.FHIRDocumentReferenceInput) (*domain.FHIRDocumentReference, error)
}

// NewFHIRMock initializes a new instance of FHIR mock
Expand Down Expand Up @@ -2296,24 +2296,33 @@ func NewFHIRMock() *FHIRMock {
Channel: domain.FHIRSubscriptionChannel{},
}, nil
},
MockCreateFHIRDocumentReferenceFn: func(ctx context.Context, subscription *domain.FHIRSubscriptionInput) (*domain.FHIRSubscription, error) {
resourceID := uuid.New().String()
return &domain.FHIRSubscription{
ID: &resourceID,
MockCreateFHIRDocumentReferenceFn: func(ctx context.Context, documentReference *domain.FHIRDocumentReferenceInput) (*domain.FHIRDocumentReference, error) {
resourceID := gofakeit.UUID()
return &domain.FHIRDocumentReference{
ID: resourceID,
Meta: &domain.FHIRMeta{},
ImplicitRules: new(string),
Language: new(string),
Text: &domain.FHIRNarrative{},
Extension: []*domain.Extension{},
ModifierExtension: []*domain.Extension{},
Identifier: []*domain.FHIRIdentifier{},
Extension: []domain.FHIRExtension{},
ModifierExtension: []domain.FHIRExtension{},
MasterIdentifier: &domain.FHIRIdentifier{},
Identifier: []domain.FHIRIdentifier{},
Status: "",
Contact: []domain.FHIRContactPoint{},
End: new(string),
Reason: "",
Criteria: "",
Error: new(string),
Channel: domain.FHIRSubscriptionChannel{},
Type: &domain.FHIRCodeableConcept{
ID: &resourceID,
},
Category: []domain.FHIRCodeableConcept{},
Subject: &domain.FHIRReference{},
Date: new(string),
Author: []domain.FHIRReference{},
Authenticator: &domain.FHIRReference{},
Custodian: &domain.FHIRReference{},
RelatesTo: []domain.FHIRDocumentReferenceRelatesTo{},
Description: "",
SecurityLabel: []domain.FHIRCodeableConcept{},
Content: []domain.FHIRDocumentReferenceContent{},
Context: &domain.FHIRDocumentReferenceContext{},
}, nil
},
}
Expand Down Expand Up @@ -2685,8 +2694,8 @@ func (fh *FHIRMock) CreateFHIRSubscription(ctx context.Context, subscription *do
}

// CreateFHIRDocumentReference mocks the implementation of creating a FHIR document reference
func (fh *FHIRMock) CreateFHIRDocumentReference(ctx context.Context, subscription *domain.FHIRSubscriptionInput) (*domain.FHIRSubscription, error) {
return fh.MockCreateFHIRSubscriptionFn(ctx, subscription)
func (fh *FHIRMock) CreateFHIRDocumentReference(ctx context.Context, documentReference *domain.FHIRDocumentReferenceInput) (*domain.FHIRDocumentReference, error) {
return fh.MockCreateFHIRDocumentReferenceFn(ctx, documentReference)
}

// PatchFHIRServiceRequest mocks the implementation of mocking patching service request
Expand Down
1 change: 1 addition & 0 deletions pkg/clinical/repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type FHIR interface {
FHIRRiskAssessment
FHIRDiagnosticReport
FHIRSubscription
FHIRDocumentReference
}

type FHIROrganization interface {
Expand Down
81 changes: 74 additions & 7 deletions pkg/clinical/usecases/clinical/referral_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import (
"time"

"github.com/SebastiaanKlippert/go-wkhtmltopdf"
"github.com/savannahghi/clinical/pkg/clinical/application/common"
"github.com/savannahghi/clinical/pkg/clinical/application/common/helpers"
"github.com/savannahghi/clinical/pkg/clinical/application/dto"
"github.com/savannahghi/clinical/pkg/clinical/application/utils"
"github.com/savannahghi/clinical/pkg/clinical/domain"
"github.com/savannahghi/scalarutils"
)

Expand Down Expand Up @@ -162,14 +165,10 @@ func (c *UseCasesClinicalImpl) GenerateReferralReportPDF(ctx context.Context, se
Footer: Footer{},
}

tmpl, err := template.ParseFiles("templates/referral_report_template.html")
if err != nil {
utils.ReportErrorToSentry(err)
return nil, err
}

var htmlBuffer bytes.Buffer

tmpl := template.Must(template.New("ReferralFormTemplate").Parse(utils.ReferralFormTemplate))

err = tmpl.Execute(&htmlBuffer, data)
if err != nil {
utils.ReportErrorToSentry(err)
Expand Down Expand Up @@ -197,11 +196,79 @@ func (c *UseCasesClinicalImpl) GenerateReferralReportPDF(ctx context.Context, se
currentTime := time.Now().Format("20060102T150405")
filename := fmt.Sprintf("%s_%s.pdf", patientData.Name, currentTime)

_, err = c.infrastructure.Upload.UploadMedia(ctx, filename, bytes.NewReader(pdfBytes), "")
result, err := c.infrastructure.Upload.UploadMedia(ctx, filename, bytes.NewReader(pdfBytes), "")
if err != nil {
utils.ReportErrorToSentry(err)
return nil, err
}

_, err = c.CreateDocumentReference(ctx, serviceRequest, result)
if err != nil {
utils.ReportErrorToSentry(err)
return nil, err
}

return pdfBytes, nil
}

// CreateDocumentReference is a helper method to abstract the creation of a document reference
func (c *UseCasesClinicalImpl) CreateDocumentReference(ctx context.Context, serviceRequest *domain.FHIRServiceRequestRelayPayload, result *dto.Media) (bool, error) {
concept, err := c.GetConcept(ctx, dto.TerminologySourceLOINC, common.ReferralNoteLOINCTerminologySystem)
if err != nil {
utils.ReportErrorToSentry(err)
return true, err
}

finalDocStatus := domain.CompositionStatusEnumFinal
status := domain.DocumentReferenceStatusEnumCurrent
instant := scalarutils.Instant(time.Now().Format(time.RFC3339))
title := fmt.Sprintf("%s's Referral report", serviceRequest.Resource.Subject.Display)
serviceRequestReference := fmt.Sprintf("ServiceRequest/%s", *serviceRequest.Resource.ID)

documentReference := &domain.FHIRDocumentReferenceInput{
Meta: &domain.FHIRMetaInput{},
Identifier: []domain.FHIRIdentifierInput{},
Status: status,
DocStatus: &finalDocStatus,
Type: &domain.FHIRCodeableConceptInput{
Coding: []*domain.FHIRCodingInput{
{
System: (*scalarutils.URI)(&concept.URL),
Code: scalarutils.Code(concept.ID),
Display: concept.DisplayName,
},
},
Text: concept.DisplayName,
},
Subject: &domain.FHIRReferenceInput{
ID: serviceRequest.Resource.Subject.ID,
Reference: serviceRequest.Resource.Subject.Reference,
},
Date: &instant,
Content: []domain.FHIRDocumentReferenceContent{
{
Attachment: domain.FHIRAttachment{
ContentType: (*scalarutils.Code)(&result.ContentType),
URL: (*scalarutils.URL)(&result.SignedURL),
Title: &title,
},
},
},
Context: &domain.FHIRDocumentReferenceContext{
Related: []domain.Reference{
{
Reference: serviceRequestReference,
Type: "ServiceRequest",
},
},
},
}

_, err = c.infrastructure.FHIR.CreateFHIRDocumentReference(ctx, documentReference)
if err != nil {
utils.ReportErrorToSentry(err)
return true, err
}

return false, nil
}
Loading

0 comments on commit 98aa104

Please sign in to comment.