feat(transit): let a feed name its own modes, and call TTC trams streetcars

A Toronto 501 and a Helsinki 1010 are both the `tram` mode. Only one of
them is called a tram by the people who ride it, and the detection label
is documented as "what a person standing at the stop would call it".

So the mode stays shared — same silhouette, same speed limits — and a
registry entry may carry `modeWords` naming the modes it runs. Everything
without an override falls through to the shared table, so this renames one
word for one operator and nothing else.

Threading the feed to the detection metric needed a resolver in queries.js,
since the cached-contact path has only the entry. It answers from live feeds
first, so a feed disabled mid-session stops supplying vocabulary.
This commit is contained in:
Cliff Flood committed 2026-10-15 20:18:15 -04:00
1 parent c5f7fe285b
commit 9fc4a5ac04
4 files changed
+125 -9

No files matched your search

+52
View File
@@ -0,0 +1,52 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {
buildTransitSelectionCopy,
transitDetectionClass,
transitDetectionMetric,
transitModeAbbr,
transitModeWord,
} from './policy.js';
import { getTransitFeed } from '../../data/transitFeeds.js';
const ttc = () => getTransitFeed('ttc-toronto');
const hsl = () => getTransitFeed('hsl-helsinki');
test('an operator that names its own vehicles overrides the shared mode word', () => {
// A Toronto 501 is a streetcar. A Helsinki 1010 is a tram. Both are the
// `tram` mode, so the word a reader sees has to come from the operator,
// not from the mode.
assert.equal(transitModeWord('tram', ttc()), 'Streetcar');
assert.equal(transitModeAbbr('tram', ttc()), 'STREETCAR');
assert.equal(transitModeWord('tram', hsl()), 'Tram');
assert.equal(transitModeAbbr('tram', hsl()), 'TRAM');
});
test('an override renames one mode, never the rest of the feed', () => {
assert.equal(transitModeWord('bus', ttc()), 'Bus');
assert.equal(transitModeWord('subway', ttc()), 'Subway');
assert.equal(transitModeAbbr('bus', ttc()), 'BUS');
});
test('a missing feed or unknown mode falls back to the shared table', () => {
assert.equal(transitModeWord('tram', null), 'Tram');
assert.equal(transitModeWord('spaceship', ttc()), 'Transit vehicle');
assert.equal(transitModeAbbr('spaceship', ttc()), 'TRANSIT');
assert.equal(transitModeWord('tram', { name: 'No override' }), 'Tram');
});
test('the selection card calls a Toronto 501 a streetcar', () => {
const record = { id: '4400', routeId: '501', timestamp: 0 };
const copy = buildTransitSelectionCopy(ttc(), record, 'tram', 0, null, null);
assert.match(copy.details.join(' | '), /Streetcar · TTC/);
assert.doesNotMatch(copy.details.join(' | '), /Tram/);
});
test('detection labels carry the operator vocabulary within the field width', () => {
const klass = transitDetectionClass('tram', ttc());
assert.equal(klass, 'STREETCAR TTC');
assert.ok(klass.length <= 20, 'fits the detection class field');
assert.equal(transitDetectionClass('tram', hsl()), 'TRAM HSL');
const metric = transitDetectionMetric({ mode: 'tram' }, 'tram', 0, ttc());
assert.match(metric, /^STREETCAR/);
});