Skip to content

Commit 1a677b9

Browse files
committed
feat: Add empirical delay to Transmodel API
1 parent 840522c commit 1a677b9

File tree

9 files changed

+156
-4
lines changed

9 files changed

+156
-4
lines changed

application/src/ext/resources/org/opentripplanner/ext/apis/transmodel/custom-documentation-entur.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,6 @@ TariffZone.description=A **zone** used to define a zonal fare structure in a zon
2323
TariffZones are deprecated, please use FareZones. \
2424
\
2525
**TariffZone data will not be maintained from 1. MAY 2025 (Entur).**
26+
27+
EmpiricalDelay.description.append=NOTE! This feature is under development and the percentiles used \
28+
may change without notice. The percentiles are currently set to min=10 and max=90.

application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ private static TransmodelRequestContext createRequestContext(
103103
return new TransmodelRequestContext(
104104
serverContext,
105105
serverContext.routingService(),
106-
serverContext.transitService()
106+
serverContext.transitService(),
107+
serverContext.empiricalDelayService()
107108
);
108109
}
109110

application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaFactory.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
import org.opentripplanner.apis.transmodel.model.timetable.BookingArrangementType;
105105
import org.opentripplanner.apis.transmodel.model.timetable.DatedServiceJourneyQuery;
106106
import org.opentripplanner.apis.transmodel.model.timetable.DatedServiceJourneyType;
107+
import org.opentripplanner.apis.transmodel.model.timetable.EmpiricalDelayType;
107108
import org.opentripplanner.apis.transmodel.model.timetable.InterchangeType;
108109
import org.opentripplanner.apis.transmodel.model.timetable.ServiceJourneyType;
109110
import org.opentripplanner.apis.transmodel.model.timetable.TimetabledPassingTimeType;
@@ -292,7 +293,8 @@ private GraphQLSchema createDefault() {
292293
DatedServiceJourneyType.REF
293294
);
294295

