create dedicated selectors
This commit is contained in:
		
							parent
							
								
									657f8e7a03
								
							
						
					
					
						commit
						8269242d28
					
				| 
						 | 
				
			
			@ -1,8 +1,7 @@
 | 
			
		|||
import { ExtendedRepositoryPort } from '@mobicoop/ddd-library';
 | 
			
		||||
import { AdEntity } from '../../domain/ad.entity';
 | 
			
		||||
import { Candidate } from '../types/algorithm.types';
 | 
			
		||||
import { MatchQuery } from '../queries/match/match.query';
 | 
			
		||||
import { AdReadModel } from '@modules/ad/infrastructure/ad.repository';
 | 
			
		||||
 | 
			
		||||
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 { Candidate, Processor } from '../../types/algorithm.types';
 | 
			
		||||
import { Candidate } from '../../types/algorithm.types';
 | 
			
		||||
import { MatchQuery } from './match.query';
 | 
			
		||||
import { AdRepositoryPort } from '@modules/ad/core/application/ports/ad.repository.port';
 | 
			
		||||
 | 
			
		||||
export abstract class Algorithm {
 | 
			
		||||
  protected candidates: Candidate[];
 | 
			
		||||
  protected selector: Selector;
 | 
			
		||||
  protected processors: Processor[];
 | 
			
