get cached matching

This commit is contained in:
sbriat 2023-09-28 15:23:17 +02:00
parent 09efe313ba
commit b810bc86e6
10 changed files with 650 additions and 151 deletions

View File

@ -60,6 +60,44 @@ export class MatchQueryHandler implements IQueryHandler {
perPage: this._defaultParams.PER_PAGE, perPage: this._defaultParams.PER_PAGE,
}) })
.setDatesAndSchedule(this.datetimeTransformer); .setDatesAndSchedule(this.datetimeTransformer);
let matchingEntity: MatchingEntity | undefined = await this._cachedMatching(
query.id,
);
if (!matchingEntity)
matchingEntity = (await this._createMatching(query)) as MatchingEntity;
const perPage: number = query.perPage as number;
const page: number = Paginator.pageNumber(
matchingEntity.getProps().matches.length,
perPage,
query.page as number,
);
return {
id: matchingEntity.id,
matches: Paginator.pageItems(
matchingEntity.getProps().matches,
page,
perPage,
),
total: matchingEntity.getProps().matches.length,
page,
perPage,
};
};
private _cachedMatching = async (
id?: string,
): Promise<MatchingEntity | undefined> => {
if (!id) return undefined;
try {
return await this.matchingRepository.get(id);
} catch (e: any) {
return undefined;
}
};
private _createMatching = async (
query: MatchQuery,
): Promise<MatchingEntity> => {
await query.setRoutes(); await query.setRoutes();
let algorithm: Algorithm; let algorithm: Algorithm;
@ -70,30 +108,8 @@ export class MatchQueryHandler implements IQueryHandler {
} }
const matches: MatchEntity[] = await algorithm.match(); const matches: MatchEntity[] = await algorithm.match();
const perPage: number = query.perPage as number; const matchingEntity = MatchingEntity.create({
const page: number = Paginator.pageNumber( matches,
matches.length,
perPage,
query.page as number,
);
// create Matching Entity for persistence
const matchingEntity: MatchingEntity = MatchingEntity.create({
matches: matches.map((matchEntity: MatchEntity) => ({
adId: matchEntity.getProps().adId,
role: matchEntity.getProps().role,
frequency: matchEntity.getProps().frequency,
distance: matchEntity.getProps().distance,
duration: matchEntity.getProps().duration,
initialDistance: matchEntity.getProps().initialDistance,
initialDuration: matchEntity.getProps().initialDuration,
distanceDetour: matchEntity.getProps().distanceDetour,
durationDetour: matchEntity.getProps().durationDetour,
distanceDetourPercentage:
matchEntity.getProps().distanceDetourPercentage,
durationDetourPercentage:
matchEntity.getProps().durationDetourPercentage,
journeys: matchEntity.getProps().journeys,
})),
query: { query: {
driver: query.driver as boolean, driver: query.driver as boolean,
passenger: query.passenger as boolean, passenger: query.passenger as boolean,
@ -120,13 +136,7 @@ export class MatchQueryHandler implements IQueryHandler {
}, },
}); });
await this.matchingRepository.save(matchingEntity); await this.matchingRepository.save(matchingEntity);
return { return matchingEntity;
id: matchingEntity.id,
matches: Paginator.pageItems(matches, page, perPage),
total: matches.length,
page,
perPage,
};
}; };
} }

View File