295-
// Timetable
296+
/* Timetable */
297+
296298
GraphQLNamedOutputType ptSituationElementType = PtSituationElementType.create(
297299
authorityType,
298300
quayType,
@@ -315,6 +317,7 @@ private GraphQLSchema createDefault() {
315317
stopToStopGeometryType,
316318
ptSituationElementType
317319
);
320+
GraphQLOutputType empiricalDealy = EmpiricalDelayType.create();
318321
GraphQLOutputType estimatedCallType = EstimatedCallType.create(
319322
bookingArrangementType,
320323
noticeType,
@@ -323,6 +326,7 @@ private GraphQLSchema createDefault() {
323326
ptSituationElementType,
324327
ServiceJourneyType.REF,
325328
DatedServiceJourneyType.REF,
329+
empiricalDealy,
326330
dateTimeScalar
327331
);
328332

application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelRequestContext.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
11
package org.opentripplanner.apis.transmodel;
22

3+
import javax.annotation.Nullable;
4+
import org.opentripplanner.ext.empiricaldelay.EmpiricalDelayService;
35
import org.opentripplanner.routing.api.RoutingService;
46
import org.opentripplanner.standalone.api.OtpServerRequestContext;
57
import org.opentripplanner.transit.service.TransitService;
8+
import org.opentripplanner.utils.lang.Sandbox;
69

710
public class TransmodelRequestContext {
811

912
private final OtpServerRequestContext serverContext;
1013
private final RoutingService routingService;
1114
private final TransitService transitService;
15+
private final @Nullable EmpiricalDelayService empiricalDelayService;
1216

1317
public TransmodelRequestContext(
1418
OtpServerRequestContext serverContext,
1519
RoutingService routingService,
16-
TransitService transitService
20+
TransitService transitService,
21+
EmpiricalDelayService empiricalDelayService
1722
) {
1823
this.serverContext = serverContext;
1924
this.routingService = routingService;
2025
this.transitService = transitService;
26+
this.empiricalDelayService = empiricalDelayService;
2127
}
2228

2329
public OtpServerRequestContext getServerContext() {
@@ -31,4 +37,10 @@ public RoutingService getRoutingService() {
3137
public TransitService getTransitService() {
3238
return transitService;
3339
}
40+
41+
@Nullable
42+
@Sandbox
43+
public EmpiricalDelayService getEmpiricalDelayService() {
44+
return empiricalDelayService;
45+
}
3446
}

application/src/main/java/org/opentripplanner/apis/transmodel/model/siri/et/EstimatedCallType.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.opentripplanner.apis.transmodel.model.EnumTypes;
2121
import org.opentripplanner.apis.transmodel.model.framework.TransmodelDirectives;
2222
import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars;
23+
import org.opentripplanner.apis.transmodel.model.timetable.EmpiricalDelayType;
2324
import org.opentripplanner.apis.transmodel.support.GqlUtil;
2425
import org.opentripplanner.model.TripTimeOnDate;
2526
import org.opentripplanner.routing.alertpatch.StopCondition;
@@ -44,6 +45,7 @@ public static GraphQLObjectType create(
4445
GraphQLOutputType ptSituationElementType,
4546
GraphQLOutputType serviceJourneyType,
4647
GraphQLOutputType datedServiceJourneyType,
48+
GraphQLOutputType empiricalDelayType,
4749
GraphQLScalarType dateTimeScalar
4850
) {
4951
return GraphQLObjectType.newObject()
@@ -151,6 +153,16 @@ public static GraphQLObjectType create(
151153
})
152154
.build()
153155
)
156+
.field(
157+
GraphQLFieldDefinition.newFieldDefinition()
158+
.name("empiricalDelay")
159+
.type(empiricalDelayType)
160+
.description(
161+
"The typical delay for this trip on this day for this stop based on historical data."
162+
)
163+
.dataFetcher(EmpiricalDelayType::dataFetcherForTripTimeOnDate)
164+
.build()
165+
)
154166
.field(
155167
GraphQLFieldDefinition.newFieldDefinition()
156168
.name("timingPoint")
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package org.opentripplanner.apis.transmodel.model.timetable;
2+
3+
import graphql.Scalars;
4+
import graphql.schema.DataFetchingEnvironment;
5+
import graphql.schema.GraphQLFieldDefinition;
6+
import graphql.schema.GraphQLObjectType;
7+
import graphql.schema.GraphQLTypeReference;
8+
import org.jspecify.annotations.Nullable;
9+
import org.opentripplanner.apis.transmodel.TransmodelRequestContext;
10+
import org.opentripplanner.ext.empiricaldelay.model.EmpiricalDelay;
11+
import org.opentripplanner.model.TripTimeOnDate;
12+
13+
public class EmpiricalDelayType {
14+
15+
static final String NAME = "EmpiricalDelay";
16+
public static final GraphQLTypeReference REF = new GraphQLTypeReference(NAME);
17+
18+
private EmpiricalDelayType() {}
19+
20+
public static GraphQLObjectType create() {
21+
return GraphQLObjectType.newObject()
22+
.name(NAME)
23+
.description(
24+
"""
25+
The empirical delay indicate how late a service journey is based on historic data. What the
26+
min and max percentiles represent is set per deployment. For example, if set to p10 and p90,
27+
then 10% of the arrival will have a delay less then the `minPercentile`, and 10% will have
28+
a delay larger than the `maxPercentile` value.
29+
"""
30+
)
31+
.field(
32+
GraphQLFieldDefinition.newFieldDefinition()
33+
.name("minPercentile")
34+
.description("Minimum percentile")
35+
.type(Scalars.GraphQLInt)
36+
.dataFetcher(e -> (empiricalDelay(e).minPercentile()))
37+
.build()
38+
)
39+
.field(
40+
GraphQLFieldDefinition.newFieldDefinition()
41+
.name("maxPercentile")
42+
.description("Maximum percentile")
43+
.type(Scalars.GraphQLInt)
44+
.dataFetcher(e -> (empiricalDelay(e).maxPercentile()))
45+
.build()
46+
)
47+
.build();
48+
}
49+
50+
@Nullable
51+
public static EmpiricalDelay dataFetcherForTripTimeOnDate(DataFetchingEnvironment environment) {
52+
TripTimeOnDate parent = environment.getSource();
53+
TransmodelRequestContext ctx = environment.getContext();
54+
var service = ctx.getEmpiricalDelayService();
55+
56+
if (parent == null || service == null) {
57+
return null;
58+
}
59+
return service
60+
.findEmpiricalDelay(
61+
parent.getTrip().getId(),
62+
parent.getServiceDay(),
63+
parent.getStopPosition()
64+
)
65+
.orElse(null);
66+
}
67+
68+
private static EmpiricalDelay empiricalDelay(DataFetchingEnvironment environment) {
69+
return environment.getSource();
70+
}
71+
}

application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,19 @@ type Emission {
216216
co2: Float
217217
}
218218

219+
"""
220+
The empirical delay indicate how late a service journey is based on historic data. What the
221+
min and max percentiles represent is set per deployment. For example, if set to p10 and p90,
222+
then 10% of the arrival will have a delay less then the `minPercentile`, and 10% will have
223+
a delay larger than the `maxPercentile` value.
224+
"""
225+
type EmpiricalDelay {
226+
"Maximum percentile"
227+
maxPercentile: Int
228+
"Minimum percentile"
229+
minPercentile: Int
230+
}
231+
219232
"List of visits to quays as part of vehicle journeys. Updated with real time information where available"
220233
type EstimatedCall {
221234
"Actual time of arrival at quay. Updated from real time information if available."
@@ -234,6 +247,8 @@ type EstimatedCall {
234247
date: Date!
235248
datedServiceJourney: DatedServiceJourney
236249
destinationDisplay: DestinationDisplay
250+
"The typical delay for this trip on this day for this stop based on historical data."
251+
empiricalDelay: EmpiricalDelay
237252
"Expected time of arrival at quay. Updated with real time information if available. Will be null if an actualArrivalTime exists"
238253
expectedArrivalTime: DateTime!
239254
"Expected time of departure from quay. Updated with real time information if available. Will be null if an actualDepartureTime exists"

application/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ void setup() {
143143
context = new TransmodelRequestContext(
144144
otpServerRequestContext,
145145
otpServerRequestContext.routingService(),
146-
otpServerRequestContext.transitService()
146+
otpServerRequestContext.transitService(),
147+
otpServerRequestContext.empiricalDelayService()
147148
);
148149
}
149150

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.opentripplanner.apis.transmodel.model.timetable;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertNotNull;
6+
7+
import graphql.Scalars;
8+
import graphql.schema.GraphQLObjectType;
9+
import org.junit.jupiter.api.Test;
10+
11+
class EmpiricalDelayTypeTest {
12+
13+
private static final GraphQLObjectType subject = EmpiricalDelayType.create();
14+
15+
@Test
16+
void create() {
17+
var subject = EmpiricalDelayType.create();
18+
19+
assertEquals(EmpiricalDelayType.NAME, subject.getName());
20+
assertThat(subject.getDescription()).isNotEmpty();
21+
22+
var minPercentile = subject.getFieldDefinition("minPercentile");
23+
assertNotNull(minPercentile);
24+
assertEquals(Scalars.GraphQLInt, minPercentile.getType());
25+
26+
var maxPercentile = subject.getFieldDefinition("maxPercentile");
27+
assertNotNull(maxPercentile);
28+
assertEquals(Scalars.GraphQLInt, maxPercentile.getType());
29+
}
30+
31+
@Test
32+
void dataFetcherForTripTimeOnDate() {}
33+
}

0 commit comments

Comments
 (0)