matcher with only db selection
This commit is contained in:
parent
e0030aba73
commit
98530af14a
|
@ -54,7 +54,7 @@ export class MatchQueryHandler implements IQueryHandler {
|
||||||
maxDetourDurationRatio: this._defaultParams.MAX_DETOUR_DURATION_RATIO,
|
maxDetourDurationRatio: this._defaultParams.MAX_DETOUR_DURATION_RATIO,
|
||||||
})
|
})
|
||||||
.setDatesAndSchedule(this.datetimeTransformer);
|
.setDatesAndSchedule(this.datetimeTransformer);
|
||||||
await query.setRoutes(this.routeProvider);
|
await query.setRoute(this.routeProvider);
|
||||||
|
|
||||||
let algorithm: Algorithm;
|
let algorithm: Algorithm;
|
||||||
switch (query.algorithmType) {
|
switch (query.algorithmType) {
|
||||||
|
|
|
@ -112,9 +112,10 @@ export class MatchQuery extends QueryBase {
|
||||||
setDatesAndSchedule = (
|
setDatesAndSchedule = (
|
||||||
datetimeTransformer: DateTimeTransformerPort,
|
datetimeTransformer: DateTimeTransformerPort,
|
||||||
): MatchQuery => {
|
): MatchQuery => {
|
||||||
|
const initialFromDate: string = this.fromDate;
|
||||||
this.fromDate = datetimeTransformer.fromDate(
|
this.fromDate = datetimeTransformer.fromDate(
|
||||||
{
|
{
|
||||||
date: this.fromDate,
|
date: initialFromDate,
|
||||||
time: this.schedule[0].time,
|
time: this.schedule[0].time,
|
||||||
coordinates: {
|
coordinates: {
|
||||||
lon: this.waypoints[0].lon,
|
lon: this.waypoints[0].lon,
|
||||||
|
@ -126,7 +127,7 @@ export class MatchQuery extends QueryBase {
|
||||||
this.toDate = datetimeTransformer.toDate(
|
this.toDate = datetimeTransformer.toDate(
|
||||||
this.toDate,
|
this.toDate,
|
||||||
{
|
{
|
||||||
date: this.fromDate,
|
date: initialFromDate,
|
||||||
time: this.schedule[0].time,
|
time: this.schedule[0].time,
|
||||||
coordinates: {
|
coordinates: {
|
||||||
lon: this.waypoints[0].lon,
|
lon: this.waypoints[0].lon,
|
||||||
|
@ -164,7 +165,7 @@ export class MatchQuery extends QueryBase {
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
setRoutes = async (routeProvider: RouteProviderPort): Promise<MatchQuery> => {
|
setRoute = async (routeProvider: RouteProviderPort): Promise<MatchQuery> => {
|
||||||
const roles: Role[] = [];
|
const roles: Role[] = [];
|
||||||
if (this.driver) roles.push(Role.DRIVER);
|
if (this.driver) roles.push(Role.DRIVER);
|
||||||
if (this.passenger) roles.push(Role.PASSENGER);
|
if (this.passenger) roles.push(Role.PASSENGER);
|
||||||
|
@ -177,7 +178,7 @@ export class MatchQuery extends QueryBase {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScheduleItem = {
|
export type ScheduleItem = {
|
||||||
day?: number;
|
day?: number;
|
||||||
time: string;
|
time: string;
|
||||||
margin?: number;
|
margin?: number;
|
||||||
|
|
|
@ -2,22 +2,24 @@ import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
|
||||||
import { Candidate } from '../../../types/algorithm.types';
|
import { Candidate } from '../../../types/algorithm.types';
|
||||||
import { Selector } from '../algorithm.abstract';
|
import { Selector } from '../algorithm.abstract';
|
||||||
import { AdReadModel } from '@modules/ad/infrastructure/ad.repository';
|
import { AdReadModel } from '@modules/ad/infrastructure/ad.repository';
|
||||||
|
import { ScheduleItem } from '../match.query';
|
||||||
|
import { Waypoint } from '../../../types/waypoint.type';
|
||||||
|
import { Coordinates } from '../../../types/coordinates.type';
|
||||||
|
|
||||||
export class PassengerOrientedSelector extends Selector {
|
export class PassengerOrientedSelector extends Selector {
|
||||||
select = async (): Promise<Candidate[]> => {
|
select = async (): Promise<Candidate[]> => {
|
||||||
const queryStringRoles: QueryStringRole[] = [];
|
const queryStringRoles: QueryStringRole[] = [];
|
||||||
if (this.query.driver)
|
if (this.query.driver)
|
||||||
queryStringRoles.push({
|
queryStringRoles.push({
|
||||||
query: this.createQueryString(Role.DRIVER),
|
query: this._createQueryString(Role.DRIVER),
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
});
|
});
|
||||||
if (this.query.passenger)
|
if (this.query.passenger)
|
||||||
queryStringRoles.push({
|
queryStringRoles.push({
|
||||||
query: this.createQueryString(Role.PASSENGER),
|
query: this._createQueryString(Role.PASSENGER),
|
||||||
role: Role.PASSENGER,
|
role: Role.PASSENGER,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(queryStringRoles);
|
|
||||||
return (
|
return (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
queryStringRoles.map<Promise<AdsRole>>(
|
queryStringRoles.map<Promise<AdsRole>>(
|
||||||
|
@ -43,66 +45,251 @@ export class PassengerOrientedSelector extends Selector {
|
||||||
.flat();
|
.flat();
|
||||||
};
|
};
|
||||||
|
|
||||||
private createQueryString = (role: Role): string =>
|
private _createQueryString = (role: Role): string =>
|
||||||
[
|
[
|
||||||
this.createSelect(role),
|
this._createSelect(role),
|
||||||
this.createFrom(),
|
this._createFrom(),
|
||||||
'WHERE',
|
'WHERE',
|
||||||
this.createWhere(role),
|
this._createWhere(role),
|
||||||
].join(' ');
|
]
|
||||||
|
.join(' ')
|
||||||
|
.replace(/\s+/g, ' '); // remove duplicate spaces for easy debug !
|
||||||
|
|
||||||
private createSelect = (role: Role): string =>
|
private _createSelect = (role: Role): string =>
|
||||||
[
|
[
|
||||||
`SELECT
|
`SELECT \
|
||||||
ad.uuid,driver,passenger,frequency,public.st_astext(matcher.ad.waypoints) as waypoints,
|
ad.uuid,driver,passenger,frequency,public.st_astext(ad.waypoints) as waypoints,\
|
||||||
"fromDate","toDate",
|
"fromDate","toDate",\
|
||||||
"seatsProposed","seatsRequested",
|
"seatsProposed","seatsRequested",\
|
||||||
strict,
|
strict,\
|
||||||
"fwdAzimuth","backAzimuth",
|
"fwdAzimuth","backAzimuth",\
|
||||||
si.day,si.time,si.margin`,
|
si.day,si.time,si.margin`,
|
||||||
role == Role.DRIVER ? this.selectAsDriver() : this.selectAsPassenger(),
|
role == Role.DRIVER ? this._selectAsDriver() : this._selectAsPassenger(),
|
||||||
].join();
|
].join();
|
||||||
|
|
||||||
private selectAsDriver = (): string =>
|
private _selectAsDriver = (): string =>
|
||||||
`${this.query.route?.driverDuration} as duration,${this.query.route?.driverDistance} as distance`;
|
`${this.query.route?.driverDuration} as duration,${this.query.route?.driverDistance} as distance`;
|
||||||
|
|
||||||
private selectAsPassenger = (): string =>
|
private _selectAsPassenger = (): string =>
|
||||||
`"driverDuration" as duration,"driverDistance" as distance`;
|
`"driverDuration" as duration,"driverDistance" as distance`;
|
||||||
|
|
||||||
private createFrom = (): string =>
|
private _createFrom = (): string =>
|
||||||
'FROM ad LEFT JOIN schedule_item si ON ad.uuid = si."adUuid"';
|
'FROM ad LEFT JOIN schedule_item si ON ad.uuid = si."adUuid"';
|
||||||
|
|
||||||
private createWhere = (role: Role): string =>
|
private _createWhere = (role: Role): string =>
|
||||||
[this.whereRole(role), this.whereStrict(), this.whereDate()].join(' AND ');
|
[
|
||||||
|
this._whereRole(role),
|
||||||
|
this._whereStrict(),
|
||||||
|
this._whereDate(),
|
||||||
|
this._whereSchedule(role),
|
||||||
|
this._whereAzimuth(),
|
||||||
|
this._whereProportion(role),
|
||||||
|
this._whereRemoteness(role),
|
||||||
|
]
|
||||||
|
.filter((where: string) => where != '')
|
||||||
|
.join(' AND ');
|
||||||
|
|
||||||
private whereRole = (role: Role): string =>
|
private _whereRole = (role: Role): string =>
|
||||||
role == Role.PASSENGER ? 'driver=True' : 'passenger=True';
|
role == Role.PASSENGER ? 'driver=True' : 'passenger=True';
|
||||||
|
|
||||||
private whereStrict = (): string =>
|
private _whereStrict = (): string =>
|
||||||
this.query.strict
|
this.query.strict
|
||||||
? this.query.frequency == Frequency.PUNCTUAL
|
? this.query.frequency == Frequency.PUNCTUAL
|
||||||
? `frequency='${Frequency.PUNCTUAL}'`
|
? `frequency='${Frequency.PUNCTUAL}'`
|
||||||
: `frequency='${Frequency.RECURRENT}'`
|
: `frequency='${Frequency.RECURRENT}'`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
private whereDate = (): string => {
|
private _whereDate = (): string =>
|
||||||
const whereDate = `(
|
`(\
|
||||||
(
|
(\
|
||||||
"fromDate" <= '${this.query.fromDate}' and "fromDate" <= '${this.query.toDate}' and
|
"fromDate" <= '${this.query.fromDate}' AND "fromDate" <= '${this.query.toDate}' AND\
|
||||||
"toDate" >= '${this.query.toDate}' and "toDate" >= '${this.query.fromDate}'
|
"toDate" >= '${this.query.toDate}' AND "toDate" >= '${this.query.fromDate}'\
|
||||||
) OR (
|
) OR (\
|
||||||
"fromDate" >= '${this.query.fromDate}' and "fromDate" <= '${this.query.toDate}' and
|
"fromDate" >= '${this.query.fromDate}' AND "fromDate" <= '${this.query.toDate}' AND\
|
||||||
"toDate" <= '${this.query.toDate}' and "toDate" >= '${this.query.fromDate}'
|
"toDate" <= '${this.query.toDate}' AND "toDate" >= '${this.query.fromDate}'\
|
||||||
) OR (
|
) OR (\
|
||||||
"fromDate" <= '${this.query.fromDate}' and "fromDate" <= '${this.query.toDate}' and
|
"fromDate" <= '${this.query.fromDate}' AND "fromDate" <= '${this.query.toDate}' AND\
|
||||||
"toDate" <= '${this.query.toDate}' and "toDate" >= '${this.query.fromDate}'
|
"toDate" <= '${this.query.toDate}' AND "toDate" >= '${this.query.fromDate}'\
|
||||||
) OR (
|
) OR (\
|
||||||
"fromDate" >= '${this.query.fromDate}' and "fromDate" <= '${this.query.toDate}' and
|
"fromDate" >= '${this.query.fromDate}' AND "fromDate" <= '${this.query.toDate}' AND\
|
||||||
"toDate" >= '${this.query.toDate}' and "toDate" >= '${this.query.fromDate}'
|
"toDate" >= '${this.query.toDate}' AND "toDate" >= '${this.query.fromDate}'\
|
||||||
)
|
)\
|
||||||
)`;
|
)`;
|
||||||
return whereDate;
|
|
||||||
|
private _whereSchedule = (role: Role): string => {
|
||||||
|
const schedule: string[] = [];
|
||||||
|
// we need full dates to compare times, because margins can lead to compare on previous or next day
|
||||||
|
// -first we establish a base calendar (up to a week)
|
||||||
|
const scheduleDates: Date[] = this._datesBetweenBoundaries(
|
||||||
|
this.query.fromDate,
|
||||||
|
this.query.toDate,
|
||||||
|
);
|
||||||
|
// - then we compare each resulting day of the schedule with each day of calendar,
|
||||||
|
// adding / removing margin depending on the role
|
||||||
|
scheduleDates.map((date: Date) => {
|
||||||
|
this.query.schedule
|
||||||
|
.filter(
|
||||||
|
(scheduleItem: ScheduleItem) => date.getDay() == scheduleItem.day,
|
||||||
|
)
|
||||||
|
.map((scheduleItem: ScheduleItem) => {
|
||||||
|
switch (role) {
|
||||||
|
case Role.PASSENGER:
|
||||||
|
schedule.push(this._wherePassengerSchedule(date, scheduleItem));
|
||||||
|
break;
|
||||||
|
case Role.DRIVER:
|
||||||
|
schedule.push(this._whereDriverSchedule(date, scheduleItem));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (schedule.length > 0) {
|
||||||
|
return ['(', schedule.join(' OR '), ')'].join('');
|
||||||
|
}
|
||||||
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private _wherePassengerSchedule = (
|
||||||
|
date: Date,
|
||||||
|
scheduleItem: ScheduleItem,
|
||||||
|
): string => {
|
||||||
|
let maxDepartureDatetime: Date = new Date(date);
|
||||||
|
maxDepartureDatetime.setHours(parseInt(scheduleItem.time.split(':')[0]));
|
||||||
|
maxDepartureDatetime.setMinutes(parseInt(scheduleItem.time.split(':')[1]));
|
||||||
|
maxDepartureDatetime = this._addMargin(
|
||||||
|
maxDepartureDatetime,
|
||||||
|
scheduleItem.margin as number,
|
||||||
|
);
|
||||||
|
// we want the min departure time of the driver to be before the max departure time of the passenger
|
||||||
|
return `make_timestamp(\
|
||||||
|
${maxDepartureDatetime.getFullYear()},\
|
||||||
|
${maxDepartureDatetime.getMonth() + 1},\
|
||||||
|
${maxDepartureDatetime.getDate()},\
|
||||||
|
CAST(EXTRACT(hour from time) as integer),\
|
||||||
|
CAST(EXTRACT(minute from time) as integer),0) - interval '1 second' * margin <=\
|
||||||
|
make_timestamp(\
|
||||||
|
${maxDepartureDatetime.getFullYear()},\
|
||||||
|
${maxDepartureDatetime.getMonth() + 1},\
|
||||||
|
${maxDepartureDatetime.getDate()},${maxDepartureDatetime.getHours()},${maxDepartureDatetime.getMinutes()},0)`;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _whereDriverSchedule = (
|
||||||
|
date: Date,
|
||||||
|
scheduleItem: ScheduleItem,
|
||||||
|
): string => {
|
||||||
|
let minDepartureDatetime: Date = new Date(date);
|
||||||
|
minDepartureDatetime.setHours(parseInt(scheduleItem.time.split(':')[0]));
|
||||||
|
minDepartureDatetime.setMinutes(parseInt(scheduleItem.time.split(':')[1]));
|
||||||
|
minDepartureDatetime = this._addMargin(
|
||||||
|
minDepartureDatetime,
|
||||||
|
-(scheduleItem.margin as number),
|
||||||
|
);
|
||||||
|
// we want the max departure time of the passenger to be after the min departure time of the driver
|
||||||
|
return `make_timestamp(\
|
||||||
|
${minDepartureDatetime.getFullYear()},
|
||||||
|
${minDepartureDatetime.getMonth() + 1},
|
||||||
|
${minDepartureDatetime.getDate()},\
|
||||||
|
CAST(EXTRACT(hour from time) as integer),\
|
||||||
|
CAST(EXTRACT(minute from time) as integer),0) + interval '1 second' * margin >=\
|
||||||
|
make_timestamp(\
|
||||||
|
${minDepartureDatetime.getFullYear()},
|
||||||
|
${minDepartureDatetime.getMonth() + 1},
|
||||||
|
${minDepartureDatetime.getDate()},${minDepartureDatetime.getHours()},${minDepartureDatetime.getMinutes()},0)`;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _whereAzimuth = (): string => {
|
||||||
|
if (!this.query.useAzimuth) return '';
|
||||||
|
const { minAzimuth, maxAzimuth } = this._azimuthRange(
|
||||||
|
this.query.route?.backAzimuth as number,
|
||||||
|
this.query.azimuthMargin as number,
|
||||||
|
);
|
||||||
|
if (minAzimuth <= maxAzimuth)
|
||||||
|
return `("fwdAzimuth" <= ${minAzimuth} OR "fwdAzimuth" >= ${maxAzimuth})`;
|
||||||
|
return `("fwdAzimuth" <= ${minAzimuth} AND "fwdAzimuth" >= ${maxAzimuth})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _whereProportion = (role: Role): string => {
|
||||||
|
if (!this.query.useProportion) return '';
|
||||||
|
switch (role) {
|
||||||
|
case Role.PASSENGER:
|
||||||
|
return `(${this.query.route?.passengerDistance}>(${this.query.proportion}*"driverDistance"))`;
|
||||||
|
case Role.DRIVER:
|
||||||
|
return `("passengerDistance">(${this.query.proportion}*${this.query.route?.driverDistance}))`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private _whereRemoteness = (role: Role): string => {
|
||||||
|
this.query.waypoints.sort(
|
||||||
|
(firstWaypoint: Waypoint, secondWaypoint: Waypoint) =>
|
||||||
|
firstWaypoint.position - secondWaypoint.position,
|
||||||
|
);
|
||||||
|
switch (role) {
|
||||||
|
case Role.PASSENGER:
|
||||||
|
return `\
|
||||||
|
public.st_distance('POINT(${this.query.waypoints[0].lon} ${
|
||||||
|
this.query.waypoints[0].lat
|
||||||
|
})'::public.geography,direction)<\
|
||||||
|
${this.query.remoteness} AND \
|
||||||
|
public.st_distance('POINT(${
|
||||||
|
this.query.waypoints[this.query.waypoints.length - 1].lon
|
||||||
|
} ${
|
||||||
|
this.query.waypoints[this.query.waypoints.length - 1].lat
|
||||||
|
})'::public.geography,direction)<\
|
||||||
|
${this.query.remoteness}`;
|
||||||
|
case Role.DRIVER:
|
||||||
|
const lineStringPoints: string[] = [];
|
||||||
|
this.query.route?.points.forEach((point: Coordinates) =>
|
||||||
|
lineStringPoints.push(
|
||||||
|
`public.st_makepoint(${point.lon},${point.lat})`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const lineString = [
|
||||||
|
'public.st_makeline( ARRAY[ ',
|
||||||
|
lineStringPoints.join(','),
|
||||||
|
'] )::public.geography',
|
||||||
|
].join('');
|
||||||
|
return `\
|
||||||
|
public.st_distance( public.st_startpoint(waypoints::public.geometry), ${lineString})<\
|
||||||
|
${this.query.remoteness} AND \
|
||||||
|
public.st_distance( public.st_endpoint(waypoints::public.geometry), ${lineString})<\
|
||||||
|
${this.query.remoteness}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private _datesBetweenBoundaries = (
|
||||||
|
firstDate: string,
|
||||||
|
lastDate: string,
|
||||||
|
max = 7,
|
||||||
|
): Date[] => {
|
||||||
|
const fromDate: Date = new Date(firstDate);
|
||||||
|
const toDate: Date = new Date(lastDate);
|
||||||
|
const dates: Date[] = [];
|
||||||
|
let count = 0;
|
||||||
|
for (
|
||||||
|
let date = fromDate;
|
||||||
|
date <= toDate;
|
||||||
|
date.setDate(date.getDate() + 1)
|
||||||
|
) {
|
||||||
|
dates.push(new Date(date));
|
||||||
|
count++;
|
||||||
|
if (count == max) break;
|
||||||
|
}
|
||||||
|
return dates;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _addMargin = (date: Date, marginInSeconds: number): Date => {
|
||||||
|
date.setTime(date.getTime() + marginInSeconds * 1000);
|
||||||
|
return date;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _azimuthRange = (
|
||||||
|
azimuth: number,
|
||||||
|
margin: number,
|
||||||
|
): { minAzimuth: number; maxAzimuth: number } => ({
|
||||||
|
minAzimuth:
|
||||||
|
azimuth - margin < 0 ? azimuth - margin + 360 : azimuth - margin,
|
||||||
|
maxAzimuth:
|
||||||
|
azimuth + margin > 360 ? azimuth + margin - 360 : azimuth + margin,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export type QueryStringRole = {
|
export type QueryStringRole = {
|
||||||
|
|
|
@ -100,10 +100,12 @@ export class AdRepository
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCandidates = async (queryString: string): Promise<AdReadModel[]> =>
|
getCandidates = async (queryString: string): Promise<AdReadModel[]> => {
|
||||||
this.toAdReadModels(
|
// console.log(queryString);
|
||||||
|
return this.toAdReadModels(
|
||||||
(await this.prismaRaw.$queryRawUnsafe(queryString)) as UngroupedAdModel[],
|
(await this.prismaRaw.$queryRawUnsafe(queryString)) as UngroupedAdModel[],
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
private toAdReadModels = (
|
private toAdReadModels = (
|
||||||
ungroupedAds: UngroupedAdModel[],
|
ungroupedAds: UngroupedAdModel[],
|
||||||
|
|
|
@ -20,7 +20,7 @@ export class TimeConverter implements TimeConverterPort {
|
||||||
date: string,
|
date: string,
|
||||||
time: string,
|
time: string,
|
||||||
timezone: string,
|
timezone: string,
|
||||||
dst = true,
|
dst = false,
|
||||||
): Date =>
|
): Date =>
|
||||||
new Date(
|
new Date(
|
||||||
new DateTime(
|
new DateTime(
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
export class MatchResponseDto {
|
import { ResponseBase } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class MatchResponseDto extends ResponseBase {
|
||||||
adId: string;
|
adId: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { Controller, UsePipes } from '@nestjs/common';
|
import { Controller, UsePipes } from '@nestjs/common';
|
||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
||||||
import { RpcValidationPipe } from '@mobicoop/ddd-library';
|
import { ResponseBase, RpcValidationPipe } from '@mobicoop/ddd-library';
|
||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||||
import { MatchPaginatedResponseDto } from '../dtos/match.paginated.response.dto';
|
import { MatchPaginatedResponseDto } from '../dtos/match.paginated.response.dto';
|
||||||
import { QueryBus } from '@nestjs/cqrs';
|
import { QueryBus } from '@nestjs/cqrs';
|
||||||
import { MatchRequestDto } from './dtos/match.request.dto';
|
import { MatchRequestDto } from './dtos/match.request.dto';
|
||||||
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
|
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
|
||||||
|
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
|
||||||
|
|
||||||
@UsePipes(
|
@UsePipes(
|
||||||
new RpcValidationPipe({
|
new RpcValidationPipe({
|
||||||
|
@ -20,13 +21,18 @@ export class MatchGrpcController {
|
||||||
@GrpcMethod('MatcherService', 'Match')
|
@GrpcMethod('MatcherService', 'Match')
|
||||||
async match(data: MatchRequestDto): Promise<MatchPaginatedResponseDto> {
|
async match(data: MatchRequestDto): Promise<MatchPaginatedResponseDto> {
|
||||||
try {
|
try {
|
||||||
const matches = await this.queryBus.execute(new MatchQuery(data));
|
const matches: MatchEntity[] = await this.queryBus.execute(
|
||||||
return {
|
new MatchQuery(data),
|
||||||
data: matches,
|
);
|
||||||
|
return new MatchPaginatedResponseDto({
|
||||||
|
data: matches.map((match: MatchEntity) => ({
|
||||||
|
...new ResponseBase(match),
|
||||||
|
adId: match.getProps().adId,
|
||||||
|
})),
|
||||||
page: 1,
|
page: 1,
|
||||||
perPage: 5,
|
perPage: 5,
|
||||||
total: matches.length,
|
total: matches.length,
|
||||||
};
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new RpcException({
|
throw new RpcException({
|
||||||
code: RpcExceptionCode.UNKNOWN,
|
code: RpcExceptionCode.UNKNOWN,
|
||||||
|
|
|
@ -55,6 +55,7 @@ enum AlgorithmType {
|
||||||
|
|
||||||
message Match {
|
message Match {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
|
string adId = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Matches {
|
message Matches {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import {
|
import {
|
||||||
AD_REPOSITORY,
|
AD_REPOSITORY,
|
||||||
|
AD_ROUTE_PROVIDER,
|
||||||
INPUT_DATETIME_TRANSFORMER,
|
INPUT_DATETIME_TRANSFORMER,
|
||||||
PARAMS_PROVIDER,
|
PARAMS_PROVIDER,
|
||||||
} from '@modules/ad/ad.di-tokens';
|
} from '@modules/ad/ad.di-tokens';
|
||||||
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||||
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
|
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
|
||||||
|
import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
|
||||||
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
|
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
|
||||||
import { MatchQueryHandler } from '@modules/ad/core/application/queries/match/match.query-handler';
|
import { MatchQueryHandler } from '@modules/ad/core/application/queries/match/match.query-handler';
|
||||||
import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
|
import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
|
||||||
|
@ -72,6 +74,10 @@ const mockInputDateTimeTransformer: DateTimeTransformerPort = {
|
||||||
time: jest.fn(),
|
time: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockRouteProvider: RouteProviderPort = {
|
||||||
|
getBasic: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
describe('Match Query Handler', () => {
|
describe('Match Query Handler', () => {
|
||||||
let matchQueryHandler: MatchQueryHandler;
|
let matchQueryHandler: MatchQueryHandler;
|
||||||
|
|
||||||
|
@ -91,6 +97,10 @@ describe('Match Query Handler', () => {
|
||||||
provide: INPUT_DATETIME_TRANSFORMER,
|
provide: INPUT_DATETIME_TRANSFORMER,
|
||||||
useValue: mockInputDateTimeTransformer,
|
useValue: mockInputDateTimeTransformer,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: AD_ROUTE_PROVIDER,
|
||||||
|
useValue: mockRouteProvider,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||||
import { DefaultParams } from '@modules/ad/core/application/ports/default-params.type';
|
import { DefaultParams } from '@modules/ad/core/application/ports/default-params.type';
|
||||||
|
import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
|
||||||
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
|
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
|
||||||
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';
|
||||||
|
@ -49,6 +50,23 @@ const mockInputDateTimeTransformer: DateTimeTransformerPort = {
|
||||||
time: jest.fn().mockImplementation(() => '23:05'),
|
time: jest.fn().mockImplementation(() => '23:05'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockRouteProvider: RouteProviderPort = {
|
||||||
|
getBasic: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementationOnce(() => ({
|
||||||
|
driverDistance: undefined,
|
||||||
|
driverDuration: undefined,
|
||||||
|
passengerDistance: 150120,
|
||||||
|
passengerDuration: 6540,
|
||||||
|
fwdAzimuth: 276,
|
||||||
|
backAzimuth: 96,
|
||||||
|
points: [],
|
||||||
|
}))
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new Error();
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
describe('Match Query', () => {
|
describe('Match Query', () => {
|
||||||
it('should set default values', async () => {
|
it('should set default values', async () => {
|
||||||
const matchQuery = new MatchQuery({
|
const matchQuery = new MatchQuery({
|
||||||
|
@ -124,4 +142,40 @@ describe('Match Query', () => {
|
||||||
expect(matchQuery.seatsProposed).toBe(3);
|
expect(matchQuery.seatsProposed).toBe(3);
|
||||||
expect(matchQuery.seatsRequested).toBe(1);
|
expect(matchQuery.seatsRequested).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set route', async () => {
|
||||||
|
const matchQuery = new MatchQuery({
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
|
fromDate: '2023-08-28',
|
||||||
|
toDate: '2023-08-28',
|
||||||
|
schedule: [
|
||||||
|
{
|
||||||
|
time: '01:05',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
waypoints: [originWaypoint, destinationWaypoint],
|
||||||
|
});
|
||||||
|
await matchQuery.setRoute(mockRouteProvider);
|
||||||
|
expect(matchQuery.route?.driverDistance).toBeUndefined();
|
||||||
|
expect(matchQuery.route?.passengerDistance).toBe(150120);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception if route is not found', async () => {
|
||||||
|
const matchQuery = new MatchQuery({
|
||||||
|
driver: true,
|
||||||
|
passenger: false,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
|
fromDate: '2023-08-28',
|
||||||
|
toDate: '2023-08-28',
|
||||||
|
schedule: [
|
||||||
|
{
|
||||||
|
time: '01:05',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
waypoints: [originWaypoint, destinationWaypoint],
|
||||||
|
});
|
||||||
|
await expect(matchQuery.setRoute(mockRouteProvider)).rejects.toBeInstanceOf(
|
||||||
|
Error,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,16 +32,44 @@ const matchQuery = new MatchQuery({
|
||||||
driver: true,
|
driver: true,
|
||||||
passenger: true,
|
passenger: true,
|
||||||
frequency: Frequency.PUNCTUAL,
|
frequency: Frequency.PUNCTUAL,
|
||||||
fromDate: '2023-08-28',
|
fromDate: '2023-06-21',
|
||||||
toDate: '2023-08-28',
|
toDate: '2023-06-21',
|
||||||
|
useAzimuth: true,
|
||||||
|
azimuthMargin: 10,
|
||||||
|
useProportion: true,
|
||||||
|
proportion: 0.3,
|
||||||
schedule: [
|
schedule: [
|
||||||
{
|
{
|
||||||
|
day: 3,
|
||||||
time: '07:05',
|
time: '07:05',
|
||||||
|
margin: 900,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
strict: false,
|
strict: false,
|
||||||
waypoints: [originWaypoint, destinationWaypoint],
|
waypoints: [originWaypoint, destinationWaypoint],
|
||||||
});
|
});
|
||||||
|
matchQuery.route = {
|
||||||
|
driverDistance: 150120,
|
||||||
|
driverDuration: 6540,
|
||||||
|
passengerDistance: 150120,
|
||||||
|
passengerDuration: 6540,
|
||||||
|
fwdAzimuth: 276,
|
||||||
|
backAzimuth: 96,
|
||||||
|
points: [
|
||||||
|
{
|
||||||
|
lat: 48.689445,
|
||||||
|
lon: 6.17651,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 48.7566,
|
||||||
|
lon: 4.3522,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 48.8566,
|
||||||
|
lon: 2.3522,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
const mockMatcherRepository: AdRepositoryPort = {
|
const mockMatcherRepository: AdRepositoryPort = {
|
||||||
insertExtra: jest.fn(),
|
insertExtra: jest.fn(),
|
||||||
|
|
|
@ -60,6 +60,7 @@ describe('Time Converter', () => {
|
||||||
parisDate,
|
parisDate,
|
||||||
parisTime,
|
parisTime,
|
||||||
'Europe/Paris',
|
'Europe/Paris',
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
expect(utcDate.toISOString()).toBe('2023-06-22T10:00:00.000Z');
|
expect(utcDate.toISOString()).toBe('2023-06-22T10:00:00.000Z');
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||||
import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
|
import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
|
||||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||||
|
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
|
||||||
import { MatchRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/match.request.dto';
|
import { MatchRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/match.request.dto';
|
||||||
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
|
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
|
||||||
import { MatchGrpcController } from '@modules/ad/interface/grpc-controllers/match.grpc-controller';
|
import { MatchGrpcController } from '@modules/ad/interface/grpc-controllers/match.grpc-controller';
|
||||||
|
@ -49,12 +50,12 @@ const mockQueryBus = {
|
||||||
execute: jest
|
execute: jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockImplementationOnce(() => [
|
.mockImplementationOnce(() => [
|
||||||
{
|
MatchEntity.create({
|
||||||
adId: 1,
|
adId: '0cc87f3b-7a27-4eff-9850-a5d642c2a0c3',
|
||||||
},
|
}),
|
||||||
{
|
MatchEntity.create({
|
||||||
adId: 2,
|
adId: 'e4cc156f-aaa5-4270-bf6f-82f5a230d748',
|
||||||
},
|
}),
|
||||||
])
|
])
|
||||||
.mockImplementationOnce(() => {
|
.mockImplementationOnce(() => {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
|
|
Loading…
Reference in New Issue