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