add actorTime

This commit is contained in:
sbriat 2023-09-19 16:49:35 +02:00
parent 996759d001
commit 6b6a169dee
20 changed files with 481 additions and 143 deletions

View File

@ -1,5 +1,5 @@
import { Route } from '@modules/geography/core/domain/route.types';
import { Point } from '../types/point.type';
import { Route } from '../types/route.type';
export interface RouteProviderPort {
/**

View File

@ -0,0 +1,9 @@
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
import { Completer } from './completer.abstract';
export class JourneyCompleter extends Completer {
complete = async (
candidates: CandidateEntity[],
): Promise<CandidateEntity[]> =>
candidates.map((candidate: CandidateEntity) => candidate.createJourney());
}

View File

@ -1,7 +1,8 @@
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
import { Completer } from './completer.abstract';
import { MatchQuery } from '../match.query';
import { WayStep } from '@modules/ad/core/domain/value-objects/waystep.value-object';
import { CarpoolStep } from '@modules/ad/core/domain/value-objects/carpool-step.value-object';
import { Step } from '../../../types/step.type';
export class RouteCompleter extends Completer {
protected readonly type: RouteCompleterType;
@ -18,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 WayStep[]).map(
(wayStep: WayStep) => wayStep.point,
(candidate.getProps().carpoolSteps as CarpoolStep[]).map(
(carpoolStep: CarpoolStep) => carpoolStep.point,
),
);
candidate.setMetrics(
@ -30,14 +31,11 @@ export class RouteCompleter extends Completer {
case RouteCompleterType.DETAILED:
const detailedCandidateRoute =
await this.query.routeProvider.getDetailed(
(candidate.getProps().carpoolSteps as WayStep[]).map(
(wayStep: WayStep) => wayStep.point,
(candidate.getProps().carpoolSteps as CarpoolStep[]).map(
(carpoolStep: CarpoolStep) => carpoolStep.point,
),
);
candidate.setMetrics(
detailedCandidateRoute.distance,
detailedCandidateRoute.duration,
);
candidate.setSteps(detailedCandidateRoute.steps as Step[]);
break;
}
return candidate;

View File

@ -5,7 +5,6 @@ import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
import { MatchRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/match.request.dto';
import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
import { RouteProviderPort } from '../../ports/route-provider.port';
import { Route } from '@modules/geography/core/domain/route.types';
import {
Path,
PathCreator,
@ -13,6 +12,7 @@ import {
TypedRoute,
} from '@modules/ad/core/domain/path-creator.service';
import { Point } from '@modules/ad/core/domain/value-objects/point.value-object';
import { Route } from '../../types/route.type';
export class MatchQuery extends QueryBase {
driver?: boolean;

View File

@ -0,0 +1,12 @@
import { Point } from './point.type';
import { Step } from './step.type';
export type Route = {
distance: number;
duration: number;
fwdAzimuth: number;
backAzimuth: number;
distanceAzimuth: number;
points: Point[];
steps?: Step[];
};

View File

@ -0,0 +1,6 @@
import { Point } from './point.type';
export type Step = Point & {
duration: number;
distance?: number;
};

View File

@ -1,6 +1,7 @@
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
import { CandidateProps, CreateCandidateProps } from './candidate.types';
import { WayStepProps } from './value-objects/waystep.value-object';
import { CarpoolStepProps } from './value-objects/carpool-step.value-object';
import { StepProps } from './value-objects/step.value-object';
export class CandidateEntity extends AggregateRoot<CandidateProps> {
protected readonly _id: AggregateID;
@ -10,8 +11,8 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
return new CandidateEntity({ id: create.id, props });
};
setCarpoolPath = (waySteps: WayStepProps[]): CandidateEntity => {
this.props.carpoolSteps = waySteps;
setCarpoolPath = (carpoolSteps: CarpoolStepProps[]): CandidateEntity => {
this.props.carpoolSteps = carpoolSteps;
return this;
};
@ -21,9 +22,16 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
return this;
};
setSteps = (steps: StepProps[]): CandidateEntity => {
this.props.steps = steps;
return this;
};
isDetourValid = (): boolean =>
this._validateDistanceDetour() && this._validateDurationDetour();
createJourney = (): CandidateEntity => this;
private _validateDurationDetour = (): boolean =>
this.props.duration
? this.props.duration <=

View File

@ -1,7 +1,9 @@
import { Role } from './ad.types';
import { PointProps } from './value-objects/point.value-object';
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
import { WayStepProps } from './value-objects/waystep.value-object';
import { CarpoolStepProps } from './value-objects/carpool-step.value-object';
import { JourneyProps } from './value-objects/journey.value-object';
import { StepProps } from './value-objects/step.value-object';
// All properties that a Candidate has
export interface CandidateProps {
@ -10,11 +12,13 @@ export interface CandidateProps {
passengerWaypoints: PointProps[];
driverDistance: number;
driverDuration: number;
carpoolSteps?: WayStepProps[]; // carpool path for the crew (driver + passenger)
carpoolSteps?: CarpoolStepProps[];
distance?: number;
duration?: number;
steps?: StepProps[];
driverSchedule: ScheduleItemProps[];
passengerSchedule: ScheduleItemProps[];
journeys?: JourneyProps[];
spacetimeDetourRatio: SpacetimeDetourRatio;
}

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 { WayStep } from './value-objects/waystep.value-object';
import { CarpoolStep } from './value-objects/carpool-step.value-object';
export class CarpoolPathCreator {
private PRECISION = 5;
@ -23,29 +23,35 @@ export class CarpoolPathCreator {
}
/**
* Creates a path (a list of waysteps) between driver waypoints
* Creates a path (a list of carpoolSteps) 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 = (): WayStep[] =>
public carpoolPath = (): CarpoolStep[] =>
this._consolidate(
this._mixedWaysteps(this._driverWaysteps(), this._passengerWaysteps()),
this._mixedCarpoolSteps(
this._driverCarpoolSteps(),
this._passengerCarpoolSteps(),
),
);
private _mixedWaysteps = (
driverWaysteps: WayStep[],
passengerWaysteps: WayStep[],
): WayStep[] =>
driverWaysteps.length == 2
? this._simpleMixedWaysteps(driverWaysteps, passengerWaysteps)
: this._complexMixedWaysteps(driverWaysteps, passengerWaysteps);
private _mixedCarpoolSteps = (
driverCarpoolSteps: CarpoolStep[],
passengerCarpoolSteps: CarpoolStep[],
): CarpoolStep[] =>
driverCarpoolSteps.length == 2
? this._simpleMixedCarpoolSteps(driverCarpoolSteps, passengerCarpoolSteps)
: this._complexMixedCarpoolSteps(
driverCarpoolSteps,
passengerCarpoolSteps,
);
private _driverWaysteps = (): WayStep[] =>
private _driverCarpoolSteps = (): CarpoolStep[] =>
this.driverWaypoints.map(
(waypoint: Point, index: number) =>
new WayStep({
new CarpoolStep({
point: new Point({
lon: waypoint.lon,
lat: waypoint.lat,
@ -60,13 +66,13 @@ export class CarpoolPathCreator {
);
/**
* Creates the passenger waysteps with original passenger waypoints, adding driver waypoints that are the same
* Creates the passenger carpoolSteps with original passenger waypoints, adding driver waypoints that are the same
*/
private _passengerWaysteps = (): WayStep[] => {
const waysteps: WayStep[] = [];
private _passengerCarpoolSteps = (): CarpoolStep[] => {
const carpoolSteps: CarpoolStep[] = [];
this.passengerWaypoints.forEach(
(passengerWaypoint: Point, index: number) => {
const waystep: WayStep = new WayStep({
const carpoolStep: CarpoolStep = new CarpoolStep({
point: new Point({
lon: passengerWaypoint.lon,
lat: passengerWaypoint.lat,
@ -83,73 +89,78 @@ export class CarpoolPathCreator {
passengerWaypoint.isSame(driverWaypoint),
).length == 0
) {
waystep.actors.push(
carpoolStep.actors.push(
new Actor({
role: Role.DRIVER,
target: Target.NEUTRAL,
}),
);
}
waysteps.push(waystep);
carpoolSteps.push(carpoolStep);
},
);
return waysteps;
return carpoolSteps;
};
private _simpleMixedWaysteps = (
driverWaysteps: WayStep[],
passengerWaysteps: WayStep[],
): WayStep[] => [driverWaysteps[0], ...passengerWaysteps, driverWaysteps[1]];
private _simpleMixedCarpoolSteps = (
driverCarpoolSteps: CarpoolStep[],
passengerCarpoolSteps: CarpoolStep[],
): CarpoolStep[] => [
driverCarpoolSteps[0],
...passengerCarpoolSteps,
driverCarpoolSteps[1],
];
private _complexMixedWaysteps = (
driverWaysteps: WayStep[],
passengerWaysteps: WayStep[],
): WayStep[] => {
let mixedWaysteps: WayStep[] = [...driverWaysteps];
private _complexMixedCarpoolSteps = (
driverCarpoolSteps: CarpoolStep[],
passengerCarpoolSteps: CarpoolStep[],
): CarpoolStep[] => {
let mixedCarpoolSteps: CarpoolStep[] = [...driverCarpoolSteps];
const originInsertIndex: number = this._insertIndex(
passengerWaysteps[0],
driverWaysteps,
passengerCarpoolSteps[0],
driverCarpoolSteps,
);
mixedWaysteps = [
...mixedWaysteps.slice(0, originInsertIndex),
passengerWaysteps[0],
...mixedWaysteps.slice(originInsertIndex),
mixedCarpoolSteps = [
...mixedCarpoolSteps.slice(0, originInsertIndex),
passengerCarpoolSteps[0],
...mixedCarpoolSteps.slice(originInsertIndex),
];
const destinationInsertIndex: number =
this._insertIndex(
passengerWaysteps[passengerWaysteps.length - 1],
driverWaysteps,
passengerCarpoolSteps[passengerCarpoolSteps.length - 1],
driverCarpoolSteps,
) + 1;
mixedWaysteps = [
...mixedWaysteps.slice(0, destinationInsertIndex),
passengerWaysteps[passengerWaysteps.length - 1],
...mixedWaysteps.slice(destinationInsertIndex),
mixedCarpoolSteps = [
...mixedCarpoolSteps.slice(0, destinationInsertIndex),
passengerCarpoolSteps[passengerCarpoolSteps.length - 1],
...mixedCarpoolSteps.slice(destinationInsertIndex),
];
return mixedWaysteps;
return mixedCarpoolSteps;
};
private _insertIndex = (
targetWaystep: WayStep,
waysteps: WayStep[],
targetCarpoolStep: CarpoolStep,
carpoolSteps: CarpoolStep[],
): number =>
this._closestSegmentIndex(targetWaystep, this._segments(waysteps)) + 1;
this._closestSegmentIndex(targetCarpoolStep, this._segments(carpoolSteps)) +
1;
private _segments = (waysteps: WayStep[]): WayStep[][] => {
const segments: WayStep[][] = [];
waysteps.forEach((waystep: WayStep, index: number) => {
if (index < waysteps.length - 1)
segments.push([waystep, waysteps[index + 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]]);
});
return segments;
};
private _closestSegmentIndex = (
waystep: WayStep,
segments: WayStep[][],
carpoolStep: CarpoolStep,
segments: CarpoolStep[][],
): number => {
const distances: Map<number, number> = new Map();
segments.forEach((segment: WayStep[], index: number) => {
distances.set(index, this._distanceToSegment(waystep, segment));
segments.forEach((segment: CarpoolStep[], index: number) => {
distances.set(index, this._distanceToSegment(carpoolStep, segment));
});
const sortedDistances: Map<number, number> = new Map(
[...distances.entries()].sort((a, b) => a[1] - b[1]),
@ -158,30 +169,33 @@ export class CarpoolPathCreator {
return closestSegmentIndex;
};
private _distanceToSegment = (waystep: WayStep, segment: WayStep[]): number =>
private _distanceToSegment = (
carpoolStep: CarpoolStep,
segment: CarpoolStep[],
): number =>
parseFloat(
Math.sqrt(this._distanceToSegmentSquared(waystep, segment)).toFixed(
Math.sqrt(this._distanceToSegmentSquared(carpoolStep, segment)).toFixed(
this.PRECISION,
),
);
private _distanceToSegmentSquared = (
waystep: WayStep,
segment: WayStep[],
carpoolStep: CarpoolStep,
segment: CarpoolStep[],
): number => {
const length2: number = this._distanceSquared(
segment[0].point,
segment[1].point,
);
if (length2 == 0)
return this._distanceSquared(waystep.point, segment[0].point);
return this._distanceSquared(carpoolStep.point, segment[0].point);
const length: number = Math.max(
0,
Math.min(
1,
((waystep.point.lon - segment[0].point.lon) *
((carpoolStep.point.lon - segment[0].point.lon) *
(segment[1].point.lon - segment[0].point.lon) +
(waystep.point.lat - segment[0].point.lat) *
(carpoolStep.point.lat - segment[0].point.lat) *
(segment[1].point.lat - segment[0].point.lat)) /
length2,
),
@ -194,7 +208,7 @@ export class CarpoolPathCreator {
segment[0].point.lat +
length * (segment[1].point.lat - segment[0].point.lat),
});
return this._distanceSquared(waystep.point, newPoint);
return this._distanceSquared(carpoolStep.point, newPoint);
};
private _distanceSquared = (point1: Point, point2: Point): number =>
@ -213,29 +227,31 @@ export class CarpoolPathCreator {
: Target.INTERMEDIATE;
/**
* Consolidate waysteps by removing duplicate actors (eg. driver with neutral and start or finish target)
* Consolidate carpoolSteps by removing duplicate actors (eg. driver with neutral and start or finish target)
*/
private _consolidate = (waysteps: WayStep[]): WayStep[] => {
private _consolidate = (carpoolSteps: CarpoolStep[]): CarpoolStep[] => {
const uniquePoints: Point[] = [];
waysteps.forEach((waystep: WayStep) => {
carpoolSteps.forEach((carpoolStep: CarpoolStep) => {
if (
uniquePoints.find((point: Point) => point.isSame(waystep.point)) ===
uniquePoints.find((point: Point) => point.isSame(carpoolStep.point)) ===
undefined
)
uniquePoints.push(
new Point({
lon: waystep.point.lon,
lat: waystep.point.lat,
lon: carpoolStep.point.lon,
lat: carpoolStep.point.lat,
}),
);
});
return uniquePoints.map(
(point: Point) =>
new WayStep({
new CarpoolStep({
point,
actors: waysteps
.filter((waystep: WayStep) => waystep.point.isSame(point))
.map((waystep: WayStep) => waystep.actors)
actors: carpoolSteps
.filter((carpoolStep: CarpoolStep) =>
carpoolStep.point.isSame(point),
)
.map((carpoolStep: CarpoolStep) => carpoolStep.actors)
.flat(),
}),
);

View File

@ -1,7 +1,7 @@
import { Route } from '@modules/geography/core/domain/route.types';
import { Role } from './ad.types';
import { Point } from './value-objects/point.value-object';
import { PathCreatorException } from './match.errors';
import { Route } from '../application/types/route.type';
export class PathCreator {
constructor(

View File

@ -0,0 +1,51 @@
import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
import { Role } from '../ad.types';
import { Target } from '../candidate.types';
import { ActorProps } from './actor.value-object';
/** Note:
* Value Objects with multiple properties can contain
* other Value Objects inside if needed.
* */
export interface ActorTimeProps extends ActorProps {
time: string;
minTime: string;
maxTime: string;
}
export class ActorTime extends ValueObject<ActorTimeProps> {
get time(): string {
return this.props.time;
}
get minTime(): string {
return this.props.minTime;
}
get maxTime(): string {
return this.props.maxTime;
}
get role(): Role {
return this.props.role;
}
get target(): Target {
return this.props.target;
}
protected validate(props: ActorTimeProps): void {
this._validateTime(props.time, 'time');
this._validateTime(props.minTime, 'minTime');
this._validateTime(props.maxTime, 'maxTime');
}
private _validateTime(time: string, property: string): void {
if (time.split(':').length != 2)
throw new ArgumentInvalidException(`${property} is invalid`);
if (parseInt(time.split(':')[0]) < 0 || parseInt(time.split(':')[0]) > 23)
throw new ArgumentInvalidException(`${property} is invalid`);
if (parseInt(time.split(':')[1]) < 0 || parseInt(time.split(':')[1]) > 59)
throw new ArgumentInvalidException(`${property} is invalid`);
}
}

View File

@ -11,12 +11,12 @@ import { Point } from './point.value-object';
* other Value Objects inside if needed.
* */
export interface WayStepProps {
export interface CarpoolStepProps {
point: Point;
actors: Actor[];
}
export class WayStep extends ValueObject<WayStepProps> {
export class CarpoolStep extends ValueObject<CarpoolStepProps> {
get point(): Point {
return this.props.point;
}
@ -25,7 +25,7 @@ export class WayStep extends ValueObject<WayStepProps> {
return this.props.actors;
}
protected validate(props: WayStepProps): void {
protected validate(props: CarpoolStepProps): void {
if (props.actors.length <= 0)
throw new ArgumentOutOfRangeException('at least one actor is required');
if (
@ -33,7 +33,7 @@ export class WayStep extends ValueObject<WayStepProps> {
1
)
throw new ArgumentOutOfRangeException(
'a waystep can contain only one driver',
'a carpoolStep can contain only one driver',
);
}
}

View File

@ -0,0 +1,42 @@
import {
ArgumentOutOfRangeException,
ValueObject,
} from '@mobicoop/ddd-library';
import { ScheduleItemProps } from './schedule-item.value-object';
import { ActorTime } from './actor-time.value-object';
/** Note:
* Value Objects with multiple properties can contain
* other Value Objects inside if needed.
* */
export interface JourneyProps extends ScheduleItemProps {
firstDate: Date;
lastDate: Date;
actorTimes: ActorTime[];
}
export class Journey extends ValueObject<JourneyProps> {
get firstDate(): Date {
return this.props.firstDate;
}
get lastDate(): Date {
return this.props.lastDate;
}
get actorTimes(): ActorTime[] {
return this.props.actorTimes;
}
protected validate(props: JourneyProps): void {
if (props.firstDate > props.lastDate)
throw new ArgumentOutOfRangeException(
'firstDate must be before lastDate',
);
if (props.actorTimes.length <= 0)
throw new ArgumentOutOfRangeException(
'at least one actorTime is required',
);
}
}

View File

@ -28,7 +28,6 @@ export class ScheduleItem extends ValueObject<ScheduleItemProps> {
return this.props.margin;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected validate(props: ScheduleItemProps): void {
if (props.day < 0 || props.day > 6)
throw new ArgumentOutOfRangeException('day must be between 0 and 6');

View File

@ -0,0 +1,41 @@
import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
import { PointProps } from './point.value-object';
/** Note:
* Value Objects with multiple properties can contain
* other Value Objects inside if needed.
* */
export interface StepProps extends PointProps {
duration: number;
distance?: number;
}
export class Step extends ValueObject<StepProps> {
get duration(): number {
return this.props.duration;
}
get distance(): number | undefined {
return this.props.distance;
}
get lon(): number {
return this.props.lon;
}
get lat(): number {
return this.props.lat;
}
protected validate(props: StepProps): void {
if (props.duration < 0)
throw new ArgumentInvalidException(
'duration must be greater than or equal to 0',
);
if (props.distance !== undefined && props.distance < 0)
throw new ArgumentInvalidException(
'distance must be greater than or equal to 0',
);
}
}

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 { WayStep } from '@modules/ad/core/domain/value-objects/waystep.value-object';
import { CarpoolStep } from '@modules/ad/core/domain/value-objects/carpool-step.value-object';
const waypoint1: Point = new Point({
lat: 0,
@ -34,71 +34,71 @@ describe('Carpool Path Creator Service', () => {
[waypoint1, waypoint6],
[waypoint2, waypoint5],
);
const waysteps: WayStep[] = carpoolPathCreator.carpoolPath();
expect(waysteps).toHaveLength(4);
expect(waysteps[0].actors.length).toBe(1);
const carpoolSteps: CarpoolStep[] = carpoolPathCreator.carpoolPath();
expect(carpoolSteps).toHaveLength(4);
expect(carpoolSteps[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 waysteps: WayStep[] = carpoolPathCreator.carpoolPath();
expect(waysteps).toHaveLength(3);
expect(waysteps[0].actors.length).toBe(1);
expect(waysteps[1].actors.length).toBe(2);
expect(waysteps[2].actors.length).toBe(2);
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);
});
it('should create a simple carpool path with same waypoints for driver and passenger', () => {
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
[waypoint1, waypoint6],
[waypoint1, waypoint6],
);
const waysteps: WayStep[] = carpoolPathCreator.carpoolPath();
expect(waysteps).toHaveLength(2);
expect(waysteps[0].actors.length).toBe(2);
expect(waysteps[1].actors.length).toBe(2);
const carpoolSteps: CarpoolStep[] = carpoolPathCreator.carpoolPath();
expect(carpoolSteps).toHaveLength(2);
expect(carpoolSteps[0].actors.length).toBe(2);
expect(carpoolSteps[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 waysteps: WayStep[] = carpoolPathCreator.carpoolPath();
expect(waysteps).toHaveLength(5);
expect(waysteps[0].actors.length).toBe(1);
expect(waysteps[1].actors.length).toBe(2);
expect(waysteps[2].actors.length).toBe(1);
expect(waysteps[3].actors.length).toBe(2);
expect(waysteps[4].actors.length).toBe(1);
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);
});
it('should create a complex carpool path with 4 driver waypoints', () => {
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
[waypoint1, waypoint3, waypoint4, waypoint6],
[waypoint2, waypoint5],
);
const waysteps: WayStep[] = carpoolPathCreator.carpoolPath();
expect(waysteps).toHaveLength(6);
expect(waysteps[0].actors.length).toBe(1);
expect(waysteps[1].actors.length).toBe(2);
expect(waysteps[2].actors.length).toBe(1);
expect(waysteps[3].actors.length).toBe(1);
expect(waysteps[4].actors.length).toBe(2);
expect(waysteps[5].actors.length).toBe(1);
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);
});
it('should create a alternate complex carpool path with 4 driver waypoints', () => {
const carpoolPathCreator: CarpoolPathCreator = new CarpoolPathCreator(
[waypoint1, waypoint2, waypoint5, waypoint6],
[waypoint3, waypoint4],
);
const waysteps: WayStep[] = carpoolPathCreator.carpoolPath();
expect(waysteps).toHaveLength(6);
expect(waysteps[0].actors.length).toBe(1);
expect(waysteps[1].actors.length).toBe(1);
expect(waysteps[2].actors.length).toBe(2);
expect(waysteps[3].actors.length).toBe(2);
expect(waysteps[4].actors.length).toBe(1);
expect(waysteps[5].actors.length).toBe(1);
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);
});
it('should throw an exception if less than 2 driver waypoints are given', () => {
try {

View File

@ -3,11 +3,11 @@ 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 { WayStep } from '@modules/ad/core/domain/value-objects/waystep.value-object';
import { CarpoolStep } from '@modules/ad/core/domain/value-objects/carpool-step.value-object';
describe('WayStep value object', () => {
it('should create a waystep value object', () => {
const wayStepVO = new WayStep({
describe('CarpoolStep value object', () => {
it('should create a carpoolStep value object', () => {
const carpoolStepVO = new CarpoolStep({
point: new Point({
lat: 48.689445,
lon: 6.17651,
@ -23,13 +23,13 @@ describe('WayStep value object', () => {
}),
],
});
expect(wayStepVO.point.lon).toBe(6.17651);
expect(wayStepVO.point.lat).toBe(48.689445);
expect(wayStepVO.actors).toHaveLength(2);
expect(carpoolStepVO.point.lon).toBe(6.17651);
expect(carpoolStepVO.point.lat).toBe(48.689445);
expect(carpoolStepVO.actors).toHaveLength(2);
});
it('should throw an exception if actors is empty', () => {
try {
new WayStep({
new CarpoolStep({
point: new Point({
lat: 48.689445,
lon: 6.17651,
@ -42,7 +42,7 @@ describe('WayStep value object', () => {
});
it('should throw an exception if actors contains more than one driver', () => {
try {
new WayStep({
new CarpoolStep({
point: new Point({
lat: 48.689445,
lon: 6.17651,

View File

@ -0,0 +1,151 @@
import { JourneyCompleter } from '@modules/ad/core/application/queries/match/completer/journey.completer';
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
import { Waypoint } from '@modules/ad/core/application/types/waypoint.type';
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,
lat: 48.689445,
lon: 6.17651,
houseNumber: '5',
street: 'Avenue Foch',
locality: 'Nancy',
postalCode: '54000',
country: 'France',
};
const destinationWaypoint: Waypoint = {
position: 1,
lat: 48.8566,
lon: 2.3522,
locality: 'Paris',
postalCode: '75000',
country: 'France',
};
const matchQuery = new MatchQuery(
{
algorithmType: AlgorithmType.PASSENGER_ORIENTED,
driver: true,
passenger: true,
frequency: Frequency.PUNCTUAL,
fromDate: '2023-08-28',
toDate: '2023-08-28',
schedule: [
{
time: '07:05',
},
],
strict: false,
waypoints: [originWaypoint, destinationWaypoint],
},
{
getBasic: jest.fn().mockImplementation(() => ({
distance: 350101,
duration: 14422,
fwdAzimuth: 273,
backAzimuth: 93,
distanceAzimuth: 336544,
points: [],
})),
getDetailed: jest.fn().mockImplementation(() => ({
distance: 350102,
duration: 14423,
fwdAzimuth: 273,
backAzimuth: 93,
distanceAzimuth: 336544,
points: [],
})),
},
);
const candidate: 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.689445,
lon: 6.17651,
},
{
lat: 48.8566,
lon: 2.3522,
},
],
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,
},
}).setCarpoolPath([
{
point: new Point({
lat: 48.689445,
lon: 6.17651,
}),
actors: [
new Actor({
role: Role.DRIVER,
target: Target.START,
}),
new Actor({
role: Role.PASSENGER,
target: Target.START,
}),
],
},
{
point: new Point({
lat: 48.8566,
lon: 2.3522,
}),
actors: [
new Actor({
role: Role.DRIVER,
target: Target.FINISH,
}),
new Actor({
role: Role.PASSENGER,
target: Target.FINISH,
}),
],
},
]);
describe('Journey completer', () => {
it('should complete candidates with their journey', async () => {
const journeyCompleter: JourneyCompleter = new JourneyCompleter(matchQuery);
const completedCandidates: CandidateEntity[] =
await journeyCompleter.complete([candidate]);
expect(completedCandidates.length).toBe(1);
});
});

View File

@ -56,12 +56,13 @@ const matchQuery = new MatchQuery(
points: [],
})),
getDetailed: jest.fn().mockImplementation(() => ({
distance: 350102,
duration: 14423,
distance: 350101,
duration: 14422,
fwdAzimuth: 273,
backAzimuth: 93,
distanceAzimuth: 336544,
points: [],
steps: [jest.fn(), jest.fn(), jest.fn(), jest.fn()],
})),
},
);
@ -163,6 +164,6 @@ describe('Route completer', () => {
const completedCandidates: CandidateEntity[] =
await routeCompleter.complete([candidate]);
expect(completedCandidates.length).toBe(1);
expect(completedCandidates[0].getProps().distance).toBe(350102);
expect(completedCandidates[0].getProps().steps).toHaveLength(4);
});
});

View File

@ -3,9 +3,9 @@ import {
AD_GET_DETAILED_ROUTE_CONTROLLER,
} from '@modules/ad/ad.di-tokens';
import { Point } from '@modules/ad/core/application/types/point.type';
import { Route } from '@modules/ad/core/application/types/route.type';
import { RouteProvider } from '@modules/ad/infrastructure/route-provider';
import { GetRouteControllerPort } from '@modules/geography/core/application/ports/get-route-controller.port';
import { Route } from '@modules/geography/core/domain/route.types';
import { Test, TestingModule } from '@nestjs/testing';
const originPoint: Point = {