init data-connector
This commit is contained in:
@@ -15,25 +15,28 @@ 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 { OneTourAtATimeDetector } from './anomaly-detectors/one-tour-at-a-time.detector';
|
||||
import { WorkingEmployeeNotInDistributionSystemDetector } from './anomaly-detectors/working-employee-not-in-distribution-system.detector';
|
||||
import { AnomalyDetector } from './anomaly-detectors/anomaly-detector.class';
|
||||
|
||||
@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,
|
||||
),
|
||||
];
|
||||
private detectors: AnomalyDetector[];
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly anomalyDetectorService: AnomalyDetectorService,
|
||||
) {}
|
||||
) {
|
||||
this.detectors = [
|
||||
new EmployeesLoggedInDetector(this.anomalyDetectorService),
|
||||
new DriverCertificateDetector(this.anomalyDetectorService),
|
||||
new DriverCodriverSameDetector(this.anomalyDetectorService),
|
||||
// new OneTourAtATimeDetector(this.anomalyDetectorService),
|
||||
new WorkingEmployeeNotInDistributionSystemDetector(
|
||||
this.anomalyDetectorService,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
detectAnomalies(interval: Interval): Promise<Anomaly[]> {
|
||||
return Promise.all(
|
||||
|
||||
+3
@@ -105,6 +105,9 @@ export class AnomalyDetectorService {
|
||||
},
|
||||
],
|
||||
},
|
||||
orderBy: {
|
||||
startDate: 'asc',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
-13
@@ -267,19 +267,6 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||
|
||||
const datetimeToCheckWithBuffer = addMinutes(datetimeToCheck, 5);
|
||||
|
||||
if (personnelNumber === '23') {
|
||||
console.log(
|
||||
datetimeToCheck,
|
||||
{
|
||||
start: new Date(workTime.startDate),
|
||||
end: new Date(workTime.endDate),
|
||||
},
|
||||
{
|
||||
start: new Date(firstClockedIn.dateTime),
|
||||
end: new Date(lastClockedOut.dateTime),
|
||||
},
|
||||
);
|
||||
}
|
||||
try {
|
||||
if (workTime) {
|
||||
const interval = {
|
||||
|
||||
+198
-166
@@ -1,5 +1,5 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Anomaly, Prisma } from '@prisma/client';
|
||||
import { Anomaly, Prisma, Tour } from '@prisma/client';
|
||||
import {
|
||||
addDays,
|
||||
areIntervalsOverlapping,
|
||||
@@ -7,11 +7,11 @@ import {
|
||||
format,
|
||||
Interval,
|
||||
isBefore,
|
||||
isWithinInterval,
|
||||
isSameDay,
|
||||
set,
|
||||
startOfDay,
|
||||
} from 'date-fns';
|
||||
import { toZonedTime, fromZonedTime } from 'date-fns-tz';
|
||||
import { fromZonedTime, toZonedTime } from 'date-fns-tz';
|
||||
|
||||
import { AnomalyDetector } from './anomaly-detector.class';
|
||||
import { AnomalyTypes } from './anomaly-types.enum';
|
||||
@@ -20,177 +20,209 @@ export class OneTourAtATimeDetector extends AnomalyDetector {
|
||||
private readonly logger = new Logger(OneTourAtATimeDetector.name);
|
||||
readonly type = AnomalyTypes.OneTourAtATime;
|
||||
|
||||
private groupToursByDay(tours: Tour[]): Record<string, Tour[]> {
|
||||
return tours.reduce(
|
||||
(acc, cur) => {
|
||||
const dayString = new Date(cur.startDate).toDateString();
|
||||
const group = acc[dayString] ?? [];
|
||||
group.push(cur);
|
||||
return { ...acc, [dayString]: group };
|
||||
},
|
||||
{} as Record<string, Tour[]>,
|
||||
);
|
||||
}
|
||||
|
||||
async detect(inInterval: Interval): Promise<Prisma.AnomalyCreateInput[]> {
|
||||
const toursInRange = await this.dataService.findToursInInterval(inInterval);
|
||||
|
||||
return toursInRange
|
||||
.map(
|
||||
(
|
||||
{
|
||||
operationId,
|
||||
startDate,
|
||||
startBegin,
|
||||
startEnd,
|
||||
targetBegin,
|
||||
target,
|
||||
empty,
|
||||
carName,
|
||||
driverId,
|
||||
driverName,
|
||||
codriverId,
|
||||
codriverName,
|
||||
},
|
||||
_,
|
||||
tours,
|
||||
) => {
|
||||
// somehow startBegin can be after empty, so we use startBegin and startEnd and take the lower value and hope for the best
|
||||
const [start] = [startBegin, startEnd].sort();
|
||||
// somehow empty can be before everything else, so we use targetBegin, target and empty and take the higher value ...
|
||||
const [end] = [targetBegin, target, empty]
|
||||
.filter((val) => !!val)
|
||||
.sort()
|
||||
.reverse();
|
||||
const toursMap = this.groupToursByDay(toursInRange);
|
||||
const result = [];
|
||||
for (const [, tours] of Object.entries(toursMap)) {
|
||||
const intermediateStep = tours
|
||||
.map(
|
||||
(
|
||||
{
|
||||
operationId,
|
||||
startDate,
|
||||
startBegin,
|
||||
startEnd,
|
||||
targetBegin,
|
||||
target,
|
||||
empty,
|
||||
carName,
|
||||
driverId,
|
||||
driverName,
|
||||
codriverId,
|
||||
codriverName,
|
||||
},
|
||||
_,
|
||||
tours,
|
||||
) => {
|
||||
// somehow startBegin can be after empty, so we use startBegin and startEnd and take the lower value and hope for the best
|
||||
const [start] = [startBegin, startEnd].sort();
|
||||
// somehow empty can be before everything else, so we use targetBegin, target and empty and take the higher value ...
|
||||
const [end] = [targetBegin, target, empty]
|
||||
.filter((val) => !!val)
|
||||
.sort()
|
||||
.reverse();
|
||||
|
||||
if (!start || !end) {
|
||||
if (isBefore(new Date(), startDate)) {
|
||||
this.logger.warn(
|
||||
`Tour ${operationId} started in the past, but is missing start or end time`,
|
||||
);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
const tourInterval = this.getTourInterval(start, end, startDate);
|
||||
|
||||
const toursInInterval = tours
|
||||
.filter((tour) =>
|
||||
isWithinInterval(tour.startDate, {
|
||||
start: startOfDay(startDate),
|
||||
end: endOfDay(startDate),
|
||||
}),
|
||||
)
|
||||
.filter(
|
||||
({
|
||||
operationId: tourListOperationId,
|
||||
carName: tourListCarName,
|
||||
}) =>
|
||||
tourListOperationId !== operationId &&
|
||||
carName !== tourListCarName,
|
||||
)
|
||||
.filter(
|
||||
({
|
||||
startDate: tourListStartDate,
|
||||
startBegin: tourListStartBegin,
|
||||
startEnd: tourListStartEnd,
|
||||
targetBegin: tourListTargetBegin,
|
||||
target: tourListTarget,
|
||||
empty: tourListEmpty,
|
||||
}) => {
|
||||
// somehow startBegin can be after empty, so we use startBegin and startEnd and take the lower value and hope for the best
|
||||
const [tourListStart] = [
|
||||
tourListStartBegin,
|
||||
tourListStartEnd,
|
||||
].sort();
|
||||
// somehow empty can be before everything else, so we use targetBegin and empty and take the higher value ...
|
||||
const [tourListEnd] = [
|
||||
tourListTargetBegin,
|
||||
tourListTarget,
|
||||
tourListEmpty,
|
||||
]
|
||||
.filter((val) => !!val)
|
||||
.sort()
|
||||
.reverse();
|
||||
|
||||
if (!tourListStart || !tourListEnd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tourListInterval = this.getTourInterval(
|
||||
tourListStart,
|
||||
tourListEnd,
|
||||
tourListStartDate,
|
||||
if (!start || !end) {
|
||||
if (isBefore(new Date(), startDate)) {
|
||||
this.logger.warn(
|
||||
`Tour ${operationId} started in the past, but is missing start or end time`,
|
||||
);
|
||||
try {
|
||||
return areIntervalsOverlapping(
|
||||
tourInterval,
|
||||
tourListInterval,
|
||||
}
|
||||
return [];
|
||||
}
|
||||
const tourInterval = this.getTourInterval(start, end, startDate);
|
||||
|
||||
const toursInInterval = tours
|
||||
.filter(
|
||||
(tour) => isSameDay(tour.startDate, startDate),
|
||||
// isWithinInterval(tour.startDate, {
|
||||
// start: startOfDay(startDate),
|
||||
// end: endOfDay(startDate),
|
||||
// }),
|
||||
)
|
||||
.filter(
|
||||
({
|
||||
operationId: tourListOperationId,
|
||||
carName: tourListCarName,
|
||||
}) =>
|
||||
tourListOperationId !== operationId &&
|
||||
carName !== tourListCarName,
|
||||
)
|
||||
.filter(
|
||||
({
|
||||
startDate: tourListStartDate,
|
||||
startBegin: tourListStartBegin,
|
||||
startEnd: tourListStartEnd,
|
||||
targetBegin: tourListTargetBegin,
|
||||
target: tourListTarget,
|
||||
empty: tourListEmpty,
|
||||
}) => {
|
||||
// somehow startBegin can be after empty, so we use startBegin and startEnd and take the lower value and hope for the best
|
||||
const [tourListStart] = [
|
||||
tourListStartBegin,
|
||||
tourListStartEnd,
|
||||
].sort();
|
||||
// somehow empty can be before everything else, so we use targetBegin and empty and take the higher value ...
|
||||
const [tourListEnd] = [
|
||||
tourListTargetBegin,
|
||||
tourListTarget,
|
||||
tourListEmpty,
|
||||
]
|
||||
.filter((val) => !!val)
|
||||
.sort()
|
||||
.reverse();
|
||||
|
||||
if (!tourListStart || !tourListEnd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tourListInterval = this.getTourInterval(
|
||||
tourListStart,
|
||||
tourListEnd,
|
||||
tourListStartDate,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
'tourListInterval',
|
||||
error,
|
||||
tourListInterval,
|
||||
tourInterval,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
try {
|
||||
return areIntervalsOverlapping(
|
||||
tourInterval,
|
||||
tourListInterval,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
'tourListInterval',
|
||||
error,
|
||||
tourListInterval,
|
||||
tourInterval,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const toursInIntervalWithDriver = toursInInterval.filter(
|
||||
({
|
||||
driverId: tourListDriverId,
|
||||
codriverId: tourListCodriverId,
|
||||
}) =>
|
||||
driverId === tourListDriverId ||
|
||||
driverId === tourListCodriverId,
|
||||
);
|
||||
const toursInIntervalWithCodriver = toursInInterval.filter(
|
||||
({
|
||||
driverId: tourListDriverId,
|
||||
codriverId: tourListCodriverId,
|
||||
}) =>
|
||||
codriverId === tourListDriverId ||
|
||||
codriverId === tourListCodriverId,
|
||||
);
|
||||
|
||||
const toursInIntervalWithDriver = toursInInterval.filter(
|
||||
({ driverId: tourListDriverId, codriverId: tourListCodriverId }) =>
|
||||
driverId === tourListDriverId || driverId === tourListCodriverId,
|
||||
);
|
||||
const toursInIntervalWithCodriver = toursInInterval.filter(
|
||||
({ driverId: tourListDriverId, codriverId: tourListCodriverId }) =>
|
||||
codriverId === tourListDriverId ||
|
||||
codriverId === tourListCodriverId,
|
||||
);
|
||||
|
||||
const overlappingCarsWithDriver = toursInIntervalWithDriver.map(
|
||||
({ carName }) => carName,
|
||||
);
|
||||
const overlappingTourInfoWithDriver = toursInIntervalWithDriver.map(
|
||||
({ operationId, startDate }) => ({
|
||||
startDate: format(new Date(startDate), 'HH:mm dd.MM.yy'),
|
||||
operationId,
|
||||
}),
|
||||
);
|
||||
const overlappingCarsWithCodriver = toursInIntervalWithCodriver.map(
|
||||
({ carName }) => carName,
|
||||
);
|
||||
const overlappingTourInfoWithCodriver =
|
||||
toursInIntervalWithCodriver.map(({ operationId, startDate }) => ({
|
||||
startDate: format(new Date(startDate), 'HH:mm dd.MM.yy'),
|
||||
operationId,
|
||||
}));
|
||||
return [
|
||||
toursInIntervalWithDriver.length
|
||||
? this.createAnomaly(
|
||||
`${operationId}-${driverId}-${this.type}`,
|
||||
operationId,
|
||||
driverName,
|
||||
startDate,
|
||||
[carName, ...overlappingCarsWithDriver],
|
||||
[
|
||||
{
|
||||
operationId,
|
||||
startDate: format(new Date(startDate), 'HH:mm dd.MM.yy'),
|
||||
},
|
||||
...overlappingTourInfoWithDriver,
|
||||
],
|
||||
)
|
||||
: undefined,
|
||||
toursInIntervalWithCodriver.length
|
||||
? this.createAnomaly(
|
||||
`${operationId}-${codriverId}-${this.type}`,
|
||||
operationId,
|
||||
codriverName,
|
||||
startDate,
|
||||
[carName, ...overlappingCarsWithCodriver],
|
||||
[
|
||||
{
|
||||
operationId,
|
||||
startDate: format(new Date(startDate), 'HH:mm dd.MM.yy'),
|
||||
},
|
||||
...overlappingTourInfoWithCodriver,
|
||||
],
|
||||
)
|
||||
: undefined,
|
||||
];
|
||||
},
|
||||
)
|
||||
.flat()
|
||||
.filter((anomaly) => !!anomaly);
|
||||
const overlappingCarsWithDriver = toursInIntervalWithDriver.map(
|
||||
({ carName }) => carName,
|
||||
);
|
||||
const overlappingTourInfoWithDriver = toursInIntervalWithDriver.map(
|
||||
({ operationId, startDate }) => ({
|
||||
startDate: format(new Date(startDate), 'HH:mm dd.MM.yy'),
|
||||
operationId,
|
||||
}),
|
||||
);
|
||||
const overlappingCarsWithCodriver = toursInIntervalWithCodriver.map(
|
||||
({ carName }) => carName,
|
||||
);
|
||||
const overlappingTourInfoWithCodriver =
|
||||
toursInIntervalWithCodriver.map(({ operationId, startDate }) => ({
|
||||
startDate: format(new Date(startDate), 'HH:mm dd.MM.yy'),
|
||||
operationId,
|
||||
}));
|
||||
return [
|
||||
toursInIntervalWithDriver.length
|
||||
? this.createAnomaly(
|
||||
`${operationId}-${driverId}-${this.type}`,
|
||||
operationId,
|
||||
driverName,
|
||||
startDate,
|
||||
[carName, ...overlappingCarsWithDriver],
|
||||
[
|
||||
{
|
||||
operationId,
|
||||
startDate: format(
|
||||
new Date(startDate),
|
||||
'HH:mm dd.MM.yy',
|
||||
),
|
||||
},
|
||||
...overlappingTourInfoWithDriver,
|
||||
],
|
||||
)
|
||||
: undefined,
|
||||
toursInIntervalWithCodriver.length
|
||||
? this.createAnomaly(
|
||||
`${operationId}-${codriverId}-${this.type}`,
|
||||
operationId,
|
||||
codriverName,
|
||||
startDate,
|
||||
[carName, ...overlappingCarsWithCodriver],
|
||||
[
|
||||
{
|
||||
operationId,
|
||||
startDate: format(
|
||||
new Date(startDate),
|
||||
'HH:mm dd.MM.yy',
|
||||
),
|
||||
},
|
||||
...overlappingTourInfoWithCodriver,
|
||||
],
|
||||
)
|
||||
: undefined,
|
||||
];
|
||||
},
|
||||
)
|
||||
.flat()
|
||||
.filter((anomaly) => !!anomaly);
|
||||
result.push(intermediateStep);
|
||||
}
|
||||
return result.flat();
|
||||
}
|
||||
|
||||
async validate(anomaly: Anomaly): Promise<Anomaly> {
|
||||
|
||||
Reference in New Issue
Block a user