Skip to content

Commit

Permalink
fix(predictions): only fetch predictions for a train when user opens …
Browse files Browse the repository at this point in the history
…the train opover (#203)

* fix: get departure predictions per route instead of per vehicle

* fix: prediction fetching

* fix: prediction fetching

* fix: prediction

* fix: remove extra file

* fix: remove redundant variable

* fix: some naming

* fix: trailing whitespace
  • Loading branch information
rudiejd authored May 22, 2024
1 parent 8fecca3 commit c564321
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 21 deletions.
8 changes: 8 additions & 0 deletions server/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ def routes(route_ids_string):
return flask.Response(json.dumps(route_data), mimetype="application/json")


# takes a single trip id
# returns the predicted departure of said trip from a given stop
@application.route("/predictions/<trip_id>/<stop_id>")
def vehicles(trip_id, stop_id):
departure = asyncio.run(mbta_api.trip_departure_predictions(trip_id, stop_id))
return flask.Response(json.dumps(departure), mimetype="application/json")


@application.route("/healthcheck")
def healthcheck():
return server.healthcheck.run()
Expand Down
21 changes: 10 additions & 11 deletions server/mbta_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,21 +86,22 @@ def maybe_reverse(stops, route):
return stops


async def departure_prediction_for_vehicle(vehicle_id, stop_id):
predictions = await getV3("predictions", {"filter[stop]": stop_id})

for prediction in predictions:
if prediction["vehicle"]["id"] == vehicle_id:
return prediction["departure_time"]
async def trip_departure_predictions(trip_id: str, stop_id: str):
try:
prediction = await getV3("predictions", {"filter[trip]": trip_id, "filter[stop]": stop_id})

return None
return {'departure_time': prediction[0]['departure_time']}
except Exception as e:
print(f"Error getting predictions for trip: {e}")
return {"departure_time": "null"}


# takes a list of route ids
# uses getV3 to request real-time vehicle data for a given route id
# returns list of all vehicles
async def vehicle_data_for_routes(route_ids):
route_ids = normalize_custom_route_ids(route_ids)

vehicles = await getV3(
"vehicles",
{
Expand All @@ -125,10 +126,9 @@ async def vehicle_data_for_routes(route_ids):
# determine if vehicle is new
is_new = fleet.vehicle_array_is_new(custom_route, vehicle["label"].split("-"))

departure_prediction = await departure_prediction_for_vehicle(vehicle["id"], vehicle["stop"]["id"])

vehicles_to_display.append(
{
"vehicleId": vehicle["id"],
"label": vehicle["label"],
"route": custom_route,
"direction": vehicle["direction_id"],
Expand All @@ -138,8 +138,7 @@ async def vehicle_data_for_routes(route_ids):
"stationId": vehicle["stop"]["parent_station"]["id"],
"tripId": vehicle["trip"]["id"],
"isNewTrain": is_new,
"updatedAt": vehicle["updated_at"],
"departurePrediction": departure_prediction,
"updatedAt": vehicle["updated_at"]
}
)

Expand Down
7 changes: 6 additions & 1 deletion src/components/TrainPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState, useLayoutEffect } from 'react';
import { createPortal } from 'react-dom';
import classNames from 'classnames';
import { renderTrainLabel } from '../labels';
import { usePrediction } from '../hooks/usePrediction';

const popoverDistance = 15;

Expand Down Expand Up @@ -71,6 +72,8 @@ export const TrainPopover = (props) => {
trainY,
]);

const prediction = usePrediction(isVisible ? train.tripId : null, train.stationId);

return createPortal(
<div
ref={setPopoverElement}
Expand All @@ -79,7 +82,9 @@ export const TrainPopover = (props) => {
aria-hidden="true"
>
<div className="scale-container" style={{ border: `2px solid ${colors.train}` }}>
<div className="train-details">{renderTrainLabel(train, route, colors.train)}</div>
<div className="train-details">
{renderTrainLabel(train, prediction, route, colors.train)}
</div>
</div>
</div>,
container
Expand Down
4 changes: 3 additions & 1 deletion src/hooks/useMbtaApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ const getTrainPositions = (routes: string[], isFirstRequest: boolean | null) =>
return Promise.resolve(initialTrainsData);
}
}
return fetch(`/trains/${routes.join(',')}`).then((res) => res.json());
return fetch(`/trains/${routes.join(',')}`).then((res) => {
res.json();
});
};

const filterNew = (trains: Train[]) => {
Expand Down
20 changes: 20 additions & 0 deletions src/hooks/usePrediction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useEffect, useState } from 'react';
import { Prediction } from '../types';

const getPrediction = (tripId: string, stopId: string) => {
return fetch(`/predictions/${tripId}/${stopId}`).then((res) => {
return res.json();
});
};

export const usePrediction = (tripId: string, stopId: string) => {
const [pred, setPrediction] = useState<Prediction | null>(null);
useEffect(() => {
if (!tripId) {
return;
}
getPrediction(tripId, stopId).then((pred) => setPrediction(pred));
}, [tripId, stopId]);

return pred;
};
21 changes: 14 additions & 7 deletions src/labels.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Route, Train } from './types';
import { Route, Train, Prediction } from './types';

const abbreviateStationName = (station: string) =>
station
Expand All @@ -19,8 +19,8 @@ const getReadableStatusLabel = (status) => {
return '';
};

const getDepartureTimePrediction = (prediction: Date) => {
if (prediction) {
const getDepartureTimePrediction = (prediction: Date | null) => {
if (prediction && !isNaN(prediction)) {
return `Next departure ${prediction.toLocaleTimeString()}`;
}

Expand Down Expand Up @@ -77,8 +77,10 @@ const renderLeadCarLabel = (train: Train, backgroundColor) => {
);
};

const renderDetailsLabel = (train: Train) => {
const departurePrediction = getDepartureTimePrediction(new Date(train.departurePrediction));
const renderDetailsLabel = (train: Train, prediction: Prediction | null) => {
const departurePrediction = getDepartureTimePrediction(
prediction ? new Date(prediction.departure_time) : null
);
const lastUpdated = getLastUpdatedAt(new Date(train.updatedAt));

return (
Expand All @@ -89,13 +91,18 @@ const renderDetailsLabel = (train: Train) => {
);
};

export const renderTrainLabel = (train: Train, route: Route, accentColor) => {
export const renderTrainLabel = (
train: Train,
prediction: Prediction | null,
route: Route,
accentColor
) => {
return (
<>
{renderStationLabel(train, route)}
{renderDestinationLabel(train, route)}
{renderLeadCarLabel(train, accentColor)}
{renderDetailsLabel(train)}
{renderDetailsLabel(train, prediction)}
</>
);
};
Expand Down
6 changes: 5 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface Routes {
export type CurrentStatus = 'IN_TRANSIT_TO' | 'INCOMING_AT' | 'STOPPED_AT';

export interface Train {
vehicleId: string;
currentStatus: CurrentStatus;
direction: number;
isNewTrain: boolean;
Expand All @@ -37,7 +38,6 @@ export interface Train {
route: string;
stationId: string;
tripId: string;
departurePrediction: string;
updatedAt: string;
}

Expand Down Expand Up @@ -86,3 +86,7 @@ export interface Pair {
}

export type VehiclesAge = 'vehicles' | 'new_vehicles' | 'old_vehicles';

export interface Prediction {
departure_time: Date;
}

0 comments on commit c564321

Please sign in to comment.