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 {
|
||||
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
|
||||
? (adEntity.getProps().driverDuration 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 });
|
||||
};
|
||||
|
||||
setCarpoolPath = (waySteps: WayStepProps[]): void => {
|
||||
setCarpoolPath = (waySteps: WayStepProps[]): CandidateEntity => {
|
||||
this.props.carpoolSteps = waySteps;
|
||||
return this;
|
||||
};
|
||||
|
||||
setMetrics = (distance: number, duration: number): void => {
|
||||
setMetrics = (distance: number, duration: number): CandidateEntity => {
|
||||
this.props.distance = distance;
|
||||
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 {
|
||||
// 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)
|
||||
distance?: number;
|
||||
duration?: number;
|
||||
spacetimeDetourRatio: SpacetimeDetourRatio;
|
||||
}
|
||||
|
||||
// Properties that are needed for a Candidate creation
|
||||
|
@ -22,6 +23,7 @@ export interface CreateCandidateProps {
|
|||
driverDuration: number;
|
||||
driverWaypoints: PointProps[];
|
||||
passengerWaypoints: PointProps[];
|
||||
spacetimeDetourRatio: SpacetimeDetourRatio;
|
||||
}
|
||||
|
||||
export enum Target {
|
||||
|
@ -30,3 +32,17 @@ export enum Target {
|
|||
FINISH = 'FINISH',
|
||||
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,
|
||||
driverDuration: 13548,
|
||||
spacetimeDetourRatio: {
|
||||
maxDistanceDetourRatio: 0.3,
|
||||
maxDurationDetourRatio: 0.3,
|
||||
},
|
||||
}),
|
||||
CandidateEntity.create({
|
||||
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||
|
@ -97,6 +101,10 @@ const candidates: CandidateEntity[] = [
|
|||
],
|
||||
driverDistance: 350145,
|
||||
driverDuration: 13548,
|
||||
spacetimeDetourRatio: {
|
||||
maxDistanceDetourRatio: 0.3,
|
||||
maxDurationDetourRatio: 0.3,
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
|
|
|
@ -45,67 +45,52 @@ const matchQuery = new MatchQuery(
|
|||
},
|
||||
);
|
||||
|
||||
const candidates: 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,
|
||||
}),
|
||||
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,
|
||||
}),
|
||||
];
|
||||
const candidate: 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,
|
||||
},
|
||||
});
|
||||
|
||||
describe('Passenger oriented geo filter', () => {
|
||||
it('should filter candidates', async () => {
|
||||
it('should not filter valid candidates', async () => {
|
||||
const passengerOrientedGeoFilter: PassengerOrientedGeoFilter =
|
||||
new PassengerOrientedGeoFilter(matchQuery);
|
||||
candidate.isDetourValid = () => true;
|
||||
const filteredCandidates: CandidateEntity[] =
|
||||
await passengerOrientedGeoFilter.filter(candidates);
|
||||
expect(filteredCandidates.length).toBe(2);
|
||||
await passengerOrientedGeoFilter.filter([candidate]);
|
||||
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