compute journeys with tests

This commit is contained in:
sbriat 2023-09-25 11:42:22 +02:00
parent 467d8a84f8
commit d8df086c6d
5 changed files with 422 additions and 247 deletions

View File

@ -20,7 +20,7 @@ export abstract class Algorithm {
for (const processor of this.processors) { for (const processor of this.processors) {
this.candidates = await processor.execute(this.candidates); this.candidates = await processor.execute(this.candidates);
} }
console.log(JSON.stringify(this.candidates, null, 2)); // console.log(JSON.stringify(this.candidates, null, 2));
return this.candidates.map((candidate: CandidateEntity) => return this.candidates.map((candidate: CandidateEntity) =>
MatchEntity.create({ adId: candidate.id }), MatchEntity.create({ adId: candidate.id }),
); );

View File

@ -63,14 +63,16 @@ export class CalendarTools {
}; };
/** /**
* Returns a date from a date and time as strings, adding optional seconds * Returns a date from a date (as a date) and a time (as a string), adding optional seconds
*/ */
static datetimeFromString = ( static datetimeWithSeconds = (
date: string, date: Date,
time: string, time: string,
additionalSeconds = 0, additionalSeconds = 0,
): Date => { ): Date => {
const datetime = new Date(`${date}T${time}:00Z`); const datetime: Date = new Date(date);
datetime.setUTCHours(parseInt(time.split(':')[0]));
datetime.setUTCMinutes(parseInt(time.split(':')[1]));
datetime.setUTCSeconds(additionalSeconds); datetime.setUTCSeconds(additionalSeconds);
return datetime; return datetime;
}; };
@ -79,7 +81,7 @@ export class CalendarTools {
* Returns dates from a day and time based on unix epoch day * Returns dates from a day and time based on unix epoch day
* (1970-01-01 is day 4) * (1970-01-01 is day 4)
* The method returns an array of dates because for edges (day 0 and 6) * The method returns an array of dates because for edges (day 0 and 6)
* we need to return 2 possibilities * we need to return 2 possibilities : one for the previous week, one for the next week
*/ */
static epochDaysFromTime = (weekDay: number, time: string): Date[] => { static epochDaysFromTime = (weekDay: number, time: string): Date[] => {
if (weekDay < 0 || weekDay > 6) if (weekDay < 0 || weekDay > 6)

View File

@ -46,6 +46,7 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
/** /**
* 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 !
*/ */
createJourneys = (): CandidateEntity => { createJourneys = (): CandidateEntity => {
this.props.journeys = this.props.driverSchedule.map( this.props.journeys = this.props.driverSchedule.map(
@ -90,6 +91,13 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
this._createJourneyItem(carpoolPathItem, index, driverScheduleItem), this._createJourneyItem(carpoolPathItem, index, driverScheduleItem),
) as JourneyItem[]; ) as JourneyItem[];
/**
* Create a journey item based on a carpool path item and driver schedule item
* The stepIndex is used to get the duration to reach the carpool path item
* from the steps prop (computed previously by a georouter)
* There MUST be a one/one relation between the carpool path items indexes
* and the steps indexes.
*/
private _createJourneyItem = ( private _createJourneyItem = (
carpoolPathItem: CarpoolPathItem, carpoolPathItem: CarpoolPathItem,
stepIndex: number, stepIndex: number,
@ -123,42 +131,55 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
actor.target == Target.START actor.target == Target.START
? 0 ? 0
: duration; : duration;
const firstDate: Date = CalendarTools.firstDate(
scheduleItem.day,
this.props.dateInterval,
);
const lastDate: Date = CalendarTools.lastDate(
scheduleItem.day,
this.props.dateInterval,
);
return new ActorTime({ return new ActorTime({
role: actor.role, role: actor.role,
target: actor.target, target: actor.target,
firstDatetime: CalendarTools.datetimeFromString( firstDatetime: CalendarTools.datetimeWithSeconds(
this.props.dateInterval.lowerDate, firstDate,
scheduleItem.time, scheduleItem.time,
effectiveDuration, effectiveDuration,
), ),
firstMinDatetime: CalendarTools.datetimeFromString( firstMinDatetime: CalendarTools.datetimeWithSeconds(
this.props.dateInterval.lowerDate, firstDate,
scheduleItem.time, scheduleItem.time,
-scheduleItem.margin + effectiveDuration, -scheduleItem.margin + effectiveDuration,
), ),
firstMaxDatetime: CalendarTools.datetimeFromString( firstMaxDatetime: CalendarTools.datetimeWithSeconds(
this.props.dateInterval.lowerDate, firstDate,
scheduleItem.time, scheduleItem.time,
scheduleItem.margin + effectiveDuration, scheduleItem.margin + effectiveDuration,
), ),
lastDatetime: CalendarTools.datetimeFromString( lastDatetime: CalendarTools.datetimeWithSeconds(
this.props.dateInterval.higherDate, lastDate,
scheduleItem.time, scheduleItem.time,
effectiveDuration, effectiveDuration,
), ),
lastMinDatetime: CalendarTools.datetimeFromString( lastMinDatetime: CalendarTools.datetimeWithSeconds(
this.props.dateInterval.higherDate, lastDate,
scheduleItem.time, scheduleItem.time,
-scheduleItem.margin + effectiveDuration, -scheduleItem.margin + effectiveDuration,
), ),
lastMaxDatetime: CalendarTools.datetimeFromString( lastMaxDatetime: CalendarTools.datetimeWithSeconds(
this.props.dateInterval.higherDate, lastDate,
scheduleItem.time, scheduleItem.time,
scheduleItem.margin + effectiveDuration, scheduleItem.margin + effectiveDuration,
), ),
}); });
}; };
/**
* Get the closest (in time) passenger schedule item for a given driver schedule item
* This is mandatory as we can't rely only on the day of the schedule item :
* items on different days can match when playing with margins around midnight
*/
private _closestPassengerScheduleItem = ( private _closestPassengerScheduleItem = (
driverScheduleItem: ScheduleItem, driverScheduleItem: ScheduleItem,
): ScheduleItem => ): ScheduleItem =>
@ -179,8 +200,12 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
: currentScheduleItemGap, : currentScheduleItemGap,
).scheduleItem; ).scheduleItem;
/**
* Find the passenger schedule item with the minimum duration between a given date and the dates of the passenger schedule
*/
private _minPassengerScheduleItemGapForDate = (date: Date): ScheduleItemGap => private _minPassengerScheduleItemGapForDate = (date: Date): ScheduleItemGap =>
this.props.passengerSchedule this.props.passengerSchedule
// first map the passenger schedule to "real" dates (we use unix epoch date as base)
.map( .map(
(scheduleItem: ScheduleItem) => (scheduleItem: ScheduleItem) =>
<ScheduleItemRange>{ <ScheduleItemRange>{
@ -191,16 +216,21 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
), ),
}, },
) )
// then compute the duration in seconds to the given date
// for each "real" date computed in step 1
.map((scheduleItemRange: ScheduleItemRange) => ({ .map((scheduleItemRange: ScheduleItemRange) => ({
scheduleItem: scheduleItemRange.scheduleItem, scheduleItem: scheduleItemRange.scheduleItem,
gap: scheduleItemRange.range gap: scheduleItemRange.range
// compute the duration
.map((scheduleDate: Date) => .map((scheduleDate: Date) =>
Math.round(Math.abs(scheduleDate.getTime() - date.getTime())), Math.round(Math.abs(scheduleDate.getTime() - date.getTime())),
) )
// keep the lowest duration
.reduce((previousGap: number, currentGap: number) => .reduce((previousGap: number, currentGap: number) =>
previousGap < currentGap ? previousGap : currentGap, previousGap < currentGap ? previousGap : currentGap,
), ),
})) }))
// finally, keep the passenger schedule item with the lowest duration
.reduce( .reduce(
( (
previousScheduleItemGap: ScheduleItemGap, previousScheduleItemGap: ScheduleItemGap,

View File

@ -90,27 +90,26 @@ describe('Calendar tools service', () => {
describe('Datetime from string', () => { describe('Datetime from string', () => {
it('should return a date with time from a string without additional seconds', () => { it('should return a date with time from a string without additional seconds', () => {
const datetime: Date = CalendarTools.datetimeFromString( const datetime: Date = CalendarTools.datetimeWithSeconds(
'2023-09-01', new Date('2023-09-01'),
'07:12', '07:12',
); );
expect(datetime.getUTCMinutes()).toBe(12); expect(datetime.getUTCMinutes()).toBe(12);
}); });
it('should return a date with time from a string with additional seconds', () => { it('should return a date with time from a string with additional seconds', () => {
const datetime: Date = CalendarTools.datetimeFromString( const datetime: Date = CalendarTools.datetimeWithSeconds(
'2023-09-01', new Date('2023-09-01'),
'07:12', '07:12',
60, 60,
); );
expect(datetime.getUTCMinutes()).toBe(13); expect(datetime.getUTCMinutes()).toBe(13);
}); });
it('should return a date with time from a string with negative additional seconds', () => { it('should return a date with time from a string with negative additional seconds', () => {
const datetime: Date = CalendarTools.datetimeFromString( const datetime: Date = CalendarTools.datetimeWithSeconds(
'2023-09-01', new Date('2023-09-01'),
'07:00', '07:00',
-60, -60,
); );
console.log(datetime);
expect(datetime.getUTCHours()).toBe(6); expect(datetime.getUTCHours()).toBe(6);
expect(datetime.getUTCMinutes()).toBe(59); expect(datetime.getUTCMinutes()).toBe(59);
}); });

View File

@ -1,7 +1,244 @@
import { Role } from '@modules/ad/core/domain/ad.types'; import { Role } from '@modules/ad/core/domain/ad.types';
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
import { Target } from '@modules/ad/core/domain/candidate.types'; import {
SpacetimeDetourRatio,
Target,
} from '@modules/ad/core/domain/candidate.types';
import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object'; import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object';
import { CarpoolPathItemProps } from '@modules/ad/core/domain/value-objects/carpool-path-item.value-object';
import { Journey } from '@modules/ad/core/domain/value-objects/journey.value-object';
import { PointProps } from '@modules/ad/core/domain/value-objects/point.value-object';
import { ScheduleItemProps } from '@modules/ad/core/domain/value-objects/schedule-item.value-object';
import { StepProps } from '@modules/ad/core/domain/value-objects/step.value-object';
const waypointsSet1: PointProps[] = [
{
lat: 48.678454,
lon: 6.189745,
},
{
lat: 48.84877,
lon: 2.398457,
},
];
const waypointsSet2: PointProps[] = [
{
lat: 48.689445,
lon: 6.17651,
},
{
lat: 48.8566,
lon: 2.3522,
},
];
const schedule1: ScheduleItemProps[] = [
{
day: 1,
time: '07:00',
margin: 900,
},
];
const schedule2: ScheduleItemProps[] = [
{
day: 1,
time: '07:10',
margin: 900,
},
];
const schedule3: ScheduleItemProps[] = [
{
day: 1,
time: '06:30',
margin: 900,
},
{
day: 2,
time: '06:30',
margin: 900,
},
{
day: 3,
time: '06:00',
margin: 900,
},
{
day: 4,
time: '06:30',
margin: 900,
},
{
day: 5,
time: '06:30',
margin: 900,
},
];
const schedule4: ScheduleItemProps[] = [
{
day: 1,
time: '06:50',
margin: 900,
},
{
day: 2,
time: '06:50',
margin: 900,
},
{
day: 4,
time: '06:50',
margin: 900,
},
{
day: 5,
time: '06:50',
margin: 900,
},
];
const schedule5: ScheduleItemProps[] = [
{
day: 0,
time: '00:10',
margin: 900,
},
{
day: 1,
time: '07:05',
margin: 900,
},
];
const schedule6: ScheduleItemProps[] = [
{
day: 1,
time: '23:10',
margin: 900,
},
{
day: 6,
time: '23:45',
margin: 900,
},
];
const spacetimeDetourRatio: SpacetimeDetourRatio = {
maxDistanceDetourRatio: 0.3,
maxDurationDetourRatio: 0.3,
};
const carpoolPath1: CarpoolPathItemProps[] = [
{
lat: 48.689445,
lon: 6.17651,
actors: [
new Actor({
role: Role.DRIVER,
target: Target.START,
}),
new Actor({
role: Role.PASSENGER,
target: Target.START,
}),
],
},
{
lat: 48.8566,
lon: 2.3522,
actors: [
new Actor({
role: Role.DRIVER,
target: Target.FINISH,
}),
new Actor({
role: Role.PASSENGER,
target: Target.FINISH,
}),
],
},
];
const carpoolPath2: CarpoolPathItemProps[] = [
{
lat: 48.689445,
lon: 6.17651,
actors: [
new Actor({
role: Role.DRIVER,
target: Target.START,
}),
],
},
{
lat: 48.678451,
lon: 6.168784,
actors: [
new Actor({
role: Role.DRIVER,
target: Target.NEUTRAL,
}),
new Actor({
role: Role.PASSENGER,
target: Target.START,
}),
],
},
{
lat: 48.848715,
lon: 2.36985,
actors: [
new Actor({
role: Role.DRIVER,
target: Target.NEUTRAL,
}),
new Actor({
role: Role.PASSENGER,
target: Target.FINISH,
}),
],
},
{
lat: 48.8566,
lon: 2.3522,
actors: [
new Actor({
role: Role.DRIVER,
target: Target.FINISH,
}),
],
},
];
const steps: StepProps[] = [
{
lat: 48.689445,
lon: 6.17651,
duration: 0,
distance: 0,
},
{
lat: 48.678451,
lon: 6.168784,
duration: 1254,
distance: 33462,
},
{
lat: 48.848715,
lon: 2.36985,
duration: 12477,
distance: 343654,
},
{
lat: 48.8566,
lon: 2.3522,
duration: 13548,
distance: 350145,
},
];
describe('Candidate entity', () => { describe('Candidate entity', () => {
it('should create a new candidate entity', () => { it('should create a new candidate entity', () => {
@ -12,46 +249,13 @@ describe('Candidate entity', () => {
lowerDate: '2023-08-28', lowerDate: '2023-08-28',
higherDate: '2023-08-28', higherDate: '2023-08-28',
}, },
driverWaypoints: [ driverWaypoints: waypointsSet1,
{ passengerWaypoints: waypointsSet2,
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, driverDistance: 350145,
driverDuration: 13548, driverDuration: 13548,
driverSchedule: [ driverSchedule: schedule1,
{ passengerSchedule: schedule2,
day: 0, spacetimeDetourRatio,
time: '07:00',
margin: 900,
},
],
passengerSchedule: [
{
day: 0,
time: '07:10',
margin: 900,
},
],
spacetimeDetourRatio: {
maxDistanceDetourRatio: 0.3,
maxDurationDetourRatio: 0.3,
},
}); });
expect(candidateEntity.id.length).toBe(36); expect(candidateEntity.id.length).toBe(36);
}); });
@ -64,76 +268,14 @@ describe('Candidate entity', () => {
lowerDate: '2023-08-28', lowerDate: '2023-08-28',
higherDate: '2023-08-28', higherDate: '2023-08-28',
}, },
driverWaypoints: [ driverWaypoints: waypointsSet2,
{ passengerWaypoints: waypointsSet2,
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, driverDistance: 350145,
driverDuration: 13548, driverDuration: 13548,
driverSchedule: [ driverSchedule: schedule1,
{ passengerSchedule: schedule2,
day: 0, spacetimeDetourRatio,
time: '07:00', }).setCarpoolPath(carpoolPath1);
margin: 900,
},
],
passengerSchedule: [
{
day: 0,
time: '07:10',
margin: 900,
},
],
spacetimeDetourRatio: {
maxDistanceDetourRatio: 0.3,
maxDurationDetourRatio: 0.3,
},
}).setCarpoolPath([
{
lat: 48.689445,
lon: 6.17651,
actors: [
new Actor({
role: Role.DRIVER,
target: Target.START,
}),
new Actor({
role: Role.PASSENGER,
target: Target.START,
}),
],
},
{
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().carpoolPath).toHaveLength(2); expect(candidateEntity.getProps().carpoolPath).toHaveLength(2);
}); });
@ -145,46 +287,13 @@ describe('Candidate entity', () => {
lowerDate: '2023-08-28', lowerDate: '2023-08-28',
higherDate: '2023-08-28', higherDate: '2023-08-28',
}, },
driverWaypoints: [ driverWaypoints: waypointsSet1,
{ passengerWaypoints: waypointsSet2,
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, driverDistance: 350145,
driverDuration: 13548, driverDuration: 13548,
driverSchedule: [ driverSchedule: schedule1,
{ passengerSchedule: schedule2,
day: 0, spacetimeDetourRatio,
time: '07:00',
margin: 900,
},
],
passengerSchedule: [
{
day: 0,
time: '07:10',
margin: 900,
},
],
spacetimeDetourRatio: {
maxDistanceDetourRatio: 0.3,
maxDurationDetourRatio: 0.3,
},
}).setMetrics(352688, 14587); }).setMetrics(352688, 14587);
expect(candidateEntity.getProps().distance).toBe(352688); expect(candidateEntity.getProps().distance).toBe(352688);
expect(candidateEntity.getProps().duration).toBe(14587); expect(candidateEntity.getProps().duration).toBe(14587);
@ -199,46 +308,13 @@ describe('Candidate entity', () => {
lowerDate: '2023-08-28', lowerDate: '2023-08-28',
higherDate: '2023-08-28', higherDate: '2023-08-28',
}, },
driverWaypoints: [ driverWaypoints: waypointsSet1,
{ passengerWaypoints: waypointsSet2,
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, driverDistance: 350145,
driverDuration: 13548, driverDuration: 13548,
driverSchedule: [ driverSchedule: schedule1,
{ passengerSchedule: schedule2,
day: 0, spacetimeDetourRatio,
time: '07:00',
margin: 900,
},
],
passengerSchedule: [
{
day: 0,
time: '07:10',
margin: 900,
},
],
spacetimeDetourRatio: {
maxDistanceDetourRatio: 0.3,
maxDurationDetourRatio: 0.3,
},
}).setMetrics(458690, 13980); }).setMetrics(458690, 13980);
expect(candidateEntity.isDetourValid()).toBeFalsy(); expect(candidateEntity.isDetourValid()).toBeFalsy();
}); });
@ -250,52 +326,120 @@ describe('Candidate entity', () => {
lowerDate: '2023-08-28', lowerDate: '2023-08-28',
higherDate: '2023-08-28', higherDate: '2023-08-28',
}, },
driverWaypoints: [ driverWaypoints: waypointsSet1,
{ passengerWaypoints: waypointsSet2,
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, driverDistance: 350145,
driverDuration: 13548, driverDuration: 13548,
driverSchedule: [ driverSchedule: schedule1,
{ passengerSchedule: schedule2,
day: 0, spacetimeDetourRatio,
time: '07:00',
margin: 900,
},
],
passengerSchedule: [
{
day: 0,
time: '07:10',
margin: 900,
},
],
spacetimeDetourRatio: {
maxDistanceDetourRatio: 0.3,
maxDurationDetourRatio: 0.3,
},
}).setMetrics(352368, 18314); }).setMetrics(352368, 18314);
expect(candidateEntity.isDetourValid()).toBeFalsy(); expect(candidateEntity.isDetourValid()).toBeFalsy();
}); });
}); });
describe('Journeys', () => { describe('Journeys', () => {
it('should create journeys', () => {}); it('should create journeys for a single date', () => {
const candidateEntity: CandidateEntity = CandidateEntity.create({
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
role: Role.PASSENGER,
dateInterval: {
lowerDate: '2023-08-28',
higherDate: '2023-08-28',
},
driverWaypoints: waypointsSet1,
passengerWaypoints: waypointsSet2,
driverDistance: 350145,
driverDuration: 13548,
driverSchedule: schedule1,
passengerSchedule: schedule2,
spacetimeDetourRatio,
})
.setCarpoolPath(carpoolPath2)
.setSteps(steps)
.createJourneys();
expect(candidateEntity.getProps().journeys).toHaveLength(1);
});
it('should create journeys for multiple dates', () => {
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: schedule3,
passengerSchedule: schedule4,
spacetimeDetourRatio,
})
.setCarpoolPath(carpoolPath2)
.setSteps(steps)
.createJourneys();
expect(candidateEntity.getProps().journeys).toHaveLength(5);
expect(
(
candidateEntity.getProps().journeys as Journey[]
)[0].firstDate.getDate(),
).toBe(4);
expect(
(
candidateEntity.getProps().journeys as Journey[]
)[0].journeyItems[1].actorTimes[1].firstMinDatetime.getDate(),
).toBe(4);
expect(
(
candidateEntity.getProps().journeys as Journey[]
)[1].journeyItems[1].actorTimes[1].firstMinDatetime.getDate(),
).toBe(5);
});
it('should create journeys for multiple dates, including week edges (saturday/sunday)', () => {
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: schedule5,
passengerSchedule: schedule6,
spacetimeDetourRatio,
})
.setCarpoolPath(carpoolPath2)
.setSteps(steps)
.createJourneys();
expect(candidateEntity.getProps().journeys).toHaveLength(2);
expect(
(candidateEntity.getProps().journeys as Journey[])[0].journeyItems[1]
.actorTimes[0].target,
).toBe(Target.NEUTRAL);
expect(
(
candidateEntity.getProps().journeys as Journey[]
)[0].journeyItems[1].actorTimes[0].firstDatetime.getUTCHours(),
).toBe(0);
expect(
(
candidateEntity.getProps().journeys as Journey[]
)[0].journeyItems[1].actorTimes[0].firstDatetime.getUTCMinutes(),
).toBe(30);
expect(
(
candidateEntity.getProps().journeys as Journey[]
)[0].journeyItems[1].actorTimes[1].firstMinDatetime.getUTCHours(),
).toBe(23);
expect(
(
candidateEntity.getProps().journeys as Journey[]
)[0].journeyItems[1].actorTimes[1].firstMinDatetime.getUTCMinutes(),
).toBe(30);
});
}); });
}); });