Skip to content

Commit

Permalink
feat: hide schedules on subway (#103)
Browse files Browse the repository at this point in the history
* feat: hide schedules on subway

* document source of truth for hiding subway times
  • Loading branch information
boringcactus authored Mar 26, 2024
1 parent 4dd47f3 commit 7d7825a
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 24 deletions.
2 changes: 2 additions & 0 deletions iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ struct NearbyTransitView_Previews: PreviewProvider {
stop: busStop,
patternsByHeadsign: [
PatternsByHeadsign(
route: busRoute,
headsign: "Houghs Neck",
patterns: [busPattern],
upcomingTrips: [
Expand All @@ -266,6 +267,7 @@ struct NearbyTransitView_Previews: PreviewProvider {
stop: crStop,
patternsByHeadsign: [
PatternsByHeadsign(
route: crRoute,
headsign: "Houghs Neck",
patterns: [crPattern],
upcomingTrips: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import com.mbta.tid.mbta_app.model.response.StopAndRoutePatternResponse
* route pattern.
*/
data class NearbyStaticData(val data: List<RouteWithStops>) {
data class HeadsignWithPatterns(val headsign: String, val patterns: List<RoutePattern>) :
Comparable<HeadsignWithPatterns> {
data class HeadsignWithPatterns(
val route: Route,
val headsign: String,
val patterns: List<RoutePattern>
) : Comparable<HeadsignWithPatterns> {
override fun compareTo(other: HeadsignWithPatterns): Int =
patterns.first().compareTo(other.patterns.first())
}
Expand Down Expand Up @@ -93,6 +96,7 @@ data class NearbyStaticData(val data: List<RouteWithStops>) {
}
.map { (headsign, routePatterns) ->
HeadsignWithPatterns(
route,
headsign,
routePatterns.sorted()
)
Expand All @@ -119,20 +123,20 @@ class NearbyStaticDataBuilder {
val data = mutableListOf<NearbyStaticData.RouteWithStops>()

fun route(route: Route, block: PatternsByStopBuilder.() -> Unit) {
val builder = PatternsByStopBuilder()
val builder = PatternsByStopBuilder(route)
builder.block()
data.add(NearbyStaticData.RouteWithStops(route, builder.data))
}

class PatternsByHeadsignBuilder {
class PatternsByHeadsignBuilder(val route: Route) {
val data = mutableListOf<NearbyStaticData.HeadsignWithPatterns>()

fun headsign(headsign: String, patterns: List<RoutePattern>) {
data.add(NearbyStaticData.HeadsignWithPatterns(headsign, patterns))
data.add(NearbyStaticData.HeadsignWithPatterns(route, headsign, patterns))
}
}

class PatternsByStopBuilder {
class PatternsByStopBuilder(val route: Route) {
val data = mutableListOf<NearbyStaticData.StopWithPatterns>()

@DefaultArgumentInterop.Enabled
Expand All @@ -141,7 +145,7 @@ class NearbyStaticDataBuilder {
childStopIds: List<String> = emptyList(),
block: PatternsByHeadsignBuilder.() -> Unit
) {
val builder = PatternsByHeadsignBuilder()
val builder = PatternsByHeadsignBuilder(route)
builder.block()
data.add(
NearbyStaticData.StopWithPatterns(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,26 @@ typealias UpcomingTripsMap = Map<UpcomingTripKey, List<UpcomingTrip>>
* for any of these [patterns]
*/
data class PatternsByHeadsign(
val route: Route,
val headsign: String,
val patterns: List<RoutePattern>,
val upcomingTrips: List<UpcomingTrip>? = null,
val alertsHere: List<Alert>? = null,
) : Comparable<PatternsByHeadsign> {
constructor(
staticData: NearbyStaticData.HeadsignWithPatterns,
routeId: String,
upcomingTripsMap: UpcomingTripsMap?,
stopIds: Set<String>,
alerts: Collection<Alert>?,
) : this(
staticData.route,
staticData.headsign,
staticData.patterns,
if (upcomingTripsMap != null) {
stopIds
.mapNotNull { stopId ->
upcomingTripsMap[UpcomingTripKey(routeId, staticData.headsign, stopId)]
upcomingTripsMap[
UpcomingTripKey(staticData.route.id, staticData.headsign, stopId)]
}
.flatten()
.sorted()
Expand All @@ -47,7 +49,7 @@ data class PatternsByHeadsign(
stopIds.flatMap { stopId ->
alerts.filter { alert ->
alert.anyInformedEntity {
it.appliesTo(routeId = routeId, stopId = stopId) &&
it.appliesTo(routeId = staticData.route.id, stopId = stopId) &&
it.activities.contains(Alert.InformedEntity.Activity.Board)
}
}
Expand Down Expand Up @@ -117,7 +119,11 @@ data class PatternsByHeadsign(
val tripsToShow =
upcomingTrips
.map { Format.Some.FormatWithId(it, now) }
.filterNot { it.format is UpcomingTrip.Format.Hidden }
.filterNot {
it.format is UpcomingTrip.Format.Hidden ||
// API best practices call for hiding scheduled times on subway
(this.route.type.isSubway() && it.format is UpcomingTrip.Format.Schedule)
}
.take(2)
if (tripsToShow.isEmpty()) {
this.alertsHere?.firstOrNull()?.let {
Expand All @@ -138,16 +144,13 @@ data class PatternsByStop(val stop: Stop, val patternsByHeadsign: List<PatternsB

constructor(
staticData: NearbyStaticData.StopWithPatterns,
routeId: String,
upcomingTripsMap: UpcomingTripsMap?,
cutoffTime: Instant,
alerts: Collection<Alert>?,
) : this(
staticData.stop,
staticData.patternsByHeadsign
.map {
PatternsByHeadsign(it, routeId, upcomingTripsMap, staticData.allStopIds, alerts)
}
.map { PatternsByHeadsign(it, upcomingTripsMap, staticData.allStopIds, alerts) }
.filter { (it.isTypical() || it.isUpcomingBefore(cutoffTime)) && !it.isArrivalOnly() }
.sorted()
)
Expand All @@ -172,7 +175,7 @@ data class StopAssociatedRoute(
) : this(
staticData.route,
staticData.patternsByStop
.map { PatternsByStop(it, staticData.route.id, upcomingTripsMap, cutoffTime, alerts) }
.map { PatternsByStop(it, upcomingTripsMap, cutoffTime, alerts) }
.filterNot { it.patternsByHeadsign.isEmpty() }
.sortedWith(
compareBy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ class NearbyResponseTest {
stop1,
listOf(
PatternsByHeadsign(
route1,
"Harvard",
listOf(pattern1, pattern2),
listOf(
Expand All @@ -463,6 +464,7 @@ class NearbyResponseTest {
stop2,
listOf(
PatternsByHeadsign(
route1,
"Nubian",
listOf(pattern3),
listOf(UpcomingTrip(stop2Pattern3Prediction))
Expand Down Expand Up @@ -593,21 +595,25 @@ class NearbyResponseTest {
stop1,
listOf(
PatternsByHeadsign(
route1,
"Typical Out",
listOf(typicalOutbound),
listOf(UpcomingTrip(typicalOutboundPrediction))
),
PatternsByHeadsign(
route1,
"Typical In",
listOf(typicalInbound),
emptyList()
),
PatternsByHeadsign(
route1,
"Deviation Out",
listOf(deviationOutbound),
listOf(UpcomingTrip(deviationOutboundPrediction))
),
PatternsByHeadsign(
route1,
"Atypical In",
listOf(atypicalInbound),
listOf(UpcomingTrip(atypicalInboundPrediction))
Expand Down Expand Up @@ -708,8 +714,18 @@ class NearbyResponseTest {
PatternsByStop(
stop1,
listOf(
PatternsByHeadsign("Typical Out", listOf(typicalOutbound), null),
PatternsByHeadsign("Typical In", listOf(typicalInbound), null),
PatternsByHeadsign(
route1,
"Typical Out",
listOf(typicalOutbound),
null
),
PatternsByHeadsign(
route1,
"Typical In",
listOf(typicalInbound),
null
),
)
)
)
Expand Down Expand Up @@ -874,6 +890,7 @@ class NearbyResponseTest {
parentStop,
listOf(
PatternsByHeadsign(
route1,
"Harvard",
listOf(pattern1),
listOf(UpcomingTrip(prediction1))
Expand Down Expand Up @@ -936,6 +953,7 @@ class NearbyResponseTest {
stop,
listOf(
PatternsByHeadsign(
route,
"A",
listOf(routePattern),
listOf(UpcomingTrip(sched1, pred1), UpcomingTrip(sched2, pred2))
Expand Down Expand Up @@ -1002,6 +1020,7 @@ class NearbyResponseTest {
stop,
listOf(
PatternsByHeadsign(
route1,
"A",
listOf(routePattern1),
listOf(UpcomingTrip(sched1, pred1))
Expand All @@ -1017,6 +1036,7 @@ class NearbyResponseTest {
stop,
listOf(
PatternsByHeadsign(
route2,
"A",
listOf(routePattern2),
listOf(UpcomingTrip(sched2, pred2))
Expand Down Expand Up @@ -1082,6 +1102,7 @@ class NearbyResponseTest {
stop,
listOf(
PatternsByHeadsign(
route,
"A",
listOf(routePattern),
emptyList(),
Expand Down Expand Up @@ -1151,6 +1172,7 @@ class NearbyResponseTest {
stop,
listOf(
PatternsByHeadsign(
route,
"A",
listOf(routePattern1),
listOf(UpcomingTrip(sched1))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.mbta.tid.mbta_app.model

import com.mbta.tid.mbta_app.model.ObjectCollectionBuilder.Single.alert
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.time.Duration.Companion.minutes
Expand All @@ -11,31 +10,40 @@ class PatternsByHeadsignTest {
fun `formats as loading when null trips`() {
val now = Clock.System.now()

val objects = ObjectCollectionBuilder()
val route = objects.route()

assertEquals(
PatternsByHeadsign.Format.Loading,
PatternsByHeadsign("", emptyList(), null, null).format(now)
PatternsByHeadsign(route, "", emptyList(), null, null).format(now)
)
}

@Test
fun `formats as alert with no trips and alert`() {
val now = Clock.System.now()

val alert = alert {}
val objects = ObjectCollectionBuilder()
val route = objects.route()

val alert = objects.alert {}

assertEquals(
PatternsByHeadsign.Format.NoService(alert),
PatternsByHeadsign("", emptyList(), emptyList(), listOf(alert)).format(now)
PatternsByHeadsign(route, "", emptyList(), emptyList(), listOf(alert)).format(now)
)
}

@Test
fun `formats as none with no trips and no alert`() {
val now = Clock.System.now()

val objects = ObjectCollectionBuilder()
val route = objects.route()

assertEquals(
PatternsByHeadsign.Format.None,
PatternsByHeadsign("", emptyList(), emptyList(), emptyList()).format(now)
PatternsByHeadsign(route, "", emptyList(), emptyList(), emptyList()).format(now)
)
}

Expand All @@ -44,6 +52,7 @@ class PatternsByHeadsignTest {
val now = Clock.System.now()

val objects = ObjectCollectionBuilder()
val route = objects.route()

val trip1 = objects.trip()
val trip2 = objects.trip()
Expand Down Expand Up @@ -71,7 +80,63 @@ class PatternsByHeadsignTest {
)
)
),
PatternsByHeadsign("", emptyList(), listOf(upcomingTrip1, upcomingTrip2)).format(now)
PatternsByHeadsign(route, "", emptyList(), listOf(upcomingTrip1, upcomingTrip2))
.format(now)
)
}

@Test
fun `format skips schedules on subway but keeps on non-subway`() {
val now = Clock.System.now()

val objects = ObjectCollectionBuilder()
val subwayRoute = objects.route { type = RouteType.LIGHT_RAIL }
val busRoute = objects.route { type = RouteType.BUS }

val trip1 = objects.trip()
val trip2 = objects.trip()

val schedule1 =
objects.schedule {
trip = trip1
departureTime = now + 5.minutes
}
val prediction2 =
objects.prediction {
trip = trip2
departureTime = now + 5.minutes
}

val upcomingTrip1 = UpcomingTrip(schedule1)
val upcomingTrip2 = UpcomingTrip(prediction2)

assertEquals(
PatternsByHeadsign.Format.Some(
listOf(
PatternsByHeadsign.Format.Some.FormatWithId(
trip2.id,
UpcomingTrip.Format.Minutes(5)
)
)
),
PatternsByHeadsign(subwayRoute, "", emptyList(), listOf(upcomingTrip1, upcomingTrip2))
.format(now)
)
assertEquals(
PatternsByHeadsign.Format.Some(
listOf(
PatternsByHeadsign.Format.Some.FormatWithId(
trip1.id,
UpcomingTrip.Format.Schedule(now + 5.minutes)
),
PatternsByHeadsign.Format.Some.FormatWithId(
trip2.id,
UpcomingTrip.Format.Minutes(5)
)
)
),
PatternsByHeadsign(busRoute, "", emptyList(), listOf(upcomingTrip1, upcomingTrip2))
.format(now)
)
}
}

0 comments on commit 7d7825a

Please sign in to comment.