add algorithm settings
This commit is contained in:
parent
b7822cf88f
commit
dd957763a3
33
.env.dist
33
.env.dist
|
@ -5,10 +5,39 @@ SERVICE_CONFIGURATION_DOMAIN=MATCHER
|
|||
HEALTH_SERVICE_PORT=6005
|
||||
|
||||
# DEFAULT CONFIGURATION
|
||||
|
||||
# default identifier used for match requests
|
||||
DEFAULT_IDENTIFIER=0
|
||||
MARGIN_DURATION=900
|
||||
VALIDITY_DURATION=365
|
||||
# default timezone
|
||||
DEFAULT_TIMEZONE=Europe/Paris
|
||||
# default number of seats proposed as driver
|
||||
DEFAULT_SEATS=3
|
||||
# algorithm type
|
||||
ALGORITHM=classic
|
||||
# strict algorithm (if relevant with the algorithm type)
|
||||
# if set to true, matches are made so that
|
||||
# punctual ads match only with punctual ads and
|
||||
# recurrent ads match only with recurrent ads
|
||||
STRICT_ALGORITHM=0
|
||||
# max distance in metres between driver
|
||||
# route and passenger pick-up / drop-off
|
||||
REMOTENESS=15000
|
||||
# use passenger proportion
|
||||
USE_PROPORTION=1
|
||||
# minimal driver proportion
|
||||
PROPORTION=0.3
|
||||
# use azimuth calculation
|
||||
USE_AZIMUTH=1
|
||||
# azimuth margin
|
||||
AZIMUTH_MARGIN=10
|
||||
# margin duration in seconds
|
||||
MARGIN_DURATION=900
|
||||
# default validity duration (in days) for recurrent proposals
|
||||
VALIDITY_DURATION=365
|
||||
# max detour ratio
|
||||
MAX_DETOUR_DISTANCE_RATIO=0.3
|
||||
MAX_DETOUR_DURATION_RATIO=0.3
|
||||
|
||||
|
||||
# PRISMA
|
||||
DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=matcher"
|
||||
|
|
|
@ -14,6 +14,24 @@ export class DefaultParamsProvider {
|
|||
MARGIN_DURATION: parseInt(this.configService.get('MARGIN_DURATION')),
|
||||
VALIDITY_DURATION: parseInt(this.configService.get('VALIDITY_DURATION')),
|
||||
DEFAULT_TIMEZONE: this.configService.get('DEFAULT_TIMEZONE'),
|
||||
DEFAULT_SEATS: parseInt(this.configService.get('DEFAULT_SEATS')),
|
||||
DEFAULT_ALGORITHM_SETTINGS: {
|
||||
algorithm: this.configService.get('ALGORITHM'),
|
||||
strict: !!parseInt(this.configService.get('STRICT_ALGORITHM')),
|
||||
remoteness: parseInt(this.configService.get('REMOTENESS')),
|
||||
useProportion: !!parseInt(this.configService.get('USE_PROPORTION')),
|
||||
proportion: parseInt(this.configService.get('PROPORTION')),
|
||||
useAzimuth: !!parseInt(this.configService.get('USE_AZIMUTH')),
|
||||
azimuthMargin: parseInt(this.configService.get('AZIMUTH_MARGIN')),
|
||||
maxDetourDistanceRatio: parseFloat(
|
||||
this.configService.get('MAX_DETOUR_DISTANCE_RATIO'),
|
||||
),
|
||||
maxDetourDurationRatio: parseFloat(
|
||||
this.configService.get('MAX_DETOUR_DURATION_RATIO'),
|
||||
),
|
||||
georouterType: this.configService.get('GEOROUTER_TYPE'),
|
||||
georouterUrl: this.configService.get('GEOROUTER_URL'),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,16 @@ import { Algorithm } from './algorithm.enum';
|
|||
import { IRequestTime } from '../interfaces/time-request.interface';
|
||||
import { IRequestPerson } from '../interfaces/person-request.interface';
|
||||
import { IRequestGeography } from '../interfaces/geography-request.interface';
|
||||
import { IRequestRequirement } from '../interfaces/requirement-request.interface';
|
||||
import { IRequestAlgorithmSettings } from '../interfaces/algorithm-settings-request.interface';
|
||||
|
||||
export class MatchRequest
|
||||
implements IRequestTime, IRequestPerson, IRequestGeography
|
||||
implements
|
||||
IRequestTime,
|
||||
IRequestPerson,
|
||||
IRequestGeography,
|
||||
IRequestRequirement,
|
||||
IRequestAlgorithmSettings
|
||||
{
|
||||
@IsArray()
|
||||
@AutoMap()
|
||||
|
@ -65,11 +72,15 @@ export class MatchRequest
|
|||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
@Max(10)
|
||||
@AutoMap()
|
||||
seatsPassenger: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
@Max(10)
|
||||
@AutoMap()
|
||||
seatsDriver: number;
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
import { Algorithm } from '../dtos/algorithm.enum';
|
||||
import { IRequestAlgorithmSettings } from '../interfaces/algorithm-settings-request.interface';
|
||||
import { DefaultAlgorithmSettings } from '../interfaces/default-algorithm-settings.type';
|
||||
import { TimingFrequency } from './timing';
|
||||
|
||||
export class AlgorithmSettings {
|
||||
_algorithmSettingsRequest: IRequestAlgorithmSettings;
|
||||
_strict: boolean;
|
||||
algorithm: Algorithm;
|
||||
restrict: TimingFrequency;
|
||||
remoteness: number;
|
||||
useProportion: boolean;
|
||||
proportion: number;
|
||||
useAzimuth: boolean;
|
||||
azimuthMargin: number;
|
||||
maxDetourDurationRatio: number;
|
||||
maxDetourDistanceRatio: number;
|
||||
georouterType: string;
|
||||
georouterUrl: string;
|
||||
|
||||
constructor(
|
||||
algorithmSettingsRequest: IRequestAlgorithmSettings,
|
||||
defaultAlgorithmSettings: DefaultAlgorithmSettings,
|
||||
frequency: TimingFrequency,
|
||||
) {
|
||||
this._algorithmSettingsRequest = algorithmSettingsRequest;
|
||||
this.algorithm =
|
||||
algorithmSettingsRequest.algorithm ?? defaultAlgorithmSettings.algorithm;
|
||||
this._strict =
|
||||
algorithmSettingsRequest.strict ?? defaultAlgorithmSettings.strict;
|
||||
this.remoteness = algorithmSettingsRequest.remoteness
|
||||
? Math.abs(algorithmSettingsRequest.remoteness)
|
||||
: defaultAlgorithmSettings.remoteness;
|
||||
this.useProportion =
|
||||
algorithmSettingsRequest.useProportion ??
|
||||
defaultAlgorithmSettings.useProportion;
|
||||
this.proportion = algorithmSettingsRequest.proportion
|
||||
? Math.abs(algorithmSettingsRequest.proportion)
|
||||
: defaultAlgorithmSettings.proportion;
|
||||
this.useAzimuth =
|
||||
algorithmSettingsRequest.useAzimuth ??
|
||||
defaultAlgorithmSettings.useAzimuth;
|
||||
this.azimuthMargin = algorithmSettingsRequest.azimuthMargin
|
||||
? Math.abs(algorithmSettingsRequest.azimuthMargin)
|
||||
: defaultAlgorithmSettings.azimuthMargin;
|
||||
this.maxDetourDistanceRatio =
|
||||
algorithmSettingsRequest.maxDetourDistanceRatio ??
|
||||
defaultAlgorithmSettings.maxDetourDistanceRatio;
|
||||
this.maxDetourDurationRatio =
|
||||
algorithmSettingsRequest.maxDetourDurationRatio ??
|
||||
defaultAlgorithmSettings.maxDetourDurationRatio;
|
||||
this.georouterType = defaultAlgorithmSettings.georouterType;
|
||||
this.georouterUrl = defaultAlgorithmSettings.georouterUrl;
|
||||
if (this._strict) {
|
||||
this.restrict = frequency;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,13 @@
|
|||
import { IRequestRequirement } from '../interfaces/requirement-request.interface';
|
||||
|
||||
export class Requirement {
|
||||
_requirementRequest: IRequestRequirement;
|
||||
seatsDriver: number;
|
||||
seatsPassenger: number;
|
||||
|
||||
constructor(requirementRequest: IRequestRequirement, defaultSeats: number) {
|
||||
this._requirementRequest = requirementRequest;
|
||||
this.seatsDriver = requirementRequest.seatsDriver ?? defaultSeats;
|
||||
this.seatsPassenger = requirementRequest.seatsPassenger ?? 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import { Georouter } from '../interfaces/georouter.interface';
|
||||
import { Algorithm } from '../dtos/algorithm.enum';
|
||||
|
||||
export class Settings {
|
||||
export interface IRequestAlgorithmSettings {
|
||||
algorithm: Algorithm;
|
||||
restrict: boolean;
|
||||
strict: boolean;
|
||||
remoteness: number;
|
||||
useProportion: boolean;
|
||||
proportion: number;
|
||||
useAzimuth: boolean;
|
||||
azimuthMargin: number;
|
||||
maxDetourDurationRatio: number;
|
||||
maxDetourDistanceRatio: number;
|
||||
georouter: Georouter;
|
||||
maxDetourDurationRatio: number;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { Algorithm } from '../dtos/algorithm.enum';
|
||||
|
||||
export type DefaultAlgorithmSettings = {
|
||||
algorithm: Algorithm;
|
||||
strict: boolean;
|
||||
remoteness: number;
|
||||
useProportion: boolean;
|
||||
proportion: number;
|
||||
useAzimuth: boolean;
|
||||
azimuthMargin: number;
|
||||
maxDetourDistanceRatio: number;
|
||||
maxDetourDurationRatio: number;
|
||||
georouterType: string;
|
||||
georouterUrl: string;
|
||||
};
|
|
@ -1,6 +1,10 @@
|
|||
import { DefaultAlgorithmSettings } from './default-algorithm-settings.type';
|
||||
|
||||
export type IDefaultParams = {
|
||||
DEFAULT_IDENTIFIER: number;
|
||||
MARGIN_DURATION: number;
|
||||
VALIDITY_DURATION: number;
|
||||
DEFAULT_TIMEZONE: string;
|
||||
DEFAULT_SEATS: number;
|
||||
DEFAULT_ALGORITHM_SETTINGS: DefaultAlgorithmSettings;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export interface IRequestRequirement {
|
||||
seatsDriver?: number;
|
||||
seatsPassenger?: number;
|
||||
}
|
|
@ -3,7 +3,7 @@ import { Geography } from '../domain/entities/geography';
|
|||
import { Person } from '../domain/entities/person';
|
||||
import { Requirement } from '../domain/entities/requirement';
|
||||
import { Role } from '../domain/entities/role.enum';
|
||||
import { Settings } from '../domain/entities/settings';
|
||||
import { AlgorithmSettings } from '../domain/entities/algorithm-settings';
|
||||
import { Time } from '../domain/entities/time';
|
||||
import { IDefaultParams } from '../domain/interfaces/default-params.type';
|
||||
|
||||
|
@ -16,7 +16,7 @@ export class MatchQuery {
|
|||
geography: Geography;
|
||||
exclusions: Array<number>;
|
||||
requirement: Requirement;
|
||||
settings: Settings;
|
||||
algorithmSettings: AlgorithmSettings;
|
||||
|
||||
constructor(matchRequest: MatchRequest, defaultParams: IDefaultParams) {
|
||||
this._matchRequest = matchRequest;
|
||||
|
@ -25,15 +25,11 @@ export class MatchQuery {
|
|||
this._setRoles();
|
||||
this._setTime();
|
||||
this._setGeography();
|
||||
this._initialize();
|
||||
this._setRequirement();
|
||||
this._setAlgorithmSettings();
|
||||
this._setExclusions();
|
||||
}
|
||||
|
||||
_initialize() {
|
||||
this.requirement = new Requirement();
|
||||
this.settings = new Settings();
|
||||
}
|
||||
|
||||
_setPerson() {
|
||||
this.person = new Person(
|
||||
this._matchRequest,
|
||||
|
@ -67,6 +63,21 @@ export class MatchQuery {
|
|||
this.geography.init();
|
||||
}
|
||||
|
||||
_setRequirement() {
|
||||
this.requirement = new Requirement(
|
||||
this._matchRequest,
|
||||
this._defaultParams.DEFAULT_SEATS,
|
||||
);
|
||||
}
|
||||
|
||||
_setAlgorithmSettings() {
|
||||
this.algorithmSettings = new AlgorithmSettings(
|
||||
this._matchRequest,
|
||||
this._defaultParams.DEFAULT_ALGORITHM_SETTINGS,
|
||||
this.time.frequency,
|
||||
);
|
||||
}
|
||||
|
||||
_setExclusions() {
|
||||
this.exclusions = [];
|
||||
if (this._matchRequest.identifier)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { Algorithm } from '../../domain/dtos/algorithm.enum';
|
||||
import { MatchRequest } from '../../domain/dtos/match.request';
|
||||
import { Role } from '../../domain/entities/role.enum';
|
||||
import { TimingFrequency } from '../../domain/entities/timing';
|
||||
import { IDefaultParams } from '../../domain/interfaces/default-params.type';
|
||||
import { MatchQuery } from '../../queries/match.query';
|
||||
|
||||
|
@ -8,6 +10,20 @@ const defaultParams: IDefaultParams = {
|
|||
MARGIN_DURATION: 900,
|
||||
VALIDITY_DURATION: 365,
|
||||
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||
DEFAULT_SEATS: 3,
|
||||
DEFAULT_ALGORITHM_SETTINGS: {
|
||||
algorithm: Algorithm.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',
|
||||
},
|
||||
};
|
||||
|
||||
describe('Match query', () => {
|
||||
|
@ -28,7 +44,6 @@ describe('Match query', () => {
|
|||
expect(matchQuery).toBeDefined();
|
||||
});
|
||||
|
||||
describe('init', () => {
|
||||
it('should create a query with excluded identifiers', () => {
|
||||
const matchRequest: MatchRequest = new MatchRequest();
|
||||
matchRequest.departure = '2023-04-01 12:00';
|
||||
|
@ -44,13 +59,9 @@ describe('Match query', () => {
|
|||
];
|
||||
matchRequest.identifier = 125;
|
||||
matchRequest.exclusions = [126, 127, 128];
|
||||
const matchQuery: MatchQuery = new MatchQuery(
|
||||
matchRequest,
|
||||
defaultParams,
|
||||
);
|
||||
const matchQuery: MatchQuery = new MatchQuery(matchRequest, defaultParams);
|
||||
expect(matchQuery.exclusions.length).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a query with driver role only', () => {
|
||||
const matchRequest: MatchRequest = new MatchRequest();
|
||||
|
@ -108,4 +119,60 @@ describe('Match query', () => {
|
|||
expect(matchQuery.roles).toContain(Role.PASSENGER);
|
||||
expect(matchQuery.roles).toContain(Role.DRIVER);
|
||||
});
|
||||
|
||||
it('should create a query with number of seats modified', () => {
|
||||
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,
|
||||
},
|
||||
];
|
||||
matchRequest.seatsDriver = 1;
|
||||
matchRequest.seatsPassenger = 2;
|
||||
const matchQuery: MatchQuery = new MatchQuery(matchRequest, defaultParams);
|
||||
expect(matchQuery.requirement.seatsDriver).toBe(1);
|
||||
expect(matchQuery.requirement.seatsPassenger).toBe(2);
|
||||
});
|
||||
|
||||
it('should create a query with modified algorithm settings', () => {
|
||||
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,
|
||||
},
|
||||
];
|
||||
matchRequest.algorithm = Algorithm.CLASSIC;
|
||||
matchRequest.strict = true;
|
||||
matchRequest.useProportion = true;
|
||||
matchRequest.proportion = 0.45;
|
||||
matchRequest.useAzimuth = true;
|
||||
matchRequest.azimuthMargin = 15;
|
||||
matchRequest.remoteness = 20000;
|
||||
matchRequest.maxDetourDistanceRatio = 0.41;
|
||||
matchRequest.maxDetourDurationRatio = 0.42;
|
||||
const matchQuery: MatchQuery = new MatchQuery(matchRequest, defaultParams);
|
||||
expect(matchQuery.algorithmSettings.algorithm).toBe(Algorithm.CLASSIC);
|
||||
expect(matchQuery.algorithmSettings.restrict).toBe(
|
||||
TimingFrequency.FREQUENCY_PUNCTUAL,
|
||||
);
|
||||
expect(matchQuery.algorithmSettings.useProportion).toBeTruthy();
|
||||
expect(matchQuery.algorithmSettings.proportion).toBe(0.45);
|
||||
expect(matchQuery.algorithmSettings.useAzimuth).toBeTruthy();
|
||||
expect(matchQuery.algorithmSettings.azimuthMargin).toBe(15);
|
||||
expect(matchQuery.algorithmSettings.remoteness).toBe(20000);
|
||||
expect(matchQuery.algorithmSettings.maxDetourDistanceRatio).toBe(0.41);
|
||||
expect(matchQuery.algorithmSettings.maxDetourDurationRatio).toBe(0.42);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ import { AdRepository } from '../../adapters/secondaries/ad.repository';
|
|||
import { AutomapperModule } from '@automapper/nestjs';
|
||||
import { classes } from '@automapper/classes';
|
||||
import { IDefaultParams } from '../../domain/interfaces/default-params.type';
|
||||
import { Algorithm } from '../../domain/dtos/algorithm.enum';
|
||||
|
||||
const mockAdRepository = {};
|
||||
|
||||
|
@ -19,6 +20,20 @@ const defaultParams: IDefaultParams = {
|
|||
MARGIN_DURATION: 900,
|
||||
VALIDITY_DURATION: 365,
|
||||
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||
DEFAULT_SEATS: 3,
|
||||
DEFAULT_ALGORITHM_SETTINGS: {
|
||||
algorithm: Algorithm.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',
|
||||
},
|
||||
};
|
||||
|
||||
describe('MatchUseCase', () => {
|
||||
|
|
Loading…
Reference in New Issue