wip - create journeys - no tests yet
This commit is contained in:
parent
dfc8dbcc51
commit
467d8a84f8
|
@ -20,7 +20,7 @@ export abstract class Algorithm {
|
||||||
for (const processor of this.processors) {
|
for (const processor of this.processors) {
|
||||||
this.candidates = await processor.execute(this.candidates);
|
this.candidates = await processor.execute(this.candidates);
|
||||||
}
|
}
|
||||||
// console.log(JSON.stringify(this.candidates, null, 2));
|
console.log(JSON.stringify(this.candidates, null, 2));
|
||||||
return this.candidates.map((candidate: CandidateEntity) =>
|
return this.candidates.map((candidate: CandidateEntity) =>
|
||||||
MatchEntity.create({ adId: candidate.id }),
|
MatchEntity.create({ adId: candidate.id }),
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,7 +5,5 @@ export class JourneyCompleter extends Completer {
|
||||||
complete = async (
|
complete = async (
|
||||||
candidates: CandidateEntity[],
|
candidates: CandidateEntity[],
|
||||||
): Promise<CandidateEntity[]> =>
|
): Promise<CandidateEntity[]> =>
|
||||||
candidates.map((candidate: CandidateEntity) =>
|
candidates.map((candidate: CandidateEntity) => candidate.createJourneys());
|
||||||
candidate.createJourneys(this.query.fromDate, this.query.toDate),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
||||||
import { Completer } from './completer.abstract';
|
import { Completer } from './completer.abstract';
|
||||||
import { MatchQuery } from '../match.query';
|
import { MatchQuery } from '../match.query';
|
||||||
import { CarpoolStep } from '@modules/ad/core/domain/value-objects/carpool-step.value-object';
|
|
||||||
import { Step } from '../../../types/step.type';
|
import { Step } from '../../../types/step.type';
|
||||||
|
import { CarpoolPathItem } from '@modules/ad/core/domain/value-objects/carpool-path-item.value-object';
|
||||||
|
|
||||||
export class RouteCompleter extends Completer {
|
export class RouteCompleter extends Completer {
|
||||||
protected readonly type: RouteCompleterType;
|
protected readonly type: RouteCompleterType;
|
||||||
|
@ -19,8 +19,8 @@ export class RouteCompleter extends Completer {
|
||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case RouteCompleterType.BASIC:
|
case RouteCompleterType.BASIC:
|
||||||
const basicCandidateRoute = await this.query.routeProvider.getBasic(
|
const basicCandidateRoute = await this.query.routeProvider.getBasic(
|
||||||
(candidate.getProps().carpoolSteps as CarpoolStep[]).map(
|
(candidate.getProps().carpoolPath as CarpoolPathItem[]).map(
|
||||||
(carpoolStep: CarpoolStep) => carpoolStep.point,
|
(carpoolPathItem: CarpoolPathItem) => carpoolPathItem,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
candidate.setMetrics(
|
candidate.setMetrics(
|
||||||
|
@ -31,8 +31,8 @@ export class RouteCompleter extends Completer {
|
||||||
case RouteCompleterType.DETAILED:
|
case RouteCompleterType.DETAILED:
|
||||||
const detailedCandidateRoute =
|
const detailedCandidateRoute =
|
||||||
await this.query.routeProvider.getDetailed(
|
await this.query.routeProvider.getDetailed(
|
||||||
(candidate.getProps().carpoolSteps as CarpoolStep[]).map(
|
(candidate.getProps().carpoolPath as CarpoolPathItem[]).map(
|
||||||
(carpoolStep: CarpoolStep) => carpoolStep.point,
|
(carpoolPathItem: CarpoolPathItem) => carpoolPathItem,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
candidate.setSteps(detailedCandidateRoute.steps as Step[]);
|
candidate.setSteps(detailedCandidateRoute.steps as Step[]);
|
||||||
|
|
|
@ -153,7 +153,7 @@ export class MatchQuery extends QueryBase {
|
||||||
);
|
);
|
||||||
this.schedule = this.schedule.map((scheduleItem: ScheduleItem) => ({
|
this.schedule = this.schedule.map((scheduleItem: ScheduleItem) => ({
|
||||||
day: datetimeTransformer.day(
|
day: datetimeTransformer.day(
|
||||||
scheduleItem.day ?? new Date(this.fromDate).getDay(),
|
scheduleItem.day ?? new Date(this.fromDate).getUTCDay(),
|
||||||
{
|
{
|
||||||
date: this.fromDate,
|
date: this.fromDate,
|
||||||
time: scheduleItem.time,
|
time: scheduleItem.time,
|
||||||
|
|
|
@ -36,6 +36,16 @@ export class PassengerOrientedSelector extends Selector {
|
||||||
CandidateEntity.create({
|
CandidateEntity.create({
|
||||||
id: adEntity.id,
|
id: adEntity.id,
|
||||||
role: adsRole.role == Role.DRIVER ? Role.PASSENGER : Role.DRIVER,
|
role: adsRole.role == Role.DRIVER ? Role.PASSENGER : Role.DRIVER,
|
||||||
|
dateInterval: {
|
||||||
|
lowerDate: this._maxDateString(
|
||||||
|
this.query.fromDate,
|
||||||
|
adEntity.getProps().fromDate,
|
||||||
|
),
|
||||||
|
higherDate: this._minDateString(
|
||||||
|
this.query.toDate,
|
||||||
|
adEntity.getProps().toDate,
|
||||||
|
),
|
||||||
|
},
|
||||||
driverWaypoints:
|
driverWaypoints:
|
||||||
adsRole.role == Role.PASSENGER
|
adsRole.role == Role.PASSENGER
|
||||||
? adEntity.getProps().waypoints
|
? adEntity.getProps().waypoints
|
||||||
|
@ -173,7 +183,7 @@ export class PassengerOrientedSelector extends Selector {
|
||||||
scheduleDates.map((date: Date) => {
|
scheduleDates.map((date: Date) => {
|
||||||
this.query.schedule
|
this.query.schedule
|
||||||
.filter(
|
.filter(
|
||||||
(scheduleItem: ScheduleItem) => date.getDay() == scheduleItem.day,
|
(scheduleItem: ScheduleItem) => date.getUTCDay() == scheduleItem.day,
|
||||||
)
|
)
|
||||||
.map((scheduleItem: ScheduleItem) => {
|
.map((scheduleItem: ScheduleItem) => {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
|
@ -205,15 +215,15 @@ export class PassengerOrientedSelector extends Selector {
|
||||||
);
|
);
|
||||||
// we want the min departure time of the driver to be before the max departure time of the passenger
|
// we want the min departure time of the driver to be before the max departure time of the passenger
|
||||||
return `make_timestamp(\
|
return `make_timestamp(\
|
||||||
${maxDepartureDatetime.getFullYear()},\
|
${maxDepartureDatetime.getUTCFullYear()},\
|
||||||
${maxDepartureDatetime.getMonth() + 1},\
|
${maxDepartureDatetime.getUTCMonth() + 1},\
|
||||||
${maxDepartureDatetime.getDate()},\
|
${maxDepartureDatetime.getUTCDate()},\
|
||||||
CAST(EXTRACT(hour from time) as integer),\
|
CAST(EXTRACT(hour from time) as integer),\
|
||||||
CAST(EXTRACT(minute from time) as integer),0) - interval '1 second' * margin <=\
|
CAST(EXTRACT(minute from time) as integer),0) - interval '1 second' * margin <=\
|
||||||
make_timestamp(\
|
make_timestamp(\
|
||||||
${maxDepartureDatetime.getFullYear()},\
|
${maxDepartureDatetime.getUTCFullYear()},\
|
||||||
${maxDepartureDatetime.getMonth() + 1},\
|
${maxDepartureDatetime.getUTCMonth() + 1},\
|
||||||
${maxDepartureDatetime.getDate()},${maxDepartureDatetime.getHours()},${maxDepartureDatetime.getMinutes()},0)`;
|
${maxDepartureDatetime.getUTCDate()},${maxDepartureDatetime.getUTCHours()},${maxDepartureDatetime.getUTCMinutes()},0)`;
|
||||||
};
|
};
|
||||||
|
|
||||||
private _whereDriverSchedule = (
|
private _whereDriverSchedule = (
|
||||||
|
@ -229,15 +239,15 @@ export class PassengerOrientedSelector extends Selector {
|
||||||
);
|
);
|
||||||
// we want the max departure time of the passenger to be after the min departure time of the driver
|
// we want the max departure time of the passenger to be after the min departure time of the driver
|
||||||
return `make_timestamp(\
|
return `make_timestamp(\
|
||||||
${minDepartureDatetime.getFullYear()},
|
${minDepartureDatetime.getUTCFullYear()},
|
||||||
${minDepartureDatetime.getMonth() + 1},
|
${minDepartureDatetime.getUTCMonth() + 1},
|
||||||
${minDepartureDatetime.getDate()},\
|
${minDepartureDatetime.getUTCDate()},\
|
||||||
CAST(EXTRACT(hour from time) as integer),\
|
CAST(EXTRACT(hour from time) as integer),\
|
||||||
CAST(EXTRACT(minute from time) as integer),0) + interval '1 second' * margin >=\
|
CAST(EXTRACT(minute from time) as integer),0) + interval '1 second' * margin >=\
|
||||||
make_timestamp(\
|
make_timestamp(\
|
||||||
${minDepartureDatetime.getFullYear()},
|
${minDepartureDatetime.getUTCFullYear()},
|
||||||
${minDepartureDatetime.getMonth() + 1},
|
${minDepartureDatetime.getUTCMonth() + 1},
|
||||||
${minDepartureDatetime.getDate()},${minDepartureDatetime.getHours()},${minDepartureDatetime.getMinutes()},0)`;
|
${minDepartureDatetime.getUTCDate()},${minDepartureDatetime.getUTCHours()},${minDepartureDatetime.getUTCMinutes()},0)`;
|
||||||
};
|
};
|
||||||
|
|
||||||
private _whereAzimuth = (): string => {
|
private _whereAzimuth = (): string => {
|
||||||
|
@ -311,7 +321,7 @@ export class PassengerOrientedSelector extends Selector {
|
||||||
for (
|
for (
|
||||||
let date = fromDate;
|
let date = fromDate;
|
||||||
date <= toDate;
|
date <= toDate;
|
||||||
date.setDate(date.getDate() + 1)
|
date.setUTCDate(date.getUTCDate() + 1)
|
||||||
) {
|
) {
|
||||||
dates.push(new Date(date));
|
dates.push(new Date(date));
|
||||||
count++;
|
count++;
|
||||||
|
@ -321,7 +331,7 @@ export class PassengerOrientedSelector extends Selector {
|
||||||
};
|
};
|
||||||
|
|
||||||
private _addMargin = (date: Date, marginInSeconds: number): Date => {
|
private _addMargin = (date: Date, marginInSeconds: number): Date => {
|
||||||
date.setTime(date.getTime() + marginInSeconds * 1000);
|
date.setUTCSeconds(marginInSeconds);
|
||||||
return date;
|
return date;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -334,6 +344,12 @@ export class PassengerOrientedSelector extends Selector {
|
||||||
maxAzimuth:
|
maxAzimuth:
|
||||||
azimuth + margin > 360 ? azimuth + margin - 360 : azimuth + margin,
|
azimuth + margin > 360 ? azimuth + margin - 360 : azimuth + margin,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private _maxDateString = (date1: string, date2: string): string =>
|
||||||
|
new Date(date1) > new Date(date2) ? date1 : date2;
|
||||||
|
|
||||||
|
private _minDateString = (date1: string, date2: string): string =>
|
||||||
|
new Date(date1) < new Date(date2) ? date1 : date2;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type QueryStringRole = {
|
export type QueryStringRole = {
|
||||||
|
|
|
@ -1,31 +1,29 @@
|
||||||
import { ExceptionBase } from '@mobicoop/ddd-library';
|
import { ExceptionBase } from '@mobicoop/ddd-library';
|
||||||
|
import { DateInterval } from './candidate.types';
|
||||||
|
|
||||||
export class CalendarTools {
|
export class CalendarTools {
|
||||||
/**
|
/**
|
||||||
* Returns the first date corresponding to a week day (0 based monday)
|
* Returns the first date corresponding to a week day (0 based monday)
|
||||||
* within a date range
|
* within a date range
|
||||||
*/
|
*/
|
||||||
static firstDate = (
|
static firstDate = (weekDay: number, dateInterval: DateInterval): Date => {
|
||||||
weekDay: number,
|
|
||||||
lowerDate: string,
|
|
||||||
higherDate: string,
|
|
||||||
): Date => {
|
|
||||||
if (weekDay < 0 || weekDay > 6)
|
if (weekDay < 0 || weekDay > 6)
|
||||||
throw new CalendarToolsException(
|
throw new CalendarToolsException(
|
||||||
new Error('weekDay must be between 0 and 6'),
|
new Error('weekDay must be between 0 and 6'),
|
||||||
);
|
);
|
||||||
const lowerDateAsDate: Date = new Date(lowerDate);
|
const lowerDateAsDate: Date = new Date(dateInterval.lowerDate);
|
||||||
const higherDateAsDate: Date = new Date(higherDate);
|
const higherDateAsDate: Date = new Date(dateInterval.higherDate);
|
||||||
if (lowerDateAsDate.getDay() == weekDay) return lowerDateAsDate;
|
if (lowerDateAsDate.getUTCDay() == weekDay) return lowerDateAsDate;
|
||||||
const nextDate: Date = new Date(lowerDateAsDate);
|
const nextDate: Date = new Date(lowerDateAsDate);
|
||||||
nextDate.setDate(
|
nextDate.setUTCDate(
|
||||||
lowerDateAsDate.getDate() + (7 - (lowerDateAsDate.getDay() - weekDay)),
|
lowerDateAsDate.getUTCDate() +
|
||||||
|
(7 - (lowerDateAsDate.getUTCDay() - weekDay)),
|
||||||
);
|
);
|
||||||
if (lowerDateAsDate.getDay() < weekDay) {
|
if (lowerDateAsDate.getUTCDay() < weekDay) {
|
||||||
nextDate.setMonth(lowerDateAsDate.getMonth());
|
nextDate.setUTCMonth(lowerDateAsDate.getUTCMonth());
|
||||||
nextDate.setFullYear(lowerDateAsDate.getFullYear());
|
nextDate.setUTCFullYear(lowerDateAsDate.getUTCFullYear());
|
||||||
nextDate.setDate(
|
nextDate.setUTCDate(
|
||||||
lowerDateAsDate.getDate() + (weekDay - lowerDateAsDate.getDay()),
|
lowerDateAsDate.getUTCDate() + (weekDay - lowerDateAsDate.getUTCDay()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (nextDate <= higherDateAsDate) return nextDate;
|
if (nextDate <= higherDateAsDate) return nextDate;
|
||||||
|
@ -38,28 +36,24 @@ export class CalendarTools {
|
||||||
* Returns the last date corresponding to a week day (0 based monday)
|
* Returns the last date corresponding to a week day (0 based monday)
|
||||||
* within a date range
|
* within a date range
|
||||||
*/
|
*/
|
||||||
static lastDate = (
|
static lastDate = (weekDay: number, dateInterval: DateInterval): Date => {
|
||||||
weekDay: number,
|
|
||||||
lowerDate: string,
|
|
||||||
higherDate: string,
|
|
||||||
): Date => {
|
|
||||||
if (weekDay < 0 || weekDay > 6)
|
if (weekDay < 0 || weekDay > 6)
|
||||||
throw new CalendarToolsException(
|
throw new CalendarToolsException(
|
||||||
new Error('weekDay must be between 0 and 6'),
|
new Error('weekDay must be between 0 and 6'),
|
||||||
);
|
);
|
||||||
const lowerDateAsDate: Date = new Date(lowerDate);
|
const lowerDateAsDate: Date = new Date(dateInterval.lowerDate);
|
||||||
const higherDateAsDate: Date = new Date(higherDate);
|
const higherDateAsDate: Date = new Date(dateInterval.higherDate);
|
||||||
if (higherDateAsDate.getDay() == weekDay) return higherDateAsDate;
|
if (higherDateAsDate.getUTCDay() == weekDay) return higherDateAsDate;
|
||||||
const previousDate: Date = new Date(higherDateAsDate);
|
const previousDate: Date = new Date(higherDateAsDate);
|
||||||
previousDate.setDate(
|
previousDate.setUTCDate(
|
||||||
higherDateAsDate.getDate() - (higherDateAsDate.getDay() - weekDay),
|
higherDateAsDate.getUTCDate() - (higherDateAsDate.getUTCDay() - weekDay),
|
||||||
);
|
);
|
||||||
if (higherDateAsDate.getDay() < weekDay) {
|
if (higherDateAsDate.getUTCDay() < weekDay) {
|
||||||
previousDate.setMonth(higherDateAsDate.getMonth());
|
previousDate.setUTCMonth(higherDateAsDate.getUTCMonth());
|
||||||
previousDate.setFullYear(higherDateAsDate.getFullYear());
|
previousDate.setUTCFullYear(higherDateAsDate.getUTCFullYear());
|
||||||
previousDate.setDate(
|
previousDate.setUTCDate(
|
||||||
higherDateAsDate.getDate() -
|
higherDateAsDate.getUTCDate() -
|
||||||
(7 + (higherDateAsDate.getDay() - weekDay)),
|
(7 + (higherDateAsDate.getUTCDay() - weekDay)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (previousDate >= lowerDateAsDate) return previousDate;
|
if (previousDate >= lowerDateAsDate) return previousDate;
|
||||||
|
@ -67,6 +61,55 @@ export class CalendarTools {
|
||||||
new Error('no available day for the given date range'),
|
new Error('no available day for the given date range'),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a date from a date and time as strings, adding optional seconds
|
||||||
|
*/
|
||||||
|
static datetimeFromString = (
|
||||||
|
date: string,
|
||||||
|
time: string,
|
||||||
|
additionalSeconds = 0,
|
||||||
|
): Date => {
|
||||||
|
const datetime = new Date(`${date}T${time}:00Z`);
|
||||||
|
datetime.setUTCSeconds(additionalSeconds);
|
||||||
|
return datetime;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns dates from a day and time based on unix epoch day
|
||||||
|
* (1970-01-01 is day 4)
|
||||||
|
* The method returns an array of dates because for edges (day 0 and 6)
|
||||||
|
* we need to return 2 possibilities
|
||||||
|
*/
|
||||||
|
static epochDaysFromTime = (weekDay: number, time: string): Date[] => {
|
||||||
|
if (weekDay < 0 || weekDay > 6)
|
||||||
|
throw new CalendarToolsException(
|
||||||
|
new Error('weekDay must be between 0 and 6'),
|
||||||
|
);
|
||||||
|
switch (weekDay) {
|
||||||
|
case 0:
|
||||||
|
return [
|
||||||
|
new Date(`1969-12-28T${time}:00Z`),
|
||||||
|
new Date(`1970-01-04T${time}:00Z`),
|
||||||
|
];
|
||||||
|
case 1:
|
||||||
|
return [new Date(`1969-12-29T${time}:00Z`)];
|
||||||
|
case 2:
|
||||||
|
return [new Date(`1969-12-30T${time}:00Z`)];
|
||||||
|
case 3:
|
||||||
|
return [new Date(`1969-12-31T${time}:00Z`)];
|
||||||
|
case 5:
|
||||||
|
return [new Date(`1970-01-02T${time}:00Z`)];
|
||||||
|
case 6:
|
||||||
|
return [
|
||||||
|
new Date(`1969-12-27T${time}:00Z`),
|
||||||
|
new Date(`1970-01-03T${time}:00Z`),
|
||||||
|
];
|
||||||
|
case 4:
|
||||||
|
default:
|
||||||
|
return [new Date(`1970-01-01T${time}:00Z`)];
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CalendarToolsException extends ExceptionBase {
|
export class CalendarToolsException extends ExceptionBase {
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
||||||
import { CandidateProps, CreateCandidateProps } from './candidate.types';
|
import {
|
||||||
import { CarpoolStepProps } from './value-objects/carpool-step.value-object';
|
CandidateProps,
|
||||||
import { StepProps } from './value-objects/step.value-object';
|
CreateCandidateProps,
|
||||||
|
Target,
|
||||||
|
} from './candidate.types';
|
||||||
|
import {
|
||||||
|
CarpoolPathItem,
|
||||||
|
CarpoolPathItemProps,
|
||||||
|
} from './value-objects/carpool-path-item.value-object';
|
||||||
|
import { Step, StepProps } from './value-objects/step.value-object';
|
||||||
import { ScheduleItem } from './value-objects/schedule-item.value-object';
|
import { ScheduleItem } from './value-objects/schedule-item.value-object';
|
||||||
import { Journey } from './value-objects/journey.value-object';
|
import { Journey } from './value-objects/journey.value-object';
|
||||||
import { CalendarTools } from './calendar-tools.service';
|
import { CalendarTools } from './calendar-tools.service';
|
||||||
import { JourneyItem } from './value-objects/journey-item.value-object';
|
import { JourneyItem } from './value-objects/journey-item.value-object';
|
||||||
|
import { Actor } from './value-objects/actor.value-object';
|
||||||
|
import { ActorTime } from './value-objects/actor-time.value-object';
|
||||||
|
import { Role } from './ad.types';
|
||||||
|
|
||||||
export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
||||||
protected readonly _id: AggregateID;
|
protected readonly _id: AggregateID;
|
||||||
|
@ -15,8 +25,8 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
||||||
return new CandidateEntity({ id: create.id, props });
|
return new CandidateEntity({ id: create.id, props });
|
||||||
};
|
};
|
||||||
|
|
||||||
setCarpoolPath = (carpoolSteps: CarpoolStepProps[]): CandidateEntity => {
|
setCarpoolPath = (carpoolPath: CarpoolPathItemProps[]): CandidateEntity => {
|
||||||
this.props.carpoolSteps = carpoolSteps;
|
this.props.carpoolPath = carpoolPath;
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,21 +44,14 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
||||||
isDetourValid = (): boolean =>
|
isDetourValid = (): boolean =>
|
||||||
this._validateDistanceDetour() && this._validateDurationDetour();
|
this._validateDistanceDetour() && this._validateDurationDetour();
|
||||||
|
|
||||||
createJourneys = (fromDate: string, toDate: string): CandidateEntity => {
|
/**
|
||||||
this.props.driverJourneys = this.props.driverSchedule
|
* Create the journeys based on the driver schedule (the driver 'drives' the carpool !)
|
||||||
.map((driverScheduleItem: ScheduleItem) =>
|
*/
|
||||||
this._createJourney(fromDate, toDate, driverScheduleItem),
|
createJourneys = (): CandidateEntity => {
|
||||||
)
|
this.props.journeys = this.props.driverSchedule.map(
|
||||||
.filter(
|
(driverScheduleItem: ScheduleItem) =>
|
||||||
(journey: Journey | undefined) => journey !== undefined,
|
this._createJourney(driverScheduleItem),
|
||||||
) as Journey[];
|
);
|
||||||
this.props.passengerJourneys = this.props.passengerSchedule
|
|
||||||
.map((passengerScheduleItem: ScheduleItem) =>
|
|
||||||
this._createJourney(fromDate, toDate, passengerScheduleItem),
|
|
||||||
)
|
|
||||||
.filter(
|
|
||||||
(journey: Journey | undefined) => journey !== undefined,
|
|
||||||
) as Journey[];
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -66,23 +69,159 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
||||||
(1 + this.props.spacetimeDetourRatio.maxDistanceDetourRatio)
|
(1 + this.props.spacetimeDetourRatio.maxDistanceDetourRatio)
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
private _createJourney = (
|
private _createJourney = (driverScheduleItem: ScheduleItem): Journey =>
|
||||||
fromDate: string,
|
|
||||||
toDate: string,
|
|
||||||
scheduleItem: ScheduleItem,
|
|
||||||
): Journey | undefined =>
|
|
||||||
new Journey({
|
new Journey({
|
||||||
day: scheduleItem.day,
|
firstDate: CalendarTools.firstDate(
|
||||||
firstDate: CalendarTools.firstDate(scheduleItem.day, fromDate, toDate),
|
driverScheduleItem.day,
|
||||||
lastDate: CalendarTools.lastDate(scheduleItem.day, fromDate, toDate),
|
this.props.dateInterval,
|
||||||
journeyItems: this._createJourneyItems(scheduleItem),
|
),
|
||||||
|
lastDate: CalendarTools.lastDate(
|
||||||
|
driverScheduleItem.day,
|
||||||
|
this.props.dateInterval,
|
||||||
|
),
|
||||||
|
journeyItems: this._createJourneyItems(driverScheduleItem),
|
||||||
});
|
});
|
||||||
|
|
||||||
private _createJourneyItems = (
|
private _createJourneyItems = (
|
||||||
scheduleItem: ScheduleItem,
|
driverScheduleItem: ScheduleItem,
|
||||||
): JourneyItem[] => [];
|
): JourneyItem[] =>
|
||||||
|
this.props.carpoolPath?.map(
|
||||||
|
(carpoolPathItem: CarpoolPathItem, index: number) =>
|
||||||
|
this._createJourneyItem(carpoolPathItem, index, driverScheduleItem),
|
||||||
|
) as JourneyItem[];
|
||||||
|
|
||||||
|
private _createJourneyItem = (
|
||||||
|
carpoolPathItem: CarpoolPathItem,
|
||||||
|
stepIndex: number,
|
||||||
|
driverScheduleItem: ScheduleItem,
|
||||||
|
): JourneyItem =>
|
||||||
|
new JourneyItem({
|
||||||
|
lon: carpoolPathItem.lon,
|
||||||
|
lat: carpoolPathItem.lat,
|
||||||
|
duration: ((this.props.steps as Step[])[stepIndex] as Step).duration,
|
||||||
|
distance: ((this.props.steps as Step[])[stepIndex] as Step).distance,
|
||||||
|
actorTimes: carpoolPathItem.actors.map((actor: Actor) =>
|
||||||
|
this._createActorTime(
|
||||||
|
actor,
|
||||||
|
driverScheduleItem,
|
||||||
|
((this.props.steps as Step[])[stepIndex] as Step).duration,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
private _createActorTime = (
|
||||||
|
actor: Actor,
|
||||||
|
driverScheduleItem: ScheduleItem,
|
||||||
|
duration: number,
|
||||||
|
): ActorTime => {
|
||||||
|
const scheduleItem: ScheduleItem =
|
||||||
|
actor.role == Role.PASSENGER && actor.target == Target.START
|
||||||
|
? this._closestPassengerScheduleItem(driverScheduleItem)
|
||||||
|
: driverScheduleItem;
|
||||||
|
const effectiveDuration =
|
||||||
|
(actor.role == Role.PASSENGER && actor.target == Target.START) ||
|
||||||
|
actor.target == Target.START
|
||||||
|
? 0
|
||||||
|
: duration;
|
||||||
|
return new ActorTime({
|
||||||
|
role: actor.role,
|
||||||
|
target: actor.target,
|
||||||
|
firstDatetime: CalendarTools.datetimeFromString(
|
||||||
|
this.props.dateInterval.lowerDate,
|
||||||
|
scheduleItem.time,
|
||||||
|
effectiveDuration,
|
||||||
|
),
|
||||||
|
firstMinDatetime: CalendarTools.datetimeFromString(
|
||||||
|
this.props.dateInterval.lowerDate,
|
||||||
|
scheduleItem.time,
|
||||||
|
-scheduleItem.margin + effectiveDuration,
|
||||||
|
),
|
||||||
|
firstMaxDatetime: CalendarTools.datetimeFromString(
|
||||||
|
this.props.dateInterval.lowerDate,
|
||||||
|
scheduleItem.time,
|
||||||
|
scheduleItem.margin + effectiveDuration,
|
||||||
|
),
|
||||||
|
lastDatetime: CalendarTools.datetimeFromString(
|
||||||
|
this.props.dateInterval.higherDate,
|
||||||
|
scheduleItem.time,
|
||||||
|
effectiveDuration,
|
||||||
|
),
|
||||||
|
lastMinDatetime: CalendarTools.datetimeFromString(
|
||||||
|
this.props.dateInterval.higherDate,
|
||||||
|
scheduleItem.time,
|
||||||
|
-scheduleItem.margin + effectiveDuration,
|
||||||
|
),
|
||||||
|
lastMaxDatetime: CalendarTools.datetimeFromString(
|
||||||
|
this.props.dateInterval.higherDate,
|
||||||
|
scheduleItem.time,
|
||||||
|
scheduleItem.margin + effectiveDuration,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _closestPassengerScheduleItem = (
|
||||||
|
driverScheduleItem: ScheduleItem,
|
||||||
|
): ScheduleItem =>
|
||||||
|
CalendarTools.epochDaysFromTime(
|
||||||
|
driverScheduleItem.day,
|
||||||
|
driverScheduleItem.time,
|
||||||
|
)
|
||||||
|
.map((driverDate: Date) =>
|
||||||
|
this._minPassengerScheduleItemGapForDate(driverDate),
|
||||||
|
)
|
||||||
|
.reduce(
|
||||||
|
(
|
||||||
|
previousScheduleItemGap: ScheduleItemGap,
|
||||||
|
currentScheduleItemGap: ScheduleItemGap,
|
||||||
|
) =>
|
||||||
|
previousScheduleItemGap.gap < currentScheduleItemGap.gap
|
||||||
|
? previousScheduleItemGap
|
||||||
|
: currentScheduleItemGap,
|
||||||
|
).scheduleItem;
|
||||||
|
|
||||||
|
private _minPassengerScheduleItemGapForDate = (date: Date): ScheduleItemGap =>
|
||||||
|
this.props.passengerSchedule
|
||||||
|
.map(
|
||||||
|
(scheduleItem: ScheduleItem) =>
|
||||||
|
<ScheduleItemRange>{
|
||||||
|
scheduleItem,
|
||||||
|
range: CalendarTools.epochDaysFromTime(
|
||||||
|
scheduleItem.day,
|
||||||
|
scheduleItem.time,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map((scheduleItemRange: ScheduleItemRange) => ({
|
||||||
|
scheduleItem: scheduleItemRange.scheduleItem,
|
||||||
|
gap: scheduleItemRange.range
|
||||||
|
.map((scheduleDate: Date) =>
|
||||||
|
Math.round(Math.abs(scheduleDate.getTime() - date.getTime())),
|
||||||
|
)
|
||||||
|
.reduce((previousGap: number, currentGap: number) =>
|
||||||
|
previousGap < currentGap ? previousGap : currentGap,
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
.reduce(
|
||||||
|
(
|
||||||
|
previousScheduleItemGap: ScheduleItemGap,
|
||||||
|
currentScheduleItemGap: ScheduleItemGap,
|
||||||
|
) =>
|
||||||
|
previousScheduleItemGap.gap < currentScheduleItemGap.gap
|
||||||
|
? previousScheduleItemGap
|
||||||
|
: currentScheduleItemGap,
|
||||||
|
);
|
||||||
|
|
||||||
validate(): void {
|
validate(): void {
|
||||||
// entity business rules validation to protect it's invariant before saving entity to a database
|
// entity business rules validation to protect it's invariant before saving entity to a database
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ScheduleItemRange = {
|
||||||
|
scheduleItem: ScheduleItem;
|
||||||
|
range: Date[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type ScheduleItemGap = {
|
||||||
|
scheduleItem: ScheduleItem;
|
||||||
|
gap: number;
|
||||||
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Role } from './ad.types';
|
import { Role } from './ad.types';
|
||||||
import { PointProps } from './value-objects/point.value-object';
|
import { PointProps } from './value-objects/point.value-object';
|
||||||
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
|
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
|
||||||
import { CarpoolStepProps } from './value-objects/carpool-step.value-object';
|
import { CarpoolPathItemProps } from './value-objects/carpool-path-item.value-object';
|
||||||
import { JourneyProps } from './value-objects/journey.value-object';
|
import { JourneyProps } from './value-objects/journey.value-object';
|
||||||
import { StepProps } from './value-objects/step.value-object';
|
import { StepProps } from './value-objects/step.value-object';
|
||||||
|
|
||||||
|
@ -10,16 +10,16 @@ export interface CandidateProps {
|
||||||
role: Role;
|
role: Role;
|
||||||
driverWaypoints: PointProps[];
|
driverWaypoints: PointProps[];
|
||||||
passengerWaypoints: PointProps[];
|
passengerWaypoints: PointProps[];
|
||||||
|
driverSchedule: ScheduleItemProps[];
|
||||||
|
passengerSchedule: ScheduleItemProps[];
|
||||||
driverDistance: number;
|
driverDistance: number;
|
||||||
driverDuration: number;
|
driverDuration: number;
|
||||||
carpoolSteps?: CarpoolStepProps[];
|
dateInterval: DateInterval;
|
||||||
|
carpoolPath?: CarpoolPathItemProps[];
|
||||||
distance?: number;
|
distance?: number;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
steps?: StepProps[];
|
steps?: StepProps[];
|
||||||
driverSchedule: ScheduleItemProps[];
|
journeys?: JourneyProps[];
|
||||||
passengerSchedule: ScheduleItemProps[];
|
|
||||||
driverJourneys?: JourneyProps[];
|
|
||||||
passengerJourneys?: JourneyProps[];
|
|
||||||
spacetimeDetourRatio: SpacetimeDetourRatio;
|
spacetimeDetourRatio: SpacetimeDetourRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ export interface CreateCandidateProps {
|
||||||
driverSchedule: ScheduleItemProps[];
|
driverSchedule: ScheduleItemProps[];
|
||||||
passengerSchedule: ScheduleItemProps[];
|
passengerSchedule: ScheduleItemProps[];
|
||||||
spacetimeDetourRatio: SpacetimeDetourRatio;
|
spacetimeDetourRatio: SpacetimeDetourRatio;
|
||||||
|
dateInterval: DateInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Target {
|
export enum Target {
|
||||||
|
@ -47,12 +48,12 @@ export abstract class Validator {
|
||||||
abstract validate(): boolean;
|
abstract validate(): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SpacetimeMetric = {
|
|
||||||
distance: number;
|
|
||||||
duration: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SpacetimeDetourRatio = {
|
export type SpacetimeDetourRatio = {
|
||||||
maxDistanceDetourRatio: number;
|
maxDistanceDetourRatio: number;
|
||||||
maxDurationDetourRatio: number;
|
maxDurationDetourRatio: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DateInterval = {
|
||||||
|
lowerDate: string;
|
||||||
|
higherDate: string;
|
||||||
|
};
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Target } from './candidate.types';
|
||||||
import { CarpoolPathCreatorException } from './match.errors';
|
import { CarpoolPathCreatorException } from './match.errors';
|
||||||
import { Actor } from './value-objects/actor.value-object';
|
import { Actor } from './value-objects/actor.value-object';
|
||||||
import { Point } from './value-objects/point.value-object';
|
import { Point } from './value-objects/point.value-object';
|
||||||
import { CarpoolStep } from './value-objects/carpool-step.value-object';
|
import { CarpoolPathItem } from './value-objects/carpool-path-item.value-object';
|
||||||
|
|
||||||
export class CarpoolPathCreator {
|
export class CarpoolPathCreator {
|
||||||
private PRECISION = 5;
|
private PRECISION = 5;
|
||||||
|
@ -23,39 +23,34 @@ export class CarpoolPathCreator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a path (a list of carpoolSteps) between driver waypoints
|
* Creates a path (a list of carpoolPathItem) between driver waypoints
|
||||||
and passenger waypoints respecting the order
|
and passenger waypoints respecting the order
|
||||||
of the driver waypoints
|
of the driver waypoints
|
||||||
Inspired by :
|
Inspired by :
|
||||||
https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
|
https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
|
||||||
*/
|
*/
|
||||||
public carpoolPath = (): CarpoolStep[] =>
|
public carpoolPath = (): CarpoolPathItem[] =>
|
||||||
this._consolidate(
|
this._consolidate(
|
||||||
this._mixedCarpoolSteps(
|
this._mixedCarpoolPath(
|
||||||
this._driverCarpoolSteps(),
|
this._driverCarpoolPath(),
|
||||||
this._passengerCarpoolSteps(),
|
this._passengerCarpoolPath(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
private _mixedCarpoolSteps = (
|
private _mixedCarpoolPath = (
|
||||||
driverCarpoolSteps: CarpoolStep[],
|
driverCarpoolPath: CarpoolPathItem[],
|
||||||
passengerCarpoolSteps: CarpoolStep[],
|
passengerCarpoolPath: CarpoolPathItem[],
|
||||||
): CarpoolStep[] =>
|
): CarpoolPathItem[] =>
|
||||||
driverCarpoolSteps.length == 2
|
driverCarpoolPath.length == 2
|
||||||
? this._simpleMixedCarpoolSteps(driverCarpoolSteps, passengerCarpoolSteps)
|
? this._simpleMixedCarpoolPath(driverCarpoolPath, passengerCarpoolPath)
|
||||||
: this._complexMixedCarpoolSteps(
|
: this._complexMixedCarpoolPath(driverCarpoolPath, passengerCarpoolPath);
|
||||||
driverCarpoolSteps,
|
|
||||||
passengerCarpoolSteps,
|
|
||||||
);
|
|
||||||
|
|
||||||
private _driverCarpoolSteps = (): CarpoolStep[] =>
|
private _driverCarpoolPath = (): CarpoolPathItem[] =>
|
||||||
this.driverWaypoints.map(
|
this.driverWaypoints.map(
|
||||||
(waypoint: Point, index: number) =>
|
(waypoint: Point, index: number) =>
|
||||||
new CarpoolStep({
|
new CarpoolPathItem({
|
||||||
point: new Point({
|
lon: waypoint.lon,
|
||||||
lon: waypoint.lon,
|
lat: waypoint.lat,
|
||||||
lat: waypoint.lat,
|
|
||||||
}),
|
|
||||||
actors: [
|
actors: [
|
||||||
new Actor({
|
new Actor({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
@ -66,17 +61,15 @@ export class CarpoolPathCreator {
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the passenger carpoolSteps with original passenger waypoints, adding driver waypoints that are the same
|
* Creates the passenger carpoolPath with original passenger waypoints, adding driver waypoints that are the same
|
||||||
*/
|
*/
|
||||||
private _passengerCarpoolSteps = (): CarpoolStep[] => {
|
private _passengerCarpoolPath = (): CarpoolPathItem[] => {
|
||||||
const carpoolSteps: CarpoolStep[] = [];
|
const carpoolPath: CarpoolPathItem[] = [];
|
||||||
this.passengerWaypoints.forEach(
|
this.passengerWaypoints.forEach(
|
||||||
(passengerWaypoint: Point, index: number) => {
|
(passengerWaypoint: Point, index: number) => {
|
||||||
const carpoolStep: CarpoolStep = new CarpoolStep({
|
const carpoolPathItem: CarpoolPathItem = new CarpoolPathItem({
|
||||||
point: new Point({
|
lon: passengerWaypoint.lon,
|
||||||
lon: passengerWaypoint.lon,
|
lat: passengerWaypoint.lat,
|
||||||
lat: passengerWaypoint.lat,
|
|
||||||
}),
|
|
||||||
actors: [
|
actors: [
|
||||||
new Actor({
|
new Actor({
|
||||||
role: Role.PASSENGER,
|
role: Role.PASSENGER,
|
||||||
|
@ -89,78 +82,80 @@ export class CarpoolPathCreator {
|
||||||
passengerWaypoint.equals(driverWaypoint),
|
passengerWaypoint.equals(driverWaypoint),
|
||||||
).length == 0
|
).length == 0
|
||||||
) {
|
) {
|
||||||
carpoolStep.actors.push(
|
carpoolPathItem.actors.push(
|
||||||
new Actor({
|
new Actor({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
target: Target.NEUTRAL,
|
target: Target.NEUTRAL,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
carpoolSteps.push(carpoolStep);
|
carpoolPath.push(carpoolPathItem);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return carpoolSteps;
|
return carpoolPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
private _simpleMixedCarpoolSteps = (
|
private _simpleMixedCarpoolPath = (
|
||||||
driverCarpoolSteps: CarpoolStep[],
|
driverCarpoolPath: CarpoolPathItem[],
|
||||||
passengerCarpoolSteps: CarpoolStep[],
|
passengerCarpoolPath: CarpoolPathItem[],
|
||||||
): CarpoolStep[] => [
|
): CarpoolPathItem[] => [
|
||||||
driverCarpoolSteps[0],
|
driverCarpoolPath[0],
|
||||||
...passengerCarpoolSteps,
|
...passengerCarpoolPath,
|
||||||
driverCarpoolSteps[1],
|
driverCarpoolPath[1],
|
||||||
];
|
];
|
||||||
|
|
||||||
private _complexMixedCarpoolSteps = (
|
private _complexMixedCarpoolPath = (
|
||||||
driverCarpoolSteps: CarpoolStep[],
|
driverCarpoolPath: CarpoolPathItem[],
|
||||||
passengerCarpoolSteps: CarpoolStep[],
|
passengerCarpoolPath: CarpoolPathItem[],
|
||||||
): CarpoolStep[] => {
|
): CarpoolPathItem[] => {
|
||||||
let mixedCarpoolSteps: CarpoolStep[] = [...driverCarpoolSteps];
|
let mixedCarpoolPath: CarpoolPathItem[] = [...driverCarpoolPath];
|
||||||
const originInsertIndex: number = this._insertIndex(
|
const originInsertIndex: number = this._insertIndex(
|
||||||
passengerCarpoolSteps[0],
|
passengerCarpoolPath[0],
|
||||||
driverCarpoolSteps,
|
driverCarpoolPath,
|
||||||
);
|
);
|
||||||
mixedCarpoolSteps = [
|
mixedCarpoolPath = [
|
||||||
...mixedCarpoolSteps.slice(0, originInsertIndex),
|
...mixedCarpoolPath.slice(0, originInsertIndex),
|
||||||
passengerCarpoolSteps[0],
|
passengerCarpoolPath[0],
|
||||||
...mixedCarpoolSteps.slice(originInsertIndex),
|
...mixedCarpoolPath.slice(originInsertIndex),
|
||||||
];
|
];
|
||||||
const destinationInsertIndex: number =
|
const destinationInsertIndex: number =
|
||||||
this._insertIndex(
|
this._insertIndex(
|
||||||
passengerCarpoolSteps[passengerCarpoolSteps.length - 1],
|
passengerCarpoolPath[passengerCarpoolPath.length - 1],
|
||||||
driverCarpoolSteps,
|
driverCarpoolPath,
|
||||||
) + 1;
|
) + 1;
|
||||||
mixedCarpoolSteps = [
|
mixedCarpoolPath = [
|
||||||
...mixedCarpoolSteps.slice(0, destinationInsertIndex),
|
...mixedCarpoolPath.slice(0, destinationInsertIndex),
|
||||||
passengerCarpoolSteps[passengerCarpoolSteps.length - 1],
|
passengerCarpoolPath[passengerCarpoolPath.length - 1],
|
||||||
...mixedCarpoolSteps.slice(destinationInsertIndex),
|
...mixedCarpoolPath.slice(destinationInsertIndex),
|
||||||
];
|
];
|
||||||
return mixedCarpoolSteps;
|
return mixedCarpoolPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
private _insertIndex = (
|
private _insertIndex = (
|
||||||
targetCarpoolStep: CarpoolStep,
|
targetCarpoolPathItem: CarpoolPathItem,
|
||||||
carpoolSteps: CarpoolStep[],
|
carpoolPath: CarpoolPathItem[],
|
||||||
): number =>
|
): number =>
|
||||||
this._closestSegmentIndex(targetCarpoolStep, this._segments(carpoolSteps)) +
|
this._closestSegmentIndex(
|
||||||
1;
|
targetCarpoolPathItem,
|
||||||
|
this._segments(carpoolPath),
|
||||||
|
) + 1;
|
||||||
|
|
||||||
private _segments = (carpoolSteps: CarpoolStep[]): CarpoolStep[][] => {
|
private _segments = (carpoolPath: CarpoolPathItem[]): CarpoolPathItem[][] => {
|
||||||
const segments: CarpoolStep[][] = [];
|
const segments: CarpoolPathItem[][] = [];
|
||||||
carpoolSteps.forEach((carpoolStep: CarpoolStep, index: number) => {
|
carpoolPath.forEach((carpoolPathItem: CarpoolPathItem, index: number) => {
|
||||||
if (index < carpoolSteps.length - 1)
|
if (index < carpoolPath.length - 1)
|
||||||
segments.push([carpoolStep, carpoolSteps[index + 1]]);
|
segments.push([carpoolPathItem, carpoolPath[index + 1]]);
|
||||||
});
|
});
|
||||||
return segments;
|
return segments;
|
||||||
};
|
};
|
||||||
|
|
||||||
private _closestSegmentIndex = (
|
private _closestSegmentIndex = (
|
||||||
carpoolStep: CarpoolStep,
|
carpoolPathItem: CarpoolPathItem,
|
||||||
segments: CarpoolStep[][],
|
segments: CarpoolPathItem[][],
|
||||||
): number => {
|
): number => {
|
||||||
const distances: Map<number, number> = new Map();
|
const distances: Map<number, number> = new Map();
|
||||||
segments.forEach((segment: CarpoolStep[], index: number) => {
|
segments.forEach((segment: CarpoolPathItem[], index: number) => {
|
||||||
distances.set(index, this._distanceToSegment(carpoolStep, segment));
|
distances.set(index, this._distanceToSegment(carpoolPathItem, segment));
|
||||||
});
|
});
|
||||||
const sortedDistances: Map<number, number> = new Map(
|
const sortedDistances: Map<number, number> = new Map(
|
||||||
[...distances.entries()].sort((a, b) => a[1] - b[1]),
|
[...distances.entries()].sort((a, b) => a[1] - b[1]),
|
||||||
|
@ -170,45 +165,62 @@ export class CarpoolPathCreator {
|
||||||
};
|
};
|
||||||
|
|
||||||
private _distanceToSegment = (
|
private _distanceToSegment = (
|
||||||
carpoolStep: CarpoolStep,
|
carpoolPathItem: CarpoolPathItem,
|
||||||
segment: CarpoolStep[],
|
segment: CarpoolPathItem[],
|
||||||
): number =>
|
): number =>
|
||||||
parseFloat(
|
parseFloat(
|
||||||
Math.sqrt(this._distanceToSegmentSquared(carpoolStep, segment)).toFixed(
|
Math.sqrt(
|
||||||
this.PRECISION,
|
this._distanceToSegmentSquared(carpoolPathItem, segment),
|
||||||
),
|
).toFixed(this.PRECISION),
|
||||||
);
|
);
|
||||||
|
|
||||||
private _distanceToSegmentSquared = (
|
private _distanceToSegmentSquared = (
|
||||||
carpoolStep: CarpoolStep,
|
carpoolPathItem: CarpoolPathItem,
|
||||||
segment: CarpoolStep[],
|
segment: CarpoolPathItem[],
|
||||||
): number => {
|
): number => {
|
||||||
const length2: number = this._distanceSquared(
|
const length2: number = this._distanceSquared(
|
||||||
segment[0].point,
|
new Point({
|
||||||
segment[1].point,
|
lon: segment[0].lon,
|
||||||
|
lat: segment[0].lat,
|
||||||
|
}),
|
||||||
|
new Point({
|
||||||
|
lon: segment[1].lon,
|
||||||
|
lat: segment[1].lat,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
if (length2 == 0)
|
if (length2 == 0)
|
||||||
return this._distanceSquared(carpoolStep.point, segment[0].point);
|
return this._distanceSquared(
|
||||||
|
new Point({
|
||||||
|
lon: carpoolPathItem.lon,
|
||||||
|
lat: carpoolPathItem.lat,
|
||||||
|
}),
|
||||||
|
new Point({
|
||||||
|
lon: segment[0].lon,
|
||||||
|
lat: segment[0].lat,
|
||||||
|
}),
|
||||||
|
);
|
||||||
const length: number = Math.max(
|
const length: number = Math.max(
|
||||||
0,
|
0,
|
||||||
Math.min(
|
Math.min(
|
||||||
1,
|
1,
|
||||||
((carpoolStep.point.lon - segment[0].point.lon) *
|
((carpoolPathItem.lon - segment[0].lon) *
|
||||||
(segment[1].point.lon - segment[0].point.lon) +
|
(segment[1].lon - segment[0].lon) +
|
||||||
(carpoolStep.point.lat - segment[0].point.lat) *
|
(carpoolPathItem.lat - segment[0].lat) *
|
||||||
(segment[1].point.lat - segment[0].point.lat)) /
|
(segment[1].lat - segment[0].lat)) /
|
||||||
length2,
|
length2,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const newPoint: Point = new Point({
|
const newPoint: Point = new Point({
|
||||||
lon:
|
lon: segment[0].lon + length * (segment[1].lon - segment[0].lon),
|
||||||
segment[0].point.lon +
|
lat: segment[0].lat + length * (segment[1].lat - segment[0].lat),
|
||||||
length * (segment[1].point.lon - segment[0].point.lon),
|
|
||||||
lat:
|
|
||||||
segment[0].point.lat +
|
|
||||||
length * (segment[1].point.lat - segment[0].point.lat),
|
|
||||||
});
|
});
|
||||||
return this._distanceSquared(carpoolStep.point, newPoint);
|
return this._distanceSquared(
|
||||||
|
new Point({
|
||||||
|
lon: carpoolPathItem.lon,
|
||||||
|
lat: carpoolPathItem.lat,
|
||||||
|
}),
|
||||||
|
newPoint,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
private _distanceSquared = (point1: Point, point2: Point): number =>
|
private _distanceSquared = (point1: Point, point2: Point): number =>
|
||||||
|
@ -227,31 +239,43 @@ export class CarpoolPathCreator {
|
||||||
: Target.INTERMEDIATE;
|
: Target.INTERMEDIATE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consolidate carpoolSteps by removing duplicate actors (eg. driver with neutral and start or finish target)
|
* Consolidate carpoolPath by removing duplicate actors (eg. driver with neutral and start or finish target)
|
||||||
*/
|
*/
|
||||||
private _consolidate = (carpoolSteps: CarpoolStep[]): CarpoolStep[] => {
|
private _consolidate = (
|
||||||
|
carpoolPath: CarpoolPathItem[],
|
||||||
|
): CarpoolPathItem[] => {
|
||||||
const uniquePoints: Point[] = [];
|
const uniquePoints: Point[] = [];
|
||||||
carpoolSteps.forEach((carpoolStep: CarpoolStep) => {
|
carpoolPath.forEach((carpoolPathItem: CarpoolPathItem) => {
|
||||||
if (
|
if (
|
||||||
uniquePoints.find((point: Point) => point.equals(carpoolStep.point)) ===
|
uniquePoints.find((point: Point) =>
|
||||||
undefined
|
point.equals(
|
||||||
|
new Point({
|
||||||
|
lon: carpoolPathItem.lon,
|
||||||
|
lat: carpoolPathItem.lat,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
) === undefined
|
||||||
)
|
)
|
||||||
uniquePoints.push(
|
uniquePoints.push(
|
||||||
new Point({
|
new Point({
|
||||||
lon: carpoolStep.point.lon,
|
lon: carpoolPathItem.lon,
|
||||||
lat: carpoolStep.point.lat,
|
lat: carpoolPathItem.lat,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return uniquePoints.map(
|
return uniquePoints.map(
|
||||||
(point: Point) =>
|
(point: Point) =>
|
||||||
new CarpoolStep({
|
new CarpoolPathItem({
|
||||||
point,
|
lon: point.lon,
|
||||||
actors: carpoolSteps
|
lat: point.lat,
|
||||||
.filter((carpoolStep: CarpoolStep) =>
|
actors: carpoolPath
|
||||||
carpoolStep.point.equals(point),
|
.filter((carpoolPathItem: CarpoolPathItem) =>
|
||||||
|
new Point({
|
||||||
|
lon: carpoolPathItem.lon,
|
||||||
|
lat: carpoolPathItem.lat,
|
||||||
|
}).equals(point),
|
||||||
)
|
)
|
||||||
.map((carpoolStep: CarpoolStep) => carpoolStep.actors)
|
.map((carpoolPathItem: CarpoolPathItem) => carpoolPathItem.actors)
|
||||||
.flat(),
|
.flat(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
@ -56,7 +56,7 @@ export class ActorTime extends ValueObject<ActorTimeProps> {
|
||||||
role: props.role,
|
role: props.role,
|
||||||
target: props.target,
|
target: props.target,
|
||||||
});
|
});
|
||||||
if (props.firstDatetime.getDay() != props.lastDatetime.getDay())
|
if (props.firstDatetime.getUTCDay() != props.lastDatetime.getUTCDay())
|
||||||
throw new ArgumentInvalidException(
|
throw new ArgumentInvalidException(
|
||||||
'firstDatetime week day must be equal to lastDatetime week day',
|
'firstDatetime week day must be equal to lastDatetime week day',
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,28 +4,36 @@ import {
|
||||||
} from '@mobicoop/ddd-library';
|
} from '@mobicoop/ddd-library';
|
||||||
import { Actor } from './actor.value-object';
|
import { Actor } from './actor.value-object';
|
||||||
import { Role } from '../ad.types';
|
import { Role } from '../ad.types';
|
||||||
import { Point } from './point.value-object';
|
import { Point, PointProps } from './point.value-object';
|
||||||
|
|
||||||
/** Note:
|
/** Note:
|
||||||
* Value Objects with multiple properties can contain
|
* Value Objects with multiple properties can contain
|
||||||
* other Value Objects inside if needed.
|
* other Value Objects inside if needed.
|
||||||
* */
|
* */
|
||||||
|
|
||||||
export interface CarpoolStepProps {
|
export interface CarpoolPathItemProps extends PointProps {
|
||||||
point: Point;
|
|
||||||
actors: Actor[];
|
actors: Actor[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CarpoolStep extends ValueObject<CarpoolStepProps> {
|
export class CarpoolPathItem extends ValueObject<CarpoolPathItemProps> {
|
||||||
get point(): Point {
|
get lon(): number {
|
||||||
return this.props.point;
|
return this.props.lon;
|
||||||
|
}
|
||||||
|
|
||||||
|
get lat(): number {
|
||||||
|
return this.props.lat;
|
||||||
}
|
}
|
||||||
|
|
||||||
get actors(): Actor[] {
|
get actors(): Actor[] {
|
||||||
return this.props.actors;
|
return this.props.actors;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected validate(props: CarpoolStepProps): void {
|
protected validate(props: CarpoolPathItemProps): void {
|
||||||
|
// validate point props
|
||||||
|
new Point({
|
||||||
|
lon: props.lon,
|
||||||
|
lat: props.lat,
|
||||||
|
});
|
||||||
if (props.actors.length <= 0)
|
if (props.actors.length <= 0)
|
||||||
throw new ArgumentOutOfRangeException('at least one actor is required');
|
throw new ArgumentOutOfRangeException('at least one actor is required');
|
||||||
if (
|
if (
|
|
@ -1,8 +1,4 @@
|
||||||
import {
|
import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
|
||||||
ArgumentInvalidException,
|
|
||||||
ArgumentOutOfRangeException,
|
|
||||||
ValueObject,
|
|
||||||
} from '@mobicoop/ddd-library';
|
|
||||||
import { JourneyItem } from './journey-item.value-object';
|
import { JourneyItem } from './journey-item.value-object';
|
||||||
|
|
||||||
/** Note:
|
/** Note:
|
||||||
|
@ -13,7 +9,6 @@ import { JourneyItem } from './journey-item.value-object';
|
||||||
export interface JourneyProps {
|
export interface JourneyProps {
|
||||||
firstDate: Date;
|
firstDate: Date;
|
||||||
lastDate: Date;
|
lastDate: Date;
|
||||||
day: number;
|
|
||||||
journeyItems: JourneyItem[];
|
journeyItems: JourneyItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,18 +21,12 @@ export class Journey extends ValueObject<JourneyProps> {
|
||||||
return this.props.lastDate;
|
return this.props.lastDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
get day(): number {
|
|
||||||
return this.props.day;
|
|
||||||
}
|
|
||||||
|
|
||||||
get journeyItems(): JourneyItem[] {
|
get journeyItems(): JourneyItem[] {
|
||||||
return this.props.journeyItems;
|
return this.props.journeyItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected validate(props: JourneyProps): void {
|
protected validate(props: JourneyProps): void {
|
||||||
if (props.day < 0 || props.day > 6)
|
if (props.firstDate.getUTCDay() != props.lastDate.getUTCDay())
|
||||||
throw new ArgumentOutOfRangeException('day must be between 0 and 6');
|
|
||||||
if (props.firstDate.getDay() != props.lastDate.getDay())
|
|
||||||
throw new ArgumentInvalidException(
|
throw new ArgumentInvalidException(
|
||||||
'firstDate week day must be equal to lastDate week day',
|
'firstDate week day must be equal to lastDate week day',
|
||||||
);
|
);
|
||||||
|
|
|
@ -79,7 +79,7 @@ export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
||||||
this._defaultTimezone,
|
this._defaultTimezone,
|
||||||
)[0],
|
)[0],
|
||||||
);
|
);
|
||||||
return new Date(this.fromDate(geoFromDate, frequency)).getDay();
|
return new Date(this.fromDate(geoFromDate, frequency)).getUTCDay();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -45,7 +45,7 @@ export class TimeConverter implements TimeConverterPort {
|
||||||
.convert(TimeZone.zone('UTC'))
|
.convert(TimeZone.zone('UTC'))
|
||||||
.toIsoString()
|
.toIsoString()
|
||||||
.split('T')[0],
|
.split('T')[0],
|
||||||
).getDay();
|
).getUTCDay();
|
||||||
|
|
||||||
localUnixEpochDayFromTime = (time: string, timezone: string): number =>
|
localUnixEpochDayFromTime = (time: string, timezone: string): number =>
|
||||||
new Date(
|
new Date(
|
||||||
|
@ -53,5 +53,5 @@ export class TimeConverter implements TimeConverterPort {
|
||||||
.convert(TimeZone.zone(timezone))
|
.convert(TimeZone.zone(timezone))
|
||||||
.toIsoString()
|
.toIsoString()
|
||||||
.split('T')[0],
|
.split('T')[0],
|
||||||
).getDay();
|
).getUTCDay();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,21 +8,21 @@ describe('Actor time value object', () => {
|
||||||
const actorTimeVO = new ActorTime({
|
const actorTimeVO = new ActorTime({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
target: Target.START,
|
target: Target.START,
|
||||||
firstDatetime: new Date('2023-09-01 07:00'),
|
firstDatetime: new Date('2023-09-01T07:00Z'),
|
||||||
firstMinDatetime: new Date('2023-09-01 06:45'),
|
firstMinDatetime: new Date('2023-09-01T06:45Z'),
|
||||||
firstMaxDatetime: new Date('2023-09-01 07:15'),
|
firstMaxDatetime: new Date('2023-09-01T07:15Z'),
|
||||||
lastDatetime: new Date('2024-08-30 07:00'),
|
lastDatetime: new Date('2024-08-30T07:00Z'),
|
||||||
lastMinDatetime: new Date('2024-08-30 06:45'),
|
lastMinDatetime: new Date('2024-08-30T06:45Z'),
|
||||||
lastMaxDatetime: new Date('2024-08-30 07:15'),
|
lastMaxDatetime: new Date('2024-08-30T07:15Z'),
|
||||||
});
|
});
|
||||||
expect(actorTimeVO.role).toBe(Role.DRIVER);
|
expect(actorTimeVO.role).toBe(Role.DRIVER);
|
||||||
expect(actorTimeVO.target).toBe(Target.START);
|
expect(actorTimeVO.target).toBe(Target.START);
|
||||||
expect(actorTimeVO.firstDatetime.getHours()).toBe(7);
|
expect(actorTimeVO.firstDatetime.getUTCHours()).toBe(7);
|
||||||
expect(actorTimeVO.firstMinDatetime.getMinutes()).toBe(45);
|
expect(actorTimeVO.firstMinDatetime.getUTCMinutes()).toBe(45);
|
||||||
expect(actorTimeVO.firstMaxDatetime.getMinutes()).toBe(15);
|
expect(actorTimeVO.firstMaxDatetime.getUTCMinutes()).toBe(15);
|
||||||
expect(actorTimeVO.lastDatetime.getHours()).toBe(7);
|
expect(actorTimeVO.lastDatetime.getUTCHours()).toBe(7);
|
||||||
expect(actorTimeVO.lastMinDatetime.getMinutes()).toBe(45);
|
expect(actorTimeVO.lastMinDatetime.getUTCMinutes()).toBe(45);
|
||||||
expect(actorTimeVO.lastMaxDatetime.getMinutes()).toBe(15);
|
expect(actorTimeVO.lastMaxDatetime.getUTCMinutes()).toBe(15);
|
||||||
});
|
});
|
||||||
it('should throw an error if dates are inconsistent', () => {
|
it('should throw an error if dates are inconsistent', () => {
|
||||||
expect(
|
expect(
|
||||||
|
@ -30,12 +30,12 @@ describe('Actor time value object', () => {
|
||||||
new ActorTime({
|
new ActorTime({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
target: Target.START,
|
target: Target.START,
|
||||||
firstDatetime: new Date('2023-09-01 07:00'),
|
firstDatetime: new Date('2023-09-01T07:00Z'),
|
||||||
firstMinDatetime: new Date('2023-09-01 07:05'),
|
firstMinDatetime: new Date('2023-09-01T07:05Z'),
|
||||||
firstMaxDatetime: new Date('2023-09-01 07:15'),
|
firstMaxDatetime: new Date('2023-09-01T07:15Z'),
|
||||||
lastDatetime: new Date('2024-08-30 07:00'),
|
lastDatetime: new Date('2024-08-30T07:00Z'),
|
||||||
lastMinDatetime: new Date('2024-08-30 06:45'),
|
lastMinDatetime: new Date('2024-08-30T06:45Z'),
|
||||||
lastMaxDatetime: new Date('2024-08-30 07:15'),
|
lastMaxDatetime: new Date('2024-08-30T07:15Z'),
|
||||||
}),
|
}),
|
||||||
).toThrow(ArgumentInvalidException);
|
).toThrow(ArgumentInvalidException);
|
||||||
expect(
|
expect(
|
||||||
|
@ -43,12 +43,12 @@ describe('Actor time value object', () => {
|
||||||
new ActorTime({
|
new ActorTime({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
target: Target.START,
|
target: Target.START,
|
||||||
firstDatetime: new Date('2023-09-01 07:00'),
|
firstDatetime: new Date('2023-09-01T07:00Z'),
|
||||||
firstMinDatetime: new Date('2023-09-01 06:45'),
|
firstMinDatetime: new Date('2023-09-01T06:45Z'),
|
||||||
firstMaxDatetime: new Date('2023-09-01 06:55'),
|
firstMaxDatetime: new Date('2023-09-01T06:55Z'),
|
||||||
lastDatetime: new Date('2024-08-30 07:00'),
|
lastDatetime: new Date('2024-08-30T07:00Z'),
|
||||||
lastMinDatetime: new Date('2024-08-30 06:45'),
|
lastMinDatetime: new Date('2024-08-30T06:45Z'),
|
||||||
lastMaxDatetime: new Date('2024-08-30 07:15'),
|
lastMaxDatetime: new Date('2024-08-30T07:15Z'),
|
||||||
}),
|
}),
|
||||||
).toThrow(ArgumentInvalidException);
|
).toThrow(ArgumentInvalidException);
|
||||||
expect(
|
expect(
|
||||||
|
@ -56,12 +56,12 @@ describe('Actor time value object', () => {
|
||||||
new ActorTime({
|
new ActorTime({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
target: Target.START,
|
target: Target.START,
|
||||||
firstDatetime: new Date('2023-09-01 07:00'),
|
firstDatetime: new Date('2023-09-01T07:00Z'),
|
||||||
firstMinDatetime: new Date('2023-09-01 06:45'),
|
firstMinDatetime: new Date('2023-09-01T06:45Z'),
|
||||||
firstMaxDatetime: new Date('2023-09-01 07:15'),
|
firstMaxDatetime: new Date('2023-09-01T07:15Z'),
|
||||||
lastDatetime: new Date('2024-08-30 07:00'),
|
lastDatetime: new Date('2024-08-30T07:00Z'),
|
||||||
lastMinDatetime: new Date('2024-08-30 07:05'),
|
lastMinDatetime: new Date('2024-08-30T07:05Z'),
|
||||||
lastMaxDatetime: new Date('2024-08-30 07:15'),
|
lastMaxDatetime: new Date('2024-08-30T07:15Z'),
|
||||||
}),
|
}),
|
||||||
).toThrow(ArgumentInvalidException);
|
).toThrow(ArgumentInvalidException);
|
||||||
expect(
|
expect(
|
||||||
|
@ -69,12 +69,12 @@ describe('Actor time value object', () => {
|
||||||
new ActorTime({
|
new ActorTime({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
target: Target.START,
|
target: Target.START,
|
||||||
firstDatetime: new Date('2023-09-01 07:00'),
|
firstDatetime: new Date('2023-09-01T07:00Z'),
|
||||||
firstMinDatetime: new Date('2023-09-01 06:45'),
|
firstMinDatetime: new Date('2023-09-01T06:45Z'),
|
||||||
firstMaxDatetime: new Date('2023-09-01 07:15'),
|
firstMaxDatetime: new Date('2023-09-01T07:15Z'),
|
||||||
lastDatetime: new Date('2024-08-30 07:00'),
|
lastDatetime: new Date('2024-08-30T07:00Z'),
|
||||||
lastMinDatetime: new Date('2024-08-30 06:45'),
|
lastMinDatetime: new Date('2024-08-30T06:45Z'),
|
||||||
lastMaxDatetime: new Date('2024-08-30 06:35'),
|
lastMaxDatetime: new Date('2024-08-30T06:35Z'),
|
||||||
}),
|
}),
|
||||||
).toThrow(ArgumentInvalidException);
|
).toThrow(ArgumentInvalidException);
|
||||||
expect(
|
expect(
|
||||||
|
@ -82,12 +82,12 @@ describe('Actor time value object', () => {
|
||||||
new ActorTime({
|
new ActorTime({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
target: Target.START,
|
target: Target.START,
|
||||||
firstDatetime: new Date('2024-08-30 07:00'),
|
firstDatetime: new Date('2024-08-30T07:00Z'),
|
||||||
firstMinDatetime: new Date('2024-08-30 06:45'),
|
firstMinDatetime: new Date('2024-08-30T06:45Z'),
|
||||||
firstMaxDatetime: new Date('2024-08-30 07:15'),
|
firstMaxDatetime: new Date('2024-08-30T07:15Z'),
|
||||||
lastDatetime: new Date('2023-09-01 07:00'),
|
lastDatetime: new Date('2023-09-01T07:00Z'),
|
||||||
lastMinDatetime: new Date('2023-09-01 06:45'),
|
lastMinDatetime: new Date('2023-09-01T06:45Z'),
|
||||||
lastMaxDatetime: new Date('2023-09-01 07:15'),
|
lastMaxDatetime: new Date('2023-09-01T07:15Z'),
|
||||||
}),
|
}),
|
||||||
).toThrow(ArgumentInvalidException);
|
).toThrow(ArgumentInvalidException);
|
||||||
expect(
|
expect(
|
||||||
|
@ -95,12 +95,12 @@ describe('Actor time value object', () => {
|
||||||
new ActorTime({
|
new ActorTime({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
target: Target.START,
|
target: Target.START,
|
||||||
firstDatetime: new Date('2023-09-01 07:00'),
|
firstDatetime: new Date('2023-09-01T07:00Z'),
|
||||||
firstMinDatetime: new Date('2023-09-01 06:45'),
|
firstMinDatetime: new Date('2023-09-01T06:45Z'),
|
||||||
firstMaxDatetime: new Date('2023-09-01 07:15'),
|
firstMaxDatetime: new Date('2023-09-01T07:15Z'),
|
||||||
lastDatetime: new Date('2024-08-31 07:00'),
|
lastDatetime: new Date('2024-08-31T07:00Z'),
|
||||||
lastMinDatetime: new Date('2024-08-31 06:45'),
|
lastMinDatetime: new Date('2024-08-31T06:45Z'),
|
||||||
lastMaxDatetime: new Date('2024-08-31 06:35'),
|
lastMaxDatetime: new Date('2024-08-31T06:35Z'),
|
||||||
}),
|
}),
|
||||||
).toThrow(ArgumentInvalidException);
|
).toThrow(ArgumentInvalidException);
|
||||||
});
|
});
|
||||||
|
|
|
@ -67,6 +67,10 @@ class SomeSelector extends Selector {
|
||||||
CandidateEntity.create({
|
CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
dateInterval: {
|
||||||
|
lowerDate: '2023-08-28',
|
||||||
|
higherDate: '2023-08-28',
|
||||||
|
},
|
||||||
driverWaypoints: [
|
driverWaypoints: [
|
||||||
{
|
{
|
||||||
lat: 48.678454,
|
lat: 48.678454,
|
||||||
|
|
|
@ -6,78 +6,175 @@ import {
|
||||||
describe('Calendar tools service', () => {
|
describe('Calendar tools service', () => {
|
||||||
describe('First date', () => {
|
describe('First date', () => {
|
||||||
it('should return the first date for a given week day within a date range', () => {
|
it('should return the first date for a given week day within a date range', () => {
|
||||||
const firstDate: Date = CalendarTools.firstDate(
|
const firstDate: Date = CalendarTools.firstDate(1, {
|
||||||
1,
|
lowerDate: '2023-08-31',
|
||||||
'2023-08-31',
|
higherDate: '2023-09-07',
|
||||||
'2023-09-07',
|
});
|
||||||
);
|
expect(firstDate.getUTCDay()).toBe(1);
|
||||||
expect(firstDate.getDay()).toBe(1);
|
expect(firstDate.getUTCDate()).toBe(4);
|
||||||
expect(firstDate.getDate()).toBe(4);
|
expect(firstDate.getUTCMonth()).toBe(8);
|
||||||
expect(firstDate.getMonth()).toBe(8);
|
const secondDate: Date = CalendarTools.firstDate(5, {
|
||||||
const secondDate: Date = CalendarTools.firstDate(
|
lowerDate: '2023-08-31',
|
||||||
5,
|
higherDate: '2023-09-07',
|
||||||
'2023-08-31',
|
});
|
||||||
'2023-09-07',
|
expect(secondDate.getUTCDay()).toBe(5);
|
||||||
);
|
expect(secondDate.getUTCDate()).toBe(1);
|
||||||
expect(secondDate.getDay()).toBe(5);
|
expect(secondDate.getUTCMonth()).toBe(8);
|
||||||
expect(secondDate.getDate()).toBe(1);
|
const thirdDate: Date = CalendarTools.firstDate(4, {
|
||||||
expect(secondDate.getMonth()).toBe(8);
|
lowerDate: '2023-08-31',
|
||||||
const thirdDate: Date = CalendarTools.firstDate(
|
higherDate: '2023-09-07',
|
||||||
4,
|
});
|
||||||
'2023-08-31',
|
expect(thirdDate.getUTCDay()).toBe(4);
|
||||||
'2023-09-07',
|
expect(thirdDate.getUTCDate()).toBe(31);
|
||||||
);
|
expect(thirdDate.getUTCMonth()).toBe(7);
|
||||||
expect(thirdDate.getDay()).toBe(4);
|
|
||||||
expect(thirdDate.getDate()).toBe(31);
|
|
||||||
expect(thirdDate.getMonth()).toBe(7);
|
|
||||||
});
|
});
|
||||||
it('should throw an exception if a given week day is not within a date range', () => {
|
it('should throw an exception if a given week day is not within a date range', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
CalendarTools.firstDate(1, '2023-09-05', '2023-09-07');
|
CalendarTools.firstDate(1, {
|
||||||
|
lowerDate: '2023-09-05',
|
||||||
|
higherDate: '2023-09-07',
|
||||||
|
});
|
||||||
}).toThrow(CalendarToolsException);
|
}).toThrow(CalendarToolsException);
|
||||||
});
|
});
|
||||||
it('should throw an exception if a given week day is invalid', () => {
|
it('should throw an exception if a given week day is invalid', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
CalendarTools.firstDate(8, '2023-09-05', '2023-09-07');
|
CalendarTools.firstDate(8, {
|
||||||
|
lowerDate: '2023-09-05',
|
||||||
|
higherDate: '2023-09-07',
|
||||||
|
});
|
||||||
}).toThrow(CalendarToolsException);
|
}).toThrow(CalendarToolsException);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Second date', () => {
|
describe('Second date', () => {
|
||||||
it('should return the last date for a given week day within a date range', () => {
|
it('should return the last date for a given week day within a date range', () => {
|
||||||
const firstDate: Date = CalendarTools.lastDate(
|
const firstDate: Date = CalendarTools.lastDate(0, {
|
||||||
0,
|
lowerDate: '2023-09-30',
|
||||||
'2023-09-30',
|
higherDate: '2024-09-30',
|
||||||
'2024-09-30',
|
});
|
||||||
);
|
expect(firstDate.getUTCDay()).toBe(0);
|
||||||
expect(firstDate.getDay()).toBe(0);
|
expect(firstDate.getUTCDate()).toBe(29);
|
||||||
expect(firstDate.getDate()).toBe(29);
|
expect(firstDate.getUTCMonth()).toBe(8);
|
||||||
expect(firstDate.getMonth()).toBe(8);
|
const secondDate: Date = CalendarTools.lastDate(5, {
|
||||||
const secondDate: Date = CalendarTools.lastDate(
|
lowerDate: '2023-09-30',
|
||||||
5,
|
higherDate: '2024-09-30',
|
||||||
'2023-09-30',
|
});
|
||||||
'2024-09-30',
|
expect(secondDate.getUTCDay()).toBe(5);
|
||||||
);
|
expect(secondDate.getUTCDate()).toBe(27);
|
||||||
expect(secondDate.getDay()).toBe(5);
|
expect(secondDate.getUTCMonth()).toBe(8);
|
||||||
expect(secondDate.getDate()).toBe(27);
|
const thirdDate: Date = CalendarTools.lastDate(1, {
|
||||||
expect(secondDate.getMonth()).toBe(8);
|
lowerDate: '2023-09-30',
|
||||||
const thirdDate: Date = CalendarTools.lastDate(
|
higherDate: '2024-09-30',
|
||||||
1,
|
});
|
||||||
'2023-09-30',
|
expect(thirdDate.getUTCDay()).toBe(1);
|
||||||
'2024-09-30',
|
expect(thirdDate.getUTCDate()).toBe(30);
|
||||||
);
|
expect(thirdDate.getUTCMonth()).toBe(8);
|
||||||
expect(thirdDate.getDay()).toBe(1);
|
|
||||||
expect(thirdDate.getDate()).toBe(30);
|
|
||||||
expect(thirdDate.getMonth()).toBe(8);
|
|
||||||
});
|
});
|
||||||
it('should throw an exception if a given week day is not within a date range', () => {
|
it('should throw an exception if a given week day is not within a date range', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
CalendarTools.lastDate(2, '2024-09-27', '2024-09-30');
|
CalendarTools.lastDate(2, {
|
||||||
|
lowerDate: '2024-09-27',
|
||||||
|
higherDate: '2024-09-30',
|
||||||
|
});
|
||||||
}).toThrow(CalendarToolsException);
|
}).toThrow(CalendarToolsException);
|
||||||
});
|
});
|
||||||
it('should throw an exception if a given week day is invalid', () => {
|
it('should throw an exception if a given week day is invalid', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
CalendarTools.lastDate(8, '2023-09-30', '2024-09-30');
|
CalendarTools.lastDate(8, {
|
||||||
|
lowerDate: '2023-09-30',
|
||||||
|
higherDate: '2024-09-30',
|
||||||
|
});
|
||||||
|
}).toThrow(CalendarToolsException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Datetime from string', () => {
|
||||||
|
it('should return a date with time from a string without additional seconds', () => {
|
||||||
|
const datetime: Date = CalendarTools.datetimeFromString(
|
||||||
|
'2023-09-01',
|
||||||
|
'07:12',
|
||||||
|
);
|
||||||
|
expect(datetime.getUTCMinutes()).toBe(12);
|
||||||
|
});
|
||||||
|
it('should return a date with time from a string with additional seconds', () => {
|
||||||
|
const datetime: Date = CalendarTools.datetimeFromString(
|
||||||
|
'2023-09-01',
|
||||||
|
'07:12',
|
||||||
|
60,
|
||||||
|
);
|
||||||
|
expect(datetime.getUTCMinutes()).toBe(13);
|
||||||
|
});
|
||||||
|
it('should return a date with time from a string with negative additional seconds', () => {
|
||||||
|
const datetime: Date = CalendarTools.datetimeFromString(
|
||||||
|
'2023-09-01',
|
||||||
|
'07:00',
|
||||||
|
-60,
|
||||||
|
);
|
||||||
|
console.log(datetime);
|
||||||
|
expect(datetime.getUTCHours()).toBe(6);
|
||||||
|
expect(datetime.getUTCMinutes()).toBe(59);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('epochDaysFromTime', () => {
|
||||||
|
it('should return the epoch day for day 1', () => {
|
||||||
|
const days: Date[] = CalendarTools.epochDaysFromTime(1, '07:00');
|
||||||
|
expect(days).toHaveLength(1);
|
||||||
|
expect(days[0].getUTCFullYear()).toBe(1969);
|
||||||
|
expect(days[0].getUTCMonth()).toBe(11);
|
||||||
|
expect(days[0].getUTCDate()).toBe(29);
|
||||||
|
});
|
||||||
|
it('should return the epoch day for day 2', () => {
|
||||||
|
const days: Date[] = CalendarTools.epochDaysFromTime(2, '07:00');
|
||||||
|
expect(days).toHaveLength(1);
|
||||||
|
expect(days[0].getUTCFullYear()).toBe(1969);
|
||||||
|
expect(days[0].getUTCMonth()).toBe(11);
|
||||||
|
expect(days[0].getUTCDate()).toBe(30);
|
||||||
|
});
|
||||||
|
it('should return the epoch day for day 3', () => {
|
||||||
|
const days: Date[] = CalendarTools.epochDaysFromTime(3, '07:00');
|
||||||
|
expect(days).toHaveLength(1);
|
||||||
|
expect(days[0].getUTCFullYear()).toBe(1969);
|
||||||
|
expect(days[0].getUTCMonth()).toBe(11);
|
||||||
|
expect(days[0].getUTCDate()).toBe(31);
|
||||||
|
});
|
||||||
|
it('should return the epoch day for day 4', () => {
|
||||||
|
const days: Date[] = CalendarTools.epochDaysFromTime(4, '07:00');
|
||||||
|
expect(days).toHaveLength(1);
|
||||||
|
expect(days[0].getUTCFullYear()).toBe(1970);
|
||||||
|
expect(days[0].getUTCMonth()).toBe(0);
|
||||||
|
expect(days[0].getUTCDate()).toBe(1);
|
||||||
|
});
|
||||||
|
it('should return the epoch day for day 5', () => {
|
||||||
|
const days: Date[] = CalendarTools.epochDaysFromTime(5, '07:00');
|
||||||
|
expect(days).toHaveLength(1);
|
||||||
|
expect(days[0].getUTCFullYear()).toBe(1970);
|
||||||
|
expect(days[0].getUTCMonth()).toBe(0);
|
||||||
|
expect(days[0].getUTCDate()).toBe(2);
|
||||||
|
});
|
||||||
|
it('should return the epoch days for day 0', () => {
|
||||||
|
const days: Date[] = CalendarTools.epochDaysFromTime(0, '07:00');
|
||||||
|
expect(days).toHaveLength(2);
|
||||||
|
expect(days[0].getUTCFullYear()).toBe(1969);
|
||||||
|
expect(days[0].getUTCMonth()).toBe(11);
|
||||||
|
expect(days[0].getUTCDate()).toBe(28);
|
||||||
|
expect(days[1].getUTCFullYear()).toBe(1970);
|
||||||
|
expect(days[1].getUTCMonth()).toBe(0);
|
||||||
|
expect(days[1].getUTCDate()).toBe(4);
|
||||||
|
});
|
||||||
|
it('should return the epoch days for day 6', () => {
|
||||||
|
const days: Date[] = CalendarTools.epochDaysFromTime(6, '07:00');
|
||||||
|
expect(days).toHaveLength(2);
|
||||||
|
expect(days[0].getUTCFullYear()).toBe(1969);
|
||||||
|
expect(days[0].getUTCMonth()).toBe(11);
|
||||||
|
expect(days[0].getUTCDate()).toBe(27);
|
||||||
|
expect(days[1].getUTCFullYear()).toBe(1970);
|
||||||
|
expect(days[1].getUTCMonth()).toBe(0);
|
||||||
|
expect(days[1].getUTCDate()).toBe(3);
|
||||||
|
});
|
||||||
|
it('should throw an exception if a given week day is invalid', () => {
|
||||||
|
expect(() => {
|
||||||
|
CalendarTools.epochDaysFromTime(8, '07:00');
|
||||||
}).toThrow(CalendarToolsException);
|
}).toThrow(CalendarToolsException);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,13 +2,16 @@ import { Role } from '@modules/ad/core/domain/ad.types';
|
||||||
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
||||||
import { Target } from '@modules/ad/core/domain/candidate.types';
|
import { Target } from '@modules/ad/core/domain/candidate.types';
|
||||||
import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object';
|
import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object';
|
||||||
import { Point } from '@modules/ad/core/domain/value-objects/point.value-object';
|
|
||||||
|
|
||||||
describe('Candidate entity', () => {
|
describe('Candidate entity', () => {
|
||||||
it('should create a new candidate entity', () => {
|
it('should create a new candidate entity', () => {
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
dateInterval: {
|
||||||
|
lowerDate: '2023-08-28',
|
||||||
|
higherDate: '2023-08-28',
|
||||||
|
},
|
||||||
driverWaypoints: [
|
driverWaypoints: [
|
||||||
{
|
{
|
||||||
lat: 48.678454,
|
lat: 48.678454,
|
||||||
|
@ -52,10 +55,15 @@ describe('Candidate entity', () => {
|
||||||
});
|
});
|
||||||
expect(candidateEntity.id.length).toBe(36);
|
expect(candidateEntity.id.length).toBe(36);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set a candidate entity carpool path', () => {
|
it('should set a candidate entity carpool path', () => {
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||||
role: Role.PASSENGER,
|
role: Role.PASSENGER,
|
||||||
|
dateInterval: {
|
||||||
|
lowerDate: '2023-08-28',
|
||||||
|
higherDate: '2023-08-28',
|
||||||
|
},
|
||||||
driverWaypoints: [
|
driverWaypoints: [
|
||||||
{
|
{
|
||||||
lat: 48.689445,
|
lat: 48.689445,
|
||||||
|
@ -98,10 +106,8 @@ describe('Candidate entity', () => {
|
||||||
},
|
},
|
||||||
}).setCarpoolPath([
|
}).setCarpoolPath([
|
||||||
{
|
{
|
||||||
point: new Point({
|
lat: 48.689445,
|
||||||
lat: 48.689445,
|
lon: 6.17651,
|
||||||
lon: 6.17651,
|
|
||||||
}),
|
|
||||||
actors: [
|
actors: [
|
||||||
new Actor({
|
new Actor({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
@ -114,10 +120,8 @@ describe('Candidate entity', () => {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
point: new Point({
|
lat: 48.8566,
|
||||||
lat: 48.8566,
|
lon: 2.3522,
|
||||||
lon: 2.3522,
|
|
||||||
}),
|
|
||||||
actors: [
|
actors: [
|
||||||
new Actor({
|
new Actor({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
@ -130,12 +134,17 @@ describe('Candidate entity', () => {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
expect(candidateEntity.getProps().carpoolSteps).toHaveLength(2);
|
expect(candidateEntity.getProps().carpoolPath).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a new candidate entity with spacetime metrics', () => {
|
it('should create a new candidate entity with spacetime metrics', () => {
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
dateInterval: {
|
||||||
|
lowerDate: '2023-08-28',
|
||||||
|
higherDate: '2023-08-28',
|
||||||
|
},
|
||||||
driverWaypoints: [
|
driverWaypoints: [
|
||||||
{
|
{
|
||||||
lat: 48.678454,
|
lat: 48.678454,
|
||||||
|
@ -180,98 +189,113 @@ describe('Candidate entity', () => {
|
||||||
expect(candidateEntity.getProps().distance).toBe(352688);
|
expect(candidateEntity.getProps().distance).toBe(352688);
|
||||||
expect(candidateEntity.getProps().duration).toBe(14587);
|
expect(candidateEntity.getProps().duration).toBe(14587);
|
||||||
});
|
});
|
||||||
it('should not validate a candidate entity with exceeding distance detour', () => {
|
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
describe('detour validation', () => {
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
it('should not validate a candidate entity with exceeding distance detour', () => {
|
||||||
role: Role.DRIVER,
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
driverWaypoints: [
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
{
|
role: Role.DRIVER,
|
||||||
lat: 48.678454,
|
dateInterval: {
|
||||||
lon: 6.189745,
|
lowerDate: '2023-08-28',
|
||||||
|
higherDate: '2023-08-28',
|
||||||
},
|
},
|
||||||
{
|
driverWaypoints: [
|
||||||
lat: 48.84877,
|
{
|
||||||
lon: 2.398457,
|
lat: 48.678454,
|
||||||
|
lon: 6.189745,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 48.84877,
|
||||||
|
lon: 2.398457,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
passengerWaypoints: [
|
||||||
|
{
|
||||||
|
lat: 48.849445,
|
||||||
|
lon: 6.68651,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 47.18746,
|
||||||
|
lon: 2.89742,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
driverDistance: 350145,
|
||||||
|
driverDuration: 13548,
|
||||||
|
driverSchedule: [
|
||||||
|
{
|
||||||
|
day: 0,
|
||||||
|
time: '07:00',
|
||||||
|
margin: 900,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
passengerSchedule: [
|
||||||
|
{
|
||||||
|
day: 0,
|
||||||
|
time: '07:10',
|
||||||
|
margin: 900,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
spacetimeDetourRatio: {
|
||||||
|
maxDistanceDetourRatio: 0.3,
|
||||||
|
maxDurationDetourRatio: 0.3,
|
||||||
},
|
},
|
||||||
],
|
}).setMetrics(458690, 13980);
|
||||||
passengerWaypoints: [
|
expect(candidateEntity.isDetourValid()).toBeFalsy();
|
||||||
{
|
});
|
||||||
lat: 48.849445,
|
it('should not validate a candidate entity with exceeding duration detour', () => {
|
||||||
lon: 6.68651,
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
|
role: Role.DRIVER,
|
||||||
|
dateInterval: {
|
||||||
|
lowerDate: '2023-08-28',
|
||||||
|
higherDate: '2023-08-28',
|
||||||
},
|
},
|
||||||
{
|
driverWaypoints: [
|
||||||
lat: 47.18746,
|
{
|
||||||
lon: 2.89742,
|
lat: 48.678454,
|
||||||
|
lon: 6.189745,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 48.84877,
|
||||||
|
lon: 2.398457,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
passengerWaypoints: [
|
||||||
|
{
|
||||||
|
lat: 48.849445,
|
||||||
|
lon: 6.68651,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 47.18746,
|
||||||
|
lon: 2.89742,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
driverDistance: 350145,
|
||||||
|
driverDuration: 13548,
|
||||||
|
driverSchedule: [
|
||||||
|
{
|
||||||
|
day: 0,
|
||||||
|
time: '07:00',
|
||||||
|
margin: 900,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
passengerSchedule: [
|
||||||
|
{
|
||||||
|
day: 0,
|
||||||
|
time: '07:10',
|
||||||
|
margin: 900,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
spacetimeDetourRatio: {
|
||||||
|
maxDistanceDetourRatio: 0.3,
|
||||||
|
maxDurationDetourRatio: 0.3,
|
||||||
},
|
},
|
||||||
],
|
}).setMetrics(352368, 18314);
|
||||||
driverDistance: 350145,
|
expect(candidateEntity.isDetourValid()).toBeFalsy();
|
||||||
driverDuration: 13548,
|
});
|
||||||
driverSchedule: [
|
|
||||||
{
|
|
||||||
day: 0,
|
|
||||||
time: '07:00',
|
|
||||||
margin: 900,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
passengerSchedule: [
|
|
||||||
{
|
|
||||||
day: 0,
|
|
||||||
time: '07:10',
|
|
||||||
margin: 900,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
spacetimeDetourRatio: {
|
|
||||||
maxDistanceDetourRatio: 0.3,
|
|
||||||
maxDurationDetourRatio: 0.3,
|
|
||||||
},
|
|
||||||
}).setMetrics(458690, 13980);
|
|
||||||
expect(candidateEntity.isDetourValid()).toBeFalsy();
|
|
||||||
});
|
});
|
||||||
it('should not validate a candidate entity with exceeding duration detour', () => {
|
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
describe('Journeys', () => {
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
it('should create journeys', () => {});
|
||||||
role: Role.DRIVER,
|
|
||||||
driverWaypoints: [
|
|
||||||
{
|
|
||||||
lat: 48.678454,
|
|
||||||
lon: 6.189745,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
lat: 48.84877,
|
|
||||||
lon: 2.398457,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
passengerWaypoints: [
|
|
||||||
{
|
|
||||||
lat: 48.849445,
|
|
||||||
lon: 6.68651,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
lat: 47.18746,
|
|
||||||
lon: 2.89742,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
driverDistance: 350145,
|
|
||||||
driverDuration: 13548,
|
|
||||||
driverSchedule: [
|
|
||||||
{
|
|
||||||
day: 0,
|
|
||||||
time: '07:00',
|
|
||||||
margin: 900,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
passengerSchedule: [
|
|
||||||
{
|
|
||||||
day: 0,
|
|
||||||
time: '07:10',
|
|
||||||
margin: 900,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
spacetimeDetourRatio: {
|
|
||||||
maxDistanceDetourRatio: 0.3,
|
|
||||||
maxDurationDetourRatio: 0.3,
|
|
||||||
},
|
|
||||||
}).setMetrics(352368, 18314);
|
|
||||||
expect(candidateEntity.isDetourValid()).toBeFalsy();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { CarpoolPathCreator } from '@modules/ad/core/domain/carpool-path-creator.service';
|
import { CarpoolPathCreator } from '@modules/ad/core/domain/carpool-path-creator.service';
|
||||||
import { CarpoolPathCreatorException } from '@modules/ad/core/domain/match.errors';
|
import { CarpoolPathCreatorException } from '@modules/ad/core/domain/match.errors';
|
||||||
import { Point } from '@modules/ad/core/domain/value-objects/point.value-object';
|
import { Point } from '@modules/ad/core/domain/value-objects/point.value-object';
|
||||||
import { CarpoolStep } from '@modules/ad/core/domain/value-objects/carpool-step.value-object';
|
import { CarpoolPathItem } from '@modules/ad/core/domain/value-objects/carpool-path-item.value-object';
|
||||||
|
|
||||||
const waypoint1: Point = new Point({
|
const waypoint1: Point = new Point({
|
||||||
lat: 0,
|
lat: 0,
|
||||||
|
@ -34,71 +34,71 @@ describe('Carpool Path Creator Service', () => {
|
||||||
[waypoint1, waypoint6],
|
[waypoint1, waypoint6],
|
||||||
[waypoint2, waypoint5],
|
[waypoint2, waypoint5],
|
||||||
);
|
);
|
||||||
const carpoolSteps: CarpoolStep[] = carpoolPathCreator.carpoolPath();
|
const carpoolPath: CarpoolPathItem[] = carpoolPathCreator.carpoolPath();
|
||||||
expect(carpoolSteps).toHaveLength(4);
|
expect(carpoolPath).toHaveLength(4);
|
||||||
expect(carpoolSteps[0].actors.length).toBe(1);
|
expect(carpoolPath[0].actors.length).toBe(1);
|
||||||
});
|
});
|
||||||
it('should create a simple carpool path with same destination for driver and passenger', () => {
|
it('should create a simple carpool path with same destination for driver and passenger', () => {
|
||||||
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
|
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
|
||||||
[waypoint1, waypoint6],
|
[waypoint1, waypoint6],
|
||||||
[waypoint2, waypoint6],
|
[waypoint2, waypoint6],
|
||||||
);
|
);
|
||||||
const carpoolSteps: CarpoolStep[] = carpoolPathCreator.carpoolPath();
|
const carpoolPath: CarpoolPathItem[] = carpoolPathCreator.carpoolPath();
|
||||||
expect(carpoolSteps).toHaveLength(3);
|
expect(carpoolPath).toHaveLength(3);
|
||||||
expect(carpoolSteps[0].actors.length).toBe(1);
|
expect(carpoolPath[0].actors.length).toBe(1);
|
||||||
expect(carpoolSteps[1].actors.length).toBe(2);
|
expect(carpoolPath[1].actors.length).toBe(2);
|
||||||
expect(carpoolSteps[2].actors.length).toBe(2);
|
expect(carpoolPath[2].actors.length).toBe(2);
|
||||||
});
|
});
|
||||||
it('should create a simple carpool path with same waypoints for driver and passenger', () => {
|
it('should create a simple carpool path with same waypoints for driver and passenger', () => {
|
||||||
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
|
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
|
||||||
[waypoint1, waypoint6],
|
[waypoint1, waypoint6],
|
||||||
[waypoint1, waypoint6],
|
[waypoint1, waypoint6],
|
||||||
);
|
);
|
||||||
const carpoolSteps: CarpoolStep[] = carpoolPathCreator.carpoolPath();
|
const carpoolPath: CarpoolPathItem[] = carpoolPathCreator.carpoolPath();
|
||||||
expect(carpoolSteps).toHaveLength(2);
|
expect(carpoolPath).toHaveLength(2);
|
||||||
expect(carpoolSteps[0].actors.length).toBe(2);
|
expect(carpoolPath[0].actors.length).toBe(2);
|
||||||
expect(carpoolSteps[1].actors.length).toBe(2);
|
expect(carpoolPath[1].actors.length).toBe(2);
|
||||||
});
|
});
|
||||||
it('should create a complex carpool path with 3 driver waypoints', () => {
|
it('should create a complex carpool path with 3 driver waypoints', () => {
|
||||||
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
|
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
|
||||||
[waypoint1, waypoint3, waypoint6],
|
[waypoint1, waypoint3, waypoint6],
|
||||||
[waypoint2, waypoint5],
|
[waypoint2, waypoint5],
|
||||||
);
|
);
|
||||||
const carpoolSteps: CarpoolStep[] = carpoolPathCreator.carpoolPath();
|
const carpoolPath: CarpoolPathItem[] = carpoolPathCreator.carpoolPath();
|
||||||
expect(carpoolSteps).toHaveLength(5);
|
expect(carpoolPath).toHaveLength(5);
|
||||||
expect(carpoolSteps[0].actors.length).toBe(1);
|
expect(carpoolPath[0].actors.length).toBe(1);
|
||||||
expect(carpoolSteps[1].actors.length).toBe(2);
|
expect(carpoolPath[1].actors.length).toBe(2);
|
||||||
expect(carpoolSteps[2].actors.length).toBe(1);
|
expect(carpoolPath[2].actors.length).toBe(1);
|
||||||
expect(carpoolSteps[3].actors.length).toBe(2);
|
expect(carpoolPath[3].actors.length).toBe(2);
|
||||||
expect(carpoolSteps[4].actors.length).toBe(1);
|
expect(carpoolPath[4].actors.length).toBe(1);
|
||||||
});
|
});
|
||||||
it('should create a complex carpool path with 4 driver waypoints', () => {
|
it('should create a complex carpool path with 4 driver waypoints', () => {
|
||||||
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
|
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
|
||||||
[waypoint1, waypoint3, waypoint4, waypoint6],
|
[waypoint1, waypoint3, waypoint4, waypoint6],
|
||||||
[waypoint2, waypoint5],
|
[waypoint2, waypoint5],
|
||||||
);
|
);
|
||||||
const carpoolSteps: CarpoolStep[] = carpoolPathCreator.carpoolPath();
|
const carpoolPath: CarpoolPathItem[] = carpoolPathCreator.carpoolPath();
|
||||||
expect(carpoolSteps).toHaveLength(6);
|
expect(carpoolPath).toHaveLength(6);
|
||||||
expect(carpoolSteps[0].actors.length).toBe(1);
|
expect(carpoolPath[0].actors.length).toBe(1);
|
||||||
expect(carpoolSteps[1].actors.length).toBe(2);
|
expect(carpoolPath[1].actors.length).toBe(2);
|
||||||
expect(carpoolSteps[2].actors.length).toBe(1);
|
expect(carpoolPath[2].actors.length).toBe(1);
|
||||||
expect(carpoolSteps[3].actors.length).toBe(1);
|
expect(carpoolPath[3].actors.length).toBe(1);
|
||||||
expect(carpoolSteps[4].actors.length).toBe(2);
|
expect(carpoolPath[4].actors.length).toBe(2);
|
||||||
expect(carpoolSteps[5].actors.length).toBe(1);
|
expect(carpoolPath[5].actors.length).toBe(1);
|
||||||
});
|
});
|
||||||
it('should create a alternate complex carpool path with 4 driver waypoints', () => {
|
it('should create a alternate complex carpool path with 4 driver waypoints', () => {
|
||||||
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
|
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
|
||||||
[waypoint1, waypoint2, waypoint5, waypoint6],
|
[waypoint1, waypoint2, waypoint5, waypoint6],
|
||||||
[waypoint3, waypoint4],
|
[waypoint3, waypoint4],
|
||||||
);
|
);
|
||||||
const carpoolSteps: CarpoolStep[] = carpoolPathCreator.carpoolPath();
|
const carpoolPath: CarpoolPathItem[] = carpoolPathCreator.carpoolPath();
|
||||||
expect(carpoolSteps).toHaveLength(6);
|
expect(carpoolPath).toHaveLength(6);
|
||||||
expect(carpoolSteps[0].actors.length).toBe(1);
|
expect(carpoolPath[0].actors.length).toBe(1);
|
||||||
expect(carpoolSteps[1].actors.length).toBe(1);
|
expect(carpoolPath[1].actors.length).toBe(1);
|
||||||
expect(carpoolSteps[2].actors.length).toBe(2);
|
expect(carpoolPath[2].actors.length).toBe(2);
|
||||||
expect(carpoolSteps[3].actors.length).toBe(2);
|
expect(carpoolPath[3].actors.length).toBe(2);
|
||||||
expect(carpoolSteps[4].actors.length).toBe(1);
|
expect(carpoolPath[4].actors.length).toBe(1);
|
||||||
expect(carpoolSteps[5].actors.length).toBe(1);
|
expect(carpoolPath[5].actors.length).toBe(1);
|
||||||
});
|
});
|
||||||
it('should throw an exception if less than 2 driver waypoints are given', () => {
|
it('should throw an exception if less than 2 driver waypoints are given', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
|
|
|
@ -2,16 +2,13 @@ import { ArgumentOutOfRangeException } from '@mobicoop/ddd-library';
|
||||||
import { Role } from '@modules/ad/core/domain/ad.types';
|
import { Role } from '@modules/ad/core/domain/ad.types';
|
||||||
import { Target } from '@modules/ad/core/domain/candidate.types';
|
import { Target } from '@modules/ad/core/domain/candidate.types';
|
||||||
import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object';
|
import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object';
|
||||||
import { Point } from '@modules/ad/core/domain/value-objects/point.value-object';
|
import { CarpoolPathItem } from '@modules/ad/core/domain/value-objects/carpool-path-item.value-object';
|
||||||
import { CarpoolStep } from '@modules/ad/core/domain/value-objects/carpool-step.value-object';
|
|
||||||
|
|
||||||
describe('CarpoolStep value object', () => {
|
describe('Carpool Path Item value object', () => {
|
||||||
it('should create a carpoolStep value object', () => {
|
it('should create a path item value object', () => {
|
||||||
const carpoolStepVO = new CarpoolStep({
|
const carpoolPathItemVO = new CarpoolPathItem({
|
||||||
point: new Point({
|
lat: 48.689445,
|
||||||
lat: 48.689445,
|
lon: 6.17651,
|
||||||
lon: 6.17651,
|
|
||||||
}),
|
|
||||||
actors: [
|
actors: [
|
||||||
new Actor({
|
new Actor({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
@ -23,28 +20,24 @@ describe('CarpoolStep value object', () => {
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
expect(carpoolStepVO.point.lon).toBe(6.17651);
|
expect(carpoolPathItemVO.lon).toBe(6.17651);
|
||||||
expect(carpoolStepVO.point.lat).toBe(48.689445);
|
expect(carpoolPathItemVO.lat).toBe(48.689445);
|
||||||
expect(carpoolStepVO.actors).toHaveLength(2);
|
expect(carpoolPathItemVO.actors).toHaveLength(2);
|
||||||
});
|
});
|
||||||
it('should throw an exception if actors is empty', () => {
|
it('should throw an exception if actors is empty', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
new CarpoolStep({
|
new CarpoolPathItem({
|
||||||
point: new Point({
|
lat: 48.689445,
|
||||||
lat: 48.689445,
|
lon: 6.17651,
|
||||||
lon: 6.17651,
|
|
||||||
}),
|
|
||||||
actors: [],
|
actors: [],
|
||||||
});
|
});
|
||||||
}).toThrow(ArgumentOutOfRangeException);
|
}).toThrow(ArgumentOutOfRangeException);
|
||||||
});
|
});
|
||||||
it('should throw an exception if actors contains more than one driver', () => {
|
it('should throw an exception if actors contains more than one driver', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
new CarpoolStep({
|
new CarpoolPathItem({
|
||||||
point: new Point({
|
lat: 48.689445,
|
||||||
lat: 48.689445,
|
lon: 6.17651,
|
||||||
lon: 6.17651,
|
|
||||||
}),
|
|
||||||
actors: [
|
actors: [
|
||||||
new Actor({
|
new Actor({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
|
@ -28,7 +28,9 @@ describe('Journey item value object', () => {
|
||||||
expect(journeyItemVO.distance).toBe(48754);
|
expect(journeyItemVO.distance).toBe(48754);
|
||||||
expect(journeyItemVO.lon).toBe(6.17651);
|
expect(journeyItemVO.lon).toBe(6.17651);
|
||||||
expect(journeyItemVO.lat).toBe(48.689445);
|
expect(journeyItemVO.lat).toBe(48.689445);
|
||||||
expect(journeyItemVO.actorTimes[0].firstMaxDatetime.getMinutes()).toBe(15);
|
expect(journeyItemVO.actorTimes[0].firstMaxDatetime.getUTCMinutes()).toBe(
|
||||||
|
15,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it('should throw an error if actorTimes is too short', () => {
|
it('should throw an error if actorTimes is too short', () => {
|
||||||
expect(
|
expect(
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
|
||||||
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
||||||
import { Target } from '@modules/ad/core/domain/candidate.types';
|
import { Target } from '@modules/ad/core/domain/candidate.types';
|
||||||
import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object';
|
import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object';
|
||||||
import { Point } from '@modules/ad/core/domain/value-objects/point.value-object';
|
|
||||||
|
|
||||||
const originWaypoint: Waypoint = {
|
const originWaypoint: Waypoint = {
|
||||||
position: 0,
|
position: 0,
|
||||||
|
@ -66,6 +65,10 @@ const matchQuery = new MatchQuery(
|
||||||
const candidate: CandidateEntity = CandidateEntity.create({
|
const candidate: CandidateEntity = CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
dateInterval: {
|
||||||
|
lowerDate: '2023-08-28',
|
||||||
|
higherDate: '2023-08-28',
|
||||||
|
},
|
||||||
driverWaypoints: [
|
driverWaypoints: [
|
||||||
{
|
{
|
||||||
lat: 48.678454,
|
lat: 48.678454,
|
||||||
|
@ -108,10 +111,8 @@ const candidate: CandidateEntity = CandidateEntity.create({
|
||||||
},
|
},
|
||||||
}).setCarpoolPath([
|
}).setCarpoolPath([
|
||||||
{
|
{
|
||||||
point: new Point({
|
lat: 48.689445,
|
||||||
lat: 48.689445,
|
lon: 6.17651,
|
||||||
lon: 6.17651,
|
|
||||||
}),
|
|
||||||
actors: [
|
actors: [
|
||||||
new Actor({
|
new Actor({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
@ -124,10 +125,8 @@ const candidate: CandidateEntity = CandidateEntity.create({
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
point: new Point({
|
lat: 48.8566,
|
||||||
lat: 48.8566,
|
lon: 2.3522,
|
||||||
lon: 2.3522,
|
|
||||||
}),
|
|
||||||
actors: [
|
actors: [
|
||||||
new Actor({
|
new Actor({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
import {
|
import { ArgumentInvalidException } from '@mobicoop/ddd-library';
|
||||||
ArgumentInvalidException,
|
|
||||||
ArgumentOutOfRangeException,
|
|
||||||
} from '@mobicoop/ddd-library';
|
|
||||||
import { Role } from '@modules/ad/core/domain/ad.types';
|
import { Role } from '@modules/ad/core/domain/ad.types';
|
||||||
import { Target } from '@modules/ad/core/domain/candidate.types';
|
import { Target } from '@modules/ad/core/domain/candidate.types';
|
||||||
import { ActorTime } from '@modules/ad/core/domain/value-objects/actor-time.value-object';
|
import { ActorTime } from '@modules/ad/core/domain/value-objects/actor-time.value-object';
|
||||||
|
@ -13,7 +10,6 @@ describe('Journey value object', () => {
|
||||||
const journeyVO = new Journey({
|
const journeyVO = new Journey({
|
||||||
firstDate: new Date('2023-09-01'),
|
firstDate: new Date('2023-09-01'),
|
||||||
lastDate: new Date('2024-08-30'),
|
lastDate: new Date('2024-08-30'),
|
||||||
day: 5,
|
|
||||||
journeyItems: [
|
journeyItems: [
|
||||||
new JourneyItem({
|
new JourneyItem({
|
||||||
lat: 48.689445,
|
lat: 48.689445,
|
||||||
|
@ -109,114 +105,9 @@ describe('Journey value object', () => {
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
expect(journeyVO.day).toBe(5);
|
|
||||||
expect(journeyVO.journeyItems).toHaveLength(4);
|
expect(journeyVO.journeyItems).toHaveLength(4);
|
||||||
expect(journeyVO.firstDate.getDate()).toBe(1);
|
expect(journeyVO.firstDate.getUTCDate()).toBe(1);
|
||||||
expect(journeyVO.lastDate.getMonth()).toBe(7);
|
expect(journeyVO.lastDate.getUTCMonth()).toBe(7);
|
||||||
});
|
|
||||||
it('should throw an error if day is wrong', () => {
|
|
||||||
expect(
|
|
||||||
() =>
|
|
||||||
new Journey({
|
|
||||||
firstDate: new Date('2023-09-01'),
|
|
||||||
lastDate: new Date('2024-08-30'),
|
|
||||||
day: 7,
|
|
||||||
journeyItems: [
|
|
||||||
new JourneyItem({
|
|
||||||
lat: 48.689445,
|
|
||||||
lon: 6.17651,
|
|
||||||
duration: 0,
|
|
||||||
distance: 0,
|
|
||||||
actorTimes: [
|
|
||||||
new ActorTime({
|
|
||||||
role: Role.DRIVER,
|
|
||||||
target: Target.START,
|
|
||||||
firstDatetime: new Date('2023-09-01 07:00'),
|
|
||||||
firstMinDatetime: new Date('2023-09-01 06:45'),
|
|
||||||
firstMaxDatetime: new Date('2023-09-01 07:15'),
|
|
||||||
lastDatetime: new Date('2024-08-30 07:00'),
|
|
||||||
lastMinDatetime: new Date('2024-08-30 06:45'),
|
|
||||||
lastMaxDatetime: new Date('2024-08-30 07:15'),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
new JourneyItem({
|
|
||||||
lat: 48.369445,
|
|
||||||
lon: 6.67487,
|
|
||||||
duration: 2100,
|
|
||||||
distance: 56878,
|
|
||||||
actorTimes: [
|
|
||||||
new ActorTime({
|
|
||||||
role: Role.DRIVER,
|
|
||||||
target: Target.NEUTRAL,
|
|
||||||
firstDatetime: new Date('2023-09-01 07:35'),
|
|
||||||
firstMinDatetime: new Date('2023-09-01 07:20'),
|
|
||||||
firstMaxDatetime: new Date('2023-09-01 07:50'),
|
|
||||||
lastDatetime: new Date('2024-08-30 07:35'),
|
|
||||||
lastMinDatetime: new Date('2024-08-30 07:20'),
|
|
||||||
lastMaxDatetime: new Date('2024-08-30 07:50'),
|
|
||||||
}),
|
|
||||||
new ActorTime({
|
|
||||||
role: Role.PASSENGER,
|
|
||||||
target: Target.START,
|
|
||||||
firstDatetime: new Date('2023-09-01 07:32'),
|
|
||||||
firstMinDatetime: new Date('2023-09-01 07:17'),
|
|
||||||
firstMaxDatetime: new Date('2023-09-01 07:47'),
|
|
||||||
lastDatetime: new Date('2024-08-30 07:32'),
|
|
||||||
lastMinDatetime: new Date('2024-08-30 07:17'),
|
|
||||||
lastMaxDatetime: new Date('2024-08-30 07:47'),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
new JourneyItem({
|
|
||||||
lat: 47.98487,
|
|
||||||
lon: 6.9427,
|
|
||||||
duration: 3840,
|
|
||||||
distance: 76491,
|
|
||||||
actorTimes: [
|
|
||||||
new ActorTime({
|
|
||||||
role: Role.DRIVER,
|
|
||||||
target: Target.NEUTRAL,
|
|
||||||
firstDatetime: new Date('2023-09-01 08:04'),
|
|
||||||
firstMinDatetime: new Date('2023-09-01 07:51'),
|
|
||||||
firstMaxDatetime: new Date('2023-09-01 08:19'),
|
|
||||||
lastDatetime: new Date('2024-08-30 08:04'),
|
|
||||||
lastMinDatetime: new Date('2024-08-30 07:51'),
|
|
||||||
lastMaxDatetime: new Date('2024-08-30 08:19'),
|
|
||||||
}),
|
|
||||||
new ActorTime({
|
|
||||||
role: Role.PASSENGER,
|
|
||||||
target: Target.FINISH,
|
|
||||||
firstDatetime: new Date('2023-09-01 08:01'),
|
|
||||||
firstMinDatetime: new Date('2023-09-01 07:46'),
|
|
||||||
firstMaxDatetime: new Date('2023-09-01 08:16'),
|
|
||||||
lastDatetime: new Date('2024-08-30 08:01'),
|
|
||||||
lastMinDatetime: new Date('2024-08-30 07:46'),
|
|
||||||
lastMaxDatetime: new Date('2024-08-30 08:16'),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
new JourneyItem({
|
|
||||||
lat: 47.365987,
|
|
||||||
lon: 7.02154,
|
|
||||||
duration: 4980,
|
|
||||||
distance: 96475,
|
|
||||||
actorTimes: [
|
|
||||||
new ActorTime({
|
|
||||||
role: Role.DRIVER,
|
|
||||||
target: Target.FINISH,
|
|
||||||
firstDatetime: new Date('2023-09-01 08:23'),
|
|
||||||
firstMinDatetime: new Date('2023-09-01 08:08'),
|
|
||||||
firstMaxDatetime: new Date('2023-09-01 08:38'),
|
|
||||||
lastDatetime: new Date('2024-08-30 08:23'),
|
|
||||||
lastMinDatetime: new Date('2024-08-30 08:08'),
|
|
||||||
lastMaxDatetime: new Date('2024-08-30 08:38'),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
).toThrow(ArgumentOutOfRangeException);
|
|
||||||
});
|
});
|
||||||
it('should throw an error if dates are inconsistent', () => {
|
it('should throw an error if dates are inconsistent', () => {
|
||||||
expect(
|
expect(
|
||||||
|
@ -224,7 +115,6 @@ describe('Journey value object', () => {
|
||||||
new Journey({
|
new Journey({
|
||||||
firstDate: new Date('2023-09-01'),
|
firstDate: new Date('2023-09-01'),
|
||||||
lastDate: new Date('2024-08-31'),
|
lastDate: new Date('2024-08-31'),
|
||||||
day: 5,
|
|
||||||
journeyItems: [
|
journeyItems: [
|
||||||
new JourneyItem({
|
new JourneyItem({
|
||||||
lat: 48.689445,
|
lat: 48.689445,
|
||||||
|
@ -326,7 +216,6 @@ describe('Journey value object', () => {
|
||||||
new Journey({
|
new Journey({
|
||||||
firstDate: new Date('2024-08-30'),
|
firstDate: new Date('2024-08-30'),
|
||||||
lastDate: new Date('2023-09-01'),
|
lastDate: new Date('2023-09-01'),
|
||||||
day: 5,
|
|
||||||
journeyItems: [
|
journeyItems: [
|
||||||
new JourneyItem({
|
new JourneyItem({
|
||||||
lat: 48.689445,
|
lat: 48.689445,
|
||||||
|
@ -430,7 +319,6 @@ describe('Journey value object', () => {
|
||||||
new Journey({
|
new Journey({
|
||||||
firstDate: new Date('2023-09-01'),
|
firstDate: new Date('2023-09-01'),
|
||||||
lastDate: new Date('2024-08-30'),
|
lastDate: new Date('2024-08-30'),
|
||||||
day: 5,
|
|
||||||
journeyItems: [
|
journeyItems: [
|
||||||
new JourneyItem({
|
new JourneyItem({
|
||||||
lat: 48.689445,
|
lat: 48.689445,
|
||||||
|
|
|
@ -50,6 +50,10 @@ const candidates: CandidateEntity[] = [
|
||||||
CandidateEntity.create({
|
CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
dateInterval: {
|
||||||
|
lowerDate: '2023-08-28',
|
||||||
|
higherDate: '2023-08-28',
|
||||||
|
},
|
||||||
driverWaypoints: [
|
driverWaypoints: [
|
||||||
{
|
{
|
||||||
lat: 48.678454,
|
lat: 48.678454,
|
||||||
|
@ -94,6 +98,10 @@ const candidates: CandidateEntity[] = [
|
||||||
CandidateEntity.create({
|
CandidateEntity.create({
|
||||||
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||||
role: Role.PASSENGER,
|
role: Role.PASSENGER,
|
||||||
|
dateInterval: {
|
||||||
|
lowerDate: '2023-08-28',
|
||||||
|
higherDate: '2023-08-28',
|
||||||
|
},
|
||||||
driverWaypoints: [
|
driverWaypoints: [
|
||||||
{
|
{
|
||||||
lat: 48.689445,
|
lat: 48.689445,
|
||||||
|
|
|
@ -49,6 +49,10 @@ const matchQuery = new MatchQuery(
|
||||||
const candidate: CandidateEntity = CandidateEntity.create({
|
const candidate: CandidateEntity = CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
dateInterval: {
|
||||||
|
lowerDate: '2023-08-28',
|
||||||
|
higherDate: '2023-08-28',
|
||||||
|
},
|
||||||
driverWaypoints: [
|
driverWaypoints: [
|
||||||
{
|
{
|
||||||
lat: 48.678454,
|
lat: 48.678454,
|
||||||
|
|
|
@ -9,7 +9,6 @@ import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
|
||||||
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
||||||
import { Target } from '@modules/ad/core/domain/candidate.types';
|
import { Target } from '@modules/ad/core/domain/candidate.types';
|
||||||
import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object';
|
import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object';
|
||||||
import { Point } from '@modules/ad/core/domain/value-objects/point.value-object';
|
|
||||||
|
|
||||||
const originWaypoint: Waypoint = {
|
const originWaypoint: Waypoint = {
|
||||||
position: 0,
|
position: 0,
|
||||||
|
@ -70,6 +69,10 @@ const matchQuery = new MatchQuery(
|
||||||
const candidate: CandidateEntity = CandidateEntity.create({
|
const candidate: CandidateEntity = CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
dateInterval: {
|
||||||
|
lowerDate: '2023-08-28',
|
||||||
|
higherDate: '2023-08-28',
|
||||||
|
},
|
||||||
driverWaypoints: [
|
driverWaypoints: [
|
||||||
{
|
{
|
||||||
lat: 48.678454,
|
lat: 48.678454,
|
||||||
|
@ -112,10 +115,8 @@ const candidate: CandidateEntity = CandidateEntity.create({
|
||||||
},
|
},
|
||||||
}).setCarpoolPath([
|
}).setCarpoolPath([
|
||||||
{
|
{
|
||||||
point: new Point({
|
lat: 48.689445,
|
||||||
lat: 48.689445,
|
lon: 6.17651,
|
||||||
lon: 6.17651,
|
|
||||||
}),
|
|
||||||
actors: [
|
actors: [
|
||||||
new Actor({
|
new Actor({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
@ -128,10 +129,8 @@ const candidate: CandidateEntity = CandidateEntity.create({
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
point: new Point({
|
lat: 48.8566,
|
||||||
lat: 48.8566,
|
lon: 2.3522,
|
||||||
lon: 2.3522,
|
|
||||||
}),
|
|
||||||
actors: [
|
actors: [
|
||||||
new Actor({
|
new Actor({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
|
|
@ -164,12 +164,14 @@ export class GraphhopperGeorouter implements GeorouterPort {
|
||||||
points: [[number, number]],
|
points: [[number, number]],
|
||||||
snappedWaypoints: [[number, number]],
|
snappedWaypoints: [[number, number]],
|
||||||
): number[] => {
|
): number[] => {
|
||||||
const indices = snappedWaypoints.map((waypoint) =>
|
const indices: number[] = snappedWaypoints.map(
|
||||||
points.findIndex(
|
(waypoint: [number, number]) =>
|
||||||
(point) => point[0] == waypoint[0] && point[1] == waypoint[1],
|
points.findIndex(
|
||||||
),
|
(point) => point[0] == waypoint[0] && point[1] == waypoint[1],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (indices.find((index) => index == -1) === undefined) return indices;
|
if (indices.find((index: number) => index == -1) === undefined)
|
||||||
|
return indices;
|
||||||
const missedWaypoints = indices
|
const missedWaypoints = indices
|
||||||
.map(
|
.map(
|
||||||
(value, index) =>
|
(value, index) =>
|
||||||
|
|
Loading…
Reference in New Issue