Skip to content

Commit

Permalink
feat: add record consent (#308)
Browse files Browse the repository at this point in the history
  • Loading branch information
allansifuna committed Jan 24, 2024
1 parent 8bd788b commit 6f6cf99
Show file tree
Hide file tree
Showing 17 changed files with 765 additions and 1 deletion.
92 changes: 92 additions & 0 deletions pkg/clinical/application/dto/enums.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package dto

import (
"fmt"
"io"
"strconv"
)

type OrganizationIdentifierType string

const (
Expand Down Expand Up @@ -170,3 +176,89 @@ const (
CompositionStatuEnumAmended CompositionStatusEnum = "AMENDED"
CompositionStatuEnumEnteredInErrorPreliminary CompositionStatusEnum = "ENTERED_IN_ERROR"
)

// ConsentStatusEnum a type enum tha represents a Consent Status field of consent resource
type ConsentStatusEnum string

const (
ConsentStatusActive = "active"
ConsentStatusInactive = "inactive"
)

// IsValid ...
func (c ConsentStatusEnum) IsValid() bool {
switch c {
case ConsentStatusActive, ConsentStatusInactive:
return true
}

return false
}

// String converts status to string
func (c ConsentStatusEnum) String() string {
return string(c)
}

// MarshalGQL writes the consent status as a quoted string
func (c ConsentStatusEnum) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(c.String()))
}

// UnmarshalGQL reads a json and converts it to a consent status enum
func (c *ConsentStatusEnum) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}

*c = ConsentStatusEnum(str)
if !c.IsValid() {
return fmt.Errorf("%s is not a valid ConsentStatus Enum", str)
}

return nil
}

// ConsentProvisionTypeEnum a type enum tha represents a Consent Provision field of consent resource
type ConsentProvisionTypeEnum string

const (
ConsentProvisionTypeDeny = "deny"
ConsentProvisionTypePermit = "permit"
)

// IsValid ...
func (c ConsentProvisionTypeEnum) IsValid() bool {
switch c {
case ConsentProvisionTypeDeny, ConsentProvisionTypePermit:
return true
}

return false
}

// String converts consent provision type to string
func (c ConsentProvisionTypeEnum) String() string {
return string(c)
}

// MarshalGQL writes the consent provision type as a quoted string
func (c ConsentProvisionTypeEnum) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(c.String()))
}

// UnmarshalGQL reads a json and converts it to a consent provision type enum
func (c *ConsentProvisionTypeEnum) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}

*c = ConsentProvisionTypeEnum(str)
if !c.IsValid() {
return fmt.Errorf("%s is not a valid ConsentProvisionTypeEnum Enum", str)
}

return nil
}
7 changes: 7 additions & 0 deletions pkg/clinical/application/dto/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,10 @@ type SectionInput struct {
Text string `json:"text"`
Section []*SectionInput `json:"section"`
}

// ConsentInput models the consent input
type ConsentInput struct {
Status ConsentStatusEnum `json:"status"`
Provision ConsentProvisionTypeEnum `json:"provision,omitempty"`
PatientID string `json:"patientID,omitempty"`
}
5 changes: 5 additions & 0 deletions pkg/clinical/application/dto/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,8 @@ type Section struct {
Text string `json:"text,omitempty"`
Section []*Section `json:"section,omitempty"`
}

// Consent models a fhir consent resource
type ConsentOutput struct {
Status *ConsentStatusEnum `json:"status"`
}
23 changes: 23 additions & 0 deletions pkg/clinical/domain/consent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package domain

import (
"github.com/savannahghi/clinical/pkg/clinical/application/dto"
)

// Consent models a fhir consent resource
type FHIRConsent struct {
ID *string `json:"id,omitempty"`
Status *dto.ConsentStatusEnum `json:"status"`
Scope *FHIRCodeableConcept `json:"scope"`
Category []*FHIRCodeableConcept `json:"category"`
PolicyRule *FHIRCodeableConcept `json:"policyRule,omitempty"`
Provision *FHIRConsentProvision `json:"provision,omitempty"`
Patient *FHIRReference `json:"patient,omitempty"`
Meta *FHIRMetaInput `json:"meta,omitempty"`
}

// ConsentProvision models a fhir consent provision
type FHIRConsentProvision struct {
ID *string `json:"id,omitempty"`
Type *dto.ConsentProvisionTypeEnum `json:"type,omitempty"`
}
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 @@ -36,6 +36,7 @@ const (
medicationResourceType = "Medication"
mediaResourceType = "Media"
questionnaireResourceType = "Questionnaire"
consentResourceType = "Consent"
)

// Dataset ...
Expand Down Expand Up @@ -1700,3 +1701,20 @@ func (fh StoreImpl) CreateFHIRQuestionnaire(_ context.Context, input *domain.FHI

return resource, nil
}

