wip - create journeys - no tests yet

This commit is contained in:
sbriat 2023-09-22 16:37:52 +02:00
parent dfc8dbcc51
commit 467d8a84f8
27 changed files with 860 additions and 622 deletions

View File

@ -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 }),
);

View File

@ -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());
}

View File

@ -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[]);

View File

@ -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,

View File

@ -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 = {

View File

@ -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 {

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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({
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({
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(),
}),
);

View File

@ -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',
);

View File

@ -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 (

View File

@ -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',
);

View File

@ -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();
};
/**

View File

@ -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();
}

View File

@ -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);
});

View File

@ -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,

View File

@ -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);
});
});

View File

@ -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,
}),
actors: [
new Actor({
role: Role.DRIVER,
@ -114,10 +120,8 @@ describe('Candidate entity', () => {
],
},
{
point: new Point({
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,10 +189,16 @@ describe('Candidate entity', () => {
expect(candidateEntity.getProps().distance).toBe(352688);
expect(candidateEntity.getProps().duration).toBe(14587);
});
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',
},
driverWaypoints: [
{
lat: 48.678454,
@ -231,6 +246,10 @@ describe('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,
@ -275,3 +294,8 @@ describe('Candidate entity', () => {
expect(candidateEntity.isDetourValid()).toBeFalsy();
});
});
describe('Journeys', () => {
it('should create journeys', () => {});
});
});

View File

@ -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(() => {

View File

@ -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({
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({
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({
new CarpoolPathItem({
lat: 48.689445,
lon: 6.17651,
}),
actors: [
new Actor({
role: Role.DRIVER,

View File

@ -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(

View File

@ -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,
}),
actors: [
new Actor({
role: Role.DRIVER,
@ -124,10 +125,8 @@ const candidate: CandidateEntity = CandidateEntity.create({
],
},
{
point: new Point({
lat: 48.8566,
lon: 2.3522,
}),
actors: [
new Actor({
role: Role.DRIVER,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,
}),
actors: [
new Actor({
role: Role.DRIVER,
@ -128,10 +129,8 @@ const candidate: CandidateEntity = CandidateEntity.create({
],
},
{
point: new Point({
lat: 48.8566,
lon: 2.3522,
}),
actors: [
new Actor({
role: Role.DRIVER,

View File

@ -164,12 +164,14 @@ export class GraphhopperGeorouter implements GeorouterPort {
points: [[number, number]],
snappedWaypoints: [[number, number]],
): number[] => {
const indices = snappedWaypoints.map((waypoint) =>
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) =>