Merge branch 'matcherEngine' into 'main'
Matcher engine See merge request v3/service/matcher!2
This commit is contained in:
commit
36c0f0fe11
|
@ -13,7 +13,7 @@ DEFAULT_TIMEZONE=Europe/Paris
|
||||||
# default number of seats proposed as driver
|
# default number of seats proposed as driver
|
||||||
DEFAULT_SEATS=3
|
DEFAULT_SEATS=3
|
||||||
# algorithm type
|
# algorithm type
|
||||||
ALGORITHM=classic
|
ALGORITHM=CLASSIC
|
||||||
# strict algorithm (if relevant with the algorithm type)
|
# strict algorithm (if relevant with the algorithm type)
|
||||||
# if set to true, matches are made so that
|
# if set to true, matches are made so that
|
||||||
# punctual ads match only with punctual ads and
|
# punctual ads match only with punctual ads and
|
||||||
|
@ -38,7 +38,6 @@ VALIDITY_DURATION=365
|
||||||
MAX_DETOUR_DISTANCE_RATIO=0.3
|
MAX_DETOUR_DISTANCE_RATIO=0.3
|
||||||
MAX_DETOUR_DURATION_RATIO=0.3
|
MAX_DETOUR_DURATION_RATIO=0.3
|
||||||
|
|
||||||
|
|
||||||
# PRISMA
|
# PRISMA
|
||||||
DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=matcher"
|
DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=matcher"
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"test:integration": "npm run migrate:test && dotenv -e .env.test -- jest --testPathPattern 'tests/integration/' --verbose",
|
"test:integration": "npm run migrate:test && dotenv -e .env.test -- jest --testPathPattern 'tests/integration/' --verbose",
|
||||||
"test:integration:ci": "npm run migrate:test:ci && dotenv -e ci/.env.ci -- jest --testPathPattern 'tests/integration/'",
|
"test:integration:ci": "npm run migrate:test:ci && dotenv -e ci/.env.ci -- jest --testPathPattern 'tests/integration/'",
|
||||||
"test:cov": "jest --testPathPattern 'tests/unit/' --coverage",
|
"test:cov": "jest --testPathPattern 'tests/unit/' --coverage",
|
||||||
|
"test:cov:watch": "jest --testPathPattern 'tests/unit/' --coverage --watch",
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||||
"generate": "docker exec v3-matcher-api sh -c 'npx prisma generate'",
|
"generate": "docker exec v3-matcher-api sh -c 'npx prisma generate'",
|
||||||
"migrate": "docker exec v3-matcher-api sh -c 'npx prisma migrate dev'",
|
"migrate": "docker exec v3-matcher-api sh -c 'npx prisma migrate dev'",
|
||||||
|
|
|
@ -4,10 +4,10 @@ import { Geodesic, GeodesicClass } from 'geographiclib-geodesic';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MatcherGeodesic implements IGeodesic {
|
export class MatcherGeodesic implements IGeodesic {
|
||||||
_geod: GeodesicClass;
|
private geod: GeodesicClass;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._geod = Geodesic.WGS84;
|
this.geod = Geodesic.WGS84;
|
||||||
}
|
}
|
||||||
|
|
||||||
inverse = (
|
inverse = (
|
||||||
|
@ -16,7 +16,7 @@ export class MatcherGeodesic implements IGeodesic {
|
||||||
lon2: number,
|
lon2: number,
|
||||||
lat2: number,
|
lat2: number,
|
||||||
): { azimuth: number; distance: number } => {
|
): { azimuth: number; distance: number } => {
|
||||||
const { azi2: azimuth, s12: distance } = this._geod.Inverse(
|
const { azi2: azimuth, s12: distance } = this.geod.Inverse(
|
||||||
lat1,
|
lat1,
|
||||||
lon1,
|
lon1,
|
||||||
lat2,
|
lat2,
|
||||||
|
|
|
@ -4,6 +4,10 @@ import { IGeorouter } from '../../domain/interfaces/georouter.interface';
|
||||||
import { GraphhopperGeorouter } from './graphhopper-georouter';
|
import { GraphhopperGeorouter } from './graphhopper-georouter';
|
||||||
import { HttpService } from '@nestjs/axios';
|
import { HttpService } from '@nestjs/axios';
|
||||||
import { MatcherGeodesic } from './geodesic';
|
import { MatcherGeodesic } from './geodesic';
|
||||||
|
import {
|
||||||
|
MatcherException,
|
||||||
|
MatcherExceptionCode,
|
||||||
|
} from '../../exceptions/matcher.exception';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GeorouterCreator implements ICreateGeorouter {
|
export class GeorouterCreator implements ICreateGeorouter {
|
||||||
|
@ -17,7 +21,10 @@ export class GeorouterCreator implements ICreateGeorouter {
|
||||||
case 'graphhopper':
|
case 'graphhopper':
|
||||||
return new GraphhopperGeorouter(url, this.httpService, this.geodesic);
|
return new GraphhopperGeorouter(url, this.httpService, this.geodesic);
|
||||||
default:
|
default:
|
||||||
throw new Error('Unknown geocoder');
|
throw new MatcherException(
|
||||||
|
MatcherExceptionCode.INVALID_ARGUMENT,
|
||||||
|
'Unknown geocoder',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,82 +9,85 @@ import { IGeodesic } from '../../domain/interfaces/geodesic.interface';
|
||||||
import { NamedRoute } from '../../domain/entities/ecosystem/named-route';
|
import { NamedRoute } from '../../domain/entities/ecosystem/named-route';
|
||||||
import { Route } from '../../domain/entities/ecosystem/route';
|
import { Route } from '../../domain/entities/ecosystem/route';
|
||||||
import { SpacetimePoint } from '../../domain/entities/ecosystem/spacetime-point';
|
import { SpacetimePoint } from '../../domain/entities/ecosystem/spacetime-point';
|
||||||
|
import {
|
||||||
|
MatcherException,
|
||||||
|
MatcherExceptionCode,
|
||||||
|
} from '../../exceptions/matcher.exception';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphhopperGeorouter implements IGeorouter {
|
export class GraphhopperGeorouter implements IGeorouter {
|
||||||
_url: string;
|
private url: string;
|
||||||
_urlArgs: Array<string>;
|
private urlArgs: Array<string>;
|
||||||
_withTime: boolean;
|
private withTime: boolean;
|
||||||
_withPoints: boolean;
|
private withPoints: boolean;
|
||||||
_withDistance: boolean;
|
private withDistance: boolean;
|
||||||
_paths: Array<Path>;
|
private paths: Array<Path>;
|
||||||
_httpService: HttpService;
|
private httpService: HttpService;
|
||||||
_geodesic: IGeodesic;
|
private geodesic: IGeodesic;
|
||||||
|
|
||||||
constructor(url: string, httpService: HttpService, geodesic: IGeodesic) {
|
constructor(url: string, httpService: HttpService, geodesic: IGeodesic) {
|
||||||
this._url = url + '/route?';
|
this.url = url + '/route?';
|
||||||
this._httpService = httpService;
|
this.httpService = httpService;
|
||||||
this._geodesic = geodesic;
|
this.geodesic = geodesic;
|
||||||
}
|
}
|
||||||
|
|
||||||
route = async (
|
route = async (
|
||||||
paths: Array<Path>,
|
paths: Array<Path>,
|
||||||
settings: GeorouterSettings,
|
settings: GeorouterSettings,
|
||||||
): Promise<Array<NamedRoute>> => {
|
): Promise<Array<NamedRoute>> => {
|
||||||
this._setDefaultUrlArgs();
|
this.setDefaultUrlArgs();
|
||||||
this._setWithTime(settings.withTime);
|
this.setWithTime(settings.withTime);
|
||||||
this._setWithPoints(settings.withPoints);
|
this.setWithPoints(settings.withPoints);
|
||||||
this._setWithDistance(settings.withDistance);
|
this.setWithDistance(settings.withDistance);
|
||||||
this._paths = paths;
|
this.paths = paths;
|
||||||
return await this._getRoutes();
|
return await this.getRoutes();
|
||||||
};
|
};
|
||||||
|
|
||||||
_setDefaultUrlArgs = (): void => {
|
private setDefaultUrlArgs = (): void => {
|
||||||
this._urlArgs = [
|
this.urlArgs = ['vehicle=car', 'weighting=fastest', 'points_encoded=false'];
|
||||||
'vehicle=car',
|
|
||||||
'weighting=fastest',
|
|
||||||
'points_encoded=false',
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_setWithTime = (withTime: boolean): void => {
|
private setWithTime = (withTime: boolean): void => {
|
||||||
this._withTime = withTime;
|
this.withTime = withTime;
|
||||||
if (withTime) {
|
if (withTime) {
|
||||||
this._urlArgs.push('details=time');
|
this.urlArgs.push('details=time');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_setWithPoints = (withPoints: boolean): void => {
|
private setWithPoints = (withPoints: boolean): void => {
|
||||||
this._withPoints = withPoints;
|
this.withPoints = withPoints;
|
||||||
if (!withPoints) {
|
if (!withPoints) {
|
||||||
this._urlArgs.push('calc_points=false');
|
this.urlArgs.push('calc_points=false');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_setWithDistance = (withDistance: boolean): void => {
|
private setWithDistance = (withDistance: boolean): void => {
|
||||||
this._withDistance = withDistance;
|
this.withDistance = withDistance;
|
||||||
if (withDistance) {
|
if (withDistance) {
|
||||||
this._urlArgs.push('instructions=true');
|
this.urlArgs.push('instructions=true');
|
||||||
} else {
|
} else {
|
||||||
this._urlArgs.push('instructions=false');
|
this.urlArgs.push('instructions=false');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_getRoutes = async (): Promise<Array<NamedRoute>> => {
|
private getRoutes = async (): Promise<Array<NamedRoute>> => {
|
||||||
const routes = Promise.all(
|
const routes = Promise.all(
|
||||||
this._paths.map(async (path) => {
|
this.paths.map(async (path) => {
|
||||||
const url: string = [
|
const url: string = [
|
||||||
this._getUrl(),
|
this.getUrl(),
|
||||||
'&point=',
|
'&point=',
|
||||||
path.points
|
path.points
|
||||||
.map((point) => [point.lat, point.lon].join())
|
.map((point) => [point.lat, point.lon].join())
|
||||||
.join('&point='),
|
.join('&point='),
|
||||||
].join('');
|
].join('');
|
||||||
const route = await lastValueFrom(
|
const route = await lastValueFrom(
|
||||||
this._httpService.get(url).pipe(
|
this.httpService.get(url).pipe(
|
||||||
map((res) => (res.data ? this._createRoute(res) : undefined)),
|
map((res) => (res.data ? this.createRoute(res) : undefined)),
|
||||||
catchError((error: AxiosError) => {
|
catchError((error: AxiosError) => {
|
||||||
throw new Error('Georouter unavailable : ' + error.message);
|
throw new MatcherException(
|
||||||
|
MatcherExceptionCode.INTERNAL,
|
||||||
|
'Georouter unavailable : ' + error.message,
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -97,12 +100,14 @@ export class GraphhopperGeorouter implements IGeorouter {
|
||||||
return routes;
|
return routes;
|
||||||
};
|
};
|
||||||
|
|
||||||
_getUrl = (): string => {
|
private getUrl = (): string => {
|
||||||
return [this._url, this._urlArgs.join('&')].join('');
|
return [this.url, this.urlArgs.join('&')].join('');
|
||||||
};
|
};
|
||||||
|
|
||||||
_createRoute = (response: AxiosResponse<GraphhopperResponse>): Route => {
|
private createRoute = (
|
||||||
const route = new Route(this._geodesic);
|
response: AxiosResponse<GraphhopperResponse>,
|
||||||
|
): Route => {
|
||||||
|
const route = new Route(this.geodesic);
|
||||||
if (response.data.paths && response.data.paths[0]) {
|
if (response.data.paths && response.data.paths[0]) {
|
||||||
const shortestPath = response.data.paths[0];
|
const shortestPath = response.data.paths[0];
|
||||||
route.distance = shortestPath.distance ?? 0;
|
route.distance = shortestPath.distance ?? 0;
|
||||||
|
@ -124,7 +129,7 @@ export class GraphhopperGeorouter implements IGeorouter {
|
||||||
if (shortestPath.instructions)
|
if (shortestPath.instructions)
|
||||||
instructions = shortestPath.instructions;
|
instructions = shortestPath.instructions;
|
||||||
route.setSpacetimePoints(
|
route.setSpacetimePoints(
|
||||||
this._generateSpacetimePoints(
|
this.generateSpacetimePoints(
|
||||||
shortestPath.points.coordinates,
|
shortestPath.points.coordinates,
|
||||||
shortestPath.snapped_waypoints.coordinates,
|
shortestPath.snapped_waypoints.coordinates,
|
||||||
shortestPath.details.time,
|
shortestPath.details.time,
|
||||||
|
@ -137,15 +142,15 @@ export class GraphhopperGeorouter implements IGeorouter {
|
||||||
return route;
|
return route;
|
||||||
};
|
};
|
||||||
|
|
||||||
_generateSpacetimePoints = (
|
private generateSpacetimePoints = (
|
||||||
points: Array<Array<number>>,
|
points: Array<Array<number>>,
|
||||||
snappedWaypoints: Array<Array<number>>,
|
snappedWaypoints: Array<Array<number>>,
|
||||||
durations: Array<Array<number>>,
|
durations: Array<Array<number>>,
|
||||||
instructions: Array<GraphhopperInstruction>,
|
instructions: Array<GraphhopperInstruction>,
|
||||||
): Array<SpacetimePoint> => {
|
): Array<SpacetimePoint> => {
|
||||||
const indices = this._getIndices(points, snappedWaypoints);
|
const indices = this.getIndices(points, snappedWaypoints);
|
||||||
const times = this._getTimes(durations, indices);
|
const times = this.getTimes(durations, indices);
|
||||||
const distances = this._getDistances(instructions, indices);
|
const distances = this.getDistances(instructions, indices);
|
||||||
return indices.map(
|
return indices.map(
|
||||||
(index) =>
|
(index) =>
|
||||||
new SpacetimePoint(
|
new SpacetimePoint(
|
||||||
|
@ -156,7 +161,7 @@ export class GraphhopperGeorouter implements IGeorouter {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
_getIndices = (
|
private getIndices = (
|
||||||
points: Array<Array<number>>,
|
points: Array<Array<number>>,
|
||||||
snappedWaypoints: Array<Array<number>>,
|
snappedWaypoints: Array<Array<number>>,
|
||||||
): Array<number> => {
|
): Array<number> => {
|
||||||
|
@ -188,7 +193,7 @@ export class GraphhopperGeorouter implements IGeorouter {
|
||||||
.filter((element) => element.index == -1);
|
.filter((element) => element.index == -1);
|
||||||
for (const index in points) {
|
for (const index in points) {
|
||||||
for (const missedWaypoint of missedWaypoints) {
|
for (const missedWaypoint of missedWaypoints) {
|
||||||
const inverse = this._geodesic.inverse(
|
const inverse = this.geodesic.inverse(
|
||||||
missedWaypoint.waypoint[0],
|
missedWaypoint.waypoint[0],
|
||||||
missedWaypoint.waypoint[1],
|
missedWaypoint.waypoint[1],
|
||||||
points[index][0],
|
points[index][0],
|
||||||
|
@ -206,7 +211,7 @@ export class GraphhopperGeorouter implements IGeorouter {
|
||||||
return indices;
|
return indices;
|
||||||
};
|
};
|
||||||
|
|
||||||
_getTimes = (
|
private getTimes = (
|
||||||
durations: Array<Array<number>>,
|
durations: Array<Array<number>>,
|
||||||
indices: Array<number>,
|
indices: Array<number>,
|
||||||
): Array<{ index: number; duration: number }> => {
|
): Array<{ index: number; duration: number }> => {
|
||||||
|
@ -256,7 +261,7 @@ export class GraphhopperGeorouter implements IGeorouter {
|
||||||
return times;
|
return times;
|
||||||
};
|
};
|
||||||
|
|
||||||
_getDistances = (
|
private getDistances = (
|
||||||
instructions: Array<GraphhopperInstruction>,
|
instructions: Array<GraphhopperInstruction>,
|
||||||
indices: Array<number>,
|
indices: Array<number>,
|
||||||
): Array<{ index: number; distance: number }> => {
|
): Array<{ index: number; distance: number }> => {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { AutoMap } from '@automapper/classes';
|
||||||
import { Point } from '../types/point.type';
|
import { Point } from '../types/point.type';
|
||||||
import { Schedule } from '../types/schedule.type';
|
import { Schedule } from '../types/schedule.type';
|
||||||
import { MarginDurations } from '../types/margin-durations.type';
|
import { MarginDurations } from '../types/margin-durations.type';
|
||||||
import { Algorithm } from '../types/algorithm.enum';
|
import { AlgorithmType } from '../types/algorithm.enum';
|
||||||
import { IRequestTime } from '../interfaces/time-request.interface';
|
import { IRequestTime } from '../interfaces/time-request.interface';
|
||||||
import { IRequestPerson } from '../interfaces/person-request.interface';
|
import { IRequestPerson } from '../interfaces/person-request.interface';
|
||||||
import { IRequestGeography } from '../interfaces/geography-request.interface';
|
import { IRequestGeography } from '../interfaces/geography-request.interface';
|
||||||
|
@ -89,9 +89,9 @@ export class MatchRequest
|
||||||
strict: boolean;
|
strict: boolean;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsEnum(Algorithm)
|
@IsEnum(AlgorithmType)
|
||||||
@AutoMap()
|
@AutoMap()
|
||||||
algorithm: Algorithm;
|
algorithm: AlgorithmType;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { IRequestAlgorithmSettings } from '../../interfaces/algorithm-settings-request.interface';
|
import { IRequestAlgorithmSettings } from '../../interfaces/algorithm-settings-request.interface';
|
||||||
import { DefaultAlgorithmSettings } from '../../types/default-algorithm-settings.type';
|
import { DefaultAlgorithmSettings } from '../../types/default-algorithm-settings.type';
|
||||||
import { Algorithm } from '../../types/algorithm.enum';
|
import { AlgorithmType } from '../../types/algorithm.enum';
|
||||||
import { TimingFrequency } from '../../types/timing';
|
import { TimingFrequency } from '../../types/timing';
|
||||||
import { ICreateGeorouter } from '../../interfaces/georouter-creator.interface';
|
import { ICreateGeorouter } from '../../interfaces/georouter-creator.interface';
|
||||||
import { IGeorouter } from '../../interfaces/georouter.interface';
|
import { IGeorouter } from '../../interfaces/georouter.interface';
|
||||||
|
|
||||||
export class AlgorithmSettings {
|
export class AlgorithmSettings {
|
||||||
_algorithmSettingsRequest: IRequestAlgorithmSettings;
|
private algorithmSettingsRequest: IRequestAlgorithmSettings;
|
||||||
_strict: boolean;
|
private strict: boolean;
|
||||||
algorithm: Algorithm;
|
algorithmType: AlgorithmType;
|
||||||
restrict: TimingFrequency;
|
restrict: TimingFrequency;
|
||||||
remoteness: number;
|
remoteness: number;
|
||||||
useProportion: boolean;
|
useProportion: boolean;
|
||||||
|
@ -25,10 +25,10 @@ export class AlgorithmSettings {
|
||||||
frequency: TimingFrequency,
|
frequency: TimingFrequency,
|
||||||
georouterCreator: ICreateGeorouter,
|
georouterCreator: ICreateGeorouter,
|
||||||
) {
|
) {
|
||||||
this._algorithmSettingsRequest = algorithmSettingsRequest;
|
this.algorithmSettingsRequest = algorithmSettingsRequest;
|
||||||
this.algorithm =
|
this.algorithmType =
|
||||||
algorithmSettingsRequest.algorithm ?? defaultAlgorithmSettings.algorithm;
|
algorithmSettingsRequest.algorithm ?? defaultAlgorithmSettings.algorithm;
|
||||||
this._strict =
|
this.strict =
|
||||||
algorithmSettingsRequest.strict ?? defaultAlgorithmSettings.strict;
|
algorithmSettingsRequest.strict ?? defaultAlgorithmSettings.strict;
|
||||||
this.remoteness = algorithmSettingsRequest.remoteness
|
this.remoteness = algorithmSettingsRequest.remoteness
|
||||||
? Math.abs(algorithmSettingsRequest.remoteness)
|
? Math.abs(algorithmSettingsRequest.remoteness)
|
||||||
|
@ -55,7 +55,7 @@ export class AlgorithmSettings {
|
||||||
defaultAlgorithmSettings.georouterType,
|
defaultAlgorithmSettings.georouterType,
|
||||||
defaultAlgorithmSettings.georouterUrl,
|
defaultAlgorithmSettings.georouterUrl,
|
||||||
);
|
);
|
||||||
if (this._strict) {
|
if (this.strict) {
|
||||||
this.restrict = frequency;
|
this.restrict = frequency;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
import { MatcherException } from '../../../exceptions/matcher.exception';
|
import {
|
||||||
|
MatcherException,
|
||||||
|
MatcherExceptionCode,
|
||||||
|
} from '../../../exceptions/matcher.exception';
|
||||||
import { IRequestGeography } from '../../interfaces/geography-request.interface';
|
import { IRequestGeography } from '../../interfaces/geography-request.interface';
|
||||||
import { PointType } from '../../types/geography.enum';
|
import { PointType } from '../../types/geography.enum';
|
||||||
import { Point } from '../../types/point.type';
|
import { Point } from '../../types/point.type';
|
||||||
|
@ -13,9 +16,9 @@ import { Step } from '../../types/step.enum';
|
||||||
import { Path } from '../../types/path.type';
|
import { Path } from '../../types/path.type';
|
||||||
|
|
||||||
export class Geography {
|
export class Geography {
|
||||||
_geographyRequest: IRequestGeography;
|
private geographyRequest: IRequestGeography;
|
||||||
_person: Person;
|
private person: Person;
|
||||||
_points: Array<Point>;
|
private points: Array<Point>;
|
||||||
originType: PointType;
|
originType: PointType;
|
||||||
destinationType: PointType;
|
destinationType: PointType;
|
||||||
timezones: Array<string>;
|
timezones: Array<string>;
|
||||||
|
@ -27,18 +30,18 @@ export class Geography {
|
||||||
defaultTimezone: string,
|
defaultTimezone: string,
|
||||||
person: Person,
|
person: Person,
|
||||||
) {
|
) {
|
||||||
this._geographyRequest = geographyRequest;
|
this.geographyRequest = geographyRequest;
|
||||||
this._person = person;
|
this.person = person;
|
||||||
this._points = [];
|
this.points = [];
|
||||||
this.originType = undefined;
|
this.originType = undefined;
|
||||||
this.destinationType = undefined;
|
this.destinationType = undefined;
|
||||||
this.timezones = [defaultTimezone];
|
this.timezones = [defaultTimezone];
|
||||||
}
|
}
|
||||||
|
|
||||||
init = (): void => {
|
init = (): void => {
|
||||||
this._validateWaypoints();
|
this.validateWaypoints();
|
||||||
this._setTimezones();
|
this.setTimezones();
|
||||||
this._setPointTypes();
|
this.setPointTypes();
|
||||||
};
|
};
|
||||||
|
|
||||||
createRoutes = async (
|
createRoutes = async (
|
||||||
|
@ -49,14 +52,14 @@ export class Geography {
|
||||||
let passengerWaypoints: Array<Waypoint> = [];
|
let passengerWaypoints: Array<Waypoint> = [];
|
||||||
const paths: Array<Path> = [];
|
const paths: Array<Path> = [];
|
||||||
if (roles.includes(Role.DRIVER) && roles.includes(Role.PASSENGER)) {
|
if (roles.includes(Role.DRIVER) && roles.includes(Role.PASSENGER)) {
|
||||||
if (this._points.length == 2) {
|
if (this.points.length == 2) {
|
||||||
// 2 points => same route for driver and passenger
|
// 2 points => same route for driver and passenger
|
||||||
const commonPath: Path = {
|
const commonPath: Path = {
|
||||||
key: RouteKey.COMMON,
|
key: RouteKey.COMMON,
|
||||||
points: this._points,
|
points: this.points,
|
||||||
};
|
};
|
||||||
driverWaypoints = this._createWaypoints(commonPath.points, Role.DRIVER);
|
driverWaypoints = this.createWaypoints(commonPath.points, Role.DRIVER);
|
||||||
passengerWaypoints = this._createWaypoints(
|
passengerWaypoints = this.createWaypoints(
|
||||||
commonPath.points,
|
commonPath.points,
|
||||||
Role.PASSENGER,
|
Role.PASSENGER,
|
||||||
);
|
);
|
||||||
|
@ -64,14 +67,14 @@ export class Geography {
|
||||||
} else {
|
} else {
|
||||||
const driverPath: Path = {
|
const driverPath: Path = {
|
||||||
key: RouteKey.DRIVER,
|
key: RouteKey.DRIVER,
|
||||||
points: this._points,
|
points: this.points,
|
||||||
};
|
};
|
||||||
driverWaypoints = this._createWaypoints(driverPath.points, Role.DRIVER);
|
driverWaypoints = this.createWaypoints(driverPath.points, Role.DRIVER);
|
||||||
const passengerPath: Path = {
|
const passengerPath: Path = {
|
||||||
key: RouteKey.PASSENGER,
|
key: RouteKey.PASSENGER,
|
||||||
points: [this._points[0], this._points[this._points.length - 1]],
|
points: [this.points[0], this.points[this.points.length - 1]],
|
||||||
};
|
};
|
||||||
passengerWaypoints = this._createWaypoints(
|
passengerWaypoints = this.createWaypoints(
|
||||||
passengerPath.points,
|
passengerPath.points,
|
||||||
Role.PASSENGER,
|
Role.PASSENGER,
|
||||||
);
|
);
|
||||||
|
@ -80,16 +83,16 @@ export class Geography {
|
||||||
} else if (roles.includes(Role.DRIVER)) {
|
} else if (roles.includes(Role.DRIVER)) {
|
||||||
const driverPath: Path = {
|
const driverPath: Path = {
|
||||||
key: RouteKey.DRIVER,
|
key: RouteKey.DRIVER,
|
||||||
points: this._points,
|
points: this.points,
|
||||||
};
|
};
|
||||||
driverWaypoints = this._createWaypoints(driverPath.points, Role.DRIVER);
|
driverWaypoints = this.createWaypoints(driverPath.points, Role.DRIVER);
|
||||||
paths.push(driverPath);
|
paths.push(driverPath);
|
||||||
} else if (roles.includes(Role.PASSENGER)) {
|
} else if (roles.includes(Role.PASSENGER)) {
|
||||||
const passengerPath: Path = {
|
const passengerPath: Path = {
|
||||||
key: RouteKey.PASSENGER,
|
key: RouteKey.PASSENGER,
|
||||||
points: [this._points[0], this._points[this._points.length - 1]],
|
points: [this.points[0], this.points[this.points.length - 1]],
|
||||||
};
|
};
|
||||||
passengerWaypoints = this._createWaypoints(
|
passengerWaypoints = this.createWaypoints(
|
||||||
passengerPath.points,
|
passengerPath.points,
|
||||||
Role.PASSENGER,
|
Role.PASSENGER,
|
||||||
);
|
);
|
||||||
|
@ -125,55 +128,61 @@ export class Geography {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_validateWaypoints = (): void => {
|
private validateWaypoints = (): void => {
|
||||||
if (this._geographyRequest.waypoints.length < 2) {
|
if (this.geographyRequest.waypoints.length < 2) {
|
||||||
throw new MatcherException(3, 'At least 2 waypoints are required');
|
|
||||||
}
|
|
||||||
this._geographyRequest.waypoints.map((point) => {
|
|
||||||
if (!this._isValidPoint(point)) {
|
|
||||||
throw new MatcherException(
|
throw new MatcherException(
|
||||||
3,
|
MatcherExceptionCode.INVALID_ARGUMENT,
|
||||||
|
'At least 2 waypoints are required',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.geographyRequest.waypoints.map((point) => {
|
||||||
|
if (!this.isValidPoint(point)) {
|
||||||
|
throw new MatcherException(
|
||||||
|
MatcherExceptionCode.INVALID_ARGUMENT,
|
||||||
`Waypoint { Lon: ${point.lon}, Lat: ${point.lat} } is not valid`,
|
`Waypoint { Lon: ${point.lon}, Lat: ${point.lat} } is not valid`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this._points.push(point);
|
this.points.push(point);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_setTimezones = (): void => {
|
private setTimezones = (): void => {
|
||||||
this.timezones = find(
|
this.timezones = find(
|
||||||
this._geographyRequest.waypoints[0].lat,
|
this.geographyRequest.waypoints[0].lat,
|
||||||
this._geographyRequest.waypoints[0].lon,
|
this.geographyRequest.waypoints[0].lon,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
_setPointTypes = (): void => {
|
private setPointTypes = (): void => {
|
||||||
this.originType =
|
this.originType =
|
||||||
this._geographyRequest.waypoints[0].type ?? PointType.OTHER;
|
this.geographyRequest.waypoints[0].type ?? PointType.OTHER;
|
||||||
this.destinationType =
|
this.destinationType =
|
||||||
this._geographyRequest.waypoints[
|
this.geographyRequest.waypoints[
|
||||||
this._geographyRequest.waypoints.length - 1
|
this.geographyRequest.waypoints.length - 1
|
||||||
].type ?? PointType.OTHER;
|
].type ?? PointType.OTHER;
|
||||||
};
|
};
|
||||||
|
|
||||||
_isValidPoint = (point: Point): boolean =>
|
private isValidPoint = (point: Point): boolean =>
|
||||||
this._isValidLongitude(point.lon) && this._isValidLatitude(point.lat);
|
this.isValidLongitude(point.lon) && this.isValidLatitude(point.lat);
|
||||||
|
|
||||||
_isValidLongitude = (longitude: number): boolean =>
|
private isValidLongitude = (longitude: number): boolean =>
|
||||||
longitude >= -180 && longitude <= 180;
|
longitude >= -180 && longitude <= 180;
|
||||||
|
|
||||||
_isValidLatitude = (latitude: number): boolean =>
|
private isValidLatitude = (latitude: number): boolean =>
|
||||||
latitude >= -90 && latitude <= 90;
|
latitude >= -90 && latitude <= 90;
|
||||||
|
|
||||||
_createWaypoints = (points: Array<Point>, role: Role): Array<Waypoint> => {
|
private createWaypoints = (
|
||||||
|
points: Array<Point>,
|
||||||
|
role: Role,
|
||||||
|
): Array<Waypoint> => {
|
||||||
return points.map((point, index) => {
|
return points.map((point, index) => {
|
||||||
const waypoint = new Waypoint(point);
|
const waypoint = new Waypoint(point);
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
waypoint.addActor(new Actor(this._person, role, Step.START));
|
waypoint.addActor(new Actor(this.person, role, Step.START));
|
||||||
} else if (index == points.length - 1) {
|
} else if (index == points.length - 1) {
|
||||||
waypoint.addActor(new Actor(this._person, role, Step.FINISH));
|
waypoint.addActor(new Actor(this.person, role, Step.FINISH));
|
||||||
} else {
|
} else {
|
||||||
waypoint.addActor(new Actor(this._person, role, Step.INTERMEDIATE));
|
waypoint.addActor(new Actor(this.person, role, Step.INTERMEDIATE));
|
||||||
}
|
}
|
||||||
return waypoint;
|
return waypoint;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { IRequestPerson } from '../../interfaces/person-request.interface';
|
import { IRequestPerson } from '../../interfaces/person-request.interface';
|
||||||
|
|
||||||
export class Person {
|
export class Person {
|
||||||
_personRequest: IRequestPerson;
|
private personRequest: IRequestPerson;
|
||||||
_defaultIdentifier: number;
|
private defaultIdentifier: number;
|
||||||
_defaultMarginDuration: number;
|
private defaultMarginDuration: number;
|
||||||
identifier: number;
|
identifier: number;
|
||||||
marginDurations: Array<number>;
|
marginDurations: Array<number>;
|
||||||
|
|
||||||
|
@ -12,23 +12,21 @@ export class Person {
|
||||||
defaultIdentifier: number,
|
defaultIdentifier: number,
|
||||||
defaultMarginDuration: number,
|
defaultMarginDuration: number,
|
||||||
) {
|
) {
|
||||||
this._personRequest = personRequest;
|
this.personRequest = personRequest;
|
||||||
this._defaultIdentifier = defaultIdentifier;
|
this.defaultIdentifier = defaultIdentifier;
|
||||||
this._defaultMarginDuration = defaultMarginDuration;
|
this.defaultMarginDuration = defaultMarginDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
init = (): void => {
|
init = (): void => {
|
||||||
this.setIdentifier(
|
this.setIdentifier(this.personRequest.identifier ?? this.defaultIdentifier);
|
||||||
this._personRequest.identifier ?? this._defaultIdentifier,
|
|
||||||
);
|
|
||||||
this.setMarginDurations([
|
this.setMarginDurations([
|
||||||
this._defaultMarginDuration,
|
this.defaultMarginDuration,
|
||||||
this._defaultMarginDuration,
|
this.defaultMarginDuration,
|
||||||
this._defaultMarginDuration,
|
this.defaultMarginDuration,
|
||||||
this._defaultMarginDuration,
|
this.defaultMarginDuration,
|
||||||
this._defaultMarginDuration,
|
this.defaultMarginDuration,
|
||||||
this._defaultMarginDuration,
|
this.defaultMarginDuration,
|
||||||
this._defaultMarginDuration,
|
this.defaultMarginDuration,
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { IRequestRequirement } from '../../interfaces/requirement-request.interface';
|
import { IRequestRequirement } from '../../interfaces/requirement-request.interface';
|
||||||
|
|
||||||
export class Requirement {
|
export class Requirement {
|
||||||
_requirementRequest: IRequestRequirement;
|
private requirementRequest: IRequestRequirement;
|
||||||
seatsDriver: number;
|
seatsDriver: number;
|
||||||
seatsPassenger: number;
|
seatsPassenger: number;
|
||||||
|
|
||||||
constructor(requirementRequest: IRequestRequirement, defaultSeats: number) {
|
constructor(requirementRequest: IRequestRequirement, defaultSeats: number) {
|
||||||
this._requirementRequest = requirementRequest;
|
this.requirementRequest = requirementRequest;
|
||||||
this.seatsDriver = requirementRequest.seatsDriver ?? defaultSeats;
|
this.seatsDriver = requirementRequest.seatsDriver ?? defaultSeats;
|
||||||
this.seatsPassenger = requirementRequest.seatsPassenger ?? 1;
|
this.seatsPassenger = requirementRequest.seatsPassenger ?? 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ export class Route {
|
||||||
waypoints: Array<Waypoint>;
|
waypoints: Array<Waypoint>;
|
||||||
points: Array<Point>;
|
points: Array<Point>;
|
||||||
spacetimePoints: Array<SpacetimePoint>;
|
spacetimePoints: Array<SpacetimePoint>;
|
||||||
_geodesic: IGeodesic;
|
private geodesic: IGeodesic;
|
||||||
|
|
||||||
constructor(geodesic: IGeodesic) {
|
constructor(geodesic: IGeodesic) {
|
||||||
this.distance = undefined;
|
this.distance = undefined;
|
||||||
|
@ -23,25 +23,25 @@ export class Route {
|
||||||
this.waypoints = [];
|
this.waypoints = [];
|
||||||
this.points = [];
|
this.points = [];
|
||||||
this.spacetimePoints = [];
|
this.spacetimePoints = [];
|
||||||
this._geodesic = geodesic;
|
this.geodesic = geodesic;
|
||||||
}
|
}
|
||||||
|
|
||||||
setWaypoints = (waypoints: Array<Waypoint>): void => {
|
setWaypoints = (waypoints: Array<Waypoint>): void => {
|
||||||
this.waypoints = waypoints;
|
this.waypoints = waypoints;
|
||||||
this._setAzimuth(waypoints.map((waypoint) => waypoint.point));
|
this.setAzimuth(waypoints.map((waypoint) => waypoint.point));
|
||||||
};
|
};
|
||||||
|
|
||||||
setPoints = (points: Array<Point>): void => {
|
setPoints = (points: Array<Point>): void => {
|
||||||
this.points = points;
|
this.points = points;
|
||||||
this._setAzimuth(points);
|
this.setAzimuth(points);
|
||||||
};
|
};
|
||||||
|
|
||||||
setSpacetimePoints = (spacetimePoints: Array<SpacetimePoint>): void => {
|
setSpacetimePoints = (spacetimePoints: Array<SpacetimePoint>): void => {
|
||||||
this.spacetimePoints = spacetimePoints;
|
this.spacetimePoints = spacetimePoints;
|
||||||
};
|
};
|
||||||
|
|
||||||
_setAzimuth = (points: Array<Point>): void => {
|
private setAzimuth = (points: Array<Point>): void => {
|
||||||
const inverse = this._geodesic.inverse(
|
const inverse = this.geodesic.inverse(
|
||||||
points[0].lon,
|
points[0].lon,
|
||||||
points[0].lat,
|
points[0].lat,
|
||||||
points[points.length - 1].lon,
|
points[points.length - 1].lon,
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import { MatcherException } from '../../../exceptions/matcher.exception';
|
import {
|
||||||
|
MatcherException,
|
||||||
|
MatcherExceptionCode,
|
||||||
|
} from '../../../exceptions/matcher.exception';
|
||||||
import { MarginDurations } from '../../types/margin-durations.type';
|
import { MarginDurations } from '../../types/margin-durations.type';
|
||||||
import { IRequestTime } from '../../interfaces/time-request.interface';
|
import { IRequestTime } from '../../interfaces/time-request.interface';
|
||||||
import { TimingDays, TimingFrequency, Days } from '../../types/timing';
|
import { TimingDays, TimingFrequency, Days } from '../../types/timing';
|
||||||
import { Schedule } from '../../types/schedule.type';
|
import { Schedule } from '../../types/schedule.type';
|
||||||
|
|
||||||
export class Time {
|
export class Time {
|
||||||
_timeRequest: IRequestTime;
|
private timeRequest: IRequestTime;
|
||||||
_defaultMarginDuration: number;
|
private defaultMarginDuration: number;
|
||||||
_defaultValidityDuration: number;
|
private defaultValidityDuration: number;
|
||||||
frequency: TimingFrequency;
|
frequency: TimingFrequency;
|
||||||
fromDate: Date;
|
fromDate: Date;
|
||||||
toDate: Date;
|
toDate: Date;
|
||||||
|
@ -19,9 +22,9 @@ export class Time {
|
||||||
defaultMarginDuration: number,
|
defaultMarginDuration: number,
|
||||||
defaultValidityDuration: number,
|
defaultValidityDuration: number,
|
||||||
) {
|
) {
|
||||||
this._timeRequest = timeRequest;
|
this.timeRequest = timeRequest;
|
||||||
this._defaultMarginDuration = defaultMarginDuration;
|
this.defaultMarginDuration = defaultMarginDuration;
|
||||||
this._defaultValidityDuration = defaultValidityDuration;
|
this.defaultValidityDuration = defaultValidityDuration;
|
||||||
this.schedule = {};
|
this.schedule = {};
|
||||||
this.marginDurations = {
|
this.marginDurations = {
|
||||||
mon: defaultMarginDuration,
|
mon: defaultMarginDuration,
|
||||||
|
@ -35,99 +38,120 @@ export class Time {
|
||||||
}
|
}
|
||||||
|
|
||||||
init = (): void => {
|
init = (): void => {
|
||||||
this._validateBaseDate();
|
this.validateBaseDate();
|
||||||
this._validatePunctualRequest();
|
this.validatePunctualRequest();
|
||||||
this._validateRecurrentRequest();
|
this.validateRecurrentRequest();
|
||||||
this._setPunctualRequest();
|
this.setPunctualRequest();
|
||||||
this._setRecurrentRequest();
|
this.setRecurrentRequest();
|
||||||
this._setMargindurations();
|
this.setMargindurations();
|
||||||
};
|
};
|
||||||
|
|
||||||
_validateBaseDate = (): void => {
|
private validateBaseDate = (): void => {
|
||||||
if (!this._timeRequest.departure && !this._timeRequest.fromDate) {
|
if (!this.timeRequest.departure && !this.timeRequest.fromDate) {
|
||||||
throw new MatcherException(3, 'departure or fromDate is required');
|
throw new MatcherException(
|
||||||
|
MatcherExceptionCode.INVALID_ARGUMENT,
|
||||||
|
'departure or fromDate is required',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_validatePunctualRequest = (): void => {
|
private validatePunctualRequest = (): void => {
|
||||||
if (this._timeRequest.departure) {
|
if (this.timeRequest.departure) {
|
||||||
this.fromDate = this.toDate = new Date(this._timeRequest.departure);
|
this.fromDate = this.toDate = new Date(this.timeRequest.departure);
|
||||||
if (!this._isDate(this.fromDate)) {
|
if (!this.isDate(this.fromDate)) {
|
||||||
throw new MatcherException(3, 'Wrong departure date');
|
throw new MatcherException(
|
||||||
|
MatcherExceptionCode.INVALID_ARGUMENT,
|
||||||
|
'Wrong departure date',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_validateRecurrentRequest = (): void => {
|
private validateRecurrentRequest = (): void => {
|
||||||
if (this._timeRequest.fromDate) {
|
if (this.timeRequest.fromDate) {
|
||||||
this.fromDate = new Date(this._timeRequest.fromDate);
|
this.fromDate = new Date(this.timeRequest.fromDate);
|
||||||
if (!this._isDate(this.fromDate)) {
|
if (!this.isDate(this.fromDate)) {
|
||||||
throw new MatcherException(3, 'Wrong fromDate');
|
throw new MatcherException(
|
||||||
|
MatcherExceptionCode.INVALID_ARGUMENT,
|
||||||
|
'Wrong fromDate',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this._timeRequest.toDate) {
|
if (this.timeRequest.toDate) {
|
||||||
this.toDate = new Date(this._timeRequest.toDate);
|
this.toDate = new Date(this.timeRequest.toDate);
|
||||||
if (!this._isDate(this.toDate)) {
|
if (!this.isDate(this.toDate)) {
|
||||||
throw new MatcherException(3, 'Wrong toDate');
|
throw new MatcherException(
|
||||||
|
MatcherExceptionCode.INVALID_ARGUMENT,
|
||||||
|
'Wrong toDate',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (this.toDate < this.fromDate) {
|
if (this.toDate < this.fromDate) {
|
||||||
throw new MatcherException(3, 'toDate must be after fromDate');
|
throw new MatcherException(
|
||||||
|
MatcherExceptionCode.INVALID_ARGUMENT,
|
||||||
|
'toDate must be after fromDate',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this._timeRequest.fromDate) {
|
if (this.timeRequest.fromDate) {
|
||||||
this._validateSchedule();
|
this.validateSchedule();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_validateSchedule = (): void => {
|
private validateSchedule = (): void => {
|
||||||
if (!this._timeRequest.schedule) {
|
if (!this.timeRequest.schedule) {
|
||||||
throw new MatcherException(3, 'Schedule is required');
|
throw new MatcherException(
|
||||||
|
MatcherExceptionCode.INVALID_ARGUMENT,
|
||||||
|
'Schedule is required',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!Object.keys(this._timeRequest.schedule).some((elem) =>
|
!Object.keys(this.timeRequest.schedule).some((elem) =>
|
||||||
Days.includes(elem),
|
Days.includes(elem),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new MatcherException(3, 'No valid day in the given schedule');
|
throw new MatcherException(
|
||||||
|
MatcherExceptionCode.INVALID_ARGUMENT,
|
||||||
|
'No valid day in the given schedule',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Object.keys(this._timeRequest.schedule).map((day) => {
|
Object.keys(this.timeRequest.schedule).map((day) => {
|
||||||
const time = new Date('1970-01-01 ' + this._timeRequest.schedule[day]);
|
const time = new Date('1970-01-01 ' + this.timeRequest.schedule[day]);
|
||||||
if (!this._isDate(time)) {
|
if (!this.isDate(time)) {
|
||||||
throw new MatcherException(3, `Wrong time for ${day} in schedule`);
|
throw new MatcherException(
|
||||||
|
MatcherExceptionCode.INVALID_ARGUMENT,
|
||||||
|
`Wrong time for ${day} in schedule`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_setPunctualRequest = (): void => {
|
private setPunctualRequest = (): void => {
|
||||||
if (this._timeRequest.departure) {
|
if (this.timeRequest.departure) {
|
||||||
this.frequency = TimingFrequency.FREQUENCY_PUNCTUAL;
|
this.frequency = TimingFrequency.FREQUENCY_PUNCTUAL;
|
||||||
this.schedule[TimingDays[this.fromDate.getDay()]] =
|
this.schedule[TimingDays[this.fromDate.getDay()]] =
|
||||||
this.fromDate.getHours() + ':' + this.fromDate.getMinutes();
|
this.fromDate.getHours() + ':' + this.fromDate.getMinutes();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_setRecurrentRequest = (): void => {
|
private setRecurrentRequest = (): void => {
|
||||||
if (this._timeRequest.fromDate) {
|
if (this.timeRequest.fromDate) {
|
||||||
this.frequency = TimingFrequency.FREQUENCY_RECURRENT;
|
this.frequency = TimingFrequency.FREQUENCY_RECURRENT;
|
||||||
if (!this.toDate) {
|
if (!this.toDate) {
|
||||||
this.toDate = this._addDays(
|
this.toDate = this.addDays(this.fromDate, this.defaultValidityDuration);
|
||||||
this.fromDate,
|
|
||||||
this._defaultValidityDuration,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
this._setSchedule();
|
this.setSchedule();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_setSchedule = (): void => {
|
private setSchedule = (): void => {
|
||||||
Object.keys(this._timeRequest.schedule).map((day) => {
|
Object.keys(this.timeRequest.schedule).map((day) => {
|
||||||
this.schedule[day] = this._timeRequest.schedule[day];
|
this.schedule[day] = this.timeRequest.schedule[day];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_setMargindurations = (): void => {
|
private setMargindurations = (): void => {
|
||||||
if (this._timeRequest.marginDuration) {
|
if (this.timeRequest.marginDuration) {
|
||||||
const duration = Math.abs(this._timeRequest.marginDuration);
|
const duration = Math.abs(this.timeRequest.marginDuration);
|
||||||
this.marginDurations = {
|
this.marginDurations = {
|
||||||
mon: duration,
|
mon: duration,
|
||||||
tue: duration,
|
tue: duration,
|
||||||
|
@ -138,30 +162,30 @@ export class Time {
|
||||||
sun: duration,
|
sun: duration,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (this._timeRequest.marginDurations) {
|
if (this.timeRequest.marginDurations) {
|
||||||
if (
|
if (
|
||||||
!Object.keys(this._timeRequest.marginDurations).some((elem) =>
|
!Object.keys(this.timeRequest.marginDurations).some((elem) =>
|
||||||
Days.includes(elem),
|
Days.includes(elem),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new MatcherException(
|
throw new MatcherException(
|
||||||
3,
|
MatcherExceptionCode.INVALID_ARGUMENT,
|
||||||
'No valid day in the given margin durations',
|
'No valid day in the given margin durations',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Object.keys(this._timeRequest.marginDurations).map((day) => {
|
Object.keys(this.timeRequest.marginDurations).map((day) => {
|
||||||
this.marginDurations[day] = Math.abs(
|
this.marginDurations[day] = Math.abs(
|
||||||
this._timeRequest.marginDurations[day],
|
this.timeRequest.marginDurations[day],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_isDate = (date: Date): boolean => {
|
private isDate = (date: Date): boolean => {
|
||||||
return date instanceof Date && isFinite(+date);
|
return date instanceof Date && isFinite(+date);
|
||||||
};
|
};
|
||||||
|
|
||||||
_addDays = (date: Date, days: number): Date => {
|
private addDays = (date: Date, days: number): Date => {
|
||||||
const result = new Date(date);
|
const result = new Date(date);
|
||||||
result.setDate(result.getDate() + days);
|
result.setDate(result.getDate() + days);
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { MatchQuery } from 'src/modules/matcher/queries/match.query';
|
||||||
|
import { AlgorithmType } from '../../../types/algorithm.enum';
|
||||||
|
import { AlgorithmFactory } from './algorithm-factory.abstract';
|
||||||
|
import { ClassicAlgorithmFactory } from './classic';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AlgorithmFactoryCreator {
|
||||||
|
create = (matchQuery: MatchQuery): AlgorithmFactory => {
|
||||||
|
let algorithmFactory: AlgorithmFactory;
|
||||||
|
switch (matchQuery.algorithmSettings.algorithmType) {
|
||||||
|
case AlgorithmType.CLASSIC:
|
||||||
|
algorithmFactory = new ClassicAlgorithmFactory(matchQuery);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return algorithmFactory;
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,15 +1,17 @@
|
||||||
import { MatchQuery } from 'src/modules/matcher/queries/match.query';
|
import { MatchQuery } from 'src/modules/matcher/queries/match.query';
|
||||||
import { Processor } from '../processor.abstract';
|
import { Processor } from '../processor/processor.abstract';
|
||||||
import { Candidate } from '../candidate';
|
import { Candidate } from '../candidate';
|
||||||
|
import { Selector } from '../selector/selector.abstract';
|
||||||
|
|
||||||
export abstract class AlgorithmFactory {
|
export abstract class AlgorithmFactory {
|
||||||
_matchQuery: MatchQuery;
|
protected matchQuery: MatchQuery;
|
||||||
_candidates: Array<Candidate>;
|
private candidates: Array<Candidate>;
|
||||||
|
|
||||||
constructor(matchQuery: MatchQuery) {
|
constructor(matchQuery: MatchQuery) {
|
||||||
this._matchQuery = matchQuery;
|
this.matchQuery = matchQuery;
|
||||||
this._candidates = [];
|
this.candidates = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract createSelector(): Selector;
|
||||||
abstract createProcessors(): Array<Processor>;
|
abstract createProcessors(): Array<Processor>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,21 @@
|
||||||
import { AlgorithmFactory } from './algorithm-factory.abstract';
|
import { AlgorithmFactory } from './algorithm-factory.abstract';
|
||||||
import { Processor } from '../processor.abstract';
|
|
||||||
import { ClassicWaypointsCompleter } from '../processor/completer/classic-waypoint.completer.processor';
|
import { ClassicWaypointsCompleter } from '../processor/completer/classic-waypoint.completer.processor';
|
||||||
|
import { RouteCompleter } from '../processor/completer/route.completer.processor';
|
||||||
|
import { ClassicGeoFilter } from '../processor/filter/geofilter/classic.filter.processor';
|
||||||
|
import { JourneyCompleter } from '../processor/completer/journey.completer.processor';
|
||||||
|
import { ClassicTimeFilter } from '../processor/filter/timefilter/classic.filter.processor';
|
||||||
|
import { Processor } from '../processor/processor.abstract';
|
||||||
|
import { Selector } from '../selector/selector.abstract';
|
||||||
|
import { ClassicSelector } from '../selector/classic.selector';
|
||||||
|
|
||||||
export class ClassicAlgorithmFactory extends AlgorithmFactory {
|
export class ClassicAlgorithmFactory extends AlgorithmFactory {
|
||||||
createProcessors(): Array<Processor> {
|
createSelector = (): Selector => new ClassicSelector(this.matchQuery);
|
||||||
return [new ClassicWaypointsCompleter(this._matchQuery)];
|
createProcessors = (): Array<Processor> => [
|
||||||
}
|
new ClassicWaypointsCompleter(this.matchQuery),
|
||||||
|
new RouteCompleter(this.matchQuery, true, true, true),
|
||||||
|
new ClassicGeoFilter(this.matchQuery),
|
||||||
|
new RouteCompleter(this.matchQuery),
|
||||||
|
new JourneyCompleter(this.matchQuery),
|
||||||
|
new ClassicTimeFilter(this.matchQuery),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,27 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
import { MatchQuery } from '../../../queries/match.query';
|
import { MatchQuery } from '../../../queries/match.query';
|
||||||
import { Algorithm } from '../../types/algorithm.enum';
|
|
||||||
import { Match } from '../ecosystem/match';
|
import { Match } from '../ecosystem/match';
|
||||||
import { Candidate } from './candidate';
|
import { Candidate } from './candidate';
|
||||||
import { AlgorithmFactory } from './factory/algorithm-factory.abstract';
|
import { AlgorithmFactory } from './factory/algorithm-factory.abstract';
|
||||||
import { ClassicAlgorithmFactory } from './factory/classic';
|
import { AlgorithmFactoryCreator } from './factory/algorithm-factory-creator';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
export class Matcher {
|
export class Matcher {
|
||||||
match = (matchQuery: MatchQuery): Array<Match> => {
|
constructor(
|
||||||
let algorithm: AlgorithmFactory;
|
private readonly algorithmFactoryCreator: AlgorithmFactoryCreator,
|
||||||
switch (matchQuery.algorithmSettings.algorithm) {
|
) {}
|
||||||
case Algorithm.CLASSIC:
|
|
||||||
algorithm = new ClassicAlgorithmFactory(matchQuery);
|
match = async (matchQuery: MatchQuery): Promise<Array<Match>> => {
|
||||||
}
|
const algorithmFactory: AlgorithmFactory =
|
||||||
let candidates: Array<Candidate> = [];
|
this.algorithmFactoryCreator.create(matchQuery);
|
||||||
for (const processor of algorithm.createProcessors()) {
|
let candidates: Array<Candidate> = await algorithmFactory
|
||||||
|
.createSelector()
|
||||||
|
.select();
|
||||||
|
for (const processor of algorithmFactory.createProcessors()) {
|
||||||
candidates = processor.execute(candidates);
|
candidates = processor.execute(candidates);
|
||||||
}
|
}
|
||||||
return [];
|
const match = new Match();
|
||||||
|
match.uuid = 'e23f9725-2c19-49a0-9ef6-17d8b9a5ec85';
|
||||||
|
return [match];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Candidate } from '../../candidate';
|
||||||
import { Completer } from './completer.abstract';
|
import { Completer } from './completer.abstract';
|
||||||
|
|
||||||
export class ClassicWaypointsCompleter extends Completer {
|
export class ClassicWaypointsCompleter extends Completer {
|
||||||
complete(candidates: Array<Candidate>): Array<Candidate> {
|
complete = (candidates: Array<Candidate>): Array<Candidate> => {
|
||||||
return [];
|
return candidates;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Candidate } from '../../candidate';
|
import { Candidate } from '../../candidate';
|
||||||
import { Processor } from '../../processor.abstract';
|
import { Processor } from '../processor.abstract';
|
||||||
|
|
||||||
export abstract class Completer extends Processor {
|
export abstract class Completer extends Processor {
|
||||||
execute = (candidates: Array<Candidate>): Array<Candidate> =>
|
execute = (candidates: Array<Candidate>): Array<Candidate> =>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { Candidate } from '../../candidate';
|
||||||
|
import { Completer } from './completer.abstract';
|
||||||
|
|
||||||
|
export class JourneyCompleter extends Completer {
|
||||||
|
complete = (candidates: Array<Candidate>): Array<Candidate> => {
|
||||||
|
return candidates;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { MatchQuery } from 'src/modules/matcher/queries/match.query';
|
||||||
|
import { Candidate } from '../../candidate';
|
||||||
|
import { Completer } from './completer.abstract';
|
||||||
|
|
||||||
|
export class RouteCompleter extends Completer {
|
||||||
|
private withPoints: boolean;
|
||||||
|
private withTime: boolean;
|
||||||
|
private withDistance: boolean;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
matchQuery: MatchQuery,
|
||||||
|
withPoints = false,
|
||||||
|
withTime = false,
|
||||||
|
withDistance = false,
|
||||||
|
) {
|
||||||
|
super(matchQuery);
|
||||||
|
this.withPoints = withPoints;
|
||||||
|
this.withTime = withTime;
|
||||||
|
this.withDistance = withDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
complete = (candidates: Array<Candidate>): Array<Candidate> => {
|
||||||
|
return candidates;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Candidate } from '../../candidate';
|
||||||
|
import { Processor } from '../processor.abstract';
|
||||||
|
|
||||||
|
export abstract class Filter extends Processor {
|
||||||
|
execute = (candidates: Array<Candidate>): Array<Candidate> =>
|
||||||
|
this.filter(candidates);
|
||||||
|
|
||||||
|
abstract filter(candidates: Array<Candidate>): Array<Candidate>;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { Candidate } from '../../../candidate';
|
||||||
|
import { Filter } from '../filter.abstract';
|
||||||
|
|
||||||
|
export class ClassicGeoFilter extends Filter {
|
||||||
|
filter = (candidates: Array<Candidate>): Array<Candidate> => {
|
||||||
|
return candidates;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { Candidate } from '../../../candidate';
|
||||||
|
import { Filter } from '../filter.abstract';
|
||||||
|
|
||||||
|
export class ClassicTimeFilter extends Filter {
|
||||||
|
filter = (candidates: Array<Candidate>): Array<Candidate> => {
|
||||||
|
return candidates;
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
import { MatchQuery } from 'src/modules/matcher/queries/match.query';
|
import { MatchQuery } from 'src/modules/matcher/queries/match.query';
|
||||||
import { Candidate } from './candidate';
|
import { Candidate } from '../candidate';
|
||||||
|
|
||||||
export abstract class Processor {
|
export abstract class Processor {
|
||||||
_matchQuery: MatchQuery;
|
private matchQuery: MatchQuery;
|
||||||
|
|
||||||
constructor(matchQuery: MatchQuery) {
|
constructor(matchQuery: MatchQuery) {
|
||||||
this._matchQuery = matchQuery;
|
this.matchQuery = matchQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract execute(candidates: Array<Candidate>): Array<Candidate>;
|
abstract execute(candidates: Array<Candidate>): Array<Candidate>;
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { Candidate } from '../candidate';
|
||||||
|
import { Selector } from './selector.abstract';
|
||||||
|
|
||||||
|
export class ClassicSelector extends Selector {
|
||||||
|
select = async (): Promise<Array<Candidate>> => {
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { MatchQuery } from 'src/modules/matcher/queries/match.query';
|
||||||
|
import { Candidate } from '../candidate';
|
||||||
|
|
||||||
|
export abstract class Selector {
|
||||||
|
private matchQuery: MatchQuery;
|
||||||
|
|
||||||
|
constructor(matchQuery: MatchQuery) {
|
||||||
|
this.matchQuery = matchQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract select(): Promise<Array<Candidate>>;
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import { Algorithm } from '../types/algorithm.enum';
|
import { AlgorithmType } from '../types/algorithm.enum';
|
||||||
|
|
||||||
export interface IRequestAlgorithmSettings {
|
export interface IRequestAlgorithmSettings {
|
||||||
algorithm: Algorithm;
|
algorithm: AlgorithmType;
|
||||||
strict: boolean;
|
strict: boolean;
|
||||||
remoteness: number;
|
remoteness: number;
|
||||||
useProportion: boolean;
|
useProportion: boolean;
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export enum Algorithm {
|
export enum AlgorithmType {
|
||||||
CLASSIC = 'CLASSIC',
|
CLASSIC = 'CLASSIC',
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Algorithm } from './algorithm.enum';
|
import { AlgorithmType } from './algorithm.enum';
|
||||||
|
|
||||||
export type DefaultAlgorithmSettings = {
|
export type DefaultAlgorithmSettings = {
|
||||||
algorithm: Algorithm;
|
algorithm: AlgorithmType;
|
||||||
strict: boolean;
|
strict: boolean;
|
||||||
remoteness: number;
|
remoteness: number;
|
||||||
useProportion: boolean;
|
useProportion: boolean;
|
||||||
|
|
|
@ -3,20 +3,40 @@ import { InjectMapper } from '@automapper/nestjs';
|
||||||
import { QueryHandler } from '@nestjs/cqrs';
|
import { QueryHandler } from '@nestjs/cqrs';
|
||||||
import { Messager } from '../../adapters/secondaries/messager';
|
import { Messager } from '../../adapters/secondaries/messager';
|
||||||
import { MatchQuery } from '../../queries/match.query';
|
import { MatchQuery } from '../../queries/match.query';
|
||||||
import { AdRepository } from '../../adapters/secondaries/ad.repository';
|
|
||||||
import { Match } from '../entities/ecosystem/match';
|
import { Match } from '../entities/ecosystem/match';
|
||||||
import { ICollection } from '../../../database/src/interfaces/collection.interface';
|
import { ICollection } from '../../../database/src/interfaces/collection.interface';
|
||||||
|
import { Matcher } from '../entities/engine/matcher';
|
||||||
|
|
||||||
@QueryHandler(MatchQuery)
|
@QueryHandler(MatchQuery)
|
||||||
export class MatchUseCase {
|
export class MatchUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _repository: AdRepository,
|
private readonly _matcher: Matcher,
|
||||||
private readonly _messager: Messager,
|
private readonly _messager: Messager,
|
||||||
@InjectMapper() private readonly _mapper: Mapper,
|
@InjectMapper() private readonly _mapper: Mapper,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
execute = async (matchQuery: MatchQuery): Promise<ICollection<Match>> => {
|
execute = async (matchQuery: MatchQuery): Promise<ICollection<Match>> => {
|
||||||
try {
|
try {
|
||||||
|
const data: Array<Match> = await this._matcher.match(matchQuery);
|
||||||
|
this._messager.publish('matcher.match', 'match !');
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
total: data.length,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
const err: Error = error;
|
||||||
|
this._messager.publish(
|
||||||
|
'logging.matcher.match.crit',
|
||||||
|
JSON.stringify({
|
||||||
|
matchQuery,
|
||||||
|
error: err.message,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// const paths = [];
|
// const paths = [];
|
||||||
// for (let i = 0; i < 1; i++) {
|
// for (let i = 0; i < 1; i++) {
|
||||||
// paths.push({
|
// paths.push({
|
||||||
|
@ -55,23 +75,3 @@ export class MatchUseCase {
|
||||||
// withTime: true,
|
// withTime: true,
|
||||||
// });
|
// });
|
||||||
// routes.map((route) => console.log(route.route.spacetimePoints));
|
// routes.map((route) => console.log(route.route.spacetimePoints));
|
||||||
const match = new Match();
|
|
||||||
match.uuid = 'e23f9725-2c19-49a0-9ef6-17d8b9a5ec85';
|
|
||||||
this._messager.publish('matcher.match', 'match !');
|
|
||||||
return {
|
|
||||||
data: [match],
|
|
||||||
total: 1,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
const err: Error = error;
|
|
||||||
this._messager.publish(
|
|
||||||
'logging.matcher.match.crit',
|
|
||||||
JSON.stringify({
|
|
||||||
matchQuery,
|
|
||||||
error: err.message,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,3 +11,23 @@ export class MatcherException implements Error {
|
||||||
return this._code;
|
return this._code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum MatcherExceptionCode {
|
||||||
|
OK = 0,
|
||||||
|
CANCELLED = 1,
|
||||||
|
UNKNOWN = 2,
|
||||||
|
INVALID_ARGUMENT = 3,
|
||||||
|
DEADLINE_EXCEEDED = 4,
|
||||||
|
NOT_FOUND = 5,
|
||||||
|
ALREADY_EXISTS = 6,
|
||||||
|
PERMISSION_DENIED = 7,
|
||||||
|
RESOURCE_EXHAUSTED = 8,
|
||||||
|
FAILED_PRECONDITION = 9,
|
||||||
|
ABORTED = 10,
|
||||||
|
OUT_OF_RANGE = 11,
|
||||||
|
UNIMPLEMENTED = 12,
|
||||||
|
INTERNAL = 13,
|
||||||
|
UNAVAILABLE = 14,
|
||||||
|
DATA_LOSS = 15,
|
||||||
|
UNAUTHENTICATED = 16,
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ import { DefaultParamsProvider } from './adapters/secondaries/default-params.pro
|
||||||
import { GeorouterCreator } from './adapters/secondaries/georouter-creator';
|
import { GeorouterCreator } from './adapters/secondaries/georouter-creator';
|
||||||
import { HttpModule } from '@nestjs/axios';
|
import { HttpModule } from '@nestjs/axios';
|
||||||
import { MatcherGeodesic } from './adapters/secondaries/geodesic';
|
import { MatcherGeodesic } from './adapters/secondaries/geodesic';
|
||||||
|
import { Matcher } from './domain/entities/engine/matcher';
|
||||||
|
import { AlgorithmFactoryCreator } from './domain/entities/engine/factory/algorithm-factory-creator';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -57,6 +59,8 @@ import { MatcherGeodesic } from './adapters/secondaries/geodesic';
|
||||||
MatchUseCase,
|
MatchUseCase,
|
||||||
GeorouterCreator,
|
GeorouterCreator,
|
||||||
MatcherGeodesic,
|
MatcherGeodesic,
|
||||||
|
Matcher,
|
||||||
|
AlgorithmFactoryCreator,
|
||||||
],
|
],
|
||||||
exports: [],
|
exports: [],
|
||||||
})
|
})
|
||||||
|
|
|
@ -107,7 +107,6 @@ describe('Geography entity', () => {
|
||||||
person,
|
person,
|
||||||
);
|
);
|
||||||
geography.init();
|
geography.init();
|
||||||
expect(geography._points.length).toBe(2);
|
|
||||||
expect(geography.originType).toBe(PointType.LOCALITY);
|
expect(geography.originType).toBe(PointType.LOCALITY);
|
||||||
expect(geography.destinationType).toBe(PointType.LOCALITY);
|
expect(geography.destinationType).toBe(PointType.LOCALITY);
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { AlgorithmFactoryCreator } from '../../../../domain/entities/engine/factory/algorithm-factory-creator';
|
||||||
|
import { MatchRequest } from '../../../../domain/dtos/match.request';
|
||||||
|
import { AlgorithmType } from '../../../../domain/types/algorithm.enum';
|
||||||
|
import { IDefaultParams } from '../../../../domain/types/default-params.type';
|
||||||
|
import { MatchQuery } from '../../../../queries/match.query';
|
||||||
|
import { ClassicAlgorithmFactory } from '../../../../domain/entities/engine/factory/classic';
|
||||||
|
|
||||||
|
const mockGeorouterCreator = {
|
||||||
|
create: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultParams: IDefaultParams = {
|
||||||
|
DEFAULT_IDENTIFIER: 0,
|
||||||
|
MARGIN_DURATION: 900,
|
||||||
|
VALIDITY_DURATION: 365,
|
||||||
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
|
DEFAULT_SEATS: 3,
|
||||||
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
|
strict: false,
|
||||||
|
remoteness: 15000,
|
||||||
|
useProportion: true,
|
||||||
|
proportion: 0.3,
|
||||||
|
useAzimuth: true,
|
||||||
|
azimuthMargin: 10,
|
||||||
|
maxDetourDistanceRatio: 0.3,
|
||||||
|
maxDetourDurationRatio: 0.3,
|
||||||
|
georouterType: 'graphhopper',
|
||||||
|
georouterUrl: 'http://localhost',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchRequest: MatchRequest = new MatchRequest();
|
||||||
|
matchRequest.departure = '2023-04-01 12:00';
|
||||||
|
matchRequest.waypoints = [
|
||||||
|
{
|
||||||
|
lat: 49.440041,
|
||||||
|
lon: 1.093912,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 50.630992,
|
||||||
|
lon: 3.045432,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const matchQuery: MatchQuery = new MatchQuery(
|
||||||
|
matchRequest,
|
||||||
|
defaultParams,
|
||||||
|
mockGeorouterCreator,
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('AlgorithmFactoryCreator', () => {
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(new AlgorithmFactoryCreator()).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a classic algorithm factory', () => {
|
||||||
|
expect(new AlgorithmFactoryCreator().create(matchQuery)).toBeInstanceOf(
|
||||||
|
ClassicAlgorithmFactory,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { MatchRequest } from '../../../../domain/dtos/match.request';
|
||||||
|
import { Candidate } from '../../../../domain/entities/engine/candidate';
|
||||||
|
import { AlgorithmFactory } from '../../../../domain/entities/engine/factory/algorithm-factory.abstract';
|
||||||
|
import { Processor } from '../../../../domain/entities/engine/processor/processor.abstract';
|
||||||
|
import { Selector } from '../../../../domain/entities/engine/selector/selector.abstract';
|
||||||
|
import { AlgorithmType } from '../../../../domain/types/algorithm.enum';
|
||||||
|
import { IDefaultParams } from '../../../../domain/types/default-params.type';
|
||||||
|
import { MatchQuery } from '../../../../queries/match.query';
|
||||||
|
|
||||||
|
const mockGeorouterCreator = {
|
||||||
|
create: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultParams: IDefaultParams = {
|
||||||
|
DEFAULT_IDENTIFIER: 0,
|
||||||
|
MARGIN_DURATION: 900,
|
||||||
|
VALIDITY_DURATION: 365,
|
||||||
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
|
DEFAULT_SEATS: 3,
|
||||||
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
|
strict: false,
|
||||||
|
remoteness: 15000,
|
||||||
|
useProportion: true,
|
||||||
|
proportion: 0.3,
|
||||||
|
useAzimuth: true,
|
||||||
|
azimuthMargin: 10,
|
||||||
|
maxDetourDistanceRatio: 0.3,
|
||||||
|
maxDetourDurationRatio: 0.3,
|
||||||
|
georouterType: 'graphhopper',
|
||||||
|
georouterUrl: 'http://localhost',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchRequest: MatchRequest = new MatchRequest();
|
||||||
|
matchRequest.departure = '2023-04-01 12:00';
|
||||||
|
matchRequest.waypoints = [
|
||||||
|
{
|
||||||
|
lat: 49.440041,
|
||||||
|
lon: 1.093912,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 50.630992,
|
||||||
|
lon: 3.045432,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const matchQuery: MatchQuery = new MatchQuery(
|
||||||
|
matchRequest,
|
||||||
|
defaultParams,
|
||||||
|
mockGeorouterCreator,
|
||||||
|
);
|
||||||
|
|
||||||
|
class FakeSelector extends Selector {
|
||||||
|
select = (): Promise<Candidate[]> => {
|
||||||
|
return Promise.resolve([new Candidate()]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeProcessor extends Processor {
|
||||||
|
execute = (candidates: Candidate[]): Candidate[] => {
|
||||||
|
return candidates;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeAlgorithmFactory extends AlgorithmFactory {
|
||||||
|
createSelector = (): Selector => {
|
||||||
|
return new FakeSelector(matchQuery);
|
||||||
|
};
|
||||||
|
createProcessors = (): Processor[] => {
|
||||||
|
return [new FakeProcessor(matchQuery)];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('AlgorithmFactory', () => {
|
||||||
|
it('should create an extended class', () => {
|
||||||
|
expect(new FakeAlgorithmFactory(matchQuery)).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { ClassicSelector } from '../../../../domain/entities/engine/selector/classic.selector';
|
||||||
|
import { MatchRequest } from '../../../../domain/dtos/match.request';
|
||||||
|
import { ClassicAlgorithmFactory } from '../../../../domain/entities/engine/factory/classic';
|
||||||
|
import { AlgorithmType } from '../../../../domain/types/algorithm.enum';
|
||||||
|
import { IDefaultParams } from '../../../../domain/types/default-params.type';
|
||||||
|
import { MatchQuery } from '../../../../queries/match.query';
|
||||||
|
|
||||||
|
const mockGeorouterCreator = {
|
||||||
|
create: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultParams: IDefaultParams = {
|
||||||
|
DEFAULT_IDENTIFIER: 0,
|
||||||
|
MARGIN_DURATION: 900,
|
||||||
|
VALIDITY_DURATION: 365,
|
||||||
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
|
DEFAULT_SEATS: 3,
|
||||||
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
|
strict: false,
|
||||||
|
remoteness: 15000,
|
||||||
|
useProportion: true,
|
||||||
|
proportion: 0.3,
|
||||||
|
useAzimuth: true,
|
||||||
|
azimuthMargin: 10,
|
||||||
|
maxDetourDistanceRatio: 0.3,
|
||||||
|
maxDetourDurationRatio: 0.3,
|
||||||
|
georouterType: 'graphhopper',
|
||||||
|
georouterUrl: 'http://localhost',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchRequest: MatchRequest = new MatchRequest();
|
||||||
|
matchRequest.departure = '2023-04-01 12:00';
|
||||||
|
matchRequest.waypoints = [
|
||||||
|
{
|
||||||
|
lat: 49.440041,
|
||||||
|
lon: 1.093912,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 50.630992,
|
||||||
|
lon: 3.045432,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const matchQuery: MatchQuery = new MatchQuery(
|
||||||
|
matchRequest,
|
||||||
|
defaultParams,
|
||||||
|
mockGeorouterCreator,
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('ClassicAlgorithmFactory', () => {
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(new ClassicAlgorithmFactory(matchQuery)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a classic selector', () => {
|
||||||
|
const classicAlgorithmFactory: ClassicAlgorithmFactory =
|
||||||
|
new ClassicAlgorithmFactory(matchQuery);
|
||||||
|
expect(classicAlgorithmFactory.createSelector()).toBeInstanceOf(
|
||||||
|
ClassicSelector,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create processors', () => {
|
||||||
|
const classicAlgorithmFactory: ClassicAlgorithmFactory =
|
||||||
|
new ClassicAlgorithmFactory(matchQuery);
|
||||||
|
expect(classicAlgorithmFactory.createProcessors().length).toBe(6);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { MatchRequest } from '../../../../domain/dtos/match.request';
|
||||||
|
import { AlgorithmType } from '../../../../domain/types/algorithm.enum';
|
||||||
|
import { IDefaultParams } from '../../../../domain/types/default-params.type';
|
||||||
|
import { MatchQuery } from '../../../../queries/match.query';
|
||||||
|
import { Candidate } from '../../../../domain/entities/engine/candidate';
|
||||||
|
import { ClassicGeoFilter } from '../../../../domain/entities/engine/processor/filter/geofilter/classic.filter.processor';
|
||||||
|
|
||||||
|
const mockGeorouterCreator = {
|
||||||
|
create: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultParams: IDefaultParams = {
|
||||||
|
DEFAULT_IDENTIFIER: 0,
|
||||||
|
MARGIN_DURATION: 900,
|
||||||
|
VALIDITY_DURATION: 365,
|
||||||
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
|
DEFAULT_SEATS: 3,
|
||||||
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
|
strict: false,
|
||||||
|
remoteness: 15000,
|
||||||
|
useProportion: true,
|
||||||
|
proportion: 0.3,
|
||||||
|
useAzimuth: true,
|
||||||
|
azimuthMargin: 10,
|
||||||
|
maxDetourDistanceRatio: 0.3,
|
||||||
|
maxDetourDurationRatio: 0.3,
|
||||||
|
georouterType: 'graphhopper',
|
||||||
|
georouterUrl: 'http://localhost',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchRequest: MatchRequest = new MatchRequest();
|
||||||
|
matchRequest.departure = '2023-04-01 12:00';
|
||||||
|
matchRequest.waypoints = [
|
||||||
|
{
|
||||||
|
lat: 49.440041,
|
||||||
|
lon: 1.093912,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 50.630992,
|
||||||
|
lon: 3.045432,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const matchQuery: MatchQuery = new MatchQuery(
|
||||||
|
matchRequest,
|
||||||
|
defaultParams,
|
||||||
|
mockGeorouterCreator,
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('ClassicGeoFilter', () => {
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(new ClassicGeoFilter(matchQuery)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter candidates', () => {
|
||||||
|
const candidates = [new Candidate(), new Candidate()];
|
||||||
|
const classicWaypointCompleter: ClassicGeoFilter = new ClassicGeoFilter(
|
||||||
|
matchQuery,
|
||||||
|
);
|
||||||
|
expect(classicWaypointCompleter.filter(candidates).length).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { MatchRequest } from '../../../../domain/dtos/match.request';
|
||||||
|
import { AlgorithmType } from '../../../../domain/types/algorithm.enum';
|
||||||
|
import { IDefaultParams } from '../../../../domain/types/default-params.type';
|
||||||
|
import { MatchQuery } from '../../../../queries/match.query';
|
||||||
|
import { Candidate } from '../../../../domain/entities/engine/candidate';
|
||||||
|
import { ClassicTimeFilter } from '../../../../domain/entities/engine/processor/filter/timefilter/classic.filter.processor';
|
||||||
|
|
||||||
|
const mockGeorouterCreator = {
|
||||||
|
create: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultParams: IDefaultParams = {
|
||||||
|
DEFAULT_IDENTIFIER: 0,
|
||||||
|
MARGIN_DURATION: 900,
|
||||||
|
VALIDITY_DURATION: 365,
|
||||||
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
|
DEFAULT_SEATS: 3,
|
||||||
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
|
strict: false,
|
||||||
|
remoteness: 15000,
|
||||||
|
useProportion: true,
|
||||||
|
proportion: 0.3,
|
||||||
|
useAzimuth: true,
|
||||||
|
azimuthMargin: 10,
|
||||||
|
maxDetourDistanceRatio: 0.3,
|
||||||
|
maxDetourDurationRatio: 0.3,
|
||||||
|
georouterType: 'graphhopper',
|
||||||
|
georouterUrl: 'http://localhost',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchRequest: MatchRequest = new MatchRequest();
|
||||||
|
matchRequest.departure = '2023-04-01 12:00';
|
||||||
|
matchRequest.waypoints = [
|
||||||
|
{
|
||||||
|
lat: 49.440041,
|
||||||
|
lon: 1.093912,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 50.630992,
|
||||||
|
lon: 3.045432,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const matchQuery: MatchQuery = new MatchQuery(
|
||||||
|
matchRequest,
|
||||||
|
defaultParams,
|
||||||
|
mockGeorouterCreator,
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('ClassicTimeFilter', () => {
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(new ClassicTimeFilter(matchQuery)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter candidates', () => {
|
||||||
|
const candidates = [new Candidate(), new Candidate()];
|
||||||
|
const classicWaypointCompleter: ClassicTimeFilter = new ClassicTimeFilter(
|
||||||
|
matchQuery,
|
||||||
|
);
|
||||||
|
expect(classicWaypointCompleter.filter(candidates).length).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { MatchRequest } from '../../../../domain/dtos/match.request';
|
||||||
|
import { AlgorithmType } from '../../../../domain/types/algorithm.enum';
|
||||||
|
import { IDefaultParams } from '../../../../domain/types/default-params.type';
|
||||||
|
import { MatchQuery } from '../../../../queries/match.query';
|
||||||
|
import { ClassicWaypointsCompleter } from '../../../../domain/entities/engine/processor/completer/classic-waypoint.completer.processor';
|
||||||
|
import { Candidate } from '../../../../domain/entities/engine/candidate';
|
||||||
|
|
||||||
|
const mockGeorouterCreator = {
|
||||||
|
create: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultParams: IDefaultParams = {
|
||||||
|
DEFAULT_IDENTIFIER: 0,
|
||||||
|
MARGIN_DURATION: 900,
|
||||||
|
VALIDITY_DURATION: 365,
|
||||||
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
|
DEFAULT_SEATS: 3,
|
||||||
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
|
strict: false,
|
||||||
|
remoteness: 15000,
|
||||||
|
useProportion: true,
|
||||||
|
proportion: 0.3,
|
||||||
|
useAzimuth: true,
|
||||||
|
azimuthMargin: 10,
|
||||||
|
maxDetourDistanceRatio: 0.3,
|
||||||
|
maxDetourDurationRatio: 0.3,
|
||||||
|
georouterType: 'graphhopper',
|
||||||
|
georouterUrl: 'http://localhost',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchRequest: MatchRequest = new MatchRequest();
|
||||||
|
matchRequest.departure = '2023-04-01 12:00';
|
||||||
|
matchRequest.waypoints = [
|
||||||
|
{
|
||||||
|
lat: 49.440041,
|
||||||
|
lon: 1.093912,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 50.630992,
|
||||||
|
lon: 3.045432,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const matchQuery: MatchQuery = new MatchQuery(
|
||||||
|
matchRequest,
|
||||||
|
defaultParams,
|
||||||
|
mockGeorouterCreator,
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('ClassicWaypointCompleter', () => {
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(new ClassicWaypointsCompleter(matchQuery)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should complete candidates', () => {
|
||||||
|
const candidates = [new Candidate(), new Candidate()];
|
||||||
|
const classicWaypointCompleter: ClassicWaypointsCompleter =
|
||||||
|
new ClassicWaypointsCompleter(matchQuery);
|
||||||
|
expect(classicWaypointCompleter.complete(candidates).length).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { MatchRequest } from '../../../../domain/dtos/match.request';
|
||||||
|
import { AlgorithmType } from '../../../../domain/types/algorithm.enum';
|
||||||
|
import { IDefaultParams } from '../../../../domain/types/default-params.type';
|
||||||
|
import { MatchQuery } from '../../../../queries/match.query';
|
||||||
|
import { Candidate } from '../../../../domain/entities/engine/candidate';
|
||||||
|
import { ClassicSelector } from '../../../../domain/entities/engine/selector/classic.selector';
|
||||||
|
|
||||||
|
const mockGeorouterCreator = {
|
||||||
|
create: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultParams: IDefaultParams = {
|
||||||
|
DEFAULT_IDENTIFIER: 0,
|
||||||
|
MARGIN_DURATION: 900,
|
||||||
|
VALIDITY_DURATION: 365,
|
||||||
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
|
DEFAULT_SEATS: 3,
|
||||||
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
|
strict: false,
|
||||||
|
remoteness: 15000,
|
||||||
|
useProportion: true,
|
||||||
|
proportion: 0.3,
|
||||||
|
useAzimuth: true,
|
||||||
|
azimuthMargin: 10,
|
||||||
|
maxDetourDistanceRatio: 0.3,
|
||||||
|
maxDetourDurationRatio: 0.3,
|
||||||
|
georouterType: 'graphhopper',
|
||||||
|
georouterUrl: 'http://localhost',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchRequest: MatchRequest = new MatchRequest();
|
||||||
|
matchRequest.departure = '2023-04-01 12:00';
|
||||||
|
matchRequest.waypoints = [
|
||||||
|
{
|
||||||
|
lat: 49.440041,
|
||||||
|
lon: 1.093912,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 50.630992,
|
||||||
|
lon: 3.045432,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const matchQuery: MatchQuery = new MatchQuery(
|
||||||
|
matchRequest,
|
||||||
|
defaultParams,
|
||||||
|
mockGeorouterCreator,
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('ClassicSelector', () => {
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(new ClassicSelector(matchQuery)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select candidates', async () => {
|
||||||
|
const classicSelector: ClassicSelector = new ClassicSelector(matchQuery);
|
||||||
|
const candidates: Candidate[] = await classicSelector.select();
|
||||||
|
expect(candidates.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { Completer } from '../../../../domain/entities/engine/processor/completer/completer.abstract';
|
||||||
|
import { MatchRequest } from '../../../../domain/dtos/match.request';
|
||||||
|
import { Candidate } from '../../../../domain/entities/engine/candidate';
|
||||||
|
import { AlgorithmType } from '../../../../domain/types/algorithm.enum';
|
||||||
|
import { IDefaultParams } from '../../../../domain/types/default-params.type';
|
||||||
|
import { MatchQuery } from '../../../../queries/match.query';
|
||||||
|
|
||||||
|
const mockGeorouterCreator = {
|
||||||
|
create: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultParams: IDefaultParams = {
|
||||||
|
DEFAULT_IDENTIFIER: 0,
|
||||||
|
MARGIN_DURATION: 900,
|
||||||
|
VALIDITY_DURATION: 365,
|
||||||
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
|
DEFAULT_SEATS: 3,
|
||||||
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
|
strict: false,
|
||||||
|
remoteness: 15000,
|
||||||
|
useProportion: true,
|
||||||
|
proportion: 0.3,
|
||||||
|
useAzimuth: true,
|
||||||
|
azimuthMargin: 10,
|
||||||
|
maxDetourDistanceRatio: 0.3,
|
||||||
|
maxDetourDurationRatio: 0.3,
|
||||||
|
georouterType: 'graphhopper',
|
||||||
|
georouterUrl: 'http://localhost',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchRequest: MatchRequest = new MatchRequest();
|
||||||
|
matchRequest.departure = '2023-04-01 12:00';
|
||||||
|
matchRequest.waypoints = [
|
||||||
|
{
|
||||||
|
lat: 49.440041,
|
||||||
|
lon: 1.093912,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 50.630992,
|
||||||
|
lon: 3.045432,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const matchQuery: MatchQuery = new MatchQuery(
|
||||||
|
matchRequest,
|
||||||
|
defaultParams,
|
||||||
|
mockGeorouterCreator,
|
||||||
|
);
|
||||||
|
|
||||||
|
class FakeCompleter extends Completer {
|
||||||
|
complete = (candidates: Candidate[]): Candidate[] => {
|
||||||
|
return candidates;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Completer', () => {
|
||||||
|
it('should create an extended class', () => {
|
||||||
|
expect(new FakeCompleter(matchQuery)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call complete method', () => {
|
||||||
|
const fakeCompleter: Completer = new FakeCompleter(matchQuery);
|
||||||
|
const completerSpy = jest.spyOn(fakeCompleter, 'complete');
|
||||||
|
fakeCompleter.execute([new Candidate()]);
|
||||||
|
expect(completerSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { MatchRequest } from '../../../../domain/dtos/match.request';
|
||||||
|
import { Candidate } from '../../../../domain/entities/engine/candidate';
|
||||||
|
import { AlgorithmType } from '../../../../domain/types/algorithm.enum';
|
||||||
|
import { IDefaultParams } from '../../../../domain/types/default-params.type';
|
||||||
|
import { MatchQuery } from '../../../../queries/match.query';
|
||||||
|
import { Filter } from '../../../../domain/entities/engine/processor/filter/filter.abstract';
|
||||||
|
|
||||||
|
const mockGeorouterCreator = {
|
||||||
|
create: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultParams: IDefaultParams = {
|
||||||
|
DEFAULT_IDENTIFIER: 0,
|
||||||
|
MARGIN_DURATION: 900,
|
||||||
|
VALIDITY_DURATION: 365,
|
||||||
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
|
DEFAULT_SEATS: 3,
|
||||||
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
|
strict: false,
|
||||||
|
remoteness: 15000,
|
||||||
|
useProportion: true,
|
||||||
|
proportion: 0.3,
|
||||||
|
useAzimuth: true,
|
||||||
|
azimuthMargin: 10,
|
||||||
|
maxDetourDistanceRatio: 0.3,
|
||||||
|
maxDetourDurationRatio: 0.3,
|
||||||
|
georouterType: 'graphhopper',
|
||||||
|
georouterUrl: 'http://localhost',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchRequest: MatchRequest = new MatchRequest();
|
||||||
|
matchRequest.departure = '2023-04-01 12:00';
|
||||||
|
matchRequest.waypoints = [
|
||||||
|
{
|
||||||
|
lat: 49.440041,
|
||||||
|
lon: 1.093912,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 50.630992,
|
||||||
|
lon: 3.045432,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const matchQuery: MatchQuery = new MatchQuery(
|
||||||
|
matchRequest,
|
||||||
|
defaultParams,
|
||||||
|
mockGeorouterCreator,
|
||||||
|
);
|
||||||
|
|
||||||
|
class FakeFilter extends Filter {
|
||||||
|
filter = (candidates: Candidate[]): Candidate[] => {
|
||||||
|
return candidates;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Filter', () => {
|
||||||
|
it('should create an extended class', () => {
|
||||||
|
expect(new FakeFilter(matchQuery)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call complete method', () => {
|
||||||
|
const fakeFilter: Filter = new FakeFilter(matchQuery);
|
||||||
|
const filterSpy = jest.spyOn(fakeFilter, 'filter');
|
||||||
|
fakeFilter.execute([new Candidate()]);
|
||||||
|
expect(filterSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { MatchRequest } from '../../../../domain/dtos/match.request';
|
||||||
|
import { AlgorithmType } from '../../../../domain/types/algorithm.enum';
|
||||||
|
import { IDefaultParams } from '../../../../domain/types/default-params.type';
|
||||||
|
import { MatchQuery } from '../../../../queries/match.query';
|
||||||
|
import { Candidate } from '../../../../domain/entities/engine/candidate';
|
||||||
|
import { JourneyCompleter } from '../../../../domain/entities/engine/processor/completer/journey.completer.processor';
|
||||||
|
|
||||||
|
const mockGeorouterCreator = {
|
||||||
|
create: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultParams: IDefaultParams = {
|
||||||
|
DEFAULT_IDENTIFIER: 0,
|
||||||
|
MARGIN_DURATION: 900,
|
||||||
|
VALIDITY_DURATION: 365,
|
||||||
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
|
DEFAULT_SEATS: 3,
|
||||||
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
|
strict: false,
|
||||||
|
remoteness: 15000,
|
||||||
|
useProportion: true,
|
||||||
|
proportion: 0.3,
|
||||||
|
useAzimuth: true,
|
||||||
|
azimuthMargin: 10,
|
||||||
|
maxDetourDistanceRatio: 0.3,
|
||||||
|
maxDetourDurationRatio: 0.3,
|
||||||
|
georouterType: 'graphhopper',
|
||||||
|
georouterUrl: 'http://localhost',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchRequest: MatchRequest = new MatchRequest();
|
||||||
|
matchRequest.departure = '2023-04-01 12:00';
|
||||||
|
matchRequest.waypoints = [
|
||||||
|
{
|
||||||
|
lat: 49.440041,
|
||||||
|
lon: 1.093912,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 50.630992,
|
||||||
|
lon: 3.045432,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const matchQuery: MatchQuery = new MatchQuery(
|
||||||
|
matchRequest,
|
||||||
|
defaultParams,
|
||||||
|
mockGeorouterCreator,
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('JourneyCompleter', () => {
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(new JourneyCompleter(matchQuery)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should complete candidates', () => {
|
||||||
|
const candidates = [new Candidate(), new Candidate()];
|
||||||
|
const journeyCompleter: JourneyCompleter = new JourneyCompleter(matchQuery);
|
||||||
|
expect(journeyCompleter.complete(candidates).length).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { AlgorithmType } from '../../../../domain/types/algorithm.enum';
|
||||||
|
import { IDefaultParams } from '../../../../domain/types/default-params.type';
|
||||||
|
import { MatchRequest } from '../../../../domain/dtos/match.request';
|
||||||
|
import { MatchQuery } from '../../../../queries/match.query';
|
||||||
|
import { Matcher } from '../../../../domain/entities/engine/matcher';
|
||||||
|
|
||||||
|
const mockAlgorithmFactoryCreator = {
|
||||||
|
create: jest.fn().mockReturnValue({
|
||||||
|
createSelector: jest.fn().mockReturnValue({
|
||||||
|
select: jest.fn(),
|
||||||
|
}),
|
||||||
|
createProcessors: jest.fn().mockReturnValue([
|
||||||
|
{
|
||||||
|
execute: jest.fn(),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockGeorouterCreator = {
|
||||||
|
create: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultParams: IDefaultParams = {
|
||||||
|
DEFAULT_IDENTIFIER: 0,
|
||||||
|
MARGIN_DURATION: 900,
|
||||||
|
VALIDITY_DURATION: 365,
|
||||||
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
|
DEFAULT_SEATS: 3,
|
||||||
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
|
strict: false,
|
||||||
|
remoteness: 15000,
|
||||||
|
useProportion: true,
|
||||||
|
proportion: 0.3,
|
||||||
|
useAzimuth: true,
|
||||||
|
azimuthMargin: 10,
|
||||||
|
maxDetourDistanceRatio: 0.3,
|
||||||
|
maxDetourDurationRatio: 0.3,
|
||||||
|
georouterType: 'graphhopper',
|
||||||
|
georouterUrl: 'http://localhost',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchRequest: MatchRequest = new MatchRequest();
|
||||||
|
matchRequest.departure = '2023-04-01 12:00';
|
||||||
|
matchRequest.waypoints = [
|
||||||
|
{
|
||||||
|
lat: 49.440041,
|
||||||
|
lon: 1.093912,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 50.630992,
|
||||||
|
lon: 3.045432,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const matchQuery: MatchQuery = new MatchQuery(
|
||||||
|
matchRequest,
|
||||||
|
defaultParams,
|
||||||
|
mockGeorouterCreator,
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('Matcher', () => {
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(new Matcher(mockAlgorithmFactoryCreator)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return matches', async () => {
|
||||||
|
const matcher = new Matcher(mockAlgorithmFactoryCreator);
|
||||||
|
const matches = await matcher.match(matchQuery);
|
||||||
|
expect(matches.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { MatchRequest } from '../../../../domain/dtos/match.request';
|
||||||
|
import { Candidate } from '../../../../domain/entities/engine/candidate';
|
||||||
|
import { Processor } from '../../../../domain/entities/engine/processor/processor.abstract';
|
||||||
|
import { AlgorithmType } from '../../../../domain/types/algorithm.enum';
|
||||||
|
import { IDefaultParams } from '../../../../domain/types/default-params.type';
|
||||||
|
import { MatchQuery } from '../../../../queries/match.query';
|
||||||
|
|
||||||
|
const mockGeorouterCreator = {
|
||||||
|
create: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultParams: IDefaultParams = {
|
||||||
|
DEFAULT_IDENTIFIER: 0,
|
||||||
|
MARGIN_DURATION: 900,
|
||||||
|
VALIDITY_DURATION: 365,
|
||||||
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
|
DEFAULT_SEATS: 3,
|
||||||
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
|
strict: false,
|
||||||
|
remoteness: 15000,
|
||||||
|
useProportion: true,
|
||||||
|
proportion: 0.3,
|
||||||
|
useAzimuth: true,
|
||||||
|
azimuthMargin: 10,
|
||||||
|
maxDetourDistanceRatio: 0.3,
|
||||||
|
maxDetourDurationRatio: 0.3,
|
||||||
|
georouterType: 'graphhopper',
|
||||||
|
georouterUrl: 'http://localhost',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchRequest: MatchRequest = new MatchRequest();
|
||||||
|
matchRequest.departure = '2023-04-01 12:00';
|
||||||
|
matchRequest.waypoints = [
|
||||||
|
{
|
||||||
|
lat: 49.440041,
|
||||||
|
lon: 1.093912,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 50.630992,
|
||||||
|
lon: 3.045432,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const matchQuery: MatchQuery = new MatchQuery(
|
||||||
|
matchRequest,
|
||||||
|
defaultParams,
|
||||||
|
mockGeorouterCreator,
|
||||||
|
);
|
||||||
|
|
||||||
|
class FakeProcessor extends Processor {
|
||||||
|
execute = (candidates: Candidate[]): Candidate[] => {
|
||||||
|
return candidates;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Processor', () => {
|
||||||
|
it('should create an extended class', () => {
|
||||||
|
expect(new FakeProcessor(matchQuery)).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { MatchRequest } from '../../../../domain/dtos/match.request';
|
||||||
|
import { AlgorithmType } from '../../../../domain/types/algorithm.enum';
|
||||||
|
import { IDefaultParams } from '../../../../domain/types/default-params.type';
|
||||||
|
import { MatchQuery } from '../../../../queries/match.query';
|
||||||
|
import { Candidate } from '../../../../domain/entities/engine/candidate';
|
||||||
|
import { RouteCompleter } from '../../../../domain/entities/engine/processor/completer/route.completer.processor';
|
||||||
|
|
||||||
|
const mockGeorouterCreator = {
|
||||||
|
create: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultParams: IDefaultParams = {
|
||||||
|
DEFAULT_IDENTIFIER: 0,
|
||||||
|
MARGIN_DURATION: 900,
|
||||||
|
VALIDITY_DURATION: 365,
|
||||||
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
|
DEFAULT_SEATS: 3,
|
||||||
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
|
strict: false,
|
||||||
|
remoteness: 15000,
|
||||||
|
useProportion: true,
|
||||||
|
proportion: 0.3,
|
||||||
|
useAzimuth: true,
|
||||||
|
azimuthMargin: 10,
|
||||||
|
maxDetourDistanceRatio: 0.3,
|
||||||
|
maxDetourDurationRatio: 0.3,
|
||||||
|
georouterType: 'graphhopper',
|
||||||
|
georouterUrl: 'http://localhost',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchRequest: MatchRequest = new MatchRequest();
|
||||||
|
matchRequest.departure = '2023-04-01 12:00';
|
||||||
|
matchRequest.waypoints = [
|
||||||
|
{
|
||||||
|
lat: 49.440041,
|
||||||
|
lon: 1.093912,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 50.630992,
|
||||||
|
lon: 3.045432,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const matchQuery: MatchQuery = new MatchQuery(
|
||||||
|
matchRequest,
|
||||||
|
defaultParams,
|
||||||
|
mockGeorouterCreator,
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('RouteCompleter', () => {
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(new RouteCompleter(matchQuery)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should complete candidates', () => {
|
||||||
|
const candidates = [new Candidate(), new Candidate()];
|
||||||
|
const routeCompleter: RouteCompleter = new RouteCompleter(matchQuery);
|
||||||
|
expect(routeCompleter.complete(candidates).length).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { MatchRequest } from '../../../../domain/dtos/match.request';
|
||||||
|
import { Candidate } from '../../../../domain/entities/engine/candidate';
|
||||||
|
import { AlgorithmType } from '../../../../domain/types/algorithm.enum';
|
||||||
|
import { IDefaultParams } from '../../../../domain/types/default-params.type';
|
||||||
|
import { MatchQuery } from '../../../../queries/match.query';
|
||||||
|
import { Selector } from '../../../../domain/entities/engine/selector/selector.abstract';
|
||||||
|
|
||||||
|
const mockGeorouterCreator = {
|
||||||
|
create: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultParams: IDefaultParams = {
|
||||||
|
DEFAULT_IDENTIFIER: 0,
|
||||||
|
MARGIN_DURATION: 900,
|
||||||
|
VALIDITY_DURATION: 365,
|
||||||
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
|
DEFAULT_SEATS: 3,
|
||||||
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
|
strict: false,
|
||||||
|
remoteness: 15000,
|
||||||
|
useProportion: true,
|
||||||
|
proportion: 0.3,
|
||||||
|
useAzimuth: true,
|
||||||
|
azimuthMargin: 10,
|
||||||
|
maxDetourDistanceRatio: 0.3,
|
||||||
|
maxDetourDurationRatio: 0.3,
|
||||||
|
georouterType: 'graphhopper',
|
||||||
|
georouterUrl: 'http://localhost',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchRequest: MatchRequest = new MatchRequest();
|
||||||
|
matchRequest.departure = '2023-04-01 12:00';
|
||||||
|
matchRequest.waypoints = [
|
||||||
|
{
|
||||||
|
lat: 49.440041,
|
||||||
|
lon: 1.093912,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 50.630992,
|
||||||
|
lon: 3.045432,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const matchQuery: MatchQuery = new MatchQuery(
|
||||||
|
matchRequest,
|
||||||
|
defaultParams,
|
||||||
|
mockGeorouterCreator,
|
||||||
|
);
|
||||||
|
|
||||||
|
class FakeSelector extends Selector {
|
||||||
|
select = (): Promise<Candidate[]> => {
|
||||||
|
return Promise.resolve([new Candidate()]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Selector', () => {
|
||||||
|
it('should create an extended class', () => {
|
||||||
|
expect(new FakeSelector(matchQuery)).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,13 +3,28 @@ import { Messager } from '../../../adapters/secondaries/messager';
|
||||||
import { MatchUseCase } from '../../../domain/usecases/match.usecase';
|
import { MatchUseCase } from '../../../domain/usecases/match.usecase';
|
||||||
import { MatchRequest } from '../../../domain/dtos/match.request';
|
import { MatchRequest } from '../../../domain/dtos/match.request';
|
||||||
import { MatchQuery } from '../../../queries/match.query';
|
import { MatchQuery } from '../../../queries/match.query';
|
||||||
import { AdRepository } from '../../../adapters/secondaries/ad.repository';
|
|
||||||
import { AutomapperModule } from '@automapper/nestjs';
|
import { AutomapperModule } from '@automapper/nestjs';
|
||||||
import { classes } from '@automapper/classes';
|
import { classes } from '@automapper/classes';
|
||||||
import { IDefaultParams } from '../../../domain/types/default-params.type';
|
import { IDefaultParams } from '../../../domain/types/default-params.type';
|
||||||
import { Algorithm } from '../../../domain/types/algorithm.enum';
|
import { AlgorithmType } from '../../../domain/types/algorithm.enum';
|
||||||
|
import { Matcher } from '../../../domain/entities/engine/matcher';
|
||||||
|
import { Match } from '../../../domain/entities/ecosystem/match';
|
||||||
|
import {
|
||||||
|
MatcherException,
|
||||||
|
MatcherExceptionCode,
|
||||||
|
} from '../../../exceptions/matcher.exception';
|
||||||
|
|
||||||
const mockAdRepository = {};
|
const mockMatcher = {
|
||||||
|
match: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementationOnce(() => [new Match(), new Match(), new Match()])
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new MatcherException(
|
||||||
|
MatcherExceptionCode.INTERNAL,
|
||||||
|
'Something terrible happened !',
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
const mockMessager = {
|
const mockMessager = {
|
||||||
publish: jest.fn().mockImplementation(),
|
publish: jest.fn().mockImplementation(),
|
||||||
|
@ -26,7 +41,7 @@ const defaultParams: IDefaultParams = {
|
||||||
DEFAULT_TIMEZONE: 'Europe/Paris',
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
DEFAULT_SEATS: 3,
|
DEFAULT_SEATS: 3,
|
||||||
DEFAULT_ALGORITHM_SETTINGS: {
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
algorithm: Algorithm.CLASSIC,
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
strict: false,
|
strict: false,
|
||||||
remoteness: 15000,
|
remoteness: 15000,
|
||||||
useProportion: true,
|
useProportion: true,
|
||||||
|
@ -40,6 +55,19 @@ const defaultParams: IDefaultParams = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const matchRequest: MatchRequest = new MatchRequest();
|
||||||
|
matchRequest.waypoints = [
|
||||||
|
{
|
||||||
|
lon: 1.093912,
|
||||||
|
lat: 49.440041,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 50.630992,
|
||||||
|
lon: 3.045432,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
matchRequest.departure = '2023-04-01 12:23:00';
|
||||||
|
|
||||||
describe('MatchUseCase', () => {
|
describe('MatchUseCase', () => {
|
||||||
let matchUseCase: MatchUseCase;
|
let matchUseCase: MatchUseCase;
|
||||||
|
|
||||||
|
@ -47,14 +75,14 @@ describe('MatchUseCase', () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })],
|
imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
|
||||||
provide: AdRepository,
|
|
||||||
useValue: mockAdRepository,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: Messager,
|
provide: Messager,
|
||||||
useValue: mockMessager,
|
useValue: mockMessager,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: Matcher,
|
||||||
|
useValue: mockMatcher,
|
||||||
|
},
|
||||||
MatchUseCase,
|
MatchUseCase,
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
@ -68,22 +96,18 @@ describe('MatchUseCase', () => {
|
||||||
|
|
||||||
describe('execute', () => {
|
describe('execute', () => {
|
||||||
it('should return matches', async () => {
|
it('should return matches', async () => {
|
||||||
const matchRequest: MatchRequest = new MatchRequest();
|
|
||||||
matchRequest.waypoints = [
|
|
||||||
{
|
|
||||||
lon: 1.093912,
|
|
||||||
lat: 49.440041,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
lat: 50.630992,
|
|
||||||
lon: 3.045432,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
matchRequest.departure = '2023-04-01 12:23:00';
|
|
||||||
const matches = await matchUseCase.execute(
|
const matches = await matchUseCase.execute(
|
||||||
new MatchQuery(matchRequest, defaultParams, mockGeorouterCreator),
|
new MatchQuery(matchRequest, defaultParams, mockGeorouterCreator),
|
||||||
);
|
);
|
||||||
expect(matches.total).toBe(1);
|
expect(matches.total).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception when error occurs', async () => {
|
||||||
|
await expect(
|
||||||
|
matchUseCase.execute(
|
||||||
|
new MatchQuery(matchRequest, defaultParams, mockGeorouterCreator),
|
||||||
|
),
|
||||||
|
).rejects.toBeInstanceOf(MatcherException);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Role } from '../../../domain/types/role.enum';
|
||||||
import { TimingFrequency } from '../../../domain/types/timing';
|
import { TimingFrequency } from '../../../domain/types/timing';
|
||||||
import { IDefaultParams } from '../../../domain/types/default-params.type';
|
import { IDefaultParams } from '../../../domain/types/default-params.type';
|
||||||
import { MatchQuery } from '../../../queries/match.query';
|
import { MatchQuery } from '../../../queries/match.query';
|
||||||
import { Algorithm } from '../../../domain/types/algorithm.enum';
|
import { AlgorithmType } from '../../../domain/types/algorithm.enum';
|
||||||
|
|
||||||
const defaultParams: IDefaultParams = {
|
const defaultParams: IDefaultParams = {
|
||||||
DEFAULT_IDENTIFIER: 0,
|
DEFAULT_IDENTIFIER: 0,
|
||||||
|
@ -12,7 +12,7 @@ const defaultParams: IDefaultParams = {
|
||||||
DEFAULT_TIMEZONE: 'Europe/Paris',
|
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||||
DEFAULT_SEATS: 3,
|
DEFAULT_SEATS: 3,
|
||||||
DEFAULT_ALGORITHM_SETTINGS: {
|
DEFAULT_ALGORITHM_SETTINGS: {
|
||||||
algorithm: Algorithm.CLASSIC,
|
algorithm: AlgorithmType.CLASSIC,
|
||||||
strict: false,
|
strict: false,
|
||||||
remoteness: 15000,
|
remoteness: 15000,
|
||||||
useProportion: true,
|
useProportion: true,
|
||||||
|
@ -181,7 +181,7 @@ describe('Match query', () => {
|
||||||
lon: 3.045432,
|
lon: 3.045432,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
matchRequest.algorithm = Algorithm.CLASSIC;
|
matchRequest.algorithm = AlgorithmType.CLASSIC;
|
||||||
matchRequest.strict = true;
|
matchRequest.strict = true;
|
||||||
matchRequest.useProportion = true;
|
matchRequest.useProportion = true;
|
||||||
matchRequest.proportion = 0.45;
|
matchRequest.proportion = 0.45;
|
||||||
|
@ -195,7 +195,9 @@ describe('Match query', () => {
|
||||||
defaultParams,
|
defaultParams,
|
||||||
mockGeorouterCreator,
|
mockGeorouterCreator,
|
||||||
);
|
);
|
||||||
expect(matchQuery.algorithmSettings.algorithm).toBe(Algorithm.CLASSIC);
|
expect(matchQuery.algorithmSettings.algorithmType).toBe(
|
||||||
|
AlgorithmType.CLASSIC,
|
||||||
|
);
|
||||||
expect(matchQuery.algorithmSettings.restrict).toBe(
|
expect(matchQuery.algorithmSettings.restrict).toBe(
|
||||||
TimingFrequency.FREQUENCY_PUNCTUAL,
|
TimingFrequency.FREQUENCY_PUNCTUAL,
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue