Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Events v2: implement deduplication configuration #8329

Merged
merged 11 commits into from
Jan 14, 2025
5 changes: 3 additions & 2 deletions packages/commons/src/events/ActionInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/

import { ActionType } from './ActionConfig'
import { z } from 'zod'
import { ActionType } from './ActionConfig'
import { FieldValue } from './FieldValue'

const BaseActionInput = z.object({
Expand Down Expand Up @@ -39,7 +39,8 @@ export const RegisterActionInput = BaseActionInput.merge(

export const ValidateActionInput = BaseActionInput.merge(
z.object({
type: z.literal(ActionType.VALIDATE).default(ActionType.VALIDATE)
type: z.literal(ActionType.VALIDATE).default(ActionType.VALIDATE),
duplicates: z.array(z.string())
})
)

Expand Down
113 changes: 113 additions & 0 deletions packages/commons/src/events/DeduplicationConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* OpenCRVS is also distributed under the terms of the Civil Registration
* & Healthcare Disclaimer located at http://opencrvs.org/license.
*
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/
import { z } from 'zod'
import { TranslationConfig } from './TranslationConfig'

const FieldReference = z.string()

const Matcher = z.object({
fieldId: z.string(),
options: z
.object({
boost: z.number().optional()
})
.optional()
.default({})
})

const FuzzyMatcher = Matcher.extend({
type: z.literal('fuzzy'),
options: z
.object({
// Names of length of 3 or less characters = 0 edits allowed
rikukissa marked this conversation as resolved.
Show resolved Hide resolved
// Names of length of 4 - 6 characters = 1 edit allowed
// Names of length of >7 characters = 2 edits allowed
fuzziness: z
.union([z.string(), z.number()])
.optional()
.default('AUTO:4,7'),
boost: z.number().optional().default(1)
})
.optional()
.default({})
})

const StrictMatcher = Matcher.extend({
type: z.literal('strict'),
options: z
.object({
boost: z.number().optional().default(1)
})
.optional()
.default({})
})

const DateRangeMatcher = Matcher.extend({
type: z.literal('dateRange'),
options: z.object({
days: z.number(),
origin: FieldReference,
boost: z.number().optional().default(1)
})
})

const DateDistanceMatcher = Matcher.extend({
type: z.literal('dateDistance'),
options: z.object({
days: z.number(),
origin: FieldReference,
boost: z.number().optional().default(1)
})
})

export type And = {
type: 'and'
clauses: any[]
}

const And: z.ZodType<And> = z.object({
type: z.literal('and'),
clauses: z.lazy(() => Clause.array())
})

export type Or = {
type: 'or'
clauses: any[]
}

const Or: z.ZodType<Or> = z.object({
type: z.literal('or'),
clauses: z.lazy(() => Clause.array())
})

export type Clause =
| And
| Or
| z.infer<typeof FuzzyMatcher>
| z.infer<typeof StrictMatcher>
| z.infer<typeof DateRangeMatcher>

export const Clause = z.union([
rikukissa marked this conversation as resolved.
Show resolved Hide resolved
And,
Or,
FuzzyMatcher,
StrictMatcher,
DateRangeMatcher,
DateDistanceMatcher
])

export const DeduplicationConfig = z.object({
id: z.string(),
label: TranslationConfig,
query: Clause
})

export type DeduplicationConfig = z.infer<typeof DeduplicationConfig>
4 changes: 3 additions & 1 deletion packages/commons/src/events/EventConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { TranslationConfig } from './TranslationConfig'
import { SummaryConfig, SummaryConfigInput } from './SummaryConfig'
import { WorkqueueConfig } from './WorkqueueConfig'
import { FormConfig, FormConfigInput } from './FormConfig'
import { DeduplicationConfig } from './DeduplicationConfig'

/**
* Description of event features defined by the country. Includes configuration for process steps and forms involved.
Expand All @@ -29,7 +30,8 @@ export const EventConfig = z.object({
summary: SummaryConfig,
label: TranslationConfig,
actions: z.array(ActionConfig),
workqueues: z.array(WorkqueueConfig)
workqueues: z.array(WorkqueueConfig),
deduplication: DeduplicationConfig.optional()
})

export const EventConfigInput = EventConfig.extend({
Expand Down
1 change: 1 addition & 0 deletions packages/commons/src/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ export * from './FieldValue'
export * from './state'
export * from './utils'
export * from './defineConfig'
export * from './DeduplicationConfig'
3 changes: 2 additions & 1 deletion packages/events/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@opencrvs/commons": "^1.7.0",
"@trpc/server": "^11.0.0-rc.532",
"app-module-path": "^2.2.0",
"date-fns": "^4.1.0",
"envalid": "^8.0.0",
"jsonwebtoken": "^9.0.0",
"mongodb": "6.9.0",
Expand All @@ -29,9 +30,9 @@
},
"devDependencies": {
"@testcontainers/elasticsearch": "^10.15.0",
"@types/jsonwebtoken": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^4.5.0",
"@typescript-eslint/parser": "^4.5.0",
"@types/jsonwebtoken": "^9.0.0",
"cross-env": "^7.0.0",
"eslint": "^7.11.0",
"eslint-config-prettier": "^9.0.0",
Expand Down
7 changes: 4 additions & 3 deletions packages/events/src/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import {
deleteEvent,
EventInputWithId,
getEventById,
patchEvent
patchEvent,
validate
} from '@events/service/events'
import { presignFilesInEvent } from '@events/service/files'
import {
Expand Down Expand Up @@ -136,9 +137,9 @@ export const appRouter = router({
})
}),
validate: publicProcedure
.input(ValidateActionInput)
.input(ValidateActionInput.omit({ duplicates: true }))
.mutation((options) => {
return addAction(options.input, {
return validate(options.input, {
eventId: options.input.eventId,
createdBy: options.ctx.user.id,
createdAtLocation: options.ctx.user.primaryOfficeId,
Expand Down
Loading
Loading