Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions common/api/hooks/landingGrades.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { useQuery } from '@tanstack/react-query';
import { ONE_HOUR } from '../../constants/time';
import type { LineDelays } from '../../types/delays';
import type { Line, LineRouteId } from '../../types/lines';
import { fetchLineDelaysByLine } from '../delays';

const DELAY_LINE_IDS: Partial<Record<Line, string[]>> = {
'line-red': ['Red'],
'line-orange': ['Orange'],
'line-blue': ['Blue'],
'line-green': ['Green-B', 'Green-C', 'Green-D', 'Green-E'],
'line-mattapan': ['Mattapan'],
'line-commuter-rail': ['CommuterRail'],
};

function getLandingDateRange(): { startDate: string; endDate: string } {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 12 * 7); // 12 weeks
return {
startDate: start.toISOString().split('T')[0],
endDate: end.toISOString().split('T')[0],
};
}

async function fetchDelaysForLineId(lineId: string): Promise<LineDelays[]> {
const { startDate, endDate } = getLandingDateRange();
return fetchLineDelaysByLine({
start_date: startDate,
end_date: endDate,
line: lineId as LineRouteId,
agg: 'weekly',
});
}

/**
* Fetch weekly delay data for a line from the API (~12 weeks).
* For Green line, aggregates all branches (B/C/D/E) by date.
*/
async function fetchLandingLineDelays(line: Line): Promise<LineDelays[]> {
const lineIds = DELAY_LINE_IDS[line];
if (!lineIds) return [];

if (lineIds.length === 1) {
return fetchDelaysForLineId(lineIds[0]);
}

// For Green line: fetch all branches and aggregate by date
const allBranches = await Promise.all(lineIds.map(fetchDelaysForLineId));
const byDate = new Map<string, LineDelays>();

for (const branch of allBranches) {
for (const entry of branch) {
const existing = byDate.get(entry.date);
if (existing) {
existing.total_delay_time += entry.total_delay_time;
existing.disabled_vehicle += entry.disabled_vehicle;
existing.door_problem += entry.door_problem;
existing.flooding += entry.flooding;
existing.fire += entry.fire;
existing.medical_emergency += entry.medical_emergency;
existing.other += entry.other;
existing.police_activity += entry.police_activity;
existing.power_problem += entry.power_problem;
existing.signal_problem += entry.signal_problem;
existing.track_work += entry.track_work;
existing.car_traffic += entry.car_traffic;
existing.mechanical_problem += entry.mechanical_problem;
existing.brake_problem += entry.brake_problem;
existing.switch_problem += entry.switch_problem;
existing.track_issue += entry.track_issue;
} else {
byDate.set(entry.date, { ...entry });
}
}
}

return Array.from(byDate.values()).sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
);
}

