Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 4 additions & 15 deletions clinics/summaries.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,33 +47,22 @@ func exportSummaryConfig(config types.Config) clinic.SummaryConfigV1 {
}

func exportSummaryDates(dates types.Dates) clinic.SummaryDatesV1 {
// The reasons are reported as empty rather than absent so that the clinic service replaces any
// previously reported reasons
lastUpdatedReason := dates.LastUpdatedReason
if lastUpdatedReason == nil {
lastUpdatedReason = []string{}
}
outdatedReason := dates.OutdatedReason
if outdatedReason == nil {
outdatedReason = []string{}
}

firstData := timeOrNil(dates.FirstData)
lastData := timeOrNil(dates.LastData)
lastUploadDate := timeOrNil(dates.LastUploadDate)

// The reasons and the outdated mark are no longer maintained; the reasons are reported as empty
// rather than omitted so that the clinic service clears any values reported before their removal
return clinic.SummaryDatesV1{
LastUpdatedDate: timeOrNil(dates.LastUpdatedDate),
LastUpdatedReason: &lastUpdatedReason,
OutdatedReason: &outdatedReason,
LastUpdatedReason: pointer.FromAny([]string{}),
OutdatedReason: pointer.FromAny([]string{}),
HasLastUploadDate: lastUploadDate != nil,
LastUploadDate: lastUploadDate,
HasFirstData: firstData != nil,
FirstData: firstData,
HasLastData: lastData != nil,
LastData: lastData,
HasOutdatedSince: dates.OutdatedSince != nil,
OutdatedSince: dates.OutdatedSince,
}
}

Expand Down
23 changes: 5 additions & 18 deletions clinics/summaries_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package clinics_test

import (
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gstruct"
Expand Down Expand Up @@ -57,10 +55,8 @@ var _ = Describe("NewPatientSummary", func() {
Expect(dates.HasLastData).To(BeTrue())
Expect(dates.LastUploadDate).To(PointTo(BeTemporally("==", cgm.Dates.LastUploadDate)))
Expect(dates.HasLastUploadDate).To(BeTrue())
Expect(dates.OutdatedSince).To(Equal(cgm.Dates.OutdatedSince))
Expect(dates.HasOutdatedSince).To(BeTrue())
Expect(dates.LastUpdatedReason).To(PointTo(Equal(cgm.Dates.LastUpdatedReason)))
Expect(dates.OutdatedReason).To(PointTo(Equal(cgm.Dates.OutdatedReason)))
Expect(dates.OutdatedSince).To(BeNil())
Expect(dates.HasOutdatedSince).To(BeFalse())
})

It("omits the dates the summary does not have", func() {
Expand All @@ -78,12 +74,9 @@ var _ = Describe("NewPatientSummary", func() {
Expect(dates.HasOutdatedSince).To(BeFalse())
})

// The clinic service replaces only what the report carries, so reasons no longer held must be
// reported as empty rather than omitted
It("reports absent reasons as empty", func() {
cgm.Dates.LastUpdatedReason = nil
cgm.Dates.OutdatedReason = nil

// The reasons are no longer maintained; they are reported as empty rather than omitted so that
// the clinic service clears any values reported before their removal
It("reports the reasons as empty", func() {
dates := clinics.NewPatientSummary(cgm, nil).CgmStats.Dates
Expect(dates.LastUpdatedReason).To(PointTo(BeEmpty()))
Expect(dates.OutdatedReason).To(PointTo(BeEmpty()))
Expand Down Expand Up @@ -287,10 +280,4 @@ var _ = Describe("NewPatientSummary", func() {
})
})

It("round-trips a zero-valued outdated since", func() {
cgm.Dates.OutdatedSince = &time.Time{}
dates := clinics.NewPatientSummary(cgm, nil).CgmStats.Dates
Expect(dates.OutdatedSince).To(PointTo(BeTemporally("==", time.Time{})))
Expect(dates.HasOutdatedSince).To(BeTrue())
})
})
107 changes: 0 additions & 107 deletions data/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ type Client interface {
GetCGMSummary(ctx context.Context, id string) (*types.Summary[*types.CGMPeriods, *types.GlucoseBucket, types.CGMPeriods, types.GlucoseBucket], error)
GetBGMSummary(ctx context.Context, id string) (*types.Summary[*types.BGMPeriods, *types.GlucoseBucket, types.BGMPeriods, types.GlucoseBucket], error)
GetContinuousSummary(ctx context.Context, id string) (*types.Summary[*types.ContinuousPeriods, *types.ContinuousBucket, types.ContinuousPeriods, types.ContinuousBucket], error)
UpdateCGMSummary(ctx context.Context, id string) (*types.Summary[*types.CGMPeriods, *types.GlucoseBucket, types.CGMPeriods, types.GlucoseBucket], error)
UpdateBGMSummary(ctx context.Context, id string) (*types.Summary[*types.BGMPeriods, *types.GlucoseBucket, types.BGMPeriods, types.GlucoseBucket], error)
UpdateContinuousSummary(ctx context.Context, id string) (*types.Summary[*types.ContinuousPeriods, *types.ContinuousBucket, types.ContinuousPeriods, types.ContinuousBucket], error)
GetOutdatedUserIDs(ctx context.Context, t string, pagination *page.Pagination) (*types.OutdatedSummariesResponse, error)
GetMigratableUserIDs(ctx context.Context, t string, pagination *page.Pagination) ([]string, error)
}

type ClientImpl struct {
Expand Down Expand Up @@ -181,108 +176,6 @@ func (c *ClientImpl) GetContinuousSummary(ctx context.Context, userId string) (*
return summary, nil
}

func (c *ClientImpl) UpdateCGMSummary(ctx context.Context, userId string) (*types.Summary[*types.CGMPeriods, *types.GlucoseBucket, types.CGMPeriods, types.GlucoseBucket], error) {
if ctx == nil {
return nil, errors.New("context is missing")
}
if userId == "" {
return nil, errors.New("id is missing")
}

url := c.client.ConstructURL("v1", "summaries", "cgm", userId)
summary := &types.Summary[*types.CGMPeriods, *types.GlucoseBucket, types.CGMPeriods, types.GlucoseBucket]{}
if err := c.client.RequestData(ctx, http.MethodPost, url, nil, nil, summary); err != nil {
if request.IsErrorResourceNotFound(err) {
return nil, nil
}
return nil, errors.Cause(err)
}

return summary, nil
}

func (c *ClientImpl) UpdateBGMSummary(ctx context.Context, userId string) (*types.Summary[*types.BGMPeriods, *types.GlucoseBucket, types.BGMPeriods, types.GlucoseBucket], error) {
if ctx == nil {
return nil, errors.New("context is missing")
}
if userId == "" {
return nil, errors.New("id is missing")
}

url := c.client.ConstructURL("v1", "summaries", "bgm", userId)
summary := &types.Summary[*types.BGMPeriods, *types.GlucoseBucket, types.BGMPeriods, types.GlucoseBucket]{}
if err := c.client.RequestData(ctx, http.MethodPost, url, nil, nil, summary); err != nil {
if request.IsErrorResourceNotFound(err) {
return nil, nil
}
return nil, errors.Cause(err)
}

return summary, nil
}

func (c *ClientImpl) UpdateContinuousSummary(ctx context.Context, userId string) (*types.Summary[*types.ContinuousPeriods, *types.ContinuousBucket, types.ContinuousPeriods, types.ContinuousBucket], error) {
if ctx == nil {
return nil, errors.New("context is missing")
}
if userId == "" {
return nil, errors.New("id is missing")
}

url := c.client.ConstructURL("v1", "summaries", "con", userId)
summary := &types.Summary[*types.ContinuousPeriods, *types.ContinuousBucket, types.ContinuousPeriods, types.ContinuousBucket]{}
if err := c.client.RequestData(ctx, http.MethodPost, url, nil, nil, summary); err != nil {
if request.IsErrorResourceNotFound(err) {
return nil, nil
}
return nil, errors.Cause(err)
}

return summary, nil
}

func (c *ClientImpl) GetOutdatedUserIDs(ctx context.Context, typ string, pagination *page.Pagination) (*types.OutdatedSummariesResponse, error) {
if ctx == nil {
return nil, errors.New("context is missing")
}
if typ == "" {
return nil, errors.New("type is missing")
}
url := c.client.ConstructURL("v1", "summaries", "outdated", typ)

if pagination == nil {
pagination = page.NewPagination()
} else if err := structureValidator.New(log.LoggerFromContext(ctx)).Validate(pagination); err != nil {
return nil, errors.Wrap(err, "pagination is invalid")
}

response := &types.OutdatedSummariesResponse{}
err := c.client.RequestData(ctx, http.MethodGet, url, []request.RequestMutator{pagination}, nil, response)

return response, err
}

func (c *ClientImpl) GetMigratableUserIDs(ctx context.Context, typ string, pagination *page.Pagination) ([]string, error) {
if ctx == nil {
return nil, errors.New("context is missing")
}
if typ == "" {
return nil, errors.New("type is missing")
}
url := c.client.ConstructURL("v1", "summaries", "migratable", typ)

if pagination == nil {
pagination = page.NewPagination()
} else if err := structureValidator.New(log.LoggerFromContext(ctx)).Validate(pagination); err != nil {
return nil, errors.Wrap(err, "pagination is invalid")
}

var userIDs []string
err := c.client.RequestData(ctx, http.MethodGet, url, []request.RequestMutator{pagination}, nil, &userIDs)

return userIDs, err
}

func (c *ClientImpl) UpdateDataSet(ctx context.Context, id string, update *data.DataSetUpdate) (*data.DataSet, error) {
if ctx == nil {
return nil, errors.New("context is missing")
Expand Down
81 changes: 9 additions & 72 deletions data/service/api/v1/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (

"github.com/tidepool-org/platform/clinics"
dataService "github.com/tidepool-org/platform/data/service"
dataWorkPostprocess "github.com/tidepool-org/platform/data/work/postprocess"
"github.com/tidepool-org/platform/errors"
"github.com/tidepool-org/platform/page"
"github.com/tidepool-org/platform/permission"
"github.com/tidepool-org/platform/request"
"github.com/tidepool-org/platform/service"
Expand All @@ -26,17 +26,9 @@ func SummaryRoutes() []dataService.Route {
dataService.Get("/v1/summaries/bgm/:userId", GetSummary[*types.BGMPeriods, *types.GlucoseBucket], api.RequireAuth),
dataService.Get("/v1/summaries/con/:userId", GetSummary[*types.ContinuousPeriods, *types.ContinuousBucket], api.RequireAuth),

dataService.Post("/v1/summaries/cgm/:userId", UpdateSummary[*types.CGMPeriods, *types.GlucoseBucket], api.RequireAuth),
dataService.Post("/v1/summaries/bgm/:userId", UpdateSummary[*types.BGMPeriods, *types.GlucoseBucket], api.RequireAuth),
dataService.Post("/v1/summaries/con/:userId", UpdateSummary[*types.ContinuousPeriods, *types.ContinuousBucket], api.RequireAuth),

dataService.Get("/v1/summaries/outdated/cgm", GetOutdatedUserIDs[*types.CGMPeriods, *types.GlucoseBucket], api.RequireAuth),
dataService.Get("/v1/summaries/outdated/bgm", GetOutdatedUserIDs[*types.BGMPeriods, *types.GlucoseBucket], api.RequireAuth),
dataService.Get("/v1/summaries/outdated/con", GetOutdatedUserIDs[*types.ContinuousPeriods, *types.ContinuousBucket], api.RequireAuth),

dataService.Get("/v1/summaries/migratable/cgm", GetMigratableUserIDs[*types.CGMPeriods, *types.GlucoseBucket], api.RequireAuth),
dataService.Get("/v1/summaries/migratable/bgm", GetMigratableUserIDs[*types.BGMPeriods, *types.GlucoseBucket], api.RequireAuth),
dataService.Get("/v1/summaries/migratable/con", GetMigratableUserIDs[*types.ContinuousPeriods, *types.ContinuousBucket], api.RequireAuth),
dataService.Post("/v1/summaries/cgm/:userId", UpdateSummary, api.RequireAuth),
dataService.Post("/v1/summaries/bgm/:userId", UpdateSummary, api.RequireAuth),
dataService.Post("/v1/summaries/con/:userId", UpdateSummary, api.RequireAuth),

dataService.Get("/v1/clinics/:clinicId/reports/realtime", GetPatientsWithRealtimeData, api.RequireAuth),
}
Expand Down Expand Up @@ -130,7 +122,9 @@ func GetPatientsWithRealtimeData(dataServiceContext dataService.Context) {
responder.Data(http.StatusOK, response)
}

func UpdateSummary[PP types.PeriodsPt[P, PB, B], PB types.BucketDataPt[B], P types.Periods, B types.BucketData](dataServiceContext dataService.Context) {
// UpdateSummary reports the data of the user as changed rather than recalculating synchronously,
// which the retired task runners required. The work created recalculates every summary of the user.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a migration plan to collapsed the three endpoints down to one, since all three do the same work?

func UpdateSummary(dataServiceContext dataService.Context) {
ctx := dataServiceContext.Request().Context()
res := dataServiceContext.Response()
req := dataServiceContext.Request()
Expand All @@ -143,67 +137,10 @@ func UpdateSummary[PP types.PeriodsPt[P, PB, B], PB types.BucketDataPt[B], P typ
return
}

summarizer := summary.GetSummarizer[PP, PB](dataServiceContext.SummarizerRegistry())
userSummary, err := summarizer.UpdateSummary(ctx, id)
if err != nil {
responder.InternalServerError(err)
} else {
responder.Data(http.StatusOK, userSummary)
}
}

func GetOutdatedUserIDs[PP types.PeriodsPt[P, PB, B], PB types.BucketDataPt[B], P types.Periods, B types.BucketData](dataServiceContext dataService.Context) {
ctx := dataServiceContext.Request().Context()
res := dataServiceContext.Response()
req := dataServiceContext.Request()

responder := request.MustNewResponder(res, req)

if details := request.GetAuthDetails(ctx); !details.IsService() {
dataServiceContext.RespondWithError(service.ErrorUnauthorized())
return
}

pagination := page.NewPagination()
if err := request.DecodeRequestQuery(req.Request, pagination); err != nil {
responder.Error(http.StatusBadRequest, err)
return
}

summarizer := summary.GetSummarizer[PP, PB](dataServiceContext.SummarizerRegistry())
response, err := summarizer.GetOutdatedUserIDs(ctx, pagination)
if err != nil {
responder.InternalServerError(err)
return
}

responder.Data(http.StatusOK, response)
}

func GetMigratableUserIDs[PP types.PeriodsPt[P, PB, B], PB types.BucketDataPt[B], P types.Periods, B types.BucketData](dataServiceContext dataService.Context) {
ctx := dataServiceContext.Request().Context()
res := dataServiceContext.Response()
req := dataServiceContext.Request()

responder := request.MustNewResponder(res, req)

if details := request.GetAuthDetails(ctx); !details.IsService() {
dataServiceContext.RespondWithError(service.ErrorUnauthorized())
return
}

pagination := page.NewPagination()
if err := request.DecodeRequestQuery(req.Request, pagination); err != nil {
responder.Error(http.StatusBadRequest, err)
return
}

summarizer := summary.GetSummarizer[PP, PB](dataServiceContext.SummarizerRegistry())
userIDs, err := summarizer.GetMigratableUserIDs(ctx, pagination)
if err != nil {
if err := dataWorkPostprocess.Enqueue(ctx, dataServiceContext.WorkClient(), id, dataWorkPostprocess.ReasonDataAdded); err != nil {
responder.InternalServerError(err)
return
}

responder.Data(http.StatusOK, userIDs)
responder.Empty(http.StatusAccepted)
}
20 changes: 0 additions & 20 deletions data/service/service/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,26 +94,6 @@ func (c *Client) GetBGMSummary(ctx context.Context, id string) (*types.Summary[*
panic("Not Implemented!")
}

func (c *Client) UpdateCGMSummary(ctx context.Context, id string) (*types.Summary[*types.CGMPeriods, *types.GlucoseBucket, types.CGMPeriods, types.GlucoseBucket], error) {
panic("Not Implemented!")
}

func (c *Client) UpdateBGMSummary(ctx context.Context, id string) (*types.Summary[*types.BGMPeriods, *types.GlucoseBucket, types.BGMPeriods, types.GlucoseBucket], error) {
panic("Not Implemented!")
}

func (c *Client) GetOutdatedUserIDs(ctx context.Context, t string, pagination *page.Pagination) (*types.OutdatedSummariesResponse, error) {
panic("Not Implemented!")
}

func (c *Client) GetMigratableUserIDs(ctx context.Context, t string, pagination *page.Pagination) ([]string, error) {
panic("Not Implemented!")
}

func (c *Client) GetContinuousSummary(ctx context.Context, id string) (*types.Summary[*types.ContinuousPeriods, *types.ContinuousBucket, types.ContinuousPeriods, types.ContinuousBucket], error) {
panic("Not Implemented!")
}

func (c *Client) UpdateContinuousSummary(ctx context.Context, id string) (*types.Summary[*types.ContinuousPeriods, *types.ContinuousBucket, types.ContinuousPeriods, types.ContinuousBucket], error) {
panic("Not Implemented!")
}
20 changes: 0 additions & 20 deletions data/store/mongo/mongo_summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,6 @@ func (d *SummaryRepository) EnsureIndexes() error {
SetUnique(true).
SetName("UserIDTypeUnique"),
},
{
Keys: bson.D{
{Key: "type", Value: 1},
{Key: "dates.outdatedSince", Value: 1},
{Key: "config.schemaVersion", Value: 1},
{Key: "dates.lastUpdatedDate", Value: 1},
},
Options: options.Index().
SetName("OutdatedSinceSchemaLastUpdated"),
},
{
Keys: bson.D{
{Key: "type", Value: 1},
{Key: "dates.outdatedSince", Value: 1},
{Key: "dates.lastUpdatedDate", Value: 1},
{Key: "config.schemaVersion", Value: 1},
},
Options: options.Index().
SetName("OutdatedAndSchemaMigration"),
},
})
}

Expand Down
10 changes: 0 additions & 10 deletions data/store/mongo/mongo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,16 +412,6 @@ var _ = Describe("Mongo", Label("mongodb", "slow", "integration"), func() {
"Unique": Equal(true),
"Name": Equal("UserIDTypeUnique"),
}),
MatchFields(IgnoreExtras, Fields{
"Key": Equal(storeStructuredMongoTest.MakeKeySlice("type", "dates.outdatedSince", "config.schemaVersion", "dates.lastUpdatedDate")),
"Background": Equal(false),
"Name": Equal("OutdatedSinceSchemaLastUpdated"),
}),
MatchFields(IgnoreExtras, Fields{
"Key": Equal(storeStructuredMongoTest.MakeKeySlice("type", "dates.outdatedSince", "dates.lastUpdatedDate", "config.schemaVersion")),
"Background": Equal(false),
"Name": Equal("OutdatedAndSchemaMigration"),
}),
))
})

Expand Down
2 changes: 1 addition & 1 deletion plugin/abbott/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ require (
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/tidepool-org/clinic/client v0.0.0-20250122123230-f89e2b1540dc // indirect
github.com/tidepool-org/clinic/client v0.0.0-20260814105914-911a077531db // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
Expand Down
Loading