Skip to content

Commit

Permalink
refactor: setup composition when starting an encounter
Browse files Browse the repository at this point in the history
Signed-off-by: Kathurima Kimathi <kathurimakimathi415@gmail.com>
  • Loading branch information
KathurimaKimathi committed Mar 27, 2024
1 parent 8d6d629 commit 771311e
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 61 deletions.
7 changes: 5 additions & 2 deletions pkg/clinical/application/common/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,11 @@ const (
// LOINCExamination defines LOINC Examination note terminology code
LOINCExamination = "29545-1"

// LOINCPLANOFCARE defines LOINC Plan of care note terminology code
LOINCPLANOFCARE = "18776-5"
// LOINCPlanOfCare defines LOINC Plan of care note terminology code
LOINCPlanOfCare = "18776-5"

// LOINCProviderUnspecifiedProgressNote defines LOINC Provider unspecified progress note terminology code
LOINCProviderUnspecifiedProgressNote = "11506-3"

// ColposcopyCIELTerminologyCode is the terminology code for colposcopy findings
ColposcopyCIELTerminologyCode = "162816"
Expand Down
13 changes: 7 additions & 6 deletions pkg/clinical/application/dto/enums.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,13 @@ const (
type CompositionCategory string

const (
AssessmentAndPlan CompositionCategory = "ASSESSMENT_PLAN"
HistoryOfPresentingIllness CompositionCategory = "HISTORY_OF_PRESENTING_ILLNESS"
SocialHistory CompositionCategory = "SOCIAL_HISTORY"
FamilyHistory CompositionCategory = "FAMILY_HISTORY"
Examination CompositionCategory = "EXAMINATION"
PlanOfCare CompositionCategory = "PLAN_OF_CARE"
AssessmentAndPlan CompositionCategory = "ASSESSMENT_PLAN"
HistoryOfPresentingIllness CompositionCategory = "HISTORY_OF_PRESENTING_ILLNESS"
SocialHistory CompositionCategory = "SOCIAL_HISTORY"
FamilyHistory CompositionCategory = "FAMILY_HISTORY"
Examination CompositionCategory = "EXAMINATION"
PlanOfCare CompositionCategory = "PLAN_OF_CARE"
ProviderUnspecifiedProgressNote CompositionCategory = "PROGRESS_NOTE"
)

// Type enum represents type composition attribute
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,14 @@ func NewFHIRMock() *FHIRMock {
return &domain.FHIREncounterRelayPayload{
Resource: &domain.FHIREncounter{
ID: &resourceID,
Meta: &domain.FHIRMeta{
Source: resourceID,
Tag: []domain.FHIRCoding{
{
Code: (*scalarutils.Code)(&resourceID),
},
},
},
},
}, nil
},
Expand Down
75 changes: 22 additions & 53 deletions pkg/clinical/usecases/clinical/composition.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,31 +54,12 @@ func (c *UseCasesClinicalImpl) CreateComposition(ctx context.Context, input dto.

id := uuid.New().String()

var compositionCategoryCode string

switch input.Category {
case "ASSESSMENT_PLAN":
compositionCategoryCode = common.LOINCAssessmentPlanCode
case "HISTORY_OF_PRESENTING_ILLNESS":
compositionCategoryCode = common.LOINCHistoryOfPresentingIllness
case "SOCIAL_HISTORY":
compositionCategoryCode = common.LOINCSocialHistory
case "FAMILY_HISTORY":
compositionCategoryCode = common.LOINCFamilyHistory
case "EXAMINATION":
compositionCategoryCode = common.LOINCExamination
case "PLAN_OF_CARE":
compositionCategoryCode = common.LOINCPLANOFCARE
default:
return nil, fmt.Errorf("category is needed")
}

compositionCategoryConcept, err := c.GetConcept(ctx, dto.TerminologySourceLOINC, compositionCategoryCode)
compositionCategoryCode, err := c.mapCategoryEnumToCode(input.Category)
if err != nil {
return nil, err
}

compositionTypeConcept, err := c.GetConcept(ctx, dto.TerminologySourceLOINC, common.LOINCProgressNoteCode)
compositionConcept, err := c.mapCompositionConcepts(ctx, compositionCategoryCode, common.LOINCProgressNoteCode)
if err != nil {
return nil, err
}
Expand All @@ -91,24 +72,24 @@ func (c *UseCasesClinicalImpl) CreateComposition(ctx context.Context, input dto.
Type: &domain.FHIRCodeableConceptInput{
Coding: []*domain.FHIRCodingInput{
{
System: (*scalarutils.URI)(&compositionTypeConcept.URL),
Code: scalarutils.Code(compositionTypeConcept.ID),
Display: compositionTypeConcept.DisplayName,
System: (*scalarutils.URI)(&compositionConcept.CompositionTypeConcept.URL),
Code: scalarutils.Code(compositionConcept.CompositionTypeConcept.ID),
Display: compositionConcept.CompositionTypeConcept.DisplayName,
},
},
Text: compositionTypeConcept.DisplayName,
Text: compositionConcept.CompositionTypeConcept.DisplayName,
},
Category: []*domain.FHIRCodeableConceptInput{
{
ID: &id,
Coding: []*domain.FHIRCodingInput{
{
System: (*scalarutils.URI)(&compositionCategoryConcept.URL),
Code: scalarutils.Code(compositionCategoryConcept.ID),
Display: compositionCategoryConcept.DisplayName,
System: (*scalarutils.URI)(&compositionConcept.CompositionCategoryConcept.URL),
Code: scalarutils.Code(compositionConcept.CompositionCategoryConcept.ID),
Display: compositionConcept.CompositionCategoryConcept.DisplayName,
},
},
Text: compositionCategoryConcept.DisplayName,
Text: compositionConcept.CompositionCategoryConcept.DisplayName,
},
},
Subject: &domain.FHIRReferenceInput{
Expand All @@ -132,18 +113,18 @@ func (c *UseCasesClinicalImpl) CreateComposition(ctx context.Context, input dto.
Section: []*domain.FHIRCompositionSectionInput{
{
ID: &id,
Title: &compositionCategoryConcept.DisplayName,
Title: &compositionConcept.CompositionCategoryConcept.DisplayName,
Code: &domain.FHIRCodeableConceptInput{
ID: &id,
Coding: []*domain.FHIRCodingInput{
{
ID: &id,
System: (*scalarutils.URI)(&compositionCategoryConcept.URL),
Code: scalarutils.Code(compositionCategoryConcept.ID),
Display: compositionCategoryConcept.DisplayName,
System: (*scalarutils.URI)(&compositionConcept.CompositionCategoryConcept.URL),
Code: scalarutils.Code(compositionConcept.CompositionCategoryConcept.ID),
Display: compositionConcept.CompositionCategoryConcept.DisplayName,
},
},
Text: compositionTypeConcept.DisplayName,
Text: compositionConcept.CompositionTypeConcept.DisplayName,
},
Author: []*domain.FHIRReferenceInput{
{
Expand Down Expand Up @@ -178,12 +159,12 @@ func (c *UseCasesClinicalImpl) CreateComposition(ctx context.Context, input dto.
ID: &id,
Coding: []*domain.FHIRCodingInput{
{
System: (*scalarutils.URI)(&compositionCategoryConcept.URL),
Code: scalarutils.Code(compositionCategoryConcept.ID),
Display: compositionCategoryConcept.DisplayName,
System: (*scalarutils.URI)(&compositionConcept.CompositionCategoryConcept.URL),
Code: scalarutils.Code(compositionConcept.CompositionCategoryConcept.ID),
Display: compositionConcept.CompositionCategoryConcept.DisplayName,
},
},
Text: compositionCategoryConcept.DisplayName,
Text: compositionConcept.CompositionCategoryConcept.DisplayName,
},
}

Expand Down Expand Up @@ -332,21 +313,9 @@ func (c *UseCasesClinicalImpl) AppendNoteToComposition(ctx context.Context, id s

organizationRef := fmt.Sprintf("Organization/%s", identifiers.OrganizationID)

var compositionCategoryCode string

switch input.Category {
case "ASSESSMENT_PLAN":
compositionCategoryCode = common.LOINCAssessmentPlanCode
case "HISTORY_OF_PRESENTING_ILLNESS":
compositionCategoryCode = common.LOINCHistoryOfPresentingIllness
case "SOCIAL_HISTORY":
compositionCategoryCode = common.LOINCSocialHistory
case "FAMILY_HISTORY":
compositionCategoryCode = common.LOINCFamilyHistory
case "EXAMINATION":
compositionCategoryCode = common.LOINCExamination
case "PLAN_OF_CARE":
compositionCategoryCode = common.LOINCPLANOFCARE
compositionCategoryCode, err := c.mapCategoryEnumToCode(input.Category)
if err != nil {
return nil, err
}

compositionCategoryConcept, err := c.GetConcept(ctx, dto.TerminologySourceLOINC, compositionCategoryCode)
Expand Down
60 changes: 60 additions & 0 deletions pkg/clinical/usecases/clinical/composition_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package clinical

import (
"context"
"fmt"

"github.com/savannahghi/clinical/pkg/clinical/application/common"
"github.com/savannahghi/clinical/pkg/clinical/application/dto"
"github.com/savannahghi/clinical/pkg/clinical/domain"
)

// CompositionConcept is used to map composition concepts
type CompositionConcept struct {
CompositionCategoryConcept *domain.Concept
CompositionTypeConcept *domain.Concept
}

// mapCompositionConcepts composes a unified representation of composition concepts and types into a single model
func (c *UseCasesClinicalImpl) mapCompositionConcepts(ctx context.Context, compositionCategoryCode, conceptID string) (*CompositionConcept, error) {
compositionCategoryConcept, err := c.GetConcept(ctx, dto.TerminologySourceLOINC, compositionCategoryCode)
if err != nil {
return nil, err
}

compositionTypeConcept, err := c.GetConcept(ctx, dto.TerminologySourceLOINC, conceptID)
if err != nil {
return nil, err
}

return &CompositionConcept{
CompositionCategoryConcept: compositionCategoryConcept,
CompositionTypeConcept: compositionTypeConcept,
}, nil
}

// mapCategoryEnumToCode is used to map various composition categories to respective LOINC codes
func (*UseCasesClinicalImpl) mapCategoryEnumToCode(category dto.CompositionCategory) (string, error) {
var compositionCategoryCode string

switch category {
case "ASSESSMENT_PLAN":
compositionCategoryCode = common.LOINCAssessmentPlanCode
case "HISTORY_OF_PRESENTING_ILLNESS":
compositionCategoryCode = common.LOINCHistoryOfPresentingIllness
case "SOCIAL_HISTORY":
compositionCategoryCode = common.LOINCSocialHistory
case "FAMILY_HISTORY":
compositionCategoryCode = common.LOINCFamilyHistory
case "EXAMINATION":
compositionCategoryCode = common.LOINCExamination
case "PLAN_OF_CARE":
compositionCategoryCode = common.LOINCPlanOfCare
case "PROGRESS_NOTE":
compositionCategoryCode = common.LOINCProviderUnspecifiedProgressNote
default:
return "", fmt.Errorf("category is needed")
}

return compositionCategoryCode, nil
}
80 changes: 80 additions & 0 deletions pkg/clinical/usecases/clinical/encounter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/mitchellh/mapstructure"
"github.com/savannahghi/clinical/pkg/clinical/application/common"
"github.com/savannahghi/clinical/pkg/clinical/application/dto"
"github.com/savannahghi/clinical/pkg/clinical/domain"
"github.com/savannahghi/scalarutils"
Expand Down Expand Up @@ -78,9 +79,88 @@ func (c *UseCasesClinicalImpl) StartEncounter(ctx context.Context, episodeID str
return "", err
}

// Create a blank composition
_, err = c.RecordFHIRComposition(ctx, encounter, tags, episodeOfCare)
if err != nil {
return "", err
}

return *encounter.Resource.ID, nil
}

func (c *UseCasesClinicalImpl) RecordFHIRComposition(ctx context.Context,
encounter *domain.FHIREncounterRelayPayload,
tags []domain.FHIRCodingInput, episodeOfCare *domain.FHIREpisodeOfCareRelayPayload) (*domain.FHIRCompositionRelayPayload, error) {
encounterRef := fmt.Sprintf("Encounter/%s", *encounter.Resource.ID)
encounterType := scalarutils.URI("Encounter")

today := time.Now()

date, err := scalarutils.NewDate(today.Day(), int(today.Month()), today.Year())
if err != nil {
return nil, err
}

preliminaryStatus := domain.CompositionStatusEnumPreliminary

organizationRef := fmt.Sprintf("Organization/%s", *encounter.Resource.Meta.Tag[0].Code)

compositionCategoryCode, err := c.mapCategoryEnumToCode(dto.ProviderUnspecifiedProgressNote)
if err != nil {
return nil, err
}

compositionConcept, err := c.mapCompositionConcepts(ctx, compositionCategoryCode, common.LOINCProviderUnspecifiedProgressNote)
if err != nil {
return nil, err
}

compositionTitle := fmt.Sprintf("%s's %s", episodeOfCare.Resource.Patient.Display, compositionConcept.CompositionCategoryConcept.DisplayName)

compositionInput := domain.FHIRCompositionInput{
Status: &preliminaryStatus,
Meta: &domain.FHIRMetaInput{
Tag: tags,
},
Type: &domain.FHIRCodeableConceptInput{
Coding: []*domain.FHIRCodingInput{
{
System: (*scalarutils.URI)(&compositionConcept.CompositionTypeConcept.URL),
Code: scalarutils.Code(compositionConcept.CompositionTypeConcept.ID),
Display: compositionConcept.CompositionTypeConcept.DisplayName,
},
},
Text: compositionConcept.CompositionTypeConcept.DisplayName,
},
Subject: &domain.FHIRReferenceInput{
ID: episodeOfCare.Resource.Patient.ID,
Reference: episodeOfCare.Resource.Patient.Reference,
Type: episodeOfCare.Resource.Patient.Type,
Display: episodeOfCare.Resource.Patient.Display,
},
Encounter: &domain.FHIRReferenceInput{
ID: encounter.Resource.ID,
Reference: &encounterRef,
Display: *encounter.Resource.ID,
Type: &encounterType,
},
Date: date,
Author: []*domain.FHIRReferenceInput{
{
Reference: &organizationRef,
},
},
Title: &compositionTitle,
}

output, err := c.infrastructure.FHIR.CreateFHIRComposition(ctx, compositionInput)
if err != nil {
return nil, err
}

return output, nil
}

func (c *UseCasesClinicalImpl) PatchEncounter(ctx context.Context, encounterID string, input dto.EncounterInput) (*dto.Encounter, error) {
if encounterID == "" {
return nil, fmt.Errorf("an encounterID is required")
Expand Down
26 changes: 26 additions & 0 deletions pkg/clinical/usecases/clinical/encounter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,22 @@ func TestUseCasesClinicalImpl_StartEncounter(t *testing.T) {
},
wantErr: true,
},
{
name: "Sad Case - failed to get concept",
args: args{
ctx: ctx,
episodeID: uuid.New().String(),
},
wantErr: true,
},
{
name: "Sad Case - failed to create FHIR composition",
args: args{
ctx: ctx,
episodeID: uuid.New().String(),
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -100,6 +116,16 @@ func TestUseCasesClinicalImpl_StartEncounter(t *testing.T) {
return nil, fmt.Errorf("failed to get tenant identifiers")
}
}
if tt.name == "Sad Case - failed to get concept" {
fakeOCL.MockGetConceptFn = func(ctx context.Context, org, source, concept string, includeMappings, includeInverseMappings bool) (*domain.Concept, error) {
return nil, fmt.Errorf("failed to get concept")
}
}
if tt.name == "Sad Case - failed to create FHIR composition" {
fakeFHIR.MockCreateFHIRCompositionFn = func(ctx context.Context, input domain.FHIRCompositionInput) (*domain.FHIRCompositionRelayPayload, error) {
return nil, fmt.Errorf("failed to create FHIR composition")
}
}

got, err := u.StartEncounter(tt.args.ctx, tt.args.episodeID)
if (err != nil) != tt.wantErr {
Expand Down

0 comments on commit 771311e

Please sign in to comment.