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,
|
RouteCompleterType,
|
||||||
} from './completer/route.completer';
|
} from './completer/route.completer';
|
||||||
import { JourneyCompleter } from './completer/journey.completer';
|
import { JourneyCompleter } from './completer/journey.completer';
|
||||||
|
import { JourneyFilter } from './filter/journey.filter';
|
||||||
|
|
||||||
export class PassengerOrientedAlgorithm extends Algorithm {
|
export class PassengerOrientedAlgorithm extends Algorithm {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -23,6 +24,7 @@ export class PassengerOrientedAlgorithm extends Algorithm {
|
||||||
new PassengerOrientedGeoFilter(query),
|
new PassengerOrientedGeoFilter(query),
|
||||||
new RouteCompleter(query, RouteCompleterType.DETAILED),
|
new RouteCompleter(query, RouteCompleterType.DETAILED),
|
||||||
new JourneyCompleter(query),
|
new JourneyCompleter(query),
|
||||||
|
new JourneyFilter(query),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,15 +44,22 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
||||||
isDetourValid = (): boolean =>
|
isDetourValid = (): boolean =>
|
||||||
this._validateDistanceDetour() && this._validateDurationDetour();
|
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 !)
|
* 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 !
|
* This is a tedious process : additional information can be found in deeper methods !
|
||||||
*/
|
*/
|
||||||
createJourneys = (): CandidateEntity => {
|
createJourneys = (): CandidateEntity => {
|
||||||
this.props.journeys = this.props.driverSchedule.map(
|
this.props.journeys = this.props.driverSchedule
|
||||||
(driverScheduleItem: ScheduleItem) =>
|
// first we create the journeys
|
||||||
|
.map((driverScheduleItem: ScheduleItem) =>
|
||||||
this._createJourney(driverScheduleItem),
|
this._createJourney(driverScheduleItem),
|
||||||
);
|
)
|
||||||
|
// then we filter the ones with invalid pickups
|
||||||
|
.filter((journey: Journey) => journey.hasValidPickUp());
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
|
import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
|
||||||
import { JourneyItem } from './journey-item.value-object';
|
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:
|
/** Note:
|
||||||
* Value Objects with multiple properties can contain
|
* Value Objects with multiple properties can contain
|
||||||
|
@ -25,6 +28,37 @@ export class Journey extends ValueObject<JourneyProps> {
|
||||||
return this.props.journeyItems;
|
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 {
|
protected validate(props: JourneyProps): void {
|
||||||
if (props.firstDate.getUTCDay() != props.lastDate.getUTCDay())
|
if (props.firstDate.getUTCDay() != props.lastDate.getUTCDay())
|
||||||
throw new ArgumentInvalidException(
|
throw new ArgumentInvalidException(
|
||||||
|
|
|
@ -103,7 +103,7 @@ const schedule4: ScheduleItemProps[] = [
|
||||||
const schedule5: ScheduleItemProps[] = [
|
const schedule5: ScheduleItemProps[] = [
|
||||||
{
|
{
|
||||||
day: 0,
|
day: 0,
|
||||||
time: '00:10',
|
time: '00:02',
|
||||||
margin: 900,
|
margin: 900,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -121,7 +121,15 @@ const schedule6: ScheduleItemProps[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
day: 6,
|
day: 6,
|
||||||
time: '23:45',
|
time: '23:57',
|
||||||
|
margin: 900,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const schedule7: ScheduleItemProps[] = [
|
||||||
|
{
|
||||||
|
day: 4,
|
||||||
|
time: '19:00',
|
||||||
margin: 900,
|
margin: 900,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -379,7 +387,7 @@ describe('Candidate entity', () => {
|
||||||
.setCarpoolPath(carpoolPath2)
|
.setCarpoolPath(carpoolPath2)
|
||||||
.setSteps(steps)
|
.setSteps(steps)
|
||||||
.createJourneys();
|
.createJourneys();
|
||||||
expect(candidateEntity.getProps().journeys).toHaveLength(5);
|
expect(candidateEntity.getProps().journeys).toHaveLength(4);
|
||||||
expect(
|
expect(
|
||||||
(
|
(
|
||||||
candidateEntity.getProps().journeys as Journey[]
|
candidateEntity.getProps().journeys as Journey[]
|
||||||
|
@ -415,7 +423,7 @@ describe('Candidate entity', () => {
|
||||||
.setCarpoolPath(carpoolPath2)
|
.setCarpoolPath(carpoolPath2)
|
||||||
.setSteps(steps)
|
.setSteps(steps)
|
||||||
.createJourneys();
|
.createJourneys();
|
||||||
expect(candidateEntity.getProps().journeys).toHaveLength(2);
|
expect(candidateEntity.getProps().journeys).toHaveLength(1);
|
||||||
expect(
|
expect(
|
||||||
(candidateEntity.getProps().journeys as Journey[])[0].journeyItems[1]
|
(candidateEntity.getProps().journeys as Journey[])[0].journeyItems[1]
|
||||||
.actorTimes[0].target,
|
.actorTimes[0].target,
|
||||||
|
@ -429,7 +437,7 @@ describe('Candidate entity', () => {
|
||||||
(
|
(
|
||||||
candidateEntity.getProps().journeys as Journey[]
|
candidateEntity.getProps().journeys as Journey[]
|
||||||
)[0].journeyItems[1].actorTimes[0].firstDatetime.getUTCMinutes(),
|
)[0].journeyItems[1].actorTimes[0].firstDatetime.getUTCMinutes(),
|
||||||
).toBe(30);
|
).toBe(22);
|
||||||
expect(
|
expect(
|
||||||
(
|
(
|
||||||
candidateEntity.getProps().journeys as Journey[]
|
candidateEntity.getProps().journeys as Journey[]
|
||||||
|
@ -439,7 +447,51 @@ describe('Candidate entity', () => {
|
||||||
(
|
(
|
||||||
candidateEntity.getProps().journeys as Journey[]
|
candidateEntity.getProps().journeys as Journey[]
|
||||||
)[0].journeyItems[1].actorTimes[1].firstMinDatetime.getUTCMinutes(),
|
)[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