empty journey filter
This commit is contained in:
		
							parent
							
								
									d8df086c6d
								
							
						
					
					
						commit
						eafa3c8bdd
					
				| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
 | 
			
		||||
import { Filter } from './filter.abstract';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Filter candidates with empty journeys
 | 
			
		||||
 */
 | 
			
		||||
export class JourneyFilter extends Filter {
 | 
			
		||||
  filter = async (candidates: CandidateEntity[]): Promise<CandidateEntity[]> =>
 | 
			
		||||
    candidates.filter((candidate: CandidateEntity) => candidate.hasJourneys());
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +9,7 @@ import {
 | 
			
		|||
  RouteCompleterType,
 | 
			
		||||
} from './completer/route.completer';
 | 
			
		||||
import { JourneyCompleter } from './completer/journey.completer';
 | 
			
		||||
import { JourneyFilter } from './filter/journey.filter';
 | 
			
		||||
 | 
			
		||||
export class PassengerOrientedAlgorithm extends Algorithm {
 | 
			
		||||
  constructor(
 | 
			
		||||
| 
						 | 
				
			
			@ -23,6 +24,7 @@ export class PassengerOrientedAlgorithm extends Algorithm {
 | 
			
		|||
      new PassengerOrientedGeoFilter(query),
 | 
			
		||||
      new RouteCompleter(query, RouteCompleterType.DETAILED),
 | 
			
		||||
      new JourneyCompleter(query),
 | 
			
		||||
      new JourneyFilter(query),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,15 +44,22 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
 | 
			
		|||
  isDetourValid = (): boolean =>
 | 
			
		||||
    this._validateDistanceDetour() && this._validateDurationDetour();
 | 
			
		||||
 | 
			
		||||
  hasJourneys = (): boolean =>
 | 
			
		||||
    this.getProps().journeys !== undefined &&
 | 
			
		||||
    (this.getProps().journeys as Journey[]).length > 0;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create the journeys based on the driver schedule (the driver 'drives' the carpool !)
 | 
			
		||||
   * This is a tedious process : additional information can be found in deeper methods !
 | 
			
		||||
   */
 | 
			
		||||
  createJourneys = (): CandidateEntity => {
 | 
			
		||||
    this.props.journeys = this.props.driverSchedule.map(
 | 
			
		||||
      (driverScheduleItem: ScheduleItem) =>
 | 
			
		||||
    this.props.journeys = this.props.driverSchedule
 | 
			
		||||
      // first we create the journeys
 | 
			
		||||
      .map((driverScheduleItem: ScheduleItem) =>
 | 
			
		||||
        this._createJourney(driverScheduleItem),
 | 
			
		||||
    );
 | 
			
		||||
      )
 | 
			
		||||
      // then we filter the ones with invalid pickups
 | 
			
		||||
      .filter((journey: Journey) => journey.hasValidPickUp());
 | 
			
		||||
    return this;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,8 @@
 | 
			
		|||
import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
 | 
			
		||||
import { JourneyItem } from './journey-item.value-object';
 | 
			
		||||
import { ActorTime } from './actor-time.value-object';
 | 
			
		||||
import { Role } from '../ad.types';
 | 
			
		||||
import { Target } from '../candidate.types';
 | 
			
		||||
 | 
			
		||||
/** Note:
 | 
			
		||||
 * Value Objects with multiple properties can contain
 | 
			
		||||
| 
						 | 
				
			
			@ -25,6 +28,37 @@ export class Journey extends ValueObject<JourneyProps> {
 | 
			
		|||
    return this.props.journeyItems;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  hasValidPickUp = (): boolean => {
 | 
			
		||||
    const passengerDepartureJourneyItem: JourneyItem = this.journeyItems.find(
 | 
			
		||||
      (journeyItem: JourneyItem) =>
 | 
			
		||||
        journeyItem.actorTimes.find(
 | 
			
		||||
          (actorTime: ActorTime) =>
 | 
			
		||||
            actorTime.role == Role.PASSENGER &&
 | 
			
		||||
            actorTime.target == Target.START,
 | 
			
		||||
        ) as ActorTime,
 | 
			
		||||
    ) as JourneyItem;
 | 
			
		||||
    const passengerDepartureActorTime =
 | 
			
		||||
      passengerDepartureJourneyItem.actorTimes.find(
 | 
			
		||||
        (actorTime: ActorTime) =>
 | 
			
		||||
          actorTime.role == Role.PASSENGER && actorTime.target == Target.START,
 | 
			
		||||
      ) as ActorTime;
 | 
			
		||||
    const driverNeutralActorTime =
 | 
			
		||||
      passengerDepartureJourneyItem.actorTimes.find(
 | 
			
		||||
        (actorTime: ActorTime) =>
 | 
			
		||||
          actorTime.role == Role.DRIVER && actorTime.target == Target.NEUTRAL,
 | 
			
		||||
      ) as ActorTime;
 | 
			
		||||
    return (
 | 
			
		||||
      (passengerDepartureActorTime.firstMinDatetime <=
 | 
			
		||||
        driverNeutralActorTime.firstMaxDatetime &&
 | 
			
		||||
        driverNeutralActorTime.firstMaxDatetime <=
 | 
			
		||||
          passengerDepartureActorTime.firstMaxDatetime) ||
 | 
			
		||||
      (passengerDepartureActorTime.firstMinDatetime <=
 | 
			
		||||
        driverNeutralActorTime.firstMinDatetime &&
 | 
			
		||||
        driverNeutralActorTime.firstMinDatetime <=
 | 
			
		||||
          passengerDepartureActorTime.firstMaxDatetime)
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  protected validate(props: JourneyProps): void {
 | 
			
		||||
    if (props.firstDate.getUTCDay() != props.lastDate.getUTCDay())
 | 
			
		||||
      throw new ArgumentInvalidException(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -103,7 +103,7 @@ const schedule4: ScheduleItemProps[] = [
 | 
			
		|||
const schedule5: ScheduleItemProps[] = [
 | 
			
		||||
  {
 | 
			
		||||
    day: 0,
 | 
			
		||||
    time: '00:10',
 | 
			
		||||
    time: '00:02',
 | 
			
		||||
    margin: 900,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
| 
						 | 
				
			
			@ -121,7 +121,15 @@ const schedule6: ScheduleItemProps[] = [
 | 
			
		|||
  },
 | 
			
		||||
  {
 | 
			
		||||
    day: 6,
 | 
			
		||||
    time: '23:45',
 | 
			
		||||
    time: '23:57',
 | 
			
		||||
    margin: 900,
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const schedule7: ScheduleItemProps[] = [
 | 
			
		||||
  {
 | 
			
		||||
    day: 4,
 | 
			
		||||
    time: '19:00',
 | 
			
		||||
    margin: 900,
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -379,7 +387,7 @@ describe('Candidate entity', () => {
 | 
			
		|||
        .setCarpoolPath(carpoolPath2)
 | 
			
		||||
        .setSteps(steps)
 | 
			
		||||
        .createJourneys();
 | 
			
		||||
      expect(candidateEntity.getProps().journeys).toHaveLength(5);
 | 
			
		||||
      expect(candidateEntity.getProps().journeys).toHaveLength(4);
 | 
			
		||||
      expect(
 | 
			
		||||
        (
 | 
			
		||||
          candidateEntity.getProps().journeys as Journey[]
 | 
			
		||||
| 
						 | 
				
			
			@ -415,7 +423,7 @@ describe('Candidate entity', () => {
 | 
			
		|||
        .setCarpoolPath(carpoolPath2)
 | 
			
		||||
        .setSteps(steps)
 | 
			
		||||
        .createJourneys();
 | 
			
		||||
      expect(candidateEntity.getProps().journeys).toHaveLength(2);
 | 
			
		||||
      expect(candidateEntity.getProps().journeys).toHaveLength(1);
 | 
			
		||||
      expect(
 | 
			
		||||
        (candidateEntity.getProps().journeys as Journey[])[0].journeyItems[1]
 | 
			
		||||
          .actorTimes[0].target,
 | 
			
		||||
| 
						 | 
				
			
			@ -429,7 +437,7 @@ describe('Candidate entity', () => {
 | 
			
		|||
        (
 | 
			
		||||
          candidateEntity.getProps().journeys as Journey[]
 | 
			
		||||
        )[0].journeyItems[1].actorTimes[0].firstDatetime.getUTCMinutes(),
 | 
			
		||||
      ).toBe(30);
 | 
			
		||||
      ).toBe(22);
 | 
			
		||||
      expect(
 | 
			
		||||
        (
 | 
			
		||||
          candidateEntity.getProps().journeys as Journey[]
 | 
			
		||||
| 
						 | 
				
			
			@ -439,7 +447,51 @@ describe('Candidate entity', () => {
 | 
			
		|||
        (
 | 
			
		||||
          candidateEntity.getProps().journeys as Journey[]
 | 
			
		||||
        )[0].journeyItems[1].actorTimes[1].firstMinDatetime.getUTCMinutes(),
 | 
			
		||||
      ).toBe(30);
 | 
			
		||||
      ).toBe(42);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not create journeys if dates does not match', () => {
 | 
			
		||||
      const candidateEntity: CandidateEntity = CandidateEntity.create({
 | 
			
		||||
        id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
 | 
			
		||||
        role: Role.PASSENGER,
 | 
			
		||||
        dateInterval: {
 | 
			
		||||
          lowerDate: '2023-09-01',
 | 
			
		||||
          higherDate: '2024-09-01',
 | 
			
		||||
        },
 | 
			
		||||
        driverWaypoints: waypointsSet1,
 | 
			
		||||
        passengerWaypoints: waypointsSet2,
 | 
			
		||||
        driverDistance: 350145,
 | 
			
		||||
        driverDuration: 13548,
 | 
			
		||||
        driverSchedule: schedule1,
 | 
			
		||||
        passengerSchedule: schedule7,
 | 
			
		||||
        spacetimeDetourRatio,
 | 
			
		||||
      })
 | 
			
		||||
        .setCarpoolPath(carpoolPath2)
 | 
			
		||||
        .setSteps(steps)
 | 
			
		||||
        .createJourneys();
 | 
			
		||||
      expect(candidateEntity.getProps().journeys).toHaveLength(0);
 | 
			
		||||
      expect(candidateEntity.hasJourneys()).toBeFalsy();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not verify journeys if journeys is undefined', () => {
 | 
			
		||||
      const candidateEntity: CandidateEntity = CandidateEntity.create({
 | 
			
		||||
        id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
 | 
			
		||||
        role: Role.PASSENGER,
 | 
			
		||||
        dateInterval: {
 | 
			
		||||
          lowerDate: '2023-09-01',
 | 
			
		||||
          higherDate: '2024-09-01',
 | 
			
		||||
        },
 | 
			
		||||
        driverWaypoints: waypointsSet1,
 | 
			
		||||
        passengerWaypoints: waypointsSet2,
 | 
			
		||||
        driverDistance: 350145,
 | 
			
		||||
        driverDuration: 13548,
 | 
			
		||||
        driverSchedule: schedule1,
 | 
			
		||||
        passengerSchedule: schedule7,
 | 
			
		||||
        spacetimeDetourRatio,
 | 
			
		||||
      })
 | 
			
		||||
        .setCarpoolPath(carpoolPath2)
 | 
			
		||||
        .setSteps(steps);
 | 
			
		||||
      expect(candidateEntity.hasJourneys()).toBeFalsy();
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,117 @@
 | 
			
		|||
import { JourneyFilter } from '@modules/ad/core/application/queries/match/filter/journey.filter';
 | 
			
		||||
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
 | 
			
		||||
import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
 | 
			
		||||
import { Waypoint } from '@modules/ad/core/application/types/waypoint.type';
 | 
			
		||||
import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
 | 
			
		||||
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
 | 
			
		||||
 | 
			
		||||
const originWaypoint: Waypoint = {
 | 
			
		||||
  position: 0,
 | 
			
		||||
  lat: 48.689445,
 | 
			
		||||
  lon: 6.17651,
 | 
			
		||||
  houseNumber: '5',
 | 
			
		||||
  street: 'Avenue Foch',
 | 
			
		||||
  locality: 'Nancy',
 | 
			
		||||
  postalCode: '54000',
 | 
			
		||||
  country: 'France',
 | 
			
		||||
};
 | 
			
		||||
const destinationWaypoint: Waypoint = {
 | 
			
		||||
  position: 1,
 | 
			
		||||
  lat: 48.8566,
 | 
			
		||||
  lon: 2.3522,
 | 
			
		||||
  locality: 'Paris',
 | 
			
		||||
  postalCode: '75000',
 | 
			
		||||
  country: 'France',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const matchQuery = new MatchQuery(
 | 
			
		||||
  {
 | 
			
		||||
    algorithmType: AlgorithmType.PASSENGER_ORIENTED,
 | 
			
		||||
    driver: true,
 | 
			
		||||
    passenger: true,
 | 
			
		||||
    frequency: Frequency.PUNCTUAL,
 | 
			
		||||
    fromDate: '2023-08-28',
 | 
			
		||||
    toDate: '2023-08-28',
 | 
			
		||||
    schedule: [
 | 
			
		||||
      {
 | 
			
		||||
        time: '07:05',
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    strict: false,
 | 
			
		||||
    waypoints: [originWaypoint, destinationWaypoint],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    getBasic: jest.fn(),
 | 
			
		||||
    getDetailed: jest.fn(),
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const candidate: CandidateEntity = CandidateEntity.create({
 | 
			
		||||
  id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
 | 
			
		||||
  role: Role.DRIVER,
 | 
			
		||||
  dateInterval: {
 | 
			
		||||
    lowerDate: '2023-08-28',
 | 
			
		||||
    higherDate: '2023-08-28',
 | 
			
		||||
  },
 | 
			
		||||
  driverWaypoints: [
 | 
			
		||||
    {
 | 
			
		||||
      lat: 48.678454,
 | 
			
		||||
      lon: 6.189745,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      lat: 48.84877,
 | 
			
		||||
      lon: 2.398457,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  passengerWaypoints: [
 | 
			
		||||
    {
 | 
			
		||||
      lat: 48.689445,
 | 
			
		||||
      lon: 6.17651,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      lat: 48.8566,
 | 
			
		||||
      lon: 2.3522,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  driverDistance: 350145,
 | 
			
		||||
  driverDuration: 13548,
 | 
			
		||||
  driverSchedule: [
 | 
			
		||||
    {
 | 
			
		||||
      day: 0,
 | 
			
		||||
      time: '07:00',
 | 
			
		||||
      margin: 900,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  passengerSchedule: [
 | 
			
		||||
    {
 | 
			
		||||
      day: 0,
 | 
			
		||||
      time: '07:10',
 | 
			
		||||
      margin: 900,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  spacetimeDetourRatio: {
 | 
			
		||||
    maxDistanceDetourRatio: 0.3,
 | 
			
		||||
    maxDurationDetourRatio: 0.3,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe('Passenger oriented time filter', () => {
 | 
			
		||||
  it('should not filter valid candidates', async () => {
 | 
			
		||||
    const passengerOrientedTimeFilter: JourneyFilter = new JourneyFilter(
 | 
			
		||||
      matchQuery,
 | 
			
		||||
    );
 | 
			
		||||
    candidate.hasJourneys = () => true;
 | 
			
		||||
    const filteredCandidates: CandidateEntity[] =
 | 
			
		||||
      await passengerOrientedTimeFilter.filter([candidate]);
 | 
			
		||||
    expect(filteredCandidates.length).toBe(1);
 | 
			
		||||
  });
 | 
			
		||||
  it('should filter invalid candidates', async () => {
 | 
			
		||||
    const passengerOrientedTimeFilter: JourneyFilter = new JourneyFilter(
 | 
			
		||||
      matchQuery,
 | 
			
		||||
    );
 | 
			
		||||
    candidate.hasJourneys = () => false;
 | 
			
		||||
    const filteredCandidates: CandidateEntity[] =
 | 
			
		||||
      await passengerOrientedTimeFilter.filter([candidate]);
 | 
			
		||||
    expect(filteredCandidates.length).toBe(0);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
		Loading…
	
		Reference in New Issue