avicenna/service/data-hub/data-connector/src/modules/feat-anomalies/anomalies.service.ts

249 lines
6.5 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import {
Anomaly,
Prisma,
SolutionTarget,
SolutionTargetStep,
Tour,
} from '@prisma/client';
import { Interval } from 'date-fns';
import { TraceTypes } from 'src/core/base/trace.types';
import { PrismaService } from 'src/core/database/prisma.service';
import { AnomalyDetectorService } from './anomaly-detectors/anomaly-detector.service';
import { AnomalyTypes } from './anomaly-detectors/anomaly-types.enum';
import { DriverCertificateDetector } from './anomaly-detectors/driver-certificate.detector';
import { DriverCodriverSameDetector } from './anomaly-detectors/driver-codriver-same.detector';
import { EmployeesLoggedInDetector } from './anomaly-detectors/employees-logged-in.detector';
import { OneTourAtATimeDetector } from './anomaly-detectors/one-tour-at-a-time.detector';
import { WorkingEmployeeNotInDistributionSystemDetector } from './anomaly-detectors/working-employee-not-in-distribution-system.detector';
@Injectable()
export class AnomaliesService {
private detectors = [
new EmployeesLoggedInDetector(this.anomalyDetectorService),
new DriverCertificateDetector(this.anomalyDetectorService),
new DriverCodriverSameDetector(this.anomalyDetectorService),
new OneTourAtATimeDetector(this.anomalyDetectorService),
new WorkingEmployeeNotInDistributionSystemDetector(
this.anomalyDetectorService,
),
];
constructor(
private readonly prisma: PrismaService,
private readonly anomalyDetectorService: AnomalyDetectorService,
) {}
detectAnomalies(interval: Interval): Promise<Anomaly[]> {
return Promise.all(
this.detectors.map((detector) => detector.detect(interval)),
).then(async (anomaliesPerDetector) => {
const anomalies = anomaliesPerDetector.flat();
const anomalyEntities: Anomaly[] = [];
for (const anomaly of anomalies) {
if (anomaly) {
anomalyEntities.push(
await this.prisma.anomaly.upsert({
where: {
id: anomaly.id,
},
create: {
...anomaly,
},
update: {},
}),
);
}
}
return anomalyEntities;
});
}
async validateAnomalies(): Promise<Anomaly[]> {
const anomalies = await this.prisma.anomaly.findMany({
where: {
resolvedAt: null,
},
});
return Promise.all(
anomalies.map((anomaly) =>
this.detectors
.find((detector) => detector.type === anomaly.type)
.validate(anomaly),
),
).then(async (anomalies) => {
const validatedAnomalies = anomalies.flat();
const updatedAnomalies: Anomaly[] = [];
for (const anomaly of validatedAnomalies) {
updatedAnomalies.push(
await this.prisma.anomaly.update({
where: {
id: anomaly.id,
},
data: {
resolvedAt: anomaly.resolvedAt,
manuallyResolved: false,
},
}),
);
}
return updatedAnomalies;
});
}
countActiveAnomalies(types: AnomalyTypes[] = []): Promise<number> {
return this.prisma.anomaly.count({
where: this.getWhereActiveAnomalyQuery(types),
});
}
findActiveAnomalies(
skip?: number,
take?: number,
types: AnomalyTypes[] = [],
): Promise<Anomaly[]> {
return this.prisma.anomaly.findMany({
...{
...(skip ? { skip, take: 10 } : {}),
...(take ? { take } : {}),
...(take && !skip ? { skip: 0 } : {}),
},
where: this.getWhereActiveAnomalyQuery(types),
orderBy: {
groupKey: 'asc',
},
include: {
solution: true,
},
});
}
async findActiveAnomaliesGroupedByGroupKey(
skip = 0,
take = 10,
): Promise<{
totalCount: number;
anomalies: { key: string; anomalies: Anomaly[] }[];
}> {
const activeAnomalies = await this.findActiveAnomalies(
undefined,
undefined,
[
AnomalyTypes.DriverCertificate,
AnomalyTypes.EmployeesLoggedIn,
AnomalyTypes.OneTourAtATime,
],
);
const groupedAnomalies = this.groupAnomaliesByGroupKey(activeAnomalies);
return {
totalCount: Object.keys(groupedAnomalies).length,
anomalies: Object.entries(groupedAnomalies)
.map(([key, anomalies]) => ({
key,
anomalies,
}))
.slice(skip, skip + take),
};
}
findById(id: string): Promise<Anomaly> {
return this.prisma.anomaly.findUnique({
where: {
id,
},
});
}
findSolutionTargetsByAnomalyId(anomalyId: string): Promise<SolutionTarget[]> {
return this.prisma.solutionTarget.findMany({
where: {
anomalyId,
},
});
}
findSolutionTargetStepsBySolutionTargetId(
solutionTargetId: string,
): Promise<SolutionTargetStep[]> {
return this.prisma.solutionTargetStep.findMany({
where: {
solutionTargetId,
},
});
}
findTourByOperationId(operationId: string): Promise<Tour> {
return this.prisma.tour.findUnique({ where: { operationId } });
}
async setAnomalyToSleep(anomalyId: string): Promise<Anomaly> {
await this.prisma.trace.create({
data: {
entityKey: anomalyId,
type: TraceTypes.AnomalyIgnored,
},
});
return this.prisma.anomaly.update({
where: {
id: anomalyId,
},
data: {
sleepSince: new Date(),
},
});
}
async acceptAnomaly(anomalyId: string): Promise<Anomaly> {
await this.prisma.trace.create({
data: {
entityKey: anomalyId,
type: TraceTypes.AnomalyAccepted,
},
});
return this.prisma.anomaly.update({
where: {
id: anomalyId,
},
data: {
resolvedAt: new Date(),
manuallyResolved: true,
},
});
}
private getWhereActiveAnomalyQuery(
types: AnomalyTypes[] = [],
): Prisma.AnomalyWhereInput {
return {
...(types.length ? { type: { in: types } } : {}),
resolvedAt: null,
OR: [
{
sleepSince: null,
},
{
sleepSince: {
lt: new Date(new Date().getTime() - 60 * 60 * 1000),
},
},
],
};
}
private groupAnomaliesByGroupKey(
anomalies: Anomaly[],
): Record<string, Anomaly[]> {
return anomalies.reduce(
(grouped, anomaly) => ({
...grouped,
[anomaly.groupKey]: [...(grouped[anomaly.groupKey] ?? []), anomaly],
}),
{} as Record<string, Anomaly[]>,
);
}
}