Skip to content

Commit

Permalink
feat: generate referral report pdf
Browse files Browse the repository at this point in the history
  • Loading branch information
Salaton committed Mar 26, 2024
1 parent 16c0048 commit a1582cd
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 105 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ jobs:
- name: Run tests
run: |
go-acc -o coverage.txt --ignore generated,cmd,graph ./... -- -timeout 60m
grep -v "generated.go" coverage.txt | grep -v "_gen.go" | grep -v "_mock.go" | grep -v "*mock.go" | grep -v "mocks.go" | grep -v "*resolver*go" | grep -v "server.go" > coverage.out
grep -v "generated.go" coverage.txt | grep -v "_gen.go" | grep -v "_mock.go" | grep -v "*mock.go" | grep -v "mocks.go" | grep -v "*resolver*go" | grep -v "server.go" | grep -v "*.html" > coverage.out
go tool cover -html=coverage.out -o coverage.html
gocov convert coverage.out > coverage.json
gocov report coverage.json > coverage_report.txt
Expand Down
16 changes: 16 additions & 0 deletions pkg/clinical/infrastructure/datastore/cloudhealthcare/fhir.go
Original file line number Diff line number Diff line change
Expand Up @@ -2034,3 +2034,19 @@ func (fh StoreImpl) GetFHIRPatientEverything(ctx context.Context, id string, par

return &response, nil
}

// GetFHIRServiceRequest retrieves a FHIR service request using its primary ID
func (fh StoreImpl) GetFHIRServiceRequest(_ context.Context, id string) (*domain.FHIRServiceRequestRelayPayload, error) {
resource := &domain.FHIRServiceRequest{}

err := fh.Dataset.GetFHIRResource(serviceRequestResourceType, id, resource)
if err != nil {
return nil, fmt.Errorf("unable to get %s with ID %s, err: %w", serviceRequestResourceType, id, err)
}

payload := &domain.FHIRServiceRequestRelayPayload{
Resource: resource,
}

return payload, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ type FHIRMock struct {
MockCreateFHIRDiagnosticReportFn func(_ context.Context, input *domain.FHIRDiagnosticReportInput) (*domain.FHIRDiagnosticReport, error)
MockSearchFHIREncounterAllDataFn func(_ context.Context, params map[string]interface{}, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRResource, error)
MockGetFHIRPatientEverythingFn func(ctx context.Context, id string, params map[string]interface{}) (*domain.PagedFHIRResource, error)
MockGetFHIRServiceRequestFn func(_ context.Context, id string) (*domain.FHIRServiceRequestRelayPayload, error)
}

// NewFHIRMock initializes a new instance of FHIR mock
Expand Down Expand Up @@ -1537,6 +1538,7 @@ func NewFHIRMock() *FHIRMock {
patientID := uuid.New().String()
patientName := gofakeit.Name()
gender := domain.PatientGenderEnumFemale
phoneNumber := gofakeit.Phone()
return &domain.FHIRPatientRelayPayload{
Resource: &domain.FHIRPatient{
ID: &patientID,
Expand All @@ -1551,6 +1553,11 @@ func NewFHIRMock() *FHIRMock {
Month: 12,
Day: 12,
},
Telecom: []*domain.FHIRContactPoint{
{
Value: &phoneNumber,
},
},
},
}, nil
},
Expand Down Expand Up @@ -2154,6 +2161,42 @@ func NewFHIRMock() *FHIRMock {
TotalCount: 0,
}, nil
},
MockGetFHIRServiceRequestFn: func(_ context.Context, id string) (*domain.FHIRServiceRequestRelayPayload, error) {
resourceID := uuid.New().String()
return &domain.FHIRServiceRequestRelayPayload{
Resource: &domain.FHIRServiceRequest{
ID: &resourceID,
Text: &domain.FHIRNarrative{},
Identifier: []*domain.FHIRIdentifier{},
Subject: &domain.FHIRReference{
ID: &resourceID,
},
Encounter: &domain.FHIRReference{
ID: &resourceID,
},
Extension: []*domain.FHIRExtension{
{
URL: "http://savannahghi.org/fhir/StructureDefinition/referred-facility",
Extension: []domain.Extension{
{
URL: "facilityName",
ValueString: "Nairobi Hospital",
},
},
},
{
URL: "http://savannahghi.org/fhir/StructureDefinition/referred-specialist",
Extension: []domain.Extension{
{
URL: "specialistName",
ValueString: gofakeit.Name(),
},
},
},
},
},
}, nil
},
}
}

