Repository: carrot-foundation/methodology-rules
Pull request #186: feature/uniqueness check rule
Author: login: AMarcosCastelo
Labels: name: feature
name: ⏳ Waiting for 1 reviewer
name: 30 min review
name: 🚨 13 Unresolved Thread(s)
Created: 2025-03-27T20:55:22Z | Merged: 2025-04-07T12:40:28Z

- **feat(rule): implement uniqueness check processor and related functionality**

### Summary

- Added a new `uniqueness-check` module with a processor to validate document uniqueness.
- Introduced helper functions for querying duplicate documents and handling API responses.
- Created comprehensive tests for the processor and its helpers, ensuring expected behavior across various scenarios.
- Updated the main index file to export the new module for integration.

### Related links

https://www.notion.so/carrot-fndn/Uniqueness-Check-1b49703d8e9c80c59a21c9efa3c12481?pvs=4

---

- [x] **I, the PR author, declare that this PR works as expected and does not break any service. I also declare that I gave my best to apply all of the best practices and that I'm leaving the code better than I found it.**


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Enhanced duplicate document detection with improved error messaging for missing or inconsistent document events.
  - Introduced a new document status "OPEN" for more granular tracking.
  - Added new functionality for handling document uniqueness checks and event extraction.
  - Integrated functionality from the `uniqueness-check` module into broader exports for easier access.

- **Tests**
  - Added extensive automated tests covering helper functions, Lambda integration, and processing logic to ensure enhanced reliability.
  - Introduced new test cases for various scenarios involving uniqueness checks and error conditions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/libs/methodologies/bold/rule-processors/mass-id/src/index.ts b/libs/methodologies/bold/rule-processors/mass-id/src/index.ts
index 65f3b3ae9..5ac81055c 100644
--- a/libs/methodologies/bold/rule-processors/mass-id/src/index.ts
+++ b/libs/methodologies/bold/rule-processors/mass-id/src/index.ts
@@ -17,3 +17,4 @@ export * from './drop-off-at-recycling-facility';
 export * from './mass-sorting';
 export * from './weighing';
 export * from './avoided-emissions';
