passenger oriented geo filter
This commit is contained in:
parent
067854b697
commit
4e118603f3
|
@ -3,5 +3,7 @@ import { Filter } from './filter.abstract';
|
||||||
|
|
||||||
export class PassengerOrientedGeoFilter extends Filter {
|
export class PassengerOrientedGeoFilter extends Filter {
|
||||||
filter = async (candidates: CandidateEntity[]): Promise<CandidateEntity[]> =>
|
filter = async (candidates: CandidateEntity[]): Promise<CandidateEntity[]> =>
|
||||||
candidates;
|
candidates.filter((candidate: CandidateEntity) =>
|
||||||
|
candidate.isDetourValid(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,12 @@ export class PassengerOrientedSelector extends Selector {
|
||||||
adsRole.role == Role.PASSENGER
|
adsRole.role == Role.PASSENGER
|
||||||
? (adEntity.getProps().driverDuration as number)
|
? (adEntity.getProps().driverDuration as number)
|
||||||
: (this.query.driverRoute?.duration as number),
|
: (this.query.driverRoute?.duration as number),
|
||||||
|
spacetimeDetourRatio: {
|
||||||
|
maxDistanceDetourRatio: this.query
|
||||||
|
.maxDetourDistanceRatio as number,
|
||||||
|
maxDurationDetourRatio: this.query
|
||||||
|
.maxDetourDurationRatio as number,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,15 +10,34 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
||||||
return new CandidateEntity({ id: create.id, props });
|
return new CandidateEntity({ id: create.id, props });
|
||||||
};
|
};
|
||||||
|
|
||||||
setCarpoolPath = (waySteps: WayStepProps[]): void => {
|
setCarpoolPath = (waySteps: WayStepProps[]): CandidateEntity => {
|
||||||
this.props.carpoolSteps = waySteps;
|
this.props.carpoolSteps = waySteps;
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
setMetrics = (distance: number, duration: number): void => {
|
setMetrics = (distance: number, duration: number): CandidateEntity => {
|
||||||
this.props.distance = distance;
|
this.props.distance = distance;
|
||||||
this.props.duration = duration;
|
this.props.duration = duration;
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
isDetourValid = (): boolean =>
|
||||||
|
this._validateDistanceDetour() && this._validateDurationDetour();
|
||||||
|
|
||||||
|
private _validateDurationDetour = (): boolean =>
|
||||||
|
this.props.duration
|
||||||
|
? this.props.duration <=
|
||||||
|
this.props.driverDuration *
|
||||||
|
(1 + this.props.spacetimeDetourRatio.maxDurationDetourRatio)
|
||||||
|
: false;
|
||||||
|
|
||||||
|
private _validateDistanceDetour = (): boolean =>
|
||||||
|
this.props.distance
|
||||||
|
? this.props.distance <=
|
||||||
|
this.props.driverDistance *
|
||||||
|
(1 + this.props.spacetimeDetourRatio.maxDistanceDetourRatio)
|
||||||
|
: false;
|
||||||
|
|
||||||
validate(): void {
|
validate(): void {
|
||||||
// entity business rules validation to protect it's invariant before saving entity to a database
|
// entity business rules validation to protect it's invariant before saving entity to a database
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ export interface CandidateProps {
|
||||||
carpoolSteps?: WayStepProps[]; // carpool path for the crew (driver + passenger)
|
carpoolSteps?: WayStepProps[]; // carpool path for the crew (driver + passenger)
|
||||||
distance?: number;
|
distance?: number;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
|
spacetimeDetourRatio: SpacetimeDetourRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Properties that are needed for a Candidate creation
|
// Properties that are needed for a Candidate creation
|
||||||
|
@ -22,6 +23,7 @@ export interface CreateCandidateProps {
|
||||||
driverDuration: number;
|
driverDuration: number;
|
||||||
driverWaypoints: PointProps[];
|
driverWaypoints: PointProps[];
|
||||||
passengerWaypoints: PointProps[];
|
passengerWaypoints: PointProps[];
|
||||||
|
spacetimeDetourRatio: SpacetimeDetourRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Target {
|
export enum Target {
|
||||||
|
@ -30,3 +32,17 @@ export enum Target {
|
||||||
FINISH = 'FINISH',
|
FINISH = 'FINISH',
|
||||||
NEUTRAL = 'NEUTRAL',
|
NEUTRAL = 'NEUTRAL',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export abstract class Validator {
|
||||||
|
abstract validate(): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SpacetimeMetric = {
|
||||||
|
distance: number;
|
||||||
|
duration: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SpacetimeDetourRatio = {
|
||||||
|
maxDistanceDetourRatio: number;
|
||||||
|
maxDurationDetourRatio: number;
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,207 @@
|
||||||
|
import { Role } from '@modules/ad/core/domain/ad.types';
|
||||||
|
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
||||||
|
import { Target } from '@modules/ad/core/domain/candidate.types';
|
||||||
|
import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object';
|
||||||
|
import { Point } from '@modules/ad/core/domain/value-objects/point.value-object';
|
||||||
|
|
||||||
|
describe('Candidate entity', () => {
|
||||||
|
it('should create a new candidate entity', () => {
|
||||||
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
|
role: Role.DRIVER,
|
||||||
|
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,
|
||||||
|
spacetimeDetourRatio: {
|
||||||
|
maxDistanceDetourRatio: 0.3,
|
||||||
|
maxDurationDetourRatio: 0.3,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(candidateEntity.id.length).toBe(36);
|
||||||
|
});
|
||||||
|
it('should set a candidate entity carpool path', () => {
|
||||||
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
|
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||||
|
role: Role.PASSENGER,
|
||||||
|
driverWaypoints: [
|
||||||
|
{
|
||||||
|
lat: 48.689445,
|
||||||
|
lon: 6.17651,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 48.8566,
|
||||||
|
lon: 2.3522,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
passengerWaypoints: [
|
||||||
|
{
|
||||||
|
lat: 48.689445,
|
||||||
|
lon: 6.17651,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 48.8566,
|
||||||
|
lon: 2.3522,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
driverDistance: 350145,
|
||||||
|
driverDuration: 13548,
|
||||||
|
spacetimeDetourRatio: {
|
||||||
|
maxDistanceDetourRatio: 0.3,
|
||||||
|
maxDurationDetourRatio: 0.3,
|
||||||
|
},
|
||||||
|
}).setCarpoolPath([
|
||||||
|
{
|
||||||
|
point: new Point({
|
||||||
|
lat: 48.689445,
|
||||||
|
lon: 6.17651,
|
||||||
|
}),
|
||||||
|
actors: [
|
||||||
|
new Actor({
|
||||||
|
role: Role.DRIVER,
|
||||||
|
target: Target.START,
|
||||||
|
}),
|
||||||
|
new Actor({
|
||||||
|
role: Role.PASSENGER,
|
||||||
|
target: Target.START,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
point: new Point({
|
||||||
|
lat: 48.8566,
|
||||||
|
lon: 2.3522,
|
||||||
|
}),
|
||||||
|
actors: [
|
||||||
|
new Actor({
|
||||||
|
role: Role.DRIVER,
|
||||||
|
target: Target.FINISH,
|
||||||
|
}),
|
||||||
|
new Actor({
|
||||||
|
role: Role.PASSENGER,
|
||||||
|
target: Target.FINISH,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(candidateEntity.getProps().carpoolSteps).toHaveLength(2);
|
||||||
|
});
|
||||||
|
it('should create a new candidate entity with spacetime metrics', () => {
|
||||||
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
|
role: Role.DRIVER,
|
||||||
|
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,
|
||||||
|
spacetimeDetourRatio: {
|
||||||
|
maxDistanceDetourRatio: 0.3,
|
||||||
|
maxDurationDetourRatio: 0.3,
|
||||||
|
},
|
||||||
|
}).setMetrics(352688, 14587);
|
||||||
|
expect(candidateEntity.getProps().distance).toBe(352688);
|
||||||
|
expect(candidateEntity.getProps().duration).toBe(14587);
|
||||||
|
});
|
||||||
|
it('should not validate a candidate entity with exceeding distance detour', () => {
|
||||||
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
|
role: Role.DRIVER,
|
||||||
|
driverWaypoints: [
|
||||||
|
{
|
||||||
|
lat: 48.678454,
|
||||||
|
lon: 6.189745,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 48.84877,
|
||||||
|
lon: 2.398457,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
passengerWaypoints: [
|
||||||
|
{
|
||||||
|
lat: 48.849445,
|
||||||
|
lon: 6.68651,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 47.18746,
|
||||||
|
lon: 2.89742,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
driverDistance: 350145,
|
||||||
|
driverDuration: 13548,
|
||||||
|
spacetimeDetourRatio: {
|
||||||
|
maxDistanceDetourRatio: 0.3,
|
||||||
|
maxDurationDetourRatio: 0.3,
|
||||||
|
},
|
||||||
|
}).setMetrics(458690, 13980);
|
||||||
|
expect(candidateEntity.isDetourValid()).toBeFalsy();
|
||||||
|
});
|
||||||
|
it('should not validate a candidate entity with exceeding duration detour', () => {
|
||||||
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
|
role: Role.DRIVER,
|
||||||
|
driverWaypoints: [
|
||||||
|
{
|
||||||
|
lat: 48.678454,
|
||||||
|
lon: 6.189745,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 48.84877,
|
||||||
|
lon: 2.398457,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
passengerWaypoints: [
|
||||||
|
{
|
||||||
|
lat: 48.849445,
|
||||||
|
lon: 6.68651,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 47.18746,
|
||||||
|
lon: 2.89742,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
driverDistance: 350145,
|
||||||
|
driverDuration: 13548,
|
||||||
|
spacetimeDetourRatio: {
|
||||||
|
maxDistanceDetourRatio: 0.3,
|
||||||
|
maxDurationDetourRatio: 0.3,
|
||||||
|
},
|
||||||
|
}).setMetrics(352368, 18314);
|
||||||
|
expect(candidateEntity.isDetourValid()).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -71,6 +71,10 @@ const candidates: CandidateEntity[] = [
|
||||||
],
|
],
|
||||||
driverDistance: 350145,
|
driverDistance: 350145,
|
||||||
driverDuration: 13548,
|
driverDuration: 13548,
|
||||||
|
spacetimeDetourRatio: {
|
||||||
|
maxDistanceDetourRatio: 0.3,
|
||||||
|
maxDurationDetourRatio: 0.3,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
CandidateEntity.create({
|
CandidateEntity.create({
|
||||||
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||||
|
@ -97,6 +101,10 @@ const candidates: CandidateEntity[] = [
|
||||||
],
|
],
|
||||||
driverDistance: 350145,
|
driverDistance: 350145,
|
||||||
driverDuration: 13548,
|
driverDuration: 13548,
|
||||||
|
spacetimeDetourRatio: {
|
||||||
|
maxDistanceDetourRatio: 0.3,
|
||||||
|
maxDurationDetourRatio: 0.3,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -45,8 +45,7 @@ const matchQuery = new MatchQuery(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const candidates: CandidateEntity[] = [
|
const candidate: CandidateEntity = CandidateEntity.create({
|
||||||
CandidateEntity.create({
|
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
driverWaypoints: [
|
driverWaypoints: [
|
||||||
|
@ -71,41 +70,27 @@ const candidates: CandidateEntity[] = [
|
||||||
],
|
],
|
||||||
driverDistance: 350145,
|
driverDistance: 350145,
|
||||||
driverDuration: 13548,
|
driverDuration: 13548,
|
||||||
}),
|
spacetimeDetourRatio: {
|
||||||
CandidateEntity.create({
|
maxDistanceDetourRatio: 0.3,
|
||||||
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
maxDurationDetourRatio: 0.3,
|
||||||
role: Role.PASSENGER,
|
|
||||||
driverWaypoints: [
|
|
||||||
{
|
|
||||||
lat: 48.689445,
|
|
||||||
lon: 6.17651,
|
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
lat: 48.8566,
|
|
||||||
lon: 2.3522,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
passengerWaypoints: [
|
|
||||||
{
|
|
||||||
lat: 48.689445,
|
|
||||||
lon: 6.17651,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
lat: 48.8566,
|
|
||||||
lon: 2.3522,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
driverDistance: 350145,
|
|
||||||
driverDuration: 13548,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
describe('Passenger oriented geo filter', () => {
|
describe('Passenger oriented geo filter', () => {
|
||||||
it('should filter candidates', async () => {
|
it('should not filter valid candidates', async () => {
|
||||||
const passengerOrientedGeoFilter: PassengerOrientedGeoFilter =
|
const passengerOrientedGeoFilter: PassengerOrientedGeoFilter =
|
||||||
new PassengerOrientedGeoFilter(matchQuery);
|
new PassengerOrientedGeoFilter(matchQuery);
|
||||||
|
candidate.isDetourValid = () => true;
|
||||||
const filteredCandidates: CandidateEntity[] =
|
const filteredCandidates: CandidateEntity[] =
|
||||||
await passengerOrientedGeoFilter.filter(candidates);
|
await passengerOrientedGeoFilter.filter([candidate]);
|
||||||
expect(filteredCandidates.length).toBe(2);
|
expect(filteredCandidates.length).toBe(1);
|
||||||
|
});
|
||||||
|
it('should filter invalid candidates', async () => {
|
||||||
|
const passengerOrientedGeoFilter: PassengerOrientedGeoFilter =
|
||||||
|
new PassengerOrientedGeoFilter(matchQuery);
|
||||||
|
candidate.isDetourValid = () => false;
|
||||||
|
const filteredCandidates: CandidateEntity[] =
|
||||||
|
await passengerOrientedGeoFilter.filter([candidate]);
|
||||||
|
expect(filteredCandidates.length).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue