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

blokh/feat/business report alerts #2311

Merged
merged 36 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2c03c6e
feat: updated callback to ongoing to format response for each report
Blokh Apr 21, 2024
fa0dd21
Merge branch 'dev' into blokh/feat/add-business-report-report-id
Blokh Apr 21, 2024
e849d29
feat: updated report return logic
Blokh Apr 21, 2024
9de6f37
Merge branch 'blokh/feat/add-business-report-report-id' of github.com…
Blokh Apr 21, 2024
35abd78
feat: added validation via zod
Blokh Apr 21, 2024
89d3169
feat: removed optional report id
Blokh Apr 21, 2024
dc05878
merged with dev
Blokh Apr 24, 2024
924ccaf
Merge branch 'dev' into blokh/feat/add-business-report-report-id
Blokh Apr 24, 2024
0d1ed0b
updated ongoing service
Blokh Apr 24, 2024
76cefc9
feat: updated business report logic and added business list fetching
Blokh Apr 25, 2024
549cb7d
updated callback handler hook
Blokh Apr 25, 2024
1d15b8c
Merge branch 'dev' into blokh/feat/add-business-report-report-id
Blokh Apr 25, 2024
e72ae5f
merged with dev
Blokh Apr 25, 2024
203f583
updated previousReport to previousReportSummary
Blokh Apr 30, 2024
0af4256
updated ongoing formats
Blokh Apr 30, 2024
5c21784
Merge branch 'dev' into blokh/feat/add-business-report-report-id
Blokh Apr 30, 2024
8d32745
merged with dev
Blokh Apr 30, 2024
8344bf7
feat: fixed generating filesync
Blokh Apr 30, 2024
9f8d3c5
Merge branch 'dev' into blokh/feat/add-business-report-report-id
Blokh May 1, 2024
36361bc
Merge branch 'dev' into blokh/feat/add-business-report-report-id
Blokh May 2, 2024
ca2c60e
feat: update business report logic
Blokh May 5, 2024
4d68f6a
Merge branch 'dev' into blokh/feat/add-business-report-report-id
Blokh May 6, 2024
f14efa1
Merge branch 'dev' into blokh/feat/add-business-report-report-id
Blokh May 7, 2024
d73bcb0
Merge branch 'dev' into blokh/feat/add-business-report-report-id
Blokh May 7, 2024
5b8e07f
Feature/implement ongoing report alert #1959 #1656 (#2341)
Blokh May 9, 2024
7443d11
feat: fixed alert service intg test
Blokh May 16, 2024
5181498
Merge branch 'dev' into blokh/feat/add-business-report-report-id
Blokh May 16, 2024
066f538
feat: merged wit hdev
Blokh May 16, 2024
4a34fe1
Merge branch 'dev' into blokh/feat/add-business-report-report-id
Blokh May 16, 2024
05aaf9e
Merge branch 'dev' into blokh/feat/add-business-report-report-id
Blokh May 21, 2024
8747e79
feat: merged with dev
Blokh May 21, 2024
2a47598
feat: updated check alerts logic
Blokh May 21, 2024
fcbcca3
feat: updated executin details
Blokh May 21, 2024
227403f
feat: resolved comments
Blokh May 21, 2024
97c07a7
fix: alerts (#2400)
liorzam May 22, 2024
d67d5f1
Merge branch 'dev' into blokh/feat/add-business-report-report-id
Blokh May 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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,5 @@
-- CreateEnum
CREATE TYPE "MonitoringType" AS ENUM ('transaction_monitoring', 'ongoing_merchant_monitoring');

-- AlterTable with default in order to avoid the error for existing data
ALTER TABLE "AlertDefinition" ADD COLUMN "monitoringType" "MonitoringType" NOT NULL DEFAULT 'transaction_monitoring';
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "AlertDefinition" ALTER COLUMN "monitoringType" DROP DEFAULT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Alert" ADD COLUMN "businessId" TEXT;

-- AddForeignKey
ALTER TABLE "Alert" ADD CONSTRAINT "Alert_businessId_fkey" FOREIGN KEY ("businessId") REFERENCES "Business"("id") ON DELETE SET NULL ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "BusinessReport" ADD COLUMN "reportId" TEXT NOT NULL;

-- CreateIndex
CREATE INDEX "BusinessReport_reportId_idx" ON "BusinessReport"("reportId");
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
Warnings:

- Added the required column `riskScore` to the `BusinessReport` table without a default value. This is not possible if the table is not empty.
Blokh marked this conversation as resolved.
Show resolved Hide resolved

*/
-- AlterTable
ALTER TABLE "BusinessReport" ADD COLUMN "riskScore" INTEGER NOT NULL;

-- CreateIndex
CREATE INDEX "BusinessReport_riskScore_idx" ON "BusinessReport"("riskScore");
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Alert" ADD COLUMN "additionalInfo" JSONB;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
Warnings:

- A unique constraint covering the columns `[reportId]` on the table `BusinessReport` will be added. If there are existing duplicate values, this will fail.

*/
-- DropIndex
DROP INDEX "BusinessReport_reportId_idx";

-- CreateIndex
CREATE UNIQUE INDEX "BusinessReport_reportId_key" ON "BusinessReport"("reportId");
26 changes: 20 additions & 6 deletions services/workflows-service/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ model Business {
project Project @relation(fields: [projectId], references: [id])
Counterparty Counterparty[]
businessReports BusinessReport[]
alerts Alert[]

@@unique([projectId, correlationId])
@@index([companyName])
Expand Down Expand Up @@ -707,10 +708,16 @@ enum AlertStatus {
completed @map("300")
}

enum MonitoringType {
transaction_monitoring
ongoing_merchant_monitoring
}

model AlertDefinition {
id String @id @default(cuid())
crossEnvKey String? @unique
correlationId String
correlationId String
monitoringType MonitoringType
name String
enabled Boolean @default(true)
description String?
Expand Down Expand Up @@ -758,6 +765,7 @@ model Alert {
severity AlertSeverity?

executionDetails Json
additionalInfo Json?

assignee User? @relation(fields: [assigneeId], references: [id], onUpdate: Cascade, onDelete: NoAction)
assigneeId String?
Expand All @@ -772,6 +780,9 @@ model Alert {
counterpartyId String?
counterparty Counterparty? @relation(fields: [counterpartyId], references: [id])

businessId String?
business Business? @relation(fields: [businessId], references: [id])

@@index([assigneeId])
@@index([projectId])
@@index([alertDefinitionId])
Expand Down Expand Up @@ -830,19 +841,22 @@ model Counterparty {
model BusinessReport {
id String @id @default(cuid())
type BusinessReportType
reportId String @unique
report Json

riskScore Int
businessId String
projectId String
business Business @relation(fields: [businessId], references: [id])

business Business @relation(fields: [businessId], references: [id])
project Project @relation(fields: [projectId], references: [id])
projectId String
project Project @relation(fields: [projectId], references: [id])

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

@@index([createdAt])
@@index([businessId])
@@index([projectId])
@@index([riskScore])
@@index([type])
}
137 changes: 114 additions & 23 deletions services/workflows-service/scripts/alerts/generate-alerts.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {
ALERT_DEDUPE_STRATEGY_DEFAULT,
daysToMinutesConverter,
MerchantAlertLabel,
SEVEN_DAYS,
THREE_DAYS,
TWENTY_ONE_DAYS,
daysToMinutesConverter,
} from '@/alert/consts';
import { TDedupeStrategy } from '@/alert/types';
import { AggregateType, TIME_UNITS } from '@/data-analytics/consts';
Expand All @@ -14,6 +15,7 @@ import {
AlertSeverity,
AlertState,
AlertStatus,
MonitoringType,
PaymentMethod,
Prisma,
PrismaClient,
Expand Down Expand Up @@ -358,6 +360,7 @@ export const ALERT_DEFINITIONS = {
description:
'High Percentage of Chargebacks - High percentage of chargebacks over a set period of time',
dedupeStrategy: {
mute: false,
cooldownTimeframeInMinutes: daysToMinutesConverter(TWENTY_ONE_DAYS),
},
inlineRule: {
Expand All @@ -369,7 +372,7 @@ export const ALERT_DEFINITIONS = {
subjectColumn: 'counterpartyOriginatorId',
minimumCount: 3,
minimumPercentage: 50,
timeAmount: 21,
timeAmount: TWENTY_ONE_DAYS,
timeUnit: TIME_UNITS.days,
},
},
Expand Down Expand Up @@ -537,16 +540,74 @@ export const ALERT_DEFINITIONS = {
},
} as const satisfies Record<string, Parameters<typeof getAlertDefinitionCreateData>[0]>;

export const MERCHANT_MONITORING_ALERT_DEFINITIONS = {
MERCHANT_ONGOING_RISK_ALERT_RISK_INCREASE: {
enabled: true,
defaultSeverity: AlertSeverity.low,
monitoringType: MonitoringType.ongoing_merchant_monitoring,
description: 'Monitor ongoing risk changes',
inlineRule: {
id: 'MERCHANT_ONGOING_RISK_ALERT_RISK_INCREASE',
fnName: 'checkMerchantOngoingAlert',
subjects: ['businessId', 'projectId'],
options: {
increaseRiskScore: 20,
},
},
},
MERCHANT_ONGOING_RISK_ALERT_THRESHOLD: {
enabled: true,
defaultSeverity: AlertSeverity.high,
monitoringType: MonitoringType.ongoing_merchant_monitoring,
description: 'Monitor ongoing risk changes',
inlineRule: {
id: 'MERCHANT_ONGOING_RISK_ALERT_THRESHOLD',
fnName: 'checkMerchantOngoingAlert',
subjects: ['businessId', 'projectId'],
options: {
maxRiskScoreThreshold: 60,
},
},
},
MERCHANT_ONGOING_RISK_ALERT_PERCENTAGE: {
enabled: true,
defaultSeverity: AlertSeverity.medium,
monitoringType: MonitoringType.ongoing_merchant_monitoring,
description: 'Monitor ongoing risk changes',
inlineRule: {
id: 'MERCHANT_ONGOING_RISK_ALERT_PERCENTAGE',
fnName: 'checkMerchantOngoingAlert',
subjects: ['businessId', 'projectId'],
options: {
increaseRiskScorePercentage: 30,
},
},
},
} as const satisfies Partial<
Record<
keyof typeof MerchantAlertLabel | string,
{
inlineRule: InlineRule & InputJsonValue;
monitoringType: MonitoringType;
defaultSeverity: AlertSeverity;
enabled?: boolean;
description?: string;
}
>
>;
liorzam marked this conversation as resolved.
Show resolved Hide resolved

export const getAlertDefinitionCreateData = (
{
inlineRule,
defaultSeverity,
description,
monitoringType = MonitoringType.transaction_monitoring,
enabled = false,
dedupeStrategy = ALERT_DEDUPE_STRATEGY_DEFAULT,
}: {
inlineRule: InlineRule;
defaultSeverity: AlertSeverity;
monitoringType?: MonitoringType;
enabled: boolean;
dedupeStrategy?: Partial<TDedupeStrategy>;
description?: string;
Expand All @@ -564,6 +625,7 @@ export const getAlertDefinitionCreateData = (
...ALERT_DEDUPE_STRATEGY_DEFAULT,
...(dedupeStrategy ?? {}),
},
monitoringType: monitoringType ?? MonitoringType.transaction_monitoring,
inlineRule,
correlationId: id,
name: id,
Expand All @@ -587,6 +649,9 @@ export const generateAlertDefinitions = async (
createdBy = 'SYSTEM',
alertsDef = ALERT_DEFINITIONS,
}: {
alertsDefConfiguration?:
| typeof ALERT_DEFINITIONS
| typeof MERCHANT_MONITORING_ALERT_DEFINITIONS;
createdBy?: string;
project: Project;
alertsDef?: Partial<typeof ALERT_DEFINITIONS>;
Expand Down Expand Up @@ -627,18 +692,34 @@ export const generateAlertDefinitions = async (
}),
);

const generateFakeAlert = ({
severity,
counterparyIds,
agentUserIds,
}: {
severity: AlertSeverity;
counterparyIds: string[];
agentUserIds: string[];
}): Omit<Prisma.AlertCreateManyAlertDefinitionInput, 'projectId'> => {
const counterypartyId = faker.helpers.arrayElement(
counterparyIds.map(id => ({ counterpartyId: id })),
);
const generateFakeAlert = (
options: {
severity: AlertSeverity;
agentUserIds: string[];
} & (
| {
counterpartyIds: string[];
}
| {
businessIds: string[];
}
),
): Omit<Prisma.AlertCreateManyAlertDefinitionInput, 'projectId'> => {
const { severity, agentUserIds } = options;

let businessId: { businessId: string } | {} = {};
let counterpartyId: { counterpartyId: string } | {} = {};

if ('businessIds' in options) {
// For merchant monitoring alerts
businessId = faker.helpers.arrayElement(options.businessIds.map(id => ({ businessId: id })));
} else if ('counterpartyIds' in options) {
// For transaction alerts
counterpartyId = faker.helpers.arrayElement(
options.counterpartyIds.map(id => ({ counterpartyId: id })),
);
}

// In chance of 1 to 5, assign an agent to the alert
const assigneeId =
faker.datatype.number({ min: 1, max: 5 }) === 1
Expand All @@ -658,29 +739,39 @@ const generateFakeAlert = ({
} as InputJsonValue,
severity,
assigneeId,
...counterypartyId,
...counterpartyId,
...businessId,
};
};

export const generateFakeAlertsAndDefinitions = async (
export const seedTransactionsAlerts = async (
prisma: PrismaClient,
{
project,
counterparyIds,
businessIds,
counterpartyIds,
agentUserIds,
}: {
project: Project;
counterparyIds: string[];
businessIds: string[];
counterpartyIds: string[];
agentUserIds: string[];
},
) => {
const alertDefinitions = await generateAlertDefinitions(prisma, {
const transactionsAlertDef = await generateAlertDefinitions(prisma, {
alertsDefConfiguration: ALERT_DEFINITIONS,
project,
createdBy: faker.internet.userName(),
});

await Promise.all(
alertDefinitions.map(alertDefinition =>
const merchantMonitoringAlertDef = await generateAlertDefinitions(prisma, {
alertsDefConfiguration: MERCHANT_MONITORING_ALERT_DEFINITIONS,
project,
createdBy: faker.internet.userName(),
});

await Promise.all([
...transactionsAlertDef.map(alertDefinition =>
prisma.alert.createMany({
data: Array.from(
{
Expand All @@ -690,7 +781,7 @@ export const generateFakeAlertsAndDefinitions = async (
alertDefinitionId: alertDefinition.id,
projectId: project.id,
...generateFakeAlert({
counterparyIds,
counterpartyIds,
agentUserIds,
severity: faker.helpers.arrayElement(Object.values(AlertSeverity)),
}),
Expand All @@ -699,5 +790,5 @@ export const generateFakeAlertsAndDefinitions = async (
skipDuplicates: true,
}),
),
);
]);
};
Loading
Loading