extract path creator
This commit is contained in:
		
							parent
							
								
									59a2644bb4
								
							
						
					
					
						commit
						2058bfce4c
					
				| 
						 | 
					@ -8,7 +8,13 @@ import { AggregateID, ConflictException } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
 | 
					import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
 | 
				
			||||||
import { RouteProviderPort } from '../../ports/route-provider.port';
 | 
					import { RouteProviderPort } from '../../ports/route-provider.port';
 | 
				
			||||||
import { Role } from '@modules/ad/core/domain/ad.types';
 | 
					import { Role } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
import { Route } from '../../types/carpool-route.type';
 | 
					import {
 | 
				
			||||||
 | 
					  Path,
 | 
				
			||||||
 | 
					  PathCreator,
 | 
				
			||||||
 | 
					  PathType,
 | 
				
			||||||
 | 
					  TypedRoute,
 | 
				
			||||||
 | 
					} from '@modules/ad/core/domain/patch-creator.service';
 | 
				
			||||||
 | 
					import { Point } from '../../types/point.type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@CommandHandler(CreateAdCommand)
 | 
					@CommandHandler(CreateAdCommand)
 | 
				
			||||||
export class CreateAdService implements ICommandHandler {
 | 
					export class CreateAdService implements ICommandHandler {
 | 
				