		||||
  constructor(
 | 
			
		||||
    protected readonly query: MatchQuery,
 | 
			
		||||
| 
						 | 
				
			
			@ -14,5 +15,37 @@ export abstract class Algorithm {
 | 
			
		|||
  /**
 | 
			
		||||
   * 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[]> =>
 | 
			
		||||
    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[]> =>
 | 
			
		||||
    this.filter(candidates);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -51,8 +51,6 @@ export class MatchQueryHandler implements IQueryHandler {
 | 
			
		|||
      })
 | 
			
		||||
      .setDatesAndSchedule(this.datetimeTransformer);
 | 
			
		||||
 | 
			
		||||
    console.log(query);
 | 
			
		||||
 | 
			
		||||
    let algorithm: Algorithm;
 | 
			
		||||
    switch (query.algorithmType) {
 | 
			
		||||
      case AlgorithmType.PASSENGER_ORIENTED:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,9 +3,7 @@ import { MatchQuery } from './match.query';
 | 
			
		|||
import { PassengerOrientedWaypointsCompleter } from './completer/passenger-oriented-waypoints.completer';
 | 
			
		||||
import { PassengerOrientedGeoFilter } from './filter/passenger-oriented-geo.filter';
 | 
			
		||||
import { AdRepositoryPort } from '../../ports/ad.repository.port';
 | 
			
		||||
import { Role } from '@modules/ad/core/domain/ad.types';
 | 
			
		||||
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
 | 
			
		||||
import { Candidate } from '../../types/algorithm.types';
 | 
			
		||||
import { PassengerOrientedSelector } from './selector/passenger-oriented.selector';
 | 
			
		||||
 | 
			
		||||
export class PassengerOrientedAlgorithm extends Algorithm {
 | 
			
		||||
  constructor(
 | 
			
		||||
| 
						 | 
				
			
			@ -13,52 +11,10 @@ export class PassengerOrientedAlgorithm extends Algorithm {
 | 
			
		|||
    protected readonly repository: AdRepositoryPort,
 | 
			
		||||
  ) {
 | 
			
		||||
    super(query, repository);
 | 
			
		||||
    this.selector = new PassengerOrientedSelector(query, repository);
 | 
			
		||||
    this.processors = [
 | 
			
		||||
      new PassengerOrientedWaypointsCompleter(),
 | 
			
		||||
      new PassengerOrientedGeoFilter(),
 | 
			
		||||
    ];
 | 
			
		||||
    this.candidates = [
 | 
			
		||||
      {
 | 
			
		||||
        ad: {
 | 
			
		||||
          id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
 | 
			
		||||
        },
 | 
			
		||||
        role: Role.DRIVER,
 | 
			
		||||
      },
 | 
			
		||||
      new PassengerOrientedWaypointsCompleter(query),
 | 
			
		||||
      new PassengerOrientedGeoFilter(query),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
  // 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',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Processor {
 | 
			
		||||
  execute(candidates: Candidate[]): Promise<Candidate[]>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type Candidate = {
 | 
			
		||||
  ad: Ad;
 | 
			
		||||
  role: Role;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,10 +7,7 @@ import { AD_MESSAGE_PUBLISHER } from '../ad.di-tokens';
 | 
			
		|||
import { AdEntity } from '../core/domain/ad.entity';
 | 
			
		||||
import { AdMapper } from '../ad.mapper';
 | 
			
		||||
import { ExtendedPrismaRepositoryBase } from '@mobicoop/ddd-library/dist/db/prisma-repository.base';
 | 
			
		||||
import { Frequency, Role } 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';
 | 
			
		||||
import { Frequency } from '../core/domain/ad.types';
 | 
			
		||||
 | 
			
		||||
export type AdBaseModel = {
 | 
			
		||||
  uuid: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +31,6 @@ export type AdBaseModel = {
 | 
			
		|||
 | 
			
		||||
export type AdReadModel = AdBaseModel & {
 | 
			
		||||
  waypoints: string;
 | 
			
		||||
  direction: string;
 | 
			
		||||
  schedule: ScheduleItemModel[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -58,6 +54,39 @@ export type ScheduleItemModel = {
 | 
			
		|||
  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
 | 
			
		||||
 * */
 | 
			
		||||
| 
						 | 
				
			
			@ -91,39 +120,50 @@ export class AdRepository
 | 
			
		|||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getCandidates = async (query: MatchQuery): Promise<Candidate[]> => {
 | 
			
		||||
    // let candidates: Candidate[] = [];
 | 
			
		||||
    const sqlQueries: QueryRole[] = [];
 | 
			
		||||
    if (query.driver)
 | 
			
		||||
      sqlQueries.push({
 | 
			
		||||
        query: AdSelector.select(Role.DRIVER, query),
 | 
			
		||||
        role: Role.DRIVER,
 | 
			
		||||
      });
 | 
			
		||||
    if (query.passenger)
 | 
			
		||||
      sqlQueries.push({
 | 
			
		||||
        query: AdSelector.select(Role.PASSENGER, query),
 | 
			
		||||
        role: Role.PASSENGER,
 | 
			
		||||
      });
 | 
			
		||||
    const results = await Promise.all(
 | 
			
		||||
      sqlQueries.map(
 | 
			
		||||
        async (queryRole: QueryRole) =>
 | 
			
		||||
          ({
 | 
			
		||||
            ads: (await this.queryRawUnsafe(queryRole.query)) as AdEntity[],
 | 
			
		||||
            role: queryRole.role,
 | 
			
		||||
          } as AdsRole),
 | 
			
		||||
      ),
 | 
			
		||||
  getCandidates = async (queryString: string): Promise<AdReadModel[]> =>
 | 
			
		||||
    this.toReadModels((await this.queryRawUnsafe(queryString)) as RawAdModel[]);
 | 
			
		||||
 | 
			
		||||
  private toReadModels = (rawAds: RawAdModel[]): AdReadModel[] => {
 | 
			
		||||
    const rawAdReadModels: RawAdReadModel[] = rawAds.map(
 | 
			
		||||
      (rawAd: RawAdModel) => ({
 | 
			
		||||
        uuid: rawAd.uuid,
 | 
			
		||||
        driver: rawAd.driver,
 | 
			
		||||
        passenger: rawAd.passenger,
 | 
			
		||||
        frequency: rawAd.frequency,
 | 
			
		||||
        fromDate: rawAd.fromDate,
 | 
			
		||||
        toDate: rawAd.toDate,
 | 
			
		||||
        schedule: [
 | 
			
		||||
          {
 | 
			
		||||
            day: rawAd.day,
 | 
			
		||||
            time: rawAd.time,
 | 
			
		||||
            margin: rawAd.margin,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        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);
 | 
			
		||||
    return [];
 | 
			
		||||
    const adReadModels: AdReadModel[] = [];
 | 
			
		||||
    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)'",
 | 
			
		||||
  direction:
 | 
			
		||||
    "'LINESTRING(6.1765102 48.689445,5.12345 48.76543,2.3522 48.8566)'",
 | 
			
		||||
  driverDistance: 350000,
 | 
			
		||||
  driverDuration: 14400,
 | 
			
		||||
  passengerDistance: 350000,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue