create dedicated selectors
This commit is contained in:
parent
657f8e7a03
commit
8269242d28
|
@ -1,8 +1,7 @@
|
||||||
import { ExtendedRepositoryPort } from '@mobicoop/ddd-library';
|
import { ExtendedRepositoryPort } from '@mobicoop/ddd-library';
|
||||||
import { AdEntity } from '../../domain/ad.entity';
|
import { AdEntity } from '../../domain/ad.entity';
|
||||||
import { Candidate } from '../types/algorithm.types';
|
import { AdReadModel } from '@modules/ad/infrastructure/ad.repository';
|
||||||
import { MatchQuery } from '../queries/match/match.query';
|
|
||||||
|
|
||||||
export type AdRepositoryPort = ExtendedRepositoryPort<AdEntity> & {
|
export type AdRepositoryPort = ExtendedRepositoryPort<AdEntity> & {
|
||||||
getCandidates(query: MatchQuery): Promise<Candidate[]>;
|
getCandidates(queryString: string): Promise<AdReadModel[]>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { MatchEntity } from '../../../domain/match.entity';
|
import { MatchEntity } from '../../../domain/match.entity';
|
||||||
import { Candidate, Processor } from '../../types/algorithm.types';
|
import { Candidate } from '../../types/algorithm.types';
|
||||||
import { MatchQuery } from './match.query';
|
import { MatchQuery } from './match.query';
|
||||||
import { AdRepositoryPort } from '@modules/ad/core/application/ports/ad.repository.port';
|
import { AdRepositoryPort } from '@modules/ad/core/application/ports/ad.repository.port';
|
||||||
|
|
||||||
export abstract class Algorithm {
|
export abstract class Algorithm {
|
||||||
protected candidates: Candidate[];
|
protected candidates: Candidate[];
|
||||||
|
protected selector: Selector;
|
||||||
protected processors: Processor[];
|
protected processors: Processor[];
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly query: MatchQuery,
|
protected readonly query: MatchQuery,
|
||||||
|
@ -14,5 +15,37 @@ export abstract class Algorithm {
|
||||||
/**
|
/**
|
||||||
* Filter candidates that matches the query
|
* Filter candidates that matches the query
|
||||||
*/
|
*/
|
||||||
abstract match(): Promise<MatchEntity[]>;
|
match = async (): Promise<MatchEntity[]> => {
|
||||||
|
this.candidates = await this.selector.select();
|
||||||
|
for (const processor of this.processors) {
|
||||||
|
this.candidates = await processor.execute(this.candidates);
|
||||||
|
}
|
||||||
|
return this.candidates.map((candidate: Candidate) =>
|
||||||
|
MatchEntity.create({ adId: candidate.ad.id }),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A selector queries potential candidates in a repository
|
||||||
|
*/
|
||||||
|
export abstract class Selector {
|
||||||
|
protected readonly query: MatchQuery;
|
||||||
|
protected readonly repository: AdRepositoryPort;
|
||||||
|
constructor(query: MatchQuery, repository: AdRepositoryPort) {
|
||||||
|
this.query = query;
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
abstract select(): Promise<Candidate[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A processor processes candidates information
|
||||||
|
*/
|
||||||
|
export abstract class Processor {
|
||||||
|
protected readonly query: MatchQuery;
|
||||||
|
constructor(query: MatchQuery) {
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
abstract execute(candidates: Candidate[]): Promise<Candidate[]>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Candidate, Processor } from '../../../types/algorithm.types';
|
import { Candidate } from '../../../types/algorithm.types';
|
||||||
|
import { Processor } from '../algorithm.abstract';
|
||||||
|
|
||||||
export abstract class Completer implements Processor {
|
export abstract class Completer extends Processor {
|
||||||
execute = async (candidates: Candidate[]): Promise<Candidate[]> =>
|
execute = async (candidates: Candidate[]): Promise<Candidate[]> =>
|
||||||
this.complete(candidates);
|
this.complete(candidates);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Candidate, Processor } from '../../../types/algorithm.types';
|
import { Candidate } from '../../../types/algorithm.types';
|
||||||
|
import { Processor } from '../algorithm.abstract';
|
||||||
|
|
||||||
export abstract class Filter implements Processor {
|
export abstract class Filter extends Processor {
|
||||||
execute = async (candidates: Candidate[]): Promise<Candidate[]> =>
|
execute = async (candidates: Candidate[]): Promise<Candidate[]> =>
|
||||||
this.filter(candidates);
|
this.filter(candidates);
|
||||||
|
|
||||||
|
|
|
@ -51,8 +51,6 @@ export class MatchQueryHandler implements IQueryHandler {
|
||||||
})
|
})
|
||||||
.setDatesAndSchedule(this.datetimeTransformer);
|
.setDatesAndSchedule(this.datetimeTransformer);
|
||||||
|
|
||||||
console.log(query);
|
|
||||||
|
|
||||||
let algorithm: Algorithm;
|
let algorithm: Algorithm;
|
||||||
switch (query.algorithmType) {
|
switch (query.algorithmType) {
|
||||||
case AlgorithmType.PASSENGER_ORIENTED:
|
case AlgorithmType.PASSENGER_ORIENTED:
|
||||||
|
|
|
@ -3,9 +3,7 @@ import { MatchQuery } from './match.query';
|
||||||
import { PassengerOrientedWaypointsCompleter } from './completer/passenger-oriented-waypoints.completer';
|
import { PassengerOrientedWaypointsCompleter } from './completer/passenger-oriented-waypoints.completer';
|
||||||
import { PassengerOrientedGeoFilter } from './filter/passenger-oriented-geo.filter';
|
import { PassengerOrientedGeoFilter } from './filter/passenger-oriented-geo.filter';
|
||||||
import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
||||||
import { Role } from '@modules/ad/core/domain/ad.types';
|
import { PassengerOrientedSelector } from './selector/passenger-oriented.selector';
|
||||||
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
|
|
||||||
import { Candidate } from '../../types/algorithm.types';
|
|
||||||
|
|
||||||
export class PassengerOrientedAlgorithm extends Algorithm {
|
export class PassengerOrientedAlgorithm extends Algorithm {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -13,52 +11,10 @@ export class PassengerOrientedAlgorithm extends Algorithm {
|
||||||
protected readonly repository: AdRepositoryPort,
|
protected readonly repository: AdRepositoryPort,
|
||||||
) {
|
) {
|
||||||
super(query, repository);
|
super(query, repository);
|
||||||
|
this.selector = new PassengerOrientedSelector(query, repository);
|
||||||
this.processors = [
|
this.processors = [
|
||||||
new PassengerOrientedWaypointsCompleter(),
|
new PassengerOrientedWaypointsCompleter(query),
|
||||||
new PassengerOrientedGeoFilter(),
|
new PassengerOrientedGeoFilter(query),
|
||||||
];
|
|
||||||
this.candidates = [
|
|
||||||
{
|
|
||||||
ad: {
|
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
|
||||||
},
|
|
||||||
role: Role.DRIVER,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
// this.candidates = (
|
|
||||||
// await Promise.all(
|
|
||||||
// sqlQueries.map(
|
|
||||||
// async (queryRole: QueryRole) =>
|
|
||||||
// ({
|
|
||||||
// ads: (await this.repository.queryRawUnsafe(
|
|
||||||
// queryRole.query,
|
|
||||||
// )) as AdEntity[],
|
|
||||||
// role: queryRole.role,
|
|
||||||
// } as AdsRole),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// .map((adsRole: AdsRole) =>
|
|
||||||
// adsRole.ads.map(
|
|
||||||
// (adEntity: AdEntity) =>
|
|
||||||
// <Candidate>{
|
|
||||||
// ad: {
|
|
||||||
// id: adEntity.id,
|
|
||||||
// },
|
|
||||||
// role: adsRole.role,
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// .flat();
|
|
||||||
|
|
||||||
match = async (): Promise<MatchEntity[]> => {
|
|
||||||
this.candidates = await this.repository.getCandidates(this.query);
|
|
||||||
for (const processor of this.processors) {
|
|
||||||
this.candidates = await processor.execute(this.candidates);
|
|
||||||
}
|
|
||||||
return this.candidates.map((candidate: Candidate) =>
|
|
||||||
MatchEntity.create({ adId: candidate.ad.id }),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { Role } from '@modules/ad/core/domain/ad.types';
|
||||||
|
import { Candidate } from '../../../types/algorithm.types';
|
||||||
|
import { Selector } from '../algorithm.abstract';
|
||||||
|
import { AdReadModel } from '@modules/ad/infrastructure/ad.repository';
|
||||||
|
|
||||||
|
export class PassengerOrientedSelector extends Selector {
|
||||||
|
select = async (): Promise<Candidate[]> => {
|
||||||
|
const queryStringRoles: QueryStringRole[] = [];
|
||||||
|
if (this.query.driver)
|
||||||
|
queryStringRoles.push({
|
||||||
|
query: this.asDriverQueryString(),
|
||||||
|
role: Role.DRIVER,
|
||||||
|
});
|
||||||
|
if (this.query.passenger)
|
||||||
|
queryStringRoles.push({
|
||||||
|
query: this.asPassengerQueryString(),
|
||||||
|
role: Role.PASSENGER,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
await Promise.all(
|
||||||
|
queryStringRoles.map(async (queryStringRole: QueryStringRole) => ({
|
||||||
|
ads: await this.repository.getCandidates(queryStringRole.query),
|
||||||
|
role: queryStringRole.role,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map((adsRole) =>
|
||||||
|
adsRole.ads.map(
|
||||||
|
(adReadModel: AdReadModel) =>
|
||||||
|
<Candidate>{
|
||||||
|
ad: {
|
||||||
|
id: adReadModel.uuid,
|
||||||
|
},
|
||||||
|
role: adsRole.role,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.flat();
|
||||||
|
};
|
||||||
|
|
||||||
|
private asPassengerQueryString = (): string => `SELECT
|
||||||
|
ad.uuid,driver,passenger,frequency,public.st_astext(matcher.ad.waypoints) as waypoints,
|
||||||
|
"fromDate","toDate",
|
||||||
|
"seatsProposed","seatsRequested",
|
||||||
|
strict,
|
||||||
|
"driverDuration","driverDistance",
|
||||||
|
"passengerDuration","passengerDistance",
|
||||||
|
"fwdAzimuth","backAzimuth",
|
||||||
|
si.day,si.time,si.margin
|
||||||
|
FROM ad LEFT JOIN schedule_item si ON ad.uuid = si."adUuid"
|
||||||
|
WHERE driver=True`;
|
||||||
|
|
||||||
|
private asDriverQueryString = (): string => `SELECT
|
||||||
|
ad.uuid,driver,passenger,frequency,public.st_astext(matcher.ad.waypoints) as waypoints,
|
||||||
|
"fromDate","toDate",
|
||||||
|
"seatsProposed","seatsRequested",
|
||||||
|
strict,
|
||||||
|
"driverDuration","driverDistance",
|
||||||
|
"passengerDuration","passengerDistance",
|
||||||
|
"fwdAzimuth","backAzimuth",
|
||||||
|
si.day,si.time,si.margin
|
||||||
|
FROM ad LEFT JOIN schedule_item si ON ad.uuid = si."adUuid"
|
||||||
|
WHERE passenger=True`;
|
||||||
|
|
||||||
|
// await this.repository.getCandidates(this.query);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type QueryStringRole = {
|
||||||
|
query: string;
|
||||||
|
role: Role;
|
||||||
|
};
|
|
@ -4,10 +4,6 @@ export enum AlgorithmType {
|
||||||
PASSENGER_ORIENTED = 'PASSENGER_ORIENTED',
|
PASSENGER_ORIENTED = 'PASSENGER_ORIENTED',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Processor {
|
|
||||||
execute(candidates: Candidate[]): Promise<Candidate[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Candidate = {
|
export type Candidate = {
|
||||||
ad: Ad;
|
ad: Ad;
|
||||||
role: Role;
|
role: Role;
|
||||||
|
|
|
@ -7,10 +7,7 @@ import { AD_MESSAGE_PUBLISHER } from '../ad.di-tokens';
|
||||||
import { AdEntity } from '../core/domain/ad.entity';
|
import { AdEntity } from '../core/domain/ad.entity';
|
||||||
import { AdMapper } from '../ad.mapper';
|
import { AdMapper } from '../ad.mapper';
|
||||||
import { ExtendedPrismaRepositoryBase } from '@mobicoop/ddd-library/dist/db/prisma-repository.base';
|
import { ExtendedPrismaRepositoryBase } from '@mobicoop/ddd-library/dist/db/prisma-repository.base';
|
||||||
import { Frequency, Role } from '../core/domain/ad.types';
|
import { Frequency } from '../core/domain/ad.types';
|
||||||
import { Candidate } from '../core/application/types/algorithm.types';
|
|
||||||
import { AdSelector } from './ad.selector';
|
|
||||||
import { MatchQuery } from '../core/application/queries/match/match.query';
|
|
||||||
|
|
||||||
export type AdBaseModel = {
|
export type AdBaseModel = {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
|
@ -34,7 +31,6 @@ export type AdBaseModel = {
|
||||||
|
|
||||||
export type AdReadModel = AdBaseModel & {
|
export type AdReadModel = AdBaseModel & {
|
||||||
waypoints: string;
|
waypoints: string;
|
||||||
direction: string;
|
|
||||||
schedule: ScheduleItemModel[];
|
schedule: ScheduleItemModel[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -58,6 +54,39 @@ export type ScheduleItemModel = {
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RawAdBaseModel = {
|
||||||
|
uuid: string;
|
||||||
|
driver: boolean;
|
||||||
|
passenger: boolean;
|
||||||
|
frequency: Frequency;
|
||||||
|
fromDate: Date;
|
||||||
|
toDate: Date;
|
||||||
|
seatsProposed: number;
|
||||||
|
seatsRequested: number;
|
||||||
|
strict: boolean;
|
||||||
|
driverDuration?: number;
|
||||||
|
driverDistance?: number;
|
||||||
|
passengerDuration?: number;
|
||||||
|
passengerDistance?: number;
|
||||||
|
fwdAzimuth: number;
|
||||||
|
backAzimuth: number;
|
||||||
|
waypoints: string;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RawScheduleItemModel = {
|
||||||
|
day: number;
|
||||||
|
time: Date;
|
||||||
|
margin: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RawAdModel = RawAdBaseModel & RawScheduleItemModel;
|
||||||
|
|
||||||
|
export type RawAdReadModel = RawAdBaseModel & {
|
||||||
|
schedule: RawScheduleItemModel[];
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository is used for retrieving/saving domain entities
|
* Repository is used for retrieving/saving domain entities
|
||||||
* */
|
* */
|
||||||
|
@ -91,39 +120,50 @@ export class AdRepository
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCandidates = async (query: MatchQuery): Promise<Candidate[]> => {
|
getCandidates = async (queryString: string): Promise<AdReadModel[]> =>
|
||||||
// let candidates: Candidate[] = [];
|
this.toReadModels((await this.queryRawUnsafe(queryString)) as RawAdModel[]);
|
||||||
const sqlQueries: QueryRole[] = [];
|
|
||||||
if (query.driver)
|
private toReadModels = (rawAds: RawAdModel[]): AdReadModel[] => {
|
||||||
sqlQueries.push({
|
const rawAdReadModels: RawAdReadModel[] = rawAds.map(
|
||||||
query: AdSelector.select(Role.DRIVER, query),
|
(rawAd: RawAdModel) => ({
|
||||||
role: Role.DRIVER,
|
uuid: rawAd.uuid,
|
||||||
});
|
driver: rawAd.driver,
|
||||||
if (query.passenger)
|
passenger: rawAd.passenger,
|
||||||
sqlQueries.push({
|
frequency: rawAd.frequency,
|
||||||
query: AdSelector.select(Role.PASSENGER, query),
|
fromDate: rawAd.fromDate,
|
||||||
role: Role.PASSENGER,
|
toDate: rawAd.toDate,
|
||||||
});
|
schedule: [
|
||||||
const results = await Promise.all(
|
{
|
||||||
sqlQueries.map(
|
day: rawAd.day,
|
||||||
async (queryRole: QueryRole) =>
|
time: rawAd.time,
|
||||||
({
|
margin: rawAd.margin,
|
||||||
ads: (await this.queryRawUnsafe(queryRole.query)) as AdEntity[],
|
},
|
||||||
role: queryRole.role,
|
],
|
||||||
} as AdsRole),
|
seatsProposed: rawAd.seatsProposed,
|
||||||
),
|
seatsRequested: rawAd.seatsRequested,
|
||||||
|
strict: rawAd.strict,
|
||||||
|
driverDuration: rawAd.driverDuration,
|
||||||
|
driverDistance: rawAd.driverDistance,
|
||||||
|
passengerDuration: rawAd.passengerDuration,
|
||||||
|
passengerDistance: rawAd.passengerDistance,
|
||||||
|
fwdAzimuth: rawAd.fwdAzimuth,
|
||||||
|
backAzimuth: rawAd.backAzimuth,
|
||||||
|
waypoints: rawAd.waypoints,
|
||||||
|
createdAt: rawAd.createdAt,
|
||||||
|
updatedAt: rawAd.updatedAt,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
// console.log(results[0].ads);
|
const adReadModels: AdReadModel[] = [];
|
||||||
return [];
|
rawAdReadModels.forEach((adReadModel: AdReadModel) => {
|
||||||
|
const ad: AdReadModel | undefined = adReadModels.find(
|
||||||
|
(arm: AdReadModel) => arm.uuid == adReadModel.uuid,
|
||||||
|
);
|
||||||
|
if (ad) {
|
||||||
|
ad.schedule.push(...adReadModel.schedule);
|
||||||
|
} else {
|
||||||
|
adReadModels.push(adReadModel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return adReadModels;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueryRole = {
|
|
||||||
query: string;
|
|
||||||
role: Role;
|
|
||||||
};
|
|
||||||
|
|
||||||
type AdsRole = {
|
|
||||||
ads: AdEntity[];
|
|
||||||
role: Role;
|
|
||||||
};
|
|
||||||
|
|
|
@ -84,8 +84,6 @@ const adReadModel: AdReadModel = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
waypoints: "'LINESTRING(6.1765102 48.689445,2.3522 48.8566)'",
|
waypoints: "'LINESTRING(6.1765102 48.689445,2.3522 48.8566)'",
|
||||||
direction:
|
|
||||||
"'LINESTRING(6.1765102 48.689445,5.12345 48.76543,2.3522 48.8566)'",
|
|
||||||
driverDistance: 350000,
|
driverDistance: 350000,
|
||||||
driverDuration: 14400,
|
driverDuration: 14400,
|
||||||
passengerDistance: 350000,
|
passengerDistance: 350000,
|
||||||
|
|
Loading…
Reference in New Issue