@ -15,6 +15,7 @@ import { Point } from '@modules/ad/core/domain/value-objects/point.value-object'
import { Route } from '../../types/route.type'; import { Route } from '../../types/route.type';
export class MatchQuery extends QueryBase { export class MatchQuery extends QueryBase {
id?: string;
driver?: boolean; driver?: boolean;
passenger?: boolean; passenger?: boolean;
readonly frequency: Frequency; readonly frequency: Frequency;
@ -43,6 +44,7 @@ export class MatchQuery extends QueryBase {
constructor(props: MatchRequestDto, routeProvider: RouteProviderPort) { constructor(props: MatchRequestDto, routeProvider: RouteProviderPort) {
super(); super();
this.id = props.id;
this.driver = props.driver; this.driver = props.driver;
this.passenger = props.passenger; this.passenger = props.passenger;
this.frequency = props.frequency; this.frequency = props.frequency;

View File

@ -1,14 +1,14 @@
import { MatchProps } from './match.types'; import { MatchEntity } from './match.entity';
import { MatchQueryProps } from './value-objects/match-query.value-object'; import { MatchQueryProps } from './value-objects/match-query.value-object';
// All properties that a Matching has // All properties that a Matching has
export interface MatchingProps { export interface MatchingProps {
query: MatchQueryProps; // the query that induced the matches query: MatchQueryProps; // the query that induced the matches
matches: MatchProps[]; matches: MatchEntity[];
} }
// Properties that are needed for a Matching creation // Properties that are needed for a Matching creation
export interface CreateMatchingProps { export interface CreateMatchingProps {
query: MatchQueryProps; query: MatchQueryProps;
matches: MatchProps[]; matches: MatchEntity[];
} }

View File

@ -7,6 +7,7 @@ import {
IsISO8601, IsISO8601,
IsInt, IsInt,
IsOptional, IsOptional,
IsUUID,
Max, Max,
Min, Min,
ValidateNested, ValidateNested,
@ -21,6 +22,10 @@ import { Frequency } from '@modules/ad/core/domain/ad.types';
import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types'; import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
export class MatchRequestDto { export class MatchRequestDto {
@IsUUID()
@IsOptional()
id?: string;
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
driver?: boolean; driver?: boolean;

View File

@ -7,25 +7,26 @@ service MatcherService {
} }
message MatchRequest { message MatchRequest {
bool driver = 1; string id = 1;
bool passenger = 2; bool driver = 2;
Frequency frequency = 3; bool passenger = 3;
string fromDate = 4; Frequency frequency = 4;
string toDate = 5; string fromDate = 5;
repeated ScheduleItem schedule = 6; string toDate = 6;
bool strict = 7; repeated ScheduleItem schedule = 7;
repeated Waypoint waypoints = 8; bool strict = 8;
AlgorithmType algorithmType = 9; repeated Waypoint waypoints = 9;
int32 remoteness = 10; AlgorithmType algorithmType = 10;
bool useProportion = 11; int32 remoteness = 11;
int32 proportion = 12; bool useProportion = 12;
bool useAzimuth = 13; int32 proportion = 13;
int32 azimuthMargin = 14; bool useAzimuth = 14;
float maxDetourDistanceRatio = 15; int32 azimuthMargin = 15;
float maxDetourDurationRatio = 16; float maxDetourDistanceRatio = 16;
int32 identifier = 22; float maxDetourDurationRatio = 17;
optional int32 page = 23; int32 identifier = 18;
optional int32 perPage = 24; optional int32 page = 19;
optional int32 perPage = 20;
} }
message ScheduleItem { message ScheduleItem {

View File

@ -15,22 +15,32 @@ export class MatchMapper {
private readonly outputDatetimeTransformer: DateTimeTransformerPort, private readonly outputDatetimeTransformer: DateTimeTransformerPort,
) {} ) {}
toResponse = (match: MatchEntity): MatchResponseDto => ({ toResponse = (match: MatchEntity): MatchResponseDto => {
...new ResponseBase(match), return {
adId: match.getProps().adId, ...new ResponseBase(match),
role: match.getProps().role, adId: match.getProps().adId,
frequency: match.getProps().frequency, role: match.getProps().role,
distance: match.getProps().distance, frequency: match.getProps().frequency,
duration: match.getProps().duration, distance: match.getProps().distance,
initialDistance: match.getProps().initialDistance, duration: match.getProps().duration,
initialDuration: match.getProps().initialDuration, initialDistance: match.getProps().initialDistance,
distanceDetour: match.getProps().distanceDetour, initialDuration: match.getProps().initialDuration,
durationDetour: match.getProps().durationDetour, distanceDetour: match.getProps().distanceDetour,
distanceDetourPercentage: match.getProps().distanceDetourPercentage, durationDetour: match.getProps().durationDetour,
durationDetourPercentage: match.getProps().durationDetourPercentage, distanceDetourPercentage: match.getProps().distanceDetourPercentage,
journeys: match.getProps().journeys.map((journey: Journey) => ({ durationDetourPercentage: match.getProps().durationDetourPercentage,
day: new Date( journeys: match.getProps().journeys.map((journey: Journey) => ({
this.outputDatetimeTransformer.fromDate( day: new Date(
this.outputDatetimeTransformer.fromDate(
{
date: journey.firstDate.toISOString().split('T')[0],
time: journey.firstDriverDepartureTime(),
coordinates: journey.driverOrigin(),
},
match.getProps().frequency,
),
).getDay(),
firstDate: this.outputDatetimeTransformer.fromDate(
{ {
date: journey.firstDate.toISOString().split('T')[0], date: journey.firstDate.toISOString().split('T')[0],
time: journey.firstDriverDepartureTime(), time: journey.firstDriverDepartureTime(),
@ -38,41 +48,33 @@ export class MatchMapper {
}, },
match.getProps().frequency, match.getProps().frequency,
), ),
).getDay(), lastDate: this.outputDatetimeTransformer.fromDate(
firstDate: this.outputDatetimeTransformer.fromDate(
{
date: journey.firstDate.toISOString().split('T')[0],
time: journey.firstDriverDepartureTime(),
coordinates: journey.driverOrigin(),
},
match.getProps().frequency,
),
lastDate: this.outputDatetimeTransformer.fromDate(
{
date: journey.lastDate.toISOString().split('T')[0],
time: journey.firstDriverDepartureTime(),
coordinates: journey.driverOrigin(),
},
match.getProps().frequency,
),
steps: journey.journeyItems.map((journeyItem: JourneyItem) => ({
duration: journeyItem.duration,
distance: journeyItem.distance as number,
lon: journeyItem.lon,
lat: journeyItem.lat,
time: this.outputDatetimeTransformer.time(
{ {
date: journey.firstDate.toISOString().split('T')[0], date: journey.lastDate.toISOString().split('T')[0],
time: journeyItem.driverTime(), time: journey.firstDriverDepartureTime(),
coordinates: journey.driverOrigin(), coordinates: journey.driverOrigin(),
}, },
match.getProps().frequency, match.getProps().frequency,
), ),
actors: journeyItem.actorTimes.map((actorTime: ActorTime) => ({ steps: journey.journeyItems.map((journeyItem: JourneyItem) => ({
role: actorTime.role, duration: journeyItem.duration,
target: actorTime.target, distance: journeyItem.distance as number,
lon: journeyItem.lon,
lat: journeyItem.lat,
time: this.outputDatetimeTransformer.time(
{
date: journey.firstDate.toISOString().split('T')[0],
time: journeyItem.driverTime(),
coordinates: journey.driverOrigin(),
},
match.getProps().frequency,
),
actors: journeyItem.actorTimes.map((actorTime: ActorTime) => ({
role: actorTime.role,
target: actorTime.target,
})),
})), })),
})), })),
})), };
}); };
} }

View File

@ -1,13 +1,234 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Mapper } from '@mobicoop/ddd-library'; import { Mapper } from '@mobicoop/ddd-library';
import { MatchingEntity } from './core/domain/matching.entity'; import { MatchingEntity } from './core/domain/matching.entity';
import { Frequency, Role } from './core/domain/ad.types';
import { MatchEntity } from './core/domain/match.entity';
import { Target } from './core/domain/candidate.types';
import { Waypoint } from './core/application/types/waypoint.type';
import { ScheduleItem } from './core/application/types/schedule-item.type';
import { Journey } from './core/domain/value-objects/journey.value-object';
import { JourneyItem } from './core/domain/value-objects/journey-item.value-object';
import { ActorTime } from './core/domain/value-objects/actor-time.value-object';
@Injectable() @Injectable()
export class MatchingMapper export class MatchingMapper
implements Mapper<MatchingEntity, string, string, undefined> implements Mapper<MatchingEntity, string, string, undefined>
{ {
toPersistence = (entity: MatchingEntity): string => JSON.stringify(entity); toPersistence = (entity: MatchingEntity): string =>
JSON.stringify(<PersistedMatching>{
id: entity.id,
createdAt: entity.createdAt.toISOString(),
updatedAt: entity.updatedAt.toISOString(),
matches: entity.getProps().matches.map((match: MatchEntity) => ({
adId: match.getProps().adId,
role: match.getProps().role,
frequency: match.getProps().frequency,
distance: match.getProps().distance,
duration: match.getProps().duration,
initialDistance: match.getProps().initialDistance,
initialDuration: match.getProps().initialDuration,
distanceDetour: match.getProps().distanceDetour,
durationDetour: match.getProps().durationDetour,
distanceDetourPercentage: match.getProps().distanceDetourPercentage,
durationDetourPercentage: match.getProps().durationDetourPercentage,
journeys: match.getProps().journeys.map((journey: Journey) => ({
firstDate: journey.firstDate.toISOString(),
lastDate: journey.lastDate.toISOString(),
journeyItems: journey.journeyItems.map(
(journeyItem: JourneyItem) => ({
lon: journeyItem.lon,
lat: journeyItem.lat,
duration: journeyItem.duration,
distance: journeyItem.distance,
actorTimes: journeyItem.actorTimes.map(
(actorTime: ActorTime) => ({
role: actorTime.role,
target: actorTime.target,
firstDatetime: actorTime.firstDatetime.toISOString(),
firstMinDatetime: actorTime.firstMinDatetime.toISOString(),
firstMaxDatetime: actorTime.firstMaxDatetime.toISOString(),
lastDatetime: actorTime.lastDatetime.toISOString(),
lastMinDatetime: actorTime.lastMinDatetime.toISOString(),
lastMaxDatetime: actorTime.lastMaxDatetime.toISOString(),
}),
),
}),
),
})),
})),
query: {
driver: entity.getProps().query.driver,
passenger: entity.getProps().query.passenger,
frequency: entity.getProps().query.frequency,
fromDate: entity.getProps().query.fromDate,
toDate: entity.getProps().query.toDate,
schedule: entity
.getProps()
.query.schedule.map((scheduleItem: ScheduleItem) => ({
day: scheduleItem.day,
time: scheduleItem.time,
margin: scheduleItem.margin,
})),
seatsProposed: entity.getProps().query.seatsProposed,
seatsRequested: entity.getProps().query.seatsRequested,
strict: entity.getProps().query.strict,
waypoints: entity
.getProps()
.query.waypoints.map((waypoint: Waypoint) => ({
lon: waypoint.lon,
lat: waypoint.lat,
position: waypoint.position,
houseNumber: waypoint.houseNumber,
street: waypoint.street,
postalCode: waypoint.postalCode,
locality: waypoint.locality,
country: waypoint.country,
})),
algorithmType: entity.getProps().query.algorithmType,
remoteness: entity.getProps().query.remoteness,
useProportion: entity.getProps().query.useProportion,
proportion: entity.getProps().query.proportion,
useAzimuth: entity.getProps().query.useAzimuth,
azimuthMargin: entity.getProps().query.azimuthMargin,
maxDetourDistanceRatio: entity.getProps().query.maxDetourDistanceRatio,
maxDetourDurationRatio: entity.getProps().query.maxDetourDurationRatio,
},
});
toDomain = (record: string): MatchingEntity => toDomain = (record: string): MatchingEntity => {
new MatchingEntity(JSON.parse(record)); const parsedRecord: PersistedMatching = JSON.parse(record);
const matchingEntity: MatchingEntity = new MatchingEntity({
id: parsedRecord.id,
createdAt: new Date(parsedRecord.createdAt),
updatedAt: new Date(parsedRecord.updatedAt),
props: {
query: parsedRecord.query,
matches: parsedRecord.matches.map((match: PersistedMatch) =>
MatchEntity.create({
adId: match.adId,
role: match.role,
frequency: match.frequency,
distance: match.distance,
duration: match.duration,
initialDistance: match.initialDistance,
initialDuration: match.initialDuration,
journeys: match.journeys.map(
(journey: PersistedJourney) =>
new Journey({
firstDate: new Date(journey.firstDate),
lastDate: new Date(journey.lastDate),
journeyItems: journey.journeyItems.map(
(journeyItem: PersistedJourneyItem) =>
new JourneyItem({
lon: journeyItem.lon,
lat: journeyItem.lat,
duration: journeyItem.duration,
distance: journeyItem.distance,
actorTimes: journeyItem.actorTimes.map(
(actorTime: PersistedActorTime) =>
new ActorTime({
role: actorTime.role,
target: actorTime.target,
firstDatetime: new Date(actorTime.firstDatetime),
firstMinDatetime: new Date(
actorTime.firstMinDatetime,
),
firstMaxDatetime: new Date(
actorTime.firstMaxDatetime,
),
lastDatetime: new Date(actorTime.lastDatetime),
lastMinDatetime: new Date(
actorTime.lastMinDatetime,
),
lastMaxDatetime: new Date(
actorTime.lastMaxDatetime,
),
}),
),
}),
),
}),
),
}),
),
},
});
return matchingEntity;
};
} }
type PersistedMatching = {
id: string;
createdAt: string;
updatedAt: string;
matches: PersistedMatch[];
query: {
driver: boolean;
passenger: boolean;
frequency: Frequency;
fromDate: string;
toDate: string;
schedule: {
day: number;
time: string;
margin: number;
}[];
seatsProposed: number;
seatsRequested: number;
strict: boolean;
waypoints: {
houseNumber: string;
street: string;
postalCode: string;
locality: string;
lon: number;
lat: number;
country: string;
position: number;
}[];
algorithmType: string;
remoteness: number;
useProportion: boolean;
proportion: number;
useAzimuth: boolean;
azimuthMargin: number;
maxDetourDistanceRatio: number;
maxDetourDurationRatio: number;
};
};
type PersistedMatch = {
adId: string;
role: Role;
frequency: Frequency;
distance: number;
duration: number;
initialDistance: number;
initialDuration: number;
journeys: PersistedJourney[];
};
type PersistedJourney = {
firstDate: string;
lastDate: string;
journeyItems: PersistedJourneyItem[];
};
type PersistedJourneyItem = {
lon: number;
lat: number;
duration: number;
distance: number;
actorTimes: PersistedActorTime[];
};
type PersistedActorTime = {
role: Role;
target: Target;
firstDatetime: string;
firstMinDatetime: string;
firstMaxDatetime: string;
lastDatetime: string;
lastMinDatetime: string;
lastMaxDatetime: string;
};

View File

@ -16,6 +16,8 @@ import {
import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types'; import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
import { Waypoint } from '@modules/ad/core/application/types/waypoint.type'; import { Waypoint } from '@modules/ad/core/application/types/waypoint.type';
import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
import { Target } from '@modules/ad/core/domain/candidate.types';
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
import { MatchingEntity } from '@modules/ad/core/domain/matching.entity'; import { MatchingEntity } from '@modules/ad/core/domain/matching.entity';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
@ -76,7 +78,156 @@ const mockAdRepository = {
}; };
const mockMatchingRepository: MatchingRepositoryPort = { const mockMatchingRepository: MatchingRepositoryPort = {
get: jest.fn(), get: jest
.fn()
.mockImplementationOnce(
() =>
new MatchingEntity({
id: 'a3b10efb-121e-4d08-9198-9f57afdb5e2d',
createdAt: new Date('2023-08-20T09:48:00Z'),
updatedAt: new Date('2023-08-20T09:48:00Z'),
props: {
matches: [
new MatchEntity({
id: '4bd4e90b-ffba-4f5f-b904-48ad0667a1d7',
createdAt: new Date('2023-08-30T08:45:00Z'),
updatedAt: new Date('2023-08-30T08:45:00Z'),
props: {
adId: 'dd937edf-1264-4868-b073-d1952abe30b1',
role: Role.DRIVER,
frequency: Frequency.PUNCTUAL,
distance: 356041,
duration: 12647,
initialDistance: 348745,
initialDuration: 12105,
distanceDetour: 7296,
durationDetour: 542,
distanceDetourPercentage: 4.1,
durationDetourPercentage: 3.8,
journeys: [
{
firstDate: new Date('2023-08-28'),
lastDate: new Date('2023-08-28'),
journeyItems: [
{
lon: 6.389745,
lat: 48.32644,
duration: 0,
distance: 0,
actorTimes: [
{
role: Role.DRIVER,
target: Target.START,
firstDatetime: new Date('2023-08-28T07:00:00Z'),
firstMinDatetime: new Date(
'2023-08-28T06:45:00Z',
),
firstMaxDatetime: new Date(
'2023-08-28T07:15:00Z',
),
lastDatetime: new Date('2023-08-28T07:00:00Z'),
lastMinDatetime: new Date('2023-08-28T06:45:00Z'),
lastMaxDatetime: new Date('2023-08-28T07:15:00Z'),
},
{
role: Role.PASSENGER,
target: Target.START,
firstDatetime: new Date('2023-08-28T07:00:00Z'),
firstMinDatetime: new Date(
'2023-08-28T06:45:00Z',
),
firstMaxDatetime: new Date(
'2023-08-28T07:15:00Z',
),
lastDatetime: new Date('2023-08-28T07:00:00Z'),
lastMinDatetime: new Date('2023-08-28T06:45:00Z'),
lastMaxDatetime: new Date('2023-08-28T07:15:00Z'),
},
],
},
{
lon: 6.984567,
lat: 48.021548,
distance: 356041,
duration: 12647,
actorTimes: [
{
role: Role.DRIVER,
target: Target.FINISH,
firstDatetime: new Date('2023-08-28T07:00:00Z'),
firstMinDatetime: new Date(
'2023-08-28T06:45:00Z',
),
firstMaxDatetime: new Date(
'2023-08-28T07:15:00Z',
),
lastDatetime: new Date('2023-08-28T07:00:00Z'),
lastMinDatetime: new Date('2023-08-28T06:45:00Z'),
lastMaxDatetime: new Date('2023-08-28T07:15:00Z'),
},
{
role: Role.PASSENGER,
target: Target.FINISH,
firstDatetime: new Date('2023-08-28T07:00:00Z'),
firstMinDatetime: new Date(
'2023-08-28T06:45:00Z',
),
firstMaxDatetime: new Date(
'2023-08-28T07:15:00Z',
),
lastDatetime: new Date('2023-08-28T07:00:00Z'),
lastMinDatetime: new Date('2023-08-28T06:45:00Z'),
lastMaxDatetime: new Date('2023-08-28T07:15:00Z'),
},
],
},
],
},
],
},
}),
],
query: {
driver: false,
passenger: true,
frequency: Frequency.PUNCTUAL,
fromDate: '2023-08-28',
toDate: '2023-08-28',
schedule: [
{
day: 1,
time: '06:40',
margin: 900,
},
],
seatsProposed: 3,
seatsRequested: 1,
strict: true,
waypoints: [
{
lon: 6.389745,
lat: 48.32644,
},
{
lon: 6.984567,
lat: 48.021548,
},
],
algorithmType: 'PASSENGER_ORIENTED',
remoteness: 15000,
useProportion: true,
proportion: 0.3,
useAzimuth: true,
azimuthMargin: 10,
maxDetourDistanceRatio: 0.3,
maxDetourDurationRatio: 0.3,
},
},
}),
)
.mockImplementationOnce(() => {
throw new Error();
}),
save: jest.fn(), save: jest.fn(),
}; };
@ -151,6 +302,10 @@ describe('Match Query Handler', () => {
matchQueryHandler = module.get<MatchQueryHandler>(MatchQueryHandler); matchQueryHandler = module.get<MatchQueryHandler>(MatchQueryHandler);
}); });
afterEach(async () => {
jest.clearAllMocks();
});
it('should be defined', () => { it('should be defined', () => {
expect(matchQueryHandler).toBeDefined(); expect(matchQueryHandler).toBeDefined();
}); });
@ -183,4 +338,64 @@ describe('Match Query Handler', () => {
expect(matching.id).toHaveLength(36); expect(matching.id).toHaveLength(36);
expect(MatchingEntity.create).toHaveBeenCalledTimes(1); expect(MatchingEntity.create).toHaveBeenCalledTimes(1);
}); });
it('should return a valid saved Matching', async () => {
jest.spyOn(MatchingEntity, 'create');
const matchQuery = new MatchQuery(
{
id: 'a3b10efb-121e-4d08-9198-9f57afdb5e2d',
algorithmType: AlgorithmType.PASSENGER_ORIENTED,
driver: false,
passenger: true,
frequency: Frequency.PUNCTUAL,
fromDate: '2023-08-28',
toDate: '2023-08-28',
schedule: [
{
time: '07:05',
day: 1,
margin: 900,
},
],
strict: false,
waypoints: [originWaypoint, destinationWaypoint],
},
mockRouteProvider,
);
const matching: MatchingResult = await matchQueryHandler.execute(
matchQuery,
);
expect(matching.id).toBe('a3b10efb-121e-4d08-9198-9f57afdb5e2d');
expect(MatchingEntity.create).toHaveBeenCalledTimes(0);
});
it('should return a new matching if saved Matching is not found', async () => {
jest.spyOn(MatchingEntity, 'create');
const matchQuery = new MatchQuery(
{
id: 'a3b10efb-121e-4d08-9198-9f57afdb5e2d',
algorithmType: AlgorithmType.PASSENGER_ORIENTED,
driver: false,
passenger: true,
frequency: Frequency.PUNCTUAL,
fromDate: '2023-08-28',
toDate: '2023-08-28',
schedule: [
{
time: '07:05',
day: 1,
margin: 900,
},
],
strict: false,
waypoints: [originWaypoint, destinationWaypoint],
},
mockRouteProvider,
);
const matching: MatchingResult = await matchQueryHandler.execute(
matchQuery,
);
expect(matching.id).toHaveLength(36);
expect(MatchingEntity.create).toHaveBeenCalledTimes(1);
});
}); });

