Skip to content

Commit

Permalink
Merge branch 'main' into mth-show-alerts
Browse files Browse the repository at this point in the history
  • Loading branch information
boringcactus committed Mar 22, 2024
2 parents 3951747 + 32d437c commit e0c04c6
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,23 @@ data class PatternsByHeadsign(
}
?: false

/**
* Checks if this headsign ends at this stop, i.e. all trips are arrival-only.
*
* Criteria:
* - Trips are loaded
* - At least one trip is scheduled as arrival-only
* - No trips are scheduled or predicted with a departure
*/
fun isArrivalOnly() =
upcomingTrips != null &&
upcomingTrips
.mapTo(mutableSetOf()) { it.isArrivalOnly() }
.let { upcomingTripsArrivalOnly ->
upcomingTripsArrivalOnly.contains(true) &&
!upcomingTripsArrivalOnly.contains(false)
}

override fun compareTo(other: PatternsByHeadsign): Int =
patterns.first().compareTo(other.patterns.first())
}
Expand All @@ -101,7 +118,7 @@ data class PatternsByStop(val stop: Stop, val patternsByHeadsign: List<PatternsB
.map {
PatternsByHeadsign(it, routeId, upcomingTripsMap, staticData.allStopIds, alerts)
}
.filter { it.isTypical() || it.isUpcomingBefore(cutoffTime) }
.filter { (it.isTypical() || it.isUpcomingBefore(cutoffTime)) && !it.isArrivalOnly() }
.sorted()
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,33 @@ data class UpcomingTrip(

override fun compareTo(other: UpcomingTrip) = nullsLast<Instant>().compare(time, other.time)

/**
* Checks whether this upcoming trip will depart its station or only arrive there.
*
* If a trip will neither arrive nor depart (e.g. trips with no schedule that have been
* cancelled), this function will return `null`. Returning `true` would hide headsigns with no
* schedule and predictions exclusively for dropped trips, which may happen during suspensions
* and would be incorrect. Returning `false` would show headsigns with added trips even if those
* trips have been cancelled, which would be incorrect.
*/
fun isArrivalOnly(): Boolean? {
val hasArrival =
if (schedule != null) {
schedule.dropOffType != Schedule.StopEdgeType.Unavailable
} else {
prediction?.arrivalTime != null
}
val hasDeparture =
if (schedule != null) {
schedule.pickUpType != Schedule.StopEdgeType.Unavailable
} else {
prediction?.departureTime != null
}
return if (!hasArrival && !hasDeparture) {
null
} else !hasDeparture
}

/**
* The state in which a prediction should be shown.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1101,4 +1101,72 @@ class NearbyResponseTest {
)
)
}

@Test
fun `withRealtimeInfo hides headsigns that are arrival-only`() {

val objects = ObjectCollectionBuilder()
val stop = objects.stop()
val route = objects.route()
val routePattern1 = objects.routePattern(route) { representativeTrip { headsign = "A" } }
val routePattern2 = objects.routePattern(route) { representativeTrip { headsign = "B" } }
val trip1 = objects.trip(routePattern1)
val trip2 = objects.trip(routePattern2)

val time = Instant.parse("2024-03-14T12:23:44-04:00")

val sched1 =
objects.schedule {
trip = trip1
stopId = stop.id
stopSequence = 90
departureTime = time + 1.minutes
}
val sched2 =
objects.schedule {
trip = trip2
stopId = stop.id
stopSequence = 90
arrivalTime = time + 2.minutes
departureTime = null
pickUpType = Schedule.StopEdgeType.Unavailable
}

val staticData =
NearbyStaticData.build {
route(route) {
stop(stop) {
headsign("A", listOf(routePattern1))
headsign("B", listOf(routePattern2))
}
}
}

assertEquals(
listOf(
StopAssociatedRoute(
route,
listOf(
PatternsByStop(
stop,
listOf(
PatternsByHeadsign(
"A",
listOf(routePattern1),
listOf(UpcomingTrip(sched1))
)
)
)
)
)
),
staticData.withRealtimeInfo(
sortByDistanceFrom = stop.position,
schedules = ScheduleResponse(objects),
predictions = PredictionsStreamDataResponse(objects),
alerts = null,
filterAtTime = time
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -375,4 +375,80 @@ class UpcomingTripTest {

assertEquals(null, UpcomingTrip(schedule, predictionDropped).time)
}

@Test
fun `isArrivalOnly handles schedule without prediction`() {
val schedule1 = schedule {
departureTime = null
pickUpType = Schedule.StopEdgeType.Unavailable
}
assertEquals(true, UpcomingTrip(schedule1).isArrivalOnly())

val schedule2 = schedule {
departureTime = Clock.System.now()
pickUpType = Schedule.StopEdgeType.Regular
}
assertEquals(false, UpcomingTrip(schedule2).isArrivalOnly())

val schedule3 = schedule {
pickUpType = Schedule.StopEdgeType.Unavailable
dropOffType = Schedule.StopEdgeType.Unavailable
}
assertEquals(null, UpcomingTrip(schedule3).isArrivalOnly())
}

@Test
fun `isArrivalOnly handles prediction without schedule`() {
val prediction1 = prediction {
arrivalTime = Clock.System.now()
departureTime = null
}
assertEquals(true, UpcomingTrip(prediction1).isArrivalOnly())

val prediction2 = prediction {
arrivalTime = null
departureTime = null
}
assertEquals(null, UpcomingTrip(prediction2).isArrivalOnly())

val prediction3 = prediction { departureTime = Clock.System.now() }
assertEquals(false, UpcomingTrip(prediction3).isArrivalOnly())
}

@Test
fun `isArrivalOnly handles schedule alongside prediction`() {
val scheduleArrivalOnly = schedule {
arrivalTime = Clock.System.now()
dropOffType = Schedule.StopEdgeType.Regular
departureTime = null
pickUpType = Schedule.StopEdgeType.Unavailable
}
val scheduleNormal = schedule {
departureTime = Clock.System.now()
pickUpType = Schedule.StopEdgeType.Regular
}
val scheduleNeither = schedule {
dropOffType = Schedule.StopEdgeType.Unavailable
pickUpType = Schedule.StopEdgeType.Unavailable
}
val predictionArrivalOnly = prediction {
arrivalTime = Clock.System.now()
departureTime = null
}
val predictionNormal = prediction { departureTime = Clock.System.now() }
val predictionNeither = prediction {
arrivalTime = null
departureTime = null
}

assertEquals(true, UpcomingTrip(scheduleArrivalOnly, predictionArrivalOnly).isArrivalOnly())
assertEquals(true, UpcomingTrip(scheduleArrivalOnly, predictionNormal).isArrivalOnly())
assertEquals(true, UpcomingTrip(scheduleArrivalOnly, predictionNeither).isArrivalOnly())
assertEquals(false, UpcomingTrip(scheduleNormal, predictionArrivalOnly).isArrivalOnly())
assertEquals(false, UpcomingTrip(scheduleNormal, predictionNormal).isArrivalOnly())
assertEquals(false, UpcomingTrip(scheduleNormal, predictionNeither).isArrivalOnly())
assertEquals(null, UpcomingTrip(scheduleNeither, predictionArrivalOnly).isArrivalOnly())
assertEquals(null, UpcomingTrip(scheduleNeither, predictionNormal).isArrivalOnly())
assertEquals(null, UpcomingTrip(scheduleNeither, predictionNeither).isArrivalOnly())
}
}

0 comments on commit e0c04c6

Please sign in to comment.