+export * from './uniqueness-check';
diff --git a/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/index.ts b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/index.ts
new file mode 100644
index 000000000..02512cd79
--- /dev/null
+++ b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/index.ts
@@ -0,0 +1 @@
+export * from './uniqueness-check.lambda';
diff --git a/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts
new file mode 100644
index 000000000..5755fc9b2
--- /dev/null
+++ b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts
@@ -0,0 +1,22 @@
+import { BaseProcessorErrors } from '@carrot-fndn/shared/methodologies/bold/processors';
+import {
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+const { DROP_OFF, PICK_UP } = DocumentEventName;
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+
+export class UniquenessCheckProcessorErrors extends BaseProcessorErrors {
+  override readonly ERROR_MESSAGE = {
+    MASS_ID_DOCUMENT_NOT_FOUND: 'MassID document not found.',
+    MISSING_DROP_OFF_EVENT: `Expected "${DROP_OFF}" event.`,
+    MISSING_PICK_UP_EVENT: `Expected "${PICK_UP}" event.`,
+    MISSING_RECYCLER_EVENT: `Expected "${RECYCLER}" event.`,
+    MISSING_VEHICLE_LICENSE_PLATE: `Expected "${VEHICLE_LICENSE_PLATE}" attribute.`,
+    MISSING_WASTE_GENERATOR_EVENT: `Expected "${WASTE_GENERATOR}" event.`,
+    REJECTED_BY_ERROR: 'Unable to validate the MassID uniqueness.',
+  } as const;
+}
diff --git a/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.e2e.spec.ts b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.e2e.spec.ts
new file mode 100644
index 000000000..551cfa04b
--- /dev/null
+++ b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.e2e.spec.ts
@@ -0,0 +1,200 @@
+import { seedDocument } from '@carrot-fndn/shared/document/seeds';
+import { AuditApiService } from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  BoldStubsBuilder,
+  stubBoldMassIdDropOffEvent,
+  stubBoldMassIdPickUpEvent,
+  stubDocument,
+  stubDocumentEvent,
+  stubDocumentEventWithMetadataAttributes,
+} from '@carrot-fndn/shared/methodologies/bold/testing';
+import {
+  DocumentCategory,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import {
+  type LicensePlate,
+  type MethodologyDocument,
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+import { random } from 'typia';
+
+import {
+  type EventsData,
+  fetchSimilarMassIdDocuments,
+} from './uniqueness-check.helpers';
+
+const { ACTOR } = DocumentEventName;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: V1_VEHICLE_LICENSE_PLATE,
+} = DocumentEventAttributeName;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE: SOURCE_ACTOR_TYPE } =
+  DocumentEventActorType;
+
+describe('Uniqueness Check Helpers E2E', () => {
+  let auditApiService: AuditApiService;
+  let v2DocumentId: NonEmptyString;
+  let v2DocumentStub: MethodologyDocument;
+
+  const vehicleLicensePlate = random<LicensePlate>();
+  const eventsData: EventsData = {
+    dropOffEvent: stubBoldMassIdDropOffEvent(),
+    pickUpEvent: stubBoldMassIdPickUpEvent({
+      metadataAttributes: [
+        {
+          name: VEHICLE_LICENSE_PLATE,
+          value: vehicleLicensePlate,
+        },
+      ],
+    }),
+    recyclerEvent: stubDocumentEvent({
+      label: RECYCLER,
+      name: ACTOR,
+    }),
+    vehicleLicensePlate,
+    wasteGeneratorEvent: stubDocumentEvent({
+      label: WASTE_GENERATOR,
+      name: ACTOR,
+    }),
+  };
+
+  beforeAll(async () => {
+    auditApiService = new AuditApiService();
+
+    const { massIdDocument } = new BoldStubsBuilder()
+      .createMassIdDocument({
+        externalEventsMap: {
+          [`${ACTOR}-${RECYCLER}`]: eventsData.recyclerEvent,
+          [`${ACTOR}-${WASTE_GENERATOR}`]: eventsData.wasteGeneratorEvent,
+          [DROP_OFF]: eventsData.dropOffEvent,
+          [PICK_UP]: eventsData.pickUpEvent,
+        },
+      })
+      .build();
+
+    v2DocumentStub = massIdDocument;
+
+    v2DocumentId = await seedDocument({
+      partialDocument: v2DocumentStub,
+    });
+  });
+
+  describe('fetchSimilarMassIdDocuments', () => {
+    it('should return an empty array when no similar documents are found', async () => {
+      const { massIdDocument } = new BoldStubsBuilder()
+        .createMassIdDocument()
+        .build();
+
+      const result = await fetchSimilarMassIdDocuments({
+        auditApiService,
+        document: massIdDocument,
+        eventsData: {
+          dropOffEvent: stubBoldMassIdDropOffEvent(),
+          pickUpEvent: stubBoldMassIdPickUpEvent(),
+          recyclerEvent: stubDocumentEvent({
+            label: RECYCLER,
+            name: ACTOR,
+          }),
+          vehicleLicensePlate: random<LicensePlate>(),
+          wasteGeneratorEvent: stubDocumentEvent({
+            label: WASTE_GENERATOR,
+            name: ACTOR,
+          }),
+        },
+      });
+
+      expect(result).toEqual([]);
+    });
+
+    it('should return a list of similar documents when similar documents are found', async () => {
+      const duplicatedDocumentId = await seedDocument({
+        partialDocument: v2DocumentStub,
+      });
+
+      const result = await fetchSimilarMassIdDocuments({
+        auditApiService,
+        document: v2DocumentStub,
+        eventsData,
+      });
+
+      expect(result).toEqual(
+        expect.arrayContaining([
+          expect.objectContaining({
+            id: duplicatedDocumentId,
+            status: expect.any(String),
+          }),
+          expect.objectContaining({
+            id: v2DocumentId,
+            status: expect.any(String),
+          }),
+        ]),
+      );
+    });
+
+    it('should return a list of similar documents when a v1 document is similar to a v2 document', async () => {
+      const v1DocumentStub = stubDocument({
+        category: DocumentCategory.MASS,
+        currentValue: v2DocumentStub.currentValue,
+        dataSetName: v2DocumentStub.dataSetName,
+        externalEvents: [
+          stubDocumentEventWithMetadataAttributes(
+            {
+              ...eventsData.pickUpEvent,
+              name: OPEN,
+            },
+            [[V1_VEHICLE_LICENSE_PLATE, vehicleLicensePlate]],
+          ),
+          stubDocumentEventWithMetadataAttributes(
+            {
+              ...eventsData.dropOffEvent,
+              name: MOVE,
+            },
+            [[MOVE_TYPE, DROP_OFF]],
+          ),
+          stubDocumentEventWithMetadataAttributes(
+            {
+              ...eventsData.recyclerEvent,
+              name: ACTOR,
+            },
+            [[ACTOR_TYPE, RECYCLER_ACTOR_TYPE]],
+          ),
+          stubDocumentEventWithMetadataAttributes(
+            {
+              ...eventsData.wasteGeneratorEvent,
+              name: ACTOR,
+            },
+            [[ACTOR_TYPE, SOURCE_ACTOR_TYPE]],
+          ),
+        ],
+        subtype: v2DocumentStub.subtype,
+        type: v2DocumentStub.type,
+      });
+
+      const v1DocumentId = await seedDocument({
+        partialDocument: v1DocumentStub,
+      });
+
+      const result = await fetchSimilarMassIdDocuments({
+        auditApiService,
+        document: v2DocumentStub,
+        eventsData,
+      });
+
+      expect(result).toEqual(
+        expect.arrayContaining([
+          expect.objectContaining({ id: v1DocumentId }),
+          expect.objectContaining({ id: v2DocumentId }),
+        ]),
+      );
+    });
+  });
+});
diff --git a/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.spec.ts b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.spec.ts
new file mode 100644
index 000000000..85187653e
--- /dev/null
+++ b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.spec.ts
@@ -0,0 +1,211 @@
+import type { AuditApiService } from '@carrot-fndn/shared/methodologies/audit-api';
+
+import {
+  BoldStubsBuilder,
+  stubBoldMassIdDropOffEvent,
+  stubBoldMassIdPickUpEvent,
+  stubDocumentEvent,
+} from '@carrot-fndn/shared/methodologies/bold/testing';
+import { DocumentCategory } from '@carrot-fndn/shared/methodologies/bold/types';
+import { type NonEmptyString } from '@carrot-fndn/shared/types';
+
+import * as helpers from './uniqueness-check.helpers';
+
+const { MASS, MASS_ID } = DocumentCategory;
+
+const mockCheckDuplicateDocuments = jest.fn();
+const mockAuditApiService = {
+  checkDuplicateDocuments: mockCheckDuplicateDocuments,
+};
+
+// eslint-disable-next-line @typescript-eslint/no-unsafe-return
+jest.mock('./uniqueness-check.helpers', () => ({
+  ...jest.requireActual('./uniqueness-check.helpers'),
+  createAuditApiService: () => mockAuditApiService,
+}));
+
+describe('uniqueness-check.helpers', () => {
+  afterEach(() => {
+    jest.clearAllMocks();
+    mockCheckDuplicateDocuments.mockReset();
+  });
+
+  describe('mapMassIdV2Query', () => {
+    it('should create a valid query object with proper structure', () => {
+      const { massIdDocument } = new BoldStubsBuilder()
+        .createMassIdDocument()
+        .build();
+
+      const eventsData: helpers.EventsData = {
+        dropOffEvent: stubBoldMassIdDropOffEvent(),
+        pickUpEvent: stubBoldMassIdPickUpEvent(),
+        recyclerEvent: stubDocumentEvent(),
+        vehicleLicensePlate: 'ABC123',
+        wasteGeneratorEvent: stubDocumentEvent(),
+      };
+
+      const query = helpers.mapMassIdV2Query(massIdDocument, eventsData);
+
+      expect(query).toHaveProperty('match.$and');
+      expect(query.match.$and).toBeInstanceOf(Array);
+      expect(query.match.$and).toHaveLength(4);
+      expect(query.match).toHaveProperty('category', MASS_ID);
+      expect(query.match).toHaveProperty('type', massIdDocument.type);
+      expect(query.match).toHaveProperty('subtype', massIdDocument.subtype);
+    });
+  });
+
+  describe('createOldFormatQuery', () => {
+    it('should create a valid query object with proper structure', () => {
+      const { massIdDocument } = new BoldStubsBuilder()
+        .createMassIdDocument()
+        .build();
+
+      const eventsData: helpers.EventsData = {
+        dropOffEvent: stubBoldMassIdDropOffEvent(),
+        pickUpEvent: stubBoldMassIdPickUpEvent(),
+        recyclerEvent: stubDocumentEvent(),
+        vehicleLicensePlate: 'ABC123',
+        wasteGeneratorEvent: stubDocumentEvent(),
+      };
+
+      const query = helpers.mapMassIdV1Query(massIdDocument, eventsData);
+
+      expect(query).toHaveProperty('match.$and');
+      expect(query.match.category).toBe(MASS);
+      expect(query.match.$and).toBeInstanceOf(Array);
+      expect(query.match.$and).toHaveLength(4);
+    });
+  });
+
+  describe('fetchSimilarMassIdDocuments', () => {
+    it('should call API with both new and old format queries and combine results', async () => {
+      const { massIdDocument } = new BoldStubsBuilder()
+        .createMassIdDocument()
+        .build();
+
+      const newFormatDuplicates = [
+        { id: 'new1', metadata: {}, status: 'OPEN' },
+        { id: 'new2', metadata: {}, status: 'CANCELLED' },
+      ];
+      const oldFormatDuplicates = [
+        { id: 'old1', metadata: {}, status: 'OPEN' },
+      ];
+
+      mockCheckDuplicateDocuments
+        .mockResolvedValueOnce(newFormatDuplicates)
+        .mockResolvedValueOnce(oldFormatDuplicates);
+
+      const eventsData: helpers.EventsData = {
+        dropOffEvent: stubBoldMassIdDropOffEvent(),
+        pickUpEvent: stubBoldMassIdPickUpEvent(),
+        recyclerEvent: stubDocumentEvent(),
+        vehicleLicensePlate: 'ABC123',
+        wasteGeneratorEvent: stubDocumentEvent(),
+      };
+
+      const result = await helpers.fetchSimilarMassIdDocuments({
+        auditApiService: mockAuditApiService as unknown as AuditApiService,
+        document: massIdDocument,
+        eventsData,
+      });
+
+      expect(result).toEqual([...newFormatDuplicates, ...oldFormatDuplicates]);
+
+      expect(mockCheckDuplicateDocuments).toHaveBeenCalledTimes(2);
+
+      const { calls } = mockCheckDuplicateDocuments.mock;
+
+      expect(calls.length).toBeGreaterThanOrEqual(2);
+
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+      expect(calls[0][0].match.category).toBe(MASS_ID);
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+      expect(calls[1][0].match.category).toBe(MASS);
+    });
+  });
+
+  describe('createLicensePlateRegex', () => {
+    it('should create a regex that matches the exact license plate value', () => {
+      const licensePlate = 'ABC1234';
+      const regex = helpers.createLicensePlateRegex(
+        licensePlate as NonEmptyString,
+      );
+
+      const pattern = new RegExp(regex, 'i');
+
+      expect(pattern.test('ABC1234')).toBe(true);
+      expect(pattern.test('A-BC1234')).toBe(true);
+      expect(pattern.test('ABC-1234')).toBe(true);
+      expect(pattern.test('A B C 1 2 3 4')).toBe(true);
+      expect(pattern.test('ABC12')).toBe(false);
+      expect(pattern.test('ABCD1234')).toBe(false);
+    });
+
+    it('should remove whitespace from the license plate value', () => {
+      const licensePlate = ' A BC 123 4';
+      const regex = helpers.createLicensePlateRegex(
+        licensePlate as NonEmptyString,
+      );
+
+      const pattern = new RegExp(regex, 'i');
+
+      expect(pattern.test('ABC1234')).toBe(true);
+      expect(pattern.test('ABC-1234')).toBe(true);
+      expect(pattern.test('ABC 1234')).toBe(true);
+      expect(pattern.test('A-B-C-1-2-3-4')).toBe(true);
+      expect(pattern.test('A B C 1 2 3 4')).toBe(true);
+    });
+
+    it('should escape special regex characters in the license plate', () => {
+      const licensePlate = 'A.B*C(1)2?3';
+      const regex = helpers.createLicensePlateRegex(
+        licensePlate as NonEmptyString,
+      );
+
+      const pattern = new RegExp(regex, 'i');
+
+      expect(pattern.test('A.B*C(1)2?3')).toBe(true);
+      expect(pattern.test('A.B*C-(1)2-?3')).toBe(true);
+      expect(pattern.test('ABC123')).toBe(false);
+      expect(pattern.test('A.BDC(1)2?3')).toBe(false);
+      expect(pattern.test('A,B*C(1)2?3')).toBe(false);
+    });
+
+    it('should match license plates ignoring hyphens and spaces', () => {
+      const plateWithHyphen = 'ABC-123';
+      const regexWithHyphen = helpers.createLicensePlateRegex(
+        plateWithHyphen as NonEmptyString,
+      );
+
+      const patternWithHyphen = new RegExp(regexWithHyphen, 'i');
+
+      expect(patternWithHyphen.test('ABC123')).toBe(true);
+      expect(patternWithHyphen.test('A-BC-123')).toBe(true);
+      expect(patternWithHyphen.test('A B C 1 2 3')).toBe(true);
+
+      const plateNoHyphen = 'ABC123';
+      const regexNoHyphen = helpers.createLicensePlateRegex(
+        plateNoHyphen as NonEmptyString,
+      );
+
+      const patternNoHyphen = new RegExp(regexNoHyphen, 'i');
+
+      expect(patternNoHyphen.test('ABC-123')).toBe(true);
+      expect(patternNoHyphen.test('A-B-C-1-2-3')).toBe(true);
+    });
+
+    it('should be case-insensitive when using the i flag', () => {
+      const licensePlate = 'ABC-123';
+      const regex = helpers.createLicensePlateRegex(
+        licensePlate as NonEmptyString,
+      );
+
+      const pattern = new RegExp(regex, 'i');
+
+      expect(pattern.test('abc123')).toBe(true);
+      expect(pattern.test('AbC123')).toBe(true);
+      expect(pattern.test('ABC123')).toBe(true);
+    });
+  });
+});
diff --git a/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
new file mode 100644
index 000000000..f83d29fba
--- /dev/null
+++ b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
@@ -0,0 +1,231 @@
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: OLD_VEHICLE_LICENSE_PLATE,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE: SOURCE_ACTOR_TYPE } =
+  DocumentEventActorType;
+
+export type EventsData = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  vehicleLicensePlate: NonEmptyString;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+export const createLicensePlateRegex = (
+  licensePlate: NonEmptyString,
+): string => {
+  const normalizedValue = licensePlate
+    .replaceAll(/\s+/g, '')
+    .replaceAll('-', '');
+
+  let escapedPattern = '';
+
+  for (const char of normalizedValue) {
+    const escapedChar = char.replaceAll(/[$()*+./?[\\\]^{|}-]/g, '\\$&');
+
+    escapedPattern += `${escapedChar}[-\\s]*`;
+  }
+
+  return `^${escapedPattern.slice(0, -6)}$`;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const mapMassIdV2Query = (
+  document: Document,
+  requiredData: EventsData,
+) => {
+  const {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    vehicleLicensePlate,
+    wasteGeneratorEvent,
+  } = requiredData;
+
+  const licensePlateRegex = createLicensePlateRegex(vehicleLicensePlate);
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elemMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: VEHICLE_LICENSE_PLATE,
+                  value: { $options: 'i', $regex: licensePlateRegex },
+                },
+              },
+              name: PICK_UP,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elemMatch: {
+              'address.id': dropOffEvent.address.id,
+              externalCreatedAt: dropOffEvent.externalCreatedAt,
+              name: DROP_OFF,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elemMatch: {
+              label: WASTE_GENERATOR,
+              name: ACTOR,
+              'participant.id': wasteGeneratorEvent.participant.id,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elemMatch: {
+              label: RECYCLER,
+              name: ACTOR,
+              'participant.id': recyclerEvent.participant.id,
+            },
+          },
+        },
+      ],
+      category: document.category,
+      currentValue: document.currentValue,
+      dataSetName: document.dataSetName,
+      subtype: document.subtype,
+      type: document.type,
+    },
+  };
+};
+
+export const mapMassIdV1Query = (document: Document, events: EventsData) => {
+  const {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    vehicleLicensePlate,
+    wasteGeneratorEvent,
+  } = events;
+
+  const licensePlateRegex = createLicensePlateRegex(vehicleLicensePlate);
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elemMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: OLD_VEHICLE_LICENSE_PLATE,
+                  value: { $options: 'i', $regex: licensePlateRegex },
+                },
+              },
+              name: OPEN,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elemMatch: {
+              'address.id': dropOffEvent.address.id,
+              externalCreatedAt: dropOffEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: MOVE_TYPE,
+                  value: DROP_OFF_MOVE_TYPE,
+                },
+              },
+              name: MOVE,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elemMatch: {
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: ACTOR_TYPE,
+                  value: SOURCE_ACTOR_TYPE,
+                },
+              },
+              name: ACTOR,
+              'participant.id': wasteGeneratorEvent.participant.id,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elemMatch: {
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: ACTOR_TYPE,
+                  value: RECYCLER_ACTOR_TYPE,
+                },
+              },
+              name: ACTOR,
+              'participant.id': recyclerEvent.participant.id,
+            },
+          },
+        },
+      ],
+      category: DocumentCategory.MASS,
+      currentValue: document.currentValue,
+      dataSetName: document.dataSetName,
+      subtype: document.subtype,
+      type: document.type,
+    },
+  };
+};
+
+export const fetchSimilarMassIdDocuments = async ({
+  auditApiService,
+  document,
+  eventsData,
+}: {
+  auditApiService: AuditApiService;
+  document: Document;
+  eventsData: EventsData;
+}): Promise<ApiDocumentCheckDuplicatesResponse[]> => {
+  const v2FormatQuery = mapMassIdV2Query(document, eventsData);
+  const v1FormatQuery = mapMassIdV1Query(document, eventsData);
+
+  const [v2FormattedDuplicates, v1FormattedDuplicates] = await Promise.all([
+    auditApiService.checkDuplicateDocuments(v2FormatQuery),
+    auditApiService.checkDuplicateDocuments(v1FormatQuery),
+  ]);
+
+  return [...v2FormattedDuplicates, ...v1FormattedDuplicates];
+};
diff --git a/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.lambda.e2e.spec.ts b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.lambda.e2e.spec.ts
new file mode 100644
index 000000000..546b3a04a
--- /dev/null
+++ b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.lambda.e2e.spec.ts
@@ -0,0 +1,104 @@
+import { toDocumentKey } from '@carrot-fndn/shared/helpers';
+import { BoldStubsBuilder } from '@carrot-fndn/shared/methodologies/bold/testing';
+import { type RuleOutput } from '@carrot-fndn/shared/rule/types';
+import {
+  prepareEnvironmentTestE2E,
+  stubContext,
+  stubRuleInput,
+  stubRuleResponse,
+} from '@carrot-fndn/shared/testing';
+import { faker } from '@faker-js/faker';
+
+import { uniquenessCheckLambda } from './uniqueness-check.lambda';
+import {
+  uniquenessCheckErrorTestCases,
+  uniquenessCheckTestCases,
+} from './uniqueness-check.test-cases';
+
+const mockCheckDuplicateDocuments = jest.fn();
+const mockAuditApiService = {
+  checkDuplicateDocuments: mockCheckDuplicateDocuments,
+};
+
+// eslint-disable-next-line @typescript-eslint/no-unsafe-return
+jest.mock('./uniqueness-check.helpers', () => ({
+  ...jest.requireActual('./uniqueness-check.helpers'),
+  createAuditApiService: () => mockAuditApiService,
+}));
+
+describe('UniquenessCheckLambda E2E', () => {
+  const documentKeyPrefix = faker.string.uuid();
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  describe('uniquenessCheckTestCases', () => {
+    it.each(uniquenessCheckTestCases)(
+      'should return $resultStatus when $scenario',
+      async ({
+        newDuplicateDocuments,
+        oldDuplicateDocuments,
+        resultStatus,
+      }) => {
+        const { massIdAuditDocument, massIdDocument } = new BoldStubsBuilder()
+          .createMassIdDocument()
+          .createMassIdAuditDocument()
+          .build();
+
+        mockCheckDuplicateDocuments
+          .mockResolvedValueOnce(newDuplicateDocuments)
+          .mockResolvedValueOnce(oldDuplicateDocuments);
+
+        prepareEnvironmentTestE2E(
+          [massIdAuditDocument, massIdDocument].map((_document) => ({
+            document: _document,
+            documentKey: toDocumentKey({
+              documentId: _document.id,
+              documentKeyPrefix,
+            }),
+          })),
+        );
+
+        const response = (await uniquenessCheckLambda(
+          stubRuleInput({
+            documentKeyPrefix,
+            parentDocumentId: massIdDocument.id,
+          }),
+          stubContext(),
+          () => stubRuleResponse(),
+        )) as RuleOutput;
+
+        expect(response.resultStatus).toBe(resultStatus);
+      },
+    );
+  });
+
+  describe('uniquenessCheckErrorTestCases', () => {
+    it.each(uniquenessCheckErrorTestCases)(
+      'should return $resultStatus when $scenario',
+      async ({ massIdAuditDocument, massIdDocument, resultStatus }) => {
+        prepareEnvironmentTestE2E(
+          [massIdAuditDocument, massIdDocument].map((_document) => ({
+            document: _document,
+            documentKey: toDocumentKey({
+              documentId: _document?.id,
+              documentKeyPrefix,
+            }),
+          })),
+        );
+
+        const response = (await uniquenessCheckLambda(
+          stubRuleInput({
+            documentKeyPrefix,
+            parentDocumentId: massIdDocument?.id ?? faker.string.uuid(),
+          }),
+          stubContext(),
+          () => stubRuleResponse(),
+        )) as RuleOutput;
+
+        expect(response.resultStatus).toBe(resultStatus);
+      },
+    );
+  });
+});
diff --git a/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.lambda.ts b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.lambda.ts
new file mode 100644
index 000000000..316cd52ef
--- /dev/null
+++ b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.lambda.ts
@@ -0,0 +1,7 @@
+import { wrapRuleIntoLambdaHandler } from '@carrot-fndn/shared/lambda/wrapper';
+
+import { UniquenessCheckProcessor } from './uniqueness-check.processor';
+
+const instance = new UniquenessCheckProcessor();
+
+export const uniquenessCheckLambda = wrapRuleIntoLambdaHandler(instance);
diff --git a/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.spec.ts b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.spec.ts
new file mode 100644
index 000000000..132d59d71
--- /dev/null
+++ b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.spec.ts
@@ -0,0 +1,90 @@
+import { loadParentDocument } from '@carrot-fndn/shared/methodologies/bold/io-helpers';
+import { BoldStubsBuilder } from '@carrot-fndn/shared/methodologies/bold/testing';
+import {
+  type RuleInput,
+  type RuleOutput,
+} from '@carrot-fndn/shared/rule/types';
+import { random } from 'typia';
+
+import { UniquenessCheckProcessor } from './uniqueness-check.processor';
+import {
+  uniquenessCheckErrorTestCases,
+  uniquenessCheckTestCases,
+} from './uniqueness-check.test-cases';
+
+jest.mock('@carrot-fndn/shared/methodologies/bold/io-helpers');
+
+const mockCheckDuplicateDocuments = jest.fn();
+const mockAuditApiService = {
+  checkDuplicateDocuments: mockCheckDuplicateDocuments,
+};
+
+// eslint-disable-next-line @typescript-eslint/no-unsafe-return
+jest.mock('./uniqueness-check.helpers', () => ({
+  ...jest.requireActual('./uniqueness-check.helpers'),
+  createAuditApiService: () => mockAuditApiService,
+}));
+
+describe('UniquenessCheckProcessor Rule', () => {
+  const ruleDataProcessor = new UniquenessCheckProcessor();
+  const documentLoaderService = jest.mocked(loadParentDocument);
+
+  describe('UniquenessCheckProcessor', () => {
+    it.each(uniquenessCheckTestCases)(
+      `should return $resultStatus when $scenario`,
+      async ({
+        newDuplicateDocuments,
+        oldDuplicateDocuments,
+        resultComment,
+        resultStatus,
+      }) => {
+        const ruleInput = random<Required<RuleInput>>();
+
+        const { massIdDocument } = new BoldStubsBuilder()
+          .createMassIdDocument()
+          .build();
+
+        mockCheckDuplicateDocuments
+          .mockResolvedValueOnce(newDuplicateDocuments)
+          .mockResolvedValueOnce(oldDuplicateDocuments);
+
+        documentLoaderService.mockResolvedValueOnce(massIdDocument);
+
+        const ruleOutput = await ruleDataProcessor.process(ruleInput);
+
+        const expectedRuleOutput: RuleOutput = {
+          requestId: ruleInput.requestId,
+          responseToken: ruleInput.responseToken,
+          responseUrl: ruleInput.responseUrl,
+          resultComment,
+          resultStatus,
+        };
+
+        expect(ruleOutput).toEqual(expectedRuleOutput);
+      },
+    );
+  });
+
+  describe('UniquenessCheckProcessor Errors', () => {
+    it.each(uniquenessCheckErrorTestCases)(
+      `should return $resultStatus when $scenario`,
+      async ({ massIdDocument, resultComment, resultStatus }) => {
+        const ruleInput = random<Required<RuleInput>>();
+
+        documentLoaderService.mockResolvedValueOnce(massIdDocument);
+
+        const ruleOutput = await ruleDataProcessor.process(ruleInput);
+
+        const expectedRuleOutput: RuleOutput = {
+          requestId: ruleInput.requestId,
+          responseToken: ruleInput.responseToken,
+          responseUrl: ruleInput.responseUrl,
+          resultComment,
+          resultStatus,
+        };
+
+        expect(ruleOutput).toEqual(expectedRuleOutput);
+      },
+    );
+  });
+});
diff --git a/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts
new file mode 100644
index 000000000..da835d7d4
--- /dev/null
+++ b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts
@@ -0,0 +1,225 @@
+import type { EvaluateResultOutput } from '@carrot-fndn/shared/rule/standard-data-processor';
+
+import {
+  getOrUndefined,
+  isNil,
+  isNonEmptyString,
+} from '@carrot-fndn/shared/helpers';
+import { getEventAttributeValue } from '@carrot-fndn/shared/methodologies/bold/getters';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import { ParentDocumentRuleProcessor } from '@carrot-fndn/shared/methodologies/bold/processors';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventName,
+  DocumentStatus,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { mapToRuleOutput } from '@carrot-fndn/shared/rule/result';
+import {
+  type RuleInput,
+  type RuleOutput,
+  RuleOutputStatus,
+} from '@carrot-fndn/shared/rule/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+import { UniquenessCheckProcessorErrors } from './uniqueness-check.errors';
+import {
+  type EventsData,
+  createAuditApiService,
+  fetchSimilarMassIdDocuments,
+} from './uniqueness-check.helpers';
+
+const { ACTOR, DROP_OFF, PICK_UP } = DocumentEventName;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const { MASS_ID } = DocumentCategory;
+
+export const RESULT_COMMENTS = {
+  NO_DUPLICATES_FOUND: `No other ${MASS_ID}s with the same attributes were found.`,
+  ONLY_CANCELLED_DUPLICATES: (
+    totalDuplicates: number,
+    cancelledCount: number,
+  ) =>
+    `${totalDuplicates} similar ${MASS_ID}s were found, but all are cancelled (${cancelledCount}).`,
+  VALID_DUPLICATE_FOUND: (
+    totalDuplicates: number,
+    validDuplicatesCount: number,
+  ) =>
+    `${totalDuplicates} similar ${MASS_ID}s were found, of which ${validDuplicatesCount} are not cancelled.`,
+} as const;
+
+interface RuleSubject {
+  cancelledCount: number;
+  totalDuplicates: number;
+  validDuplicatesCount: number;
+}
+
+export class UniquenessCheckProcessor extends ParentDocumentRuleProcessor<RuleSubject> {
+  protected readonly errorProcessor = new UniquenessCheckProcessorErrors();
+
+  private collectRequiredEventsData(document: Document): EventsData {
+    const dropOffEvent = this.getEventOrThrow(
+      document,
+      { name: [DROP_OFF] },
+      'MISSING_DROP_OFF_EVENT',
+    );
+
+    const pickUpEvent = this.getEventOrThrow(
+      document,
+      { name: [PICK_UP] },
+      'MISSING_PICK_UP_EVENT',
+    );
+
+    const recyclerEvent = this.getEventOrThrow(
+      document,
+      { label: [RECYCLER], name: [ACTOR] },
+      'MISSING_RECYCLER_EVENT',
+    );
+
+    const wasteGeneratorEvent = this.getEventOrThrow(
+      document,
+      { label: [WASTE_GENERATOR], name: [ACTOR] },
+      'MISSING_WASTE_GENERATOR_EVENT',
+    );
+
+    const vehicleLicensePlate = this.getVehicleLicensePlate(pickUpEvent);
+
+    return {
+      dropOffEvent,
+      pickUpEvent,
+      recyclerEvent,
+      vehicleLicensePlate,
+      wasteGeneratorEvent,
+    };
+  }
+
+  private getEventOrThrow(
+    document: Document,
+    criteria: {
+      label?: MethodologyDocumentEventLabel[];
+      name: DocumentEventName[];
+    },
+    errorMessage: keyof UniquenessCheckProcessorErrors['ERROR_MESSAGE'],
+  ): DocumentEvent {
+    const { label, name } = criteria;
+
+    const nameFilter = eventNameIsAnyOf(name);
+    const labelFilter = label ? eventLabelIsAnyOf(label) : null;
+
+    const event = document.externalEvents?.find(
+      labelFilter ? and(nameFilter, labelFilter) : nameFilter,
+    );
+
+    if (isNil(event)) {
+      throw this.errorProcessor.getKnownError(
+        this.errorProcessor.ERROR_MESSAGE[errorMessage],
+      );
+    }
+
+    return event;
+  }
+
+  private getVehicleLicensePlate(pickUpEvent: DocumentEvent): NonEmptyString {
+    const vehicleLicensePlate = getEventAttributeValue(
+      pickUpEvent,
+      VEHICLE_LICENSE_PLATE,
+    );
+
+    if (isNonEmptyString(vehicleLicensePlate)) {
+      return vehicleLicensePlate;
+    }
+
+    throw this.errorProcessor.getKnownError(
+      this.errorProcessor.ERROR_MESSAGE.MISSING_VEHICLE_LICENSE_PLATE,
+    );
+  }
+
+  protected evaluateResult({
+    cancelledCount,
+    totalDuplicates,
+    validDuplicatesCount,
+  }: RuleSubject): EvaluateResultOutput {
+    if (validDuplicatesCount > 0) {
+      return {
+        resultComment: RESULT_COMMENTS.VALID_DUPLICATE_FOUND(
+          totalDuplicates,
+          validDuplicatesCount,
+        ),
+        resultStatus: RuleOutputStatus.REJECTED,
+      };
+    }
+
+    if (cancelledCount > 0) {
+      return {
+        resultComment: RESULT_COMMENTS.ONLY_CANCELLED_DUPLICATES(
+          totalDuplicates,
+          cancelledCount,
+        ),
+        resultStatus: RuleOutputStatus.APPROVED,
+      };
+    }
+
+    return {
+      resultComment: RESULT_COMMENTS.NO_DUPLICATES_FOUND,
+      resultStatus: RuleOutputStatus.APPROVED,
+    };
+  }
+
+  protected async getRuleSubject(document: Document): Promise<RuleSubject> {
+    const eventsData = this.collectRequiredEventsData(document);
+
+    const duplicateDocuments = await fetchSimilarMassIdDocuments({
+      auditApiService: createAuditApiService(),
+      document,
+      eventsData,
+    });
+
+    const cancelledCount = duplicateDocuments.filter(
+      (duplicateDocument) =>
+        duplicateDocument.status === DocumentStatus.CANCELLED,
+    ).length;
+
+    return {
+      cancelledCount,
+      totalDuplicates: duplicateDocuments.length,
+      validDuplicatesCount: duplicateDocuments.length - cancelledCount,
+    };
+  }
+
+  override async process(ruleInput: RuleInput): Promise<RuleOutput> {
+    try {
+      const document = await this.loadDocument(ruleInput);
+
+      if (isNil(document)) {
+        return mapToRuleOutput(ruleInput, RuleOutputStatus.REJECTED, {
+          resultComment: this.errorProcessor.getResultCommentFromError(
+            this.errorProcessor.getKnownError(
+              this.errorProcessor.ERROR_MESSAGE.MASS_ID_DOCUMENT_NOT_FOUND,
+            ),
+          ),
+        });
+      }
+
+      const ruleSubject = await this.getRuleSubject(document);
+
+      const { resultComment, resultStatus } = this.evaluateResult(ruleSubject);
+
+      return mapToRuleOutput(ruleInput, resultStatus, {
+        resultComment: getOrUndefined(resultComment),
+      });
+    } catch (error: unknown) {
+      return mapToRuleOutput(ruleInput, RuleOutputStatus.REJECTED, {
+        resultComment: this.errorProcessor.getResultCommentFromError(error),
+      });
+    }
+  }
+}
diff --git a/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts
new file mode 100644
index 000000000..b6541eabe
--- /dev/null
+++ b/libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts
@@ -0,0 +1,139 @@
+import {
+  BoldStubsBuilder,
+  stubBoldMassIdPickUpEvent,
+} from '@carrot-fndn/shared/methodologies/bold/testing';
+import {
+  DocumentEventName,
+  DocumentStatus,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { RuleOutputStatus } from '@carrot-fndn/shared/rule/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+import { UniquenessCheckProcessorErrors } from './uniqueness-check.errors';
+import { RESULT_COMMENTS } from './uniqueness-check.processor';
+
+const { CANCELLED, OPEN } = DocumentStatus;
+const { DROP_OFF, PICK_UP } = DocumentEventName;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+
+export const uniquenessCheckTestCases = [
+  {
+    newDuplicateDocuments: [],
+    oldDuplicateDocuments: [],
+    resultComment: RESULT_COMMENTS.NO_DUPLICATES_FOUND,
+    resultStatus: RuleOutputStatus.APPROVED,
+    scenario: 'the document is unique',
+  },
+  {
+    newDuplicateDocuments: [{ status: CANCELLED }, { status: CANCELLED }],
+    oldDuplicateDocuments: [],
+    resultComment: RESULT_COMMENTS.ONLY_CANCELLED_DUPLICATES(2, 2),
+    resultStatus: RuleOutputStatus.APPROVED,
+    scenario: 'only cancelled duplicates are found',
+  },
+  {
+    newDuplicateDocuments: [{ status: OPEN }, { status: CANCELLED }],
+    oldDuplicateDocuments: [],
+    resultComment: RESULT_COMMENTS.VALID_DUPLICATE_FOUND(2, 1),
+    resultStatus: RuleOutputStatus.REJECTED,
+    scenario: 'valid duplicates are found',
+  },
+  {
+    newDuplicateDocuments: [],
+    oldDuplicateDocuments: [{ status: OPEN }, { status: OPEN }],
+    resultComment: RESULT_COMMENTS.VALID_DUPLICATE_FOUND(2, 2),
+    resultStatus: RuleOutputStatus.REJECTED,
+    scenario: 'valid duplicates are found in old format',
+  },
+  {
+    newDuplicateDocuments: [{ status: OPEN }],
+    oldDuplicateDocuments: [{ status: CANCELLED }],
+    resultComment: RESULT_COMMENTS.VALID_DUPLICATE_FOUND(2, 1),
+    resultStatus: RuleOutputStatus.REJECTED,
+    scenario: 'valid duplicates are found in both formats',
+  },
+];
+
+const processorErrors = new UniquenessCheckProcessorErrors();
+
+const { massIdAuditDocument, massIdDocument } = new BoldStubsBuilder()
+  .createMassIdDocument()
+  .createMassIdAuditDocument()
+  .build();
+
+export const uniquenessCheckErrorTestCases = [
+  {
+    massIdAuditDocument,
+    massIdDocument: undefined,
+    resultComment: processorErrors.ERROR_MESSAGE.MASS_ID_DOCUMENT_NOT_FOUND,
+    resultStatus: RuleOutputStatus.REJECTED,
+    scenario: 'when the MassID document is missing',
+  },
+  {
+    massIdAuditDocument,
+    massIdDocument: {
+      ...massIdDocument,
+      externalEvents: massIdDocument.externalEvents?.filter(
+        (event) => event.name !== DROP_OFF.toString(),
+      ),
+    },
+    resultComment: processorErrors.ERROR_MESSAGE.MISSING_DROP_OFF_EVENT,
+    resultStatus: RuleOutputStatus.REJECTED,
+    scenario: `when the "${DROP_OFF}" event is missing`,
+  },
+  {
+    massIdAuditDocument,
+    massIdDocument: {
+      ...massIdDocument,
+      externalEvents: massIdDocument.externalEvents?.filter(
+        (event) => event.name !== PICK_UP.toString(),
+      ),
+    },
+    resultComment: processorErrors.ERROR_MESSAGE.MISSING_PICK_UP_EVENT,
+    resultStatus: RuleOutputStatus.REJECTED,
+    scenario: `when the "${PICK_UP}" event is missing`,
+  },
+  {
+    massIdAuditDocument,
+    massIdDocument: {
+      ...massIdDocument,
+      externalEvents: massIdDocument.externalEvents?.filter(
+        (event) => event.label !== WASTE_GENERATOR.toString(),
+      ),
+    },
+    resultComment: processorErrors.ERROR_MESSAGE.MISSING_WASTE_GENERATOR_EVENT,
+    resultStatus: RuleOutputStatus.REJECTED,
+    scenario: `when the "${WASTE_GENERATOR}" event is missing`,
+  },
+  {
+    massIdAuditDocument,
+    massIdDocument: {
+      ...massIdDocument,
+      externalEvents: massIdDocument.externalEvents?.filter(
+        (event) => event.label !== RECYCLER.toString(),
+      ),
+    },
+    resultComment: processorErrors.ERROR_MESSAGE.MISSING_RECYCLER_EVENT,
+    resultStatus: RuleOutputStatus.REJECTED,
+    scenario: `when the "${RECYCLER}" event is missing`,
+  },
+  {
+    massIdAuditDocument,
+    massIdDocument: {
+      ...massIdDocument,
+      externalEvents: [
+        ...(massIdDocument.externalEvents ?? []).filter(
+          (event) => event.name !== PICK_UP.toString(),
+        ),
+        stubBoldMassIdPickUpEvent({
+          metadataAttributes: [[VEHICLE_LICENSE_PLATE, undefined]],
+        }),
+      ],
+    },
+    resultComment: processorErrors.ERROR_MESSAGE.MISSING_VEHICLE_LICENSE_PLATE,
+    resultStatus: RuleOutputStatus.REJECTED,
+    scenario: `when the "${VEHICLE_LICENSE_PLATE}" attribute is missing`,
+  },
+];
diff --git a/libs/shared/document/seeds/src/document.seeds.ts b/libs/shared/document/seeds/src/document.seeds.ts
index 6ac77bc15..06818fb33 100644
--- a/libs/shared/document/seeds/src/document.seeds.ts
+++ b/libs/shared/document/seeds/src/document.seeds.ts
@@ -1,53 +1,66 @@
-import type {
-  AuditApiDocumentPrimitiveEntity,
-  MethodologyDocument,
-  NonEmptyString,
-  Uri,
-} from '@carrot-fndn/shared/types';
 import type { Logger } from 'pino';
-import type { PartialDeep } from 'type-fest';
 
 import {
   isNonEmptyObject,
   logger as pinoLogger,
 } from '@carrot-fndn/shared/helpers';
 import { httpRequest } from '@carrot-fndn/shared/http-request';
+import {
+  type MethodologyDocument,
+  type NonEmptyString,
+  type Uri,
+} from '@carrot-fndn/shared/types';
 import { faker } from '@faker-js/faker';
 import { assert, random } from 'typia';
 
+import type {
+  ApiDocumentCreateDto,
+  AuditApiDocumentPartSnapshotEntity,
+  AuditApiDocumentPrimitiveEntity,
+} from './document.seeds.types';
+
+const mapDocumentParts = (
+  document: Partial<MethodologyDocument>,
+): AuditApiDocumentPartSnapshotEntity[] =>
+  document.externalEvents?.map((event) => ({
+    part: event,
+    partId: event.id,
+    path: 'externalEvents',
+  })) ?? [];
+
 export const seedDocument = async ({
-  endpoint,
   logger = pinoLogger,
   partialDocument = {},
 }: {
-  endpoint: Uri;
   logger?: Logger;
-  partialDocument?: PartialDeep<MethodologyDocument> | undefined;
-}): Promise<NonEmptyString> => {
-  const documentId = partialDocument.id ?? faker.string.uuid();
+  partialDocument?: Partial<MethodologyDocument>;
+} = {}): Promise<NonEmptyString> => {
+  const documentId = faker.string.uuid();
+  const endpoint = `${assert<Uri>(process.env['AUDIT_URL'])}/documents`;
+
+  const data: ApiDocumentCreateDto = {
+    ...random<ApiDocumentCreateDto>(),
+    document: {
+      ...random<MethodologyDocument>(),
+      ...partialDocument,
+      createdAt: new Date().toISOString(),
+      externalCreatedAt: new Date().toISOString(),
+      id: documentId,
+      status: 'OPEN',
+      tags: {
+        'e2e-test': 'true',
+        'test-source': 'methodology-rules',
+      },
+    },
+    documentId,
+    parts: mapDocumentParts(partialDocument),
+    versionDate: new Date().toISOString(),
+  };
 
   const response = await httpRequest(
     {
       baseURL: endpoint,
-      data: {
-        ...random<AuditApiDocumentPrimitiveEntity>(),
-        createdAt: new Date().toISOString(),
-        document: {
-          ...random<MethodologyDocument>(),
-          ...partialDocument,
-          createdAt: new Date().toISOString(),
-          dataSetName: 'TEST',
-          externalCreatedAt: new Date().toISOString(),
-          id: documentId,
-          status: 'OPEN',
-          tags: {
-            'e2e-test': 'true',
-            'test-source': 'methodology-rules',
-          },
-        },
-        documentId,
-        versionDate: new Date().toISOString(),
-      },
+      data,
       method: 'POST',
     },
     { logger },
diff --git a/libs/shared/document/seeds/src/document.seeds.types.ts b/libs/shared/document/seeds/src/document.seeds.types.ts
new file mode 100644
index 000000000..0768707d6
--- /dev/null
+++ b/libs/shared/document/seeds/src/document.seeds.types.ts
@@ -0,0 +1,31 @@
+import type {
+  AnyObject,
+  MethodologyDocument,
+  NonEmptyString,
+} from '@carrot-fndn/shared/types';
+import type { Primitive, tags } from 'typia';
+
+export interface AuditApiDocumentPrimitiveEntity {
+  createdAt: string & tags.Format<'date-time'>;
+  document: Primitive<MethodologyDocument>;
+  documentId: string;
+  snapshotId: string;
+  versionDate: string & tags.Format<'date-time'>;
+}
+
+export interface AuditApiDocumentPartSnapshotEntity<
+  T extends AnyObject = AnyObject,
+> {
+  part: T;
+  partId: NonEmptyString;
+  path: NonEmptyString;
+}
+
+export interface ApiDocumentCreateDto
+  extends Omit<
+    AuditApiDocumentPrimitiveEntity,
+    'createdAt' | 'id' | 'versionDate'
+  > {
+  parts?: AuditApiDocumentPartSnapshotEntity[] | undefined;
+  versionDate: string & tags.Format<'date-time'>;
+}
diff --git a/libs/shared/methodologies/audit-api/src/audit-api.service.e2e.spec.ts b/libs/shared/methodologies/audit-api/src/audit-api.service.e2e.spec.ts
index 540fcccb5..8794b566f 100644
--- a/libs/shared/methodologies/audit-api/src/audit-api.service.e2e.spec.ts
+++ b/libs/shared/methodologies/audit-api/src/audit-api.service.e2e.spec.ts
@@ -1,11 +1,8 @@
 import { seedDocument } from '@carrot-fndn/shared/document/seeds';
 import { faker } from '@faker-js/faker';
 
-import { AUDIT_API_URL } from './audit-api.constants';
 import { AuditApiService } from './audit-api.service';
 
-const endpoint = `${AUDIT_API_URL}/documents`;
-
 describe('Audit API Service', () => {
   let auditApiService: AuditApiService;
 
@@ -37,11 +34,9 @@ describe('Audit API Service', () => {
 
       const [documentId1, documentId2] = await Promise.all([
         seedDocument({
-          endpoint,
           partialDocument: duplicatedDto,
         }),
         seedDocument({
-          endpoint,
           partialDocument: duplicatedDto,
         }),
       ]);
diff --git a/libs/shared/methodologies/bold/testing/src/utils/e2e.helpers.ts b/libs/shared/methodologies/bold/testing/src/utils/e2e.helpers.ts
index cf9116c44..e455f7664 100644
--- a/libs/shared/methodologies/bold/testing/src/utils/e2e.helpers.ts
+++ b/libs/shared/methodologies/bold/testing/src/utils/e2e.helpers.ts
@@ -17,12 +17,6 @@ import { faker } from '@faker-js/faker';
 
 import { approvedMassDocument, rejectedMassDocument } from '../documents';
 
-process.env = {
-  ...process.env,
-  AWS_ACCESS_KEY_ID: faker.string.uuid(),
-  AWS_SECRET_ACCESS_KEY: faker.string.uuid(),
-};
-
 export const testRuleProcessorWithMassDocuments = (
   options: {
     handler: Handler<RuleInput, MethodologyRuleResponse>;
@@ -38,6 +32,12 @@ export const testRuleProcessorWithMassDocuments = (
 ) => {
   const { handler, ruleName, skipRejectTest = false } = options;
 
+  process.env = {
+    ...process.env,
+    AWS_ACCESS_KEY_ID: faker.string.uuid(),
+    AWS_SECRET_ACCESS_KEY: faker.string.uuid(),
+  };
+
   describe(`E2E - ${ruleName} with pre-defined documents`, () => {
     const approvedMassDocumentScenario =
       'return expected resultStatus for the approvedMassDocument';
diff --git a/libs/shared/methodologies/bold/types/src/enum.types.ts b/libs/shared/methodologies/bold/types/src/enum.types.ts
index fd50a5105..118c46600 100644
--- a/libs/shared/methodologies/bold/types/src/enum.types.ts
+++ b/libs/shared/methodologies/bold/types/src/enum.types.ts
@@ -8,6 +8,7 @@ export enum DocumentCategory {
 
 export enum DocumentStatus {
   CANCELLED = 'CANCELLED',
+  OPEN = 'OPEN',
 }
 
 export enum DocumentType {
diff --git a/libs/shared/rule/standard-data-processor/src/rule-standard-data-processor.ts b/libs/shared/rule/standard-data-processor/src/rule-standard-data-processor.ts
index 793e56ed8..5c325e240 100644
--- a/libs/shared/rule/standard-data-processor/src/rule-standard-data-processor.ts
+++ b/libs/shared/rule/standard-data-processor/src/rule-standard-data-processor.ts
@@ -38,7 +38,7 @@ export abstract class RuleStandardDataProcessor<
       });
     }
 
-    const ruleSubject = this.getRuleSubject(document);
+    const ruleSubject = await this.getRuleSubject(document);
 
     if (isNil(ruleSubject)) {
       return mapToRuleOutput(ruleInput, RuleOutputStatus.APPROVED, {
@@ -65,7 +65,7 @@ export abstract class RuleStandardDataProcessor<
 
   protected abstract getRuleSubject(
     document: InputDocument,
-  ): RuleSubject | undefined;
+  ): Promise<RuleSubject | undefined> | RuleSubject | undefined;
 
   protected abstract loadDocument(
     ruleInput: RuleInput,
diff --git a/libs/shared/testing/src/helpers/e2e.helpers.ts b/libs/shared/testing/src/helpers/e2e.helpers.ts
index 75d9410be..f8285e875 100644
--- a/libs/shared/testing/src/helpers/e2e.helpers.ts
+++ b/libs/shared/testing/src/helpers/e2e.helpers.ts
@@ -12,7 +12,9 @@ export const prepareEnvironmentTestE2E = <T = UnknownObject>(
 ) => {
   process.env = {
     ...process.env,
+    AWS_ACCESS_KEY_ID: faker.string.uuid(),
     AWS_REGION: faker.string.uuid(),
+    AWS_SECRET_ACCESS_KEY: faker.string.uuid(),
     SMAUG_API_GATEWAY_ASSUME_ROLE_ARN: faker.string.uuid(),
   };
 
diff --git a/libs/shared/types/src/methodology/methodology-document.types.ts b/libs/shared/types/src/methodology/methodology-document.types.ts
index 020f25ca0..fbc4ce82b 100644
--- a/libs/shared/types/src/methodology/methodology-document.types.ts
+++ b/libs/shared/types/src/methodology/methodology-document.types.ts
@@ -1,4 +1,4 @@
-import { type Primitive, tags } from 'typia';
+import { tags } from 'typia';
 
 import type { UnknownObject } from '../common.types';
 import type { MethodologyAddress } from './methodology-address.types';
@@ -34,14 +34,7 @@ export interface MethodologyDocument {
   primaryParticipant: MethodologyParticipant;
   status: string;
   subtype?: string | undefined;
+  tags?: Record<string, null | string | undefined> | undefined;
   type?: string | undefined;
   updatedAt: string & tags.Format<'date-time'>;
 }
-
-export interface AuditApiDocumentPrimitiveEntity {
-  createdAt: string & tags.Format<'date-time'>;
-  document: Primitive<MethodologyDocument>;
-  documentId: string;
-  snapshotId: string;
-  versionDate: string & tags.Format<'date-time'>;
-}


Reviews:
user: login: coderabbitai[bot]
body: > [!CAUTION]
> Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.  If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

**Actionable comments posted: 4**

<details>
<summary>🧹 Nitpick comments (38)</summary><blockquote>

<details>
<summary>libs/shared/aws-http/tsconfig.json (1)</summary><blockquote>

`1-10`: **TypeScript Configuration for AWS HTTP Library**  
The new `tsconfig.json` extends from the base configuration and correctly references `tsconfig.eslint.json` for linting purposes. The empty `"files"` and `"include"` arrays appear intentional—likely to control which files are picked up via references. Consider adding an inline comment to explain why these arrays are left empty, which may help future maintainers understand the design decision.

</blockquote></details>
<details>
<summary>libs/shared/document/seeds/package.json (1)</summary><blockquote>

`1-4`: **New Package Manifest is Minimal and Functional**  
The `package.json` for `@carrot-fndn/shared/document/seeds` is correctly set up with a package name and initial version. Consider adding additional metadata (like a description or repository link) for clarity and future package management; however, the minimal manifest as-is is acceptable given the current scope.

</blockquote></details>
<details>
<summary>libs/shared/http-request/package.json (1)</summary><blockquote>

`1-5`: **Consider setting `sideEffects` to `false` if possible**

The package.json configuration looks good overall. However, setting `sideEffects` to `true` indicates that this package has code with side effects that should not be eliminated by tree-shaking. For utility libraries like HTTP request handlers, this is often not necessary.

If this package doesn't actually have side effects, consider setting this to `false` to enable better tree-shaking and potentially reduce bundle sizes.

</blockquote></details>
<details>
<summary>libs/shared/document/seeds/project.json (1)</summary><blockquote>

`1-11`: **Project configuration is incomplete**

The project configuration looks good overall with appropriate tags and metadata. However, the lint and ts targets are currently empty objects.


Consider adding proper configurations for these targets to ensure the library can be properly linted and built. For example:

```diff
{
  "name": "shared-document-seeds",
  "$schema": "../../../../node_modules/nx/schemas/project-schema.json",
  "tags": ["app:any", "scope:shared", "stack:any", "type:lib"],
  "sourceRoot": "libs/shared/document/seeds/src",
  "projectType": "library",
  "targets": {
-    "lint": {},
-    "ts": {}
+    "lint": {
+      "executor": "@nx/eslint:lint",
+      "outputs": ["{options.outputFile}"]
+    },
+    "ts": {
+      "executor": "@nx/js:tsc",
+      "outputs": ["{options.outputPath}"],
+      "options": {
+        "outputPath": "dist/libs/shared/document/seeds",
+        "tsConfig": "libs/shared/document/seeds/tsconfig.json",
+        "packageJson": "libs/shared/document/seeds/package.json",
+        "main": "libs/shared/document/seeds/src/index.ts"
+      }
+    }
  }
}
```

</blockquote></details>
<details>
<summary>libs/shared/methodologies/audit-api/project.json (1)</summary><blockquote>

`1-12`: **Incomplete target configurations**

The project configuration is missing detailed settings for the lint, test, and ts targets. While the general structure is correct, these empty target objects should be filled with appropriate executor configurations.


Consider adding proper configurations for all targets to ensure the library can be properly linted, tested, and built. For example:

```diff
{
  "name": "shared-methodologies-audit-api",
  "$schema": "../../../node_modules/nx/schemas/project-schema.json",
  "tags": ["app:any", "scope:shared", "stack:any", "type:lib"],
  "sourceRoot": "libs/shared/methodologies/audit-api/src",
  "projectType": "library",
  "targets": {
-    "lint": {},
-    "test": {},
-    "ts": {}
+    "lint": {
+      "executor": "@nx/eslint:lint",
+      "outputs": ["{options.outputFile}"]
+    },
+    "test": {
+      "executor": "@nx/jest:jest",
+      "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
+      "options": {
+        "jestConfig": "libs/shared/methodologies/audit-api/jest.config.ts"
+      }
+    },
+    "ts": {
+      "executor": "@nx/js:tsc",
+      "outputs": ["{options.outputPath}"],
+      "options": {
+        "outputPath": "dist/libs/shared/methodologies/audit-api",
+        "tsConfig": "libs/shared/methodologies/audit-api/tsconfig.json",
+        "packageJson": "libs/shared/methodologies/audit-api/package.json",
+        "main": "libs/shared/methodologies/audit-api/src/index.ts"
+      }
+    }
  }
}
```

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts (2)</summary><blockquote>

`8-44`: **Consider enhancing test document objects for more realistic testing**

The test cases cover important scenarios including unique documents, cancelled duplicates, valid duplicates, and combinations of new and old formats. However, the document objects are minimalistic with only a `status` property.


Consider enhancing the test documents to more closely match real document structures that would be processed by the uniqueness check. This would make the tests more realistic and could potentially catch edge cases related to document structure.

For example:
```diff
 {
   newDuplicateDocuments: [
-    { status: OPEN },
+    { 
+      id: 'doc-1',
+      status: OPEN,
+      createdAt: new Date().toISOString(),
+      attributes: { /* relevant attributes for uniqueness check */ }
+    },
     { status: CANCELLED }
   ],
   oldDuplicateDocuments: [],
   resultComment: RESULT_COMMENTS.VALID_DUPLICATE_FOUND(2, 1),
   resultStatus: RuleOutputStatus.REJECTED,
   scenario: 'valid duplicates are found',
 },
```

---

`8-44`: **Consider adding comments to explain newDuplicateDocuments vs oldDuplicateDocuments**

The test cases distinguish between `newDuplicateDocuments` and `oldDuplicateDocuments`, but it's not immediately clear what this distinction represents.


Add a comment explaining the difference between new and old document formats to improve code clarity. For example:

```diff
+ // newDuplicateDocuments represent documents in the current format
+ // oldDuplicateDocuments represent documents in the legacy format that may still exist in the system
 export const uniquenessCheckTestCases = [
   {
     newDuplicateDocuments: [],
     oldDuplicateDocuments: [],
```

</blockquote></details>
<details>
<summary>libs/shared/methodologies/audit-api/src/audit-api.service.spec.ts (2)</summary><blockquote>

`1-1`: **Use Jest's afterEach instead of node:test**

Since you're using Jest for testing, it would be more appropriate to use Jest's `afterEach` instead of importing it from 'node:test'.

```diff
-import { afterEach } from 'node:test';
```

---

`20-32`: **Consider using AUDIT_API_URL instead of hardcoding the environment**

The test is directly using `AUDIT_API_URL_MAP.development`, which might cause the test to fail if the environment is set to 'production'. Consider importing and using `AUDIT_API_URL` instead to make the test more reliable across different environments.

```diff
-      expect(service['post']).toHaveBeenCalledWith(
-        `${AUDIT_API_URL_MAP.development}/documents/check-duplicates`,
-        dto,
-      );
+      expect(service['post']).toHaveBeenCalledWith(
+        `${AUDIT_API_URL}/documents/check-duplicates`,
+        dto,
+      );
```

</blockquote></details>
<details>
<summary>libs/shared/methodologies/audit-api/src/audit-api.constants.spec.ts (1)</summary><blockquote>

`38-38`: **Consider using undefined assignment instead of delete**

Using the `delete` operator can have a small performance impact. While this is generally negligible in test code, you might consider using an undefined assignment instead.

```diff
-      delete process.env['ENVIRONMENT'];
+      process.env['ENVIRONMENT'] = undefined;
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 38-38: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.


(lint/performance/noDelete)

</details>

</details>

</blockquote></details>
<details>
<summary>libs/shared/methodologies/audit-api/src/audit-api.service.ts (1)</summary><blockquote>

`16-28`: **Simple and focused service implementation**

The `AuditApiService` correctly extends `AwsHttpService` and implements a focused method for checking duplicate documents. Good practices include:
1. Proper DTO validation before making the request
2. Clear method signature with typed return value
3. Reuse of the base class's POST method

One potential enhancement would be to add more specific error handling for this particular endpoint.


Consider adding more specific error handling for this endpoint:

```diff
  async checkDuplicateDocuments(
    dto: CheckDuplicatesDto,
  ): Promise<ApiDocumentCheckDuplicatesResponse[]> {
    assertCheckDuplicatesDto(dto);

-   return this.post(`${AUDIT_API_URL}/documents/check-duplicates`, dto);
+   try {
+     return this.post(`${AUDIT_API_URL}/documents/check-duplicates`, dto);
+   } catch (error) {
+     throw new Error(`Failed to check duplicate documents: ${error instanceof Error ? error.message : 'Unknown error'}`);
+   }
  }
```

</blockquote></details>
<details>
<summary>libs/shared/aws-http/src/aws-http.service.spec.ts (1)</summary><blockquote>

`65-71`: **Performance issue with delete operator**

The use of `delete` operator on process.env can impact performance.

Replace the delete operation with an undefined assignment:

```diff
-  delete process.env['AWS_REGION'];
+  process.env['AWS_REGION'] = undefined;
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 66-66: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.


(lint/performance/noDelete)

</details>

</details>

</blockquote></details>
<details>
<summary>libs/shared/methodologies/audit-api/src/audit-api.service.e2e.spec.ts (2)</summary><blockquote>

`9-9`: **Reevaluate Jest timeout.**

A 100-second timeout for end-to-end tests may be longer than necessary. Consider adjusting to a reasonable threshold (e.g., 30 seconds) to detect slow tests sooner, unless your test environment genuinely needs this prolonged duration.

```diff
- jest.setTimeout(100_000);
+ jest.setTimeout(30_000);
```

---

`18-29`: **Strengthen negative test coverage.**

This test verifies the case where there are no duplicates, but consider adding variations—for example, a test with unrelated seeded documents—to ensure non-duplicate scenarios are consistently handled.

</blockquote></details>
<details>
<summary>libs/shared/helpers/src/common.helpers.spec.ts (1)</summary><blockquote>

`99-114`: **Solid validation for isNonEmptyObject.**

These tests confirm correct detection of empty vs. non-empty objects. Consider adding test cases for nested objects or inherited properties, but overall this is well-structured.

</blockquote></details>
<details>
<summary>libs/shared/document/seeds/src/document.seeds.ts (1)</summary><blockquote>

`29-40`: **Use stable seeds for reproducible test data**  
Relying on randomly generated fields can cause unpredictable test outcomes and harder debugging. Consider seeding your random generator or providing consistent data for a more deterministic and debuggable test environment.

</blockquote></details>
<details>
<summary>libs/shared/aws-http/src/aws-http.service.helpers.ts (2)</summary><blockquote>

`8-9`: **Restrict HTTP method type**  
Currently, `method` is declared as a generic string, which can lead to runtime errors or confusion if an invalid HTTP method is passed. Consider using a more precise type, e.g. `'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'`.

---

`28-40`: **Handle non-object bodies**  
Right now, the code only sets `Content-Type: application/json` if `body` is detected as an object. Passing a string or buffer to `body` would skip setting this header, which may lead to unexpected behavior. Consider supporting or validating other data types.

</blockquote></details>
<details>
<summary>libs/shared/aws-http/src/aws-http.service.ts (2)</summary><blockquote>

`33-42`: **Reevaluate error rethrowing**  
Wrapping every error in a `TypeError` might mask the original error type or message. Consider throwing a custom error type or rethrowing the original error to preserve stack traces and diagnostic details.

---

`44-51`: **Remove unused 'GET' method case**  
The `signRequest` method expects `'GET' | 'POST' | 'PUT'`, but this class only calls it with `'POST'` or `'PUT'`. If `GET` is unused, consider removing it or adding an actual GET method.

</blockquote></details>
<details>
<summary>libs/shared/aws-http/src/aws-http.service.helpers.spec.ts (1)</summary><blockquote>

`74-106`: **Improve environment testing strategy.**

The test strategy for different node environments is good, but there's an opportunity to improve the clarity of what's being tested. The current approach sets different environment variables for production vs. development, but the assertion only checks that the authorization header exists in both cases without verifying how the credential provider selection logic varies between environments.


Consider refactoring to:
1. Add explicit assertions that verify the credential provider selection logic behaves differently in each environment
2. Add comments explaining why `AWS_EXECUTION_ENV: 'AWS_ECS'` is specifically set for production
3. Verify that in production, `fromContainerMetadata` is actually used instead of `fromEnv()`

```diff
 mockFromContainerMetadata.mockReturnValue({
   accessKeyId: faker.string.uuid(),
   secretAccessKey: faker.string.uuid(),
   sessionToken: faker.string.uuid(),
 });

+// In production environment, fromContainerMetadata should be called
+// while in development environment, fromEnv() should be used
 process.env = {
   AWS_ACCESS_KEY_ID: faker.string.uuid(), // required by "fromEnv()"
   AWS_SECRET_ACCESS_KEY: faker.string.uuid(), // required by "fromEnv()"
   NODE_ENV: nodeEnvironment,
   ...awsExecutionEnvironment,
 };

 const result = await signRequest(input, faker.string.uuid());

 expect(result).toMatchObject({
   headers: expect.objectContaining({
     Host: input.url.host,
     authorization: expect.any(String),
   }),
 });
+
+// Verify credential provider selection based on environment
+if (nodeEnvironment === 'production') {
+  expect(mockFromContainerMetadata).toHaveBeenCalled();
+} else {
+  expect(mockFromContainerMetadata).not.toHaveBeenCalled();
+}
```

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.spec.ts (3)</summary><blockquote>

`93-93`: **Fix inconsistent function name in describe block.**

The describe block references `fetchSimilarMassIdDocumentsImpl` but the actual function being tested is `fetchSimilarMassIdDocuments`. This inconsistency could lead to confusion during test maintenance.


```diff
-describe('fetchSimilarMassIdDocumentsImpl', () => {
+describe('fetchSimilarMassIdDocuments', () => {
```

---

`127-130`: **Consider testing with mock instead of real service.**

The test is correctly using a mocked `AuditApiService`, but the way it's cast could be improved for better type safety and intent clarity.


```diff
 const result = await helpers.fetchSimilarMassIdDocuments(
   massIdDocument,
-  mockAuditApiService as unknown as AuditApiService,
+  mockAuditApiService as jest.Mocked<AuditApiService>,
 );
```

---

`138-143`: **Replace unsafe member access with type-safe assertions.**

The code uses eslint-disable directives to bypass type checking for accessing properties of API call arguments. This approach is less type-safe and makes the code harder to maintain.


```diff
-// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
-expect(calls[0][0].match.category).toBe(massIdDocument.category);
-// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
-expect(calls[1][0].match.category).toBe(DocumentCategory.MASS);
+// Get the query objects passed to checkDuplicateDocuments
+const [newFormatQuery, oldFormatQuery] = mockCheckDuplicateDocuments.mock.calls.map(call => call[0]);
+
+// Assert on the properly typed query objects
+expect(newFormatQuery).toHaveProperty('match.category', massIdDocument.category);
+expect(oldFormatQuery).toHaveProperty('match.category', DocumentCategory.MASS);
```

</blockquote></details>
<details>
<summary>libs/shared/helpers/src/common.helpers.ts (3)</summary><blockquote>

`22-24`: **Rename shadowed global variable.**

The variable `constructor` shadows the global `constructor` property, which could lead to confusion. Static analysis tools have flagged this as a potential issue.


```diff
-  const constructor =
+  const constructorProp =
     Object.prototype.hasOwnProperty.call(proto, 'constructor') &&
     proto.constructor;

   return (
-    typeof constructor === 'function' &&
-    constructor.toString() === Object.toString()
+    typeof constructorProp === 'function' &&
+    constructorProp.toString() === Object.toString()
   );
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 22-22: Do not shadow the global "constructor" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

</details>

</details>

---

`16-30`: **Enhance object prototype safety check.**

The implementation of `isPlainObject` has potential edge cases. The current implementation may not correctly identify objects created with `Object.create(someObject)` as non-plain objects.


A more robust implementation could:

```diff
  const proto = Object.getPrototypeOf(value) as Record<string, unknown> | null;

  if (proto === null) {
    return true;
  }

+  // Check if the prototype is Object.prototype
+  if (proto === Object.prototype) {
+    return true;
+  }

  const constructorProp =
    Object.prototype.hasOwnProperty.call(proto, 'constructor') &&
    proto.constructor;

  return (
    typeof constructorProp === 'function' &&
-    constructorProp.toString() === Object.toString()
+    constructorProp === Object
  );
```

This implementation more explicitly handles the case of direct `Object` instances, which is more reliable than string comparison.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 22-22: Do not shadow the global "constructor" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

</details>

</details>

---

`6-35`: **Add documentation to new helper functions.**

These utility functions are well-implemented, but lack documentation that explains their purpose, behavior with different inputs, and return values. Adding JSDoc comments would make the code more maintainable.


```diff
+/**
+ * Checks if the given value is an object and not null.
+ * @param value - The value to check
+ * @returns True if the value is an object and not null
+ */
export const isObject = (value: unknown): value is Record<string, unknown> =>
  typeof value === 'object' && value !== null;

+/**
+ * Checks if the given value is a plain JavaScript object.
+ * A plain object is one created by the Object constructor or Object.create(null).
+ * @param value - The value to check
+ * @returns True if the value is a plain JavaScript object
+ */
export const isPlainObject = (
  value: unknown,
): value is Record<string, unknown> => {
  // Implementation...
};

+/**
+ * Checks if the given value is a non-empty plain object.
+ * @param value - The value to check
+ * @returns True if the value is a plain object with at least one key
+ */
export const isNonEmptyObject = (
  value: unknown,
): value is Record<string, unknown> =>
  isPlainObject(value) && Object.keys(value).length > 0;
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 22-22: Do not shadow the global "constructor" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

</details>

</details>

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (2)</summary><blockquote>

`19-28`: **Improve pluralization in result messages.**

The current message templates don't handle singular vs. plural cases correctly. For example, if there's exactly 1 duplicate, the message will read "1 similar documents were found" instead of "1 similar document was found".


```diff
 export const RESULT_COMMENTS = {
   NO_DUPLICATES_FOUND:
     'No other documents with the same attributes were found.',
   ONLY_CANCELLED_DUPLICATES: (
     totalDuplicates: number,
     cancelledCount: number,
   ) =>
-    `${totalDuplicates} similar documents were found, but all are cancelled (${cancelledCount}).`,
+    `${totalDuplicates} similar ${totalDuplicates === 1 ? 'document was' : 'documents were'} found, but all ${cancelledCount === 1 ? 'is' : 'are'} cancelled (${cancelledCount}).`,
   VALID_DUPLICATE_FOUND: (
     totalDuplicates: number,
     validDuplicatesCount: number,
   ) =>
-    `${totalDuplicates} similar documents were found, of which ${validDuplicatesCount} are not cancelled.`,
+    `${totalDuplicates} similar ${totalDuplicates === 1 ? 'document was' : 'documents were'} found, of which ${validDuplicatesCount} ${validDuplicatesCount === 1 ? 'is' : 'are'} not cancelled.`,
 } as const;
```

---

`37-39`: **Add class-level documentation.**

The `UniquenessCheckProcessor` class lacks documentation explaining its purpose, how it fits into the overall architecture, and how it should be used.


```diff
+/**
+ * Processor that checks the uniqueness of mass ID documents.
+ * 
+ * This processor fetches similar documents from the Audit API and evaluates
+ * whether the document is unique based on the presence of valid (non-cancelled)
+ * duplicate documents.
+ */
export class UniquenessCheckProcessor extends ParentDocumentRuleProcessor<RuleSubject> {
  protected readonly processorErrors = new UniquenessCheckProcessorErrors();
```

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.spec.ts (2)</summary><blockquote>

`41-41`: **Consider using a deterministic input instead of random.**

Using `random<Required<RuleInput>>()` might introduce non-deterministic behavior in tests, which could potentially lead to flaky tests.

```diff
-      const ruleInput = random<Required<RuleInput>>();
+      const ruleInput: Required<RuleInput> = {
+        // Define fixed test properties here
+        requestId: 'test-request-id',
+        responseToken: 'test-response-token',
+        responseUrl: 'test-response-url',
+        // Add other required properties...
+      };
```

---

`48-50`: **Ensure mock implementation is reset in case of test failure.**

The mock implementation is set up correctly, but it might be better to use `mockReset()` in the `afterEach` block instead of just `clearAllMocks()` to ensure the mock implementations are also reset.

```diff
  afterEach(() => {
-   jest.clearAllMocks();
+   jest.resetAllMocks();
  });
```

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts (4)</summary><blockquote>

`34-34`: **Hardcoded license plate regex pattern could be extracted.**

The license plate regex pattern is hardcoded and its meaning is not immediately clear from the code. Consider extracting this to a named constant with comments explaining the pattern.

```diff
-const LICENSE_PLATE_REGEX = '^IO\\*{3}1\\s*$';
+// Format: Starts with 'IO', followed by exactly 3 asterisks, 
+// then '1' and optional whitespace at the end
+const LICENSE_PLATE_REGEX = '^IO\\*{3}1\\s*$';
```

---

`43-45`: **Improve testability by avoiding direct instantiation.**

The `istanbul ignore next` comment suggests this function is not being tested. Consider making the function more testable by allowing dependency injection or adding proper unit tests for it.

```diff
-/* istanbul ignore next */
-export const createAuditApiService = (): AuditApiService =>
-  new AuditApiService();
+export const createAuditApiService = (
+  customService?: AuditApiService
+): AuditApiService => 
+  customService ?? new AuditApiService();
```

---

`80-140`: **Reduce duplication between query creation functions.**

There's significant structural duplication between `createNewFormatQuery` and `createOldFormatQuery`. Consider extracting common logic into a shared helper function to improve maintainability.


You could create a function like:

```typescript
const createQueryBase = (
  document: Document,
  events: RequiredEvents,
  formatSpecificConfig: object
) => {
  return {
    match: {
      $and: [
        // Common query conditions
        ...formatSpecificConfig,
      ],
      category: formatSpecificConfig.category || document.category,
      currentValue: document.currentValue,
      dataSetName: document.dataSetName,
      subtype: document.subtype,
      type: document.type,
    },
  };
};
```

Then use it in both query creation functions.


Also applies to: 142-218

---

`220-239`: **Add logging for missing required events.**

The function returns an empty array if required events are not found, but this condition isn't logged. Consider adding a debug log to help with troubleshooting.

```diff
export const fetchSimilarMassIdDocuments = async (
  document: Document,
  apiService: AuditApiService,
+  logger?: Logger,
): Promise<ApiDocumentCheckDuplicatesResponse[]> => {
  const events = findRequiredEvents(document);

  if (!events) {
+    logger?.debug('Required events not found for document uniqueness check', {
+      documentId: document.id
+    });
    return [];
  }
```

</blockquote></details>
<details>
<summary>libs/shared/http-request/src/http-request.ts (3)</summary><blockquote>

`43-77`: **Improve error handling and message construction.**

The error message in line 56 uses the status code before checking if it exists. Also, consider improving the error handling for non-response errors.

```diff
export const handleRequestError = (
  error: unknown,
  config: AxiosRequestConfig,
  options: { ignoreTimeoutError: boolean; logger: Logger },
) => {
  const { ignoreTimeoutError, logger } = options;
  const url = new URL([config.baseURL, config.url].join(''));

  if (!axios.isAxiosError(error)) {
-    throw new Error('Request failed', { cause: error });
+    throw new Error(`Request to ${url.toString()} failed: ${String(error)}`, { cause: error });
  }

  const status = error.response?.status;
-  const message = `API call ${url.toString()} responded with ${status}`;
+  const message = status 
+    ? `API call ${url.toString()} responded with status ${status}` 
+    : `API call ${url.toString()} failed without response`;

-  if (status === 504) {
+  if (status === 504) {
    if (!ignoreTimeoutError) {
      logger.error(message);
    }

    if (ignoreTimeoutError) {
      return null;
    }
  } else {
    logger.error(message);
  }

  logger.debug(error, message);

-  if (status >= 400 && status < 500) {
+  if (status && status >= 400 && status < 500) {
    return error.response;
  }

-  throw new Error('Request failed', { cause: error });
+  throw new Error(`Request to ${url.toString()} failed: ${error.message || 'Unknown error'}`, { cause: error });
```

---

`79-99`: **Add return type annotation and improve error handling.**

The `httpRequest` function doesn't have a return type annotation, which would be helpful for consumers. Also, consider adding more specific error handling for different types of request failures.

```diff
-export const httpRequest = async (
+export const httpRequest = async <T = any>(
  config: AxiosRequestConfig,
  {
    ignoreTimeoutError = false,
    logger = pinoLogger,
  }: {
    ignoreTimeoutError?: boolean;
    logger?: Logger;
  } = {},
-) => {
+): Promise<AxiosResponse<T> | null> => {
  const requestConfig = await prepareHttpRequestConfig(config);

  try {
    return await axios(requestConfig);
  } catch (error) {
    return handleRequestError(error, requestConfig, {
      ignoreTimeoutError,
      logger,
    });
  }
};
```

---

`13-13`: **Simplify boolean logic.**

The expression `config.baseURL?.includes('localhost') !== true` can be simplified.

```diff
-  const signedRequest = config.baseURL?.includes('localhost') !== true;
+  const signedRequest = !config.baseURL?.includes('localhost');
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🛑 Comments failed to post (4)</summary><blockquote>

<details>
<summary>libs/shared/methodologies/audit-api/tsconfig.build.json (1)</summary><blockquote>

1-3: _⚠️ Potential issue_

**Fix the path in the extends array**

There's an extra dot at the beginning of the second path in the extends array.


```diff
{
-  "extends": ["./tsconfig.json", ".../../../../.tsconfig/tsconfig.build.json"]
+  "extends": ["./tsconfig.json", "../../../../.tsconfig/tsconfig.build.json"]
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
{
  "extends": ["./tsconfig.json", "../../../../.tsconfig/tsconfig.build.json"]
}
`````

</details>

<!-- suggestion_end -->

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (1)</summary><blockquote>

71-89: _🛠️ Refactor suggestion_

**Add error handling for API calls.**

The `getRuleSubject` method doesn't handle potential errors from the API calls. If `fetchSimilarMassIdDocuments` or `createAuditApiService` fail, there's no explicit error handling.


```diff
 protected override async getRuleSubject(
   document: Document,
 ): Promise<RuleSubject> {
-  const duplicateDocuments = await fetchSimilarMassIdDocuments(
-    document,
-    createAuditApiService(),
-  );
+  try {
+    const apiService = createAuditApiService();
+    const duplicateDocuments = await fetchSimilarMassIdDocuments(
+      document,
+      apiService,
+    );
 
-  const cancelledCount = duplicateDocuments.filter(
-    (duplicateDocument) =>
-      duplicateDocument.status === DocumentStatus.CANCELLED,
-  ).length;
+    const cancelledCount = duplicateDocuments.filter(
+      (duplicateDocument) =>
+        duplicateDocument.status === DocumentStatus.CANCELLED,
+    ).length;
 
-  return {
-    cancelledCount,
-    totalDuplicates: duplicateDocuments.length,
-    validDuplicatesCount: duplicateDocuments.length - cancelledCount,
-  };
+    return {
+      cancelledCount,
+      totalDuplicates: duplicateDocuments.length,
+      validDuplicatesCount: duplicateDocuments.length - cancelledCount,
+    };
+  } catch (error) {
+    // Log the error for diagnostics
+    console.error('Error fetching similar documents:', error);
+    // Throw a specific processor error for better error handling
+    throw this.processorErrors.createError(
+      this.processorErrors.ERROR_MESSAGE.REJECTED_BY_ERROR
+    );
+  }
 }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  protected override async getRuleSubject(
    document: Document,
  ): Promise<RuleSubject> {
    try {
      const apiService = createAuditApiService();
      const duplicateDocuments = await fetchSimilarMassIdDocuments(
        document,
        apiService,
      );

      const cancelledCount = duplicateDocuments.filter(
        (duplicateDocument) =>
          duplicateDocument.status === DocumentStatus.CANCELLED,
      ).length;

      return {
        cancelledCount,
        totalDuplicates: duplicateDocuments.length,
        validDuplicatesCount: duplicateDocuments.length - cancelledCount,
      };
    } catch (error) {
      // Log the error for diagnostics
      console.error('Error fetching similar documents:', error);
      // Throw a specific processor error for better error handling
      throw this.processorErrors.createError(
        this.processorErrors.ERROR_MESSAGE.REJECTED_BY_ERROR
      );
    }
  }
`````

</details>

<!-- suggestion_end -->

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts (1)</summary><blockquote>

47-70: _⚠️ Potential issue_

**Add null check for document.externalEvents.**

There's no null check for `document.externalEvents` before accessing it with the `find` method. If `externalEvents` is undefined, this will lead to a runtime error.

```diff
export const findRequiredEvents = (
  document: Document,
): RequiredEvents | null => {
+  if (!document.externalEvents) {
+    return null;
+  }

-  const pickUpEvent = document.externalEvents?.find(
+  const pickUpEvent = document.externalEvents.find(
    eventNameIsAnyOf([PICK_UP]),
  );
-  const dropOffEvent = document.externalEvents?.find(
+  const dropOffEvent = document.externalEvents.find(
    eventNameIsAnyOf([DROP_OFF]),
  );
-  const wasteGeneratorEvent = document.externalEvents?.find(
+  const wasteGeneratorEvent = document.externalEvents.find(
    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([WASTE_GENERATOR])),
  );
-  const recyclerEvent = document.externalEvents?.find(
+  const recyclerEvent = document.externalEvents.find(
    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([RECYCLER])),
  );
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
export const findRequiredEvents = (
  document: Document,
): RequiredEvents | null => {
  if (!document.externalEvents) {
    return null;
  }

  const pickUpEvent = document.externalEvents.find(
    eventNameIsAnyOf([PICK_UP]),
  );
  const dropOffEvent = document.externalEvents.find(
    eventNameIsAnyOf([DROP_OFF]),
  );
  const wasteGeneratorEvent = document.externalEvents.find(
    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([WASTE_GENERATOR])),
  );
  const recyclerEvent = document.externalEvents.find(
    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([RECYCLER])),
  );

  if (
    isNil(pickUpEvent) ||
    isNil(dropOffEvent) ||
    isNil(wasteGeneratorEvent) ||
    isNil(recyclerEvent)
  ) {
    return null;
  }
  
  // Assuming additional code follows here...
};
`````

</details>

<!-- suggestion_end -->

</blockquote></details>
<details>
<summary>libs/shared/http-request/src/http-request.ts (1)</summary><blockquote>

9-41: _⚠️ Potential issue_

**Improve URL construction and environment variable handling.**

The current URL joining approach in line 12 might not handle path separators correctly. Also, there's no fallback for the AWS_REGION environment variable in line 31.

```diff
export const prepareHttpRequestConfig = async (
  config: AxiosRequestConfig,
): Promise<AxiosRequestConfig> => {
-  const url = new URL([config.baseURL, config.url].join(''));
+  // Ensure proper URL construction with path handling
+  const baseUrl = config.baseURL?.endsWith('/') 
+    ? config.baseURL.slice(0, -1) 
+    : config.baseURL || '';
+  const path = config.url?.startsWith('/') 
+    ? config.url 
+    : `/${config.url || ''}`;
+  const url = new URL(baseUrl + path);
  const signedRequest = config.baseURL?.includes('localhost') !== true;

  if (!isNonEmptyString(config.method)) {
    throw new Error('Request method is required');
  }

  if (!signedRequest) {
    return config;
  }

+  const awsRegion = process.env['AWS_REGION'];
+  if (!awsRegion) {
+    throw new Error('AWS_REGION environment variable is required for signed requests');
+  }

  const { headers } = await signRequest(
    {
      body: config.method === 'POST' ? config.data : undefined,
      method: config.method,
      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
      query: config.method === 'GET' ? config.params : undefined,
      url,
    },
-    process.env['AWS_REGION'] as string,
+    awsRegion,
  );
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
export const prepareHttpRequestConfig = async (
  config: AxiosRequestConfig,
): Promise<AxiosRequestConfig> => {
  // Ensure proper URL construction with path handling
  const baseUrl = config.baseURL?.endsWith('/')
    ? config.baseURL.slice(0, -1)
    : config.baseURL || '';
  const path = config.url?.startsWith('/')
    ? config.url
    : `/${config.url || ''}`;
  const url = new URL(baseUrl + path);
  const signedRequest = config.baseURL?.includes('localhost') !== true;

  if (!isNonEmptyString(config.method)) {
    throw new Error('Request method is required');
  }

  if (!signedRequest) {
    return config;
  }

  const awsRegion = process.env['AWS_REGION'];
  if (!awsRegion) {
    throw new Error('AWS_REGION environment variable is required for signed requests');
  }

  const { headers } = await signRequest(
    {
      body: config.method === 'POST' ? config.data : undefined,
      method: config.method,
      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
      query: config.method === 'GET' ? config.params : undefined,
      url,
    },
    awsRegion,
  );

  return {
    ...config,
    headers: {
      ...config.headers,
      ...headers,
    },
  };
};
`````

</details>

<!-- suggestion_end -->

</blockquote></details>

</blockquote></details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-27T21:05:20Z
user: login: cris-santos
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T17:06:40Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T19:49:36Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T19:49:40Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T19:49:44Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T19:49:48Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T19:49:51Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T19:49:54Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T19:49:57Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T19:50:00Z
user: login: sangalli
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T20:57:37Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts (1)</summary><blockquote>

`12-22`: **Consider including the document ID for debugging.**  
Including the relevant document ID (or additional context) in the error messages can help users identify which specific record triggered the error, making troubleshooting more efficient.

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.spec.ts (1)</summary><blockquote>

`81-117`: **Consider adding negative test coverage.**  
Currently, tests focus on successful merges of new/old format duplicates. Adding a scenario where the API call fails or returns an error would ensure greater resilience of the error-handling logic.

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts (1)</summary><blockquote>

`205-224`: **Consider deduplicating results from the new/old format queries.**  
Currently, the `fetchSimilarMassIdDocuments` function merges duplicates returned by both queries without removing duplicates that might appear in both. Although it might be acceptable behavior in some contexts, you could optionally filter out identical documents by their unique identifier.  


Below is a possible diff to illustrate a simple deduplication strategy around lines 217-222:

```diff
  const [newFormattedDuplicates, oldFormattedDuplicates] = await Promise.all([
    auditApiService.checkDuplicateDocuments(newFormatQuery),
    auditApiService.checkDuplicateDocuments(oldFormatQuery),
  ]);

- return [...newFormattedDuplicates, ...oldFormattedDuplicates];
+ const allDuplicates = [...newFormattedDuplicates, ...oldFormattedDuplicates];
+ const uniqueMap = new Map(allDuplicates.map(doc => [doc.id, doc]));
+ return Array.from(uniqueMap.values());
```

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (1)</summary><blockquote>

`258-275`: **Consider logging or capturing errors in the catch block for debugging.**  
The current `try/catch` structure returns a generic rejected status with an error comment. A small improvement would be logging or capturing these errors in a centralized logger to aid in debugging potential issues without altering user-facing messages.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Lite**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 2780cc78f00178e723b89eebce294de12b71f17e and 2b1cc6fc94ea5f130a85f8eafe472be280be52cc.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts` (1 hunks)
* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.spec.ts` (1 hunks)
* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts` (1 hunks)
* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.spec.ts` (1 hunks)
* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts` (1 hunks)
* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts` (1 hunks)
* `libs/shared/methodologies/bold/types/src/enum.types.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* libs/shared/methodologies/bold/types/src/enum.types.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (4)</summary>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts (2)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (1)</summary>

* `RESULT_COMMENTS` (55-68)

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts (1)</summary>

* `UniquenessCheckProcessorErrors` (12-22)

</details>

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.spec.ts (2)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (1)</summary>

* `UniquenessCheckProcessor` (76-274)

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts (2)</summary>

* `uniquenessCheckTestCases` (26-62)
* `uniquenessCheckErrorTestCases` (80-182)

</details>

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (3)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts (1)</summary>

* `UniquenessCheckProcessorErrors` (12-22)

</details>
<details>
<summary>libs/shared/rule/standard-data-processor/src/rule-standard-data-processor.ts (1)</summary>

* `EvaluateResultOutput` (12-16)

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts (3)</summary>

* `EventsData` (32-38)
* `fetchSimilarMassIdDocuments` (205-223)
* `createAuditApiService` (51-52)

</details>

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.spec.ts (1)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts (1)</summary>

* `EventsData` (32-38)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (9)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts (1)</summary>

`1-5`: **Imports look correct.**  
Code dependencies appear to be well-organized and consistent with the rest of the project structure.

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.spec.ts (2)</summary>

`16-25`: **Mocking approach is well-structured.**  
The test leverages a partial mock for `AuditApiService` and overrides `createAuditApiService` effectively, isolating the tested logic from external dependencies.

---

`128-176`: **Great coverage for license plate regex creation.**  
The tests thoroughly verify whitespace trimming, special character escaping, and exact matching, providing strong confidence in the regex-based logic.

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts (2)</summary>

`26-62`: **Comprehensive coverage for duplicates.**  
These scenarios effectively cover custom logic for unique documents, only-cancelled duplicates, and valid duplicates in both new and old formats.

---

`80-182`: **Well-defined error scenarios.**  
The test cases thoroughly capture missing documents, events, and vehicle license plates, ensuring the processor handles critical edge cases.

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts (1)</summary>

`1-49`: **The approach to create license plate regex and handle event data is well-structured.**  
Everything in these initial imports, constants, and the `createLicensePlateRegex` utility looks correct and follows best practices for ensuring proper escaping of user input or external data.

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (1)</summary>

`1-80`: **Good structure and error handling in event retrievers.**  
The methods (`getDropOffEvent`, `getPickUpEvent`, etc.) neatly encapsulate event validation and error throwing, making the logic readable and maintainable.

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.spec.ts (2)</summary>

`1-25`: **Test setup and mocking strategy seem clear and effective.**  
Using `spyOnDocumentQueryServiceLoad` and `mockCheckDuplicateDocuments` ensures a tight scope for unit tests. The approach is easy to follow and robust against regressions.

---

`76-106`: **Comprehensive error-related test coverage.**  
The table-driven tests in the error cases confirm that your processor handles each missing-event scenario correctly, returning the expected result comment and status.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-31T21:01:37Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T16:51:41Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T16:51:47Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T16:51:49Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.lambda.e2e.spec.ts (3)</summary><blockquote>

`23-27`: **Consider avoiding the eslint-disable comment.**

The eslint-disable comment suggests there might be a better way to implement this mock. Consider refactoring to a more type-safe approach that doesn't require disabling linting rules.

```diff
-// eslint-disable-next-line @typescript-eslint/no-unsafe-return
-jest.mock('./uniqueness-check.helpers', () => ({
-  ...jest.requireActual('./uniqueness-check.helpers'),
-  createAuditApiService: () => mockAuditApiService,
-}));
+jest.mock('./uniqueness-check.helpers', () => {
+  const actual = jest.requireActual('./uniqueness-check.helpers');
+  return {
+    ...actual,
+    createAuditApiService: () => mockAuditApiService,
+  };
+});
```

---

`72-72`: **Consider adding more specific assertions for comprehensive test coverage.**

The test only verifies the `resultStatus` but doesn't check other parts of the response such as the `resultComment`. Adding more specific assertions would increase test confidence.

```diff
 expect(response.resultStatus).toBe(resultStatus);
+expect(response.resultComment).toBe(resultComment);
```

---

`100-100`: **Enhance error case assertions for better test coverage.**

Similar to the first test group, consider adding assertions for `resultComment` to verify the complete error response.

```diff
 expect(response.resultStatus).toBe(resultStatus);
+expect(response.resultComment).toBe(resultComment);
```

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts (2)</summary><blockquote>

`64-78`: **Potential shared state issue in test fixture.**

Defining test fixtures outside of test functions creates shared state that could lead to issues if tests are run in isolation or in a different order. Consider moving this setup inside each test case or use a factory function.

```diff
-const { massIdAuditDocument, massIdDocument } = new BoldStubsBuilder()
-  .createMassIdDocument({
-    externalEventsMap: {
-      [PICK_UP]: stubBoldMassIdPickUpEvent({
-        metadataAttributes: [
-          [VEHICLE_LICENSE_PLATE_V1, faker.vehicle.vrm()],
-          [VEHICLE_LICENSE_PLATE_V2, faker.vehicle.vrm()],
-        ],
-      }),
-    },
-  })
-  .createMassIdAuditDocument()
-  .build();

+const createTestDocuments = () => {
+  return new BoldStubsBuilder()
+    .createMassIdDocument({
+      externalEventsMap: {
+        [PICK_UP]: stubBoldMassIdPickUpEvent({
+          metadataAttributes: [
+            [VEHICLE_LICENSE_PLATE_V1, faker.vehicle.vrm()],
+            [VEHICLE_LICENSE_PLATE_V2, faker.vehicle.vrm()],
+          ],
+        }),
+      },
+    })
+    .createMassIdAuditDocument()
+    .build();
+};

// Then in each test case:
// const { massIdAuditDocument, massIdDocument } = createTestDocuments();
```

---

`136-169`: **Duplicate test case for vehicle license plate error condition.**

The last two error test cases appear to be testing the same condition with different scenario descriptions, but they have the same implementation and expected error. Consider consolidating these test cases or more clearly differentiating their purposes.

```diff
-  {
-    massIdAuditDocument,
-    massIdDocument: {
-      ...massIdDocument,
-      externalEvents: [
-        ...(massIdDocument.externalEvents ?? []).filter(
-          (event) => event.name !== PICK_UP.toString(),
-        ),
-        stubBoldMassIdPickUpEvent({
-          metadataAttributes: [[VEHICLE_LICENSE_PLATE_V2, undefined]],
-        }),
-      ],
-    },
-    resultComment: processorErrors.ERROR_MESSAGE.MISSING_VEHICLE_LICENSE_PLATE,
-    resultStatus: RuleOutputStatus.REJECTED,
-    scenario: `when the old "${VEHICLE_LICENSE_PLATE_V1}" attribute is missing`,
-  },
-  {
-    massIdAuditDocument,
-    massIdDocument: {
-      ...massIdDocument,
-      externalEvents: [
-        ...(massIdDocument.externalEvents ?? []).filter(
-          (event) => event.name !== PICK_UP.toString(),
-        ),
-        stubBoldMassIdPickUpEvent({
-          metadataAttributes: [[VEHICLE_LICENSE_PLATE_V2, undefined]],
-        }),
-      ],
-    },
-    resultComment: processorErrors.ERROR_MESSAGE.MISSING_VEHICLE_LICENSE_PLATE,
-    resultStatus: RuleOutputStatus.REJECTED,
-    scenario: `when the new "${VEHICLE_LICENSE_PLATE_V2}" attribute is missing`,
-  },
+  {
+    massIdAuditDocument,
+    massIdDocument: {
+      ...massIdDocument,
+      externalEvents: [
+        ...(massIdDocument.externalEvents ?? []).filter(
+          (event) => event.name !== PICK_UP.toString(),
+        ),
+        stubBoldMassIdPickUpEvent({
+          metadataAttributes: [[VEHICLE_LICENSE_PLATE_V2, undefined]],
+        }),
+      ],
+    },
+    resultComment: processorErrors.ERROR_MESSAGE.MISSING_VEHICLE_LICENSE_PLATE,
+    resultStatus: RuleOutputStatus.REJECTED,
+    scenario: `when the vehicle license plate attributes are missing`,
+  },
```

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.spec.ts (2)</summary><blockquote>

`42-42`: **Consider using a fixed rule input for deterministic tests.**

Using `random<Required<RuleInput>>()` could lead to non-deterministic test behavior. For better test stability, consider using a fixed rule input with known values.

```diff
-const ruleInput = random<Required<RuleInput>>();
+const ruleInput: Required<RuleInput> = {
+  documentKeyPrefix: 'test-prefix',
+  parentDocumentId: 'test-document-id',
+  requestId: 'test-request-id',
+  responseToken: 'test-response-token',
+  responseUrl: 'test-response-url',
+};
```

---

`47-52`: **Verify mock function calls with expected arguments.**

The test doesn't verify that the mock functions were called with the expected arguments. Adding these assertions would ensure the processor is using the APIs correctly.

```diff
mockCheckDuplicateDocuments
  .mockResolvedValueOnce(newDuplicateDocuments)
  .mockResolvedValueOnce(oldDuplicateDocuments);

documentLoaderService.mockResolvedValueOnce(massIdDocument);

const ruleOutput = await ruleDataProcessor.process(ruleInput);

+expect(documentLoaderService).toHaveBeenCalledWith(ruleInput);
+expect(mockCheckDuplicateDocuments).toHaveBeenCalledTimes(2);
```

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (1)</summary><blockquote>

`195-195`: **Improve testability by injecting dependencies.**

Directly calling `createAuditApiService()` makes testing harder as it tightly couples the code to a specific implementation. Consider injecting this dependency through the constructor or method parameters for better testability.

```diff
-export class UniquenessCheckProcessor extends ParentDocumentRuleProcessor<RuleSubject> {
+export class UniquenessCheckProcessor extends ParentDocumentRuleProcessor<RuleSubject> {
+  constructor(
+    private readonly auditApiServiceFactory = createAuditApiService
+  ) {
+    super();
+  }

// Then update the getRuleSubject method:
protected async getRuleSubject(document: Document): Promise<RuleSubject> {
  const eventsData = this.collectRequiredEventsData(document);

  const duplicateDocuments = await fetchSimilarMassIdDocuments({
-    auditApiService: createAuditApiService(),
+    auditApiService: this.auditApiServiceFactory(),
    document,
    eventsData,
  });
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Lite**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 2b1cc6fc94ea5f130a85f8eafe472be280be52cc and 37240136f764c9b56616fae8b3c09ca50d79fbab.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts` (1 hunks)
* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.spec.ts` (1 hunks)
* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.lambda.e2e.spec.ts` (1 hunks)
* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.spec.ts` (1 hunks)
* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts` (1 hunks)
* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts
* libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.spec.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (4)</summary>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.lambda.e2e.spec.ts (2)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts (2)</summary>

* `uniquenessCheckTestCases` (26-62)
* `uniquenessCheckErrorTestCases` (80-170)

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.lambda.ts (1)</summary>

* `uniquenessCheckLambda` (7-7)

</details>

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts (2)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (1)</summary>

* `RESULT_COMMENTS` (48-61)

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts (1)</summary>

* `UniquenessCheckProcessorErrors` (12-22)

</details>

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (3)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts (1)</summary>

* `UniquenessCheckProcessorErrors` (12-22)

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts (3)</summary>

* `EventsData` (32-38)
* `fetchSimilarMassIdDocuments` (205-223)
* `createAuditApiService` (51-52)

</details>
<details>
<summary>libs/shared/rule/standard-data-processor/src/rule-standard-data-processor.ts (1)</summary>

* `EvaluateResultOutput` (12-16)

</details>

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.spec.ts (2)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (1)</summary>

* `UniquenessCheckProcessor` (69-239)

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts (2)</summary>

* `uniquenessCheckTestCases` (26-62)
* `uniquenessCheckErrorTestCases` (80-170)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (4)</summary>

`72-106`: **Refactor for improved readability and separation of concerns.**

The `collectRequiredEventsData` method is quite lengthy. Consider breaking down complex methods into smaller, more focused functions for better readability and maintainability.


Refactoring this method as suggested in previous comments would improve code organization.

---

`108-134`: **Well-implemented helper method with good error handling.**

The `getEventOrThrow` method is a great example of extracting common functionality into a reusable helper. It handles various criteria combinations well and provides clear error messages.

---

`160-189`: **Well-structured evaluation logic with clear conditions.**

The `evaluateResult` method is well-designed with clear conditions and appropriate return values for each scenario. The use of formatted message templates with dynamic values is also a good practice.

---

`212-237`: **Comprehensive error handling in process method.**

The process method has robust error handling, checking for document existence and catching any unexpected errors. This approach ensures consistent error responses and prevents application crashes.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T16:53:41Z
user: login: sangalli
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T16:56:30Z
user: login: cris-santos
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T10:18:54Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T17:48:32Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts (1)</summary><blockquote>

`14-14`: **Consider making the error message more specific.**

The error message "MassID document not found." doesn't specify which document type was not found. This was previously noted in a review comment. For better user experience and easier debugging, consider specifying the document type that is missing.

For example: "MassID document of type {documentType} not found."

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Lite**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 37240136f764c9b56616fae8b3c09ca50d79fbab and 55b268da2ed3531d7c626082c0728f834e94ce27.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts (2)</summary>

`20-20`: **Consider making the error message more direct.**

The previous review suggested making error messages more direct by omitting the "Unable to validate the MassID uniqueness because the..." prefix. Consider revising this message to be more specific about the actual error that prevented validation.

For example: "Failed to validate MassID uniqueness: {specific reason}"

---

`1-22`: **Overall, the error class is well-structured.**

The error class is well-organized with clear, descriptive error messages. Good use of constants from imported enums instead of string literals makes the code more maintainable.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-02T17:48:47Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T18:03:12Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T18:03:41Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (1)</summary><blockquote>

`73-107`: **Consider extracting common event retrieval logic into a helper function.**

The calls to `this.getEventOrThrow` are correct and expressive. However, the code repeats the same pattern of:

1. Searching for an event by name/label  
2. Throwing an error if not found

You may consider creating a small helper that takes the document and an array of `{ criteria, errorKey }` objects, iterates over them, and gathers the events into a single object. This would centralize the error handling and might reduce repetitive boilerplate.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Lite**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 55b268da2ed3531d7c626082c0728f834e94ce27 and 08ef348c2fa96e4d5f7c76052f72d104b265f85b.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (3)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts (1)</summary>

* `UniquenessCheckProcessorErrors` (12-22)

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts (3)</summary>

* `EventsData` (32-38)
* `fetchSimilarMassIdDocuments` (205-223)
* `createAuditApiService` (51-52)

</details>
<details>
<summary>libs/shared/rule/standard-data-processor/src/rule-standard-data-processor.ts (1)</summary>

* `EvaluateResultOutput` (12-16)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (4)</summary>

`109-135`: **Confirm that only the first matching event is needed.**

Here, `document.externalEvents?.find(...)` returns the first matching event. If the document contains multiple matching events, only the initial one will be used. Verify that this behavior is deliberate and confirm with the team whether additional matching events should be handled or flagged as an error.

---

`137-159`: **Fallback logic for vehicle license plate is well-structured.**

The approach of attempting to retrieve the V1 attribute first, then falling back to the V2 attribute, and finally throwing an error if neither is found is logically sound and maintains backward compatibility for existing data. Nice work!

---

`161-190`: **Confirm partial duplicate handling requirements.**

The branching logic, which checks for any valid duplicates before acknowledging only-cancelled duplicates or no duplicates, is clear and concise. However, consider whether any special handling is necessary if some duplicates are cancelled while others are missing status. Depending on your domain requirements, you may need to treat unknown statuses like valid duplicates. Please verify the intended behavior with stakeholders.

---

`213-239`: **Robust error handling in the `process` method.**

Wrapping document loading and subject evaluation in `try/catch` and mapping caught errors to a standardized rule output ensures consistent handling of unexpected scenarios. This aligns well with best practices for graceful error recovery.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-02T18:05:13Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T18:59:03Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts (3)</summary><blockquote>

`21-57`: **Well-structured uniqueness test cases covering multiple scenarios**

The test cases effectively cover various uniqueness scenarios including:
- Documents with no duplicates
- Documents with only cancelled duplicates
- Documents with valid duplicates in new format
- Documents with valid duplicates in old format
- Documents with valid duplicates in both formats

Each test case properly includes the expected result status and comments.


Consider adding TypeScript interfaces to define the structure of these test cases for improved type safety and documentation:

```typescript
interface UniquenessCheckTestCase {
  newDuplicateDocuments: Array<{ status: DocumentStatus }>;
  oldDuplicateDocuments: Array<{ status: DocumentStatus }>;
  resultComment: string;
  resultStatus: RuleOutputStatus;
  scenario: string;
}
```

---

`66-139`: **Comprehensive error test cases covering validation failures**

The error test cases thoroughly cover essential validation scenarios including:
- Missing document
- Missing required events (DROP_OFF, PICK_UP)
- Missing required labels (WASTE_GENERATOR, RECYCLER)
- Missing required attributes (VEHICLE_LICENSE_PLATE)

Each scenario includes appropriate error messages and rejection status.


There's some repeated filter logic when removing events. Consider extracting a helper function to reduce duplication:

```diff
+ const filterOutEvent = (events, eventNameOrLabel, isLabel = false) =>
+   events?.filter(event => 
+     isLabel 
+       ? event.label !== eventNameOrLabel.toString()
+       : event.name !== eventNameOrLabel.toString()
+   );

  // Then use it like:
  massIdDocument: {
    ...massIdDocument,
    externalEvents: filterOutEvent(massIdDocument.externalEvents, DROP_OFF),
  },
  
  // For label filtering:
  massIdDocument: {
    ...massIdDocument,
    externalEvents: filterOutEvent(massIdDocument.externalEvents, WASTE_GENERATOR, true),
  },
```

---

`21-21`: **Add JSDoc comments for exported constants**

Consider adding JSDoc comments to the exported constants to improve documentation:

```typescript
/**
 * Test cases for validating uniqueness check processor functionality with valid inputs.
 * Each case tests different combinations of duplicate documents and expected outcomes.
 */
export const uniquenessCheckTestCases = [
  // ...
];

/**
 * Test cases for validating uniqueness check processor error handling.
 * Each case tests different validation failures and expected error messages.
 */
export const uniquenessCheckErrorTestCases = [
  // ...
];
```



Also applies to: 66-66

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (2)</summary><blockquote>

`66-103`: **Opportunity to reduce repeated logic.**  
You are calling `getEventOrThrow` multiple times for different events. If additional required events arise in the future, consider refactoring them into a single method or loop to reduce boilerplate.

---

`219-223`: **Enhance error visibility.**  
Consider logging the caught error details to aid debugging and provide better traceability:  

```diff
} catch (error: unknown) {
+  console.error('UniquenessCheckProcessor error:', error);
   return mapToRuleOutput(ruleInput, RuleOutputStatus.REJECTED, {
     resultComment: this.errorProcessor.getResultCommentFromError(error),
   });
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Lite**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 08ef348c2fa96e4d5f7c76052f72d104b265f85b and 9ca4a7680ae9646ec56765dacc9bf8d53cdd57c0.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts` (1 hunks)
* `libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (2)</summary>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts (2)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (1)</summary>

* `RESULT_COMMENTS` (46-58)

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts (1)</summary>

* `UniquenessCheckProcessorErrors` (12-22)

</details>

</blockquote></details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (3)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts (1)</summary>

* `UniquenessCheckProcessorErrors` (12-22)

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts (3)</summary>

* `EventsData` (32-38)
* `fetchSimilarMassIdDocuments` (205-223)
* `createAuditApiService` (51-52)

</details>
<details>
<summary>libs/shared/rule/standard-data-processor/src/rule-standard-data-processor.ts (1)</summary>

* `EvaluateResultOutput` (12-16)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.test-cases.ts (3)</summary>

`1-15`: **Well-organized imports and necessary dependencies**

The imports are appropriately organized, including testing utilities, document types, and error handling components necessary for the uniqueness check functionality.

---

`16-20`: **Good use of destructured constants**

Destructuring these constants from enums improves code readability and maintenance.

---

`59-65`: **Good preparation of test fixtures**

Creating processor error instances and test stubs before defining test cases is a clean approach.

</details>
<details>
<summary>libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts (3)</summary>

`1-33`: **All imports are logically consistent.**  
They appear to be well-organized, with no sign of unused references or extraneous modules.

---

`46-58`: **Result comments are concise and expressive.**  
Using dynamic functions for generating string messages is a neat approach, making it clear to read and maintain.

---

`146-175`: **Evaluation logic is straightforward and correct.**  
Differentiating between valid duplicates and cancelled duplicates aligns well with the intended uniqueness check requirements.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-02T19:02:10Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T20:14:13Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T20:14:15Z
user: login: AMarcosCastelo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T20:14:19Z
user: login: cris-santos
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T12:39:37Z

Review comments:
diff_hunk: @@ -0,0 +1,8 @@
+import { BaseProcessorErrors } from '@carrot-fndn/shared/methodologies/bold/processors';
+
+export class UniquenessCheckProcessorErrors extends BaseProcessorErrors {
+  override readonly ERROR_MESSAGE = {
+    DOCUMENT_TYPE_NOT_FOUND: 'Document type not found.',
+    REJECTED_BY_ERROR: 'Unable to validate the uniqueness-check.',
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts
user: login: cris-santos
body: Unable to validate the MassID uniqueness
created_at: 2025-03-28T16:59:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,8 @@
+import { BaseProcessorErrors } from '@carrot-fndn/shared/methodologies/bold/processors';
+
+export class UniquenessCheckProcessorErrors extends BaseProcessorErrors {
+  override readonly ERROR_MESSAGE = {
+    DOCUMENT_TYPE_NOT_FOUND: 'Document type not found.',
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts
user: login: cris-santos
body: when is this error used? maybe we could specify which document type was not found
created_at: 2025-03-28T17:00:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import { isNil } from '@carrot-fndn/shared/helpers';
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_OLD,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE } = DocumentEventActorType;
+
+const LICENSE_PLATE_REGEX = '^IO\\*{3}1\\s*$';
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: cris-santos
body: shouldn't this regex be generic? it seems to be specific
created_at: 2025-03-28T17:01:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import { isNil } from '@carrot-fndn/shared/helpers';
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_OLD,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE } = DocumentEventActorType;
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: cris-santos
body: to make it consistent, the SOURCE could also be SOURCE_ACTOR_TYPE
created_at: 2025-03-28T17:01:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import { isNil } from '@carrot-fndn/shared/helpers';
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_OLD,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE } = DocumentEventActorType;
+
+const LICENSE_PLATE_REGEX = '^IO\\*{3}1\\s*$';
+
+export type RequiredEvents = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const findRequiredEvents = (
+  document: Document,
+): RequiredEvents | null => {
+  const pickUpEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([PICK_UP]),
+  );
+  const dropOffEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([DROP_OFF]),
+  );
+  const wasteGeneratorEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([WASTE_GENERATOR])),
+  );
+  const recyclerEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([RECYCLER])),
+  );
+
+  if (
+    isNil(pickUpEvent) ||
+    isNil(dropOffEvent) ||
+    isNil(wasteGeneratorEvent) ||
+    isNil(recyclerEvent)
+  ) {
+    return null;
+  }
+
+  return {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    wasteGeneratorEvent,
+  };
+};
+
+export const createNewFormatQuery = (
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: cris-santos
body: instead of newFormat and oldFormat, we could call it "versions", ex: `mapMassIdV1Query`, `mapMassIdV2Query`
created_at: 2025-03-28T17:03:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import { isNil } from '@carrot-fndn/shared/helpers';
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_OLD,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE } = DocumentEventActorType;
+
+const LICENSE_PLATE_REGEX = '^IO\\*{3}1\\s*$';
+
+export type RequiredEvents = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const findRequiredEvents = (
+  document: Document,
+): RequiredEvents | null => {
+  const pickUpEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([PICK_UP]),
+  );
+  const dropOffEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([DROP_OFF]),
+  );
+  const wasteGeneratorEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([WASTE_GENERATOR])),
+  );
+  const recyclerEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([RECYCLER])),
+  );
+
+  if (
+    isNil(pickUpEvent) ||
+    isNil(dropOffEvent) ||
+    isNil(wasteGeneratorEvent) ||
+    isNil(recyclerEvent)
+  ) {
+    return null;
+  }
+
+  return {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    wasteGeneratorEvent,
+  };
+};
+
+export const createNewFormatQuery = (
+  document: Document,
+  events: RequiredEvents,
+) => {
+  const { dropOffEvent, pickUpEvent, recyclerEvent, wasteGeneratorEvent } =
+    events;
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: VEHICLE_LICENSE_PLATE,
+                  value: { $options: 'i', $regex: LICENSE_PLATE_REGEX },
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: cris-santos
body: the regex should match the document vehicle license plate
created_at: 2025-03-28T17:03:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import { isNil } from '@carrot-fndn/shared/helpers';
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_OLD,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE } = DocumentEventActorType;
+
+const LICENSE_PLATE_REGEX = '^IO\\*{3}1\\s*$';
+
+export type RequiredEvents = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const findRequiredEvents = (
+  document: Document,
+): RequiredEvents | null => {
+  const pickUpEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([PICK_UP]),
+  );
+  const dropOffEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([DROP_OFF]),
+  );
+  const wasteGeneratorEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([WASTE_GENERATOR])),
+  );
+  const recyclerEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([RECYCLER])),
+  );
+
+  if (
+    isNil(pickUpEvent) ||
+    isNil(dropOffEvent) ||
+    isNil(wasteGeneratorEvent) ||
+    isNil(recyclerEvent)
+  ) {
+    return null;
+  }
+
+  return {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    wasteGeneratorEvent,
+  };
+};
+
+export const createNewFormatQuery = (
+  document: Document,
+  events: RequiredEvents,
+) => {
+  const { dropOffEvent, pickUpEvent, recyclerEvent, wasteGeneratorEvent } =
+    events;
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: VEHICLE_LICENSE_PLATE,
+                  value: { $options: 'i', $regex: LICENSE_PLATE_REGEX },
+                },
+              },
+              name: PICK_UP,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': dropOffEvent.address.id,
+              externalCreatedAt: dropOffEvent.externalCreatedAt,
+              name: DROP_OFF,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              label: WASTE_GENERATOR,
+              name: ACTOR,
+              'participant.id': wasteGeneratorEvent.participant.id,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              label: RECYCLER,
+              name: ACTOR,
+              'participant.id': recyclerEvent.participant.id,
+            },
+          },
+        },
+      ],
+      category: document.category,
+      currentValue: document.currentValue,
+      dataSetName: document.dataSetName,
+      subtype: document.subtype,
+      type: document.type,
+    },
+  };
+};
+
+export const createOldFormatQuery = (
+  document: Document,
+  events: RequiredEvents,
+) => {
+  const { dropOffEvent, pickUpEvent, recyclerEvent, wasteGeneratorEvent } =
+    events;
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: VEHICLE_LICENSE_PLATE_OLD,
+                  value: { $options: 'i', $regex: LICENSE_PLATE_REGEX },
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: cris-santos
body: same as above
created_at: 2025-03-28T17:04:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import { isNil } from '@carrot-fndn/shared/helpers';
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_OLD,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE } = DocumentEventActorType;
+
+const LICENSE_PLATE_REGEX = '^IO\\*{3}1\\s*$';
+
+export type RequiredEvents = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const findRequiredEvents = (
+  document: Document,
+): RequiredEvents | null => {
+  const pickUpEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([PICK_UP]),
+  );
+  const dropOffEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([DROP_OFF]),
+  );
+  const wasteGeneratorEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([WASTE_GENERATOR])),
+  );
+  const recyclerEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([RECYCLER])),
+  );
+
+  if (
+    isNil(pickUpEvent) ||
+    isNil(dropOffEvent) ||
+    isNil(wasteGeneratorEvent) ||
+    isNil(recyclerEvent)
+  ) {
+    return null;
+  }
+
+  return {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    wasteGeneratorEvent,
+  };
+};
+
+export const createNewFormatQuery = (
+  document: Document,
+  events: RequiredEvents,
+) => {
+  const { dropOffEvent, pickUpEvent, recyclerEvent, wasteGeneratorEvent } =
+    events;
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: VEHICLE_LICENSE_PLATE,
+                  value: { $options: 'i', $regex: LICENSE_PLATE_REGEX },
+                },
+              },
+              name: PICK_UP,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': dropOffEvent.address.id,
+              externalCreatedAt: dropOffEvent.externalCreatedAt,
+              name: DROP_OFF,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              label: WASTE_GENERATOR,
+              name: ACTOR,
+              'participant.id': wasteGeneratorEvent.participant.id,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              label: RECYCLER,
+              name: ACTOR,
+              'participant.id': recyclerEvent.participant.id,
+            },
+          },
+        },
+      ],
+      category: document.category,
+      currentValue: document.currentValue,
+      dataSetName: document.dataSetName,
+      subtype: document.subtype,
+      type: document.type,
+    },
+  };
+};
+
+export const createOldFormatQuery = (
+  document: Document,
+  events: RequiredEvents,
+) => {
+  const { dropOffEvent, pickUpEvent, recyclerEvent, wasteGeneratorEvent } =
+    events;
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: VEHICLE_LICENSE_PLATE_OLD,
+                  value: { $options: 'i', $regex: LICENSE_PLATE_REGEX },
+                },
+              },
+              name: OPEN,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': dropOffEvent.address.id,
+              externalCreatedAt: dropOffEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: MOVE_TYPE,
+                  value: DROP_OFF_MOVE_TYPE,
+                },
+              },
+              name: MOVE,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: ACTOR_TYPE,
+                  value: SOURCE,
+                },
+              },
+              name: ACTOR,
+              'participant.id': wasteGeneratorEvent.participant.id,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: ACTOR_TYPE,
+                  value: RECYCLER_ACTOR_TYPE,
+                },
+              },
+              name: ACTOR,
+              'participant.id': recyclerEvent.participant.id,
+            },
+          },
+        },
+      ],
+      category: DocumentCategory.MASS,
+      currentValue: document.currentValue,
+      dataSetName: document.dataSetName,
+      subtype: document.subtype,
+      type: document.type,
+    },
+  };
+};
+
+export const fetchSimilarMassIdDocuments = async (
+  document: Document,
+  apiService: AuditApiService,
+): Promise<ApiDocumentCheckDuplicatesResponse[]> => {
+  const events = findRequiredEvents(document);
+
+  if (!events) {
+    return [];
+  }
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: cris-santos
body: shouldn't throw an error if the events were not found?
created_at: 2025-03-28T17:05:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import { isNil } from '@carrot-fndn/shared/helpers';
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_OLD,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE } = DocumentEventActorType;
+
+const LICENSE_PLATE_REGEX = '^IO\\*{3}1\\s*$';
+
+export type RequiredEvents = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const findRequiredEvents = (
+  document: Document,
+): RequiredEvents | null => {
+  const pickUpEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([PICK_UP]),
+  );
+  const dropOffEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([DROP_OFF]),
+  );
+  const wasteGeneratorEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([WASTE_GENERATOR])),
+  );
+  const recyclerEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([RECYCLER])),
+  );
+
+  if (
+    isNil(pickUpEvent) ||
+    isNil(dropOffEvent) ||
+    isNil(wasteGeneratorEvent) ||
+    isNil(recyclerEvent)
+  ) {
+    return null;
+  }
+
+  return {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    wasteGeneratorEvent,
+  };
+};
+
+export const createNewFormatQuery = (
+  document: Document,
+  events: RequiredEvents,
+) => {
+  const { dropOffEvent, pickUpEvent, recyclerEvent, wasteGeneratorEvent } =
+    events;
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: VEHICLE_LICENSE_PLATE,
+                  value: { $options: 'i', $regex: LICENSE_PLATE_REGEX },
+                },
+              },
+              name: PICK_UP,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': dropOffEvent.address.id,
+              externalCreatedAt: dropOffEvent.externalCreatedAt,
+              name: DROP_OFF,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              label: WASTE_GENERATOR,
+              name: ACTOR,
+              'participant.id': wasteGeneratorEvent.participant.id,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              label: RECYCLER,
+              name: ACTOR,
+              'participant.id': recyclerEvent.participant.id,
+            },
+          },
+        },
+      ],
+      category: document.category,
+      currentValue: document.currentValue,
+      dataSetName: document.dataSetName,
+      subtype: document.subtype,
+      type: document.type,
+    },
+  };
+};
+
+export const createOldFormatQuery = (
+  document: Document,
+  events: RequiredEvents,
+) => {
+  const { dropOffEvent, pickUpEvent, recyclerEvent, wasteGeneratorEvent } =
+    events;
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: VEHICLE_LICENSE_PLATE_OLD,
+                  value: { $options: 'i', $regex: LICENSE_PLATE_REGEX },
+                },
+              },
+              name: OPEN,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': dropOffEvent.address.id,
+              externalCreatedAt: dropOffEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: MOVE_TYPE,
+                  value: DROP_OFF_MOVE_TYPE,
+                },
+              },
+              name: MOVE,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: ACTOR_TYPE,
+                  value: SOURCE,
+                },
+              },
+              name: ACTOR,
+              'participant.id': wasteGeneratorEvent.participant.id,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: ACTOR_TYPE,
+                  value: RECYCLER_ACTOR_TYPE,
+                },
+              },
+              name: ACTOR,
+              'participant.id': recyclerEvent.participant.id,
+            },
+          },
+        },
+      ],
+      category: DocumentCategory.MASS,
+      currentValue: document.currentValue,
+      dataSetName: document.dataSetName,
+      subtype: document.subtype,
+      type: document.type,
+    },
+  };
+};
+
+export const fetchSimilarMassIdDocuments = async (
+  document: Document,
+  apiService: AuditApiService,
+): Promise<ApiDocumentCheckDuplicatesResponse[]> => {
+  const events = findRequiredEvents(document);
+
+  if (!events) {
+    return [];
+  }
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/2b1cc6fc94ea5f130a85f8eafe472be280be52cc
created_at: 2025-03-31T19:49:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import { isNil } from '@carrot-fndn/shared/helpers';
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_OLD,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE } = DocumentEventActorType;
+
+const LICENSE_PLATE_REGEX = '^IO\\*{3}1\\s*$';
+
+export type RequiredEvents = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const findRequiredEvents = (
+  document: Document,
+): RequiredEvents | null => {
+  const pickUpEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([PICK_UP]),
+  );
+  const dropOffEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([DROP_OFF]),
+  );
+  const wasteGeneratorEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([WASTE_GENERATOR])),
+  );
+  const recyclerEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([RECYCLER])),
+  );
+
+  if (
+    isNil(pickUpEvent) ||
+    isNil(dropOffEvent) ||
+    isNil(wasteGeneratorEvent) ||
+    isNil(recyclerEvent)
+  ) {
+    return null;
+  }
+
+  return {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    wasteGeneratorEvent,
+  };
+};
+
+export const createNewFormatQuery = (
+  document: Document,
+  events: RequiredEvents,
+) => {
+  const { dropOffEvent, pickUpEvent, recyclerEvent, wasteGeneratorEvent } =
+    events;
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: VEHICLE_LICENSE_PLATE,
+                  value: { $options: 'i', $regex: LICENSE_PLATE_REGEX },
+                },
+              },
+              name: PICK_UP,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': dropOffEvent.address.id,
+              externalCreatedAt: dropOffEvent.externalCreatedAt,
+              name: DROP_OFF,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              label: WASTE_GENERATOR,
+              name: ACTOR,
+              'participant.id': wasteGeneratorEvent.participant.id,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              label: RECYCLER,
+              name: ACTOR,
+              'participant.id': recyclerEvent.participant.id,
+            },
+          },
+        },
+      ],
+      category: document.category,
+      currentValue: document.currentValue,
+      dataSetName: document.dataSetName,
+      subtype: document.subtype,
+      type: document.type,
+    },
+  };
+};
+
+export const createOldFormatQuery = (
+  document: Document,
+  events: RequiredEvents,
+) => {
+  const { dropOffEvent, pickUpEvent, recyclerEvent, wasteGeneratorEvent } =
+    events;
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: VEHICLE_LICENSE_PLATE_OLD,
+                  value: { $options: 'i', $regex: LICENSE_PLATE_REGEX },
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/2b1cc6fc94ea5f130a85f8eafe472be280be52cc
created_at: 2025-03-31T19:49:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import { isNil } from '@carrot-fndn/shared/helpers';
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_OLD,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE } = DocumentEventActorType;
+
+const LICENSE_PLATE_REGEX = '^IO\\*{3}1\\s*$';
+
+export type RequiredEvents = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const findRequiredEvents = (
+  document: Document,
+): RequiredEvents | null => {
+  const pickUpEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([PICK_UP]),
+  );
+  const dropOffEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([DROP_OFF]),
+  );
+  const wasteGeneratorEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([WASTE_GENERATOR])),
+  );
+  const recyclerEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([RECYCLER])),
+  );
+
+  if (
+    isNil(pickUpEvent) ||
+    isNil(dropOffEvent) ||
+    isNil(wasteGeneratorEvent) ||
+    isNil(recyclerEvent)
+  ) {
+    return null;
+  }
+
+  return {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    wasteGeneratorEvent,
+  };
+};
+
+export const createNewFormatQuery = (
+  document: Document,
+  events: RequiredEvents,
+) => {
+  const { dropOffEvent, pickUpEvent, recyclerEvent, wasteGeneratorEvent } =
+    events;
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: VEHICLE_LICENSE_PLATE,
+                  value: { $options: 'i', $regex: LICENSE_PLATE_REGEX },
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/2b1cc6fc94ea5f130a85f8eafe472be280be52cc
created_at: 2025-03-31T19:49:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import { isNil } from '@carrot-fndn/shared/helpers';
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_OLD,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE } = DocumentEventActorType;
+
+const LICENSE_PLATE_REGEX = '^IO\\*{3}1\\s*$';
+
+export type RequiredEvents = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const findRequiredEvents = (
+  document: Document,
+): RequiredEvents | null => {
+  const pickUpEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([PICK_UP]),
+  );
+  const dropOffEvent = document.externalEvents?.find(
+    eventNameIsAnyOf([DROP_OFF]),
+  );
+  const wasteGeneratorEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([WASTE_GENERATOR])),
+  );
+  const recyclerEvent = document.externalEvents?.find(
+    and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([RECYCLER])),
+  );
+
+  if (
+    isNil(pickUpEvent) ||
+    isNil(dropOffEvent) ||
+    isNil(wasteGeneratorEvent) ||
+    isNil(recyclerEvent)
+  ) {
+    return null;
+  }
+
+  return {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    wasteGeneratorEvent,
+  };
+};
+
+export const createNewFormatQuery = (
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/2b1cc6fc94ea5f130a85f8eafe472be280be52cc
created_at: 2025-03-31T19:49:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import { isNil } from '@carrot-fndn/shared/helpers';
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_OLD,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE } = DocumentEventActorType;
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/2b1cc6fc94ea5f130a85f8eafe472be280be52cc
created_at: 2025-03-31T19:49:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import { isNil } from '@carrot-fndn/shared/helpers';
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_OLD,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE } = DocumentEventActorType;
+
+const LICENSE_PLATE_REGEX = '^IO\\*{3}1\\s*$';
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/2b1cc6fc94ea5f130a85f8eafe472be280be52cc
created_at: 2025-03-31T19:49:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,8 @@
+import { BaseProcessorErrors } from '@carrot-fndn/shared/methodologies/bold/processors';
+
+export class UniquenessCheckProcessorErrors extends BaseProcessorErrors {
+  override readonly ERROR_MESSAGE = {
+    DOCUMENT_TYPE_NOT_FOUND: 'Document type not found.',
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/2b1cc6fc94ea5f130a85f8eafe472be280be52cc
created_at: 2025-03-31T19:49:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,8 @@
+import { BaseProcessorErrors } from '@carrot-fndn/shared/methodologies/bold/processors';
+
+export class UniquenessCheckProcessorErrors extends BaseProcessorErrors {
+  override readonly ERROR_MESSAGE = {
+    DOCUMENT_TYPE_NOT_FOUND: 'Document type not found.',
+    REJECTED_BY_ERROR: 'Unable to validate the uniqueness-check.',
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/2b1cc6fc94ea5f130a85f8eafe472be280be52cc
created_at: 2025-03-31T19:50:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,274 @@
+import type { EvaluateResultOutput } from '@carrot-fndn/shared/rule/standard-data-processor';
+
+import { RuleDataProcessor } from '@carrot-fndn/shared/app/types';
+import { provideDocumentLoaderService } from '@carrot-fndn/shared/document/loader';
+import {
+  getOrUndefined,
+  isNil,
+  isNonEmptyString,
+} from '@carrot-fndn/shared/helpers';
+import { getEventAttributeValue } from '@carrot-fndn/shared/methodologies/bold/getters';
+import {
+  type DocumentQuery,
+  DocumentQueryService,
+} from '@carrot-fndn/shared/methodologies/bold/io-helpers';
+import { MASS_ID } from '@carrot-fndn/shared/methodologies/bold/matchers';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  type DocumentEvent,
+  DocumentEventAttributeName,
+  DocumentEventName,
+  DocumentStatus,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { mapDocumentReference } from '@carrot-fndn/shared/methodologies/bold/utils';
+import { mapToRuleOutput } from '@carrot-fndn/shared/rule/result';
+import {
+  type RuleInput,
+  type RuleOutput,
+  RuleOutputStatus,
+} from '@carrot-fndn/shared/rule/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+import { UniquenessCheckProcessorErrors } from './uniqueness-check.errors';
+import {
+  type EventsData,
+  createAuditApiService,
+  fetchSimilarMassIdDocuments,
+} from './uniqueness-check.helpers';
+
+const { ACTOR, DROP_OFF, PICK_UP } = DocumentEventName;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V1 } =
+  DocumentEventAttributeName;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V2 } =
+  NewDocumentEventAttributeName;
+
+export const RESULT_COMMENTS = {
+  NO_DUPLICATES_FOUND:
+    'No other documents with the same attributes were found.',
+  ONLY_CANCELLED_DUPLICATES: (
+    totalDuplicates: number,
+    cancelledCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, but all are cancelled (${cancelledCount}).`,
+  VALID_DUPLICATE_FOUND: (
+    totalDuplicates: number,
+    validDuplicatesCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, of which ${validDuplicatesCount} are not cancelled.`,
+} as const;
+
+interface RuleSubject {
+  cancelledCount: number;
+  totalDuplicates: number;
+  validDuplicatesCount: number;
+}
+
+export class UniquenessCheckProcessor extends RuleDataProcessor {
+  protected readonly errorProcessor = new UniquenessCheckProcessorErrors();
+
+  private getDropOffEvent(document: Document): DocumentEvent {
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts
user: login: sangalli
body: We can create a helper such as `getEventOrThrow(document: Document, eventPredicate: <a type I don't remember>, notFoundErrorMessage: NonEmptyString)`
created_at: 2025-03-31T20:18:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,274 @@
+import type { EvaluateResultOutput } from '@carrot-fndn/shared/rule/standard-data-processor';
+
+import { RuleDataProcessor } from '@carrot-fndn/shared/app/types';
+import { provideDocumentLoaderService } from '@carrot-fndn/shared/document/loader';
+import {
+  getOrUndefined,
+  isNil,
+  isNonEmptyString,
+} from '@carrot-fndn/shared/helpers';
+import { getEventAttributeValue } from '@carrot-fndn/shared/methodologies/bold/getters';
+import {
+  type DocumentQuery,
+  DocumentQueryService,
+} from '@carrot-fndn/shared/methodologies/bold/io-helpers';
+import { MASS_ID } from '@carrot-fndn/shared/methodologies/bold/matchers';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  type DocumentEvent,
+  DocumentEventAttributeName,
+  DocumentEventName,
+  DocumentStatus,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { mapDocumentReference } from '@carrot-fndn/shared/methodologies/bold/utils';
+import { mapToRuleOutput } from '@carrot-fndn/shared/rule/result';
+import {
+  type RuleInput,
+  type RuleOutput,
+  RuleOutputStatus,
+} from '@carrot-fndn/shared/rule/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+import { UniquenessCheckProcessorErrors } from './uniqueness-check.errors';
+import {
+  type EventsData,
+  createAuditApiService,
+  fetchSimilarMassIdDocuments,
+} from './uniqueness-check.helpers';
+
+const { ACTOR, DROP_OFF, PICK_UP } = DocumentEventName;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V1 } =
+  DocumentEventAttributeName;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V2 } =
+  NewDocumentEventAttributeName;
+
+export const RESULT_COMMENTS = {
+  NO_DUPLICATES_FOUND:
+    'No other documents with the same attributes were found.',
+  ONLY_CANCELLED_DUPLICATES: (
+    totalDuplicates: number,
+    cancelledCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, but all are cancelled (${cancelledCount}).`,
+  VALID_DUPLICATE_FOUND: (
+    totalDuplicates: number,
+    validDuplicatesCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, of which ${validDuplicatesCount} are not cancelled.`,
+} as const;
+
+interface RuleSubject {
+  cancelledCount: number;
+  totalDuplicates: number;
+  validDuplicatesCount: number;
+}
+
+export class UniquenessCheckProcessor extends RuleDataProcessor {
+  protected readonly errorProcessor = new UniquenessCheckProcessorErrors();
+
+  private getDropOffEvent(document: Document): DocumentEvent {
+    const dropOffEvent = document.externalEvents?.find(
+      eventNameIsAnyOf([DROP_OFF]),
+    );
+
+    if (isNil(dropOffEvent)) {
+      throw this.errorProcessor.getKnownError(
+        this.errorProcessor.ERROR_MESSAGE.MISSING_DROP_OFF_EVENT,
+      );
+    }
+
+    return dropOffEvent;
+  }
+
+  private async getMassIdDocument(
+    documentQuery: DocumentQuery<Document>,
+  ): Promise<Document> {
+    let massIdDocument: Document | undefined;
+
+    await documentQuery.iterator().each(({ document }) => {
+      const documentReference = mapDocumentReference(document);
+
+      if (MASS_ID.matches(documentReference)) {
+        massIdDocument = document;
+      }
+    });
+
+    if (isNil(massIdDocument)) {
+      throw this.errorProcessor.getKnownError(
+        this.errorProcessor.ERROR_MESSAGE.MASS_DOCUMENT_NOT_FOUND,
+      );
+    }
+
+    return massIdDocument;
+  }
+
+  private getPickUpEvent(document: Document): DocumentEvent {
+    const pickUpEvent = document.externalEvents?.find(
+      eventNameIsAnyOf([PICK_UP]),
+    );
+
+    if (isNil(pickUpEvent)) {
+      throw this.errorProcessor.getKnownError(
+        this.errorProcessor.ERROR_MESSAGE.MISSING_PICK_UP_EVENT,
+      );
+    }
+
+    return pickUpEvent;
+  }
+
+  private getRecyclerEvent(document: Document): DocumentEvent {
+    const recyclerEvent = document.externalEvents?.find(
+      and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([RECYCLER])),
+    );
+
+    if (isNil(recyclerEvent)) {
+      throw this.errorProcessor.getKnownError(
+        this.errorProcessor.ERROR_MESSAGE.MISSING_RECYCLER_EVENT,
+      );
+    }
+
+    return recyclerEvent;
+  }
+
+  private getVehicleLicensePlate(pickUpEvent: DocumentEvent): NonEmptyString {
+    const vehicleLicensePlateV1 = getEventAttributeValue(
+      pickUpEvent,
+      VEHICLE_LICENSE_PLATE_V1,
+    );
+
+    if (isNonEmptyString(vehicleLicensePlateV1)) {
+      return vehicleLicensePlateV1;
+    }
+
+    const vehicleLicensePlateV2 = getEventAttributeValue(
+      pickUpEvent,
+      VEHICLE_LICENSE_PLATE_V2,
+    );
+
+    if (isNonEmptyString(vehicleLicensePlateV2)) {
+      return vehicleLicensePlateV2;
+    }
+
+    throw this.errorProcessor.getKnownError(
+      this.errorProcessor.ERROR_MESSAGE.MISSING_VEHICLE_LICENSE_PLATE,
+    );
+  }
+
+  private getWasteGeneratorEvent(document: Document): DocumentEvent {
+    const wasteGeneratorEvent = document.externalEvents?.find(
+      and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([WASTE_GENERATOR])),
+    );
+
+    if (isNil(wasteGeneratorEvent)) {
+      throw this.errorProcessor.getKnownError(
+        this.errorProcessor.ERROR_MESSAGE.MISSING_WASTE_GENERATOR_EVENT,
+      );
+    }
+
+    return wasteGeneratorEvent;
+  }
+
+  protected evaluateResult({
+    cancelledCount,
+    totalDuplicates,
+    validDuplicatesCount,
+  }: RuleSubject): EvaluateResultOutput {
+    if (validDuplicatesCount > 0) {
+      return {
+        resultComment: RESULT_COMMENTS.VALID_DUPLICATE_FOUND(
+          totalDuplicates,
+          validDuplicatesCount,
+        ),
+        resultStatus: RuleOutputStatus.REJECTED,
+      };
+    }
+
+    if (cancelledCount > 0) {
+      return {
+        resultComment: RESULT_COMMENTS.ONLY_CANCELLED_DUPLICATES(
+          totalDuplicates,
+          cancelledCount,
+        ),
+        resultStatus: RuleOutputStatus.APPROVED,
+      };
+    }
+
+    return {
+      resultComment: RESULT_COMMENTS.NO_DUPLICATES_FOUND,
+      resultStatus: RuleOutputStatus.APPROVED,
+    };
+  }
+
+  protected async generateDocumentQuery(ruleInput: RuleInput) {
+    const documentQueryService = new DocumentQueryService(
+      provideDocumentLoaderService,
+    );
+
+    return documentQueryService.load({
+      context: {
+        s3KeyPrefix: ruleInput.documentKeyPrefix,
+      },
+      criteria: {
+        parentDocument: {},
+      },
+      documentId: ruleInput.documentId,
+    });
+  }
+
+  protected async getRuleSubject(document: Document): Promise<RuleSubject> {
+    const eventsData: EventsData = {
+      dropOffEvent: this.getDropOffEvent(document),
+      pickUpEvent: this.getPickUpEvent(document),
+      recyclerEvent: this.getRecyclerEvent(document),
+      vehicleLicensePlate: this.getVehicleLicensePlate(
+        this.getPickUpEvent(document),
+      ),
+      wasteGeneratorEvent: this.getWasteGeneratorEvent(document),
+    };
+
+    const duplicateDocuments = await fetchSimilarMassIdDocuments({
+      auditApiService: createAuditApiService(),
+      document,
+      eventsData,
+    });
+
+    const cancelledCount = duplicateDocuments.filter(
+      (duplicateDocument) =>
+        duplicateDocument.status === DocumentStatus.CANCELLED,
+    ).length;
+
+    return {
+      cancelledCount,
+      totalDuplicates: duplicateDocuments.length,
+      validDuplicatesCount: duplicateDocuments.length - cancelledCount,
+    };
+  }
+
+  async process(ruleInput: RuleInput): Promise<RuleOutput> {
+    try {
+      const documentsQuery = await this.generateDocumentQuery(ruleInput);
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts
user: login: sangalli
body: Just a reminder that as we are getting only the MassId, there is no need to use the document query if you don't want to. I think there is a base class already implemented to fetch the parent document, or you can only fetch the parent directly.
created_at: 2025-03-31T20:20:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,223 @@
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: OLD_VEHICLE_LICENSE_PLATE,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE: SOURCE_ACTOR_TYPE } =
+  DocumentEventActorType;
+
+export type EventsData = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  vehicleLicensePlate: NonEmptyString;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+export const createLicensePlateRegex = (
+  licensePlate: NonEmptyString,
+): string => {
+  const escapedValue = licensePlate
+    .replaceAll(/\s+/g, '')
+    .replaceAll(/[$()*+./?[\\\]^{|}-]/g, '\\$&');
+
+  return `^${escapedValue}$`;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const mapMassIdV2Query = (
+  document: Document,
+  requiredData: EventsData,
+) => {
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: sangalli
body: To avoid edit errors in the future, is there a return type we can declare here?
created_at: 2025-03-31T20:24:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,223 @@
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: OLD_VEHICLE_LICENSE_PLATE,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE: SOURCE_ACTOR_TYPE } =
+  DocumentEventActorType;
+
+export type EventsData = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  vehicleLicensePlate: NonEmptyString;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+export const createLicensePlateRegex = (
+  licensePlate: NonEmptyString,
+): string => {
+  const escapedValue = licensePlate
+    .replaceAll(/\s+/g, '')
+    .replaceAll(/[$()*+./?[\\\]^{|}-]/g, '\\$&');
+
+  return `^${escapedValue}$`;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const mapMassIdV2Query = (
+  document: Document,
+  requiredData: EventsData,
+) => {
+  const {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    vehicleLicensePlate,
+    wasteGeneratorEvent,
+  } = requiredData;
+
+  const licensePlateRegex = createLicensePlateRegex(vehicleLicensePlate);
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: VEHICLE_LICENSE_PLATE,
+                  value: { $options: 'i', $regex: licensePlateRegex },
+                },
+              },
+              name: PICK_UP,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': dropOffEvent.address.id,
+              externalCreatedAt: dropOffEvent.externalCreatedAt,
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: sangalli
body: I'm wondering if a system error could generate the same massID with a few seconds or minutes away. It does not seem physically possible to have multiple masses with the same weight, generator and recycler in a short period, maybe 30 minutes. What do you think of checking a range of time in these externalCreatedAt matches?
created_at: 2025-03-31T20:36:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,223 @@
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: OLD_VEHICLE_LICENSE_PLATE,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE: SOURCE_ACTOR_TYPE } =
+  DocumentEventActorType;
+
+export type EventsData = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  vehicleLicensePlate: NonEmptyString;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+export const createLicensePlateRegex = (
+  licensePlate: NonEmptyString,
+): string => {
+  const escapedValue = licensePlate
+    .replaceAll(/\s+/g, '')
+    .replaceAll(/[$()*+./?[\\\]^{|}-]/g, '\\$&');
+
+  return `^${escapedValue}$`;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const mapMassIdV2Query = (
+  document: Document,
+  requiredData: EventsData,
+) => {
+  const {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    vehicleLicensePlate,
+    wasteGeneratorEvent,
+  } = requiredData;
+
+  const licensePlateRegex = createLicensePlateRegex(vehicleLicensePlate);
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: VEHICLE_LICENSE_PLATE,
+                  value: { $options: 'i', $regex: licensePlateRegex },
+                },
+              },
+              name: PICK_UP,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': dropOffEvent.address.id,
+              externalCreatedAt: dropOffEvent.externalCreatedAt,
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: sangalli
body: Having talked offline with you, I agree that it is too soon to implement something more flexible. We should look into this again in the future.
created_at: 2025-03-31T20:45:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,177 @@
+import type { AuditApiService } from '@carrot-fndn/shared/methodologies/audit-api';
+
+import {
+  BoldStubsBuilder,
+  stubBoldMassIdDropOffEvent,
+  stubBoldMassIdPickUpEvent,
+  stubDocumentEvent,
+} from '@carrot-fndn/shared/methodologies/bold/testing';
+import { DocumentCategory } from '@carrot-fndn/shared/methodologies/bold/types';
+import { type NonEmptyString } from '@carrot-fndn/shared/types';
+
+import * as helpers from './uniqueness-check.helpers';
+
+const { MASS, MASS_ID } = DocumentCategory;
+
+const mockCheckDuplicateDocuments = jest.fn();
+const mockAuditApiService = {
+  checkDuplicateDocuments: mockCheckDuplicateDocuments,
+};
+
+// eslint-disable-next-line @typescript-eslint/no-unsafe-return
+jest.mock('./uniqueness-check.helpers', () => ({
+  ...jest.requireActual('./uniqueness-check.helpers'),
+  createAuditApiService: () => mockAuditApiService,
+}));
+
+describe('uniqueness-check.helpers', () => {
+  afterEach(() => {
+    jest.clearAllMocks();
+    mockCheckDuplicateDocuments.mockReset();
+  });
+
+  describe('mapMassIdV2Query', () => {
+    it('should create a valid query object with proper structure', () => {
+      const { massIdDocument } = new BoldStubsBuilder()
+        .createMassIdDocument()
+        .build();
+
+      const eventsData: helpers.EventsData = {
+        dropOffEvent: stubBoldMassIdDropOffEvent(),
+        pickUpEvent: stubBoldMassIdPickUpEvent(),
+        recyclerEvent: stubDocumentEvent(),
+        vehicleLicensePlate: 'ABC123',
+        wasteGeneratorEvent: stubDocumentEvent(),
+      };
+
+      const query = helpers.mapMassIdV2Query(massIdDocument, eventsData);
+
+      expect(query).toHaveProperty('match.$and');
+      expect(query.match.$and).toBeInstanceOf(Array);
+      expect(query.match.$and).toHaveLength(4);
+      expect(query.match).toHaveProperty('category', MASS_ID);
+      expect(query.match).toHaveProperty('type', massIdDocument.type);
+      expect(query.match).toHaveProperty('subtype', massIdDocument.subtype);
+    });
+  });
+
+  describe('createOldFormatQuery', () => {
+    it('should create a valid query object with proper structure', () => {
+      const { massIdDocument } = new BoldStubsBuilder()
+        .createMassIdDocument()
+        .build();
+
+      const eventsData: helpers.EventsData = {
+        dropOffEvent: stubBoldMassIdDropOffEvent(),
+        pickUpEvent: stubBoldMassIdPickUpEvent(),
+        recyclerEvent: stubDocumentEvent(),
+        vehicleLicensePlate: 'ABC123',
+        wasteGeneratorEvent: stubDocumentEvent(),
+      };
+
+      const query = helpers.mapMassIdV1Query(massIdDocument, eventsData);
+
+      expect(query).toHaveProperty('match.$and');
+      expect(query.match.category).toBe(MASS);
+      expect(query.match.$and).toBeInstanceOf(Array);
+      expect(query.match.$and).toHaveLength(4);
+    });
+  });
+
+  describe('fetchSimilarMassIdDocumentsImpl', () => {
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.spec.ts
user: login: sangalli
body: ```suggestion
  describe('fetchSimilarMassIdDocuments', () => {
```
created_at: 2025-03-31T20:47:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,177 @@
+import type { AuditApiService } from '@carrot-fndn/shared/methodologies/audit-api';
+
+import {
+  BoldStubsBuilder,
+  stubBoldMassIdDropOffEvent,
+  stubBoldMassIdPickUpEvent,
+  stubDocumentEvent,
+} from '@carrot-fndn/shared/methodologies/bold/testing';
+import { DocumentCategory } from '@carrot-fndn/shared/methodologies/bold/types';
+import { type NonEmptyString } from '@carrot-fndn/shared/types';
+
+import * as helpers from './uniqueness-check.helpers';
+
+const { MASS, MASS_ID } = DocumentCategory;
+
+const mockCheckDuplicateDocuments = jest.fn();
+const mockAuditApiService = {
+  checkDuplicateDocuments: mockCheckDuplicateDocuments,
+};
+
+// eslint-disable-next-line @typescript-eslint/no-unsafe-return
+jest.mock('./uniqueness-check.helpers', () => ({
+  ...jest.requireActual('./uniqueness-check.helpers'),
+  createAuditApiService: () => mockAuditApiService,
+}));
+
+describe('uniqueness-check.helpers', () => {
+  afterEach(() => {
+    jest.clearAllMocks();
+    mockCheckDuplicateDocuments.mockReset();
+  });
+
+  describe('mapMassIdV2Query', () => {
+    it('should create a valid query object with proper structure', () => {
+      const { massIdDocument } = new BoldStubsBuilder()
+        .createMassIdDocument()
+        .build();
+
+      const eventsData: helpers.EventsData = {
+        dropOffEvent: stubBoldMassIdDropOffEvent(),
+        pickUpEvent: stubBoldMassIdPickUpEvent(),
+        recyclerEvent: stubDocumentEvent(),
+        vehicleLicensePlate: 'ABC123',
+        wasteGeneratorEvent: stubDocumentEvent(),
+      };
+
+      const query = helpers.mapMassIdV2Query(massIdDocument, eventsData);
+
+      expect(query).toHaveProperty('match.$and');
+      expect(query.match.$and).toBeInstanceOf(Array);
+      expect(query.match.$and).toHaveLength(4);
+      expect(query.match).toHaveProperty('category', MASS_ID);
+      expect(query.match).toHaveProperty('type', massIdDocument.type);
+      expect(query.match).toHaveProperty('subtype', massIdDocument.subtype);
+    });
+  });
+
+  describe('createOldFormatQuery', () => {
+    it('should create a valid query object with proper structure', () => {
+      const { massIdDocument } = new BoldStubsBuilder()
+        .createMassIdDocument()
+        .build();
+
+      const eventsData: helpers.EventsData = {
+        dropOffEvent: stubBoldMassIdDropOffEvent(),
+        pickUpEvent: stubBoldMassIdPickUpEvent(),
+        recyclerEvent: stubDocumentEvent(),
+        vehicleLicensePlate: 'ABC123',
+        wasteGeneratorEvent: stubDocumentEvent(),
+      };
+
+      const query = helpers.mapMassIdV1Query(massIdDocument, eventsData);
+
+      expect(query).toHaveProperty('match.$and');
+      expect(query.match.category).toBe(MASS);
+      expect(query.match.$and).toBeInstanceOf(Array);
+      expect(query.match.$and).toHaveLength(4);
+    });
+  });
+
+  describe('fetchSimilarMassIdDocumentsImpl', () => {
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.spec.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/37240136f764c9b56616fae8b3c09ca50d79fbab
created_at: 2025-04-01T16:51:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,274 @@
+import type { EvaluateResultOutput } from '@carrot-fndn/shared/rule/standard-data-processor';
+
+import { RuleDataProcessor } from '@carrot-fndn/shared/app/types';
+import { provideDocumentLoaderService } from '@carrot-fndn/shared/document/loader';
+import {
+  getOrUndefined,
+  isNil,
+  isNonEmptyString,
+} from '@carrot-fndn/shared/helpers';
+import { getEventAttributeValue } from '@carrot-fndn/shared/methodologies/bold/getters';
+import {
+  type DocumentQuery,
+  DocumentQueryService,
+} from '@carrot-fndn/shared/methodologies/bold/io-helpers';
+import { MASS_ID } from '@carrot-fndn/shared/methodologies/bold/matchers';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  type DocumentEvent,
+  DocumentEventAttributeName,
+  DocumentEventName,
+  DocumentStatus,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { mapDocumentReference } from '@carrot-fndn/shared/methodologies/bold/utils';
+import { mapToRuleOutput } from '@carrot-fndn/shared/rule/result';
+import {
+  type RuleInput,
+  type RuleOutput,
+  RuleOutputStatus,
+} from '@carrot-fndn/shared/rule/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+import { UniquenessCheckProcessorErrors } from './uniqueness-check.errors';
+import {
+  type EventsData,
+  createAuditApiService,
+  fetchSimilarMassIdDocuments,
+} from './uniqueness-check.helpers';
+
+const { ACTOR, DROP_OFF, PICK_UP } = DocumentEventName;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V1 } =
+  DocumentEventAttributeName;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V2 } =
+  NewDocumentEventAttributeName;
+
+export const RESULT_COMMENTS = {
+  NO_DUPLICATES_FOUND:
+    'No other documents with the same attributes were found.',
+  ONLY_CANCELLED_DUPLICATES: (
+    totalDuplicates: number,
+    cancelledCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, but all are cancelled (${cancelledCount}).`,
+  VALID_DUPLICATE_FOUND: (
+    totalDuplicates: number,
+    validDuplicatesCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, of which ${validDuplicatesCount} are not cancelled.`,
+} as const;
+
+interface RuleSubject {
+  cancelledCount: number;
+  totalDuplicates: number;
+  validDuplicatesCount: number;
+}
+
+export class UniquenessCheckProcessor extends RuleDataProcessor {
+  protected readonly errorProcessor = new UniquenessCheckProcessorErrors();
+
+  private getDropOffEvent(document: Document): DocumentEvent {
+    const dropOffEvent = document.externalEvents?.find(
+      eventNameIsAnyOf([DROP_OFF]),
+    );
+
+    if (isNil(dropOffEvent)) {
+      throw this.errorProcessor.getKnownError(
+        this.errorProcessor.ERROR_MESSAGE.MISSING_DROP_OFF_EVENT,
+      );
+    }
+
+    return dropOffEvent;
+  }
+
+  private async getMassIdDocument(
+    documentQuery: DocumentQuery<Document>,
+  ): Promise<Document> {
+    let massIdDocument: Document | undefined;
+
+    await documentQuery.iterator().each(({ document }) => {
+      const documentReference = mapDocumentReference(document);
+
+      if (MASS_ID.matches(documentReference)) {
+        massIdDocument = document;
+      }
+    });
+
+    if (isNil(massIdDocument)) {
+      throw this.errorProcessor.getKnownError(
+        this.errorProcessor.ERROR_MESSAGE.MASS_DOCUMENT_NOT_FOUND,
+      );
+    }
+
+    return massIdDocument;
+  }
+
+  private getPickUpEvent(document: Document): DocumentEvent {
+    const pickUpEvent = document.externalEvents?.find(
+      eventNameIsAnyOf([PICK_UP]),
+    );
+
+    if (isNil(pickUpEvent)) {
+      throw this.errorProcessor.getKnownError(
+        this.errorProcessor.ERROR_MESSAGE.MISSING_PICK_UP_EVENT,
+      );
+    }
+
+    return pickUpEvent;
+  }
+
+  private getRecyclerEvent(document: Document): DocumentEvent {
+    const recyclerEvent = document.externalEvents?.find(
+      and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([RECYCLER])),
+    );
+
+    if (isNil(recyclerEvent)) {
+      throw this.errorProcessor.getKnownError(
+        this.errorProcessor.ERROR_MESSAGE.MISSING_RECYCLER_EVENT,
+      );
+    }
+
+    return recyclerEvent;
+  }
+
+  private getVehicleLicensePlate(pickUpEvent: DocumentEvent): NonEmptyString {
+    const vehicleLicensePlateV1 = getEventAttributeValue(
+      pickUpEvent,
+      VEHICLE_LICENSE_PLATE_V1,
+    );
+
+    if (isNonEmptyString(vehicleLicensePlateV1)) {
+      return vehicleLicensePlateV1;
+    }
+
+    const vehicleLicensePlateV2 = getEventAttributeValue(
+      pickUpEvent,
+      VEHICLE_LICENSE_PLATE_V2,
+    );
+
+    if (isNonEmptyString(vehicleLicensePlateV2)) {
+      return vehicleLicensePlateV2;
+    }
+
+    throw this.errorProcessor.getKnownError(
+      this.errorProcessor.ERROR_MESSAGE.MISSING_VEHICLE_LICENSE_PLATE,
+    );
+  }
+
+  private getWasteGeneratorEvent(document: Document): DocumentEvent {
+    const wasteGeneratorEvent = document.externalEvents?.find(
+      and(eventNameIsAnyOf([ACTOR]), eventLabelIsAnyOf([WASTE_GENERATOR])),
+    );
+
+    if (isNil(wasteGeneratorEvent)) {
+      throw this.errorProcessor.getKnownError(
+        this.errorProcessor.ERROR_MESSAGE.MISSING_WASTE_GENERATOR_EVENT,
+      );
+    }
+
+    return wasteGeneratorEvent;
+  }
+
+  protected evaluateResult({
+    cancelledCount,
+    totalDuplicates,
+    validDuplicatesCount,
+  }: RuleSubject): EvaluateResultOutput {
+    if (validDuplicatesCount > 0) {
+      return {
+        resultComment: RESULT_COMMENTS.VALID_DUPLICATE_FOUND(
+          totalDuplicates,
+          validDuplicatesCount,
+        ),
+        resultStatus: RuleOutputStatus.REJECTED,
+      };
+    }
+
+    if (cancelledCount > 0) {
+      return {
+        resultComment: RESULT_COMMENTS.ONLY_CANCELLED_DUPLICATES(
+          totalDuplicates,
+          cancelledCount,
+        ),
+        resultStatus: RuleOutputStatus.APPROVED,
+      };
+    }
+
+    return {
+      resultComment: RESULT_COMMENTS.NO_DUPLICATES_FOUND,
+      resultStatus: RuleOutputStatus.APPROVED,
+    };
+  }
+
+  protected async generateDocumentQuery(ruleInput: RuleInput) {
+    const documentQueryService = new DocumentQueryService(
+      provideDocumentLoaderService,
+    );
+
+    return documentQueryService.load({
+      context: {
+        s3KeyPrefix: ruleInput.documentKeyPrefix,
+      },
+      criteria: {
+        parentDocument: {},
+      },
+      documentId: ruleInput.documentId,
+    });
+  }
+
+  protected async getRuleSubject(document: Document): Promise<RuleSubject> {
+    const eventsData: EventsData = {
+      dropOffEvent: this.getDropOffEvent(document),
+      pickUpEvent: this.getPickUpEvent(document),
+      recyclerEvent: this.getRecyclerEvent(document),
+      vehicleLicensePlate: this.getVehicleLicensePlate(
+        this.getPickUpEvent(document),
+      ),
+      wasteGeneratorEvent: this.getWasteGeneratorEvent(document),
+    };
+
+    const duplicateDocuments = await fetchSimilarMassIdDocuments({
+      auditApiService: createAuditApiService(),
+      document,
+      eventsData,
+    });
+
+    const cancelledCount = duplicateDocuments.filter(
+      (duplicateDocument) =>
+        duplicateDocument.status === DocumentStatus.CANCELLED,
+    ).length;
+
+    return {
+      cancelledCount,
+      totalDuplicates: duplicateDocuments.length,
+      validDuplicatesCount: duplicateDocuments.length - cancelledCount,
+    };
+  }
+
+  async process(ruleInput: RuleInput): Promise<RuleOutput> {
+    try {
+      const documentsQuery = await this.generateDocumentQuery(ruleInput);
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/37240136f764c9b56616fae8b3c09ca50d79fbab
created_at: 2025-04-01T16:51:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,274 @@
+import type { EvaluateResultOutput } from '@carrot-fndn/shared/rule/standard-data-processor';
+
+import { RuleDataProcessor } from '@carrot-fndn/shared/app/types';
+import { provideDocumentLoaderService } from '@carrot-fndn/shared/document/loader';
+import {
+  getOrUndefined,
+  isNil,
+  isNonEmptyString,
+} from '@carrot-fndn/shared/helpers';
+import { getEventAttributeValue } from '@carrot-fndn/shared/methodologies/bold/getters';
+import {
+  type DocumentQuery,
+  DocumentQueryService,
+} from '@carrot-fndn/shared/methodologies/bold/io-helpers';
+import { MASS_ID } from '@carrot-fndn/shared/methodologies/bold/matchers';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import {
+  type Document,
+  type DocumentEvent,
+  DocumentEventAttributeName,
+  DocumentEventName,
+  DocumentStatus,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { mapDocumentReference } from '@carrot-fndn/shared/methodologies/bold/utils';
+import { mapToRuleOutput } from '@carrot-fndn/shared/rule/result';
+import {
+  type RuleInput,
+  type RuleOutput,
+  RuleOutputStatus,
+} from '@carrot-fndn/shared/rule/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+import { UniquenessCheckProcessorErrors } from './uniqueness-check.errors';
+import {
+  type EventsData,
+  createAuditApiService,
+  fetchSimilarMassIdDocuments,
+} from './uniqueness-check.helpers';
+
+const { ACTOR, DROP_OFF, PICK_UP } = DocumentEventName;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V1 } =
+  DocumentEventAttributeName;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V2 } =
+  NewDocumentEventAttributeName;
+
+export const RESULT_COMMENTS = {
+  NO_DUPLICATES_FOUND:
+    'No other documents with the same attributes were found.',
+  ONLY_CANCELLED_DUPLICATES: (
+    totalDuplicates: number,
+    cancelledCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, but all are cancelled (${cancelledCount}).`,
+  VALID_DUPLICATE_FOUND: (
+    totalDuplicates: number,
+    validDuplicatesCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, of which ${validDuplicatesCount} are not cancelled.`,
+} as const;
+
+interface RuleSubject {
+  cancelledCount: number;
+  totalDuplicates: number;
+  validDuplicatesCount: number;
+}
+
+export class UniquenessCheckProcessor extends RuleDataProcessor {
+  protected readonly errorProcessor = new UniquenessCheckProcessorErrors();
+
+  private getDropOffEvent(document: Document): DocumentEvent {
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/37240136f764c9b56616fae8b3c09ca50d79fbab
created_at: 2025-04-01T16:51:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+import { BaseProcessorErrors } from '@carrot-fndn/shared/methodologies/bold/processors';
+import {
+  DocumentCategory,
+  DocumentEventName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+const { DROP_OFF, PICK_UP } = DocumentEventName;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { MASS_ID } = DocumentCategory;
+
+export class UniquenessCheckProcessorErrors extends BaseProcessorErrors {
+  override readonly ERROR_MESSAGE = {
+    MASS_ID_DOCUMENT_NOT_FOUND: 'MassID document not found.',
+    MISSING_DROP_OFF_EVENT: `Unable to validate the "${MASS_ID}" uniqueness because the "${DROP_OFF}" event is missing.`,
+    MISSING_PICK_UP_EVENT: `Unable to validate the "${MASS_ID}" uniqueness because the "${PICK_UP}" event is missing.`,
+    MISSING_RECYCLER_EVENT: `Unable to validate the "${MASS_ID}" uniqueness because the "${RECYCLER}" event is missing.`,
+    MISSING_VEHICLE_LICENSE_PLATE: `Unable to validate the "${MASS_ID}" uniqueness because the vehicle license plate is missing.`,
+    MISSING_WASTE_GENERATOR_EVENT: `Unable to validate the "${MASS_ID}" uniqueness because the "${WASTE_GENERATOR}" event is missing.`,
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts
user: login: cris-santos
body: Not a problem to keep the way it is, but we could omit the `Unable to validate the MassID uniqueness because the ...`. If we look at the other rules comments, we usually keep it direct
created_at: 2025-04-02T10:09:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,223 @@
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: OLD_VEHICLE_LICENSE_PLATE,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE: SOURCE_ACTOR_TYPE } =
+  DocumentEventActorType;
+
+export type EventsData = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  vehicleLicensePlate: NonEmptyString;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+export const createLicensePlateRegex = (
+  licensePlate: NonEmptyString,
+): string => {
+  const escapedValue = licensePlate
+    .replaceAll(/\s+/g, '')
+    .replaceAll(/[$()*+./?[\\\]^{|}-]/g, '\\$&');
+
+  return `^${escapedValue}$`;
+};
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: cris-santos
body: to make sure the regex works, we should create an E2E test that actually makes the request to the API
created_at: 2025-04-02T10:13:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,223 @@
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: OLD_VEHICLE_LICENSE_PLATE,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE: SOURCE_ACTOR_TYPE } =
+  DocumentEventActorType;
+
+export type EventsData = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  vehicleLicensePlate: NonEmptyString;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+export const createLicensePlateRegex = (
+  licensePlate: NonEmptyString,
+): string => {
+  const escapedValue = licensePlate
+    .replaceAll(/\s+/g, '')
+    .replaceAll(/[$()*+./?[\\\]^{|}-]/g, '\\$&');
+
+  return `^${escapedValue}$`;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const mapMassIdV2Query = (
+  document: Document,
+  requiredData: EventsData,
+) => {
+  const {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    vehicleLicensePlate,
+    wasteGeneratorEvent,
+  } = requiredData;
+
+  const licensePlateRegex = createLicensePlateRegex(vehicleLicensePlate);
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: VEHICLE_LICENSE_PLATE,
+                  value: { $options: 'i', $regex: licensePlateRegex },
+                },
+              },
+              name: PICK_UP,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': dropOffEvent.address.id,
+              externalCreatedAt: dropOffEvent.externalCreatedAt,
+              name: DROP_OFF,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              label: WASTE_GENERATOR,
+              name: ACTOR,
+              'participant.id': wasteGeneratorEvent.participant.id,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              label: RECYCLER,
+              name: ACTOR,
+              'participant.id': recyclerEvent.participant.id,
+            },
+          },
+        },
+      ],
+      category: document.category,
+      currentValue: document.currentValue,
+      dataSetName: document.dataSetName,
+      subtype: document.subtype,
+      type: document.type,
+    },
+  };
+};
+
+export const mapMassIdV1Query = (document: Document, events: EventsData) => {
+  const {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    vehicleLicensePlate,
+    wasteGeneratorEvent,
+  } = events;
+
+  const licensePlateRegex = createLicensePlateRegex(vehicleLicensePlate);
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: OLD_VEHICLE_LICENSE_PLATE,
+                  value: { $options: 'i', $regex: licensePlateRegex },
+                },
+              },
+              name: OPEN,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': dropOffEvent.address.id,
+              externalCreatedAt: dropOffEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: MOVE_TYPE,
+                  value: DROP_OFF_MOVE_TYPE,
+                },
+              },
+              name: MOVE,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: ACTOR_TYPE,
+                  value: SOURCE_ACTOR_TYPE,
+                },
+              },
+              name: ACTOR,
+              'participant.id': wasteGeneratorEvent.participant.id,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: ACTOR_TYPE,
+                  value: RECYCLER_ACTOR_TYPE,
+                },
+              },
+              name: ACTOR,
+              'participant.id': recyclerEvent.participant.id,
+            },
+          },
+        },
+      ],
+      category: DocumentCategory.MASS,
+      currentValue: document.currentValue,
+      dataSetName: document.dataSetName,
+      subtype: document.subtype,
+      type: document.type,
+    },
+  };
+};
+
+export const fetchSimilarMassIdDocuments = async ({
+  auditApiService,
+  document,
+  eventsData,
+}: {
+  auditApiService: AuditApiService;
+  document: Document;
+  eventsData: EventsData;
+}): Promise<ApiDocumentCheckDuplicatesResponse[]> => {
+  const newFormatQuery = mapMassIdV2Query(document, eventsData);
+  const oldFormatQuery = mapMassIdV1Query(document, eventsData);
+
+  const [newFormattedDuplicates, oldFormattedDuplicates] = await Promise.all([
+    auditApiService.checkDuplicateDocuments(newFormatQuery),
+    auditApiService.checkDuplicateDocuments(oldFormatQuery),
+  ]);
+
+  return [...newFormattedDuplicates, ...oldFormattedDuplicates];
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: cris-santos
body: instead of new/old, we could name it v1 and v2
created_at: 2025-04-02T10:14:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,223 @@
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: OLD_VEHICLE_LICENSE_PLATE,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE: SOURCE_ACTOR_TYPE } =
+  DocumentEventActorType;
+
+export type EventsData = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  vehicleLicensePlate: NonEmptyString;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+export const createLicensePlateRegex = (
+  licensePlate: NonEmptyString,
+): string => {
+  const escapedValue = licensePlate
+    .replaceAll(/\s+/g, '')
+    .replaceAll(/[$()*+./?[\\\]^{|}-]/g, '\\$&');
+
+  return `^${escapedValue}$`;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const mapMassIdV2Query = (
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: cris-santos
body: to make sure both query works as expected, we should have E2E tests that actually makes the request to the API
created_at: 2025-04-02T10:15:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import type { EvaluateResultOutput } from '@carrot-fndn/shared/rule/standard-data-processor';
+
+import {
+  getOrUndefined,
+  isNil,
+  isNonEmptyString,
+} from '@carrot-fndn/shared/helpers';
+import { getEventAttributeValue } from '@carrot-fndn/shared/methodologies/bold/getters';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import { ParentDocumentRuleProcessor } from '@carrot-fndn/shared/methodologies/bold/processors';
+import {
+  type Document,
+  type DocumentEvent,
+  DocumentEventAttributeName,
+  DocumentEventName,
+  DocumentStatus,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { mapToRuleOutput } from '@carrot-fndn/shared/rule/result';
+import {
+  type RuleInput,
+  type RuleOutput,
+  RuleOutputStatus,
+} from '@carrot-fndn/shared/rule/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+import { UniquenessCheckProcessorErrors } from './uniqueness-check.errors';
+import {
+  type EventsData,
+  createAuditApiService,
+  fetchSimilarMassIdDocuments,
+} from './uniqueness-check.helpers';
+
+const { ACTOR, DROP_OFF, PICK_UP } = DocumentEventName;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V1 } =
+  DocumentEventAttributeName;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V2 } =
+  NewDocumentEventAttributeName;
+
+export const RESULT_COMMENTS = {
+  NO_DUPLICATES_FOUND:
+    'No other documents with the same attributes were found.',
+  ONLY_CANCELLED_DUPLICATES: (
+    totalDuplicates: number,
+    cancelledCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, but all are cancelled (${cancelledCount}).`,
+  VALID_DUPLICATE_FOUND: (
+    totalDuplicates: number,
+    validDuplicatesCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, of which ${validDuplicatesCount} are not cancelled.`,
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts
user: login: cris-santos
body: we could replace "documents" with "MassID"
created_at: 2025-04-02T10:16:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import type { EvaluateResultOutput } from '@carrot-fndn/shared/rule/standard-data-processor';
+
+import {
+  getOrUndefined,
+  isNil,
+  isNonEmptyString,
+} from '@carrot-fndn/shared/helpers';
+import { getEventAttributeValue } from '@carrot-fndn/shared/methodologies/bold/getters';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import { ParentDocumentRuleProcessor } from '@carrot-fndn/shared/methodologies/bold/processors';
+import {
+  type Document,
+  type DocumentEvent,
+  DocumentEventAttributeName,
+  DocumentEventName,
+  DocumentStatus,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { mapToRuleOutput } from '@carrot-fndn/shared/rule/result';
+import {
+  type RuleInput,
+  type RuleOutput,
+  RuleOutputStatus,
+} from '@carrot-fndn/shared/rule/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+import { UniquenessCheckProcessorErrors } from './uniqueness-check.errors';
+import {
+  type EventsData,
+  createAuditApiService,
+  fetchSimilarMassIdDocuments,
+} from './uniqueness-check.helpers';
+
+const { ACTOR, DROP_OFF, PICK_UP } = DocumentEventName;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V1 } =
+  DocumentEventAttributeName;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V2 } =
+  NewDocumentEventAttributeName;
+
+export const RESULT_COMMENTS = {
+  NO_DUPLICATES_FOUND:
+    'No other documents with the same attributes were found.',
+  ONLY_CANCELLED_DUPLICATES: (
+    totalDuplicates: number,
+    cancelledCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, but all are cancelled (${cancelledCount}).`,
+  VALID_DUPLICATE_FOUND: (
+    totalDuplicates: number,
+    validDuplicatesCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, of which ${validDuplicatesCount} are not cancelled.`,
+} as const;
+
+interface RuleSubject {
+  cancelledCount: number;
+  totalDuplicates: number;
+  validDuplicatesCount: number;
+}
+
+export class UniquenessCheckProcessor extends ParentDocumentRuleProcessor<RuleSubject> {
+  protected readonly errorProcessor = new UniquenessCheckProcessorErrors();
+
+  private collectRequiredEventsData(document: Document): EventsData {
+    const dropOffEvent = this.getEventOrThrow(
+      document,
+      { name: [DROP_OFF] },
+      'MISSING_DROP_OFF_EVENT',
+    );
+
+    const pickUpEvent = this.getEventOrThrow(
+      document,
+      { name: [PICK_UP] },
+      'MISSING_PICK_UP_EVENT',
+    );
+
+    const recyclerEvent = this.getEventOrThrow(
+      document,
+      { label: [RECYCLER], name: [ACTOR] },
+      'MISSING_RECYCLER_EVENT',
+    );
+
+    const wasteGeneratorEvent = this.getEventOrThrow(
+      document,
+      { label: [WASTE_GENERATOR], name: [ACTOR] },
+      'MISSING_WASTE_GENERATOR_EVENT',
+    );
+
+    const vehicleLicensePlate = this.getVehicleLicensePlate(pickUpEvent);
+
+    return {
+      dropOffEvent,
+      pickUpEvent,
+      recyclerEvent,
+      vehicleLicensePlate,
+      wasteGeneratorEvent,
+    };
+  }
+
+  private getEventOrThrow(
+    document: Document,
+    criteria: {
+      label?: MethodologyDocumentEventLabel[];
+      name?: DocumentEventName[];
+    },
+    errorMessage: keyof UniquenessCheckProcessorErrors['ERROR_MESSAGE'],
+  ): DocumentEvent {
+    const { label, name } = criteria;
+
+    const nameFilter = name ? eventNameIsAnyOf(name) : null;
+    const labelFilter = label ? eventLabelIsAnyOf(label) : null;
+
+    const event = document.externalEvents?.find(
+      nameFilter && labelFilter
+        ? and(nameFilter, labelFilter)
+        : nameFilter || labelFilter || (() => true),
+    );
+
+    if (isNil(event)) {
+      throw this.errorProcessor.getKnownError(
+        this.errorProcessor.ERROR_MESSAGE[errorMessage],
+      );
+    }
+
+    return event;
+  }
+
+  private getVehicleLicensePlate(pickUpEvent: DocumentEvent): NonEmptyString {
+    const vehicleLicensePlateV1 = getEventAttributeValue(
+      pickUpEvent,
+      VEHICLE_LICENSE_PLATE_V1,
+    );
+
+    if (isNonEmptyString(vehicleLicensePlateV1)) {
+      return vehicleLicensePlateV1;
+    }
+
+    const vehicleLicensePlateV2 = getEventAttributeValue(
+      pickUpEvent,
+      VEHICLE_LICENSE_PLATE_V2,
+    );
+
+    if (isNonEmptyString(vehicleLicensePlateV2)) {
+      return vehicleLicensePlateV2;
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts
user: login: cris-santos
body: why are we handling both versions? won't the rule only be executed on the v2 document?
created_at: 2025-04-02T10:17:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+import { BaseProcessorErrors } from '@carrot-fndn/shared/methodologies/bold/processors';
+import {
+  DocumentCategory,
+  DocumentEventName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { MethodologyDocumentEventLabel } from '@carrot-fndn/shared/types';
+
+const { DROP_OFF, PICK_UP } = DocumentEventName;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { MASS_ID } = DocumentCategory;
+
+export class UniquenessCheckProcessorErrors extends BaseProcessorErrors {
+  override readonly ERROR_MESSAGE = {
+    MASS_ID_DOCUMENT_NOT_FOUND: 'MassID document not found.',
+    MISSING_DROP_OFF_EVENT: `Unable to validate the "${MASS_ID}" uniqueness because the "${DROP_OFF}" event is missing.`,
+    MISSING_PICK_UP_EVENT: `Unable to validate the "${MASS_ID}" uniqueness because the "${PICK_UP}" event is missing.`,
+    MISSING_RECYCLER_EVENT: `Unable to validate the "${MASS_ID}" uniqueness because the "${RECYCLER}" event is missing.`,
+    MISSING_VEHICLE_LICENSE_PLATE: `Unable to validate the "${MASS_ID}" uniqueness because the vehicle license plate is missing.`,
+    MISSING_WASTE_GENERATOR_EVENT: `Unable to validate the "${MASS_ID}" uniqueness because the "${WASTE_GENERATOR}" event is missing.`,
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.errors.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/55b268da2ed3531d7c626082c0728f834e94ce27
created_at: 2025-04-02T17:48:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import type { EvaluateResultOutput } from '@carrot-fndn/shared/rule/standard-data-processor';
+
+import {
+  getOrUndefined,
+  isNil,
+  isNonEmptyString,
+} from '@carrot-fndn/shared/helpers';
+import { getEventAttributeValue } from '@carrot-fndn/shared/methodologies/bold/getters';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import { ParentDocumentRuleProcessor } from '@carrot-fndn/shared/methodologies/bold/processors';
+import {
+  type Document,
+  type DocumentEvent,
+  DocumentEventAttributeName,
+  DocumentEventName,
+  DocumentStatus,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { mapToRuleOutput } from '@carrot-fndn/shared/rule/result';
+import {
+  type RuleInput,
+  type RuleOutput,
+  RuleOutputStatus,
+} from '@carrot-fndn/shared/rule/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+import { UniquenessCheckProcessorErrors } from './uniqueness-check.errors';
+import {
+  type EventsData,
+  createAuditApiService,
+  fetchSimilarMassIdDocuments,
+} from './uniqueness-check.helpers';
+
+const { ACTOR, DROP_OFF, PICK_UP } = DocumentEventName;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V1 } =
+  DocumentEventAttributeName;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V2 } =
+  NewDocumentEventAttributeName;
+
+export const RESULT_COMMENTS = {
+  NO_DUPLICATES_FOUND:
+    'No other documents with the same attributes were found.',
+  ONLY_CANCELLED_DUPLICATES: (
+    totalDuplicates: number,
+    cancelledCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, but all are cancelled (${cancelledCount}).`,
+  VALID_DUPLICATE_FOUND: (
+    totalDuplicates: number,
+    validDuplicatesCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, of which ${validDuplicatesCount} are not cancelled.`,
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/08ef348c2fa96e4d5f7c76052f72d104b265f85b
created_at: 2025-04-02T18:03:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import type { EvaluateResultOutput } from '@carrot-fndn/shared/rule/standard-data-processor';
+
+import {
+  getOrUndefined,
+  isNil,
+  isNonEmptyString,
+} from '@carrot-fndn/shared/helpers';
+import { getEventAttributeValue } from '@carrot-fndn/shared/methodologies/bold/getters';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import { ParentDocumentRuleProcessor } from '@carrot-fndn/shared/methodologies/bold/processors';
+import {
+  type Document,
+  type DocumentEvent,
+  DocumentEventAttributeName,
+  DocumentEventName,
+  DocumentStatus,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { mapToRuleOutput } from '@carrot-fndn/shared/rule/result';
+import {
+  type RuleInput,
+  type RuleOutput,
+  RuleOutputStatus,
+} from '@carrot-fndn/shared/rule/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+import { UniquenessCheckProcessorErrors } from './uniqueness-check.errors';
+import {
+  type EventsData,
+  createAuditApiService,
+  fetchSimilarMassIdDocuments,
+} from './uniqueness-check.helpers';
+
+const { ACTOR, DROP_OFF, PICK_UP } = DocumentEventName;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V1 } =
+  DocumentEventAttributeName;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V2 } =
+  NewDocumentEventAttributeName;
+
+export const RESULT_COMMENTS = {
+  NO_DUPLICATES_FOUND:
+    'No other documents with the same attributes were found.',
+  ONLY_CANCELLED_DUPLICATES: (
+    totalDuplicates: number,
+    cancelledCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, but all are cancelled (${cancelledCount}).`,
+  VALID_DUPLICATE_FOUND: (
+    totalDuplicates: number,
+    validDuplicatesCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, of which ${validDuplicatesCount} are not cancelled.`,
+} as const;
+
+interface RuleSubject {
+  cancelledCount: number;
+  totalDuplicates: number;
+  validDuplicatesCount: number;
+}
+
+export class UniquenessCheckProcessor extends ParentDocumentRuleProcessor<RuleSubject> {
+  protected readonly errorProcessor = new UniquenessCheckProcessorErrors();
+
+  private collectRequiredEventsData(document: Document): EventsData {
+    const dropOffEvent = this.getEventOrThrow(
+      document,
+      { name: [DROP_OFF] },
+      'MISSING_DROP_OFF_EVENT',
+    );
+
+    const pickUpEvent = this.getEventOrThrow(
+      document,
+      { name: [PICK_UP] },
+      'MISSING_PICK_UP_EVENT',
+    );
+
+    const recyclerEvent = this.getEventOrThrow(
+      document,
+      { label: [RECYCLER], name: [ACTOR] },
+      'MISSING_RECYCLER_EVENT',
+    );
+
+    const wasteGeneratorEvent = this.getEventOrThrow(
+      document,
+      { label: [WASTE_GENERATOR], name: [ACTOR] },
+      'MISSING_WASTE_GENERATOR_EVENT',
+    );
+
+    const vehicleLicensePlate = this.getVehicleLicensePlate(pickUpEvent);
+
+    return {
+      dropOffEvent,
+      pickUpEvent,
+      recyclerEvent,
+      vehicleLicensePlate,
+      wasteGeneratorEvent,
+    };
+  }
+
+  private getEventOrThrow(
+    document: Document,
+    criteria: {
+      label?: MethodologyDocumentEventLabel[];
+      name?: DocumentEventName[];
+    },
+    errorMessage: keyof UniquenessCheckProcessorErrors['ERROR_MESSAGE'],
+  ): DocumentEvent {
+    const { label, name } = criteria;
+
+    const nameFilter = name ? eventNameIsAnyOf(name) : null;
+    const labelFilter = label ? eventLabelIsAnyOf(label) : null;
+
+    const event = document.externalEvents?.find(
+      nameFilter && labelFilter
+        ? and(nameFilter, labelFilter)
+        : nameFilter || labelFilter || (() => true),
+    );
+
+    if (isNil(event)) {
+      throw this.errorProcessor.getKnownError(
+        this.errorProcessor.ERROR_MESSAGE[errorMessage],
+      );
+    }
+
+    return event;
+  }
+
+  private getVehicleLicensePlate(pickUpEvent: DocumentEvent): NonEmptyString {
+    const vehicleLicensePlateV1 = getEventAttributeValue(
+      pickUpEvent,
+      VEHICLE_LICENSE_PLATE_V1,
+    );
+
+    if (isNonEmptyString(vehicleLicensePlateV1)) {
+      return vehicleLicensePlateV1;
+    }
+
+    const vehicleLicensePlateV2 = getEventAttributeValue(
+      pickUpEvent,
+      VEHICLE_LICENSE_PLATE_V2,
+    );
+
+    if (isNonEmptyString(vehicleLicensePlateV2)) {
+      return vehicleLicensePlateV2;
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts
user: login: AMarcosCastelo
body: Good point!!
created_at: 2025-04-02T18:03:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+import type { EvaluateResultOutput } from '@carrot-fndn/shared/rule/standard-data-processor';
+
+import {
+  getOrUndefined,
+  isNil,
+  isNonEmptyString,
+} from '@carrot-fndn/shared/helpers';
+import { getEventAttributeValue } from '@carrot-fndn/shared/methodologies/bold/getters';
+import {
+  and,
+  eventLabelIsAnyOf,
+  eventNameIsAnyOf,
+} from '@carrot-fndn/shared/methodologies/bold/predicates';
+import { ParentDocumentRuleProcessor } from '@carrot-fndn/shared/methodologies/bold/processors';
+import {
+  type Document,
+  type DocumentEvent,
+  DocumentEventAttributeName,
+  DocumentEventName,
+  DocumentStatus,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import { mapToRuleOutput } from '@carrot-fndn/shared/rule/result';
+import {
+  type RuleInput,
+  type RuleOutput,
+  RuleOutputStatus,
+} from '@carrot-fndn/shared/rule/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+import { UniquenessCheckProcessorErrors } from './uniqueness-check.errors';
+import {
+  type EventsData,
+  createAuditApiService,
+  fetchSimilarMassIdDocuments,
+} from './uniqueness-check.helpers';
+
+const { ACTOR, DROP_OFF, PICK_UP } = DocumentEventName;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V1 } =
+  DocumentEventAttributeName;
+const { VEHICLE_LICENSE_PLATE: VEHICLE_LICENSE_PLATE_V2 } =
+  NewDocumentEventAttributeName;
+
+export const RESULT_COMMENTS = {
+  NO_DUPLICATES_FOUND:
+    'No other documents with the same attributes were found.',
+  ONLY_CANCELLED_DUPLICATES: (
+    totalDuplicates: number,
+    cancelledCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, but all are cancelled (${cancelledCount}).`,
+  VALID_DUPLICATE_FOUND: (
+    totalDuplicates: number,
+    validDuplicatesCount: number,
+  ) =>
+    `${totalDuplicates} similar documents were found, of which ${validDuplicatesCount} are not cancelled.`,
+} as const;
+
+interface RuleSubject {
+  cancelledCount: number;
+  totalDuplicates: number;
+  validDuplicatesCount: number;
+}
+
+export class UniquenessCheckProcessor extends ParentDocumentRuleProcessor<RuleSubject> {
+  protected readonly errorProcessor = new UniquenessCheckProcessorErrors();
+
+  private collectRequiredEventsData(document: Document): EventsData {
+    const dropOffEvent = this.getEventOrThrow(
+      document,
+      { name: [DROP_OFF] },
+      'MISSING_DROP_OFF_EVENT',
+    );
+
+    const pickUpEvent = this.getEventOrThrow(
+      document,
+      { name: [PICK_UP] },
+      'MISSING_PICK_UP_EVENT',
+    );
+
+    const recyclerEvent = this.getEventOrThrow(
+      document,
+      { label: [RECYCLER], name: [ACTOR] },
+      'MISSING_RECYCLER_EVENT',
+    );
+
+    const wasteGeneratorEvent = this.getEventOrThrow(
+      document,
+      { label: [WASTE_GENERATOR], name: [ACTOR] },
+      'MISSING_WASTE_GENERATOR_EVENT',
+    );
+
+    const vehicleLicensePlate = this.getVehicleLicensePlate(pickUpEvent);
+
+    return {
+      dropOffEvent,
+      pickUpEvent,
+      recyclerEvent,
+      vehicleLicensePlate,
+      wasteGeneratorEvent,
+    };
+  }
+
+  private getEventOrThrow(
+    document: Document,
+    criteria: {
+      label?: MethodologyDocumentEventLabel[];
+      name?: DocumentEventName[];
+    },
+    errorMessage: keyof UniquenessCheckProcessorErrors['ERROR_MESSAGE'],
+  ): DocumentEvent {
+    const { label, name } = criteria;
+
+    const nameFilter = name ? eventNameIsAnyOf(name) : null;
+    const labelFilter = label ? eventLabelIsAnyOf(label) : null;
+
+    const event = document.externalEvents?.find(
+      nameFilter && labelFilter
+        ? and(nameFilter, labelFilter)
+        : nameFilter || labelFilter || (() => true),
+    );
+
+    if (isNil(event)) {
+      throw this.errorProcessor.getKnownError(
+        this.errorProcessor.ERROR_MESSAGE[errorMessage],
+      );
+    }
+
+    return event;
+  }
+
+  private getVehicleLicensePlate(pickUpEvent: DocumentEvent): NonEmptyString {
+    const vehicleLicensePlateV1 = getEventAttributeValue(
+      pickUpEvent,
+      VEHICLE_LICENSE_PLATE_V1,
+    );
+
+    if (isNonEmptyString(vehicleLicensePlateV1)) {
+      return vehicleLicensePlateV1;
+    }
+
+    const vehicleLicensePlateV2 = getEventAttributeValue(
+      pickUpEvent,
+      VEHICLE_LICENSE_PLATE_V2,
+    );
+
+    if (isNonEmptyString(vehicleLicensePlateV2)) {
+      return vehicleLicensePlateV2;
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.processor.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/9ca4a7680ae9646ec56765dacc9bf8d53cdd57c0
created_at: 2025-04-02T18:59:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,223 @@
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: OLD_VEHICLE_LICENSE_PLATE,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE: SOURCE_ACTOR_TYPE } =
+  DocumentEventActorType;
+
+export type EventsData = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  vehicleLicensePlate: NonEmptyString;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+export const createLicensePlateRegex = (
+  licensePlate: NonEmptyString,
+): string => {
+  const escapedValue = licensePlate
+    .replaceAll(/\s+/g, '')
+    .replaceAll(/[$()*+./?[\\\]^{|}-]/g, '\\$&');
+
+  return `^${escapedValue}$`;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const mapMassIdV2Query = (
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/1983fb68e79a259ffa316eb0dd357cb53972f5eb
created_at: 2025-04-04T20:14:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,223 @@
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: OLD_VEHICLE_LICENSE_PLATE,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE: SOURCE_ACTOR_TYPE } =
+  DocumentEventActorType;
+
+export type EventsData = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  vehicleLicensePlate: NonEmptyString;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+export const createLicensePlateRegex = (
+  licensePlate: NonEmptyString,
+): string => {
+  const escapedValue = licensePlate
+    .replaceAll(/\s+/g, '')
+    .replaceAll(/[$()*+./?[\\\]^{|}-]/g, '\\$&');
+
+  return `^${escapedValue}$`;
+};
+
+/* istanbul ignore next */
+export const createAuditApiService = (): AuditApiService =>
+  new AuditApiService();
+
+export const mapMassIdV2Query = (
+  document: Document,
+  requiredData: EventsData,
+) => {
+  const {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    vehicleLicensePlate,
+    wasteGeneratorEvent,
+  } = requiredData;
+
+  const licensePlateRegex = createLicensePlateRegex(vehicleLicensePlate);
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: VEHICLE_LICENSE_PLATE,
+                  value: { $options: 'i', $regex: licensePlateRegex },
+                },
+              },
+              name: PICK_UP,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': dropOffEvent.address.id,
+              externalCreatedAt: dropOffEvent.externalCreatedAt,
+              name: DROP_OFF,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              label: WASTE_GENERATOR,
+              name: ACTOR,
+              'participant.id': wasteGeneratorEvent.participant.id,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              label: RECYCLER,
+              name: ACTOR,
+              'participant.id': recyclerEvent.participant.id,
+            },
+          },
+        },
+      ],
+      category: document.category,
+      currentValue: document.currentValue,
+      dataSetName: document.dataSetName,
+      subtype: document.subtype,
+      type: document.type,
+    },
+  };
+};
+
+export const mapMassIdV1Query = (document: Document, events: EventsData) => {
+  const {
+    dropOffEvent,
+    pickUpEvent,
+    recyclerEvent,
+    vehicleLicensePlate,
+    wasteGeneratorEvent,
+  } = events;
+
+  const licensePlateRegex = createLicensePlateRegex(vehicleLicensePlate);
+
+  return {
+    match: {
+      $and: [
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': pickUpEvent.address.id,
+              externalCreatedAt: pickUpEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: OLD_VEHICLE_LICENSE_PLATE,
+                  value: { $options: 'i', $regex: licensePlateRegex },
+                },
+              },
+              name: OPEN,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'address.id': dropOffEvent.address.id,
+              externalCreatedAt: dropOffEvent.externalCreatedAt,
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: MOVE_TYPE,
+                  value: DROP_OFF_MOVE_TYPE,
+                },
+              },
+              name: MOVE,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: ACTOR_TYPE,
+                  value: SOURCE_ACTOR_TYPE,
+                },
+              },
+              name: ACTOR,
+              'participant.id': wasteGeneratorEvent.participant.id,
+            },
+          },
+        },
+        {
+          externalEvents: {
+            $elementMatch: {
+              'metadata.attributes': {
+                $elemMatch: {
+                  name: ACTOR_TYPE,
+                  value: RECYCLER_ACTOR_TYPE,
+                },
+              },
+              name: ACTOR,
+              'participant.id': recyclerEvent.participant.id,
+            },
+          },
+        },
+      ],
+      category: DocumentCategory.MASS,
+      currentValue: document.currentValue,
+      dataSetName: document.dataSetName,
+      subtype: document.subtype,
+      type: document.type,
+    },
+  };
+};
+
+export const fetchSimilarMassIdDocuments = async ({
+  auditApiService,
+  document,
+  eventsData,
+}: {
+  auditApiService: AuditApiService;
+  document: Document;
+  eventsData: EventsData;
+}): Promise<ApiDocumentCheckDuplicatesResponse[]> => {
+  const newFormatQuery = mapMassIdV2Query(document, eventsData);
+  const oldFormatQuery = mapMassIdV1Query(document, eventsData);
+
+  const [newFormattedDuplicates, oldFormattedDuplicates] = await Promise.all([
+    auditApiService.checkDuplicateDocuments(newFormatQuery),
+    auditApiService.checkDuplicateDocuments(oldFormatQuery),
+  ]);
+
+  return [...newFormattedDuplicates, ...oldFormattedDuplicates];
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/1983fb68e79a259ffa316eb0dd357cb53972f5eb
created_at: 2025-04-04T20:14:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,223 @@
+import {
+  type ApiDocumentCheckDuplicatesResponse,
+  AuditApiService,
+} from '@carrot-fndn/shared/methodologies/audit-api';
+import {
+  type Document,
+  DocumentCategory,
+  type DocumentEvent,
+  DocumentEventActorType,
+  DocumentEventAttributeName,
+  DocumentEventMoveType,
+  DocumentEventName,
+  NewDocumentEventAttributeName,
+} from '@carrot-fndn/shared/methodologies/bold/types';
+import {
+  MethodologyDocumentEventLabel,
+  type NonEmptyString,
+} from '@carrot-fndn/shared/types';
+
+const { VEHICLE_LICENSE_PLATE } = NewDocumentEventAttributeName;
+const {
+  ACTOR_TYPE,
+  MOVE_TYPE,
+  VEHICLE_LICENSE_PLATE: OLD_VEHICLE_LICENSE_PLATE,
+} = DocumentEventAttributeName;
+const { ACTOR, DROP_OFF, MOVE, OPEN, PICK_UP } = DocumentEventName;
+const { DROP_OFF: DROP_OFF_MOVE_TYPE } = DocumentEventMoveType;
+const { RECYCLER, WASTE_GENERATOR } = MethodologyDocumentEventLabel;
+const { RECYCLER: RECYCLER_ACTOR_TYPE, SOURCE: SOURCE_ACTOR_TYPE } =
+  DocumentEventActorType;
+
+export type EventsData = {
+  dropOffEvent: DocumentEvent;
+  pickUpEvent: DocumentEvent;
+  recyclerEvent: DocumentEvent;
+  vehicleLicensePlate: NonEmptyString;
+  wasteGeneratorEvent: DocumentEvent;
+};
+
+export const createLicensePlateRegex = (
+  licensePlate: NonEmptyString,
+): string => {
+  const escapedValue = licensePlate
+    .replaceAll(/\s+/g, '')
+    .replaceAll(/[$()*+./?[\\\]^{|}-]/g, '\\$&');
+
+  return `^${escapedValue}$`;
+};
path: libs/methodologies/bold/rule-processors/mass-id/src/uniqueness-check/uniqueness-check.helpers.ts
user: login: AMarcosCastelo
body: https://github.com/carrot-foundation/methodology-rules/pull/186/commits/1983fb68e79a259ffa316eb0dd357cb53972f5eb
created_at: 2025-04-04T20:14:19Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-27T20:55:32Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This pull request introduces a new uniqueness check feature for MassID documents. Changes include new export statements to re-export functionality from the uniqueness-check module, helper functions for query generation and regex creation, and a processor class that evaluates document uniqueness. An error handling class for customized error messages is added, along with a suite of unit, integration, and end-to-end tests. An AWS Lambda handler wrapping the processor is provided, a new enum member (`OPEN`) is introduced, and the asynchronous handling of rule subjects is updated in a shared standard data processor.

## Changes

| Files                                                                                                               | Change Summary |
|---------------------------------------------------------------------------------------------------------------------|----------------|
| `libs/methodologies/bold/.../mass-id/src/index.ts`, `libs/methodologies/bold/.../mass-id/src/uniqueness-check/index.ts` | Added export statements to re-export functionality from the uniqueness-check module. |
| `libs/methodologies/bold/.../mass-id/src/uniqueness-check/uniqueness-check.errors.ts`                                | Introduced `UniquenessCheckProcessorErrors` class with specific error messages for missing documents/events. |
| `libs/methodologies/bold/.../mass-id/src/uniqueness-check/uniqueness-check.helpers.ts`                               | Added helper functions and types for query generation, regex creation, and API service initialization. |
| `libs/methodologies/bold/.../mass-id/src/uniqueness-check/uniqueness-check.lambda.ts`                                | Introduced a new AWS Lambda handler by wrapping the UniquenessCheckProcessor. |
| `libs/methodologies/bold/.../mass-id/src/uniqueness-check/uniqueness-check.processor.ts`                             | Added `UniquenessCheckProcessor` implementing the document uniqueness evaluation logic. |
| `libs/methodologies/bold/.../mass-id/src/uniqueness-check/uniqueness-check.helpers.spec.ts`, `.../processor.spec.ts`, `.../uniqueness-check.lambda.e2e.spec.ts`, `.../uniqueness-check.test-cases.ts` | Added new unit, integration, and end-to-end test suites and test cases covering helper functions, processor logic, and lambda functionality. |
| `libs/shared/methodologies/bold/.../enum.types.ts`                                                                 | Added a new enum member `OPEN` to the DocumentStatus. |
| `libs/shared/rule/standard-data-processor/.../rule-standard-data-processor.ts`                                       | Updated the `getRuleSubject` method to support asynchronous operations using `await`. |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
    participant C as Client
    participant L as uniquenessCheckLambda
    participant P as UniquenessCheckProcessor
    participant A as AuditApiService

    C->>L: Invoke uniqueness check
    L->>P: Process document uniqueness
    P->>A: Fetch similar documents (await getRuleSubject)
    A-->>P: Return duplicates
    P->>P: Evaluate duplicates & errors
    P-->>L: Return processed result
    L-->>C: Deliver uniqueness check outcome
```

## Poem
> I'm a bunny, hopping with glee,  
> Celebrating changes as fresh as can be,  
> New exports and helpers, a processor so bright,  
> Testing and errors now all in sight.  
> With carrots 🥕 and a joyful code spree,  
> My rabbit heart dances in pure unity!

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=carrot-foundation/methodology-rules&utm_content=186):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: gitstream-cm[bot]
created_at: 2025-03-31T19:50:26Z
body: This PR is 99.81220657276995% new code.

<automation id="gitstream/percent_new_code"/>
author_association: NONE
user: login: gitstream-cm[bot]
created_at: 2025-03-31T19:51:58Z
body: 🥷 **Code experts: no user but you matched threshold 10** 
 
AMarcosCastelo has most 👩‍💻 **activity** in the files. 
AMarcosCastelo has most 🧠 **knowledge** in the files. 
 <details>
 <summary>See details</summary>


`libs/methodologies/bold/rule-processors/mass-id/src/index.ts` 
 

Activity based on git-commit: 

 |  | AMarcosCastelo |  
 | --- | --- | 
| MAR | 20 additions & 4 deletions |  
| FEB | 4 additions & 1 deletions |  
| JAN |   |  
| DEC |   |  
| NOV |   |  
| OCT |   |  
 

Knowledge based on git-blame: 
 AMarcosCastelo: 100% 

`libs/shared/methodologies/bold/types/src/enum.types.ts` 
 

Activity based on git-commit: 

 |  | AMarcosCastelo |  
 | --- | --- | 
| MAR | 97 additions & 3 deletions |  
| FEB | 184 additions & 0 deletions |  
| JAN |   |  
| DEC |   |  
| NOV |   |  
| OCT |   |  
 

Knowledge based on git-blame: 
 AMarcosCastelo: 97% 

`libs/shared/rule/standard-data-processor/src/rule-standard-data-processor.ts` 
 

Activity based on git-commit: 

 |  | AMarcosCastelo |  
 | --- | --- | 
| MAR | 7 additions & 1 deletions |  
| FEB |   |  
| JAN |   |  
| DEC |   |  
| NOV |   |  
| OCT |   |  
 

Knowledge based on git-blame: 
 AMarcosCastelo: 100% 

</details>
 
 
 
 
To learn more about /:\ gitStream - [Visit our Docs](https://docs.gitstream.cm/) 
 
 

<automation id="gitstream/explain_code_experts"/>
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-04-07T12:39:12Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=carrot-foundation_audit-rules&pullRequest=186) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [4.5% Duplication on New Code](https://sonarcloud.io/component_measures?id=carrot-foundation_audit-rules&pullRequest=186&metric=new_duplicated_lines_density&view=list) (required ≤ 3%)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=carrot-foundation_audit-rules&pullRequest=186)


author_association: NONE