View File

@ -1,6 +1,7 @@
import { getRedisToken } from '@liaoliaots/nestjs-redis'; import { getRedisToken } from '@liaoliaots/nestjs-redis';
import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
import { Target } from '@modules/ad/core/domain/candidate.types'; import { Target } from '@modules/ad/core/domain/candidate.types';
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
import { MatchingEntity } from '@modules/ad/core/domain/matching.entity'; import { MatchingEntity } from '@modules/ad/core/domain/matching.entity';
import { MatchingNotFoundException } from '@modules/ad/core/domain/matching.errors'; import { MatchingNotFoundException } from '@modules/ad/core/domain/matching.errors';
import { MatchingRepository } from '@modules/ad/infrastructure/matching.repository'; import { MatchingRepository } from '@modules/ad/infrastructure/matching.repository';
@ -52,7 +53,7 @@ const matchingEntity: MatchingEntity = new MatchingEntity({
updatedAt: new Date(), updatedAt: new Date(),
props: { props: {
matches: [ matches: [
{ MatchEntity.create({
adId: 'dd937edf-1264-4868-b073-d1952abe30b1', adId: 'dd937edf-1264-4868-b073-d1952abe30b1',
role: Role.DRIVER, role: Role.DRIVER,
frequency: Frequency.PUNCTUAL, frequency: Frequency.PUNCTUAL,
@ -60,10 +61,6 @@ const matchingEntity: MatchingEntity = new MatchingEntity({
duration: 12647, duration: 12647,
initialDistance: 348745, initialDistance: 348745,
initialDuration: 12105, initialDuration: 12105,
distanceDetour: 7296,
durationDetour: 542,
distanceDetourPercentage: 4.1,
durationDetourPercentage: 3.8,
journeys: [ journeys: [
{ {
firstDate: new Date('2023-09-01'), firstDate: new Date('2023-09-01'),
@ -91,7 +88,7 @@ const matchingEntity: MatchingEntity = new MatchingEntity({
}, },
], ],
// ... // ...
}, }),
], ],
query: { query: {
driver: false, driver: false,

View File

@ -1,5 +1,6 @@
import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
import { Target } from '@modules/ad/core/domain/candidate.types'; import { Target } from '@modules/ad/core/domain/candidate.types';
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
import { MatchingEntity } from '@modules/ad/core/domain/matching.entity'; import { MatchingEntity } from '@modules/ad/core/domain/matching.entity';
import { MatchingMapper } from '@modules/ad/matching.mapper'; import { MatchingMapper } from '@modules/ad/matching.mapper';
import { Test } from '@nestjs/testing'; import { Test } from '@nestjs/testing';
@ -25,46 +26,88 @@ describe('Matching Mapper', () => {
updatedAt: new Date('2023-08-20T09:48:00Z'), updatedAt: new Date('2023-08-20T09:48:00Z'),
props: { props: {
matches: [ matches: [
{ new MatchEntity({
adId: 'dd937edf-1264-4868-b073-d1952abe30b1', id: '4bd4e90b-ffba-4f5f-b904-48ad0667a1d7',
role: Role.DRIVER, createdAt: new Date('2023-08-30T08:45:00Z'),
frequency: Frequency.PUNCTUAL, updatedAt: new Date('2023-08-30T08:45:00Z'),
distance: 356041, props: {
duration: 12647, adId: 'dd937edf-1264-4868-b073-d1952abe30b1',
initialDistance: 348745, role: Role.DRIVER,
initialDuration: 12105, frequency: Frequency.PUNCTUAL,
distanceDetour: 7296, distance: 356041,
durationDetour: 542, duration: 12647,
distanceDetourPercentage: 4.1, initialDistance: 348745,
durationDetourPercentage: 3.8, initialDuration: 12105,
journeys: [ distanceDetour: 7296,
{ durationDetour: 542,
firstDate: new Date('2023-09-01'), distanceDetourPercentage: 4.1,
lastDate: new Date('2023-09-01'), durationDetourPercentage: 3.8,
journeyItems: [ journeys: [
{ {
lon: 6.35484, firstDate: new Date('2023-09-01'),
lat: 48.26587, lastDate: new Date('2023-09-01'),
duration: 0, journeyItems: [
distance: 0, {
actorTimes: [ lon: 6.389745,
{ lat: 48.32644,
role: Role.DRIVER, duration: 0,
target: Target.START, distance: 0,
firstDatetime: new Date('2023-09-01T07:00:00Z'), actorTimes: [
firstMinDatetime: new Date('2023-09-01T06:45:00Z'), {
firstMaxDatetime: new Date('2023-09-01T07:15:00Z'), role: Role.DRIVER,
lastDatetime: new Date('2023-09-01T07:00:00Z'), target: Target.START,
lastMinDatetime: new Date('2023-09-01T06:45:00Z'), firstDatetime: new Date('2023-09-01T07:00:00Z'),
lastMaxDatetime: new Date('2023-09-01T07:15:00Z'), firstMinDatetime: new Date('2023-09-01T06:45:00Z'),
}, firstMaxDatetime: new Date('2023-09-01T07:15:00Z'),
], lastDatetime: new Date('2023-09-01T07:00:00Z'),
}, lastMinDatetime: new Date('2023-09-01T06:45:00Z'),
], lastMaxDatetime: new Date('2023-09-01T07:15:00Z'),
}, },
], {
// ... role: Role.PASSENGER,
}, target: Target.START,
firstDatetime: new Date('2023-09-01T07:00:00Z'),
firstMinDatetime: new Date('2023-09-01T06:45:00Z'),
firstMaxDatetime: new Date('2023-09-01T07:15:00Z'),
lastDatetime: new Date('2023-09-01T07:00:00Z'),
lastMinDatetime: new Date('2023-09-01T06:45:00Z'),
lastMaxDatetime: new Date('2023-09-01T07:15:00Z'),
},
],
},
{
lon: 6.984567,
lat: 48.021548,
distance: 356041,
duration: 12647,
actorTimes: [
{
role: Role.DRIVER,
target: Target.FINISH,
firstDatetime: new Date('2023-09-01T07:00:00Z'),
firstMinDatetime: new Date('2023-09-01T06:45:00Z'),
firstMaxDatetime: new Date('2023-09-01T07:15:00Z'),
lastDatetime: new Date('2023-09-01T07:00:00Z'),
lastMinDatetime: new Date('2023-09-01T06:45:00Z'),
lastMaxDatetime: new Date('2023-09-01T07:15:00Z'),
},
{
role: Role.PASSENGER,
target: Target.FINISH,
firstDatetime: new Date('2023-09-01T07:00:00Z'),
firstMinDatetime: new Date('2023-09-01T06:45:00Z'),
firstMaxDatetime: new Date('2023-09-01T07:15:00Z'),
lastDatetime: new Date('2023-09-01T07:00:00Z'),
lastMinDatetime: new Date('2023-09-01T06:45:00Z'),
lastMaxDatetime: new Date('2023-09-01T07:15:00Z'),
},
],
},
],
},
],
},
}),
], ],
query: { query: {
driver: false, driver: false,
@ -105,14 +148,17 @@ describe('Matching Mapper', () => {
}); });
const mapped: string = matchingMapper.toPersistence(matchingEntity); const mapped: string = matchingMapper.toPersistence(matchingEntity);
expect(mapped).toBe( expect(mapped).toBe(
'{"_id":"644a7cb3-6436-4db5-850d-b4c7421d4b97","_createdAt":"2023-08-20T09:48:00.000Z","_updatedAt":"2023-08-20T09:48:00.000Z","props":{"matches":[{"adId":"dd937edf-1264-4868-b073-d1952abe30b1","role":"DRIVER","frequency":"PUNCTUAL","distance":356041,"duration":12647,"initialDistance":348745,"initialDuration":12105,"distanceDetour":7296,"durationDetour":542,"distanceDetourPercentage":4.1,"durationDetourPercentage":3.8,"journeys":[{"firstDate":"2023-09-01T00:00:00.000Z","lastDate":"2023-09-01T00:00:00.000Z","journeyItems":[{"lon":6.35484,"lat":48.26587,"duration":0,"distance":0,"actorTimes":[{"role":"DRIVER","target":"START","firstDatetime":"2023-09-01T07:00:00.000Z","firstMinDatetime":"2023-09-01T06:45:00.000Z","firstMaxDatetime":"2023-09-01T07:15:00.000Z","lastDatetime":"2023-09-01T07:00:00.000Z","lastMinDatetime":"2023-09-01T06:45:00.000Z","lastMaxDatetime":"2023-09-01T07:15:00.000Z"}]}]}]}],"query":{"driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-09-01","toDate":"2023-09-01","schedule":[{"day":5,"time":"06:40","margin":900}],"seatsProposed":3,"seatsRequested":1,"strict":true,"waypoints":[{"lon":6.389745,"lat":48.32644},{"lon":6.984567,"lat":48.021548}],"algorithmType":"PASSENGER_ORIENTED","remoteness":15000,"useProportion":true,"proportion":0.3,"useAzimuth":true,"azimuthMargin":10,"maxDetourDistanceRatio":0.3,"maxDetourDurationRatio":0.3}},"_domainEvents":[]}', '{"id":"644a7cb3-6436-4db5-850d-b4c7421d4b97","createdAt":"2023-08-20T09:48:00.000Z","updatedAt":"2023-08-20T09:48:00.000Z","matches":[{"adId":"dd937edf-1264-4868-b073-d1952abe30b1","role":"DRIVER","frequency":"PUNCTUAL","distance":356041,"duration":12647,"initialDistance":348745,"initialDuration":12105,"distanceDetour":7296,"durationDetour":542,"distanceDetourPercentage":4.1,"durationDetourPercentage":3.8,"journeys":[{"firstDate":"2023-09-01T00:00:00.000Z","lastDate":"2023-09-01T00:00:00.000Z","journeyItems":[{"lon":6.389745,"lat":48.32644,"duration":0,"distance":0,"actorTimes":[{"role":"DRIVER","target":"START","firstDatetime":"2023-09-01T07:00:00.000Z","firstMinDatetime":"2023-09-01T06:45:00.000Z","firstMaxDatetime":"2023-09-01T07:15:00.000Z","lastDatetime":"2023-09-01T07:00:00.000Z","lastMinDatetime":"2023-09-01T06:45:00.000Z","lastMaxDatetime":"2023-09-01T07:15:00.000Z"},{"role":"PASSENGER","target":"START","firstDatetime":"2023-09-01T07:00:00.000Z","firstMinDatetime":"2023-09-01T06:45:00.000Z","firstMaxDatetime":"2023-09-01T07:15:00.000Z","lastDatetime":"2023-09-01T07:00:00.000Z","lastMinDatetime":"2023-09-01T06:45:00.000Z","lastMaxDatetime":"2023-09-01T07:15:00.000Z"}]},{"lon":6.984567,"lat":48.021548,"duration":12647,"distance":356041,"actorTimes":[{"role":"DRIVER","target":"FINISH","firstDatetime":"2023-09-01T07:00:00.000Z","firstMinDatetime":"2023-09-01T06:45:00.000Z","firstMaxDatetime":"2023-09-01T07:15:00.000Z","lastDatetime":"2023-09-01T07:00:00.000Z","lastMinDatetime":"2023-09-01T06:45:00.000Z","lastMaxDatetime":"2023-09-01T07:15:00.000Z"},{"role":"PASSENGER","target":"FINISH","firstDatetime":"2023-09-01T07:00:00.000Z","firstMinDatetime":"2023-09-01T06:45:00.000Z","firstMaxDatetime":"2023-09-01T07:15:00.000Z","lastDatetime":"2023-09-01T07:00:00.000Z","lastMinDatetime":"2023-09-01T06:45:00.000Z","lastMaxDatetime":"2023-09-01T07:15:00.000Z"}]}]}]}],"query":{"driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-09-01","toDate":"2023-09-01","schedule":[{"day":5,"time":"06:40","margin":900}],"seatsProposed":3,"seatsRequested":1,"strict":true,"waypoints":[{"lon":6.389745,"lat":48.32644},{"lon":6.984567,"lat":48.021548}],"algorithmType":"PASSENGER_ORIENTED","remoteness":15000,"useProportion":true,"proportion":0.3,"useAzimuth":true,"azimuthMargin":10,"maxDetourDistanceRatio":0.3,"maxDetourDurationRatio":0.3}}',
); );
}); });
it('should map persisted string to domain entity', async () => { it('should map persisted string to domain entity', async () => {
const matchingEntity: MatchingEntity = matchingMapper.toDomain( const matchingEntity: MatchingEntity = matchingMapper.toDomain(
'{"_id":"644a7cb3-6436-4db5-850d-b4c7421d4b97","_createdAt":"2023-08-20T09:48:00.000Z","_updatedAt":"2023-08-20T09:48:00.000Z","props":{"matches":[{"adId":"dd937edf-1264-4868-b073-d1952abe30b1","role":"DRIVER","frequency":"PUNCTUAL","distance":356041,"duration":12647,"initialDistance":348745,"initialDuration":12105,"distanceDetour":7296,"durationDetour":542,"distanceDetourPercentage":4.1,"durationDetourPercentage":3.8,"journeys":[{"firstDate":"2023-09-01T00:00:00.000Z","lastDate":"2023-09-01T00:00:00.000Z","journeyItems":[{"lon":6.35484,"lat":48.26587,"duration":0,"distance":0,"actorTimes":[{"role":"DRIVER","target":"START","firstDatetime":"2023-09-01T07:00:00.000Z","firstMinDatetime":"2023-09-01T06:45:00.000Z","firstMaxDatetime":"2023-09-01T07:15:00.000Z","lastDatetime":"2023-09-01T07:00:00.000Z","lastMinDatetime":"2023-09-01T06:45:00.000Z","lastMaxDatetime":"2023-09-01T07:15:00.000Z"}]}]}]}],"query":{"driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-09-01","toDate":"2023-09-01","schedule":[{"day":5,"time":"06:40","margin":900}],"seatsProposed":3,"seatsRequested":1,"strict":true,"waypoints":[{"lon":6.389745,"lat":48.32644},{"lon":6.984567,"lat":48.021548}],"algorithmType":"PASSENGER_ORIENTED","remoteness":15000,"useProportion":true,"proportion":0.3,"useAzimuth":true,"azimuthMargin":10,"maxDetourDistanceRatio":0.3,"maxDetourDurationRatio":0.3}},"_domainEvents":[]}', '{"id":"644a7cb3-6436-4db5-850d-b4c7421d4b97","createdAt":"2023-08-20T09:48:00.000Z","updatedAt":"2023-08-20T09:48:00.000Z","matches":[{"adId":"dd937edf-1264-4868-b073-d1952abe30b1","role":"DRIVER","frequency":"PUNCTUAL","distance":356041,"duration":12647,"initialDistance":348745,"initialDuration":12105,"distanceDetour":7296,"durationDetour":542,"distanceDetourPercentage":4.1,"durationDetourPercentage":3.8,"journeys":[{"firstDate":"2023-09-01T00:00:00.000Z","lastDate":"2023-09-01T00:00:00.000Z","journeyItems":[{"lon":6.389745,"lat":48.32644,"duration":0,"distance":0,"actorTimes":[{"role":"DRIVER","target":"START","firstDatetime":"2023-09-01T07:00:00.000Z","firstMinDatetime":"2023-09-01T06:45:00.000Z","firstMaxDatetime":"2023-09-01T07:15:00.000Z","lastDatetime":"2023-09-01T07:00:00.000Z","lastMinDatetime":"2023-09-01T06:45:00.000Z","lastMaxDatetime":"2023-09-01T07:15:00.000Z"},{"role":"PASSENGER","target":"START","firstDatetime":"2023-09-01T07:00:00.000Z","firstMinDatetime":"2023-09-01T06:45:00.000Z","firstMaxDatetime":"2023-09-01T07:15:00.000Z","lastDatetime":"2023-09-01T07:00:00.000Z","lastMinDatetime":"2023-09-01T06:45:00.000Z","lastMaxDatetime":"2023-09-01T07:15:00.000Z"}]},{"lon":6.984567,"lat":48.021548,"duration":12647,"distance":356041,"actorTimes":[{"role":"DRIVER","target":"FINISH","firstDatetime":"2023-09-01T07:00:00.000Z","firstMinDatetime":"2023-09-01T06:45:00.000Z","firstMaxDatetime":"2023-09-01T07:15:00.000Z","lastDatetime":"2023-09-01T07:00:00.000Z","lastMinDatetime":"2023-09-01T06:45:00.000Z","lastMaxDatetime":"2023-09-01T07:15:00.000Z"},{"role":"PASSENGER","target":"FINISH","firstDatetime":"2023-09-01T07:00:00.000Z","firstMinDatetime":"2023-09-01T06:45:00.000Z","firstMaxDatetime":"2023-09-01T07:15:00.000Z","lastDatetime":"2023-09-01T07:00:00.000Z","lastMinDatetime":"2023-09-01T06:45:00.000Z","lastMaxDatetime":"2023-09-01T07:15:00.000Z"}]}]}]}],"query":{"driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-09-01","toDate":"2023-09-01","schedule":[{"day":5,"time":"06:40","margin":900}],"seatsProposed":3,"seatsRequested":1,"strict":true,"waypoints":[{"lon":6.389745,"lat":48.32644},{"lon":6.984567,"lat":48.021548}],"algorithmType":"PASSENGER_ORIENTED","remoteness":15000,"useProportion":true,"proportion":0.3,"useAzimuth":true,"azimuthMargin":10,"maxDetourDistanceRatio":0.3,"maxDetourDurationRatio":0.3}}',
); );
expect(matchingEntity.getProps().query.fromDate).toBe('2023-09-01'); expect(matchingEntity.getProps().query.fromDate).toBe('2023-09-01');
expect(matchingEntity.getProps().matches[0].getProps().adId).toBe(
'dd937edf-1264-4868-b073-d1952abe30b1',
);
}); });
}); });