export const useLandingLineDelays = (line: Line) => {
return useQuery({
queryKey: ['landingLineDelays', line],
queryFn: () => fetchLandingLineDelays(line),
staleTime: ONE_HOUR,
});
};
1 change: 1 addition & 0 deletions common/constants/baselines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const PEAK_RIDERSHIP: {
'line-blue': 60129,
'line-green': 87148,
'line-mattapan': 0,
'line-bus': 520580,
'1': 15272,
'4': 850,
'7': 5126,
Expand Down
2 changes: 2 additions & 0 deletions common/types/dataPoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface DayDelayTotals {
Orange: number;
Red: number;
Green: number;
Mattapan: number;
}

export type Direction = 'northbound' | 'southbound';
Expand Down Expand Up @@ -105,6 +106,7 @@ export interface DeliveredTripMetrics {
total_time: number;
count: number;
miles_per_hour?: string;
avg_car_age?: number;
}

export type LineSegmentData = {
Expand Down
2 changes: 1 addition & 1 deletion common/types/ridership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type ServiceDay = 'weekday' | 'saturday' | 'sunday';
export type ServiceRegime = 'peak' | 'current';

export type RidershipKey =
| Exclude<Line, 'line-bus'>
| Line
| BusRouteId
| CommuterRailRoute
| 'line-RIDE'
Expand Down
27 changes: 27 additions & 0 deletions copy/landingCopy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@ export const ServiceBaseline = <p className={BASELINE_STYLE}>Compared to histori

export const RidershipBaseline = <p className={BASELINE_STYLE}>Compared to historical maximums</p>;

export const SlowZonesBaseline = (
<p className={BASELINE_STYLE}>Total slow zone delay in minutes per line</p>
);

const SlowZonesDescription = (
<p className={DESCRIPTION_STYLE}>
How many slow zones are adding delays?
<br />
<span className="text-sm text-stone-800">
Slow zones are track segments where trains must operate below normal speed due to
infrastructure issues. Less delay means faster, more reliable service.
</span>
</p>
);

const SpeedDescription = (
<p className={DESCRIPTION_STYLE}>
How quickly can riders move through Boston?
Expand Down Expand Up @@ -45,8 +60,20 @@ const RidershipDescription = (
</p>
);

const FleetAgeDescription = (
<p className={DESCRIPTION_STYLE}>
How old are the vehicles in the system?
<br />
<span className="text-sm text-stone-800">
We measure fleet age by the average age of the vehicles in the system.
</span>
</p>
);

export const LandingChartCopyMap: { [key in LandingCharts]: React.JSX.Element } = {
Service: ServiceDescription,
Speed: SpeedDescription,
Ridership: RidershipDescription,
'Slow Zones': SlowZonesDescription,
'Fleet Age': FleetAgeDescription,
};
16 changes: 15 additions & 1 deletion modules/landing/LandingChartWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,23 @@ import type { LandingCharts } from './types';
interface LandingChartWidgetProps {
title: LandingCharts;
children: React.ReactNode;
compact?: boolean;
}

export const LandingChartWidget: React.FC<LandingChartWidgetProps> = ({ title, children }) => {
export const LandingChartWidget: React.FC<LandingChartWidgetProps> = ({
title,
children,
compact = false,
}) => {
if (compact) {
return (
<div className="flex w-full flex-col gap-2">
<h3 className="text-2xl font-thin text-stone-900">{title}</h3>
<div className="flex flex-col gap-y-2">{children}</div>
</div>
);
}

return (
<div className="flex w-full max-w-5xl flex-col gap-x-8 gap-y-4 px-4 md:px-8 lg:items-center lg:gap-y-8 lg:px-12">
<h2 className="w-full text-7xl font-thin text-stone-900 lg:text-5xl xl:text-7xl">{title}</h2>
Expand Down
33 changes: 24 additions & 9 deletions modules/landing/LandingCharts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { TODAY_STRING } from '../../common/constants/dates';
import { OverallSpeedChartWrapper } from './charts/OverallSpeedChartWrapper';
import { OverallServiceChartWrapper } from './charts/OverallServiceChartWrapper';
import { OverallRidershipChartWrapper } from './charts/OverallRidershipChartWrapper';
import { OverallSlowZonesChartWrapper } from './charts/OverallSlowZonesChartWrapper';
import { OverallFleetAgeChartWrapper } from './charts/OverallFleetAgeChartWrapper';
import { LandingChartWidget } from './LandingChartWidget';
import { LineReportCard } from './LineReportCard';

export const LandingCharts: React.FC = () => {
// This query is just to get the lambdas "warm"
Expand All @@ -16,15 +19,27 @@ export const LandingCharts: React.FC = () => {
});
return (
<div className="flex flex-col items-center gap-8 lg:gap-12">
<LandingChartWidget title="Speed">
<OverallSpeedChartWrapper />
</LandingChartWidget>
<LandingChartWidget title="Service">
<OverallServiceChartWrapper />
</LandingChartWidget>
<LandingChartWidget title="Ridership">
<OverallRidershipChartWrapper />
</LandingChartWidget>
<LineReportCard />
<div className="flex w-full max-w-5xl flex-col gap-4 px-4 md:px-8 lg:px-12">
<h2 className="text-3xl font-thin text-stone-900 lg:text-4xl">Performance trends</h2>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
<LandingChartWidget title="Speed" compact>
<OverallSpeedChartWrapper />
</LandingChartWidget>
<LandingChartWidget title="Service" compact>
<OverallServiceChartWrapper />
</LandingChartWidget>
<LandingChartWidget title="Ridership" compact>
<OverallRidershipChartWrapper />
</LandingChartWidget>
<LandingChartWidget title="Slow Zones" compact>
<OverallSlowZonesChartWrapper />
</LandingChartWidget>
<LandingChartWidget title="Fleet Age" compact>
<OverallFleetAgeChartWrapper />
</LandingChartWidget>
</div>
</div>
</div>
);
};
Loading
Loading