-
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow specifying custom cadence for EHR reports
- Loading branch information
1 parent
9a6c927
commit fe0cf08
Showing
22 changed files
with
874 additions
and
124 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package reconcile | ||
|
||
import ( | ||
"context" | ||
|
||
duration "github.com/xhit/go-str2duration/v2" | ||
|
||
"github.com/tidepool-org/platform/clinics" | ||
"github.com/tidepool-org/platform/ehr/sync" | ||
"github.com/tidepool-org/platform/log" | ||
"github.com/tidepool-org/platform/task" | ||
) | ||
|
||
type Planner struct { | ||
clinicsClient clinics.Client | ||
logger log.Logger | ||
} | ||
|
||
func NewPlanner(clinicsClient clinics.Client, logger log.Logger) *Planner { | ||
return &Planner{ | ||
clinicsClient: clinicsClient, | ||
logger: logger, | ||
} | ||
} | ||
|
||
func (p *Planner) GetReconciliationPlan(ctx context.Context, syncTasks map[string]task.Task) (*ReconciliationPlan, error) { | ||
toCreate := make([]task.TaskCreate, 0) | ||
toDelete := make([]task.Task, 0) | ||
toUpdate := make(map[string]*task.TaskUpdate) | ||
|
||
// Get the list of all EHR enabled clinics | ||
clinicsList, err := p.clinicsClient.ListEHREnabledClinics(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// At the end of the loop syncTasks will contain only the tasks that need to be deleted, | ||
// and toCreate will contain tasks for new clinics that need to be synced. | ||
for _, clinic := range clinicsList { | ||
clinicId := *clinic.Id | ||
settings, err := p.clinicsClient.GetEHRSettings(ctx, clinicId) | ||
if err != nil { | ||
return nil, err | ||
} else if settings == nil || !settings.Enabled { | ||
continue | ||
} | ||
|
||
// Use the default value for all clinics which don't have a cadence | ||
cadenceFromSettings := sync.DefaultCadence | ||
parsed, err := duration.ParseDuration(string(settings.ScheduledReports.Cadence)) | ||
if err != nil { | ||
p.logger.WithField("clinicId", clinicId).WithError(err).Error("unable to parse scheduled report cadence") | ||
continue | ||
} | ||
cadenceFromSettings = parsed | ||
|
||
tsk, exists := syncTasks[clinicId] | ||
if exists { | ||
|
||
delete(syncTasks, clinicId) | ||
if cadenceFromSettings == 0 { | ||
toDelete = append(toDelete, tsk) | ||
continue | ||
} | ||
|
||
cadenceFromTask := sync.GetCadence(tsk.Data) | ||
if cadenceFromTask == nil || *cadenceFromTask != cadenceFromSettings { | ||
sync.SetCadence(tsk.Data, cadenceFromSettings) | ||
update := task.NewTaskUpdate() | ||
update.Data = &tsk.Data | ||
toUpdate[tsk.ID] = update | ||
} | ||
} else if cadenceFromSettings != 0 { | ||
// The task doesn't exist yet and scheduled reports are not disabled | ||
create := sync.NewTaskCreate(clinicId, cadenceFromSettings) | ||
toCreate = append(toCreate, *create) | ||
} | ||
} | ||
for _, tsk := range syncTasks { | ||
toDelete = append(toDelete, tsk) | ||
} | ||
return &ReconciliationPlan{ | ||
ToCreate: toCreate, | ||
ToDelete: toDelete, | ||
ToUpdate: toUpdate, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
package reconcile_test | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/golang/mock/gomock" | ||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
. "github.com/onsi/gomega/gstruct" | ||
api "github.com/tidepool-org/clinic/client" | ||
|
||
"github.com/tidepool-org/platform/clinics" | ||
"github.com/tidepool-org/platform/log" | ||
"github.com/tidepool-org/platform/log/null" | ||
|
||
clinicsTest "github.com/tidepool-org/platform/clinics/test" | ||
"github.com/tidepool-org/platform/ehr/reconcile" | ||
"github.com/tidepool-org/platform/ehr/sync" | ||
"github.com/tidepool-org/platform/task" | ||
"github.com/tidepool-org/platform/test" | ||
) | ||
|
||
var _ = Describe("Planner", func() { | ||
var authCtrl *gomock.Controller | ||
var clinicsCtrl *gomock.Controller | ||
var taskCtrl *gomock.Controller | ||
|
||
var clinicsClient *clinics.MockClient | ||
var logger log.Logger | ||
var planner *reconcile.Planner | ||
|
||
BeforeEach(func() { | ||
authCtrl = gomock.NewController(GinkgoT()) | ||
clinicsCtrl = gomock.NewController(GinkgoT()) | ||
taskCtrl = gomock.NewController(GinkgoT()) | ||
clinicsClient = clinics.NewMockClient(clinicsCtrl) | ||
logger = null.NewLogger() | ||
planner = reconcile.NewPlanner(clinicsClient, logger) | ||
}) | ||
|
||
AfterEach(func() { | ||
authCtrl.Finish() | ||
clinicsCtrl.Finish() | ||
taskCtrl.Finish() | ||
}) | ||
|
||
Context("With random data", func() { | ||
var clinics []api.Clinic | ||
var tasks map[string]task.Task | ||
|
||
BeforeEach(func() { | ||
clinics = test.RandomArrayWithLength(3, clinicsTest.NewRandomClinic) | ||
tasks = make(map[string]task.Task) | ||
for _, clinic := range clinics { | ||
clinic := clinic | ||
tsk, err := task.NewTask(sync.NewTaskCreate(*clinic.Id, sync.DefaultCadence)) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(tsk).ToNot(BeNil()) | ||
tasks[*clinic.Id] = *tsk | ||
} | ||
}) | ||
|
||
Describe("GetReconciliationPlan", func() { | ||
It("returns an empty plan when each clinic has a corresponding task", func() { | ||
clinicsClient.EXPECT().ListEHREnabledClinics(gomock.Any()).Return(clinics, nil) | ||
setupEHRSettingsForClinics(clinicsClient, clinics) | ||
|
||
plan, err := planner.GetReconciliationPlan(context.Background(), tasks) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(plan).ToNot(BeNil()) | ||
Expect(plan.ToCreate).To(BeEmpty()) | ||
Expect(plan.ToDelete).To(BeEmpty()) | ||
}) | ||
|
||
It("returns a clinic creation task when a task for the clinic doesn't exist", func() { | ||
clinicsClient.EXPECT().ListEHREnabledClinics(gomock.Any()).Return(clinics, nil) | ||
setupEHRSettingsForClinics(clinicsClient, clinics) | ||
delete(tasks, *clinics[0].Id) | ||
|
||
plan, err := planner.GetReconciliationPlan(context.Background(), tasks) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(plan).ToNot(BeNil()) | ||
Expect(plan.ToCreate).To(HaveLen(1)) | ||
Expect(plan.ToCreate[0].Name).To(PointTo(Equal(sync.TaskName(*clinics[0].Id)))) | ||
Expect(plan.ToDelete).To(BeEmpty()) | ||
}) | ||
|
||
It("returns multiple clinic creation tasks when multiple clinics don't exist", func() { | ||
clinicsClient.EXPECT().ListEHREnabledClinics(gomock.Any()).Return(clinics, nil) | ||
setupEHRSettingsForClinics(clinicsClient, clinics) | ||
delete(tasks, *clinics[1].Id) | ||
delete(tasks, *clinics[2].Id) | ||
|
||
plan, err := planner.GetReconciliationPlan(context.Background(), tasks) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(plan).ToNot(BeNil()) | ||
Expect(plan.ToCreate).To(HaveLen(2)) | ||
Expect(plan.ToCreate[0].Name).To(PointTo(Equal(sync.TaskName(*clinics[1].Id)))) | ||
Expect(plan.ToCreate[1].Name).To(PointTo(Equal(sync.TaskName(*clinics[2].Id)))) | ||
Expect(plan.ToDelete).To(BeEmpty()) | ||
}) | ||
|
||
It("returns a clinic for deletion when the task doesn't exist", func() { | ||
deleted := clinics[2] | ||
clinics = clinics[0:2] | ||
clinicsClient.EXPECT().ListEHREnabledClinics(gomock.Any()).Return(clinics, nil) | ||
setupEHRSettingsForClinics(clinicsClient, clinics) | ||
|
||
plan, err := planner.GetReconciliationPlan(context.Background(), tasks) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(plan).ToNot(BeNil()) | ||
Expect(plan.ToCreate).To(BeEmpty()) | ||
Expect(plan.ToDelete).To(HaveLen(1)) | ||
Expect(plan.ToDelete[0].Name).To(PointTo(Equal(sync.TaskName(*deleted.Id)))) | ||
}) | ||
|
||
It("returns multiple clinics for deletion when multiple tasks don't exist", func() { | ||
firstDeleted := clinics[1] | ||
secondDeleted := clinics[2] | ||
clinics = []api.Clinic{clinics[0]} | ||
|
||
clinicsClient.EXPECT().ListEHREnabledClinics(gomock.Any()).Return(clinics, nil) | ||
setupEHRSettingsForClinics(clinicsClient, clinics) | ||
|
||
plan, err := planner.GetReconciliationPlan(context.Background(), tasks) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(plan).ToNot(BeNil()) | ||
Expect(plan.ToCreate).To(BeEmpty()) | ||
Expect(plan.ToDelete).To(HaveLen(2)) | ||
Expect( | ||
[]string{*plan.ToDelete[0].Name, *plan.ToDelete[1].Name}, | ||
).To( | ||
ConsistOf(sync.TaskName(*firstDeleted.Id), sync.TaskName(*secondDeleted.Id)), | ||
) | ||
}) | ||
|
||
It("returns a task for deletion when the report settings are disabled", func() { | ||
settings := clinicsTest.NewRandomEHRSettings() | ||
settings.ScheduledReports.Cadence = api.N0d | ||
|
||
clinics = clinics[0:1] | ||
clinicsClient.EXPECT().ListEHREnabledClinics(gomock.Any()).Return(clinics, nil) | ||
clinicsClient.EXPECT().GetEHRSettings(gomock.Any(), *clinics[0].Id).Return(settings, nil) | ||
|
||
tasks = map[string]task.Task{ | ||
*clinics[0].Id: tasks[*clinics[0].Id], | ||
} | ||
|
||
plan, err := planner.GetReconciliationPlan(context.Background(), tasks) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(plan).ToNot(BeNil()) | ||
Expect(plan.ToCreate).To(BeEmpty()) | ||
Expect(plan.ToUpdate).To(BeEmpty()) | ||
Expect(plan.ToDelete).To(HaveLen(1)) | ||
}) | ||
|
||
It("returns a task for update when the report cadence is different", func() { | ||
settings := clinicsTest.NewRandomEHRSettings() | ||
settings.ScheduledReports.Cadence = api.N7d | ||
|
||
clinics = clinics[0:1] | ||
clinicsClient.EXPECT().ListEHREnabledClinics(gomock.Any()).Return(clinics, nil) | ||
clinicsClient.EXPECT().GetEHRSettings(gomock.Any(), *clinics[0].Id).Return(settings, nil) | ||
|
||
tsk := tasks[*clinics[0].Id] | ||
tasks = map[string]task.Task{ | ||
*clinics[0].Id: tsk, | ||
} | ||
|
||
plan, err := planner.GetReconciliationPlan(context.Background(), tasks) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(plan).ToNot(BeNil()) | ||
Expect(plan.ToCreate).To(BeEmpty()) | ||
Expect(plan.ToDelete).To(BeEmpty()) | ||
Expect(plan.ToUpdate).To(HaveLen(1)) | ||
|
||
update, exists := plan.ToUpdate[tsk.ID] | ||
Expect(exists).To(BeTrue()) | ||
Expect(update.Data).ToNot(BeNil()) | ||
Expect((*update.Data)["cadence"]).To(Equal("168h0m0s")) | ||
}) | ||
}) | ||
}) | ||
}) | ||
|
||
func setupEHRSettingsForClinics(clinicsClient *clinics.MockClient, clinics []api.Clinic) { | ||
for _, clinic := range clinics { | ||
clinicsClient.EXPECT().GetEHRSettings(gomock.Any(), *clinic.Id).Return(clinicsTest.NewRandomEHRSettings(), nil) | ||
} | ||
} |
Oops, something went wrong.