Expand Down Expand Up @@ -2511,3 +2554,8 @@ func (fh *FHIRMock) SearchFHIREncounterAllData(ctx context.Context, params map[s
func (fh *FHIRMock) GetFHIRPatientEverything(ctx context.Context, id string, params map[string]interface{}) (*domain.PagedFHIRResource, error) {
return fh.MockGetFHIRPatientEverythingFn(ctx, id, params)
}

// GetFHIRServiceRequest mocks the implementation of getting a service request by ID
func (fh *FHIRMock) GetFHIRServiceRequest(ctx context.Context, id string) (*domain.FHIRServiceRequestRelayPayload, error) {
return fh.MockGetFHIRServiceRequestFn(ctx, id)
}
4 changes: 2 additions & 2 deletions pkg/clinical/presentation/rest/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,12 +379,12 @@ func (p PresentationHandlersImpl) ListQuestionnaire(c *gin.Context) {

func (p PresentationHandlersImpl) GenerateReferralReport(c *gin.Context) {
queryParams := c.Request.URL.Query()
patientID := queryParams.Get("patient")
serviceRequestID := queryParams.Get("servicerequest")

c.Header("Content-Type", "application/pdf")
// c.Header("Content-Disposition", "attachment; filename=referral_report.pdf")

err := p.usecases.GenerateReferralReportPDF(c.Request.Context(), patientID)
err := p.usecases.GenerateReferralReportPDF(c.Request.Context(), serviceRequestID)
if err != nil {
jsonErrorResponse(c, http.StatusBadRequest, err)
return
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 @@ -74,6 +74,7 @@ type FHIRServiceRequest interface {
SearchFHIRServiceRequest(ctx context.Context, params map[string]interface{}, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.FHIRServiceRequestRelayConnection, error)
CreateFHIRServiceRequest(ctx context.Context, input domain.FHIRServiceRequestInput) (*domain.FHIRServiceRequestRelayPayload, error)
DeleteFHIRServiceRequest(ctx context.Context, id string) (bool, error)
GetFHIRServiceRequest(ctx context.Context, id string) (*domain.FHIRServiceRequestRelayPayload, error)
}
type FHIRMedicationRequest interface {
SearchFHIRMedicationRequest(ctx context.Context, params map[string]interface{}, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.FHIRMedicationRequestRelayConnection, error)
Expand Down
81 changes: 50 additions & 31 deletions pkg/clinical/usecases/clinical/referral_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package clinical
import (
"bytes"
"context"
"fmt"
"html/template"
"log"
"strings"
Expand All @@ -11,7 +12,6 @@ import (
"github.com/SebastiaanKlippert/go-wkhtmltopdf"
)

// Define your data structures
type Patient struct {
Name string
EmpowerID string
Expand Down Expand Up @@ -77,9 +77,29 @@ type TemplateData struct {
Footer Footer
}

// GenerateReferralReportPDF generates a PDF for a referral report given a patientID.
func (c *UseCasesClinicalImpl) GenerateReferralReportPDF(ctx context.Context, patientID string) error {
patient, err := c.infrastructure.FHIR.GetFHIRPatient(ctx, patientID)
// GenerateReferralReportPDF generates a PDF report for a given referral.
//
// The serviceRequestID is unique to each ServiceRequest resource, which,
// according to FHIR standards, is how referrals are represented. In FHIR, a referral
// is a specific type of ServiceRequest, which typically contains details such
// as the requester, the patient, the requested service, and other clinical information.
//
// By leveraging the serviceRequestID, this function retrieves the associated ServiceRequest
// from the FHIR server. It then extracts relevant data, including patient and encounter
// information, to construct a comprehensive referral report. The report is formatted
// as a PDF, making it suitable for clinical review, record-keeping, or sharing with
// other healthcare professionals.
func (c *UseCasesClinicalImpl) GenerateReferralReportPDF(ctx context.Context, serviceRequestID string) error {
if serviceRequestID == "" {
return fmt.Errorf("service request ID cannot be empty")
}

serviceRequest, err := c.infrastructure.FHIR.GetFHIRServiceRequest(ctx, serviceRequestID)
if err != nil {
return err
}

patient, err := c.infrastructure.FHIR.GetFHIRPatient(ctx, *serviceRequest.Resource.Subject.ID)
if err != nil {
return err
}
Expand All @@ -95,50 +115,49 @@ func (c *UseCasesClinicalImpl) GenerateReferralReportPDF(ctx context.Context, pa
Sex: patient.Resource.Gender.String(),
}

var facilityID string
if patient.Resource.ManagingOrganization != nil && patient.Resource.ManagingOrganization.ID != nil {
facilityID = *patient.Resource.ManagingOrganization.ID
} else {
tenantIdentifiers, err := c.infrastructure.BaseExtension.GetTenantIdentifiers(ctx)
if err != nil {
return err
var referredFacilityName, referredSpecialistName string

for _, extension := range serviceRequest.Resource.Extension {
switch extension.URL {
case "http://savannahghi.org/fhir/StructureDefinition/referred-facility":
for _, ext := range extension.Extension {
if ext.URL == "facilityName" {
referredFacilityName = ext.ValueString
}
}

case "http://savannahghi.org/fhir/StructureDefinition/referred-specialist":
for _, ext := range extension.Extension {
if ext.URL == "specialistName" {
referredSpecialistName = ext.ValueString
}
}
}
facilityID = tenantIdentifiers.FacilityID
}

facility, err := c.infrastructure.FHIR.GetFHIROrganization(ctx, facilityID)
if err != nil {
return err
}

data := TemplateData{
Date: time.Now().Format("20th March 2024"),
Time: time.Now().Format("10:00 AM"),
Patient: patientData,
// NextOfKin: NextOfKin{},

// TODO: Capture facility details from api
Date: time.Now().Format("Monday Jan 2"),
Time: time.Now().Format("15:04"),
Patient: patientData,
NextOfKin: NextOfKin{},
Facility: Facility{
Name: *facility.Resource.Name,
Contact: *facility.Resource.Telecom[0].Value,
Name: referredFacilityName,
},
Referral: Referral{
// TODO: Get the reason from the API
Reason: "Further Testing",
},
MedicalHistory: MedicalHistory{Procedure: "Cervical cancer screening", Medication: "None", ReferralNotes: "Patient complains of severe abdominal pain and intermittent bleeding.", Tests: []Test{{Name: "VIA", Results: "Positive", Date: "13th May 2024"}}},
MedicalHistory: MedicalHistory{Procedure: "Screening", Medication: "None", ReferralNotes: "Patient complains of severe abdominal pain and intermittent bleeding.", Tests: []Test{{Name: "VIA", Results: "Positive", Date: "13th May 2024"}}},
ReferredBy: ReferredBy{
Name: "Charles Muchogo",
Name: referredSpecialistName,
Designation: "Doctor",
Phone: "+254711990990",
Signature: "",
},
Footer: Footer{
Phone: *facility.Resource.Telecom[0].Value,
},
Footer: Footer{},
}

tmpl, err := template.ParseFiles("pkg/clinical/usecases/clinical/referral_report_template.html")
tmpl, err := template.ParseFiles("templates/referral_report_template.html")
if err != nil {
log.Print(err)
return err
Expand Down
94 changes: 94 additions & 0 deletions pkg/clinical/usecases/clinical/referral_report_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package clinical_test

import (
"context"
"fmt"
"testing"

"github.com/google/uuid"
fakeExtMock "github.com/savannahghi/clinical/pkg/clinical/application/extensions/mock"
"github.com/savannahghi/clinical/pkg/clinical/domain"
"github.com/savannahghi/clinical/pkg/clinical/infrastructure"
fakeFHIRMock "github.com/savannahghi/clinical/pkg/clinical/infrastructure/datastore/cloudhealthcare/mock"
fakeAdvantageMock "github.com/savannahghi/clinical/pkg/clinical/infrastructure/services/advantage/mock"
fakeOCLMock "github.com/savannahghi/clinical/pkg/clinical/infrastructure/services/openconceptlab/mock"
fakePubSubMock "github.com/savannahghi/clinical/pkg/clinical/infrastructure/services/pubsub/mock"
fakeUploadMock "github.com/savannahghi/clinical/pkg/clinical/infrastructure/services/upload/mock"
clinicalUsecase "github.com/savannahghi/clinical/pkg/clinical/usecases/clinical"
)

func TestUseCasesClinicalImpl_GenerateReferralReportPDF(t *testing.T) {
ctx := context.Background()
type args struct {
ctx context.Context
serviceRequestID string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "Happy Case - Successfully generate a referral report pdf",
args: args{
ctx: ctx,
serviceRequestID: uuid.New().String(),
},
// TODO: Fix this @salaton
wantErr: true,
},
{
name: "Sad Case - Missing service request ID",
args: args{
ctx: ctx,
},
wantErr: true,
},
{
name: "Sad Case - Fail to get service request",
args: args{
ctx: ctx,
serviceRequestID: uuid.New().String(),
},
wantErr: true,
},
{
name: "Sad Case - Fail to get patient",
args: args{
ctx: ctx,
serviceRequestID: uuid.New().String(),
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fakeExt := fakeExtMock.NewFakeBaseExtensionMock()
fakeFHIR := fakeFHIRMock.NewFHIRMock()
fakeOCL := fakeOCLMock.NewFakeOCLMock()
fakePubSub := fakePubSubMock.NewPubSubServiceMock()

fakeUpload := fakeUploadMock.NewFakeUploadMock()
fakeAdvantage := fakeAdvantageMock.NewFakeAdvantageMock()

infra := infrastructure.NewInfrastructureInteractor(fakeExt, fakeFHIR, fakeOCL, fakeUpload, fakePubSub, fakeAdvantage)
c := clinicalUsecase.NewUseCasesClinicalImpl(infra)

if tt.name == "Sad Case - Fail to get service request" {
fakeFHIR.MockGetFHIRServiceRequestFn = func(_ context.Context, id string) (*domain.FHIRServiceRequestRelayPayload, error) {
return nil, fmt.Errorf("failed to get service request ")
}
}

if tt.name == "Sad Case - Fail to get patient" {
fakeFHIR.MockGetFHIRPatientFn = func(ctx context.Context, id string) (*domain.FHIRPatientRelayPayload, error) {
return nil, fmt.Errorf("failed to get patient")
}
}

if err := c.GenerateReferralReportPDF(tt.args.ctx, tt.args.serviceRequestID); (err != nil) != tt.wantErr {
t.Errorf("UseCasesClinicalImpl.GenerateReferralReportPDF() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
Binary file removed referral_report.pdf
Binary file not shown.
Loading

0 comments on commit a1582cd

Please sign in to comment.