			||||||
| 
						 | 
					@ -23,36 +29,73 @@ export class CreateAdService implements ICommandHandler {
 | 
				
			||||||
    const roles: Role[] = [];
 | 
					    const roles: Role[] = [];
 | 
				
			||||||
    if (command.driver) roles.push(Role.DRIVER);
 | 
					    if (command.driver) roles.push(Role.DRIVER);
 | 
				
			||||||
    if (command.passenger) roles.push(Role.PASSENGER);
 | 
					    if (command.passenger) roles.push(Role.PASSENGER);
 | 
				
			||||||
    const route: Route = await this.routeProvider.getBasic(command.waypoints);
 | 
					    const pathCreator: PathCreator = new PathCreator(roles, command.waypoints);
 | 
				
			||||||
    const ad = AdEntity.create({
 | 
					    let typedRoutes: TypedRoute[];
 | 
				
			||||||
      id: command.id,
 | 
					 | 
				
			||||||
      driver: command.driver,
 | 
					 | 
				
			||||||
      passenger: command.passenger,
 | 
					 | 
				
			||||||
      frequency: command.frequency,
 | 
					 | 
				
			||||||
      fromDate: command.fromDate,
 | 
					 | 
				
			||||||
      toDate: command.toDate,
 | 
					 | 
				
			||||||
      schedule: command.schedule,
 | 
					 | 
				
			||||||
      seatsProposed: command.seatsProposed,
 | 
					 | 
				
			||||||
      seatsRequested: command.seatsRequested,
 | 
					 | 
				
			||||||
      strict: command.strict,
 | 
					 | 
				
			||||||
      waypoints: command.waypoints,
 | 
					 | 
				
			||||||
      points: route.points,
 | 
					 | 
				
			||||||
      driverDistance: route.driverDistance,
 | 
					 | 
				
			||||||
      driverDuration: route.driverDuration,
 | 
					 | 
				
			||||||
      passengerDistance: route.passengerDistance,
 | 
					 | 
				
			||||||
      passengerDuration: route.passengerDuration,
 | 
					 | 
				
			||||||
      fwdAzimuth: route.fwdAzimuth,
 | 
					 | 
				
			||||||
      backAzimuth: route.backAzimuth,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      await this.repository.insertExtra(ad, 'ad');
 | 
					      typedRoutes = await Promise.all(
 | 
				
			||||||
      return ad.id;
 | 
					        pathCreator.getPaths().map(async (path: Path) => ({
 | 
				
			||||||
    } catch (error: any) {
 | 
					          type: path.type,
 | 
				
			||||||
      if (error instanceof ConflictException) {
 | 
					          route: await this.routeProvider.getBasic(path.waypoints),
 | 
				
			||||||
        throw new AdAlreadyExistsException(error);
 | 
					        })),
 | 
				
			||||||
      }
 | 
					      );
 | 
				
			||||||
      throw error;
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      throw new Error('Unable to find a route for given waypoints');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let driverDistance: number | undefined;
 | 
				
			||||||
 | 
					    let driverDuration: number | undefined;
 | 
				
			||||||
 | 
					    let passengerDistance: number | undefined;
 | 
				
			||||||
 | 
					    let passengerDuration: number | undefined;
 | 
				
			||||||
 | 
					    let points: Point[] | undefined;
 | 
				
			||||||
 | 
					    let fwdAzimuth: number | undefined;
 | 
				
			||||||
 | 
					    let backAzimuth: number | undefined;
 | 
				
			||||||
 | 
					    typedRoutes.forEach((typedRoute: TypedRoute) => {
 | 
				
			||||||
 | 
					      if (typedRoute.type !== PathType.PASSENGER) {
 | 
				
			||||||
 | 
					        driverDistance = typedRoute.route.distance;
 | 
				
			||||||
 | 
					        driverDuration = typedRoute.route.duration;
 | 
				
			||||||
 | 
					        points = typedRoute.route.points;
 | 
				
			||||||
 | 
					        fwdAzimuth = typedRoute.route.fwdAzimuth;
 | 
				
			||||||
 | 
					        backAzimuth = typedRoute.route.backAzimuth;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (typedRoute.type !== PathType.DRIVER) {
 | 
				
			||||||
 | 
					        passengerDistance = typedRoute.route.distance;
 | 
				
			||||||
 | 
					        passengerDuration = typedRoute.route.duration;
 | 
				
			||||||
 | 
					        if (!points) points = typedRoute.route.points;
 | 
				
			||||||
 | 
					        if (!fwdAzimuth) fwdAzimuth = typedRoute.route.fwdAzimuth;
 | 
				
			||||||
 | 
					        if (!backAzimuth) backAzimuth = typedRoute.route.backAzimuth;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (points && fwdAzimuth && backAzimuth) {
 | 
				
			||||||
 | 
					      const ad = AdEntity.create({
 | 
				
			||||||
 | 
					        id: command.id,
 | 
				
			||||||
 | 
					        driver: command.driver,
 | 
				
			||||||
 | 
					        passenger: command.passenger,
 | 
				
			||||||
 | 
					        frequency: command.frequency,
 | 
				
			||||||
 | 
					        fromDate: command.fromDate,
 | 
				
			||||||
 | 
					        toDate: command.toDate,
 | 
				
			||||||
 | 
					        schedule: command.schedule,
 | 
				
			||||||
 | 
					        seatsProposed: command.seatsProposed,
 | 
				
			||||||
 | 
					        seatsRequested: command.seatsRequested,
 | 
				
			||||||
 | 
					        strict: command.strict,
 | 
				
			||||||
 | 
					        waypoints: command.waypoints,
 | 
				
			||||||
 | 
					        points,
 | 
				
			||||||
 | 
					        driverDistance,
 | 
				
			||||||
 | 
					        driverDuration,
 | 
				
			||||||
 | 
					        passengerDistance,
 | 
				
			||||||
 | 
					        passengerDuration,
 | 
				
			||||||
 | 
					        fwdAzimuth,
 | 
				
			||||||
 | 
					        backAzimuth,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        await this.repository.insertExtra(ad, 'ad');
 | 
				
			||||||
 | 
					        return ad.id;
 | 
				
			||||||
 | 
					      } catch (error: any) {
 | 
				
			||||||
 | 
					        if (error instanceof ConflictException) {
 | 
				
			||||||
 | 
					          throw new AdAlreadyExistsException(error);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw error;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    throw new Error('Route error');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,10 @@
 | 
				
			||||||
 | 
					import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
 | 
				
			||||||
import { MatchEntity } from '../../../domain/match.entity';
 | 
					import { MatchEntity } from '../../../domain/match.entity';
 | 
				
			||||||
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: CandidateEntity[];
 | 
				
			||||||
  protected selector: Selector;
 | 
					  protected selector: Selector;
 | 
				
			||||||
  protected processors: Processor[];
 | 
					  protected processors: Processor[];
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
| 
						 | 
					@ -20,8 +20,9 @@ export abstract class Algorithm {
 | 
				
			||||||
    for (const processor of this.processors) {
 | 
					    for (const processor of this.processors) {
 | 
				
			||||||
      this.candidates = await processor.execute(this.candidates);
 | 
					      this.candidates = await processor.execute(this.candidates);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return this.candidates.map((candidate: Candidate) =>
 | 
					    // console.log(this.candidates);
 | 
				
			||||||
      MatchEntity.create({ adId: candidate.ad.id }),
 | 
					    return this.candidates.map((candidate: CandidateEntity) =>
 | 
				
			||||||
 | 
					      MatchEntity.create({ adId: candidate.id }),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -36,7 +37,7 @@ export abstract class Selector {
 | 
				
			||||||
    this.query = query;
 | 
					    this.query = query;
 | 
				
			||||||
    this.repository = repository;
 | 
					    this.repository = repository;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  abstract select(): Promise<Candidate[]>;
 | 
					  abstract select(): Promise<CandidateEntity[]>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -47,5 +48,5 @@ export abstract class Processor {
 | 
				
			||||||
  constructor(query: MatchQuery) {
 | 
					  constructor(query: MatchQuery) {
 | 
				
			||||||
    this.query = query;
 | 
					    this.query = query;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  abstract execute(candidates: Candidate[]): Promise<Candidate[]>;
 | 
					  abstract execute(candidates: CandidateEntity[]): Promise<CandidateEntity[]>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,9 @@
 | 
				
			||||||
import { Candidate } from '../../../types/algorithm.types';
 | 
					import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
 | 
				
			||||||
import { Processor } from '../algorithm.abstract';
 | 
					import { Processor } from '../algorithm.abstract';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export abstract class Completer extends Processor {
 | 
					export abstract class Completer extends Processor {
 | 
				
			||||||
  execute = async (candidates: Candidate[]): Promise<Candidate[]> =>
 | 
					  execute = async (candidates: CandidateEntity[]): Promise<CandidateEntity[]> =>
 | 
				
			||||||
    this.complete(candidates);
 | 
					    this.complete(candidates);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  abstract complete(candidates: Candidate[]): Promise<Candidate[]>;
 | 
					  abstract complete(candidates: CandidateEntity[]): Promise<CandidateEntity[]>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,20 @@
 | 
				
			||||||
import { Candidate } from '../../../types/algorithm.types';
 | 
					import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
 | 
				
			||||||
import { Completer } from './completer.abstract';
 | 
					import { Completer } from './completer.abstract';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Complete candidates by setting driver and crew waypoints
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
export class PassengerOrientedWaypointsCompleter extends Completer {
 | 
					export class PassengerOrientedWaypointsCompleter extends Completer {
 | 
				
			||||||
  complete = async (candidates: Candidate[]): Promise<Candidate[]> =>
 | 
					  complete = async (
 | 
				
			||||||
    candidates;
 | 
					    candidates: CandidateEntity[],
 | 
				
			||||||
 | 
					  ): Promise<CandidateEntity[]> => candidates;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// complete = async (candidates: Candidate[]): Promise<Candidate[]> => {
 | 
				
			||||||
 | 
					//     candidates.forEach( (candidate: Candidate) => {
 | 
				
			||||||
 | 
					//       if (candidate.role == Role.DRIVER) {
 | 
				
			||||||
 | 
					//         candidate.driverWaypoints = th
 | 
				
			||||||
 | 
					//       }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//     return candidates;
 | 
				
			||||||
 | 
					//   }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,9 @@
 | 
				
			||||||
import { Candidate } from '../../../types/algorithm.types';
 | 
					import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
 | 
				
			||||||
import { Processor } from '../algorithm.abstract';
 | 
					import { Processor } from '../algorithm.abstract';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export abstract class Filter extends Processor {
 | 
					export abstract class Filter extends Processor {
 | 
				
			||||||
  execute = async (candidates: Candidate[]): Promise<Candidate[]> =>
 | 
					  execute = async (candidates: CandidateEntity[]): Promise<CandidateEntity[]> =>
 | 
				
			||||||
    this.filter(candidates);
 | 
					    this.filter(candidates);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  abstract filter(candidates: Candidate[]): Promise<Candidate[]>;
 | 
					  abstract filter(candidates: CandidateEntity[]): Promise<CandidateEntity[]>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
import { Candidate } from '../../../types/algorithm.types';
 | 
					import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
 | 
				
			||||||
import { Filter } from './filter.abstract';
 | 
					import { Filter } from './filter.abstract';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class PassengerOrientedGeoFilter extends Filter {
 | 
					export class PassengerOrientedGeoFilter extends Filter {
 | 
				
			||||||
  filter = async (candidates: Candidate[]): Promise<Candidate[]> => candidates;
 | 
					  filter = async (candidates: CandidateEntity[]): Promise<CandidateEntity[]> =>
 | 
				
			||||||
 | 
					    candidates;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,17 @@
 | 
				
			||||||
import { QueryBase } from '@mobicoop/ddd-library';
 | 
					import { QueryBase } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { AlgorithmType } from '../../types/algorithm.types';
 | 
					import { AlgorithmType } from '../../types/algorithm.types';
 | 
				
			||||||
import { Waypoint } from '../../types/waypoint.type';
 | 
					import { Waypoint } from '../../types/waypoint.type';
 | 
				
			||||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
 | 
					import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
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 { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
 | 
					import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
 | 
				
			||||||
import { RouteProviderPort } from '../../ports/route-provider.port';
 | 
					import { RouteProviderPort } from '../../ports/route-provider.port';
 | 
				
			||||||
import { Route } from '@modules/geography/core/domain/route.types';
 | 
					import { Route } from '@modules/geography/core/domain/route.types';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Path,
 | 
				
			||||||
 | 
					  PathCreator,
 | 
				
			||||||
 | 
					  PathType,
 | 
				
			||||||
 | 
					  TypedRoute,
 | 
				
			||||||
 | 
					} from '@modules/ad/core/domain/patch-creator.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class MatchQuery extends QueryBase {
 | 
					export class MatchQuery extends QueryBase {
 | 
				
			||||||
  driver?: boolean;
 | 
					  driver?: boolean;
 | 
				
			||||||
| 
						 | 
					@ -168,10 +174,14 @@ export class MatchQuery extends QueryBase {
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setRoutes = async (routeProvider: RouteProviderPort): Promise<MatchQuery> => {
 | 
					  setRoutes = async (routeProvider: RouteProviderPort): Promise<MatchQuery> => {
 | 
				
			||||||
 | 
					    const roles: Role[] = [];
 | 
				
			||||||
 | 
					    if (this.driver) roles.push(Role.DRIVER);
 | 
				
			||||||
 | 
					    if (this.passenger) roles.push(Role.PASSENGER);
 | 
				
			||||||
 | 
					    const pathCreator: PathCreator = new PathCreator(roles, this.waypoints);
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      (
 | 
					      (
 | 
				
			||||||
        await Promise.all(
 | 
					        await Promise.all(
 | 
				
			||||||
          this._getPaths().map(async (path: Path) => ({
 | 
					          pathCreator.getPaths().map(async (path: Path) => ({
 | 
				
			||||||
            type: path.type,
 | 
					            type: path.type,
 | 
				
			||||||
            route: await routeProvider.getBasic(path.waypoints),
 | 
					            route: await routeProvider.getBasic(path.waypoints),
 | 
				
			||||||
          })),
 | 
					          })),
 | 
				
			||||||
| 
						 | 
					@ -192,43 +202,6 @@ export class MatchQuery extends QueryBase {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return this;
 | 
					    return this;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					 | 
				
			||||||
  private _getPaths = (): Path[] => {
 | 
					 | 
				
			||||||
    const paths: Path[] = [];
 | 
					 | 
				
			||||||
    if (this.driver && this.passenger) {
 | 
					 | 
				
			||||||
      if (this.waypoints.length == 2) {
 | 
					 | 
				
			||||||
        // 2 points => same route for driver and passenger
 | 
					 | 
				
			||||||
        paths.push(this._createGenericPath(this.waypoints));
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        paths.push(
 | 
					 | 
				
			||||||
          this._createDriverPath(this.waypoints),
 | 
					 | 
				
			||||||
          this._createPassengerPath(this.waypoints),
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else if (this.driver) {
 | 
					 | 
				
			||||||
      paths.push(this._createDriverPath(this.waypoints));
 | 
					 | 
				
			||||||
    } else if (this.passenger) {
 | 
					 | 
				
			||||||
      paths.push(this._createPassengerPath(this.waypoints));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return paths;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private _createGenericPath = (waypoints: Waypoint[]): Path =>
 | 
					 | 
				
			||||||
    this._createPath(waypoints, PathType.GENERIC);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private _createDriverPath = (waypoints: Waypoint[]): Path =>
 | 
					 | 
				
			||||||
    this._createPath(waypoints, PathType.DRIVER);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private _createPassengerPath = (waypoints: Waypoint[]): Path =>
 | 
					 | 
				
			||||||
    this._createPath(
 | 
					 | 
				
			||||||
      [waypoints[0], waypoints[waypoints.length - 1]],
 | 
					 | 
				
			||||||
      PathType.PASSENGER,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private _createPath = (waypoints: Waypoint[], type: PathType): Path => ({
 | 
					 | 
				
			||||||
    type,
 | 
					 | 
				
			||||||
    waypoints,
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ScheduleItem = {
 | 
					export type ScheduleItem = {
 | 
				
			||||||
| 
						 | 
					@ -254,19 +227,3 @@ interface DefaultAlgorithmParameters {
 | 
				
			||||||
  maxDetourDistanceRatio: number;
 | 
					  maxDetourDistanceRatio: number;
 | 
				
			||||||
  maxDetourDurationRatio: number;
 | 
					  maxDetourDurationRatio: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
type Path = {
 | 
					 | 
				
			||||||
  type: PathType;
 | 
					 | 
				
			||||||
  waypoints: Waypoint[];
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum PathType {
 | 
					 | 
				
			||||||
  GENERIC = 'generic',
 | 
					 | 
				
			||||||
  DRIVER = 'driver',
 | 
					 | 
				
			||||||
  PASSENGER = 'passenger',
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type TypedRoute = {
 | 
					 | 
				
			||||||
  type: PathType;
 | 
					 | 
				
			||||||
  route: Route;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,13 @@
 | 
				
			||||||
import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
 | 
					import { Frequency, Role } from '@modules/ad/core/domain/ad.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 { ScheduleItem } from '../match.query';
 | 
				
			||||||
import { Waypoint } from '../../../types/waypoint.type';
 | 
					import { Waypoint } from '../../../types/waypoint.type';
 | 
				
			||||||
import { Point } from '../../../types/point.type';
 | 
					import { Point } from '../../../types/point.type';
 | 
				
			||||||
 | 
					import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class PassengerOrientedSelector extends Selector {
 | 
					export class PassengerOrientedSelector extends Selector {
 | 
				
			||||||
  select = async (): Promise<Candidate[]> => {
 | 
					  select = async (): Promise<CandidateEntity[]> => {
 | 
				
			||||||
    const queryStringRoles: QueryStringRole[] = [];
 | 
					    const queryStringRoles: QueryStringRole[] = [];
 | 
				
			||||||
    if (this.query.driver)
 | 
					    if (this.query.driver)
 | 
				
			||||||
      queryStringRoles.push({
 | 
					      queryStringRoles.push({
 | 
				
			||||||
| 
						 | 
					@ -32,14 +32,11 @@ export class PassengerOrientedSelector extends Selector {
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
      .map((adsRole: AdsRole) =>
 | 
					      .map((adsRole: AdsRole) =>
 | 
				
			||||||
        adsRole.ads.map(
 | 
					        adsRole.ads.map((adReadModel: AdReadModel) =>
 | 
				
			||||||
          (adReadModel: AdReadModel) =>
 | 
					          CandidateEntity.create({
 | 
				
			||||||
            <Candidate>{
 | 
					            id: adReadModel.uuid,
 | 
				
			||||||
              ad: {
 | 
					            role: adsRole.role,
 | 
				
			||||||
                id: adReadModel.uuid,
 | 
					          }),
 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
              role: adsRole.role,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
      .flat();
 | 
					      .flat();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,8 @@ export enum AlgorithmType {
 | 
				
			||||||
export type Candidate = {
 | 
					export type Candidate = {
 | 
				
			||||||
  ad: Ad;
 | 
					  ad: Ad;
 | 
				
			||||||
  role: Role;
 | 
					  role: Role;
 | 
				
			||||||
  waypoints: Waypoint[];
 | 
					  driverWaypoints: Waypoint[];
 | 
				
			||||||
 | 
					  crewWaypoints: Waypoint[];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type Ad = {
 | 
					export type Ad = {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
import { Point } from './point.type';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A carpool route is a route with distance and duration as driver and / or passenger
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type Route = {
 | 
					 | 
				
			||||||
  driverDistance?: number;
 | 
					 | 
				
			||||||
  driverDuration?: number;
 | 
					 | 
				
			||||||
  passengerDistance?: number;
 | 
					 | 
				
			||||||
  passengerDuration?: number;
 | 
					 | 
				
			||||||
  fwdAzimuth: number;
 | 
					 | 
				
			||||||
  backAzimuth: number;
 | 
					 | 
				
			||||||
  points: Point[];
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { CandidateProps, CreateCandidateProps } from './candidate.types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class CandidateEntity extends AggregateRoot<CandidateProps> {
 | 
				
			||||||
 | 
					  protected readonly _id: AggregateID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static create = (create: CreateCandidateProps): CandidateEntity => {
 | 
				
			||||||
 | 
					    const props: CandidateProps = { ...create };
 | 
				
			||||||
 | 
					    return new CandidateEntity({ id: create.id, props });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validate(): void {
 | 
				
			||||||
 | 
					    // entity business rules validation to protect it's invariant before saving entity to a database
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					import { Role } from './ad.types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// All properties that a Candidate has
 | 
				
			||||||
 | 
					export interface CandidateProps {
 | 
				
			||||||
 | 
					  role: Role;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Properties that are needed for a Candidate creation
 | 
				
			||||||
 | 
					export interface CreateCandidateProps {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					  role: Role;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Waypoint = {
 | 
				
			||||||
 | 
					  lon: number;
 | 
				
			||||||
 | 
					  lat: number;
 | 
				
			||||||
 | 
					  position: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,77 @@
 | 
				
			||||||
 | 
					import { Route } from '@modules/geography/core/domain/route.types';
 | 
				
			||||||
 | 
					import { Role } from './ad.types';
 | 
				
			||||||
 | 
					import { Waypoint } from './candidate.types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class PathCreator {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    private readonly roles: Role[],
 | 
				
			||||||
 | 
					    private readonly waypoints: Waypoint[],
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public getPaths = (): Path[] => {
 | 
				
			||||||
 | 
					    const paths: Path[] = [];
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      this.roles.includes(Role.DRIVER) &&
 | 
				
			||||||
 | 
					      this.roles.includes(Role.PASSENGER)
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      if (this.waypoints.length == 2) {
 | 
				
			||||||
 | 
					        // 2 points => same route for driver and passenger
 | 
				
			||||||
 | 
					        paths.push(this._createGenericPath());
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        paths.push(this._createDriverPath(), this._createPassengerPath());
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else if (this.roles.includes(Role.DRIVER)) {
 | 
				
			||||||
 | 
					      paths.push(this._createDriverPath());
 | 
				
			||||||
 | 
					    } else if (this.roles.includes(Role.PASSENGER)) {
 | 
				
			||||||
 | 
					      paths.push(this._createPassengerPath());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return paths;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _createGenericPath = (): Path =>
 | 
				
			||||||
 | 
					    this._createPath(this.waypoints, PathType.GENERIC);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _createDriverPath = (): Path =>
 | 
				
			||||||
 | 
					    this._createPath(this.waypoints, PathType.DRIVER);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _createPassengerPath = (): Path =>
 | 
				
			||||||
 | 
					    this._createPath(
 | 
				
			||||||
 | 
					      [this._firstWaypoint(), this._lastWaypoint()],
 | 
				
			||||||
 | 
					      PathType.PASSENGER,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _firstWaypoint = (): Waypoint =>
 | 
				
			||||||
 | 
					    this.waypoints.find(
 | 
				
			||||||
 | 
					      (waypoint: Waypoint) => waypoint.position == 0,
 | 
				
			||||||
 | 
					    ) as Waypoint;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _lastWaypoint = (): Waypoint =>
 | 
				
			||||||
 | 
					    this.waypoints.find(
 | 
				
			||||||
 | 
					      (waypoint: Waypoint) =>
 | 
				
			||||||
 | 
					        waypoint.position ==
 | 
				
			||||||
 | 
					        Math.max(
 | 
				
			||||||
 | 
					          ...this.waypoints.map((waypoint: Waypoint) => waypoint.position),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ) as Waypoint;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _createPath = (waypoints: Waypoint[], type: PathType): Path => ({
 | 
				
			||||||
 | 
					    type,
 | 
				
			||||||
 | 
					    waypoints,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Path = {
 | 
				
			||||||
 | 
					  type: PathType;
 | 
				
			||||||
 | 
					  waypoints: Waypoint[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type TypedRoute = {
 | 
				
			||||||
 | 
					  type: PathType;
 | 
				
			||||||
 | 
					  route: Route;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum PathType {
 | 
				
			||||||
 | 
					  GENERIC = 'generic',
 | 
				
			||||||
 | 
					  DRIVER = 'driver',
 | 
				
			||||||
 | 
					  PASSENGER = 'passenger',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -60,27 +60,40 @@ const mockAdRepository = {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mockRouteProvider: RouteProviderPort = {
 | 
					const mockRouteProvider: RouteProviderPort = {
 | 
				
			||||||
  getBasic: jest.fn().mockImplementation(() => ({
 | 
					  getBasic: jest
 | 
				
			||||||
    distance: 350101,
 | 
					    .fn()
 | 
				
			||||||
    duration: 14422,
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
    fwdAzimuth: 273,
 | 
					      throw new Error();
 | 
				
			||||||
    backAzimuth: 93,
 | 
					    })
 | 
				
			||||||
    distanceAzimuth: 336544,
 | 
					    .mockImplementationOnce(() => ({
 | 
				
			||||||
    points: [
 | 
					      distance: 350101,
 | 
				
			||||||
      {
 | 
					      duration: 14422,
 | 
				
			||||||
        lon: 6.1765102,
 | 
					      fwdAzimuth: 273,
 | 
				
			||||||
        lat: 48.689445,
 | 
					      backAzimuth: 93,
 | 
				
			||||||
      },
 | 
					      distanceAzimuth: 336544,
 | 
				
			||||||
      {
 | 
					      points: undefined,
 | 
				
			||||||
        lon: 4.984578,
 | 
					    }))
 | 
				
			||||||
        lat: 48.725687,
 | 
					    .mockImplementation(() => ({
 | 
				
			||||||
      },
 | 
					      distance: 350101,
 | 
				
			||||||
      {
 | 
					      duration: 14422,
 | 
				
			||||||
        lon: 2.3522,
 | 
					      fwdAzimuth: 273,
 | 
				
			||||||
        lat: 48.8566,
 | 
					      backAzimuth: 93,
 | 
				
			||||||
      },
 | 
					      distanceAzimuth: 336544,
 | 
				
			||||||
    ],
 | 
					      points: [
 | 
				
			||||||
  })),
 | 
					        {
 | 
				
			||||||
 | 
					          lon: 6.1765102,
 | 
				
			||||||
 | 
					          lat: 48.689445,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          lon: 4.984578,
 | 
				
			||||||
 | 
					          lat: 48.725687,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          lon: 2.3522,
 | 
				
			||||||
 | 
					          lat: 48.8566,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    })),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('create-ad.service', () => {
 | 
					describe('create-ad.service', () => {
 | 
				
			||||||
| 
						 | 
					@ -110,6 +123,16 @@ describe('create-ad.service', () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('execution', () => {
 | 
					  describe('execution', () => {
 | 
				
			||||||
    const createAdCommand = new CreateAdCommand(createAdProps);
 | 
					    const createAdCommand = new CreateAdCommand(createAdProps);
 | 
				
			||||||
 | 
					    it('should throw an error if route cant be computed', async () => {
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        createAdService.execute(createAdCommand),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(Error);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should throw an error if route is corrupted', async () => {
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        createAdService.execute(createAdCommand),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(Error);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('should create a new ad', async () => {
 | 
					    it('should create a new ad', async () => {
 | 
				
			||||||
      AdEntity.create = jest.fn().mockReturnValue({
 | 
					      AdEntity.create = jest.fn().mockReturnValue({
 | 
				
			||||||
        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
					        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
				
			||||||
| 
						 | 
					@ -120,17 +143,11 @@ describe('create-ad.service', () => {
 | 
				
			||||||
      expect(result).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da');
 | 
					      expect(result).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    it('should throw an error if something bad happens', async () => {
 | 
					    it('should throw an error if something bad happens', async () => {
 | 
				
			||||||
      AdEntity.create = jest.fn().mockReturnValue({
 | 
					 | 
				
			||||||
        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      await expect(
 | 
					      await expect(
 | 
				
			||||||
        createAdService.execute(createAdCommand),
 | 
					        createAdService.execute(createAdCommand),
 | 
				
			||||||
      ).rejects.toBeInstanceOf(Error);
 | 
					      ).rejects.toBeInstanceOf(Error);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    it('should throw an exception if Ad already exists', async () => {
 | 
					    it('should throw an exception if Ad already exists', async () => {
 | 
				
			||||||
      AdEntity.create = jest.fn().mockReturnValue({
 | 
					 | 
				
			||||||
        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      await expect(
 | 
					      await expect(
 | 
				
			||||||
        createAdService.execute(createAdCommand),
 | 
					        createAdService.execute(createAdCommand),
 | 
				
			||||||
      ).rejects.toBeInstanceOf(AdAlreadyExistsException);
 | 
					      ).rejects.toBeInstanceOf(AdAlreadyExistsException);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,9 @@
 | 
				
			||||||
import { PassengerOrientedGeoFilter } from '@modules/ad/core/application/queries/match/filter/passenger-oriented-geo.filter';
 | 
					import { PassengerOrientedGeoFilter } from '@modules/ad/core/application/queries/match/filter/passenger-oriented-geo.filter';
 | 
				
			||||||
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
 | 
					import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
 | 
				
			||||||
import {
 | 
					import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
 | 
				
			||||||
  AlgorithmType,
 | 
					 | 
				
			||||||
  Candidate,
 | 
					 | 
				
			||||||
} 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 { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const originWaypoint: Waypoint = {
 | 
					const originWaypoint: Waypoint = {
 | 
				
			||||||
  position: 0,
 | 
					  position: 0,
 | 
				
			||||||
| 
						 | 
					@ -42,50 +40,22 @@ const matchQuery = new MatchQuery({
 | 
				
			||||||
  waypoints: [originWaypoint, destinationWaypoint],
 | 
					  waypoints: [originWaypoint, destinationWaypoint],
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const candidates: Candidate[] = [
 | 
					const candidates: CandidateEntity[] = [
 | 
				
			||||||
  {
 | 
					  CandidateEntity.create({
 | 
				
			||||||
    ad: {
 | 
					    id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
 | 
				
			||||||
      id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    role: Role.DRIVER,
 | 
					    role: Role.DRIVER,
 | 
				
			||||||
    waypoints: [
 | 
					  }),
 | 
				
			||||||
      {
 | 
					  CandidateEntity.create({
 | 
				
			||||||
        position: 0,
 | 
					    id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
 | 
				
			||||||
        lat: 48.68874,
 | 
					 | 
				
			||||||
        lon: 6.18546,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        position: 1,
 | 
					 | 
				
			||||||
        lat: 48.87845,
 | 
					 | 
				
			||||||
        lon: 2.36547,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    ad: {
 | 
					 | 
				
			||||||
      id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    role: Role.PASSENGER,
 | 
					    role: Role.PASSENGER,
 | 
				
			||||||
    waypoints: [
 | 
					  }),
 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        position: 0,
 | 
					 | 
				
			||||||
        lat: 48.69844,
 | 
					 | 
				
			||||||
        lon: 6.168484,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        position: 1,
 | 
					 | 
				
			||||||
        lat: 48.855648,
 | 
					 | 
				
			||||||
        lon: 2.34645,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Passenger oriented geo filter', () => {
 | 
					describe('Passenger oriented geo filter', () => {
 | 
				
			||||||
  it('should filter candidates', async () => {
 | 
					  it('should filter candidates', async () => {
 | 
				
			||||||
    const passengerOrientedGeoFilter: PassengerOrientedGeoFilter =
 | 
					    const passengerOrientedGeoFilter: PassengerOrientedGeoFilter =
 | 
				
			||||||
      new PassengerOrientedGeoFilter(matchQuery);
 | 
					      new PassengerOrientedGeoFilter(matchQuery);
 | 
				
			||||||
    const filteredCandidates: Candidate[] =
 | 
					    const filteredCandidates: CandidateEntity[] =
 | 
				
			||||||
      await passengerOrientedGeoFilter.filter(candidates);
 | 
					      await passengerOrientedGeoFilter.filter(candidates);
 | 
				
			||||||
    expect(filteredCandidates.length).toBe(2);
 | 
					    expect(filteredCandidates.length).toBe(2);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,10 @@
 | 
				
			||||||
import { AdRepositoryPort } from '@modules/ad/core/application/ports/ad.repository.port';
 | 
					import { AdRepositoryPort } from '@modules/ad/core/application/ports/ad.repository.port';
 | 
				
			||||||
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
 | 
					import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
 | 
				
			||||||
import { PassengerOrientedSelector } from '@modules/ad/core/application/queries/match/selector/passenger-oriented.selector';
 | 
					import { PassengerOrientedSelector } from '@modules/ad/core/application/queries/match/selector/passenger-oriented.selector';
 | 
				
			||||||
import {
 | 
					import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
 | 
				
			||||||
  AlgorithmType,
 | 
					 | 
				
			||||||
  Candidate,
 | 
					 | 
				
			||||||
} 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 } from '@modules/ad/core/domain/ad.types';
 | 
					import { Frequency } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
 | 
					import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const originWaypoint: Waypoint = {
 | 
					const originWaypoint: Waypoint = {
 | 
				
			||||||
  position: 0,
 | 
					  position: 0,
 | 
				
			||||||
| 
						 | 
					@ -136,7 +134,8 @@ describe('Passenger oriented selector', () => {
 | 
				
			||||||
  it('should select candidates', async () => {
 | 
					  it('should select candidates', async () => {
 | 
				
			||||||
    const passengerOrientedSelector: PassengerOrientedSelector =
 | 
					    const passengerOrientedSelector: PassengerOrientedSelector =
 | 
				
			||||||
      new PassengerOrientedSelector(matchQuery, mockMatcherRepository);
 | 
					      new PassengerOrientedSelector(matchQuery, mockMatcherRepository);
 | 
				
			||||||
    const candidates: Candidate[] = await passengerOrientedSelector.select();
 | 
					    const candidates: CandidateEntity[] =
 | 
				
			||||||
 | 
					      await passengerOrientedSelector.select();
 | 
				
			||||||
    expect(candidates.length).toBe(2);
 | 
					    expect(candidates.length).toBe(2);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,9 @@
 | 
				
			||||||
import { PassengerOrientedWaypointsCompleter } from '@modules/ad/core/application/queries/match/completer/passenger-oriented-waypoints.completer';
 | 
					import { PassengerOrientedWaypointsCompleter } from '@modules/ad/core/application/queries/match/completer/passenger-oriented-waypoints.completer';
 | 
				
			||||||
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
 | 
					import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
 | 
				
			||||||
import {
 | 
					import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
 | 
				
			||||||
  AlgorithmType,
 | 
					 | 
				
			||||||
  Candidate,
 | 
					 | 
				
			||||||
} 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 { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const originWaypoint: Waypoint = {
 | 
					const originWaypoint: Waypoint = {
 | 
				
			||||||
  position: 0,
 | 
					  position: 0,
 | 
				
			||||||
| 
						 | 
					@ -42,50 +40,22 @@ const matchQuery = new MatchQuery({
 | 
				
			||||||
  waypoints: [originWaypoint, destinationWaypoint],
 | 
					  waypoints: [originWaypoint, destinationWaypoint],
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const candidates: Candidate[] = [
 | 
					const candidates: CandidateEntity[] = [
 | 
				
			||||||
  {
 | 
					  CandidateEntity.create({
 | 
				
			||||||
    ad: {
 | 
					    id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
 | 
				
			||||||
      id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    role: Role.DRIVER,
 | 
					    role: Role.DRIVER,
 | 
				
			||||||
    waypoints: [
 | 
					  }),
 | 
				
			||||||
      {
 | 
					  CandidateEntity.create({
 | 
				
			||||||
        position: 0,
 | 
					    id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
 | 
				
			||||||
        lat: 48.69,
 | 
					 | 
				
			||||||
        lon: 6.18,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        position: 1,
 | 
					 | 
				
			||||||
        lat: 48.87,
 | 
					 | 
				
			||||||
        lon: 2.37,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    ad: {
 | 
					 | 
				
			||||||
      id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    role: Role.PASSENGER,
 | 
					    role: Role.PASSENGER,
 | 
				
			||||||
    waypoints: [
 | 
					  }),
 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        position: 0,
 | 
					 | 
				
			||||||
        lat: 48.63584,
 | 
					 | 
				
			||||||
        lon: 6.148754,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        position: 1,
 | 
					 | 
				
			||||||
        lat: 48.89874,
 | 
					 | 
				
			||||||
        lon: 2.368745,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Passenger oriented waypoints completer', () => {
 | 
					describe('Passenger oriented waypoints completer', () => {
 | 
				
			||||||
  it('should complete candidates', async () => {
 | 
					  it('should complete candidates', async () => {
 | 
				
			||||||
    const passengerOrientedWaypointsCompleter: PassengerOrientedWaypointsCompleter =
 | 
					    const passengerOrientedWaypointsCompleter: PassengerOrientedWaypointsCompleter =
 | 
				
			||||||
      new PassengerOrientedWaypointsCompleter(matchQuery);
 | 
					      new PassengerOrientedWaypointsCompleter(matchQuery);
 | 
				
			||||||
    const completedCandidates: Candidate[] =
 | 
					    const completedCandidates: CandidateEntity[] =
 | 
				
			||||||
      await passengerOrientedWaypointsCompleter.complete(candidates);
 | 
					      await passengerOrientedWaypointsCompleter.complete(candidates);
 | 
				
			||||||
    expect(completedCandidates.length).toBe(2);
 | 
					    expect(completedCandidates.length).toBe(2);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,78 @@
 | 
				
			||||||
 | 
					import { Role } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
 | 
					import { Waypoint } from '@modules/ad/core/domain/candidate.types';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Path,
 | 
				
			||||||
 | 
					  PathCreator,
 | 
				
			||||||
 | 
					  PathType,
 | 
				
			||||||
 | 
					} from '@modules/ad/core/domain/patch-creator.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const originWaypoint: Waypoint = {
 | 
				
			||||||
 | 
					  position: 0,
 | 
				
			||||||
 | 
					  lat: 48.689445,
 | 
				
			||||||
 | 
					  lon: 6.17651,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					const destinationWaypoint: Waypoint = {
 | 
				
			||||||
 | 
					  position: 1,
 | 
				
			||||||
 | 
					  lat: 48.8566,
 | 
				
			||||||
 | 
					  lon: 2.3522,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					const intermediateWaypoint: Waypoint = {
 | 
				
			||||||
 | 
					  position: 1,
 | 
				
			||||||
 | 
					  lat: 48.74488,
 | 
				
			||||||
 | 
					  lon: 4.8972,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Path Creator Service', () => {
 | 
				
			||||||
 | 
					  it('should create a path for a driver only', () => {
 | 
				
			||||||
 | 
					    const pathCreator: PathCreator = new PathCreator(
 | 
				
			||||||
 | 
					      [Role.DRIVER],
 | 
				
			||||||
 | 
					      [originWaypoint, destinationWaypoint],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    const paths: Path[] = pathCreator.getPaths();
 | 
				
			||||||
 | 
					    expect(paths).toHaveLength(1);
 | 
				
			||||||
 | 
					    expect(paths[0].type).toBe(PathType.DRIVER);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  it('should create a path for a passenger only', () => {
 | 
				
			||||||
 | 
					    const pathCreator: PathCreator = new PathCreator(
 | 
				
			||||||
 | 
					      [Role.PASSENGER],
 | 
				
			||||||
 | 
					      [originWaypoint, destinationWaypoint],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    const paths: Path[] = pathCreator.getPaths();
 | 
				
			||||||
 | 
					    expect(paths).toHaveLength(1);
 | 
				
			||||||
 | 
					    expect(paths[0].type).toBe(PathType.PASSENGER);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  it('should create a single path for a driver and passenger', () => {
 | 
				
			||||||
 | 
					    const pathCreator: PathCreator = new PathCreator(
 | 
				
			||||||
 | 
					      [Role.DRIVER, Role.PASSENGER],
 | 
				
			||||||
 | 
					      [originWaypoint, destinationWaypoint],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    const paths: Path[] = pathCreator.getPaths();
 | 
				
			||||||
 | 
					    expect(paths).toHaveLength(1);
 | 
				
			||||||
 | 
					    expect(paths[0].type).toBe(PathType.GENERIC);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  it('should create two different paths for a driver and passenger with intermediate waypoint', () => {
 | 
				
			||||||
 | 
					    const pathCreator: PathCreator = new PathCreator(
 | 
				
			||||||
 | 
					      [Role.DRIVER, Role.PASSENGER],
 | 
				
			||||||
 | 
					      [
 | 
				
			||||||
 | 
					        originWaypoint,
 | 
				
			||||||
 | 
					        intermediateWaypoint,
 | 
				
			||||||
 | 
					        { ...destinationWaypoint, position: 2 },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    const paths: Path[] = pathCreator.getPaths();
 | 
				
			||||||
 | 
					    expect(paths).toHaveLength(2);
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      paths.filter((path: Path) => path.type == PathType.DRIVER),
 | 
				
			||||||
 | 
					    ).toHaveLength(1);
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      paths.filter((path: Path) => path.type == PathType.DRIVER)[0].waypoints,
 | 
				
			||||||
 | 
					    ).toHaveLength(3);
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      paths.filter((path: Path) => path.type == PathType.PASSENGER),
 | 
				
			||||||
 | 
					    ).toHaveLength(1);
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      paths.filter((path: Path) => path.type == PathType.PASSENGER)[0]
 | 
				
			||||||
 | 
					        .waypoints,
 | 
				
			||||||
 | 
					    ).toHaveLength(2);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
		Loading…
	
		Reference in New Issue