better testing

This commit is contained in:
sbriat 2023-04-21 17:17:14 +02:00
parent 400b26dcc5
commit 3c54308935
24 changed files with 260 additions and 231 deletions

View File

@ -4,10 +4,10 @@ import { Geodesic, GeodesicClass } from 'geographiclib-geodesic';
@Injectable()
export class MatcherGeodesic implements IGeodesic {
_geod: GeodesicClass;
private geod: GeodesicClass;
constructor() {
this._geod = Geodesic.WGS84;
this.geod = Geodesic.WGS84;
}
inverse = (
@ -16,7 +16,7 @@ export class MatcherGeodesic implements IGeodesic {
lon2: number,
lat2: number,
): { azimuth: number; distance: number } => {
const { azi2: azimuth, s12: distance } = this._geod.Inverse(
const { azi2: azimuth, s12: distance } = this.geod.Inverse(
lat1,
lon1,
lat2,

View File

@ -16,77 +16,73 @@ import {
@Injectable()
export class GraphhopperGeorouter implements IGeorouter {
_url: string;
_urlArgs: Array<string>;
_withTime: boolean;
_withPoints: boolean;
_withDistance: boolean;
_paths: Array<Path>;
_httpService: HttpService;
_geodesic: IGeodesic;
private url: string;
private urlArgs: Array<string>;
private withTime: boolean;
private withPoints: boolean;
private withDistance: boolean;
private paths: Array<Path>;
private httpService: HttpService;
private geodesic: IGeodesic;
constructor(url: string, httpService: HttpService, geodesic: IGeodesic) {
this._url = url + '/route?';
this._httpService = httpService;
this._geodesic = geodesic;
this.url = url + '/route?';
this.httpService = httpService;
this.geodesic = geodesic;
}
route = async (
paths: Array<Path>,
settings: GeorouterSettings,
): Promise<Array<NamedRoute>> => {
this._setDefaultUrlArgs();
this._setWithTime(settings.withTime);
this._setWithPoints(settings.withPoints);
this._setWithDistance(settings.withDistance);
this._paths = paths;
return await this._getRoutes();
this.setDefaultUrlArgs();
this.setWithTime(settings.withTime);
this.setWithPoints(settings.withPoints);
this.setWithDistance(settings.withDistance);
this.paths = paths;
return await this.getRoutes();
};
_setDefaultUrlArgs = (): void => {
this._urlArgs = [
'vehicle=car',
'weighting=fastest',
'points_encoded=false',
];
private setDefaultUrlArgs = (): void => {
this.urlArgs = ['vehicle=car', 'weighting=fastest', 'points_encoded=false'];
};
_setWithTime = (withTime: boolean): void => {
this._withTime = withTime;
private setWithTime = (withTime: boolean): void => {
this.withTime = withTime;
if (withTime) {
this._urlArgs.push('details=time');
this.urlArgs.push('details=time');
}
};
_setWithPoints = (withPoints: boolean): void => {
this._withPoints = withPoints;
private setWithPoints = (withPoints: boolean): void => {
this.withPoints = withPoints;
if (!withPoints) {
this._urlArgs.push('calc_points=false');
this.urlArgs.push('calc_points=false');
}
};
_setWithDistance = (withDistance: boolean): void => {
this._withDistance = withDistance;
private setWithDistance = (withDistance: boolean): void => {
this.withDistance = withDistance;
if (withDistance) {
this._urlArgs.push('instructions=true');
this.urlArgs.push('instructions=true');
} 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(
this._paths.map(async (path) => {
this.paths.map(async (path) => {
const url: string = [
this._getUrl(),
this.getUrl(),
'&point=',
path.points
.map((point) => [point.lat, point.lon].join())
.join('&point='),
].join('');
const route = await lastValueFrom(
this._httpService.get(url).pipe(
map((res) => (res.data ? this._createRoute(res) : undefined)),
this.httpService.get(url).pipe(
map((res) => (res.data ? this.createRoute(res) : undefined)),
catchError((error: AxiosError) => {
throw new MatcherException(
MatcherExceptionCode.INTERNAL,
@ -104,12 +100,14 @@ export class GraphhopperGeorouter implements IGeorouter {
return routes;
};
_getUrl = (): string => {
return [this._url, this._urlArgs.join('&')].join('');
private getUrl = (): string => {
return [this.url, this.urlArgs.join('&')].join('');
};
_createRoute = (response: AxiosResponse<GraphhopperResponse>): Route => {
const route = new Route(this._geodesic);
private createRoute = (
response: AxiosResponse<GraphhopperResponse>,
): Route => {
const route = new Route(this.geodesic);
if (response.data.paths && response.data.paths[0]) {
const shortestPath = response.data.paths[0];
route.distance = shortestPath.distance ?? 0;
@ -131,7 +129,7 @@ export class GraphhopperGeorouter implements IGeorouter {
if (shortestPath.instructions)
instructions = shortestPath.instructions;
route.setSpacetimePoints(
this._generateSpacetimePoints(
this.generateSpacetimePoints(
shortestPath.points.coordinates,
shortestPath.snapped_waypoints.coordinates,
shortestPath.details.time,
@ -144,15 +142,15 @@ export class GraphhopperGeorouter implements IGeorouter {
return route;
};
_generateSpacetimePoints = (
private generateSpacetimePoints = (
points: Array<Array<number>>,
snappedWaypoints: Array<Array<number>>,
durations: Array<Array<number>>,
instructions: Array<GraphhopperInstruction>,
): Array<SpacetimePoint> => {
const indices = this._getIndices(points, snappedWaypoints);
const times = this._getTimes(durations, indices);
const distances = this._getDistances(instructions, indices);
const indices = this.getIndices(points, snappedWaypoints);
const times = this.getTimes(durations, indices);
const distances = this.getDistances(instructions, indices);
return indices.map(
(index) =>
new SpacetimePoint(
@ -163,7 +161,7 @@ export class GraphhopperGeorouter implements IGeorouter {
);
};
_getIndices = (
private getIndices = (
points: Array<Array<number>>,
snappedWaypoints: Array<Array<number>>,
): Array<number> => {
@ -195,7 +193,7 @@ export class GraphhopperGeorouter implements IGeorouter {
.filter((element) => element.index == -1);
for (const index in points) {
for (const missedWaypoint of missedWaypoints) {
const inverse = this._geodesic.inverse(
const inverse = this.geodesic.inverse(
missedWaypoint.waypoint[0],
missedWaypoint.waypoint[1],
points[index][0],
@ -213,7 +211,7 @@ export class GraphhopperGeorouter implements IGeorouter {
return indices;
};
_getTimes = (
private getTimes = (
durations: Array<Array<number>>,
indices: Array<number>,
): Array<{ index: number; duration: number }> => {
@ -263,7 +261,7 @@ export class GraphhopperGeorouter implements IGeorouter {
return times;
};
_getDistances = (
private getDistances = (
instructions: Array<GraphhopperInstruction>,
indices: Array<number>,
): Array<{ index: number; distance: number }> => {

View File

@ -13,7 +13,7 @@ import { AutoMap } from '@automapper/classes';
import { Point } from '../types/point.type';
import { Schedule } from '../types/schedule.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 { IRequestPerson } from '../interfaces/person-request.interface';
import { IRequestGeography } from '../interfaces/geography-request.interface';
@ -89,9 +89,9 @@ export class MatchRequest
strict: boolean;
@IsOptional()
@IsEnum(Algorithm)
@IsEnum(AlgorithmType)
@AutoMap()
algorithm: Algorithm;
algorithm: AlgorithmType;
@IsOptional()
@IsNumber()

View File

@ -1,14 +1,14 @@
import { IRequestAlgorithmSettings } from '../../interfaces/algorithm-settings-request.interface';
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 { ICreateGeorouter } from '../../interfaces/georouter-creator.interface';
import { IGeorouter } from '../../interfaces/georouter.interface';
export class AlgorithmSettings {
_algorithmSettingsRequest: IRequestAlgorithmSettings;
_strict: boolean;
algorithm: Algorithm;
private algorithmSettingsRequest: IRequestAlgorithmSettings;
private strict: boolean;
algorithmType: AlgorithmType;
restrict: TimingFrequency;
remoteness: number;
useProportion: boolean;
@ -25,10 +25,10 @@ export class AlgorithmSettings {
frequency: TimingFrequency,
georouterCreator: ICreateGeorouter,
) {
this._algorithmSettingsRequest = algorithmSettingsRequest;
this.algorithm =
this.algorithmSettingsRequest = algorithmSettingsRequest;
this.algorithmType =
algorithmSettingsRequest.algorithm ?? defaultAlgorithmSettings.algorithm;
this._strict =
this.strict =
algorithmSettingsRequest.strict ?? defaultAlgorithmSettings.strict;
this.remoteness = algorithmSettingsRequest.remoteness
? Math.abs(algorithmSettingsRequest.remoteness)
@ -55,7 +55,7 @@ export class AlgorithmSettings {
defaultAlgorithmSettings.georouterType,
defaultAlgorithmSettings.georouterUrl,
);
if (this._strict) {
if (this.strict) {
this.restrict = frequency;
}
}

View File

@ -16,9 +16,9 @@ import { Step } from '../../types/step.enum';
import { Path } from '../../types/path.type';
export class Geography {
_geographyRequest: IRequestGeography;
_person: Person;
_points: Array<Point>;
private geographyRequest: IRequestGeography;
private person: Person;
private points: Array<Point>;
originType: PointType;
destinationType: PointType;
timezones: Array<string>;
@ -30,18 +30,18 @@ export class Geography {
defaultTimezone: string,
person: Person,
) {
this._geographyRequest = geographyRequest;
this._person = person;
this._points = [];
this.geographyRequest = geographyRequest;
this.person = person;
this.points = [];
this.originType = undefined;
this.destinationType = undefined;
this.timezones = [defaultTimezone];
}
init = (): void => {
this._validateWaypoints();
this._setTimezones();
this._setPointTypes();
this.validateWaypoints();
this.setTimezones();
this.setPointTypes();
};
createRoutes = async (
@ -52,14 +52,14 @@ export class Geography {
let passengerWaypoints: Array<Waypoint> = [];
const paths: Array<Path> = [];
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
const commonPath: Path = {
key: RouteKey.COMMON,
points: this._points,
points: this.points,
};
driverWaypoints = this._createWaypoints(commonPath.points, Role.DRIVER);
passengerWaypoints = this._createWaypoints(
driverWaypoints = this.createWaypoints(commonPath.points, Role.DRIVER);
passengerWaypoints = this.createWaypoints(
commonPath.points,
Role.PASSENGER,
);
@ -67,14 +67,14 @@ export class Geography {
} else {
const driverPath: Path = {
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 = {
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,
Role.PASSENGER,
);
@ -83,16 +83,16 @@ export class Geography {
} else if (roles.includes(Role.DRIVER)) {
const driverPath: Path = {
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);
} else if (roles.includes(Role.PASSENGER)) {
const passengerPath: Path = {
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,
Role.PASSENGER,
);
@ -128,58 +128,61 @@ export class Geography {
}
};
_validateWaypoints = (): void => {
if (this._geographyRequest.waypoints.length < 2) {
private validateWaypoints = (): void => {
if (this.geographyRequest.waypoints.length < 2) {
throw new MatcherException(
MatcherExceptionCode.INVALID_ARGUMENT,
'At least 2 waypoints are required',
);
}
this._geographyRequest.waypoints.map((point) => {
if (!this._isValidPoint(point)) {
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`,
);
}
this._points.push(point);
this.points.push(point);
});
};
_setTimezones = (): void => {
private setTimezones = (): void => {
this.timezones = find(
this._geographyRequest.waypoints[0].lat,
this._geographyRequest.waypoints[0].lon,
this.geographyRequest.waypoints[0].lat,
this.geographyRequest.waypoints[0].lon,
);
};
_setPointTypes = (): void => {
private setPointTypes = (): void => {
this.originType =
this._geographyRequest.waypoints[0].type ?? PointType.OTHER;
this.geographyRequest.waypoints[0].type ?? PointType.OTHER;
this.destinationType =
this._geographyRequest.waypoints[
this._geographyRequest.waypoints.length - 1
this.geographyRequest.waypoints[
this.geographyRequest.waypoints.length - 1
].type ?? PointType.OTHER;
};
_isValidPoint = (point: Point): boolean =>
this._isValidLongitude(point.lon) && this._isValidLatitude(point.lat);
private isValidPoint = (point: Point): boolean =>
this.isValidLongitude(point.lon) && this.isValidLatitude(point.lat);
_isValidLongitude = (longitude: number): boolean =>
private isValidLongitude = (longitude: number): boolean =>
longitude >= -180 && longitude <= 180;
_isValidLatitude = (latitude: number): boolean =>
private isValidLatitude = (latitude: number): boolean =>
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) => {
const waypoint = new Waypoint(point);
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) {
waypoint.addActor(new Actor(this._person, role, Step.FINISH));
waypoint.addActor(new Actor(this.person, role, Step.FINISH));
} else {
waypoint.addActor(new Actor(this._person, role, Step.INTERMEDIATE));
waypoint.addActor(new Actor(this.person, role, Step.INTERMEDIATE));
}
return waypoint;
});

View File

@ -1,9 +1,9 @@
import { IRequestPerson } from '../../interfaces/person-request.interface';
export class Person {
_personRequest: IRequestPerson;
_defaultIdentifier: number;
_defaultMarginDuration: number;
private personRequest: IRequestPerson;
private defaultIdentifier: number;
private defaultMarginDuration: number;
identifier: number;
marginDurations: Array<number>;
@ -12,23 +12,21 @@ export class Person {
defaultIdentifier: number,
defaultMarginDuration: number,
) {
this._personRequest = personRequest;
this._defaultIdentifier = defaultIdentifier;
this._defaultMarginDuration = defaultMarginDuration;
this.personRequest = personRequest;
this.defaultIdentifier = defaultIdentifier;
this.defaultMarginDuration = defaultMarginDuration;
}
init = (): void => {
this.setIdentifier(
this._personRequest.identifier ?? this._defaultIdentifier,
);
this.setIdentifier(this.personRequest.identifier ?? this.defaultIdentifier);
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,
]);
};

View File

@ -1,12 +1,12 @@
import { IRequestRequirement } from '../../interfaces/requirement-request.interface';
export class Requirement {
_requirementRequest: IRequestRequirement;
private requirementRequest: IRequestRequirement;
seatsDriver: number;
seatsPassenger: number;
constructor(requirementRequest: IRequestRequirement, defaultSeats: number) {
this._requirementRequest = requirementRequest;
this.requirementRequest = requirementRequest;
this.seatsDriver = requirementRequest.seatsDriver ?? defaultSeats;
this.seatsPassenger = requirementRequest.seatsPassenger ?? 1;
}

View File

@ -12,7 +12,7 @@ export class Route {
waypoints: Array<Waypoint>;
points: Array<Point>;
spacetimePoints: Array<SpacetimePoint>;
_geodesic: IGeodesic;
private geodesic: IGeodesic;
constructor(geodesic: IGeodesic) {
this.distance = undefined;
@ -23,25 +23,25 @@ export class Route {
this.waypoints = [];
this.points = [];
this.spacetimePoints = [];
this._geodesic = geodesic;
this.geodesic = geodesic;
}
setWaypoints = (waypoints: Array<Waypoint>): void => {
this.waypoints = waypoints;
this._setAzimuth(waypoints.map((waypoint) => waypoint.point));
this.setAzimuth(waypoints.map((waypoint) => waypoint.point));
};
setPoints = (points: Array<Point>): void => {
this.points = points;
this._setAzimuth(points);
this.setAzimuth(points);
};
setSpacetimePoints = (spacetimePoints: Array<SpacetimePoint>): void => {
this.spacetimePoints = spacetimePoints;
};
_setAzimuth = (points: Array<Point>): void => {
const inverse = this._geodesic.inverse(
private setAzimuth = (points: Array<Point>): void => {
const inverse = this.geodesic.inverse(
points[0].lon,
points[0].lat,
points[points.length - 1].lon,

View File

@ -8,9 +8,9 @@ import { TimingDays, TimingFrequency, Days } from '../../types/timing';
import { Schedule } from '../../types/schedule.type';
export class Time {
_timeRequest: IRequestTime;
_defaultMarginDuration: number;
_defaultValidityDuration: number;
private timeRequest: IRequestTime;
private defaultMarginDuration: number;
private defaultValidityDuration: number;
frequency: TimingFrequency;
fromDate: Date;
toDate: Date;
@ -22,9 +22,9 @@ export class Time {
defaultMarginDuration: number,
defaultValidityDuration: number,
) {
this._timeRequest = timeRequest;
this._defaultMarginDuration = defaultMarginDuration;
this._defaultValidityDuration = defaultValidityDuration;
this.timeRequest = timeRequest;
this.defaultMarginDuration = defaultMarginDuration;
this.defaultValidityDuration = defaultValidityDuration;
this.schedule = {};
this.marginDurations = {
mon: defaultMarginDuration,
@ -38,16 +38,16 @@ export class Time {
}
init = (): void => {
this._validateBaseDate();
this._validatePunctualRequest();
this._validateRecurrentRequest();
this._setPunctualRequest();
this._setRecurrentRequest();
this._setMargindurations();
this.validateBaseDate();
this.validatePunctualRequest();
this.validateRecurrentRequest();
this.setPunctualRequest();
this.setRecurrentRequest();
this.setMargindurations();
};
_validateBaseDate = (): void => {
if (!this._timeRequest.departure && !this._timeRequest.fromDate) {
private validateBaseDate = (): void => {
if (!this.timeRequest.departure && !this.timeRequest.fromDate) {
throw new MatcherException(
MatcherExceptionCode.INVALID_ARGUMENT,
'departure or fromDate is required',
@ -55,10 +55,10 @@ export class Time {
}
};
_validatePunctualRequest = (): void => {
if (this._timeRequest.departure) {
this.fromDate = this.toDate = new Date(this._timeRequest.departure);
if (!this._isDate(this.fromDate)) {
private validatePunctualRequest = (): void => {
if (this.timeRequest.departure) {
this.fromDate = this.toDate = new Date(this.timeRequest.departure);
if (!this.isDate(this.fromDate)) {
throw new MatcherException(
MatcherExceptionCode.INVALID_ARGUMENT,
'Wrong departure date',
@ -67,19 +67,19 @@ export class Time {
}
};
_validateRecurrentRequest = (): void => {
if (this._timeRequest.fromDate) {
this.fromDate = new Date(this._timeRequest.fromDate);
if (!this._isDate(this.fromDate)) {
private validateRecurrentRequest = (): void => {
if (this.timeRequest.fromDate) {
this.fromDate = new Date(this.timeRequest.fromDate);
if (!this.isDate(this.fromDate)) {
throw new MatcherException(
MatcherExceptionCode.INVALID_ARGUMENT,
'Wrong fromDate',
);
}
}
if (this._timeRequest.toDate) {
this.toDate = new Date(this._timeRequest.toDate);
if (!this._isDate(this.toDate)) {
if (this.timeRequest.toDate) {
this.toDate = new Date(this.timeRequest.toDate);
if (!this.isDate(this.toDate)) {
throw new MatcherException(
MatcherExceptionCode.INVALID_ARGUMENT,
'Wrong toDate',
@ -92,20 +92,20 @@ export class Time {
);
}
}
if (this._timeRequest.fromDate) {
this._validateSchedule();
if (this.timeRequest.fromDate) {
this.validateSchedule();
}
};
_validateSchedule = (): void => {
if (!this._timeRequest.schedule) {
private validateSchedule = (): void => {
if (!this.timeRequest.schedule) {
throw new MatcherException(
MatcherExceptionCode.INVALID_ARGUMENT,
'Schedule is required',
);
}
if (
!Object.keys(this._timeRequest.schedule).some((elem) =>
!Object.keys(this.timeRequest.schedule).some((elem) =>
Days.includes(elem),
)
) {
@ -114,9 +114,9 @@ export class Time {
'No valid day in the given schedule',
);
}
Object.keys(this._timeRequest.schedule).map((day) => {
const time = new Date('1970-01-01 ' + this._timeRequest.schedule[day]);
if (!this._isDate(time)) {
Object.keys(this.timeRequest.schedule).map((day) => {
const time = new Date('1970-01-01 ' + this.timeRequest.schedule[day]);
if (!this.isDate(time)) {
throw new MatcherException(
MatcherExceptionCode.INVALID_ARGUMENT,
`Wrong time for ${day} in schedule`,
@ -125,36 +125,33 @@ export class Time {
});
};
_setPunctualRequest = (): void => {
if (this._timeRequest.departure) {
private setPunctualRequest = (): void => {
if (this.timeRequest.departure) {
this.frequency = TimingFrequency.FREQUENCY_PUNCTUAL;
this.schedule[TimingDays[this.fromDate.getDay()]] =
this.fromDate.getHours() + ':' + this.fromDate.getMinutes();
}
};
_setRecurrentRequest = (): void => {
if (this._timeRequest.fromDate) {
private setRecurrentRequest = (): void => {
if (this.timeRequest.fromDate) {
this.frequency = TimingFrequency.FREQUENCY_RECURRENT;
if (!this.toDate) {
this.toDate = this._addDays(
this.fromDate,
this._defaultValidityDuration,
);
this.toDate = this.addDays(this.fromDate, this.defaultValidityDuration);
}
this._setSchedule();
this.setSchedule();
}
};
_setSchedule = (): void => {
Object.keys(this._timeRequest.schedule).map((day) => {
this.schedule[day] = this._timeRequest.schedule[day];
private setSchedule = (): void => {
Object.keys(this.timeRequest.schedule).map((day) => {
this.schedule[day] = this.timeRequest.schedule[day];
});
};
_setMargindurations = (): void => {
if (this._timeRequest.marginDuration) {
const duration = Math.abs(this._timeRequest.marginDuration);
private setMargindurations = (): void => {
if (this.timeRequest.marginDuration) {
const duration = Math.abs(this.timeRequest.marginDuration);
this.marginDurations = {
mon: duration,
tue: duration,
@ -165,9 +162,9 @@ export class Time {
sun: duration,
};
}
if (this._timeRequest.marginDurations) {
if (this.timeRequest.marginDurations) {
if (
!Object.keys(this._timeRequest.marginDurations).some((elem) =>
!Object.keys(this.timeRequest.marginDurations).some((elem) =>
Days.includes(elem),
)
) {
@ -176,19 +173,19 @@ export class Time {
'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._timeRequest.marginDurations[day],
this.timeRequest.marginDurations[day],
);
});
}
};
_isDate = (date: Date): boolean => {
private isDate = (date: Date): boolean => {
return date instanceof Date && isFinite(+date);
};
_addDays = (date: Date, days: number): Date => {
private addDays = (date: Date, days: number): Date => {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;

View File

@ -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 algorithm: AlgorithmFactory;
switch (matchQuery.algorithmSettings.algorithmType) {
case AlgorithmType.CLASSIC:
algorithm = new ClassicAlgorithmFactory(matchQuery);
break;
}
return algorithm;
};
}

View File

@ -4,12 +4,12 @@ import { Candidate } from '../candidate';
import { Selector } from '../selector/selector.abstract';
export abstract class AlgorithmFactory {
_matchQuery: MatchQuery;
_candidates: Array<Candidate>;
protected matchQuery: MatchQuery;
private candidates: Array<Candidate>;
constructor(matchQuery: MatchQuery) {
this._matchQuery = matchQuery;
this._candidates = [];
this.matchQuery = matchQuery;
this.candidates = [];
}
abstract createSelector(): Selector;

View File

@ -9,13 +9,13 @@ import { Selector } from '../selector/selector.abstract';
import { ClassicSelector } from '../selector/classic.selector';
export class ClassicAlgorithmFactory extends AlgorithmFactory {
createSelector = (): Selector => new ClassicSelector(this._matchQuery);
createSelector = (): Selector => new ClassicSelector(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),
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),
];
}

View File

@ -1,24 +1,23 @@
import { Injectable } from '@nestjs/common';
import { MatchQuery } from '../../../queries/match.query';
import { Algorithm } from '../../types/algorithm.enum';
import { Match } from '../ecosystem/match';
import { Candidate } from './candidate';
import { AlgorithmFactory } from './factory/algorithm-factory.abstract';
import { ClassicAlgorithmFactory } from './factory/classic';
import { AlgorithmFactoryCreator } from './factory/algorithm-factory-creator';
@Injectable()
export class Matcher {
constructor(
private readonly algorithmFactoryCreator: AlgorithmFactoryCreator,
) {}
match = async (matchQuery: MatchQuery): Promise<Array<Match>> => {
let algorithm: AlgorithmFactory;
switch (matchQuery.algorithmSettings.algorithm) {
case Algorithm.CLASSIC:
algorithm = new ClassicAlgorithmFactory(matchQuery);
break;
}
let candidates: Array<Candidate> = await algorithm
const algorithmFactory: AlgorithmFactory =
this.algorithmFactoryCreator.create(matchQuery);
let candidates: Array<Candidate> = await algorithmFactory
.createSelector()
.select();
for (const processor of algorithm.createProcessors()) {
for (const processor of algorithmFactory.createProcessors()) {
candidates = processor.execute(candidates);
}
const match = new Match();

View File

@ -3,9 +3,9 @@ import { Candidate } from '../../candidate';
import { Completer } from './completer.abstract';
export class RouteCompleter extends Completer {
_withPoints: boolean;
_withTime: boolean;
_withDistance: boolean;
private withPoints: boolean;
private withTime: boolean;
private withDistance: boolean;
constructor(
matchQuery: MatchQuery,
@ -14,9 +14,9 @@ export class RouteCompleter extends Completer {
withDistance = false,
) {
super(matchQuery);
this._withPoints = withPoints;
this._withTime = withTime;
this._withDistance = withDistance;
this.withPoints = withPoints;
this.withTime = withTime;
this.withDistance = withDistance;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars

View File

@ -2,10 +2,10 @@ import { MatchQuery } from 'src/modules/matcher/queries/match.query';
import { Candidate } from '../candidate';
export abstract class Processor {
_matchQuery: MatchQuery;
private matchQuery: MatchQuery;
constructor(matchQuery: MatchQuery) {
this._matchQuery = matchQuery;
this.matchQuery = matchQuery;
}
abstract execute(candidates: Array<Candidate>): Array<Candidate>;

View File

@ -2,10 +2,10 @@ import { MatchQuery } from 'src/modules/matcher/queries/match.query';
import { Candidate } from '../candidate';
export abstract class Selector {
_matchQuery: MatchQuery;
private matchQuery: MatchQuery;
constructor(matchQuery: MatchQuery) {
this._matchQuery = matchQuery;
this.matchQuery = matchQuery;
}
abstract select(): Promise<Array<Candidate>>;

View File

@ -1,7 +1,7 @@
import { Algorithm } from '../types/algorithm.enum';
import { AlgorithmType } from '../types/algorithm.enum';
export interface IRequestAlgorithmSettings {
algorithm: Algorithm;
algorithm: AlgorithmType;
strict: boolean;
remoteness: number;
useProportion: boolean;

View File

@ -1,3 +1,3 @@
export enum Algorithm {
export enum AlgorithmType {
CLASSIC = 'CLASSIC',
}

View File

@ -1,7 +1,7 @@
import { Algorithm } from './algorithm.enum';
import { AlgorithmType } from './algorithm.enum';
export type DefaultAlgorithmSettings = {
algorithm: Algorithm;
algorithm: AlgorithmType;
strict: boolean;
remoteness: number;
useProportion: boolean;

View File

@ -16,6 +16,7 @@ import { GeorouterCreator } from './adapters/secondaries/georouter-creator';
import { HttpModule } from '@nestjs/axios';
import { MatcherGeodesic } from './adapters/secondaries/geodesic';
import { Matcher } from './domain/entities/engine/matcher';
import { AlgorithmFactoryCreator } from './domain/entities/engine/factory/algorithm-factory-creator';
@Module({
imports: [
@ -59,6 +60,7 @@ import { Matcher } from './domain/entities/engine/matcher';
GeorouterCreator,
MatcherGeodesic,
Matcher,
AlgorithmFactoryCreator,
],
exports: [],
})

View File

@ -107,7 +107,6 @@ describe('Geography entity', () => {
person,
);
geography.init();
expect(geography._points.length).toBe(2);
expect(geography.originType).toBe(PointType.LOCALITY);
expect(geography.destinationType).toBe(PointType.LOCALITY);
});

View File

@ -1,9 +1,22 @@
import { Algorithm } from '../../../../domain/types/algorithm.enum';
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(),
};
@ -15,7 +28,7 @@ const defaultParams: IDefaultParams = {
DEFAULT_TIMEZONE: 'Europe/Paris',
DEFAULT_SEATS: 3,
DEFAULT_ALGORITHM_SETTINGS: {
algorithm: Algorithm.CLASSIC,
algorithm: AlgorithmType.CLASSIC,
strict: false,
remoteness: 15000,
useProportion: true,
@ -49,11 +62,11 @@ const matchQuery: MatchQuery = new MatchQuery(
describe('Matcher', () => {
it('should be defined', () => {
expect(new Matcher()).toBeDefined();
expect(new Matcher(mockAlgorithmFactoryCreator)).toBeDefined();
});
it('should return matches', async () => {
const matcher = new Matcher();
const matcher = new Matcher(mockAlgorithmFactoryCreator);
const matches = await matcher.match(matchQuery);
expect(matches.length).toBe(1);
});

View File

@ -6,7 +6,7 @@ import { MatchQuery } from '../../../queries/match.query';
import { AutomapperModule } from '@automapper/nestjs';
import { classes } from '@automapper/classes';
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 {
@ -41,7 +41,7 @@ const defaultParams: IDefaultParams = {
DEFAULT_TIMEZONE: 'Europe/Paris',
DEFAULT_SEATS: 3,
DEFAULT_ALGORITHM_SETTINGS: {
algorithm: Algorithm.CLASSIC,
algorithm: AlgorithmType.CLASSIC,
strict: false,
remoteness: 15000,
useProportion: true,

View File

@ -3,7 +3,7 @@ import { Role } from '../../../domain/types/role.enum';
import { TimingFrequency } from '../../../domain/types/timing';
import { IDefaultParams } from '../../../domain/types/default-params.type';
import { MatchQuery } from '../../../queries/match.query';
import { Algorithm } from '../../../domain/types/algorithm.enum';
import { AlgorithmType } from '../../../domain/types/algorithm.enum';
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
@ -12,7 +12,7 @@ const defaultParams: IDefaultParams = {
DEFAULT_TIMEZONE: 'Europe/Paris',
DEFAULT_SEATS: 3,
DEFAULT_ALGORITHM_SETTINGS: {
algorithm: Algorithm.CLASSIC,
algorithm: AlgorithmType.CLASSIC,
strict: false,
remoteness: 15000,
useProportion: true,
@ -181,7 +181,7 @@ describe('Match query', () => {
lon: 3.045432,
},
];
matchRequest.algorithm = Algorithm.CLASSIC;
matchRequest.algorithm = AlgorithmType.CLASSIC;
matchRequest.strict = true;
matchRequest.useProportion = true;
matchRequest.proportion = 0.45;
@ -195,7 +195,9 @@ describe('Match query', () => {
defaultParams,
mockGeorouterCreator,
);
expect(matchQuery.algorithmSettings.algorithm).toBe(Algorithm.CLASSIC);
expect(matchQuery.algorithmSettings.algorithmType).toBe(
AlgorithmType.CLASSIC,
);
expect(matchQuery.algorithmSettings.restrict).toBe(
TimingFrequency.FREQUENCY_PUNCTUAL,
);