Repository: tidepool-org/platform
Pull request #832: [BACK-3514] Handle out-of-order food and bolus data for twiist
Author: login: darinkrauss
Created: 2025-04-02T20:40:45Z | Merged: 2025-04-10T20:09:16Z

- Handle out-of-order food and bolus data for twiist
- Add new deduplicator
- https://tidepool.atlassian.net/browse/BACK-3514


Diff:
diff --git a/data/data.go b/data/data.go
index 476a3cf27..10e1d357a 100644
--- a/data/data.go
+++ b/data/data.go
@@ -16,7 +16,8 @@ const (
 )
 
 type SelectorOrigin struct {
-	ID *string `json:"id,omitempty"`
+	ID   *string `json:"id,omitempty" bson:"id,omitempty"`
+	Time *string `json:"time,omitempty" bson:"time,omitempty"` // Inclusive, currently NOT used in database query
 }
 
 func ParseSelectorOrigin(parser structure.ObjectParser) *SelectorOrigin {
@@ -34,15 +35,38 @@ func NewSelectorOrigin() *SelectorOrigin {
 
 func (s *SelectorOrigin) Parse(parser structure.ObjectParser) {
 	s.ID = parser.String("id")
+	s.Time = parser.String("time")
 }
 
 func (s *SelectorOrigin) Validate(validator structure.Validator) {
 	validator.String("id", s.ID).Exists().NotEmpty().LengthLessThanOrEqualTo(SelectorOriginIDLengthMaximum)
+	validator.String("time", s.Time).AsTime(time.RFC3339Nano).NotZero()
+}
+
+func (s *SelectorOrigin) Includes(other *SelectorOrigin) bool {
+	if s == nil || other == nil { // Must not be missing
+		return false
+	} else if s.ID != nil && (other.ID == nil || *s.ID != *other.ID) { // If id matters, then must include
+		return false
+	} else if s.Time == nil { // If time does not matter, success
+		return true
+	} else if other.Time == nil { // Must exist
+		return false
+	} else if sTime, err := time.Parse(time.RFC3339Nano, *s.Time); err != nil || sTime.IsZero() { // Must parse
+		return false
+	} else if otherTime, err := time.Parse(time.RFC3339Nano, *other.Time); err != nil || otherTime.IsZero() { // Must parse
+		return false
+	} else if otherTime.Before(sTime) { // Must include
+		return false
+	} else {
+		return true
+	}
 }
 
 type Selector struct {
-	ID     *string         `json:"id,omitempty"`
-	Origin *SelectorOrigin `json:"origin,omitempty"`
+	ID     *string         `json:"id,omitempty" bson:"id,omitempty"`
+	Time   *time.Time      `json:"time,omitempty" bson:"time,omitempty"` // Inclusive, currently NOT used in database query
+	Origin *SelectorOrigin `json:"origin,omitempty" bson:"origin,omitempty"`
 }
 
 func ParseSelector(parser structure.ObjectParser) *Selector {
@@ -60,6 +84,7 @@ func NewSelector() *Selector {
 
 func (s *Selector) Parse(parser structure.ObjectParser) {
 	s.ID = parser.String("id")
+	s.Time = parser.Time("time", TimeFormat)
 	s.Origin = ParseSelectorOrigin(parser.WithReferenceObjectParser("origin"))
 }
 
@@ -68,11 +93,27 @@ func (s *Selector) Validate(validator structure.Validator) {
 		validator.ReportError(structureValidator.ErrorValuesNotExistForOne("id", "origin"))
 	} else if s.ID != nil {
 		validator.String("id", s.ID).Using(IDValidator)
+		validator.Time("time", s.Time).NotZero()
 	} else {
+		validator.Time("time", s.Time).NotExists()
 		s.Origin.Validate(validator.WithReference("origin"))
 	}
 }
 
+func (s *Selector) Includes(other *Selector) bool {
+	if s == nil || other == nil { // Must not be missing
+		return false
+	} else if s.ID != nil && (other.ID == nil || *s.ID != *other.ID) { // If id matters, then must include
+		return false
+	} else if s.Time != nil && (other.Time == nil || other.Time.Before(*s.Time)) { // If time matters, then must include
+		return false
+	} else if s.Origin != nil && (other.Origin == nil || !s.Origin.Includes(other.Origin)) { // If origin matters, then must include
+		return false
+	} else {
+		return true
+	}
+}
+
 type Selectors []*Selector
 
 func ParseSelectors(parser structure.ArrayParser) *Selectors {
@@ -107,6 +148,16 @@ func (s *Selectors) Validate(validator structure.Validator) {
 	}
 }
 
+func (s *Selectors) Filter(predicate func(*Selector) bool) *Selectors {
+	filtered := Selectors{}
+	for _, selector := range *s {
+		if predicate(selector) {
+			filtered = append(filtered, selector)
+		}
+	}
+	return &filtered
+}
+
 func NewID() string {
 	return id.Must(id.New(16))
 }
diff --git a/data/data_test.go b/data/data_test.go
index 519cfac85..88a1796b8 100644
--- a/data/data_test.go
+++ b/data/data_test.go
@@ -1,19 +1,83 @@
 package data_test
 
 import (
+	"time"
+
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 
 	"github.com/tidepool-org/platform/data"
 	errorsTest "github.com/tidepool-org/platform/errors/test"
+	"github.com/tidepool-org/platform/pointer"
 	structureTest "github.com/tidepool-org/platform/structure/test"
 	structureValidator "github.com/tidepool-org/platform/structure/validator"
 	"github.com/tidepool-org/platform/test"
 )
 
 var _ = Describe("Data", func() {
+	Context("SelectorOrigin", func() {
+		Context("Includes", func() {
+			now := time.Now()
+			tm := pointer.FromString(now.Format(time.RFC3339Nano))
+			id := pointer.FromString(data.NewID())
+
+			DescribeTable("return the expected results when the selector origins",
+				func(origin *data.SelectorOrigin, otherOrigin *data.SelectorOrigin, expectedResult bool) {
+					Expect(origin.Includes(otherOrigin)).To(Equal(expectedResult))
+				},
+				Entry("both are nil", nil, nil, false),
+				Entry("origin is nil", nil, &data.SelectorOrigin{}, false),
+				Entry("other origin is nil", &data.SelectorOrigin{}, nil, false),
+				Entry("id and time are nil", &data.SelectorOrigin{}, &data.SelectorOrigin{}, true),
+				Entry("origin id is nil", &data.SelectorOrigin{}, &data.SelectorOrigin{ID: id}, true),
+				Entry("other origin id is nil", &data.SelectorOrigin{ID: id}, &data.SelectorOrigin{}, false),
+				Entry("id mismatch", &data.SelectorOrigin{ID: id}, &data.SelectorOrigin{ID: pointer.FromString("mismatch")}, false),
+				Entry("id includes", &data.SelectorOrigin{ID: id}, &data.SelectorOrigin{ID: id}, true),
+				Entry("origin time is nil", &data.SelectorOrigin{ID: id}, &data.SelectorOrigin{ID: id, Time: tm}, true),
+				Entry("other origin time is nil", &data.SelectorOrigin{ID: id, Time: tm}, &data.SelectorOrigin{ID: id}, false),
+				Entry("time earlier", &data.SelectorOrigin{ID: id, Time: tm}, &data.SelectorOrigin{ID: id, Time: pointer.FromString(now.Add(-time.Hour).Format(time.RFC3339Nano))}, false),
+				Entry("time same", &data.SelectorOrigin{ID: id, Time: tm}, &data.SelectorOrigin{ID: id, Time: tm}, true),
+				Entry("time same in different time zone", &data.SelectorOrigin{ID: id, Time: tm}, &data.SelectorOrigin{ID: id, Time: pointer.FromString(now.In(time.FixedZone("Etc/GMT-1", int(-time.Hour.Seconds()))).Format(time.RFC3339Nano))}, true),
+				Entry("time later", &data.SelectorOrigin{ID: id, Time: tm}, &data.SelectorOrigin{ID: id, Time: pointer.FromString(now.Add(time.Hour).Format(time.RFC3339Nano))}, true),
+			)
+		})
+	})
+
+	Context("Selector", func() {
+		Context("Includes", func() {
+			now := time.Now()
+			tm := pointer.FromTime(now)
+			id := pointer.FromString(data.NewID())
+			originID := pointer.FromString(data.NewID())
+
+			DescribeTable("return the expected results when the selector origins",
+				func(origin *data.Selector, otherOrigin *data.Selector, expectedResult bool) {
+					Expect(origin.Includes(otherOrigin)).To(Equal(expectedResult))
+				},
+				Entry("both are nil", nil, nil, false),
+				Entry("selector is nil", nil, &data.Selector{}, false),
+				Entry("other selector is nil", &data.Selector{}, nil, false),
+				Entry("id, time, and origin are nil", &data.Selector{}, &data.Selector{}, true),
+				Entry("selector id is nil", &data.Selector{}, &data.Selector{ID: id}, true),
+				Entry("other selector id is nil", &data.Selector{ID: id}, &data.Selector{}, false),
+				Entry("id mismatch", &data.Selector{ID: id}, &data.Selector{ID: pointer.FromString("mismatch")}, false),
+				Entry("id includes", &data.Selector{ID: id}, &data.Selector{ID: id}, true),
+				Entry("selector time is nil", &data.Selector{ID: id}, &data.Selector{ID: id, Time: tm}, true),
+				Entry("other selector time is nil", &data.Selector{ID: id, Time: tm}, &data.Selector{ID: id}, false),
+				Entry("time earlier", &data.Selector{ID: id, Time: tm}, &data.Selector{ID: id, Time: pointer.FromTime(now.Add(-time.Hour))}, false),
+				Entry("time same", &data.Selector{ID: id, Time: tm}, &data.Selector{ID: id, Time: tm}, true),
+				Entry("time same in different time zone", &data.Selector{ID: id, Time: tm}, &data.Selector{ID: id, Time: pointer.FromTime(now.In(time.FixedZone("Etc/GMT-1", int(-time.Hour.Seconds()))))}, true),
+				Entry("time later", &data.Selector{ID: id, Time: tm}, &data.Selector{ID: id, Time: pointer.FromTime(now.Add(time.Hour))}, true),
+				Entry("selector origin is nil", &data.Selector{ID: id}, &data.Selector{ID: id, Origin: &data.SelectorOrigin{ID: originID}}, true),
+				Entry("other selector origin is nil", &data.Selector{ID: id, Origin: &data.SelectorOrigin{ID: originID}}, &data.Selector{ID: id}, false),
+				Entry("origin id mismatch", &data.Selector{ID: id, Origin: &data.SelectorOrigin{ID: originID}}, &data.Selector{ID: id, Origin: &data.SelectorOrigin{ID: pointer.FromString("mismatch")}}, false),
+				Entry("origin match", &data.Selector{ID: id, Origin: &data.SelectorOrigin{ID: originID}}, &data.Selector{ID: id, Origin: &data.SelectorOrigin{ID: originID}}, true),
+			)
+		})
+	})
+
 	Context("NewID", func() {
-		It("returns a string of 32 lowercase hexidecimal characters", func() {
+		It("returns a string of 32 lowercase hexadecimal characters", func() {
 			Expect(data.NewID()).To(MatchRegexp("^[0-9a-f]{32}$"))
 		})
 
diff --git a/data/datum.go b/data/datum.go
index a233e0194..2c11ea5aa 100644
--- a/data/datum.go
+++ b/data/datum.go
@@ -60,6 +60,16 @@ func (d Data) SetModifiedTime(modifiedTime *time.Time) {
 	}
 }
 
+func (d Data) Filter(predicate func(Datum) bool) Data {
+	filtered := Data{}
+	for _, datum := range d {
+		if predicate(datum) {
+			filtered = append(filtered, datum)
+		}
+	}
+	return filtered
+}
+
 // Provenance of a document.
 //
 // Useful for determining additional actions to take. For example, if the
diff --git a/data/deduplicator/deduplicator/data_set_delete_origin.go b/data/deduplicator/deduplicator/data_set_delete_origin.go
index 1e35c6d9a..6dc6b1b5f 100644
--- a/data/deduplicator/deduplicator/data_set_delete_origin.go
+++ b/data/deduplicator/deduplicator/data_set_delete_origin.go
@@ -6,24 +6,26 @@ import (
 	"github.com/tidepool-org/platform/data"
 	dataStore "github.com/tidepool-org/platform/data/store"
 	dataTypesUpload "github.com/tidepool-org/platform/data/types/upload"
-	"github.com/tidepool-org/platform/errors"
 	"github.com/tidepool-org/platform/pointer"
 )
 
-const DataSetDeleteOriginName = "org.tidepool.deduplicator.dataset.delete.origin"
+const (
+	DataSetDeleteOriginName    = "org.tidepool.deduplicator.dataset.delete.origin"
+	DataSetDeleteOriginVersion = "1.0.0"
+)
 
 type DataSetDeleteOrigin struct {
-	*Base
+	*DataSetDeleteOriginBase
 }
 
 func NewDataSetDeleteOrigin() (*DataSetDeleteOrigin, error) {
-	base, err := NewBase(DataSetDeleteOriginName, "1.0.0")
+	dataSetDeleteOriginBase, err := NewDataSetDeleteOriginBase(DataSetDeleteOriginName, DataSetDeleteOriginVersion, &dataSetDeleteOriginProvider{})
 	if err != nil {
 		return nil, err
 	}
 
 	return &DataSetDeleteOrigin{
-		Base: base,
+		DataSetDeleteOriginBase: dataSetDeleteOriginBase,
 	}, nil
 }
 
@@ -32,106 +34,23 @@ func (d *DataSetDeleteOrigin) New(ctx context.Context, dataSet *dataTypesUpload.
 }
 
 func (d *DataSetDeleteOrigin) Get(ctx context.Context, dataSet *dataTypesUpload.Upload) (bool, error) {
-	if found, err := d.Base.Get(ctx, dataSet); err != nil || found {
+	if found, err := d.DataSetDeleteOriginBase.Get(ctx, dataSet); err != nil || found {
 		return found, err
 	}
 
 	return dataSet.HasDeduplicatorNameMatch("org.tidepool.continuous.origin"), nil // TODO: DEPRECATED
 }
 
-func (d *DataSetDeleteOrigin) Open(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload) (*dataTypesUpload.Upload, error) {
-	if ctx == nil {
-		return nil, errors.New("context is missing")
-	}
-	if repository == nil {
-		return nil, errors.New("repository is missing")
-	}
-	if dataSet == nil {
-		return nil, errors.New("data set is missing")
-	}
-
-	if dataSet.HasDataSetTypeContinuous() {
-		dataSet.SetActive(true)
-	}
-
-	return d.Base.Open(ctx, repository, dataSet)
-}
-
-func (d *DataSetDeleteOrigin) AddData(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload, dataSetData data.Data) error {
-	if ctx == nil {
-		return errors.New("context is missing")
-	}
-	if repository == nil {
-		return errors.New("repository is missing")
-	}
-	if dataSet == nil {
-		return errors.New("data set is missing")
-	}
-	if dataSetData == nil {
-		return errors.New("data set data is missing")
-	}
-
-	if dataSet.HasDataSetTypeContinuous() {
-		dataSetData.SetActive(true)
-	}
-
-	if selectors := d.getSelectors(dataSetData); selectors != nil {
-		if err := repository.DeleteDataSetData(ctx, dataSet, selectors); err != nil {
-			return err
-		}
-		if err := d.Base.AddData(ctx, repository, dataSet, dataSetData); err != nil {
-			return err
-		}
-		return repository.DestroyDeletedDataSetData(ctx, dataSet, selectors)
-	}
+type dataSetDeleteOriginProvider struct{}
 
-	return d.Base.AddData(ctx, repository, dataSet, dataSetData)
+func (d *dataSetDeleteOriginProvider) FilterData(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload, dataSetData data.Data) (data.Data, error) {
+	return dataSetData, nil
 }
 
-func (d *DataSetDeleteOrigin) DeleteData(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload, selectors *data.Selectors) error {
-	if ctx == nil {
-		return errors.New("context is missing")
-	}
-	if repository == nil {
-		return errors.New("repository is missing")
-	}
-	if dataSet == nil {
-		return errors.New("data set is missing")
-	}
-	if selectors == nil {
-		return errors.New("selectors is missing")
-	}
-
-	return repository.ArchiveDataSetData(ctx, dataSet, selectors)
-}
-
-func (d *DataSetDeleteOrigin) Close(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload) error {
-	if ctx == nil {
-		return errors.New("context is missing")
-	}
-	if repository == nil {
-		return errors.New("repository is missing")
-	}
-	if dataSet == nil {
-		return errors.New("data set is missing")
-	}
-
-	if dataSet.HasDataSetTypeContinuous() {
-		return nil
-	}
-
-	return d.Base.Close(ctx, repository, dataSet)
-}
-
-func (d *DataSetDeleteOrigin) getSelectors(dataSetData data.Data) *data.Selectors {
+func (d *dataSetDeleteOriginProvider) GetDataSelectors(dataSetData data.Data) *data.Selectors {
 	selectors := data.Selectors{}
 	for _, dataSetDatum := range dataSetData {
-		if origin := dataSetDatum.GetOrigin(); origin != nil && origin.ID != nil {
-			selector := &data.Selector{
-				Origin: &data.SelectorOrigin{
-					ID: pointer.CloneString(origin.ID),
-				},
-			}
+		if selector := d.getDatumSelector(dataSetDatum); selector != nil {
 			selectors = append(selectors, selector)
 		}
 	}
@@ -140,3 +59,14 @@ func (d *DataSetDeleteOrigin) getSelectors(dataSetData data.Data) *data.Selector
 	}
 	return &selectors
 }
+
+func (d *dataSetDeleteOriginProvider) getDatumSelector(dataSetDatum data.Datum) *data.Selector {
+	if origin := dataSetDatum.GetOrigin(); origin != nil && origin.ID != nil {
+		return &data.Selector{
+			Origin: &data.SelectorOrigin{
+				ID: pointer.CloneString(origin.ID),
+			},
+		}
+	}
+	return nil
+}
diff --git a/data/deduplicator/deduplicator/data_set_delete_origin_base.go b/data/deduplicator/deduplicator/data_set_delete_origin_base.go
new file mode 100644
index 000000000..978ae6400
--- /dev/null
+++ b/data/deduplicator/deduplicator/data_set_delete_origin_base.go
@@ -0,0 +1,121 @@
+package deduplicator
+
+import (
+	"context"
+
+	"github.com/tidepool-org/platform/data"
+	dataStore "github.com/tidepool-org/platform/data/store"
+	dataTypesUpload "github.com/tidepool-org/platform/data/types/upload"
+	"github.com/tidepool-org/platform/errors"
+)
+
+type DataSetDeleteOriginProvider interface {
+	FilterData(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload, dataSetData data.Data) (data.Data, error)
+	GetDataSelectors(datum data.Data) *data.Selectors
+}
+
+type DataSetDeleteOriginBase struct {
+	*Base
+	provider DataSetDeleteOriginProvider
+}
+
+func NewDataSetDeleteOriginBase(name string, version string, provider DataSetDeleteOriginProvider) (*DataSetDeleteOriginBase, error) {
+	base, err := NewBase(name, version)
+	if err != nil {
+		return nil, err
+	}
+
+	return &DataSetDeleteOriginBase{
+		Base:     base,
+		provider: provider,
+	}, nil
+}
+
+func (d *DataSetDeleteOriginBase) Open(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload) (*dataTypesUpload.Upload, error) {
+	if ctx == nil {
+		return nil, errors.New("context is missing")
+	}
+	if repository == nil {
+		return nil, errors.New("repository is missing")
+	}
+	if dataSet == nil {
+		return nil, errors.New("data set is missing")
+	}
+
+	if dataSet.HasDataSetTypeContinuous() {
+		dataSet.SetActive(true)
+	}
+
+	return d.Base.Open(ctx, repository, dataSet)
+}
+
+func (d *DataSetDeleteOriginBase) AddData(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload, dataSetData data.Data) error {
+	if ctx == nil {
+		return errors.New("context is missing")
+	}
+	if repository == nil {
+		return errors.New("repository is missing")
+	}
+	if dataSet == nil {
+		return errors.New("data set is missing")
+	}
+	if dataSetData == nil {
+		return errors.New("data set data is missing")
+	}
+
+	if dataSet.HasDataSetTypeContinuous() {
+		dataSetData.SetActive(true)
+	}
+
+	var err error
+	if dataSetData, err = d.provider.FilterData(ctx, repository, dataSet, dataSetData); err != nil {
+		return err
+	}
+
+	if selectors := d.provider.GetDataSelectors(dataSetData); selectors != nil {
+		if err := repository.DeleteDataSetData(ctx, dataSet, selectors); err != nil {
+			return err
+		}
+		if err := d.Base.AddData(ctx, repository, dataSet, dataSetData); err != nil {
+			return err
+		}
+		return repository.DestroyDeletedDataSetData(ctx, dataSet, selectors)
+	}
+
+	return d.Base.AddData(ctx, repository, dataSet, dataSetData)
+}
+
+func (d *DataSetDeleteOriginBase) DeleteData(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload, selectors *data.Selectors) error {
+	if ctx == nil {
+		return errors.New("context is missing")
+	}
+	if repository == nil {
+		return errors.New("repository is missing")
+	}
+	if dataSet == nil {
+		return errors.New("data set is missing")
+	}
+	if selectors == nil {
+		return errors.New("selectors is missing")
+	}
+
+	return repository.ArchiveDataSetData(ctx, dataSet, selectors)
+}
+
+func (d *DataSetDeleteOriginBase) Close(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload) error {
+	if ctx == nil {
+		return errors.New("context is missing")
+	}
+	if repository == nil {
+		return errors.New("repository is missing")
+	}
+	if dataSet == nil {
+		return errors.New("data set is missing")
+	}
+
+	if dataSet.HasDataSetTypeContinuous() {
+		return nil
+	}
+
+	return d.Base.Close(ctx, repository, dataSet)
+}
diff --git a/data/deduplicator/deduplicator/data_set_delete_origin_older.go b/data/deduplicator/deduplicator/data_set_delete_origin_older.go
new file mode 100644
index 000000000..d6887c2a1
--- /dev/null
+++ b/data/deduplicator/deduplicator/data_set_delete_origin_older.go
@@ -0,0 +1,94 @@
+package deduplicator
+
+import (
+	"context"
+	"slices"
+
+	"github.com/tidepool-org/platform/data"
+	dataStore "github.com/tidepool-org/platform/data/store"
+	dataTypesBolus "github.com/tidepool-org/platform/data/types/bolus"
+	dataTypesFood "github.com/tidepool-org/platform/data/types/food"
+	dataTypesUpload "github.com/tidepool-org/platform/data/types/upload"
+	"github.com/tidepool-org/platform/pointer"
+)
+
+const (
+	DataSetDeleteOriginOlderName    = "org.tidepool.deduplicator.dataset.delete.origin.older"
+	DataSetDeleteOriginOlderVersion = "1.0.0"
+)
+
+type DataSetDeleteOriginOlder struct {
+	*DataSetDeleteOriginBase
+}
+
+func NewDataSetDeleteOriginOlder() (*DataSetDeleteOriginOlder, error) {
+	dataSetDeleteOriginBase, err := NewDataSetDeleteOriginBase(DataSetDeleteOriginOlderName, DataSetDeleteOriginOlderVersion, &dataSetDeleteOriginOlderProvider{})
+	if err != nil {
+		return nil, err
+	}
+
+	return &DataSetDeleteOriginOlder{
+		DataSetDeleteOriginBase: dataSetDeleteOriginBase,
+	}, nil
+}
+
+type dataSetDeleteOriginOlderProvider struct{}
+
+func (d *dataSetDeleteOriginOlderProvider) FilterData(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload, dataSetData data.Data) (data.Data, error) {
+	filterableDataSetData := dataSetData.Filter(func(datum data.Datum) bool {
+		return slices.Contains(filterableDataSetDataTypes, datum.GetType())
+	})
+
+	if selectors := d.GetDataSelectors(filterableDataSetData); selectors != nil {
+		if existingSelectors, err := repository.NewerDataSetData(ctx, dataSet, selectors); err != nil {
+			return nil, err
+		} else if existingSelectors != nil && len(*existingSelectors) > 0 {
+			existingSelectorsMap := make(map[string]*data.Selector, len(*existingSelectors))
+			for _, existingSelector := range *existingSelectors {
+				if existingSelector != nil && existingSelector.Origin != nil && existingSelector.Origin.ID != nil {
+					existingSelectorsMap[*existingSelector.Origin.ID] = existingSelector
+				}
+			}
+			dataSetData = dataSetData.Filter(func(datum data.Datum) bool {
+				if datumSelector := d.getDatumSelector(datum); datumSelector != nil && datumSelector.Origin != nil && datumSelector.Origin.ID != nil {
+					if existingSelector, ok := existingSelectorsMap[*datumSelector.Origin.ID]; ok && existingSelector != nil {
+						return !datumSelector.Includes(existingSelector)
+					}
+				}
+				return true
+			})
+		}
+	}
+
+	return dataSetData, nil
+}
+
+func (d *dataSetDeleteOriginOlderProvider) GetDataSelectors(dataSetData data.Data) *data.Selectors {
+	selectors := data.Selectors{}
+	for _, dataSetDatum := range dataSetData {
+		if selector := d.getDatumSelector(dataSetDatum); selector != nil {
+			selectors = append(selectors, selector)
+		}
+	}
+	if len(selectors) == 0 {
+		return nil
+	}
+	return &selectors
+}
+
+func (d *dataSetDeleteOriginOlderProvider) getDatumSelector(dataSetDatum data.Datum) *data.Selector {
+	if origin := dataSetDatum.GetOrigin(); origin != nil && origin.ID != nil {
+		return &data.Selector{
+			Origin: &data.SelectorOrigin{
+				ID:   pointer.CloneString(origin.ID),
+				Time: origin.Time,
+			},
+		}
+	}
+	return nil
+}
+
+var filterableDataSetDataTypes = []string{
+	dataTypesBolus.Type,
+	dataTypesFood.Type,
+}
diff --git a/data/deduplicator/deduplicator/data_set_delete_origin_older_test.go b/data/deduplicator/deduplicator/data_set_delete_origin_older_test.go
new file mode 100644
index 000000000..eec7927dd
--- /dev/null
+++ b/data/deduplicator/deduplicator/data_set_delete_origin_older_test.go
@@ -0,0 +1,609 @@
+package deduplicator_test
+
+import (
+	"context"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/tidepool-org/platform/data"
+	dataDeduplicatorDeduplicator "github.com/tidepool-org/platform/data/deduplicator/deduplicator"
+	dataStoreTest "github.com/tidepool-org/platform/data/store/test"
+	dataTest "github.com/tidepool-org/platform/data/test"
+	dataTypes "github.com/tidepool-org/platform/data/types"
+	dataTypesTest "github.com/tidepool-org/platform/data/types/test"
+	dataTypesUpload "github.com/tidepool-org/platform/data/types/upload"
+	dataTypesUploadTest "github.com/tidepool-org/platform/data/types/upload/test"
+	errorsTest "github.com/tidepool-org/platform/errors/test"
+	netTest "github.com/tidepool-org/platform/net/test"
+	"github.com/tidepool-org/platform/pointer"
+	"github.com/tidepool-org/platform/test"
+)
+
+var _ = Describe("DataSetDeleteOriginOlder", func() {
+	It("DataSetDeleteOriginOlderName is expected", func() {
+		Expect(dataDeduplicatorDeduplicator.DataSetDeleteOriginOlderName).To(Equal("org.tidepool.deduplicator.dataset.delete.origin.older"))
+	})
+
+	It("DataSetDeleteOriginOlderVersion is expected", func() {
+		Expect(dataDeduplicatorDeduplicator.DataSetDeleteOriginOlderVersion).To(Equal("1.0.0"))
+	})
+
+	Context("NewDataSetDeleteOriginOlder", func() {
+		It("returns successfully", func() {
+			Expect(dataDeduplicatorDeduplicator.NewDataSetDeleteOriginOlder()).ToNot(BeNil())
+		})
+	})
+
+	Context("with new deduplicator", func() {
+		var deduplicator *dataDeduplicatorDeduplicator.DataSetDeleteOriginOlder
+		var dataSet *dataTypesUpload.Upload
+
+		BeforeEach(func() {
+			var err error
+			deduplicator, err = dataDeduplicatorDeduplicator.NewDataSetDeleteOriginOlder()
+			Expect(err).ToNot(HaveOccurred())
+			Expect(deduplicator).ToNot(BeNil())
+			dataSet = dataTypesUploadTest.RandomUpload()
+			dataSet.Deduplicator.Name = pointer.FromString("org.tidepool.deduplicator.dataset.delete.origin.older")
+		})
+
+		Context("New", func() {
+			It("returns an error when the data set is missing", func() {
+				found, err := deduplicator.New(context.Background(), nil)
+				Expect(err).To(MatchError("data set is missing"))
+				Expect(found).To(BeFalse())
+			})
+
+			It("returns false when the deduplicator is missing", func() {
+				dataSet.Deduplicator = nil
+				Expect(deduplicator.New(context.Background(), dataSet)).To(BeFalse())
+			})
+
+			It("returns false when the deduplicator name is missing", func() {
+				dataSet.Deduplicator.Name = nil
+				Expect(deduplicator.New(context.Background(), dataSet)).To(BeFalse())
+			})
+
+			It("returns false when the deduplicator name does not match", func() {
+				dataSet.Deduplicator.Name = pointer.FromString(netTest.RandomReverseDomain())
+				Expect(deduplicator.New(context.Background(), dataSet)).To(BeFalse())
+			})
+
+			It("returns true when the deduplicator name matches", func() {
+				Expect(deduplicator.New(context.Background(), dataSet)).To(BeTrue())
+			})
+		})
+
+		Context("Get", func() {
+			It("returns an error when the data set is missing", func() {
+				found, err := deduplicator.Get(context.Background(), nil)
+				Expect(err).To(MatchError("data set is missing"))
+				Expect(found).To(BeFalse())
+			})
+
+			It("returns false when the deduplicator is missing", func() {
+				dataSet.Deduplicator = nil
+				Expect(deduplicator.Get(context.Background(), dataSet)).To(BeFalse())
+			})
+
+			It("returns false when the deduplicator name is missing", func() {
+				dataSet.Deduplicator.Name = nil
+				Expect(deduplicator.Get(context.Background(), dataSet)).To(BeFalse())
+			})
+
+			It("returns false when the deduplicator name does not match", func() {
+				dataSet.Deduplicator.Name = pointer.FromString(netTest.RandomReverseDomain())
+				Expect(deduplicator.Get(context.Background(), dataSet)).To(BeFalse())
+			})
+
+			It("returns true when the deduplicator name matches", func() {
+				Expect(deduplicator.Get(context.Background(), dataSet)).To(BeTrue())
+			})
+		})
+
+		Context("with context and repository", func() {
+			var ctx context.Context
+			var repository *dataStoreTest.DataRepository
+
+			BeforeEach(func() {
+				ctx = context.Background()
+				repository = dataStoreTest.NewDataRepository()
+			})
+
+			AfterEach(func() {
+				repository.AssertOutputsEmpty()
+			})
+
+			Context("Open", func() {
+				It("returns an error when the context is missing", func() {
+					result, err := deduplicator.Open(nil, repository, dataSet)
+					Expect(err).To(MatchError("context is missing"))
+					Expect(result).To(BeNil())
+				})
+
+				It("returns an error when the repository is missing", func() {
+					result, err := deduplicator.Open(ctx, nil, dataSet)
+					Expect(err).To(MatchError("repository is missing"))
+					Expect(result).To(BeNil())
+				})
+
+				It("returns an error when the data set is missing", func() {
+					result, err := deduplicator.Open(ctx, repository, nil)
+					Expect(err).To(MatchError("data set is missing"))
+					Expect(result).To(BeNil())
+				})
+
+				When("UpdateDataSet is invoked", func() {
+					var update *data.DataSetUpdate
+
+					BeforeEach(func() {
+						update = data.NewDataSetUpdate()
+						update.Deduplicator = &data.DeduplicatorDescriptor{
+							Name:    pointer.FromString("org.tidepool.deduplicator.dataset.delete.origin.older"),
+							Version: pointer.FromString("1.0.0"),
+						}
+					})
+
+					AfterEach(func() {
+						Expect(repository.UpdateDataSetInputs).To(Equal([]dataStoreTest.UpdateDataSetInput{{Context: ctx, ID: *dataSet.UploadID, Update: update}}))
+					})
+
+					updateAssertions := func() {
+						When("the data set does not have a deduplicator", func() {
+							BeforeEach(func() {
+								dataSet.Deduplicator = nil
+							})
+
+							It("returns an error when update data set returns an error", func() {
+								responseErr := errorsTest.RandomError()
+								repository.UpdateDataSetOutputs = []dataStoreTest.UpdateDataSetOutput{{DataSet: nil, Error: responseErr}}
+								result, err := deduplicator.Open(ctx, repository, dataSet)
+								Expect(err).To(Equal(responseErr))
+								Expect(result).To(BeNil())
+							})
+
+							It("returns successfully when update data set returns successfully", func() {
+								responseDataSet := dataTypesUploadTest.RandomUpload()
+								repository.UpdateDataSetOutputs = []dataStoreTest.UpdateDataSetOutput{{DataSet: responseDataSet, Error: nil}}
+								Expect(deduplicator.Open(ctx, repository, dataSet)).To(Equal(responseDataSet))
+							})
+						})
+
+						When("the data set has a deduplicator with matching name and version does not exist", func() {
+							BeforeEach(func() {
+								dataSet.Deduplicator.Version = nil
+							})
+
+							It("returns an error when update data set returns an error", func() {
+								responseErr := errorsTest.RandomError()
+								repository.UpdateDataSetOutputs = []dataStoreTest.UpdateDataSetOutput{{DataSet: nil, Error: responseErr}}
+								result, err := deduplicator.Open(ctx, repository, dataSet)
+								Expect(err).To(Equal(responseErr))
+								Expect(result).To(BeNil())
+							})
+
+							It("returns successfully when update data set returns successfully", func() {
+								responseDataSet := dataTypesUploadTest.RandomUpload()
+								repository.UpdateDataSetOutputs = []dataStoreTest.UpdateDataSetOutput{{DataSet: responseDataSet, Error: nil}}
+								Expect(deduplicator.Open(ctx, repository, dataSet)).To(Equal(responseDataSet))
+							})
+						})
+
+						When("the data set has a deduplicator with matching name and version exists", func() {
+							BeforeEach(func() {
+								dataSet.Deduplicator.Version = pointer.FromString(netTest.RandomSemanticVersion())
+							})
+
+							It("returns an error when update data set returns an error", func() {
+								responseErr := errorsTest.RandomError()
+								repository.UpdateDataSetOutputs = []dataStoreTest.UpdateDataSetOutput{{DataSet: nil, Error: responseErr}}
+								result, err := deduplicator.Open(ctx, repository, dataSet)
+								Expect(err).To(Equal(responseErr))
+								Expect(result).To(BeNil())
+							})
+
+							It("returns successfully when update data set returns successfully", func() {
+								responseDataSet := dataTypesUploadTest.RandomUpload()
+								repository.UpdateDataSetOutputs = []dataStoreTest.UpdateDataSetOutput{{DataSet: responseDataSet, Error: nil}}
+								Expect(deduplicator.Open(ctx, repository, dataSet)).To(Equal(responseDataSet))
+							})
+						})
+					}
+
+					When("data set type is not specified", func() {
+						BeforeEach(func() {
+							dataSet.DataSetType = nil
+							update.Active = pointer.FromBool(false)
+						})
+
+						AfterEach(func() {
+							Expect(dataSet.Active).To(BeFalse())
+						})
+
+						updateAssertions()
+					})
+
+					When("data set type is continuous", func() {
+						BeforeEach(func() {
+							dataSet.DataSetType = pointer.FromString("continuous")
+							update.Active = pointer.FromBool(true)
+						})
+
+						AfterEach(func() {
+							Expect(dataSet.Active).To(BeTrue())
+						})
+
+						updateAssertions()
+					})
+
+					When("data set type is normal", func() {
+						BeforeEach(func() {
+							dataSet.DataSetType = pointer.FromString("normal")
+							update.Active = pointer.FromBool(false)
+						})
+
+						AfterEach(func() {
+							Expect(dataSet.Active).To(BeFalse())
+						})
+
+						updateAssertions()
+					})
+				})
+			})
+
+			Context("AddData", func() {
+				var unfilteredDataSetData data.Data
+				var filteredDataSetData data.Data
+				var allDataSetData data.Data
+				var unfilteredSelectors *data.Selectors
+				var filteredSelectors *data.Selectors
+
+				BeforeEach(func() {
+					unfilteredDataSetData = make(data.Data, test.RandomIntFromRange(1, 3))
+					unfilteredSelectors = data.NewSelectors()
+					for index := range unfilteredDataSetData {
+						base := dataTypesTest.RandomBase()
+						unfilteredDataSetData[index] = base
+						*unfilteredSelectors = append(*unfilteredSelectors, &data.Selector{Origin: &data.SelectorOrigin{ID: pointer.CloneString(base.Origin.ID), Time: base.Origin.Time}})
+					}
+					filteredDataSetData = make(data.Data, test.RandomIntFromRange(1, 3))
+					filteredSelectors = data.NewSelectors()
+					for index := range filteredDataSetData {
+						base := dataTypesTest.RandomBase()
+						base.Type = test.RandomStringFromArray([]string{"bolus", "food"})
+						filteredDataSetData[index] = base
+						*filteredSelectors = append(*filteredSelectors, &data.Selector{Origin: &data.SelectorOrigin{ID: pointer.CloneString(base.Origin.ID), Time: base.Origin.Time}})
+					}
+					allDataSetData = append(unfilteredDataSetData, filteredDataSetData...)
+				})
+
+				It("returns an error when the context is missing", func() {
+					Expect(deduplicator.AddData(nil, repository, dataSet, allDataSetData)).To(MatchError("context is missing"))
+				})
+
+				It("returns an error when the repository is missing", func() {
+					Expect(deduplicator.AddData(ctx, nil, dataSet, allDataSetData)).To(MatchError("repository is missing"))
+				})
+
+				It("returns an error when the data set is missing", func() {
+					Expect(deduplicator.AddData(ctx, repository, nil, allDataSetData)).To(MatchError("data set is missing"))
+				})
+
+				It("returns an error when the data set data is missing", func() {
+					Expect(deduplicator.AddData(ctx, repository, dataSet, nil)).To(MatchError("data set data is missing"))
+				})
+
+				dataSetTypeAssertions := func() {
+					originAssertions := func() {
+						When("create data set data is invoked", func() {
+							AfterEach(func() {
+								Expect(repository.CreateDataSetDataInputs).To(Equal([]dataStoreTest.CreateDataSetDataInput{{Context: ctx, DataSet: dataSet, DataSetData: allDataSetData}}))
+							})
+
+							It("returns an error when create data set data returns an error", func() {
+								responseErr := errorsTest.RandomError()
+								repository.CreateDataSetDataOutputs = []error{responseErr}
+								Expect(deduplicator.AddData(ctx, repository, dataSet, allDataSetData)).To(Equal(responseErr))
+							})
+
+							It("returns successfully when create data set data returns successfully", func() {
+								repository.CreateDataSetDataOutputs = []error{nil}
+								Expect(deduplicator.AddData(ctx, repository, dataSet, allDataSetData)).To(Succeed())
+							})
+						})
+					}
+
+					When("data set data does not have an origin", func() {
+						BeforeEach(func() {
+							for index := range allDataSetData {
+								base := dataTypesTest.RandomBase()
+								base.Origin = nil
+								allDataSetData[index] = base
+							}
+						})
+
+						originAssertions()
+					})
+
+					When("data set data does not have an origin id", func() {
+						BeforeEach(func() {
+							for index := range allDataSetData {
+								base := dataTypesTest.RandomBase()
+								base.Origin.ID = nil
+								allDataSetData[index] = base
+							}
+						})
+
+						originAssertions()
+					})
+
+					When("data set data has an origin id", func() {
+						When("newer data set data using origin ids is invoked", func() {
+							AfterEach(func() {
+								Expect(repository.NewerDataSetDataInputs).To(Equal([]dataStoreTest.NewerDataSetDataInput{{Context: ctx, DataSet: dataSet, Selectors: filteredSelectors}}))
+							})
+
+							It("returns an error when newer data set data using origin id returns an error", func() {
+								responseErr := errorsTest.RandomError()
+								repository.NewerDataSetDataOutputs = []dataStoreTest.NewerDataSetDataOutput{{Selectors: nil, Error: responseErr}}
+								Expect(deduplicator.AddData(ctx, repository, dataSet, allDataSetData)).To(Equal(responseErr))
+							})
+
+							When("delete data set data using origin ids is invoked", func() {
+								BeforeEach(func() {
+									repository.NewerDataSetDataOutputs = []dataStoreTest.NewerDataSetDataOutput{{Selectors: filteredSelectors, Error: nil}}
+								})
+
+								AfterEach(func() {
+									Expect(repository.DeleteDataSetDataInputs).To(Equal([]dataStoreTest.DeleteDataSetDataInput{{Context: ctx, DataSet: dataSet, Selectors: unfilteredSelectors}}))
+								})
+
+								It("returns an error when delete data set data using origin id returns an error", func() {
+									responseErr := errorsTest.RandomError()
+									repository.DeleteDataSetDataOutputs = []error{responseErr}
+									Expect(deduplicator.AddData(ctx, repository, dataSet, allDataSetData)).To(Equal(responseErr))
+								})
+
+								When("create data set data is invoked", func() {
+									BeforeEach(func() {
+										repository.DeleteDataSetDataOutputs = []error{nil}
+									})
+
+									AfterEach(func() {
+										Expect(repository.CreateDataSetDataInputs).To(Equal([]dataStoreTest.CreateDataSetDataInput{{Context: ctx, DataSet: dataSet, DataSetData: unfilteredDataSetData}}))
+									})
+
+									It("returns an error when create data set data returns an error", func() {
+										responseErr := errorsTest.RandomError()
+										repository.CreateDataSetDataOutputs = []error{responseErr}
+										Expect(deduplicator.AddData(ctx, repository, dataSet, allDataSetData)).To(Equal(responseErr))
+									})
+
+									When("destroy deleted data set data is invoked", func() {
+										BeforeEach(func() {
+											repository.CreateDataSetDataOutputs = []error{nil}
+										})
+
+										AfterEach(func() {
+											Expect(repository.DestroyDeletedDataSetDataInputs).To(Equal([]dataStoreTest.DestroyDeletedDataSetDataInput{{Context: ctx, DataSet: dataSet, Selectors: unfilteredSelectors}}))
+										})
+
+										It("returns an error when destroy deleted data set data returns an error", func() {
+											responseErr := errorsTest.RandomError()
+											repository.DestroyDeletedDataSetDataOutputs = []error{responseErr}
+											Expect(deduplicator.AddData(ctx, repository, dataSet, allDataSetData)).To(Equal(responseErr))
+										})
+
+										It("returns successfully when destroy deleted data set data returns successfully", func() {
+											repository.DestroyDeletedDataSetDataOutputs = []error{nil}
+											Expect(deduplicator.AddData(ctx, repository, dataSet, allDataSetData)).To(Succeed())
+										})
+									})
+								})
+							})
+						})
+					})
+				}
+
+				When("data set type is not specified", func() {
+					BeforeEach(func() {
+						dataSet.DataSetType = nil
+					})
+
+					AfterEach(func() {
+						for _, datum := range allDataSetData {
+							base, ok := datum.(*dataTypes.Base)
+							Expect(ok).To(BeTrue())
+							Expect(base).ToNot(BeNil())
+							Expect(base.Active).To(BeFalse())
+						}
+					})
+
+					dataSetTypeAssertions()
+				})
+
+				When("data set type is continuous", func() {
+					BeforeEach(func() {
+						dataSet.DataSetType = pointer.FromString("continuous")
+					})
+
+					AfterEach(func() {
+						for _, datum := range allDataSetData {
+							base, ok := datum.(*dataTypes.Base)
+							Expect(ok).To(BeTrue())
+							Expect(base).ToNot(BeNil())
+							Expect(base.Active).To(BeTrue())
+						}
+					})
+
+					dataSetTypeAssertions()
+				})
+
+				When("data set type is normal", func() {
+					BeforeEach(func() {
+						dataSet.DataSetType = pointer.FromString("normal")
+					})
+
+					AfterEach(func() {
+						for _, datum := range allDataSetData {
+							base, ok := datum.(*dataTypes.Base)
+							Expect(ok).To(BeTrue())
+							Expect(base).ToNot(BeNil())
+							Expect(base.Active).To(BeFalse())
+						}
+					})
+
+					dataSetTypeAssertions()
+				})
+			})
+
+			Context("DeleteData", func() {
+				var selectors *data.Selectors
+
+				BeforeEach(func() {
+					selectors = dataTest.RandomSelectors()
+				})
+
+				It("returns an error when the context is missing", func() {
+					Expect(deduplicator.DeleteData(nil, repository, dataSet, selectors)).To(MatchError("context is missing"))
+				})
+
+				It("returns an error when the repository is missing", func() {
+					Expect(deduplicator.DeleteData(ctx, nil, dataSet, selectors)).To(MatchError("repository is missing"))
+				})
+
+				It("returns an error when the data set is missing", func() {
+					Expect(deduplicator.DeleteData(ctx, repository, nil, selectors)).To(MatchError("data set is missing"))
+				})
+
+				It("returns an error when the selectors is missing", func() {
+					Expect(deduplicator.DeleteData(ctx, repository, dataSet, nil)).To(MatchError("selectors is missing"))
+				})
+
+				When("archive data set data is invoked", func() {
+					AfterEach(func() {
+						Expect(repository.ArchiveDataSetDataInputs).To(Equal([]dataStoreTest.ArchiveDataSetDataInput{{Context: ctx, DataSet: dataSet, Selectors: selectors}}))
+					})
+
+					It("returns an error when archive data set data returns an error", func() {
+						responseErr := errorsTest.RandomError()
+						repository.ArchiveDataSetDataOutputs = []error{responseErr}
+						Expect(deduplicator.DeleteData(ctx, repository, dataSet, selectors)).To(Equal(responseErr))
+					})
+
+					It("returns successfully when archive data set data returns successfully", func() {
+						repository.ArchiveDataSetDataOutputs = []error{nil}
+						Expect(deduplicator.DeleteData(ctx, repository, dataSet, selectors)).To(Succeed())
+					})
+				})
+			})
+
+			Context("Close", func() {
+				It("returns an error when the context is missing", func() {
+					Expect(deduplicator.Close(nil, repository, dataSet)).To(MatchError("context is missing"))
+				})
+
+				It("returns an error when the repository is missing", func() {
+					Expect(deduplicator.Close(ctx, nil, dataSet)).To(MatchError("repository is missing"))
+				})
+
+				It("returns an error when the data set is missing", func() {
+					Expect(deduplicator.Close(ctx, repository, nil)).To(MatchError("data set is missing"))
+				})
+
+				When("data set type is continuous", func() {
+					BeforeEach(func() {
+						dataSet.DataSetType = pointer.FromString("continuous")
+					})
+
+					It("returns successfully", func() {
+						Expect(deduplicator.Close(ctx, repository, dataSet)).To(Succeed())
+					})
+				})
+
+				When("UpdateDataSet is invoked", func() {
+					AfterEach(func() {
+						Expect(repository.UpdateDataSetInputs).To(Equal([]dataStoreTest.UpdateDataSetInput{{Context: ctx, ID: *dataSet.UploadID, Update: &data.DataSetUpdate{Active: pointer.FromBool(true)}}}))
+					})
+
+					updateAssertions := func() {
+						It("returns an error when update data set data returns an error", func() {
+							responseErr := errorsTest.RandomError()
+							repository.UpdateDataSetOutputs = []dataStoreTest.UpdateDataSetOutput{{DataSet: nil, Error: responseErr}}
+							Expect(deduplicator.Close(ctx, repository, dataSet)).To(Equal(responseErr))
+						})
+
+						When("activate data set data is invoked", func() {
+							BeforeEach(func() {
+								repository.UpdateDataSetOutputs = []dataStoreTest.UpdateDataSetOutput{{DataSet: dataSet, Error: nil}}
+							})
+
+							AfterEach(func() {
+								Expect(repository.ActivateDataSetDataInputs).To(Equal([]dataStoreTest.ActivateDataSetDataInput{{Context: ctx, DataSet: dataSet, Selectors: nil}}))
+							})
+
+							It("returns an error when active data set data returns an error", func() {
+								responseErr := errorsTest.RandomError()
+								repository.ActivateDataSetDataOutputs = []error{responseErr}
+								Expect(deduplicator.Close(ctx, repository, dataSet)).To(Equal(responseErr))
+							})
+
+							It("returns successfully when active data set data returns successfully", func() {
+								repository.ActivateDataSetDataOutputs = []error{nil}
+								Expect(deduplicator.Close(ctx, repository, dataSet)).To(Succeed())
+							})
+						})
+					}
+
+					When("data set type is not specified", func() {
+						BeforeEach(func() {
+							dataSet.DataSetType = nil
+						})
+
+						updateAssertions()
+					})
+
+					When("data set type is normal", func() {
+						BeforeEach(func() {
+							dataSet.DataSetType = pointer.FromString("normal")
+						})
+
+						updateAssertions()
+					})
+				})
+			})
+
+			Context("Delete", func() {
+				It("returns an error when the context is missing", func() {
+					Expect(deduplicator.Delete(nil, repository, dataSet)).To(MatchError("context is missing"))
+				})
+
+				It("returns an error when the repository is missing", func() {
+					Expect(deduplicator.Delete(ctx, nil, dataSet)).To(MatchError("repository is missing"))
+				})
+
+				It("returns an error when the data set is missing", func() {
+					Expect(deduplicator.Delete(ctx, repository, nil)).To(MatchError("data set is missing"))
+				})
+
+				When("delete data set is invoked", func() {
+					AfterEach(func() {
+						Expect(repository.DeleteDataSetInputs).To(Equal([]dataStoreTest.DeleteDataSetInput{{Context: ctx, DataSet: dataSet}}))
+					})
+
+					It("returns an error when delete data set returns an error", func() {
+						responseErr := errorsTest.RandomError()
+						repository.DeleteDataSetOutputs = []error{responseErr}
+						Expect(deduplicator.Delete(ctx, repository, dataSet)).To(Equal(responseErr))
+					})
+
+					It("returns successfully when delete data set returns successfully", func() {
+						repository.DeleteDataSetOutputs = []error{nil}
+						Expect(deduplicator.Delete(ctx, repository, dataSet)).To(Succeed())
+					})
+				})
+			})
+		})
+	})
+})
diff --git a/data/deduplicator/deduplicator/data_set_delete_origin_test.go b/data/deduplicator/deduplicator/data_set_delete_origin_test.go
index 669147963..06be79180 100644
--- a/data/deduplicator/deduplicator/data_set_delete_origin_test.go
+++ b/data/deduplicator/deduplicator/data_set_delete_origin_test.go
@@ -26,7 +26,7 @@ var _ = Describe("DataSetDeleteOrigin", func() {
 	})
 
 	Context("NewDataSetDeleteOrigin", func() {
-		It("returns succesfully", func() {
+		It("returns successfully", func() {
 			Expect(dataDeduplicatorDeduplicator.NewDataSetDeleteOrigin()).ToNot(BeNil())
 		})
 	})
diff --git a/data/service/service/standard.go b/data/service/service/standard.go
index 80911f4b2..976adeeef 100644
--- a/data/service/service/standard.go
+++ b/data/service/service/standard.go
@@ -221,6 +221,13 @@ func (s *Standard) initializeDataDeduplicatorFactory() error {
 		return errors.Wrap(err, "unable to create data set delete origin deduplicator")
 	}
 
+	s.Logger().Debug("Creating data set delete origin older deduplicator")
+
+	dataSetDeleteOriginOlderDeduplicator, err := dataDeduplicatorDeduplicator.NewDataSetDeleteOriginOlder()
+	if err != nil {
+		return errors.Wrap(err, "unable to create data set delete origin older deduplicator")
+	}
+
 	s.Logger().Debug("Creating none deduplicator")
 
 	noneDeduplicator, err := dataDeduplicatorDeduplicator.NewNone()
@@ -234,6 +241,7 @@ func (s *Standard) initializeDataDeduplicatorFactory() error {
 		deviceDeactivateHashDeduplicator,
 		deviceTruncateDataSetDeduplicator,
 		dataSetDeleteOriginDeduplicator,
+		dataSetDeleteOriginOlderDeduplicator,
 		noneDeduplicator,
 	}
 
diff --git a/data/store/mongo/mongo_datum.go b/data/store/mongo/mongo_datum.go
index 977c6f813..8f2ce004e 100644
--- a/data/store/mongo/mongo_datum.go
+++ b/data/store/mongo/mongo_datum.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"slices"
 	"time"
 
 	"go.mongodb.org/mongo-driver/bson"
@@ -196,6 +197,52 @@ func (d *DatumRepository) CreateDataSetData(ctx context.Context, dataSet *upload
 	return nil
 }
 
+func (d *DatumRepository) NewerDataSetData(ctx context.Context, dataSet *upload.Upload, selectors *data.Selectors) (*data.Selectors, error) {
+	if ctx == nil {
+		return nil, errors.New("context is missing")
+	}
+	if err := validateDataSet(dataSet); err != nil {
+		return nil, err
+	}
+	selector, _, err := validateAndTranslateSelectors(ctx, selectors)
+	if err != nil {
+		return nil, err
+	}
+
+	now := time.Now().UTC()
+	logger := log.LoggerFromContext(ctx).WithField("dataSetId", *dataSet.UploadID)
+
+	selector["_userId"] = dataSet.UserID
+	selector["uploadId"] = dataSet.UploadID
+	selector["_active"] = true
+	selector["deletedTime"] = bson.M{"$exists": false}
+
+	findOptions := options.Find()
+	findOptions.SetProjection(bson.M{"_id": 0, "id": 1, "time": 1, "origin.id": 1, "origin.time": 1})
+
+	cursor, err := d.Find(ctx, selector, findOptions)
+	if err != nil {
+		logger.WithError(err).Error("Unable to get newer data set data selectors")
+		return nil, fmt.Errorf("unable to get newer data set data selectors: %w", err)
+	}
+
+	newerSelectors := data.NewSelectors()
+	if err = cursor.All(ctx, newerSelectors); err != nil {
+		logger.WithError(err).Error("Unable to decode newer data set data selectors")
+		return nil, fmt.Errorf("unable to decode newer data set data selectors: %w", err)
+	}
+
+	// Post-process to exclude any not newer
+	newerSelectors = newerSelectors.Filter(func(newerSelector *data.Selector) bool {
+		return slices.ContainsFunc(*selectors, func(selector *data.Selector) bool {
+			return selector.Includes(newerSelector)
+		})
+	})
+
+	logger.WithFields(log.Fields{"newerSelectors": newerSelectors, "duration": time.Since(now) / time.Microsecond}).Debug("NewerDataSetData")
+	return newerSelectors, nil
+}
+
 func (d *DatumRepository) ActivateDataSetData(ctx context.Context, dataSet *upload.Upload, selectors *data.Selectors) error {
 	if ctx == nil {
 		return errors.New("context is missing")
@@ -537,6 +584,11 @@ func (d *DatumRepository) UnarchiveDeviceDataUsingHashesFromDataSet(ctx context.
 	return overallErr
 }
 
+// FUTURE: Currently does not translate time or origin.time fields. Since origin.time is currently persisted as a string
+// (and not time.Time) we cannot reliably query for it due to potentially variable timezone offsets. Eventually, migrate
+// origin.time to time.Time and add additional qualifiers to the database selector that document origin.time must be
+// greater than or equal to the incoming selector origin.time. For now, we can only query on id and origin.id.
+// See: https://tidepool.atlassian.net/browse/BACK-3548
 func validateAndTranslateSelectors(ctx context.Context, selectors *data.Selectors) (filter bson.M, hasOriginID bool, err error) {
 	if selectors == nil {
 		return bson.M{}, false, nil
diff --git a/data/store/store.go b/data/store/store.go
index 7410d76c8..11ce0a5d0 100644
--- a/data/store/store.go
+++ b/data/store/store.go
@@ -50,6 +50,7 @@ type DatumRepository interface {
 	EnsureIndexes() error
 
 	CreateDataSetData(ctx context.Context, dataSet *upload.Upload, dataSetData []data.Datum) error
+	NewerDataSetData(ctx context.Context, dataSet *upload.Upload, selectors *data.Selectors) (*data.Selectors, error)
 	ActivateDataSetData(ctx context.Context, dataSet *upload.Upload, selectors *data.Selectors) error
 	ArchiveDataSetData(ctx context.Context, dataSet *upload.Upload, selectors *data.Selectors) error
 	DeleteDataSetData(ctx context.Context, dataSet *upload.Upload, selectors *data.Selectors) error
diff --git a/data/store/test/data_repository.go b/data/store/test/data_repository.go
index ff5a857bc..356a73ffa 100644
--- a/data/store/test/data_repository.go
+++ b/data/store/test/data_repository.go
@@ -64,6 +64,17 @@ type CreateDataSetDataInput struct {
 	DataSetData []data.Datum
 }
 
+type NewerDataSetDataInput struct {
+	Context   context.Context
+	DataSet   *upload.Upload
+	Selectors *data.Selectors
+}
+
+type NewerDataSetDataOutput struct {
+	Selectors *data.Selectors
+	Error     error
+}
+
 type ActivateDataSetDataInput struct {
 	Context   context.Context
 	DataSet   *upload.Upload
@@ -210,6 +221,9 @@ type DataRepository struct {
 	CreateDataSetDataInvocations                         int
 	CreateDataSetDataInputs                              []CreateDataSetDataInput
 	CreateDataSetDataOutputs                             []error
+	NewerDataSetDataInvocations                          int
+	NewerDataSetDataInputs                               []NewerDataSetDataInput
+	NewerDataSetDataOutputs                              []NewerDataSetDataOutput
 	ActivateDataSetDataInvocations                       int
 	ActivateDataSetDataInputs                            []ActivateDataSetDataInput
 	ActivateDataSetDataOutputs                           []error
@@ -348,6 +362,18 @@ func (d *DataRepository) CreateDataSetData(ctx context.Context, dataSet *upload.
 	return output
 }
 
+func (d *DataRepository) NewerDataSetData(ctx context.Context, dataSet *upload.Upload, selectors *data.Selectors) (*data.Selectors, error) {
+	d.NewerDataSetDataInvocations++
+
+	d.NewerDataSetDataInputs = append(d.NewerDataSetDataInputs, NewerDataSetDataInput{Context: ctx, DataSet: dataSet, Selectors: selectors})
+
+	gomega.Expect(d.NewerDataSetDataOutputs).ToNot(gomega.BeEmpty())
+
+	output := d.NewerDataSetDataOutputs[0]
+	d.NewerDataSetDataOutputs = d.NewerDataSetDataOutputs[1:]
+	return output.Selectors, output.Error
+}
+
 func (d *DataRepository) ActivateDataSetData(ctx context.Context, dataSet *upload.Upload, selectors *data.Selectors) error {
 	d.ActivateDataSetDataInvocations++
 


Reviews:
user: login: toddkazakov
body: Overall looks good, left few comments inline.
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T09:22:36Z
user: login: jh-bate
body: feels like we are missing a few tests? 
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T19:19:42Z
user: login: jh-bate
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T22:30:12Z
user: login: darinkrauss
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T22:40:50Z
user: login: darinkrauss
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T22:41:20Z
user: login: darinkrauss
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T22:41:34Z
user: login: darinkrauss
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T22:42:07Z
user: login: darinkrauss
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T22:42:14Z
user: login: toddkazakov
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T06:58:21Z

Review comments:
diff_hunk: @@ -537,6 +584,10 @@ func (d *DatumRepository) UnarchiveDeviceDataUsingHashesFromDataSet(ctx context.
 	return overallErr
 }
 
+// TODO: Currently does not translate time or origin.time fields. Since origin.time is currently persisted as a string
path: data/store/mongo/mongo_datum.go
user: login: toddkazakov
body: This comment is useful, but please remove the TODO and open a JIRA ticket so we can track the work.
created_at: 2025-04-03T08:53:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+package deduplicator
+
+import (
+	"context"
+	"slices"
+
+	"github.com/tidepool-org/platform/data"
+	dataStore "github.com/tidepool-org/platform/data/store"
+	dataTypesBolus "github.com/tidepool-org/platform/data/types/bolus"
+	dataTypesFood "github.com/tidepool-org/platform/data/types/food"
+	dataTypesUpload "github.com/tidepool-org/platform/data/types/upload"
+	"github.com/tidepool-org/platform/pointer"
+)
+
+const (
+	DataSetDeleteOriginOlderName    = "org.tidepool.deduplicator.dataset.delete.origin.older"
+	DataSetDeleteOriginOlderVersion = "1.0.0"
+)
+
+type DataSetDeleteOriginOlder struct {
+	*DataSetDeleteOriginBase
+}
+
+func NewDataSetDeleteOriginOlder() (*DataSetDeleteOriginOlder, error) {
+	dataSetDeleteOriginBase, err := NewDataSetDeleteOriginBase(DataSetDeleteOriginOlderName, DataSetDeleteOriginOlderVersion, &dataSetDeleteOriginOlderProvider{})
+	if err != nil {
+		return nil, err
+	}
+
+	return &DataSetDeleteOriginOlder{
+		DataSetDeleteOriginBase: dataSetDeleteOriginBase,
+	}, nil
+}
+
+type dataSetDeleteOriginOlderProvider struct{}
+
+func (d *dataSetDeleteOriginOlderProvider) FilterData(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload, dataSetData data.Data) (data.Data, error) {
+	filterableDataSetData := dataSetData.Filter(func(datum data.Datum) bool {
+		return slices.Contains(filterableDataSetDataTypes, datum.GetType())
+	})
+
+	if selectors := d.GetDataSelectors(filterableDataSetData); selectors != nil {
path: data/deduplicator/deduplicator/data_set_delete_origin_older.go
user: login: toddkazakov
body: This block has quadratic complexity and might not work very well if we are processing large batches of data. Should we log a warning if the data exceeds a certain size?
created_at: 2025-04-03T09:18:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+package deduplicator
+
+import (
+	"context"
+	"slices"
+
+	"github.com/tidepool-org/platform/data"
+	dataStore "github.com/tidepool-org/platform/data/store"
+	dataTypesBolus "github.com/tidepool-org/platform/data/types/bolus"
+	dataTypesFood "github.com/tidepool-org/platform/data/types/food"
+	dataTypesUpload "github.com/tidepool-org/platform/data/types/upload"
+	"github.com/tidepool-org/platform/pointer"
+)
+
+const (
+	DataSetDeleteOriginOlderName    = "org.tidepool.deduplicator.dataset.delete.origin.older"
+	DataSetDeleteOriginOlderVersion = "1.0.0"
+)
+
+type DataSetDeleteOriginOlder struct {
+	*DataSetDeleteOriginBase
+}
+
+func NewDataSetDeleteOriginOlder() (*DataSetDeleteOriginOlder, error) {
+	dataSetDeleteOriginBase, err := NewDataSetDeleteOriginBase(DataSetDeleteOriginOlderName, DataSetDeleteOriginOlderVersion, &dataSetDeleteOriginOlderProvider{})
+	if err != nil {
+		return nil, err
+	}
+
+	return &DataSetDeleteOriginOlder{
+		DataSetDeleteOriginBase: dataSetDeleteOriginBase,
+	}, nil
+}
+
+type dataSetDeleteOriginOlderProvider struct{}
+
+func (d *dataSetDeleteOriginOlderProvider) FilterData(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload, dataSetData data.Data) (data.Data, error) {
+	filterableDataSetData := dataSetData.Filter(func(datum data.Datum) bool {
+		return slices.Contains(filterableDataSetDataTypes, datum.GetType())
+	})
+
+	if selectors := d.GetDataSelectors(filterableDataSetData); selectors != nil {
+		if existingSelectors, err := repository.NewerDataSetData(ctx, dataSet, selectors); err != nil {
+			return nil, err
+		} else if existingSelectors != nil && len(*existingSelectors) > 0 {
+			dataSetData = dataSetData.Filter(func(datum data.Datum) bool {
+				return !slices.ContainsFunc(*existingSelectors, d.getDatumSelector(datum).Includes)
path: data/deduplicator/deduplicator/data_set_delete_origin_older.go
user: login: toddkazakov
body: Should the selectors be added to a map keyed by the origin id to avoid the quadratic complexity or is this premature optimization?
created_at: 2025-04-03T09:19:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,15 +35,38 @@ func NewSelectorOrigin() *SelectorOrigin {
 
 func (s *SelectorOrigin) Parse(parser structure.ObjectParser) {
 	s.ID = parser.String("id")
+	s.Time = parser.String("time")
 }
 
 func (s *SelectorOrigin) Validate(validator structure.Validator) {
 	validator.String("id", s.ID).Exists().NotEmpty().LengthLessThanOrEqualTo(SelectorOriginIDLengthMaximum)
+	validator.String("time", s.Time).AsTime(time.RFC3339Nano).NotZero()
+}
+
+func (s *SelectorOrigin) Includes(other *SelectorOrigin) bool {
path: data/data.go
user: login: jh-bate
body: Feels worth a test with so many decisions in this? Or is it somehow covered elsewhere 
created_at: 2025-04-03T19:18:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -68,11 +93,27 @@ func (s *Selector) Validate(validator structure.Validator) {
 		validator.ReportError(structureValidator.ErrorValuesNotExistForOne("id", "origin"))
 	} else if s.ID != nil {
 		validator.String("id", s.ID).Using(IDValidator)
+		validator.Time("time", s.Time).NotZero()
 	} else {
+		validator.Time("time", s.Time).NotExists()
 		s.Origin.Validate(validator.WithReference("origin"))
 	}
 }
 
+func (s *Selector) Includes(other *Selector) bool {
path: data/data.go
user: login: jh-bate
body: Again feels test worthy unless its covered elsewhere?
created_at: 2025-04-03T19:19:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+package deduplicator
+
+import (
+	"context"
+	"slices"
+
+	"github.com/tidepool-org/platform/data"
+	dataStore "github.com/tidepool-org/platform/data/store"
+	dataTypesBolus "github.com/tidepool-org/platform/data/types/bolus"
+	dataTypesFood "github.com/tidepool-org/platform/data/types/food"
+	dataTypesUpload "github.com/tidepool-org/platform/data/types/upload"
+	"github.com/tidepool-org/platform/pointer"
+)
+
+const (
+	DataSetDeleteOriginOlderName    = "org.tidepool.deduplicator.dataset.delete.origin.older"
+	DataSetDeleteOriginOlderVersion = "1.0.0"
+)
+
+type DataSetDeleteOriginOlder struct {
+	*DataSetDeleteOriginBase
+}
+
+func NewDataSetDeleteOriginOlder() (*DataSetDeleteOriginOlder, error) {
+	dataSetDeleteOriginBase, err := NewDataSetDeleteOriginBase(DataSetDeleteOriginOlderName, DataSetDeleteOriginOlderVersion, &dataSetDeleteOriginOlderProvider{})
+	if err != nil {
+		return nil, err
+	}
+
+	return &DataSetDeleteOriginOlder{
+		DataSetDeleteOriginBase: dataSetDeleteOriginBase,
+	}, nil
+}
+
+type dataSetDeleteOriginOlderProvider struct{}
+
+func (d *dataSetDeleteOriginOlderProvider) FilterData(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload, dataSetData data.Data) (data.Data, error) {
+	filterableDataSetData := dataSetData.Filter(func(datum data.Datum) bool {
+		return slices.Contains(filterableDataSetDataTypes, datum.GetType())
+	})
+
+	if selectors := d.GetDataSelectors(filterableDataSetData); selectors != nil {
path: data/deduplicator/deduplicator/data_set_delete_origin_older.go
user: login: darinkrauss
body: Updated code to remove complexity.
created_at: 2025-04-03T22:40:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+package deduplicator
+
+import (
+	"context"
+	"slices"
+
+	"github.com/tidepool-org/platform/data"
+	dataStore "github.com/tidepool-org/platform/data/store"
+	dataTypesBolus "github.com/tidepool-org/platform/data/types/bolus"
+	dataTypesFood "github.com/tidepool-org/platform/data/types/food"
+	dataTypesUpload "github.com/tidepool-org/platform/data/types/upload"
+	"github.com/tidepool-org/platform/pointer"
+)
+
+const (
+	DataSetDeleteOriginOlderName    = "org.tidepool.deduplicator.dataset.delete.origin.older"
+	DataSetDeleteOriginOlderVersion = "1.0.0"
+)
+
+type DataSetDeleteOriginOlder struct {
+	*DataSetDeleteOriginBase
+}
+
+func NewDataSetDeleteOriginOlder() (*DataSetDeleteOriginOlder, error) {
+	dataSetDeleteOriginBase, err := NewDataSetDeleteOriginBase(DataSetDeleteOriginOlderName, DataSetDeleteOriginOlderVersion, &dataSetDeleteOriginOlderProvider{})
+	if err != nil {
+		return nil, err
+	}
+
+	return &DataSetDeleteOriginOlder{
+		DataSetDeleteOriginBase: dataSetDeleteOriginBase,
+	}, nil
+}
+
+type dataSetDeleteOriginOlderProvider struct{}
+
+func (d *dataSetDeleteOriginOlderProvider) FilterData(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload, dataSetData data.Data) (data.Data, error) {
+	filterableDataSetData := dataSetData.Filter(func(datum data.Datum) bool {
+		return slices.Contains(filterableDataSetDataTypes, datum.GetType())
+	})
+
+	if selectors := d.GetDataSelectors(filterableDataSetData); selectors != nil {
+		if existingSelectors, err := repository.NewerDataSetData(ctx, dataSet, selectors); err != nil {
+			return nil, err
+		} else if existingSelectors != nil && len(*existingSelectors) > 0 {
+			dataSetData = dataSetData.Filter(func(datum data.Datum) bool {
+				return !slices.ContainsFunc(*existingSelectors, d.getDatumSelector(datum).Includes)
path: data/deduplicator/deduplicator/data_set_delete_origin_older.go
user: login: darinkrauss
body: Updated code to remove complexity.
created_at: 2025-04-03T22:41:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -537,6 +584,10 @@ func (d *DatumRepository) UnarchiveDeviceDataUsingHashesFromDataSet(ctx context.
 	return overallErr
 }
 
+// TODO: Currently does not translate time or origin.time fields. Since origin.time is currently persisted as a string
path: data/store/mongo/mongo_datum.go
user: login: darinkrauss
body: Updated comment and added JIRA ticket.
created_at: 2025-04-03T22:41:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,15 +35,38 @@ func NewSelectorOrigin() *SelectorOrigin {
 
 func (s *SelectorOrigin) Parse(parser structure.ObjectParser) {
 	s.ID = parser.String("id")
+	s.Time = parser.String("time")
 }
 
 func (s *SelectorOrigin) Validate(validator structure.Validator) {
 	validator.String("id", s.ID).Exists().NotEmpty().LengthLessThanOrEqualTo(SelectorOriginIDLengthMaximum)
+	validator.String("time", s.Time).AsTime(time.RFC3339Nano).NotZero()
+}
+
+func (s *SelectorOrigin) Includes(other *SelectorOrigin) bool {
path: data/data.go
user: login: darinkrauss
body: Added tests.
created_at: 2025-04-03T22:42:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -68,11 +93,27 @@ func (s *Selector) Validate(validator structure.Validator) {
 		validator.ReportError(structureValidator.ErrorValuesNotExistForOne("id", "origin"))
 	} else if s.ID != nil {
 		validator.String("id", s.ID).Using(IDValidator)
+		validator.Time("time", s.Time).NotZero()
 	} else {
+		validator.Time("time", s.Time).NotExists()
 		s.Origin.Validate(validator.WithReference("origin"))
 	}
 }
 
+func (s *Selector) Includes(other *Selector) bool {
path: data/data.go
user: login: darinkrauss
body: Added tests.
created_at: 2025-04-03T22:42:14Z
author_association: CONTRIBUTOR

PR comments:
user: login: darinkrauss
created_at: 2025-04-02T20:41:34Z
body: /deploy int
author_association: CONTRIBUTOR
user: login: tidebot
created_at: 2025-04-02T20:41:37Z
body: darinkrauss updated values.yaml file in external
author_association: COLLABORATOR
user: login: tidebot
created_at: 2025-04-02T20:41:38Z
body: darinkrauss updated flux policies file in external
author_association: COLLABORATOR
user: login: tidebot
created_at: 2025-04-02T20:41:39Z
body: darinkrauss deployed platform BACK-3514-out-of-order-bolus-food branch to external namespace
author_association: COLLABORATOR