249 lines
6.5 KiB
TypeScript
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[]>,
|
|
);
|
|
}
|
|
}
|