// CreateFHIRConsent creates a FHIRConsent instance
func (fh StoreImpl) CreateFHIRConsent(_ context.Context, input domain.FHIRConsent) (*domain.FHIRConsent, error) {
payload, err := converterandformatter.StructToMap(input)
if err != nil {
return nil, fmt.Errorf("unable to turn %s input into a map: %w", consentResourceType, err)
}

resource := &domain.FHIRConsent{}

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

return resource, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -4467,3 +4467,68 @@ func TestStoreImpl_CreateFHIRQuestionnaire(t *testing.T) {
})
}
}

func TestStoreImpl_CreateFHIRConsent(t *testing.T) {
ID := gofakeit.UUID()
status := dto.ConsentStatusActive
provisionType := dto.ConsentProvisionTypePermit
type args struct {
ctx context.Context
consent domain.FHIRConsent
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "Happy case: create a fhir consent",
args: args{
ctx: context.Background(),
consent: domain.FHIRConsent{
Status: (*dto.ConsentStatusEnum)(&status),
Scope: &domain.FHIRCodeableConcept{Text: gofakeit.BeerIbu()},
Category: []*domain.FHIRCodeableConcept{&domain.FHIRCodeableConcept{Text: gofakeit.BeerIbu()}},
PolicyRule: &domain.FHIRCodeableConcept{Text: gofakeit.BeerIbu()},
Provision: &domain.FHIRConsentProvision{Type: (*dto.ConsentProvisionTypeEnum)(&provisionType)},
Patient: &domain.FHIRReference{ID: &ID},
Meta: &domain.FHIRMetaInput{Tag: []domain.FHIRCodingInput{}},
},
},
wantErr: false,
},
{
name: "Sad case: unable to create consent",
args: args{
ctx: context.Background(),
consent: domain.FHIRConsent{
Status: (*dto.ConsentStatusEnum)(&status),
PolicyRule: &domain.FHIRCodeableConcept{Text: gofakeit.BeerIbu()},
Provision: &domain.FHIRConsentProvision{Type: (*dto.ConsentProvisionTypeEnum)(&provisionType)},
Patient: &domain.FHIRReference{ID: &ID},
Meta: &domain.FHIRMetaInput{Tag: []domain.FHIRCodingInput{}},
},
},
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 consent" {
fakeDataset.MockCreateFHIRResourceFn = func(resourceType string, payload map[string]interface{}, resource interface{}) error {
return errors.New("unable to create fhir consent")
}

}

_, err := fh.CreateFHIRConsent(tt.args.ctx, tt.args.consent)
if (err != nil) != tt.wantErr {
t.Errorf("StoreImpl.CreateFHIRConsent() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ type FHIRMock struct {
MockCreateFHIRMediaFn func(ctx context.Context, input domain.FHIRMedia) (*domain.FHIRMedia, error)
MockSearchPatientMediaFn func(ctx context.Context, patientReference string, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRMedia, error)
MockCreateFHIRQuestionnaireFn func(ctx context.Context, input *domain.FHIRQuestionnaire) (*domain.FHIRQuestionnaire, error)
MockCreateFHIRConsentFn func(ctx context.Context, input domain.FHIRConsent) (*domain.FHIRConsent, error)
}

// NewFHIRMock initializes a new instance of FHIR mock
Expand Down Expand Up @@ -1867,6 +1868,9 @@ func NewFHIRMock() *FHIRMock {
Item: []*domain.FHIRQuestionnaireItem{},
}, nil
},
MockCreateFHIRConsentFn: func(ctx context.Context, input domain.FHIRConsent) (*domain.FHIRConsent, error) {
return &input, nil
},
}
}

Expand Down Expand Up @@ -2174,3 +2178,8 @@ func (fh *FHIRMock) SearchPatientMedia(ctx context.Context, patientReference str
func (fh *FHIRMock) CreateFHIRQuestionnaire(ctx context.Context, input *domain.FHIRQuestionnaire) (*domain.FHIRQuestionnaire, error) {
return fh.MockCreateFHIRQuestionnaireFn(ctx, input)
}

// CreateFHIRConsent mocks the create consent resource on fhir
func (fh *FHIRMock) CreateFHIRConsent(ctx context.Context, input domain.FHIRConsent) (*domain.FHIRConsent, error) {
return fh.MockCreateFHIRConsentFn(ctx, input)
}
2 changes: 2 additions & 0 deletions pkg/clinical/presentation/graph/clinical.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,6 @@ extend type Mutation {
patchPatientDiastolicBloodPressure(id: String!, value: String!): Observation!
patchPatientSystolicBloodPressure(id: String!, value: String!): Observation!
patchPatientRespiratoryRate(id: String!, value: String!): Observation!
# Consent
recordConsent(input: ConsentInput!): ConsentOutput!
}
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.

10 changes: 10 additions & 0 deletions pkg/clinical/presentation/graph/enums.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,13 @@ enum ConditionCategory {
PROBLEM_LIST_ITEM
ENCOUNTER_DIAGNOSIS
}

enum ConsentProvisionTypeEnum {
permit
deny
}

enum ConsentStatusEnum{
active
inactive
}
Loading

0 comments on commit 6f6cf99

Please sign in to comment.