matcher as injectable
This commit is contained in:
parent
45b33f1ce1
commit
400b26dcc5
|
@ -13,7 +13,7 @@ DEFAULT_TIMEZONE=Europe/Paris
|
|||
# default number of seats proposed as driver
|
||||
DEFAULT_SEATS=3
|
||||
# algorithm type
|
||||
ALGORITHM=classic
|
||||
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
|
||||
|
@ -38,7 +38,6 @@ VALIDITY_DURATION=365
|
|||
MAX_DETOUR_DISTANCE_RATIO=0.3
|
||||
MAX_DETOUR_DURATION_RATIO=0.3
|
||||
|
||||
|
||||
# PRISMA
|
||||
DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=matcher"
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { MatchQuery } from 'src/modules/matcher/queries/match.query';
|
||||
import { Processor } from '../processor/processor.abstract';
|
||||
import { Candidate } from '../candidate';
|
||||
import { Selector } from '../selector/selector.abstract';
|
||||
|
||||
export abstract class AlgorithmFactory {
|
||||
_matchQuery: MatchQuery;
|
||||
|
@ -11,5 +12,6 @@ export abstract class AlgorithmFactory {
|
|||
this._candidates = [];
|
||||
}
|
||||
|
||||
abstract createSelector(): Selector;
|
||||
abstract createProcessors(): Array<Processor>;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,11 @@ import { ClassicGeoFilter } from '../processor/filter/geofilter/classic.filter.p
|
|||
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 {
|
||||
createSelector = (): Selector => new ClassicSelector(this._matchQuery);
|
||||
createProcessors = (): Array<Processor> => [
|
||||
new ClassicWaypointsCompleter(this._matchQuery),
|
||||
new RouteCompleter(this._matchQuery, true, true, true),
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import {
|
||||
MatcherException,
|
||||
MatcherExceptionCode,
|
||||
} from 'src/modules/matcher/exceptions/matcher.exception';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { MatchQuery } from '../../../queries/match.query';
|
||||
import { Algorithm } from '../../types/algorithm.enum';
|
||||
import { Match } from '../ecosystem/match';
|
||||
|
@ -9,23 +6,23 @@ import { Candidate } from './candidate';
|
|||
import { AlgorithmFactory } from './factory/algorithm-factory.abstract';
|
||||
import { ClassicAlgorithmFactory } from './factory/classic';
|
||||
|
||||
@Injectable()
|
||||
export class Matcher {
|
||||
match = (matchQuery: MatchQuery): Array<Match> => {
|
||||
match = async (matchQuery: MatchQuery): Promise<Array<Match>> => {
|
||||
let algorithm: AlgorithmFactory;
|
||||
switch (matchQuery.algorithmSettings.algorithm) {
|
||||
case Algorithm.CLASSIC:
|
||||
algorithm = new ClassicAlgorithmFactory(matchQuery);
|
||||
break;
|
||||
default:
|
||||
throw new MatcherException(
|
||||
MatcherExceptionCode.INVALID_ARGUMENT,
|
||||
'Unknown algorithm',
|
||||
);
|
||||
}
|
||||
let candidates: Array<Candidate> = [];
|
||||
let candidates: Array<Candidate> = await algorithm
|
||||
.createSelector()
|
||||
.select();
|
||||
for (const processor of algorithm.createProcessors()) {
|
||||
candidates = processor.execute(candidates);
|
||||
}
|
||||
return [];
|
||||
const match = new Match();
|
||||
match.uuid = 'e23f9725-2c19-49a0-9ef6-17d8b9a5ec85';
|
||||
return [match];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,64 +3,25 @@ import { InjectMapper } from '@automapper/nestjs';
|
|||
import { QueryHandler } from '@nestjs/cqrs';
|
||||
import { Messager } from '../../adapters/secondaries/messager';
|
||||
import { MatchQuery } from '../../queries/match.query';
|
||||
import { AdRepository } from '../../adapters/secondaries/ad.repository';
|
||||
import { Match } from '../entities/ecosystem/match';
|
||||
import { ICollection } from '../../../database/src/interfaces/collection.interface';
|
||||
import { Matcher } from '../entities/engine/matcher';
|
||||
|
||||
@QueryHandler(MatchQuery)
|
||||
export class MatchUseCase {
|
||||
constructor(
|
||||
private readonly _repository: AdRepository,
|
||||
private readonly _matcher: Matcher,
|
||||
private readonly _messager: Messager,
|
||||
@InjectMapper() private readonly _mapper: Mapper,
|
||||
) {}
|
||||
|
||||
execute = async (matchQuery: MatchQuery): Promise<ICollection<Match>> => {
|
||||
try {
|
||||
// const paths = [];
|
||||
// for (let i = 0; i < 1; i++) {
|
||||
// paths.push({
|
||||
// key: 'route' + i,
|
||||
// points: [
|
||||
// {
|
||||
// lat: 48.110899,
|
||||
// lon: -1.68365,
|
||||
// },
|
||||
// {
|
||||
// lat: 48.131105,
|
||||
// lon: -1.690067,
|
||||
// },
|
||||
// {
|
||||
// lat: 48.534769,
|
||||
// lon: -1.894032,
|
||||
// },
|
||||
// {
|
||||
// lat: 48.56516,
|
||||
// lon: -1.923553,
|
||||
// },
|
||||
// {
|
||||
// lat: 48.622813,
|
||||
// lon: -1.997177,
|
||||
// },
|
||||
// {
|
||||
// lat: 48.67846,
|
||||
// lon: -1.8554,
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
// }
|
||||
// const routes = await matchQuery.algorithmSettings.georouter.route(paths, {
|
||||
// withDistance: false,
|
||||
// withPoints: true,
|
||||
// withTime: true,
|
||||
// });
|
||||
// routes.map((route) => console.log(route.route.spacetimePoints));
|
||||
const match = new Match();
|
||||
match.uuid = 'e23f9725-2c19-49a0-9ef6-17d8b9a5ec85';
|
||||
const data: Array<Match> = await this._matcher.match(matchQuery);
|
||||
this._messager.publish('matcher.match', 'match !');
|
||||
return {
|
||||
data: [match],
|
||||
total: 1,
|
||||
data,
|
||||
total: data.length,
|
||||
};
|
||||
} catch (error) {
|
||||
const err: Error = error;
|
||||
|
@ -75,3 +36,42 @@ export class MatchUseCase {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
// const paths = [];
|
||||
// for (let i = 0; i < 1; i++) {
|
||||
// paths.push({
|
||||
// key: 'route' + i,
|
||||
// points: [
|
||||
// {
|
||||
// lat: 48.110899,
|
||||
// lon: -1.68365,
|
||||
// },
|
||||
// {
|
||||
// lat: 48.131105,
|
||||
// lon: -1.690067,
|
||||
// },
|
||||
// {
|
||||
// lat: 48.534769,
|
||||
// lon: -1.894032,
|
||||
// },
|
||||
// {
|
||||
// lat: 48.56516,
|
||||
// lon: -1.923553,
|
||||
// },
|
||||
// {
|
||||
// lat: 48.622813,
|
||||
// lon: -1.997177,
|
||||
// },
|
||||
// {
|
||||
// lat: 48.67846,
|
||||
// lon: -1.8554,
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
// }
|
||||
// const routes = await matchQuery.algorithmSettings.georouter.route(paths, {
|
||||
// withDistance: false,
|
||||
// withPoints: true,
|
||||
// withTime: true,
|
||||
// });
|
||||
// routes.map((route) => console.log(route.route.spacetimePoints));
|
||||
|
|
|
@ -15,6 +15,7 @@ import { DefaultParamsProvider } from './adapters/secondaries/default-params.pro
|
|||
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';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
@ -57,6 +58,7 @@ import { MatcherGeodesic } from './adapters/secondaries/geodesic';
|
|||
MatchUseCase,
|
||||
GeorouterCreator,
|
||||
MatcherGeodesic,
|
||||
Matcher,
|
||||
],
|
||||
exports: [],
|
||||
})
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import { Algorithm } 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 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: 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',
|
||||
},
|
||||
};
|
||||
|
||||
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()).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return matches', async () => {
|
||||
const matcher = new Matcher();
|
||||
const matches = await matcher.match(matchQuery);
|
||||
expect(matches.length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -3,13 +3,28 @@ import { Messager } from '../../../adapters/secondaries/messager';
|
|||
import { MatchUseCase } from '../../../domain/usecases/match.usecase';
|
||||
import { MatchRequest } from '../../../domain/dtos/match.request';
|
||||
import { MatchQuery } from '../../../queries/match.query';
|
||||
import { AdRepository } from '../../../adapters/secondaries/ad.repository';
|
||||
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 { 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 = {
|
||||
publish: jest.fn().mockImplementation(),
|
||||
|
@ -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', () => {
|
||||
let matchUseCase: MatchUseCase;
|
||||
|
||||
|
@ -47,14 +75,14 @@ describe('MatchUseCase', () => {
|
|||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })],
|
||||
providers: [
|
||||
{
|
||||
provide: AdRepository,
|
||||
useValue: mockAdRepository,
|
||||
},
|
||||
{
|
||||
provide: Messager,
|
||||
useValue: mockMessager,
|
||||
},
|
||||
{
|
||||
provide: Matcher,
|
||||
useValue: mockMatcher,
|
||||
},
|
||||
MatchUseCase,
|
||||
],
|
||||
}).compile();
|
||||
|
@ -68,22 +96,18 @@ describe('MatchUseCase', () => {
|
|||
|
||||
describe('execute', () => {
|
||||
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(
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue