Skip to content

Feature/implement risk rule ( WIP ) BAL-2445 #2528

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

Open
wants to merge 10 commits into
base: dev
Choose a base branch
from
11 changes: 9 additions & 2 deletions packages/common/src/rule-engine/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,14 @@ export type RuleResult = PassedRuleResult | FailedRuleResult;

export type RuleResultSet = RuleResult[];

export interface TFindAllRulesOptions {
export type TNotionRulesOptions = {
databaseId: string;
source: 'notion';
}
};

export type TDatabaseRulesOptions = {
policyId: string;
source: 'database';
};

export type TFindAllRulesOptions = TNotionRulesOptions | TDatabaseRulesOptions;
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,7 @@ export interface IterativePluginParams {
type RuleSetOptions = { databaseId: string };
export interface RiskRulesPluginParams {
name: string;
rulesSource: {
source: 'notion';
databaseId: string;
};
rulesSource: TFindAllRulesOptions;
stateNames: string[];
successAction?: string;
errorAction?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module.exports = {
},
create: context => {
const isRepository =
/^(?!.*\/(customer|data-migration)\.repository\.(ts|js)$).*\.repository\.(ts|js)$/.test(
/^(?!.*\/(customer|data-migration|risk-rule-set)\.repository\.(ts|js)$).*\.repository\.(ts|js)$/.test(
// Using deprecated .getFilename here because relevant context.filename returns undefined
context.getFilename(),
);
Expand Down
2 changes: 1 addition & 1 deletion services/workflows-service/prisma/data-migrations
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
-- CreateEnum
CREATE TYPE "IndicatorRiskLevel" AS ENUM ('positive', 'moderate', 'critical');
Copy link
Collaborator

Choose a reason for hiding this comment

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

low


-- CreateEnum
CREATE TYPE "RuleEngine" AS ENUM ('Ballerine', 'JsonLogic');

-- CreateTable
CREATE TABLE "WorkflowDefinitionRiskRulePolicy" (
"id" TEXT NOT NULL,
"workflowDefinitionId" TEXT NOT NULL,
"riskRulesPolicyId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "WorkflowDefinitionRiskRulePolicy_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "RiskRulesPolicy" (
"id" TEXT NOT NULL,
"projectId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "RiskRulesPolicy_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "RiskRuleSet" (
"id" TEXT NOT NULL,
"riskRulePolicyId" TEXT NOT NULL,
"domain" TEXT NOT NULL,
"indicator" TEXT NOT NULL,
"riskLevel" "IndicatorRiskLevel" NOT NULL,
"baseRiskScore" INTEGER NOT NULL,
"additionalRiskScore" INTEGER NOT NULL,
"minRiskScore" INTEGER,
"maxRiskScore" INTEGER,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "RiskRuleSet_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Rule" (
"id" TEXT NOT NULL,
"riskRuleSetId" TEXT NOT NULL,
"key" TEXT NOT NULL,
"operator" TEXT NOT NULL,
"comparisonValue" JSONB NOT NULL,
"engine" "RuleEngine" NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"riskRuleSetRiskRulePolicyId" TEXT,

CONSTRAINT "Rule_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "WorkflowDefinitionRiskRulePolicy_workflowDefinitionId_key" ON "WorkflowDefinitionRiskRulePolicy"("workflowDefinitionId");

-- CreateIndex
CREATE INDEX "WorkflowDefinitionRiskRulePolicy_workflowDefinitionId_idx" ON "WorkflowDefinitionRiskRulePolicy"("workflowDefinitionId");

-- CreateIndex
CREATE INDEX "WorkflowDefinitionRiskRulePolicy_riskRulesPolicyId_idx" ON "WorkflowDefinitionRiskRulePolicy"("riskRulesPolicyId");

-- CreateIndex
CREATE UNIQUE INDEX "WorkflowDefinitionRiskRulePolicy_workflowDefinitionId_riskR_key" ON "WorkflowDefinitionRiskRulePolicy"("workflowDefinitionId", "riskRulesPolicyId");

-- CreateIndex
CREATE INDEX "RiskRulesPolicy_projectId_idx" ON "RiskRulesPolicy"("projectId");

-- CreateIndex
CREATE INDEX "RiskRuleSet_riskRulePolicyId_idx" ON "RiskRuleSet"("riskRulePolicyId");

-- CreateIndex
CREATE UNIQUE INDEX "RiskRuleSet_id_riskRulePolicyId_key" ON "RiskRuleSet"("id", "riskRulePolicyId");

-- AddForeignKey
ALTER TABLE "WorkflowDefinitionRiskRulePolicy" ADD CONSTRAINT "WorkflowDefinitionRiskRulePolicy_riskRulesPolicyId_fkey" FOREIGN KEY ("riskRulesPolicyId") REFERENCES "RiskRulesPolicy"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "WorkflowDefinitionRiskRulePolicy" ADD CONSTRAINT "WorkflowDefinitionRiskRulePolicy_workflowDefinitionId_fkey" FOREIGN KEY ("workflowDefinitionId") REFERENCES "WorkflowDefinition"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "RiskRulesPolicy" ADD CONSTRAINT "RiskRulesPolicy_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "RiskRuleSet" ADD CONSTRAINT "RiskRuleSet_riskRulePolicyId_fkey" FOREIGN KEY ("riskRulePolicyId") REFERENCES "RiskRulesPolicy"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Rule" ADD CONSTRAINT "Rule_riskRuleSetId_fkey" FOREIGN KEY ("riskRuleSetId") REFERENCES "RiskRuleSet"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
94 changes: 88 additions & 6 deletions services/workflows-service/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -188,12 +188,13 @@ model WorkflowDefinition {
persistStates Json?
submitStates Json?

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdBy String @default("SYSTEM")
project Project? @relation(fields: [projectId], references: [id])
workflowRuntimeData WorkflowRuntimeData[]
uiDefinitions UiDefinition[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdBy String @default("SYSTEM")
project Project? @relation(fields: [projectId], references: [id])
workflowRuntimeData WorkflowRuntimeData[]
uiDefinitions UiDefinition[]
workflowDefinitionRiskRulePolicy WorkflowDefinitionRiskRulePolicy?

@@unique([name, version, projectId, definitionType])
@@index([projectId, name, definitionType])
Expand Down Expand Up @@ -370,6 +371,7 @@ model Project {
SalesforceIntegration SalesforceIntegration?
workflowDefinitions WorkflowDefinition[]
uiDefinitions UiDefinition[]
riskRulesPolicies RiskRulesPolicy[]
WorkflowRuntimeDataToken WorkflowRuntimeDataToken[]
TransactionRecord TransactionRecord[]
AlertDefinition AlertDefinition[]
Expand Down Expand Up @@ -806,6 +808,12 @@ enum RiskCategory {
high
}

enum IndicatorRiskLevel {
positive
moderate
critical
}

enum ComplianceStatus {
compliant
non_compliant
Expand Down Expand Up @@ -855,6 +863,11 @@ enum BusinessReportStatus {
completed
}

enum RuleEngine {
Ballerine
JsonLogic
}

model BusinessReport {
id String @id @default(cuid())
type BusinessReportType
Expand All @@ -878,3 +891,72 @@ model BusinessReport {
@@index([riskScore])
@@index([type])
}

model WorkflowDefinitionRiskRulePolicy {
id String @id @default(cuid())
workflowDefinitionId String @unique
riskRulesPolicyId String
riskRulesPolicy RiskRulesPolicy @relation(fields: [riskRulesPolicyId], references: [id])
workflowDefinition WorkflowDefinition @relation(fields: [workflowDefinitionId], references: [id])

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@unique([workflowDefinitionId, riskRulesPolicyId])
@@index([workflowDefinitionId])
@@index([riskRulesPolicyId])
}

model RiskRulesPolicy {
id String @id @default(cuid())
projectId String

riskRuleSets RiskRuleSet[]
workflowDefinitionRiskRulePolicies WorkflowDefinitionRiskRulePolicy[]
project Project @relation(fields: [projectId], references: [id])

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@index([projectId])
}

model RiskRuleSet {
id String @id @default(cuid())
riskRulePolicyId String
Copy link
Collaborator

Choose a reason for hiding this comment

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

name

Copy link
Collaborator

Choose a reason for hiding this comment

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

operator


domain String
indicator String
riskLevel IndicatorRiskLevel

baseRiskScore Int
additionalRiskScore Int
minRiskScore Int?
maxRiskScore Int?

riskRulePolicy RiskRulesPolicy @relation(fields: [riskRulePolicyId], references: [id])
rules Rule[]

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@unique([id, riskRulePolicyId])
@@index([riskRulePolicyId])
}

model Rule {
id String @id @default(cuid())
Copy link
Collaborator

Choose a reason for hiding this comment

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

name

riskRuleSetId String

key String
operator String
comparisonValue Json
engine RuleEngine

riskRuleSet RiskRuleSet @relation(fields: [riskRuleSetId], references: [id])

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

riskRuleSetRiskRulePolicyId String?
}
7 changes: 7 additions & 0 deletions services/workflows-service/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ import z from 'zod';
import { hashKey } from './customer/api-key/utils';
import { RuleEngineModule } from './rule-engine/rule-engine.module';
import { NotionModule } from '@/notion/notion.module';
import { RiskRulePolicyModule } from '@/risk-rules/risk-rule-policy/risk-rule-policy.module';
import { RiskRuleSetModule } from '@/risk-rules/risk-rule-set/risk-rule-set.module';
import { RuleModule } from '@/risk-rules/rule/rule.module';

export const validate = async (config: Record<string, unknown>) => {
const zodEnvSchema = z
Expand Down Expand Up @@ -124,6 +127,10 @@ export const validate = async (config: Record<string, unknown>) => {
initHttpMoudle(),
RuleEngineModule,
NotionModule,
// Risk rules Modules
RiskRulePolicyModule,
RiskRuleSetModule,
RuleModule,
],
providers: [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Type } from '@sinclair/typebox';

export const TypeStringEnum = <T extends string[]>(values: [...T], description?: string) =>
Type.Unsafe<T[number]>({
type: 'string',
enum: values,
description,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { AppLoggerModule } from '@/common/app-logger/app-logger.module';
import { RiskRulePolicyService } from '@/risk-rules/risk-rule-policy/risk-rule-policy.service';
import { RiskRulePolicyRepository } from '@/risk-rules/risk-rule-policy/risk-rule-policy.repository';
import { PrismaModule } from '@/prisma/prisma.module';
import { RiskRuleSetModule } from '@/risk-rules/risk-rule-set/risk-rule-set.module';

@Module({
imports: [AppLoggerModule, PrismaModule, RiskRuleSetModule],
providers: [RiskRulePolicyService, RiskRulePolicyRepository],
exports: [RiskRulePolicyService],
})
export class RiskRulePolicyModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/prisma/prisma.service';
import { Prisma } from '@prisma/client';
import { ProjectScopeService } from '@/project/project-scope.service';
import { TProjectIds } from '@/types';

@Injectable()
export class RiskRulePolicyRepository {
constructor(
private readonly prisma: PrismaService,
private readonly scopeService: ProjectScopeService,
) {}

async createRiskRulePolicy(
projectId: string,
createArgs: Prisma.RiskRulesPolicyCreateArgs['data'],
) {
return this.prisma.riskRulesPolicy.create({
data: createArgs,
});
}

async findRiskRulePolicyById(id: string, projectIds: TProjectIds) {
return this.prisma.riskRulesPolicy.findUnique({
where: { id },
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider scoping the query to specific projects.

The method currently retrieves a risk rule policy by ID without considering the project scope. It might be beneficial to include a filter by projectIds to ensure data access is scoped correctly.

-      where: { id },
+      where: { id, projectId: { in: projectIds } },
Committable suggestion

‼️ 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.

Suggested change
async findRiskRulePolicyById(id: string, projectIds: TProjectIds) {
return this.prisma.riskRulesPolicy.findUnique({
where: { id },
});
}
async findRiskRulePolicyById(id: string, projectIds: TProjectIds) {
return this.prisma.riskRulesPolicy.findUnique({
where: { id, projectId: { in: projectIds } },
});
}


async findRiskRulePolicyByWorkflowDefinitionId(
workflowDefinitionId: string,
projectIds: TProjectIds,
) {
return this.prisma.riskRulesPolicy.findFirstOrThrow({
where: {
workflowDefinitionRiskRulePolicies: {
some: {
workflowDefinitionId,
},
},
},
select: {
riskRuleSets: true,
},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Add project scoping to the query.

The method fetches data based on the workflow definition ID but does not scope the query to specific projects, which might lead to unauthorized data access.

-      where: {
+      where: {
+        projectId: { in: projectIds },
         workflowDefinitionRiskRulePolicies: {
           some: {
             workflowDefinitionId,
           },
         },
+      },
Committable suggestion

‼️ 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.

Suggested change
async findRiskRulePolicyByWorkflowDefinitionId(
workflowDefinitionId: string,
projectIds: TProjectIds,
) {
return this.prisma.riskRulesPolicy.findFirstOrThrow({
where: {
workflowDefinitionRiskRulePolicies: {
some: {
workflowDefinitionId,
},
},
},
select: {
riskRuleSets: true,
},
});
async findRiskRulePolicyByWorkflowDefinitionId(
workflowDefinitionId: string,
projectIds: TProjectIds,
) {
return this.prisma.riskRulesPolicy.findFirstOrThrow({
where: {
projectId: { in: projectIds },
workflowDefinitionRiskRulePolicies: {
some: {
workflowDefinitionId,
},
},
},
select: {
riskRuleSets: true,
},
});

}

async findRiskRulePolicyByProjectId(projectId: string) {
return this.prisma.riskRulesPolicy.findFirst({
where: { projectId },
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Injectable } from '@nestjs/common';
import { RiskRulePolicyRepository } from './risk-rule-policy.repository';

@Injectable()
export class RiskRulePolicyService {
constructor(private readonly riskRuleSetRepository: RiskRulePolicyRepository) {}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Well-structured service class for risk rule policy management.

The RiskRulePolicyService class is correctly set up with dependency injection for the RiskRulePolicyRepository. This follows NestJS best practices.

Consider adding service methods.

Currently, the service class does not contain any methods. Depending on the requirements, you might want to add methods to handle specific business logic related to risk rule policies.

Would you like me to help draft some of the service methods based on typical CRUD operations?

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { AppLoggerModule } from '@/common/app-logger/app-logger.module';
import { PrismaModule } from '@/prisma/prisma.module';
import { RiskRuleSetService } from '@/risk-rules/risk-rule-set/risk-rule-set.service';
import { RiskRuleSetRepository } from '@/risk-rules/risk-rule-set/risk-rule-set.repository';

@Module({
imports: [AppLoggerModule, PrismaModule],
providers: [RiskRuleSetService, RiskRuleSetRepository],
exports: [RiskRuleSetService],
})
export class RiskRuleSetModule {}
Loading
Loading