Files
gods-eye-view/src/data/cctvLod.test.mjs
T
12790b36c6 feat(cctv): seven camera packs, semantic labels, terrain-clear monitor planes (#433)
Seven contributed CCTV camera packs (Ontario 511, Fintraffic Finland, DriveBC, TxDOT Texas, Estonia Tallinn + Tarktee, Warendorf, Live Traffic NSW) on a fair per-pack catalog cap; semantic camera labels; monitor planes lifted clear of the terrain from a precomputed, bundled ground-height dataset. Default catalog 800 → 3,446 cameras. Full context in PR #433.

Co-authored-by: sendmebits <[email protected]>
Co-authored-by: avocadoAgent <[email protected]>
Co-authored-by: Alex Harvey <[email protected]>
Co-authored-by: Jason Meredith <[email protected]>
Co-authored-by: Daniel Smith <[email protected]>
Co-authored-by: Vladimir Sobolev <[email protected]>
Co-authored-by: Matthias Schneider <[email protected]>
Co-authored-by: Cyber Knight <[email protected]>
Co-authored-by: NovaSoft Australia <[email protected]>
2026-09-13 21:48:33 -05:00

648 lines
21 KiB
JavaScript

// src/data/cctvLod.test.mjs
// Pure LOD-engine tests, adapted from Manjunath's Part C suite
// (fix/cctv-part-c-review): zoom-scaled card budgets, nearest-first in-view
// selection, video exclusion, the eviction-grace planner, and source-aware
// static-frame pacing.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
CCTV_CARD_CENTER_WEIGHT,
CCTV_CARD_INCUMBENT_FACTOR,
CCTV_LOD_GRACE_MS,
CCTV_LOD_GRACE_PASSES,
CCTV_AMBIENT_CARD_MAX,
CCTV_AMBIENT_CARD_MID,
CCTV_AMBIENT_CARD_MIN,
applyEvictionGrace,
blendCenterRankKm,
cctvCandidateSpreadKm,
cctvLodBudgets,
distributeCctvCards,
hasFiniteCctvViewport,
incumbentRankKm,
screenCenterFraction,
selectCctvLod,
staticFrameRefreshMs,
} from './cctvLod.js';
function candidates(count, options = {}) {
return Array.from({ length: count }, (_, index) => ({
id: `cam-${String(index).padStart(3, '0')}`,
distanceKm: index / 10,
inView: options.hiddenAt !== index,
isVideo: options.videoAt === index,
}));
}
test('cctvLodBudgets scales the card budget from 20 to 40 with view height', () => {
// Owner round 2 (item C): budgets raised 16/24/32 -> 20/28/40.
assert.equal(CCTV_AMBIENT_CARD_MIN, 20);
assert.equal(CCTV_AMBIENT_CARD_MID, 28);
assert.equal(CCTV_AMBIENT_CARD_MAX, 40);
assert.deepEqual(cctvLodBudgets(500), { cardLimit: CCTV_AMBIENT_CARD_MIN });
assert.deepEqual(cctvLodBudgets(8_000), { cardLimit: CCTV_AMBIENT_CARD_MID });
assert.deepEqual(cctvLodBudgets(30_000), {
cardLimit: CCTV_AMBIENT_CARD_MAX,
});
});
test('selectCctvLod caps ambient cards at the 40 hard cap regardless of catalog size', () => {
const selected = selectCctvLod(candidates(900), { cameraHeightM: 30_000 });
assert.equal(selected.cardIds.length, CCTV_AMBIENT_CARD_MAX);
assert.equal(selected.budgets.cardLimit, CCTV_AMBIENT_CARD_MAX);
});
test('selectCctvLod uses only in-view cameras and orders by viewer distance', () => {
const input = candidates(20, { hiddenAt: 0 }).reverse();
const selected = selectCctvLod(input, { cameraHeightM: 500 });
assert.equal(selected.cardIds.includes('cam-000'), false);
assert.deepEqual(selected.cardIds.slice(0, 3), [
'cam-001',
'cam-002',
'cam-003',
]);
});
test('video cameras never consume ambient card slots', () => {
const selected = selectCctvLod(candidates(30, { videoAt: 0 }), {
cameraHeightM: 500,
});
assert.equal(selected.cardIds.includes('cam-000'), false);
assert.equal(selected.cardIds.length, CCTV_AMBIENT_CARD_MIN);
});
test('selectCctvLod tolerates malformed candidate rows', () => {
const selected = selectCctvLod(
[
null,
{},
{ id: '', inView: true },
{ id: 'cam-ok', inView: true, distanceKm: NaN },
],
{ cameraHeightM: 500 },
);
assert.deepEqual(selected.cardIds, ['cam-ok']);
});
// ─── Selection-level incumbency (owner field-test finding 4) ────────────────
test('incumbentRankKm discounts incumbents by the 20% factor', () => {
assert.equal(incumbentRankKm(10, false), 10);
assert.equal(incumbentRankKm(10, true), 10 * CCTV_CARD_INCUMBENT_FACTOR);
assert.equal(incumbentRankKm(NaN, true), Infinity);
});
test('selectCctvLod: a carded camera outranks a slightly-nearer non-carded one', () => {
// Street budget filled: budget-1 anchors + one contested slot. The
// incumbent at 5.0 km holds its card against a newcomer at 4.5 km (only
// 10% closer).
const anchors = Array.from({ length: CCTV_AMBIENT_CARD_MIN - 1 }, (_, i) => ({
id: `anchor-${String(i).padStart(2, '0')}`,
distanceKm: i / 100,
inView: true,
}));
const contested = [
{ id: 'incumbent', distanceKm: 5.0, inView: true },
{ id: 'newcomer', distanceKm: 4.5, inView: true },
];
const selected = selectCctvLod([...anchors, ...contested], {
cameraHeightM: 500,
incumbentIds: new Set(['incumbent']),
});
assert.equal(selected.cardIds.length, CCTV_AMBIENT_CARD_MIN);
assert.ok(selected.cardIds.includes('incumbent'), 'incumbent keeps its card');
assert.equal(selected.cardIds.includes('newcomer'), false);
});
test('selectCctvLod: a meaningfully (>20%) closer camera still displaces an incumbent', () => {
const anchors = Array.from({ length: CCTV_AMBIENT_CARD_MIN - 1 }, (_, i) => ({
id: `anchor-${String(i).padStart(2, '0')}`,
distanceKm: i / 100,
inView: true,
}));
const contested = [
{ id: 'incumbent', distanceKm: 5.0, inView: true },
{ id: 'newcomer', distanceKm: 3.9, inView: true }, // beats 5.0 * 0.8 = 4.0
];
const selected = selectCctvLod([...anchors, ...contested], {
cameraHeightM: 500,
incumbentIds: new Set(['incumbent']),
});
assert.ok(
selected.cardIds.includes('newcomer'),
'meaningfully closer camera wins',
);
assert.equal(selected.cardIds.includes('incumbent'), false);
});
test('selectCctvLod: no incumbents means plain nearest-first (unchanged behavior)', () => {
const plain = selectCctvLod(candidates(20), { cameraHeightM: 500 });
const withEmpty = selectCctvLod(candidates(20), {
cameraHeightM: 500,
incumbentIds: new Set(),
});
assert.deepEqual(plain.cardIds, withEmpty.cardIds);
});
// ─── distributeCctvCards — screen distribution (owner round 2, item C) ──────
// Grid: 5 cols x 4 rows. At viewW 1000 / viewH 800 each cell is 200x200 px.
test('distributeCctvCards spreads clustered candidates across cells', () => {
// Four nearest candidates cluster in the top-left cell; a farther one
// sits alone bottom-right. Nearest-first would take the whole cluster —
// distribution gives every occupied cell its best first.
const clustered = Array.from({ length: 4 }, (_, i) => ({
id: `center-${i}`,
sx: 50 + i * 10,
sy: 50,
rankKm: i,
}));
const lonely = { id: 'periphery', sx: 950, sy: 750, rankKm: 30 };
assert.deepEqual(
distributeCctvCards([...clustered, lonely], {
budget: 2,
viewW: 1000,
viewH: 800,
}),
['center-0', 'periphery'],
);
});
test('distributeCctvCards: leftover budget falls back to global rank order', () => {
const clustered = Array.from({ length: 4 }, (_, i) => ({
id: `center-${i}`,
sx: 50 + i * 10,
sy: 50,
rankKm: i,
}));
const lonely = { id: 'periphery', sx: 950, sy: 750, rankKm: 30 };
// 2 occupied cells, budget 4: both cell winners, then the cluster's next
// two by rank.
assert.deepEqual(
distributeCctvCards([...clustered, lonely], {
budget: 4,
viewW: 1000,
viewH: 800,
}),
['center-0', 'periphery', 'center-1', 'center-2'],
);
});
test('distributeCctvCards: empty cells are skipped, budget respected', () => {
// Everything in ONE cell — the 19 empty cells claim nothing; the single
// occupied cell fills the whole budget in rank order.
const oneCell = Array.from({ length: 5 }, (_, i) => ({
id: `c-${i}`,
sx: 20 + i * 5,
sy: 20,
rankKm: i,
}));
const ids = distributeCctvCards(oneCell, {
budget: 3,
viewW: 1000,
viewH: 800,
});
assert.deepEqual(ids, ['c-0', 'c-1', 'c-2']);
});
test('distributeCctvCards: an under-budget cut keeps the nearest cell winners', () => {
const ids = distributeCctvCards(
[
{ id: 'far-cell', sx: 900, sy: 700, rankKm: 9 },
{ id: 'near-cell', sx: 100, sy: 100, rankKm: 1 },
],
{ budget: 1, viewW: 1000, viewH: 800 },
);
assert.deepEqual(ids, ['near-cell']);
});
test('distributeCctvCards: incumbency (effective distance) is honored inside a cell', () => {
// Same cell: the incumbent at 5.0 km (discounted to 4.0) beats the
// non-carded newcomer at 4.5 km for the cell slot.
const ids = distributeCctvCards(
[
{ id: 'newcomer', sx: 100, sy: 100, rankKm: incumbentRankKm(4.5, false) },
{ id: 'incumbent', sx: 120, sy: 100, rankKm: incumbentRankKm(5.0, true) },
],
{ budget: 1, viewW: 1000, viewH: 800 },
);
assert.deepEqual(ids, ['incumbent']);
});
test('distributeCctvCards: malformed rows dropped; offscreen anchors clamp to edge cells', () => {
const ids = distributeCctvCards(
[
null,
{ id: '', sx: 10, sy: 10, rankKm: 0 },
{ id: 'nan', sx: NaN, sy: 10, rankKm: 0 },
{ id: 'ok', sx: 10, sy: 10, rankKm: 1 },
// Margin candidates just outside the viewport land in the edge cells.
{ id: 'edge', sx: -20, sy: 850, rankKm: 2 },
],
{ budget: 5, viewW: 1000, viewH: 800 },
);
assert.deepEqual(ids.sort(), ['edge', 'ok']);
assert.deepEqual(
distributeCctvCards([], { budget: 5, viewW: 1000, viewH: 800 }),
[],
);
});
test('selectCctvLod with viewport dims routes the fill through screen distribution', () => {
// A cluster larger than the street budget sits mid-screen; a periphery
// camera ranks past the budget. Nearest-first drops it — distribution
// keeps it because its cell is otherwise empty.
const clustered = Array.from(
{ length: CCTV_AMBIENT_CARD_MIN + 2 },
(_, i) => ({
id: `center-${String(i).padStart(2, '0')}`,
distanceKm: i / 10,
inView: true,
sx: 400 + (i % 5) * 8,
sy: 300 + Math.floor(i / 5) * 8,
}),
);
const periphery = {
id: 'periphery',
distanceKm: 9,
inView: true,
sx: 950,
sy: 750,
};
const selected = selectCctvLod([...clustered, periphery], {
cameraHeightM: 500,
viewW: 1000,
viewH: 800,
});
assert.equal(
selected.cardIds.length,
CCTV_AMBIENT_CARD_MIN,
'budget respected',
);
assert.ok(
selected.cardIds.includes('periphery'),
'periphery cell holds a card',
);
// Without screen info the original nearest-first cap applies unchanged.
const plain = selectCctvLod(
[...clustered, periphery].map(({ sx, sy, ...rest }) => rest),
{ cameraHeightM: 500 },
);
assert.equal(plain.cardIds.includes('periphery'), false);
});
test('center-weight helpers are bounded and robust to invalid anchors and outliers', () => {
assert.equal(CCTV_CARD_CENTER_WEIGHT, 0.5);
assert.equal(hasFiniteCctvViewport(1000, 800), true);
assert.equal(hasFiniteCctvViewport(Infinity, 800), false);
assert.equal(screenCenterFraction(500, 400, 1000, 800), 0);
assert.equal(screenCenterFraction(1000, 800, 1000, 800), 1);
assert.equal(screenCenterFraction(NaN, 400, 1000, 800), 1);
assert.equal(screenCenterFraction(-200, -200, 1000, 800), 1);
assert.equal(screenCenterFraction(500, 400, 0, 0), 0);
const near = Array.from({ length: 20 }, (_, index) => 1 + index / 100);
assert.equal(cctvCandidateSpreadKm([...near, 900]), 1.18);
assert.equal(cctvCandidateSpreadKm([Infinity, NaN]), 0);
assert.equal(blendCenterRankKm(10, 0, 20), 5);
assert.equal(blendCenterRankKm(10, 1, 20), 15);
assert.equal(blendCenterRankKm(10, 0.5, 20, 0), 10);
});
test('non-finite positive viewport dimensions retain exact legacy nearest-first selection', () => {
const near = Array.from({ length: CCTV_AMBIENT_CARD_MIN }, (_, index) => ({
id: `near-${String(index).padStart(2, '0')}`,
distanceKm: index,
inView: true,
sx: 100,
sy: 100,
}));
const far = {
id: 'far-other-cell',
distanceKm: 100,
inView: true,
sx: 900,
sy: 700,
};
const input = [...near, far];
const legacy = selectCctvLod(input, { cameraHeightM: 500 });
assert.deepEqual(
selectCctvLod(input, { cameraHeightM: 500, viewW: Infinity, viewH: 800 })
.cardIds,
legacy.cardIds,
);
assert.deepEqual(
selectCctvLod(input, { cameraHeightM: 500, viewW: 1000, viewH: Infinity })
.cardIds,
legacy.cardIds,
);
const legacyEdgeIds = [
{ id: ' ', distanceKm: 1, inView: true },
{ id: 'normal', distanceKm: 2, inView: true },
];
assert.deepEqual(
selectCctvLod(legacyEdgeIds, { cameraHeightM: 500, viewW: 0, viewH: 800 })
.cardIds,
[' ', 'normal'],
'invalid viewport preserves every non-empty string ID accepted by the legacy path',
);
});
function edgeVsCenterPool() {
const edge = Array.from({ length: 30 }, (_, index) => ({
id: `edge-${String(index).padStart(2, '0')}`,
distanceKm: 1 + index / 100,
inView: true,
sx: 20 + (index % 5) * 2,
sy: 780 - Math.floor(index / 5) * 2,
}));
const center = Array.from({ length: 10 }, (_, index) => ({
id: `center-${String(index).padStart(2, '0')}`,
distanceKm: 12 + index / 100,
inView: true,
sx: 490 + (index % 5) * 4,
sy: 390 + (index % 5) * 2,
}));
return { edge, center };
}
test('center weighting deterministically favors center without starving peripheral coverage', () => {
const { edge, center } = edgeVsCenterPool();
const view = { cameraHeightM: 500, viewW: 1000, viewH: 800 };
const selected = selectCctvLod([...edge, ...center], view);
const reordered = selectCctvLod(
[...center].reverse().concat([...edge].reverse()),
view,
);
assert.equal(selected.cardIds.length, CCTV_AMBIENT_CARD_MIN);
assert.equal(
selected.cardIds.filter((id) => id.startsWith('center-')).length,
10,
);
assert.equal(
selected.cardIds.filter((id) => id.startsWith('edge-')).length,
10,
);
assert.deepEqual(
reordered.cardIds,
selected.cardIds,
'input order cannot affect winners or ordering',
);
const legacy = selectCctvLod(
[...edge, ...center].map(({ sx, sy, ...candidate }) => candidate),
{ cameraHeightM: 500 },
);
assert.equal(
legacy.cardIds.filter((id) => id.startsWith('center-')).length,
0,
);
});
test('ineligible video, hidden, and malformed-distance rows cannot alter eligible winners', () => {
const { edge, center } = edgeVsCenterPool();
const eligible = [...edge, ...center];
const view = { cameraHeightM: 500, viewW: 1000, viewH: 800 };
const baseline = selectCctvLod(eligible, view).cardIds;
const noise = [
...Array.from({ length: 50 }, (_, index) => ({
id: `video-${index}`,
distanceKm: 1_000 + index,
inView: true,
isVideo: true,
sx: 500,
sy: 400,
})),
{
id: 'hidden-outlier',
distanceKm: 9_000,
inView: false,
sx: 900,
sy: 700,
},
{ id: 'bad-distance', distanceKm: NaN, inView: true, sx: 900, sy: 700 },
{ id: '', distanceKm: 0, inView: true, sx: 500, sy: 400 },
];
assert.deepEqual(
selectCctvLod([...eligible, ...noise], view).cardIds,
baseline,
);
assert.deepEqual(
selectCctvLod([...noise].reverse().concat([...eligible].reverse()), view)
.cardIds,
baseline,
);
});
test('malformed-distance rows remain slot-neutral when valid candidates leave spare capacity', () => {
const view = { cameraHeightM: 500, viewW: 1000, viewH: 800 };
const candidates = [
{ id: 'valid', distanceKm: 1, inView: true, sx: 500, sy: 400 },
{ id: 'bad', distanceKm: NaN, inView: true, sx: 510, sy: 410 },
];
assert.deepEqual(selectCctvLod(candidates, view).cardIds, ['valid']);
assert.deepEqual(selectCctvLod([...candidates].reverse(), view).cardIds, [
'valid',
]);
});
test('duplicate IDs are slot-neutral and choose a deterministic representative', () => {
const { edge, center } = edgeVsCenterPool();
const eligible = [...edge, ...center];
const view = { cameraHeightM: 500, viewW: 1000, viewH: 800 };
const baseline = selectCctvLod(eligible, view).cardIds;
const duplicates = [
{ ...edge[0], distanceKm: 1_000, sx: 500, sy: 400 },
{ ...center[0], sx: 20, sy: 780 },
{ ...center[0], distanceKm: NaN, sx: 500, sy: 400 },
];
assert.deepEqual(
selectCctvLod([...eligible, ...duplicates], view).cardIds,
baseline,
);
assert.deepEqual(
selectCctvLod([...duplicates, ...eligible].reverse(), view).cardIds,
baseline,
'input order cannot choose a different duplicate anchor or affect spread',
);
});
test('center weighting has deterministic ID ties and preserves invalid-anchor eligibility', () => {
const anchors = Array.from(
{ length: CCTV_AMBIENT_CARD_MIN - 1 },
(_, index) => ({
id: `anchor-${String(index).padStart(2, '0')}`,
distanceKm: index / 100,
inView: true,
sx: 500,
sy: 400,
}),
);
const tied = [
{ id: 'tie-b', distanceKm: 10, inView: true, sx: 500, sy: 400 },
{ id: 'tie-a', distanceKm: 10, inView: true, sx: 500, sy: 400 },
{ id: 'invalid-anchor', distanceKm: 30, inView: true, sx: NaN, sy: NaN },
];
const selected = selectCctvLod([...anchors, ...tied], {
cameraHeightM: 500,
viewW: 1000,
viewH: 800,
});
assert.ok(selected.cardIds.includes('tie-a'));
assert.equal(
selected.cardIds.includes('tie-b'),
false,
'ID breaks an exact contested tie',
);
assert.equal(
selected.cardIds.includes('invalid-anchor'),
false,
'invalid anchor does not displace valid winners',
);
const onlyInvalid = selectCctvLod(
[{ id: 'invalid-only', distanceKm: 1, inView: true, sx: NaN, sy: NaN }],
{ cameraHeightM: 500, viewW: 1000, viewH: 800 },
);
assert.deepEqual(
onlyInvalid.cardIds,
['invalid-only'],
'defensive top-up preserves eligibility',
);
});
test('applyEvictionGrace keeps a fallen-out card and clears grace when it returns', () => {
// Pass 1: cam-b falls out of the selection but keeps its live card.
const pass1 = applyEvictionGrace({
selectedIds: ['cam-a'],
builtIds: ['cam-a', 'cam-b'],
graceState: new Map(),
nowMs: 1_000,
cardLimit: 16,
});
assert.ok(pass1.keepIds.includes('cam-b'), 'graced card stays alive');
assert.deepEqual(pass1.evictIds, []);
assert.deepEqual(pass1.graceState.get('cam-b'), { misses: 1, since: 1_000 });
// Pass 2: cam-b is selected again — card survived, grace entry cleared.
const pass2 = applyEvictionGrace({
selectedIds: ['cam-a', 'cam-b'],
builtIds: ['cam-a', 'cam-b'],
graceState: pass1.graceState,
nowMs: 1_500,
cardLimit: 16,
});
assert.ok(pass2.keepIds.includes('cam-b'));
assert.deepEqual(pass2.evictIds, []);
assert.equal(pass2.graceState.size, 0);
});
test('applyEvictionGrace evicts a card that stays unselected past the pass budget', () => {
let graceState = new Map();
let result;
// cam-b stays out: survives CCTV_LOD_GRACE_PASSES consecutive misses...
for (let pass = 1; pass <= CCTV_LOD_GRACE_PASSES; pass++) {
result = applyEvictionGrace({
selectedIds: ['cam-a'],
builtIds: ['cam-a', 'cam-b'],
graceState,
nowMs: 1_000 + pass * 100,
cardLimit: 16,
});
graceState = result.graceState;
assert.ok(result.keepIds.includes('cam-b'), `still graced at miss ${pass}`);
}
// ...and is dropped on the next pass.
result = applyEvictionGrace({
selectedIds: ['cam-a'],
builtIds: ['cam-a', 'cam-b'],
graceState,
nowMs: 1_400,
cardLimit: 16,
});
assert.deepEqual(result.evictIds, ['cam-b']);
assert.equal(result.keepIds.includes('cam-b'), false);
assert.equal(result.graceState.has('cam-b'), false);
});
test('applyEvictionGrace evicts on wall-time even with few passes', () => {
const pass1 = applyEvictionGrace({
selectedIds: ['cam-a'],
builtIds: ['cam-a', 'cam-b'],
graceState: new Map(),
nowMs: 10_000,
cardLimit: 16,
});
assert.ok(pass1.keepIds.includes('cam-b'));
const pass2 = applyEvictionGrace({
selectedIds: ['cam-a'],
builtIds: ['cam-a', 'cam-b'],
graceState: pass1.graceState,
nowMs: 10_000 + CCTV_LOD_GRACE_MS,
cardLimit: 16,
});
assert.deepEqual(pass2.evictIds, ['cam-b']);
});
test('applyEvictionGrace never exceeds the hard cap; graced cards go first, oldest first', () => {
// Two cards in grace, cam-old fell out before cam-new.
const graceState = new Map([
['cam-old', { misses: 1, since: 1_000 }],
['cam-new', { misses: 1, since: 2_000 }],
]);
// Selection fills 3 of 4 slots: only one grace slot remains.
const result = applyEvictionGrace({
selectedIds: ['cam-a', 'cam-b', 'cam-c'],
builtIds: ['cam-a', 'cam-b', 'cam-old', 'cam-new'],
graceState,
nowMs: 2_100,
cardLimit: 4,
});
assert.equal(result.keepIds.length, 4);
// cam-c is newly selected and enters immediately despite grace pressure.
assert.ok(result.keepIds.includes('cam-c'));
assert.ok(result.keepIds.includes('cam-new'), 'newest grace entry survives');
assert.deepEqual(
result.evictIds,
['cam-old'],
'oldest grace entry evicted for the cap',
);
});
test('applyEvictionGrace with a full selection keeps no graced cards', () => {
const selectedIds = Array.from({ length: 4 }, (_, i) => `cam-${i}`);
const result = applyEvictionGrace({
selectedIds,
builtIds: [...selectedIds, 'cam-extra'],
graceState: new Map(),
nowMs: 1_000,
cardLimit: 4,
});
assert.deepEqual(result.keepIds.sort(), selectedIds.sort());
assert.deepEqual(result.evictIds, ['cam-extra']);
});
test('staticFrameRefreshMs follows known pack cadences and bounds explicit values', () => {
assert.equal(
staticFrameRefreshMs({ provider: 'Austin Transportation & Public Works' }),
300_000,
);
assert.equal(
staticFrameRefreshMs({ provider: 'Transport for London' }),
180_000,
);
assert.equal(staticFrameRefreshMs({ provider: 'Caltrans' }), 180_000);
assert.equal(staticFrameRefreshMs({ provider: 'Ontario 511' }), 180_000);
assert.equal(staticFrameRefreshMs({ provider: 'TxDOT' }), 180_000);
// Digitraffic publishes one weathercam frame per 600 s collection interval.
assert.equal(staticFrameRefreshMs({ provider: 'Fintraffic' }), 600_000);
assert.equal(staticFrameRefreshMs({ frameRefreshMs: 5_000 }), 60_000);
assert.equal(staticFrameRefreshMs({ frameRefreshMs: 3_000_000 }), 1_200_000);
assert.equal(staticFrameRefreshMs({ provider: 'Unknown Provider' }), 300_000);
});