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) {
|
||||
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) =>
|
||||
MatchEntity.create({ adId: candidate.id }),
|
||||
);
|
||||
|
|
|
@ -5,7 +5,5 @@ export class JourneyCompleter extends Completer {
|
|||
complete = async (
|
||||
candidates: CandidateEntity[],
|
||||
): Promise<CandidateEntity[]> =>
|
||||
candidates.map((candidate: CandidateEntity) =>
|
||||
candidate.createJourneys(this.query.fromDate, this.query.toDate),
|
||||
);
|
||||
candidates.map((candidate: CandidateEntity) => candidate.createJourneys());
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
||||
import { Completer } from './completer.abstract';
|
||||
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 { CarpoolPathItem } from '@modules/ad/core/domain/value-objects/carpool-path-item.value-object';
|
||||
|
||||
export class RouteCompleter extends Completer {
|
||||
protected readonly type: RouteCompleterType;
|
||||
|
@ -19,8 +19,8 @@ export class RouteCompleter extends Completer {
|
|||
switch (this.type) {
|
||||
case RouteCompleterType.BASIC:
|
||||
const basicCandidateRoute = await this.query.routeProvider.getBasic(
|
||||
(candidate.getProps().carpoolSteps as CarpoolStep[]).map(
|
||||
(carpoolStep: CarpoolStep) => carpoolStep.point,
|
||||
(candidate.getProps().carpoolPath as CarpoolPathItem[]).map(
|
||||
(carpoolPathItem: CarpoolPathItem) => carpoolPathItem,
|
||||
),
|
||||
);
|
||||
candidate.setMetrics(
|
||||
|
@ -31,8 +31,8 @@ export class RouteCompleter extends Completer {
|
|||
case RouteCompleterType.DETAILED:
|
||||
const detailedCandidateRoute =
|
||||
await this.query.routeProvider.getDetailed(
|
||||
(candidate.getProps().carpoolSteps as CarpoolStep[]).map(
|
||||
(carpoolStep: CarpoolStep) => carpoolStep.point,
|
||||
(candidate.getProps().carpoolPath as CarpoolPathItem[]).map(
|
||||
(carpoolPathItem: CarpoolPathItem) => carpoolPathItem,
|
||||
),
|
||||
);
|
||||
candidate.setSteps(detailedCandidateRoute.steps as Step[]);
|
||||
|
|
|
@ -153,7 +153,7 @@ export class MatchQuery extends QueryBase {
|
|||
);
|
||||
this.schedule = this.schedule.map((scheduleItem: ScheduleItem) => ({
|
||||
day: datetimeTransformer.day(
|
||||
scheduleItem.day ?? new Date(this.fromDate).getDay(),
|
||||
scheduleItem.day ?? new Date(this.fromDate).getUTCDay(),
|
||||
{
|
||||
date: this.fromDate,
|
||||
time: scheduleItem.time,
|
||||
|
|
|
@ -36,6 +36,16 @@ export class PassengerOrientedSelector extends Selector {
|
|||
CandidateEntity.create({
|
||||
id: adEntity.id,
|
||||
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:
|
||||
adsRole.role == Role.PASSENGER
|
||||
? adEntity.getProps().waypoints
|
||||
|
@ -173,7 +183,7 @@ export class PassengerOrientedSelector extends Selector {
|
|||
scheduleDates.map((date: Date) => {
|
||||
this.query.schedule
|
||||
.filter(
|
||||
(scheduleItem: ScheduleItem) => date.getDay() == scheduleItem.day,
|
||||
(scheduleItem: ScheduleItem) => date.getUTCDay() == scheduleItem.day,
|
||||
)
|
||||
.map((scheduleItem: ScheduleItem) => {
|
||||
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
|
||||
return `make_timestamp(\
|
||||
${maxDepartureDatetime.getFullYear()},\
|
||||
${maxDepartureDatetime.getMonth() + 1},\
|
||||
${maxDepartureDatetime.getDate()},\
|
||||
${maxDepartureDatetime.getUTCFullYear()},\
|
||||
${maxDepartureDatetime.getUTCMonth() + 1},\
|
||||
${maxDepartureDatetime.getUTCDate()},\
|
||||
CAST(EXTRACT(hour from time) as integer),\
|
||||
CAST(EXTRACT(minute from time) as integer),0) - interval '1 second' * margin <=\
|
||||
make_timestamp(\
|
||||
${maxDepartureDatetime.getFullYear()},\
|
||||
${maxDepartureDatetime.getMonth() + 1},\
|
||||
${maxDepartureDatetime.getDate()},${maxDepartureDatetime.getHours()},${maxDepartureDatetime.getMinutes()},0)`;
|
||||
${maxDepartureDatetime.getUTCFullYear()},\
|
||||
${maxDepartureDatetime.getUTCMonth() + 1},\
|
||||
${maxDepartureDatetime.getUTCDate()},${maxDepartureDatetime.getUTCHours()},${maxDepartureDatetime.getUTCMinutes()},0)`;
|
||||
};
|
||||
|
||||
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
|
||||
return `make_timestamp(\
|
||||
${minDepartureDatetime.getFullYear()},
|
||||
${minDepartureDatetime.getMonth() + 1},
|
||||
${minDepartureDatetime.getDate()},\
|
||||
${minDepartureDatetime.getUTCFullYear()},
|
||||
${minDepartureDatetime.getUTCMonth() + 1},
|
||||
${minDepartureDatetime.getUTCDate()},\
|
||||
CAST(EXTRACT(hour from time) as integer),\
|
||||
CAST(EXTRACT(minute from time) as integer),0) + interval '1 second' * margin >=\
|
||||
make_timestamp(\
|
||||
${minDepartureDatetime.getFullYear()},
|
||||
${minDepartureDatetime.getMonth() + 1},
|
||||
${minDepartureDatetime.getDate()},${minDepartureDatetime.getHours()},${minDepartureDatetime.getMinutes()},0)`;
|
||||
${minDepartureDatetime.getUTCFullYear()},
|
||||
${minDepartureDatetime.getUTCMonth() + 1},
|
||||
${minDepartureDatetime.getUTCDate()},${minDepartureDatetime.getUTCHours()},${minDepartureDatetime.getUTCMinutes()},0)`;
|
||||
};
|
||||
|
||||
private _whereAzimuth = (): string => {
|
||||
|
@ -311,7 +321,7 @@ export class PassengerOrientedSelector extends Selector {
|
|||
for (
|
||||
let date = fromDate;
|
||||
date <= toDate;
|
||||
date.setDate(date.getDate() + 1)
|
||||
date.setUTCDate(date.getUTCDate() + 1)
|
||||
) {
|
||||
dates.push(new Date(date));
|
||||
count++;
|
||||
|
@ -321,7 +331,7 @@ export class PassengerOrientedSelector extends Selector {
|
|||
};
|
||||
|
||||
private _addMargin = (date: Date, marginInSeconds: number): Date => {
|
||||
date.setTime(date.getTime() + marginInSeconds * 1000);
|
||||
date.setUTCSeconds(marginInSeconds);
|
||||
return date;
|
||||
};
|
||||
|
||||
|
@ -334,6 +344,12 @@ export class PassengerOrientedSelector extends Selector {
|
|||
maxAzimuth:
|
||||
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 = {
|
||||
|
|
|
@ -1,31 +1,29 @@
|
|||
import { ExceptionBase } from '@mobicoop/ddd-library';
|
||||
import { DateInterval } from './candidate.types';
|
||||
|
||||
export class CalendarTools {
|
||||
/**
|
||||
* Returns the first date corresponding to a week day (0 based monday)
|
||||
* within a date range
|
||||
*/
|
||||
static firstDate = (
|
||||
weekDay: number,
|
||||
lowerDate: string,
|
||||
higherDate: string,
|
||||
): Date => {
|
||||
static firstDate = (weekDay: number, dateInterval: DateInterval): Date => {
|
||||
if (weekDay < 0 || weekDay > 6)
|
||||
throw new CalendarToolsException(
|
||||
new Error('weekDay must be between 0 and 6'),
|
||||
);
|
||||
const lowerDateAsDate: Date = new Date(lowerDate);
|
||||
const higherDateAsDate: Date = new Date(higherDate);
|
||||
if (lowerDateAsDate.getDay() == weekDay) return lowerDateAsDate;
|
||||
const lowerDateAsDate: Date = new Date(dateInterval.lowerDate);
|
||||
const higherDateAsDate: Date = new Date(dateInterval.higherDate);
|
||||
if (lowerDateAsDate.getUTCDay() == weekDay) return lowerDateAsDate;
|
||||
const nextDate: Date = new Date(lowerDateAsDate);
|
||||
nextDate.setDate(
|
||||
lowerDateAsDate.getDate() + (7 - (lowerDateAsDate.getDay() - weekDay)),
|
||||
nextDate.setUTCDate(
|
||||
lowerDateAsDate.getUTCDate() +
|
||||
(7 - (lowerDateAsDate.getUTCDay() - weekDay)),
|
||||
);
|
||||
if (lowerDateAsDate.getDay() < weekDay) {
|
||||
nextDate.setMonth(lowerDateAsDate.getMonth());
|
||||
nextDate.setFullYear(lowerDateAsDate.getFullYear());
|
||||
nextDate.setDate(
|
||||
lowerDateAsDate.getDate() + (weekDay - lowerDateAsDate.getDay()),
|
||||
if (lowerDateAsDate.getUTCDay() < weekDay) {
|
||||
nextDate.setUTCMonth(lowerDateAsDate.getUTCMonth());
|
||||
nextDate.setUTCFullYear(lowerDateAsDate.getUTCFullYear());
|
||||
nextDate.setUTCDate(
|
||||
lowerDateAsDate.getUTCDate() + (weekDay - lowerDateAsDate.getUTCDay()),
|
||||
);
|
||||
}
|
||||
if (nextDate <= higherDateAsDate) return nextDate;
|
||||
|
@ -38,28 +36,24 @@ export class CalendarTools {
|
|||
* Returns the last date corresponding to a week day (0 based monday)
|
||||
* within a date range
|
||||
*/
|
||||
static lastDate = (
|
||||
weekDay: number,
|
||||
lowerDate: string,
|
||||
higherDate: string,
|
||||
): Date => {
|
||||
static lastDate = (weekDay: number, dateInterval: DateInterval): Date => {
|
||||
if (weekDay < 0 || weekDay > 6)
|
||||
throw new CalendarToolsException(
|
||||
new Error('weekDay must be between 0 and 6'),
|
||||
);
|
||||
const lowerDateAsDate: Date = new Date(lowerDate);
|
||||
const higherDateAsDate: Date = new Date(higherDate);
|
||||
if (higherDateAsDate.getDay() == weekDay) return higherDateAsDate;
|
||||
const lowerDateAsDate: Date = new Date(dateInterval.lowerDate);
|
||||
const higherDateAsDate: Date = new Date(dateInterval.higherDate);
|
||||
if (higherDateAsDate.getUTCDay() == weekDay) return higherDateAsDate;
|
||||
const previousDate: Date = new Date(higherDateAsDate);
|
||||
previousDate.setDate(
|
||||
higherDateAsDate.getDate() - (higherDateAsDate.getDay() - weekDay),
|
||||
previousDate.setUTCDate(
|
||||
higherDateAsDate.getUTCDate() - (higherDateAsDate.getUTCDay() - weekDay),
|
||||
);
|
||||
if (higherDateAsDate.getDay() < weekDay) {
|
||||
previousDate.setMonth(higherDateAsDate.getMonth());
|
||||
previousDate.setFullYear(higherDateAsDate.getFullYear());
|
||||
previousDate.setDate(
|
||||
higherDateAsDate.getDate() -
|
||||
(7 + (higherDateAsDate.getDay() - weekDay)),
|
||||
if (higherDateAsDate.getUTCDay() < weekDay) {
|
||||
previousDate.setUTCMonth(higherDateAsDate.getUTCMonth());
|
||||
previousDate.setUTCFullYear(higherDateAsDate.getUTCFullYear());
|
||||
previousDate.setUTCDate(
|
||||
higherDateAsDate.getUTCDate() -
|
||||
(7 + (higherDateAsDate.getUTCDay() - weekDay)),
|
||||
);
|
||||
}
|
||||
if (previousDate >= lowerDateAsDate) return previousDate;
|
||||
|
@ -67,6 +61,55 @@ export class CalendarTools {
|
|||
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 {
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
||||
import { CandidateProps, CreateCandidateProps } from './candidate.types';
|
||||
import { CarpoolStepProps } from './value-objects/carpool-step.value-object';
|
||||
import { StepProps } from './value-objects/step.value-object';
|
||||
import {
|
||||
CandidateProps,
|
||||
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 { Journey } from './value-objects/journey.value-object';
|
||||
import { CalendarTools } from './calendar-tools.service';
|
||||
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> {
|
||||
protected readonly _id: AggregateID;
|
||||
|
@ -15,8 +25,8 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
|||
return new CandidateEntity({ id: create.id, props });
|
||||
};
|
||||
|
||||
setCarpoolPath = (carpoolSteps: CarpoolStepProps[]): CandidateEntity => {
|
||||
this.props.carpoolSteps = carpoolSteps;
|
||||
setCarpoolPath = (carpoolPath: CarpoolPathItemProps[]): CandidateEntity => {
|
||||
this.props.carpoolPath = carpoolPath;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
@ -34,21 +44,14 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
|||
isDetourValid = (): boolean =>
|
||||
this._validateDistanceDetour() && this._validateDurationDetour();
|
||||
|
||||
createJourneys = (fromDate: string, toDate: string): CandidateEntity => {
|
||||
this.props.driverJourneys = this.props.driverSchedule
|
||||
.map((driverScheduleItem: ScheduleItem) =>
|
||||
this._createJourney(fromDate, toDate, driverScheduleItem),
|
||||
)
|
||||
.filter(
|
||||
(journey: Journey | undefined) => journey !== undefined,
|
||||
) as Journey[];
|
||||
this.props.passengerJourneys = this.props.passengerSchedule
|
||||
.map((passengerScheduleItem: ScheduleItem) =>
|
||||
this._createJourney(fromDate, toDate, passengerScheduleItem),
|
||||
)
|
||||
.filter(
|
||||
(journey: Journey | undefined) => journey !== undefined,
|
||||
) as Journey[];
|
||||
/**
|
||||
* Create the journeys based on the driver schedule (the driver 'drives' the carpool !)
|
||||
*/
|
||||
createJourneys = (): CandidateEntity => {
|
||||
this.props.journeys = this.props.driverSchedule.map(
|
||||
(driverScheduleItem: ScheduleItem) =>
|
||||
this._createJourney(driverScheduleItem),
|
||||
);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
@ -66,23 +69,159 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
|||
(1 + this.props.spacetimeDetourRatio.maxDistanceDetourRatio)
|
||||
: false;
|
||||
|
||||
private _createJourney = (
|
||||
fromDate: string,
|
||||
toDate: string,
|
||||
scheduleItem: ScheduleItem,
|
||||
): Journey | undefined =>
|
||||
private _createJourney = (driverScheduleItem: ScheduleItem): Journey =>
|
||||
new Journey({
|
||||
day: scheduleItem.day,
|
||||
firstDate: CalendarTools.firstDate(scheduleItem.day, fromDate, toDate),
|
||||
lastDate: CalendarTools.lastDate(scheduleItem.day, fromDate, toDate),
|
||||
journeyItems: this._createJourneyItems(scheduleItem),
|
||||
firstDate: CalendarTools.firstDate(
|
||||
driverScheduleItem.day,
|
||||
this.props.dateInterval,
|
||||
),
|
||||
lastDate: CalendarTools.lastDate(
|
||||
driverScheduleItem.day,
|
||||
this.props.dateInterval,
|
||||
),
|
||||
journeyItems: this._createJourneyItems(driverScheduleItem),
|
||||
});
|
||||
|
||||
private _createJourneyItems = (
|
||||
scheduleItem: ScheduleItem,
|
||||
): JourneyItem[] => [];
|
||||
driverScheduleItem: ScheduleItem,
|
||||
): 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 {
|
||||
// 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 { PointProps } from './value-objects/point.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 { StepProps } from './value-objects/step.value-object';
|
||||
|
||||
|
@ -10,16 +10,16 @@ export interface CandidateProps {
|
|||
role: Role;
|
||||
driverWaypoints: PointProps[];
|
||||
passengerWaypoints: PointProps[];
|
||||
driverSchedule: ScheduleItemProps[];
|
||||
passengerSchedule: ScheduleItemProps[];
|
||||
driverDistance: number;
|
||||
driverDuration: number;
|
||||
carpoolSteps?: CarpoolStepProps[];
|
||||
dateInterval: DateInterval;
|
||||
carpoolPath?: CarpoolPathItemProps[];
|
||||
distance?: number;
|
||||
duration?: number;
|
||||
steps?: StepProps[];
|
||||
driverSchedule: ScheduleItemProps[];
|
||||
passengerSchedule: ScheduleItemProps[];
|
||||
driverJourneys?: JourneyProps[];
|
||||
passengerJourneys?: JourneyProps[];
|
||||
journeys?: JourneyProps[];
|
||||
spacetimeDetourRatio: SpacetimeDetourRatio;
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ export interface CreateCandidateProps {
|
|||
driverSchedule: ScheduleItemProps[];
|
||||
passengerSchedule: ScheduleItemProps[];
|
||||
spacetimeDetourRatio: SpacetimeDetourRatio;
|
||||
dateInterval: DateInterval;
|
||||
}
|
||||
|
||||
export enum Target {
|
||||
|
@ -47,12 +48,12 @@ export abstract class Validator {
|
|||
abstract validate(): boolean;
|
||||
}
|
||||
|
||||
export type SpacetimeMetric = {
|
||||
distance: number;
|
||||
duration: number;
|
||||
};
|
||||
|
||||
export type SpacetimeDetourRatio = {
|
||||
maxDistanceDetourRatio: 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 { Actor } from './value-objects/actor.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 {
|
||||
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
|
||||
of the driver waypoints
|
||||
Inspired by :
|
||||
https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
|
||||
*/
|
||||
public carpoolPath = (): CarpoolStep[] =>
|
||||
public carpoolPath = (): CarpoolPathItem[] =>
|
||||
this._consolidate(
|
||||
this._mixedCarpoolSteps(
|
||||
this._driverCarpoolSteps(),
|
||||
this._passengerCarpoolSteps(),
|
||||
this._mixedCarpoolPath(
|
||||
this._driverCarpoolPath(),
|
||||
this._passengerCarpoolPath(),
|
||||
),
|
||||
);
|
||||
|
||||
private _mixedCarpoolSteps = (
|
||||
driverCarpoolSteps: CarpoolStep[],
|
||||
passengerCarpoolSteps: CarpoolStep[],
|
||||
): CarpoolStep[] =>
|
||||
driverCarpoolSteps.length == 2
|
||||
? this._simpleMixedCarpoolSteps(driverCarpoolSteps, passengerCarpoolSteps)
|
||||
: this._complexMixedCarpoolSteps(
|
||||
driverCarpoolSteps,
|
||||
passengerCarpoolSteps,
|
||||
);
|
||||
private _mixedCarpoolPath = (
|
||||
driverCarpoolPath: CarpoolPathItem[],
|
||||
passengerCarpoolPath: CarpoolPathItem[],
|
||||
): CarpoolPathItem[] =>
|
||||
driverCarpoolPath.length == 2
|
||||
? this._simpleMixedCarpoolPath(driverCarpoolPath, passengerCarpoolPath)
|
||||
: this._complexMixedCarpoolPath(driverCarpoolPath, passengerCarpoolPath);
|
||||
|
||||
private _driverCarpoolSteps = (): CarpoolStep[] =>
|
||||
private _driverCarpoolPath = (): CarpoolPathItem[] =>
|
||||
this.driverWaypoints.map(
|
||||
(waypoint: Point, index: number) =>
|
||||
new CarpoolStep({
|
||||
point: new Point({
|
||||
lon: waypoint.lon,
|
||||
lat: waypoint.lat,
|
||||
}),
|
||||
new CarpoolPathItem({
|
||||
lon: waypoint.lon,
|
||||
lat: waypoint.lat,
|
||||
actors: [
|
||||
new Actor({
|
||||
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[] => {
|
||||
const carpoolSteps: CarpoolStep[] = [];
|
||||
private _passengerCarpoolPath = (): CarpoolPathItem[] => {
|
||||
const carpoolPath: CarpoolPathItem[] = [];
|
||||
this.passengerWaypoints.forEach(
|
||||
(passengerWaypoint: Point, index: number) => {
|
||||
const carpoolStep: CarpoolStep = new CarpoolStep({
|
||||
point: new Point({
|
||||
lon: passengerWaypoint.lon,
|
||||
lat: passengerWaypoint.lat,
|
||||
}),
|
||||
const carpoolPathItem: CarpoolPathItem = new CarpoolPathItem({
|
||||
lon: passengerWaypoint.lon,
|
||||
lat: passengerWaypoint.lat,
|
||||
actors: [
|
||||
new Actor({
|
||||
role: Role.PASSENGER,
|
||||
|
@ -89,78 +82,80 @@ export class CarpoolPathCreator {
|
|||
passengerWaypoint.equals(driverWaypoint),
|
||||
).length == 0
|
||||
) {
|
||||
carpoolStep.actors.push(
|
||||
carpoolPathItem.actors.push(
|
||||
new Actor({
|
||||
role: Role.DRIVER,
|
||||
target: Target.NEUTRAL,
|
||||
}),
|
||||
);
|
||||
}
|
||||
carpoolSteps.push(carpoolStep);
|
||||
carpoolPath.push(carpoolPathItem);
|
||||
},
|
||||
);
|
||||
return carpoolSteps;
|
||||
return carpoolPath;
|
||||
};
|
||||
|
||||
private _simpleMixedCarpoolSteps = (
|
||||
driverCarpoolSteps: CarpoolStep[],
|
||||
passengerCarpoolSteps: CarpoolStep[],
|
||||
): CarpoolStep[] => [
|
||||
driverCarpoolSteps[0],
|
||||
...passengerCarpoolSteps,
|
||||
driverCarpoolSteps[1],
|
||||
private _simpleMixedCarpoolPath = (
|
||||
driverCarpoolPath: CarpoolPathItem[],
|
||||
passengerCarpoolPath: CarpoolPathItem[],
|
||||
): CarpoolPathItem[] => [
|
||||
driverCarpoolPath[0],
|
||||
...passengerCarpoolPath,
|
||||
driverCarpoolPath[1],
|
||||
];
|
||||
|
||||
private _complexMixedCarpoolSteps = (
|
||||
driverCarpoolSteps: CarpoolStep[],
|
||||
passengerCarpoolSteps: CarpoolStep[],
|
||||
): CarpoolStep[] => {
|
||||
let mixedCarpoolSteps: CarpoolStep[] = [...driverCarpoolSteps];
|
||||
private _complexMixedCarpoolPath = (
|
||||
driverCarpoolPath: CarpoolPathItem[],
|
||||
passengerCarpoolPath: CarpoolPathItem[],
|
||||
): CarpoolPathItem[] => {
|
||||
let mixedCarpoolPath: CarpoolPathItem[] = [...driverCarpoolPath];
|
||||
const originInsertIndex: number = this._insertIndex(
|
||||
passengerCarpoolSteps[0],
|
||||
driverCarpoolSteps,
|
||||
passengerCarpoolPath[0],
|
||||
driverCarpoolPath,
|
||||
);
|
||||
mixedCarpoolSteps = [
|
||||
...mixedCarpoolSteps.slice(0, originInsertIndex),
|
||||
passengerCarpoolSteps[0],
|
||||
...mixedCarpoolSteps.slice(originInsertIndex),
|
||||
mixedCarpoolPath = [
|
||||
...mixedCarpoolPath.slice(0, originInsertIndex),
|
||||
passengerCarpoolPath[0],
|
||||
...mixedCarpoolPath.slice(originInsertIndex),
|
||||
];
|
||||
const destinationInsertIndex: number =
|
||||
this._insertIndex(
|
||||
passengerCarpoolSteps[passengerCarpoolSteps.length - 1],
|
||||
driverCarpoolSteps,
|
||||
passengerCarpoolPath[passengerCarpoolPath.length - 1],
|
||||
driverCarpoolPath,
|
||||
) + 1;
|
||||
mixedCarpoolSteps = [
|
||||
...mixedCarpoolSteps.slice(0, destinationInsertIndex),
|
||||
passengerCarpoolSteps[passengerCarpoolSteps.length - 1],
|
||||
...mixedCarpoolSteps.slice(destinationInsertIndex),
|
||||
mixedCarpoolPath = [
|
||||
...mixedCarpoolPath.slice(0, destinationInsertIndex),
|
||||
passengerCarpoolPath[passengerCarpoolPath.length - 1],
|
||||
...mixedCarpoolPath.slice(destinationInsertIndex),
|
||||
];
|
||||
return mixedCarpoolSteps;
|
||||
return mixedCarpoolPath;
|
||||
};
|
||||
|
||||
private _insertIndex = (
|
||||
targetCarpoolStep: CarpoolStep,
|
||||
carpoolSteps: CarpoolStep[],
|
||||
targetCarpoolPathItem: CarpoolPathItem,
|
||||
carpoolPath: CarpoolPathItem[],
|
||||
): number =>
|
||||
this._closestSegmentIndex(targetCarpoolStep, this._segments(carpoolSteps)) +
|
||||
1;
|
||||
this._closestSegmentIndex(
|
||||
targetCarpoolPathItem,
|
||||
this._segments(carpoolPath),
|
||||
) + 1;
|
||||
|
||||
private _segments = (carpoolSteps: CarpoolStep[]): CarpoolStep[][] => {
|
||||
const segments: CarpoolStep[][] = [];
|
||||
carpoolSteps.forEach((carpoolStep: CarpoolStep, index: number) => {
|
||||
if (index < carpoolSteps.length - 1)
|
||||
segments.push([carpoolStep, carpoolSteps[index + 1]]);
|
||||
private _segments = (carpoolPath: CarpoolPathItem[]): CarpoolPathItem[][] => {
|
||||
const segments: CarpoolPathItem[][] = [];
|
||||
carpoolPath.forEach((carpoolPathItem: CarpoolPathItem, index: number) => {
|
||||
if (index < carpoolPath.length - 1)
|
||||
segments.push([carpoolPathItem, carpoolPath[index + 1]]);
|
||||
});
|
||||
return segments;
|
||||
};
|
||||
|
||||
private _closestSegmentIndex = (
|
||||
carpoolStep: CarpoolStep,
|
||||
segments: CarpoolStep[][],
|
||||
carpoolPathItem: CarpoolPathItem,
|
||||
segments: CarpoolPathItem[][],
|
||||
): number => {
|
||||
const distances: Map<number, number> = new Map();
|
||||
segments.forEach((segment: CarpoolStep[], index: number) => {
|
||||
distances.set(index, this._distanceToSegment(carpoolStep, segment));
|
||||
segments.forEach((segment: CarpoolPathItem[], index: number) => {
|
||||
distances.set(index, this._distanceToSegment(carpoolPathItem, segment));
|
||||
});
|
||||
const sortedDistances: Map<number, number> = new Map(
|
||||
[...distances.entries()].sort((a, b) => a[1] - b[1]),
|
||||
|
@ -170,45 +165,62 @@ export class CarpoolPathCreator {
|
|||
};
|
||||
|
||||
private _distanceToSegment = (
|
||||
carpoolStep: CarpoolStep,
|
||||
segment: CarpoolStep[],
|
||||
carpoolPathItem: CarpoolPathItem,
|
||||
segment: CarpoolPathItem[],
|
||||
): number =>
|
||||
parseFloat(
|
||||
Math.sqrt(this._distanceToSegmentSquared(carpoolStep, segment)).toFixed(
|
||||
this.PRECISION,
|
||||
),
|
||||
Math.sqrt(
|
||||
this._distanceToSegmentSquared(carpoolPathItem, segment),
|
||||
).toFixed(this.PRECISION),
|
||||
);
|
||||
|
||||
private _distanceToSegmentSquared = (
|
||||
carpoolStep: CarpoolStep,
|
||||
segment: CarpoolStep[],
|
||||
carpoolPathItem: CarpoolPathItem,
|
||||
segment: CarpoolPathItem[],
|
||||
): number => {
|
||||
const length2: number = this._distanceSquared(
|
||||
segment[0].point,
|
||||
segment[1].point,
|
||||
new Point({
|
||||
lon: segment[0].lon,
|
||||
lat: segment[0].lat,
|
||||
}),
|
||||
new Point({
|
||||
lon: segment[1].lon,
|
||||
lat: segment[1].lat,
|
||||
}),
|
||||
);
|
||||
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(
|
||||
0,
|
||||
Math.min(
|
||||
1,
|
||||
((carpoolStep.point.lon - segment[0].point.lon) *
|
||||
(segment[1].point.lon - segment[0].point.lon) +
|
||||
(carpoolStep.point.lat - segment[0].point.lat) *
|
||||
(segment[1].point.lat - segment[0].point.lat)) /
|
||||
((carpoolPathItem.lon - segment[0].lon) *
|
||||
(segment[1].lon - segment[0].lon) +
|
||||
(carpoolPathItem.lat - segment[0].lat) *
|
||||
(segment[1].lat - segment[0].lat)) /
|
||||
length2,
|
||||
),
|
||||
);
|
||||
const newPoint: Point = new Point({
|
||||
lon:
|
||||
segment[0].point.lon +
|
||||
length * (segment[1].point.lon - segment[0].point.lon),
|
||||
lat:
|
||||
segment[0].point.lat +
|
||||
length * (segment[1].point.lat - segment[0].point.lat),
|
||||
lon: segment[0].lon + length * (segment[1].lon - segment[0].lon),
|
||||
lat: segment[0].lat + length * (segment[1].lat - segment[0].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 =>
|
||||
|
@ -227,31 +239,43 @@ export class CarpoolPathCreator {
|
|||
: 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[] = [];
|
||||
carpoolSteps.forEach((carpoolStep: CarpoolStep) => {
|
||||
carpoolPath.forEach((carpoolPathItem: CarpoolPathItem) => {
|
||||
if (
|
||||
uniquePoints.find((point: Point) => point.equals(carpoolStep.point)) ===
|
||||
undefined
|
||||
uniquePoints.find((point: Point) =>
|
||||
point.equals(
|
||||
new Point({
|
||||
lon: carpoolPathItem.lon,
|
||||
lat: carpoolPathItem.lat,
|
||||
}),
|
||||
),
|
||||
) === undefined
|
||||
)
|
||||
uniquePoints.push(
|
||||
new Point({
|
||||
lon: carpoolStep.point.lon,
|
||||
lat: carpoolStep.point.lat,
|
||||
lon: carpoolPathItem.lon,
|
||||
lat: carpoolPathItem.lat,
|
||||
}),
|
||||
);
|
||||
});
|
||||
return uniquePoints.map(
|
||||
(point: Point) =>
|
||||
new CarpoolStep({
|
||||
point,
|
||||
actors: carpoolSteps
|
||||
.filter((carpoolStep: CarpoolStep) =>
|
||||
carpoolStep.point.equals(point),
|
||||
new CarpoolPathItem({
|
||||
lon: point.lon,
|
||||
lat: point.lat,
|
||||
actors: carpoolPath
|
||||
.filter((carpoolPathItem: CarpoolPathItem) =>
|
||||
new Point({
|
||||
lon: carpoolPathItem.lon,
|
||||
lat: carpoolPathItem.lat,
|
||||
}).equals(point),
|
||||
)
|
||||
.map((carpoolStep: CarpoolStep) => carpoolStep.actors)
|
||||
.map((carpoolPathItem: CarpoolPathItem) => carpoolPathItem.actors)
|
||||
.flat(),
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -56,7 +56,7 @@ export class ActorTime extends ValueObject<ActorTimeProps> {
|
|||
role: props.role,
|
||||
target: props.target,
|
||||
});
|
||||
if (props.firstDatetime.getDay() != props.lastDatetime.getDay())
|
||||
if (props.firstDatetime.getUTCDay() != props.lastDatetime.getUTCDay())
|
||||
throw new ArgumentInvalidException(
|
||||
'firstDatetime week day must be equal to lastDatetime week day',
|
||||
);
|
||||
|
|
|
@ -4,28 +4,36 @@ import {
|
|||
} from '@mobicoop/ddd-library';
|
||||
import { Actor } from './actor.value-object';
|
||||
import { Role } from '../ad.types';
|
||||
import { Point } from './point.value-object';
|
||||
import { Point, PointProps } from './point.value-object';
|
||||
|
||||
/** Note:
|
||||
* Value Objects with multiple properties can contain
|
||||
* other Value Objects inside if needed.
|
||||
* */
|
||||
|
||||
export interface CarpoolStepProps {
|
||||
point: Point;
|
||||
export interface CarpoolPathItemProps extends PointProps {
|
||||
actors: Actor[];
|
||||
}
|
||||
|
||||
export class CarpoolStep extends ValueObject<CarpoolStepProps> {
|
||||
get point(): Point {
|
||||
return this.props.point;
|
||||
export class CarpoolPathItem extends ValueObject<CarpoolPathItemProps> {
|
||||
get lon(): number {
|
||||
return this.props.lon;
|
||||
}
|
||||
|
||||
get lat(): number {
|
||||
return this.props.lat;
|
||||
}
|
||||
|
||||
get actors(): Actor[] {
|
||||
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)
|
||||
throw new ArgumentOutOfRangeException('at least one actor is required');
|
||||
if (
|
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
ArgumentInvalidException,
|
||||
ArgumentOutOfRangeException,
|
||||
ValueObject,
|
||||
} from '@mobicoop/ddd-library';
|
||||
import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
|
||||
import { JourneyItem } from './journey-item.value-object';
|
||||
|
||||
/** Note:
|
||||
|
@ -13,7 +9,6 @@ import { JourneyItem } from './journey-item.value-object';
|
|||
export interface JourneyProps {
|
||||
firstDate: Date;
|
||||
lastDate: Date;
|
||||
day: number;
|
||||
journeyItems: JourneyItem[];
|
||||
}
|
||||
|
||||
|
@ -26,18 +21,12 @@ export class Journey extends ValueObject<JourneyProps> {
|
|||
return this.props.lastDate;
|
||||
}
|
||||
|
||||
get day(): number {
|
||||
return this.props.day;
|
||||
}
|
||||
|
||||
get journeyItems(): JourneyItem[] {
|
||||
return this.props.journeyItems;
|
||||
}
|
||||
|
||||
protected validate(props: JourneyProps): void {
|
||||
if (props.day < 0 || props.day > 6)
|
||||
throw new ArgumentOutOfRangeException('day must be between 0 and 6');
|
||||
if (props.firstDate.getDay() != props.lastDate.getDay())
|
||||
if (props.firstDate.getUTCDay() != props.lastDate.getUTCDay())
|
||||
throw new ArgumentInvalidException(
|
||||
'firstDate week day must be equal to lastDate week day',
|
||||
);
|
||||
|
|
|
@ -79,7 +79,7 @@ export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
|||
this._defaultTimezone,
|
||||
)[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'))
|
||||
.toIsoString()
|
||||
.split('T')[0],
|
||||
).getDay();
|
||||
).getUTCDay();
|
||||
|
||||
localUnixEpochDayFromTime = (time: string, timezone: string): number =>
|
||||
new Date(
|
||||
|
@ -53,5 +53,5 @@ export class TimeConverter implements TimeConverterPort {
|
|||
.convert(TimeZone.zone(timezone))
|
||||
.toIsoString()
|
||||
.split('T')[0],
|
||||
).getDay();
|
||||
).getUTCDay();
|
||||
}
|
||||
|
|
|
@ -8,21 +8,21 @@ describe('Actor time value object', () => {
|
|||
const actorTimeVO = 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'),
|
||||
firstDatetime: new Date('2023-09-01T07:00Z'),
|
||||
firstMinDatetime: new Date('2023-09-01T06:45Z'),
|
||||
firstMaxDatetime: new Date('2023-09-01T07:15Z'),
|
||||
lastDatetime: new Date('2024-08-30T07:00Z'),
|
||||
lastMinDatetime: new Date('2024-08-30T06:45Z'),
|
||||
lastMaxDatetime: new Date('2024-08-30T07:15Z'),
|
||||
});
|
||||
expect(actorTimeVO.role).toBe(Role.DRIVER);
|
||||
expect(actorTimeVO.target).toBe(Target.START);
|
||||
expect(actorTimeVO.firstDatetime.getHours()).toBe(7);
|
||||
expect(actorTimeVO.firstMinDatetime.getMinutes()).toBe(45);
|
||||
expect(actorTimeVO.firstMaxDatetime.getMinutes()).toBe(15);
|
||||
expect(actorTimeVO.lastDatetime.getHours()).toBe(7);
|
||||
expect(actorTimeVO.lastMinDatetime.getMinutes()).toBe(45);
|
||||
expect(actorTimeVO.lastMaxDatetime.getMinutes()).toBe(15);
|
||||
expect(actorTimeVO.firstDatetime.getUTCHours()).toBe(7);
|
||||
expect(actorTimeVO.firstMinDatetime.getUTCMinutes()).toBe(45);
|
||||
expect(actorTimeVO.firstMaxDatetime.getUTCMinutes()).toBe(15);
|
||||
expect(actorTimeVO.lastDatetime.getUTCHours()).toBe(7);
|
||||
expect(actorTimeVO.lastMinDatetime.getUTCMinutes()).toBe(45);
|
||||
expect(actorTimeVO.lastMaxDatetime.getUTCMinutes()).toBe(15);
|
||||
});
|
||||
it('should throw an error if dates are inconsistent', () => {
|
||||
expect(
|
||||
|
@ -30,12 +30,12 @@ describe('Actor time value object', () => {
|
|||
new ActorTime({
|
||||
role: Role.DRIVER,
|
||||
target: Target.START,
|
||||
firstDatetime: new Date('2023-09-01 07:00'),
|
||||
firstMinDatetime: new Date('2023-09-01 07:05'),
|
||||
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'),
|
||||
firstDatetime: new Date('2023-09-01T07:00Z'),
|
||||
firstMinDatetime: new Date('2023-09-01T07:05Z'),
|
||||
firstMaxDatetime: new Date('2023-09-01T07:15Z'),
|
||||
lastDatetime: new Date('2024-08-30T07:00Z'),
|
||||
lastMinDatetime: new Date('2024-08-30T06:45Z'),
|
||||
lastMaxDatetime: new Date('2024-08-30T07:15Z'),
|
||||
}),
|
||||
).toThrow(ArgumentInvalidException);
|
||||
expect(
|
||||
|
@ -43,12 +43,12 @@ describe('Actor time value object', () => {
|
|||
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 06:55'),
|
||||
lastDatetime: new Date('2024-08-30 07:00'),
|
||||
lastMinDatetime: new Date('2024-08-30 06:45'),
|
||||
lastMaxDatetime: new Date('2024-08-30 07:15'),
|
||||
firstDatetime: new Date('2023-09-01T07:00Z'),
|
||||
firstMinDatetime: new Date('2023-09-01T06:45Z'),
|
||||
firstMaxDatetime: new Date('2023-09-01T06:55Z'),
|
||||
lastDatetime: new Date('2024-08-30T07:00Z'),
|
||||
lastMinDatetime: new Date('2024-08-30T06:45Z'),
|
||||
lastMaxDatetime: new Date('2024-08-30T07:15Z'),
|
||||
}),
|
||||
).toThrow(ArgumentInvalidException);
|
||||
expect(
|
||||
|
@ -56,12 +56,12 @@ describe('Actor time value object', () => {
|
|||
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 07:05'),
|
||||
lastMaxDatetime: new Date('2024-08-30 07:15'),
|
||||
firstDatetime: new Date('2023-09-01T07:00Z'),
|
||||
firstMinDatetime: new Date('2023-09-01T06:45Z'),
|
||||
firstMaxDatetime: new Date('2023-09-01T07:15Z'),
|
||||
lastDatetime: new Date('2024-08-30T07:00Z'),
|
||||
lastMinDatetime: new Date('2024-08-30T07:05Z'),
|
||||
lastMaxDatetime: new Date('2024-08-30T07:15Z'),
|
||||
}),
|
||||
).toThrow(ArgumentInvalidException);
|
||||
expect(
|
||||
|
@ -69,12 +69,12 @@ describe('Actor time value object', () => {
|
|||
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 06:35'),
|
||||
firstDatetime: new Date('2023-09-01T07:00Z'),
|
||||
firstMinDatetime: new Date('2023-09-01T06:45Z'),
|
||||
firstMaxDatetime: new Date('2023-09-01T07:15Z'),
|
||||
lastDatetime: new Date('2024-08-30T07:00Z'),
|
||||
lastMinDatetime: new Date('2024-08-30T06:45Z'),
|
||||
lastMaxDatetime: new Date('2024-08-30T06:35Z'),
|
||||
}),
|
||||
).toThrow(ArgumentInvalidException);
|
||||
expect(
|
||||
|
@ -82,12 +82,12 @@ describe('Actor time value object', () => {
|
|||
new ActorTime({
|
||||
role: Role.DRIVER,
|
||||
target: Target.START,
|
||||
firstDatetime: new Date('2024-08-30 07:00'),
|
||||
firstMinDatetime: new Date('2024-08-30 06:45'),
|
||||
firstMaxDatetime: new Date('2024-08-30 07:15'),
|
||||
lastDatetime: new Date('2023-09-01 07:00'),
|
||||
lastMinDatetime: new Date('2023-09-01 06:45'),
|
||||
lastMaxDatetime: new Date('2023-09-01 07:15'),
|
||||
firstDatetime: new Date('2024-08-30T07:00Z'),
|
||||
firstMinDatetime: new Date('2024-08-30T06:45Z'),
|
||||
firstMaxDatetime: new Date('2024-08-30T07:15Z'),
|
||||
lastDatetime: new Date('2023-09-01T07:00Z'),
|
||||
lastMinDatetime: new Date('2023-09-01T06:45Z'),
|
||||
lastMaxDatetime: new Date('2023-09-01T07:15Z'),
|
||||
}),
|
||||
).toThrow(ArgumentInvalidException);
|
||||
expect(
|
||||
|
@ -95,12 +95,12 @@ describe('Actor time value object', () => {
|
|||
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-31 07:00'),
|
||||
lastMinDatetime: new Date('2024-08-31 06:45'),
|
||||
lastMaxDatetime: new Date('2024-08-31 06:35'),
|
||||
firstDatetime: new Date('2023-09-01T07:00Z'),
|
||||
firstMinDatetime: new Date('2023-09-01T06:45Z'),
|
||||
firstMaxDatetime: new Date('2023-09-01T07:15Z'),
|
||||
lastDatetime: new Date('2024-08-31T07:00Z'),
|
||||
lastMinDatetime: new Date('2024-08-31T06:45Z'),
|
||||
lastMaxDatetime: new Date('2024-08-31T06:35Z'),
|
||||
}),
|
||||
).toThrow(ArgumentInvalidException);
|
||||
});
|
||||
|
|
|
@ -67,6 +67,10 @@ class SomeSelector extends Selector {
|
|||
CandidateEntity.create({
|
||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||
role: Role.DRIVER,
|
||||
dateInterval: {
|
||||
lowerDate: '2023-08-28',
|
||||
higherDate: '2023-08-28',
|
||||
},
|
||||
driverWaypoints: [
|
||||
{
|
||||
lat: 48.678454,
|
||||
|
|
|
@ -6,78 +6,175 @@ import {
|
|||
describe('Calendar tools service', () => {
|
||||
describe('First date', () => {
|
||||
it('should return the first date for a given week day within a date range', () => {
|
||||
const firstDate: Date = CalendarTools.firstDate(
|
||||
1,
|
||||
'2023-08-31',
|
||||
'2023-09-07',
|
||||
);
|
||||
expect(firstDate.getDay()).toBe(1);
|
||||
expect(firstDate.getDate()).toBe(4);
|
||||
expect(firstDate.getMonth()).toBe(8);
|
||||
const secondDate: Date = CalendarTools.firstDate(
|
||||
5,
|
||||
'2023-08-31',
|
||||
'2023-09-07',
|
||||
);
|
||||
expect(secondDate.getDay()).toBe(5);
|
||||
expect(secondDate.getDate()).toBe(1);
|
||||
expect(secondDate.getMonth()).toBe(8);
|
||||
const thirdDate: Date = CalendarTools.firstDate(
|
||||
4,
|
||||
'2023-08-31',
|
||||
'2023-09-07',
|
||||
);
|
||||
expect(thirdDate.getDay()).toBe(4);
|
||||
expect(thirdDate.getDate()).toBe(31);
|
||||
expect(thirdDate.getMonth()).toBe(7);
|
||||
const firstDate: Date = CalendarTools.firstDate(1, {
|
||||
lowerDate: '2023-08-31',
|
||||
higherDate: '2023-09-07',
|
||||
});
|
||||
expect(firstDate.getUTCDay()).toBe(1);
|
||||
expect(firstDate.getUTCDate()).toBe(4);
|
||||
expect(firstDate.getUTCMonth()).toBe(8);
|
||||
const secondDate: Date = CalendarTools.firstDate(5, {
|
||||
lowerDate: '2023-08-31',
|
||||
higherDate: '2023-09-07',
|
||||
});
|
||||
expect(secondDate.getUTCDay()).toBe(5);
|
||||
expect(secondDate.getUTCDate()).toBe(1);
|
||||
expect(secondDate.getUTCMonth()).toBe(8);
|
||||
const thirdDate: Date = CalendarTools.firstDate(4, {
|
||||
lowerDate: '2023-08-31',
|
||||
higherDate: '2023-09-07',
|
||||
});
|
||||
expect(thirdDate.getUTCDay()).toBe(4);
|
||||
expect(thirdDate.getUTCDate()).toBe(31);
|
||||
expect(thirdDate.getUTCMonth()).toBe(7);
|
||||
});
|
||||
it('should throw an exception if a given week day is not within a date range', () => {
|
||||
expect(() => {
|
||||
CalendarTools.firstDate(1, '2023-09-05', '2023-09-07');
|
||||
CalendarTools.firstDate(1, {
|
||||
lowerDate: '2023-09-05',
|
||||
higherDate: '2023-09-07',
|
||||
});
|
||||
}).toThrow(CalendarToolsException);
|
||||
});
|
||||
it('should throw an exception if a given week day is invalid', () => {
|
||||
expect(() => {
|
||||
CalendarTools.firstDate(8, '2023-09-05', '2023-09-07');
|
||||
CalendarTools.firstDate(8, {
|
||||
lowerDate: '2023-09-05',
|
||||
higherDate: '2023-09-07',
|
||||
});
|
||||
}).toThrow(CalendarToolsException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Second date', () => {
|
||||
it('should return the last date for a given week day within a date range', () => {
|
||||
const firstDate: Date = CalendarTools.lastDate(
|
||||
0,
|
||||
'2023-09-30',
|
||||
'2024-09-30',
|
||||
);
|
||||
expect(firstDate.getDay()).toBe(0);
|
||||
expect(firstDate.getDate()).toBe(29);
|
||||
expect(firstDate.getMonth()).toBe(8);
|
||||
const secondDate: Date = CalendarTools.lastDate(
|
||||
5,
|
||||
'2023-09-30',
|
||||
'2024-09-30',
|
||||
);
|
||||
expect(secondDate.getDay()).toBe(5);
|
||||
expect(secondDate.getDate()).toBe(27);
|
||||
expect(secondDate.getMonth()).toBe(8);
|
||||
const thirdDate: Date = CalendarTools.lastDate(
|
||||
1,
|
||||
'2023-09-30',
|
||||
'2024-09-30',
|
||||
);
|
||||
expect(thirdDate.getDay()).toBe(1);
|
||||
expect(thirdDate.getDate()).toBe(30);
|
||||
expect(thirdDate.getMonth()).toBe(8);
|
||||
const firstDate: Date = CalendarTools.lastDate(0, {
|
||||
lowerDate: '2023-09-30',
|
||||
higherDate: '2024-09-30',
|
||||
});
|
||||
expect(firstDate.getUTCDay()).toBe(0);
|
||||
expect(firstDate.getUTCDate()).toBe(29);
|
||||
expect(firstDate.getUTCMonth()).toBe(8);
|
||||
const secondDate: Date = CalendarTools.lastDate(5, {
|
||||
lowerDate: '2023-09-30',
|
||||
higherDate: '2024-09-30',
|
||||
});
|
||||
expect(secondDate.getUTCDay()).toBe(5);
|
||||
expect(secondDate.getUTCDate()).toBe(27);
|
||||
expect(secondDate.getUTCMonth()).toBe(8);
|
||||
const thirdDate: Date = CalendarTools.lastDate(1, {
|
||||
lowerDate: '2023-09-30',
|
||||
higherDate: '2024-09-30',
|
||||
});
|
||||
expect(thirdDate.getUTCDay()).toBe(1);
|
||||
expect(thirdDate.getUTCDate()).toBe(30);
|
||||
expect(thirdDate.getUTCMonth()).toBe(8);
|
||||
});
|
||||
it('should throw an exception if a given week day is not within a date range', () => {
|
||||
expect(() => {
|
||||
CalendarTools.lastDate(2, '2024-09-27', '2024-09-30');
|
||||
CalendarTools.lastDate(2, {
|
||||
lowerDate: '2024-09-27',
|
||||
higherDate: '2024-09-30',
|
||||
});
|
||||
}).toThrow(CalendarToolsException);
|
||||
});
|
||||
it('should throw an exception if a given week day is invalid', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,13 +2,16 @@ import { Role } from '@modules/ad/core/domain/ad.types';
|
|||
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
||||
import { Target } from '@modules/ad/core/domain/candidate.types';
|
||||
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', () => {
|
||||
it('should create a new candidate entity', () => {
|
||||
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: 48.678454,
|
||||
|
@ -52,10 +55,15 @@ describe('Candidate entity', () => {
|
|||
});
|
||||
expect(candidateEntity.id.length).toBe(36);
|
||||
});
|
||||
|
||||
it('should set a candidate entity carpool path', () => {
|
||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||
role: Role.PASSENGER,
|
||||
dateInterval: {
|
||||
lowerDate: '2023-08-28',
|
||||
higherDate: '2023-08-28',
|
||||
},
|
||||
driverWaypoints: [
|
||||
{
|
||||
lat: 48.689445,
|
||||
|
@ -98,10 +106,8 @@ describe('Candidate entity', () => {
|
|||
},
|
||||
}).setCarpoolPath([
|
||||
{
|
||||
point: new Point({
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
}),
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
actors: [
|
||||
new Actor({
|
||||
role: Role.DRIVER,
|
||||
|
@ -114,10 +120,8 @@ describe('Candidate entity', () => {
|
|||
],
|
||||
},
|
||||
{
|
||||
point: new Point({
|
||||
lat: 48.8566,
|
||||
lon: 2.3522,
|
||||
}),
|
||||
lat: 48.8566,
|
||||
lon: 2.3522,
|
||||
actors: [
|
||||
new Actor({
|
||||
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', () => {
|
||||
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: 48.678454,
|
||||
|
@ -180,98 +189,113 @@ describe('Candidate entity', () => {
|
|||
expect(candidateEntity.getProps().distance).toBe(352688);
|
||||
expect(candidateEntity.getProps().duration).toBe(14587);
|
||||
});
|
||||
it('should not validate a candidate entity with exceeding distance detour', () => {
|
||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||
role: Role.DRIVER,
|
||||
driverWaypoints: [
|
||||
{
|
||||
lat: 48.678454,
|
||||
lon: 6.189745,
|
||||
|
||||
describe('detour validation', () => {
|
||||
it('should not validate a candidate entity with exceeding distance detour', () => {
|
||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||
role: Role.DRIVER,
|
||||
dateInterval: {
|
||||
lowerDate: '2023-08-28',
|
||||
higherDate: '2023-08-28',
|
||||
},
|
||||
{
|
||||
lat: 48.84877,
|
||||
lon: 2.398457,
|
||||
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,
|
||||
},
|
||||
],
|
||||
passengerWaypoints: [
|
||||
{
|
||||
lat: 48.849445,
|
||||
lon: 6.68651,
|
||||
}).setMetrics(458690, 13980);
|
||||
expect(candidateEntity.isDetourValid()).toBeFalsy();
|
||||
});
|
||||
it('should not validate a candidate entity with exceeding duration detour', () => {
|
||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||
role: Role.DRIVER,
|
||||
dateInterval: {
|
||||
lowerDate: '2023-08-28',
|
||||
higherDate: '2023-08-28',
|
||||
},
|
||||
{
|
||||
lat: 47.18746,
|
||||
lon: 2.89742,
|
||||
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,
|
||||
},
|
||||
],
|
||||
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);
|
||||
expect(candidateEntity.isDetourValid()).toBeFalsy();
|
||||
}).setMetrics(352368, 18314);
|
||||
expect(candidateEntity.isDetourValid()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
it('should not validate a candidate entity with exceeding duration detour', () => {
|
||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||
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();
|
||||
|
||||
describe('Journeys', () => {
|
||||
it('should create journeys', () => {});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { CarpoolPathCreator } from '@modules/ad/core/domain/carpool-path-creator.service';
|
||||
import { CarpoolPathCreatorException } from '@modules/ad/core/domain/match.errors';
|
||||
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({
|
||||
lat: 0,
|
||||
|
@ -34,71 +34,71 @@ describe('Carpool Path Creator Service', () => {
|
|||
[waypoint1, waypoint6],
|
||||
[waypoint2, waypoint5],
|
||||
);
|
||||
const carpoolSteps: CarpoolStep[] = carpoolPathCreator.carpoolPath();
|
||||
expect(carpoolSteps).toHaveLength(4);
|
||||
expect(carpoolSteps[0].actors.length).toBe(1);
|
||||
const carpoolPath: CarpoolPathItem[] = carpoolPathCreator.carpoolPath();
|
||||
expect(carpoolPath).toHaveLength(4);
|
||||
expect(carpoolPath[0].actors.length).toBe(1);
|
||||
});
|
||||
it('should create a simple carpool path with same destination for driver and passenger', () => {
|
||||
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
|
||||
[waypoint1, waypoint6],
|
||||
[waypoint2, waypoint6],
|
||||
);
|
||||
const carpoolSteps: CarpoolStep[] = carpoolPathCreator.carpoolPath();
|
||||
expect(carpoolSteps).toHaveLength(3);
|
||||
expect(carpoolSteps[0].actors.length).toBe(1);
|
||||
expect(carpoolSteps[1].actors.length).toBe(2);
|
||||
expect(carpoolSteps[2].actors.length).toBe(2);
|
||||
const carpoolPath: CarpoolPathItem[] = carpoolPathCreator.carpoolPath();
|
||||
expect(carpoolPath).toHaveLength(3);
|
||||
expect(carpoolPath[0].actors.length).toBe(1);
|
||||
expect(carpoolPath[1].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', () => {
|
||||
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
|
||||
[waypoint1, waypoint6],
|
||||
[waypoint1, waypoint6],
|
||||
);
|
||||
const carpoolSteps: CarpoolStep[] = carpoolPathCreator.carpoolPath();
|
||||
expect(carpoolSteps).toHaveLength(2);
|
||||
expect(carpoolSteps[0].actors.length).toBe(2);
|
||||
expect(carpoolSteps[1].actors.length).toBe(2);
|
||||
const carpoolPath: CarpoolPathItem[] = carpoolPathCreator.carpoolPath();
|
||||
expect(carpoolPath).toHaveLength(2);
|
||||
expect(carpoolPath[0].actors.length).toBe(2);
|
||||
expect(carpoolPath[1].actors.length).toBe(2);
|
||||
});
|
||||
it('should create a complex carpool path with 3 driver waypoints', () => {
|
||||
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
|
||||
[waypoint1, waypoint3, waypoint6],
|
||||
[waypoint2, waypoint5],
|
||||
);
|
||||
const carpoolSteps: CarpoolStep[] = carpoolPathCreator.carpoolPath();
|
||||
expect(carpoolSteps).toHaveLength(5);
|
||||
expect(carpoolSteps[0].actors.length).toBe(1);
|
||||
expect(carpoolSteps[1].actors.length).toBe(2);
|
||||
expect(carpoolSteps[2].actors.length).toBe(1);
|
||||
expect(carpoolSteps[3].actors.length).toBe(2);
|
||||
expect(carpoolSteps[4].actors.length).toBe(1);
|
||||
const carpoolPath: CarpoolPathItem[] = carpoolPathCreator.carpoolPath();
|
||||
expect(carpoolPath).toHaveLength(5);
|
||||
expect(carpoolPath[0].actors.length).toBe(1);
|
||||
expect(carpoolPath[1].actors.length).toBe(2);
|
||||
expect(carpoolPath[2].actors.length).toBe(1);
|
||||
expect(carpoolPath[3].actors.length).toBe(2);
|
||||
expect(carpoolPath[4].actors.length).toBe(1);
|
||||
});
|
||||
it('should create a complex carpool path with 4 driver waypoints', () => {
|
||||
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
|
||||
[waypoint1, waypoint3, waypoint4, waypoint6],
|
||||
[waypoint2, waypoint5],
|
||||
);
|
||||
const carpoolSteps: CarpoolStep[] = carpoolPathCreator.carpoolPath();
|
||||
expect(carpoolSteps).toHaveLength(6);
|
||||
expect(carpoolSteps[0].actors.length).toBe(1);
|
||||
expect(carpoolSteps[1].actors.length).toBe(2);
|
||||
expect(carpoolSteps[2].actors.length).toBe(1);
|
||||
expect(carpoolSteps[3].actors.length).toBe(1);
|
||||
expect(carpoolSteps[4].actors.length).toBe(2);
|
||||
expect(carpoolSteps[5].actors.length).toBe(1);
|
||||
const carpoolPath: CarpoolPathItem[] = carpoolPathCreator.carpoolPath();
|
||||
expect(carpoolPath).toHaveLength(6);
|
||||
expect(carpoolPath[0].actors.length).toBe(1);
|
||||
expect(carpoolPath[1].actors.length).toBe(2);
|
||||
expect(carpoolPath[2].actors.length).toBe(1);
|
||||
expect(carpoolPath[3].actors.length).toBe(1);
|
||||
expect(carpoolPath[4].actors.length).toBe(2);
|
||||
expect(carpoolPath[5].actors.length).toBe(1);
|
||||
});
|
||||
it('should create a alternate complex carpool path with 4 driver waypoints', () => {
|
||||
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
|
||||
[waypoint1, waypoint2, waypoint5, waypoint6],
|
||||
[waypoint3, waypoint4],
|
||||
);
|
||||
const carpoolSteps: CarpoolStep[] = carpoolPathCreator.carpoolPath();
|
||||
expect(carpoolSteps).toHaveLength(6);
|
||||
expect(carpoolSteps[0].actors.length).toBe(1);
|
||||
expect(carpoolSteps[1].actors.length).toBe(1);
|
||||
expect(carpoolSteps[2].actors.length).toBe(2);
|
||||
expect(carpoolSteps[3].actors.length).toBe(2);
|
||||
expect(carpoolSteps[4].actors.length).toBe(1);
|
||||
expect(carpoolSteps[5].actors.length).toBe(1);
|
||||
const carpoolPath: CarpoolPathItem[] = carpoolPathCreator.carpoolPath();
|
||||
expect(carpoolPath).toHaveLength(6);
|
||||
expect(carpoolPath[0].actors.length).toBe(1);
|
||||
expect(carpoolPath[1].actors.length).toBe(1);
|
||||
expect(carpoolPath[2].actors.length).toBe(2);
|
||||
expect(carpoolPath[3].actors.length).toBe(2);
|
||||
expect(carpoolPath[4].actors.length).toBe(1);
|
||||
expect(carpoolPath[5].actors.length).toBe(1);
|
||||
});
|
||||
it('should throw an exception if less than 2 driver waypoints are given', () => {
|
||||
expect(() => {
|
||||
|
|
|
@ -2,16 +2,13 @@ import { ArgumentOutOfRangeException } from '@mobicoop/ddd-library';
|
|||
import { Role } from '@modules/ad/core/domain/ad.types';
|
||||
import { Target } from '@modules/ad/core/domain/candidate.types';
|
||||
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 { 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';
|
||||
|
||||
describe('CarpoolStep value object', () => {
|
||||
it('should create a carpoolStep value object', () => {
|
||||
const carpoolStepVO = new CarpoolStep({
|
||||
point: new Point({
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
}),
|
||||
describe('Carpool Path Item value object', () => {
|
||||
it('should create a path item value object', () => {
|
||||
const carpoolPathItemVO = new CarpoolPathItem({
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
actors: [
|
||||
new Actor({
|
||||
role: Role.DRIVER,
|
||||
|
@ -23,28 +20,24 @@ describe('CarpoolStep value object', () => {
|
|||
}),
|
||||
],
|
||||
});
|
||||
expect(carpoolStepVO.point.lon).toBe(6.17651);
|
||||
expect(carpoolStepVO.point.lat).toBe(48.689445);
|
||||
expect(carpoolStepVO.actors).toHaveLength(2);
|
||||
expect(carpoolPathItemVO.lon).toBe(6.17651);
|
||||
expect(carpoolPathItemVO.lat).toBe(48.689445);
|
||||
expect(carpoolPathItemVO.actors).toHaveLength(2);
|
||||
});
|
||||
it('should throw an exception if actors is empty', () => {
|
||||
expect(() => {
|
||||
new CarpoolStep({
|
||||
point: new Point({
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
}),
|
||||
new CarpoolPathItem({
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
actors: [],
|
||||
});
|
||||
}).toThrow(ArgumentOutOfRangeException);
|
||||
});
|
||||
it('should throw an exception if actors contains more than one driver', () => {
|
||||
expect(() => {
|
||||
new CarpoolStep({
|
||||
point: new Point({
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
}),
|
||||
new CarpoolPathItem({
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
actors: [
|
||||
new Actor({
|
||||
role: Role.DRIVER,
|
|
@ -28,7 +28,9 @@ describe('Journey item value object', () => {
|
|||
expect(journeyItemVO.distance).toBe(48754);
|
||||
expect(journeyItemVO.lon).toBe(6.17651);
|
||||
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', () => {
|
||||
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 { Target } from '@modules/ad/core/domain/candidate.types';
|
||||
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 = {
|
||||
position: 0,
|
||||
|
@ -66,6 +65,10 @@ const matchQuery = new MatchQuery(
|
|||
const candidate: CandidateEntity = CandidateEntity.create({
|
||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||
role: Role.DRIVER,
|
||||
dateInterval: {
|
||||
lowerDate: '2023-08-28',
|
||||
higherDate: '2023-08-28',
|
||||
},
|
||||
driverWaypoints: [
|
||||
{
|
||||
lat: 48.678454,
|
||||
|
@ -108,10 +111,8 @@ const candidate: CandidateEntity = CandidateEntity.create({
|
|||
},
|
||||
}).setCarpoolPath([
|
||||
{
|
||||
point: new Point({
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
}),
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
actors: [
|
||||
new Actor({
|
||||
role: Role.DRIVER,
|
||||
|
@ -124,10 +125,8 @@ const candidate: CandidateEntity = CandidateEntity.create({
|
|||
],
|
||||
},
|
||||
{
|
||||
point: new Point({
|
||||
lat: 48.8566,
|
||||
lon: 2.3522,
|
||||
}),
|
||||
lat: 48.8566,
|
||||
lon: 2.3522,
|
||||
actors: [
|
||||
new Actor({
|
||||
role: Role.DRIVER,
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import {
|
||||
ArgumentInvalidException,
|
||||
ArgumentOutOfRangeException,
|
||||
} from '@mobicoop/ddd-library';
|
||||
import { ArgumentInvalidException } from '@mobicoop/ddd-library';
|
||||
import { Role } from '@modules/ad/core/domain/ad.types';
|
||||
import { Target } from '@modules/ad/core/domain/candidate.types';
|
||||
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({
|
||||
firstDate: new Date('2023-09-01'),
|
||||
lastDate: new Date('2024-08-30'),
|
||||
day: 5,
|
||||
journeyItems: [
|
||||
new JourneyItem({
|
||||
lat: 48.689445,
|
||||
|
@ -109,114 +105,9 @@ describe('Journey value object', () => {
|
|||
}),
|
||||
],
|
||||
});
|
||||
expect(journeyVO.day).toBe(5);
|
||||
expect(journeyVO.journeyItems).toHaveLength(4);
|
||||
expect(journeyVO.firstDate.getDate()).toBe(1);
|
||||
expect(journeyVO.lastDate.getMonth()).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);
|
||||
expect(journeyVO.firstDate.getUTCDate()).toBe(1);
|
||||
expect(journeyVO.lastDate.getUTCMonth()).toBe(7);
|
||||
});
|
||||
it('should throw an error if dates are inconsistent', () => {
|
||||
expect(
|
||||
|
@ -224,7 +115,6 @@ describe('Journey value object', () => {
|
|||
new Journey({
|
||||
firstDate: new Date('2023-09-01'),
|
||||
lastDate: new Date('2024-08-31'),
|
||||
day: 5,
|
||||
journeyItems: [
|
||||
new JourneyItem({
|
||||
lat: 48.689445,
|
||||
|
@ -326,7 +216,6 @@ describe('Journey value object', () => {
|
|||
new Journey({
|
||||
firstDate: new Date('2024-08-30'),
|
||||
lastDate: new Date('2023-09-01'),
|
||||
day: 5,
|
||||
journeyItems: [
|
||||
new JourneyItem({
|
||||
lat: 48.689445,
|
||||
|
@ -430,7 +319,6 @@ describe('Journey value object', () => {
|
|||
new Journey({
|
||||
firstDate: new Date('2023-09-01'),
|
||||
lastDate: new Date('2024-08-30'),
|
||||
day: 5,
|
||||
journeyItems: [
|
||||
new JourneyItem({
|
||||
lat: 48.689445,
|
||||
|
|
|
@ -50,6 +50,10 @@ const candidates: CandidateEntity[] = [
|
|||
CandidateEntity.create({
|
||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||
role: Role.DRIVER,
|
||||
dateInterval: {
|
||||
lowerDate: '2023-08-28',
|
||||
higherDate: '2023-08-28',
|
||||
},
|
||||
driverWaypoints: [
|
||||
{
|
||||
lat: 48.678454,
|
||||
|
@ -94,6 +98,10 @@ const candidates: CandidateEntity[] = [
|
|||
CandidateEntity.create({
|
||||
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||
role: Role.PASSENGER,
|
||||
dateInterval: {
|
||||
lowerDate: '2023-08-28',
|
||||
higherDate: '2023-08-28',
|
||||
},
|
||||
driverWaypoints: [
|
||||
{
|
||||
lat: 48.689445,
|
||||
|
|
|
@ -49,6 +49,10 @@ const matchQuery = new MatchQuery(
|
|||
const candidate: CandidateEntity = CandidateEntity.create({
|
||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||
role: Role.DRIVER,
|
||||
dateInterval: {
|
||||
lowerDate: '2023-08-28',
|
||||
higherDate: '2023-08-28',
|
||||
},
|
||||
driverWaypoints: [
|
||||
{
|
||||
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 { Target } from '@modules/ad/core/domain/candidate.types';
|
||||
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 = {
|
||||
position: 0,
|
||||
|
@ -70,6 +69,10 @@ const matchQuery = new MatchQuery(
|
|||
const candidate: CandidateEntity = CandidateEntity.create({
|
||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||
role: Role.DRIVER,
|
||||
dateInterval: {
|
||||
lowerDate: '2023-08-28',
|
||||
higherDate: '2023-08-28',
|
||||
},
|
||||
driverWaypoints: [
|
||||
{
|
||||
lat: 48.678454,
|
||||
|
@ -112,10 +115,8 @@ const candidate: CandidateEntity = CandidateEntity.create({
|
|||
},
|
||||
}).setCarpoolPath([
|
||||
{
|
||||
point: new Point({
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
}),
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
actors: [
|
||||
new Actor({
|
||||
role: Role.DRIVER,
|
||||
|
@ -128,10 +129,8 @@ const candidate: CandidateEntity = CandidateEntity.create({
|
|||
],
|
||||
},
|
||||
{
|
||||
point: new Point({
|
||||
lat: 48.8566,
|
||||
lon: 2.3522,
|
||||
}),
|
||||
lat: 48.8566,
|
||||
lon: 2.3522,
|
||||
actors: [
|
||||
new Actor({
|
||||
role: Role.DRIVER,
|
||||
|
|
|
@ -164,12 +164,14 @@ export class GraphhopperGeorouter implements GeorouterPort {
|
|||
points: [[number, number]],
|
||||
snappedWaypoints: [[number, number]],
|
||||
): number[] => {
|
||||
const indices = snappedWaypoints.map((waypoint) =>
|
||||
points.findIndex(
|
||||
(point) => point[0] == waypoint[0] && point[1] == waypoint[1],
|
||||
),
|
||||
const indices: number[] = snappedWaypoints.map(
|
||||
(waypoint: [number, number]) =>
|
||||
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
|
||||
.map(
|
||||
(value, index) =>
|
||||
|
|
Loading…
Reference in New Issue