From c4f18169ebeac81272badaa9c833a4d68be98fd2 Mon Sep 17 00:00:00 2001 From: bassem chagra Date: Sun, 27 Sep 2026 12:20:45 +0000 Subject: [PATCH] fix(cctv): raise the default catalog cap to 4,500 and enforce it over every pack The per-pack defaults already summed to 4,054 before WSDOT (4,304 with it), past the documented 4,000 cap, so a default install was trimming. The existing sum check in the DriveBC test listed packs by hand and missed Calgary and DelDOT. Replace it with a check in cctvCatalogCap.test.mjs that sums every exported DEFAULT_*_MAX_SOURCES, and drop the Calgary test that pinned the cap at exactly 4,000. --- .env.example | 2 +- CHANGELOG.md | 6 ++++- docs/CURRENT-STATE.md | 2 +- server/providers/cctv/constants.js | 7 +++-- src/data/cctvCalgary.test.mjs | 5 ---- src/data/cctvCatalogCap.test.mjs | 28 +++++++++++++++++--- src/data/cctvDriveBcSource.test.mjs | 40 +---------------------------- 7 files changed, 37 insertions(+), 53 deletions(-) diff --git a/.env.example b/.env.example index 45ae52099..5f153ca40 100644 --- a/.env.example +++ b/.env.example @@ -182,6 +182,6 @@ AISSTREAM_API_KEY= # CCTV_CALGARY_ROWS_URL= # override the Open Calgary catalog URL # CCTV_WSDOT_ENABLED=1 # set 0 to disable the WSDOT (Washington State) highway cameras # CCTV_WSDOT_MAX_SOURCES=250 # cap cameras loaded from the WSDOT catalog, nearest Seattle/Spokane first -# CCTV_MAX_SOURCES=4000 # catalog-wide ceiling, shared round-robin across packs +# CCTV_MAX_SOURCES=4500 # catalog-wide ceiling, shared round-robin across packs # CCTV_PREFER_AUSTIN=1 # prioritize Austin cameras in the registry # CCTV_FORCE_AUSTIN= # force live default sources when a source-pack JSON is configured diff --git a/CHANGELOG.md b/CHANGELOG.md index 912e64ff4..1b559c1e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,11 @@ diagonal, get a high-confidence heading; `BW` (Bothways) cameras use the shared id-hash fallback at low confidence. By default the 250 cameras nearest Seattle and Spokane load; `CCTV_WSDOT_MAX_SOURCES` sets the cap and - `CCTV_WSDOT_ENABLED=0` turns the pack off (bassem chagra, #644). + `CCTV_WSDOT_ENABLED=0` turns the pack off. The default catalog cap + (`CCTV_MAX_SOURCES`) rises from 4,000 to 4,500: the per-pack defaults already + summed to 4,054 before WSDOT, so a default install was being trimmed. A test + now sums every pack's default and fails if they exceed the cap + (bassem chagra, #644). - CCTV cameras whose bearing is a guess now say so. Packs mark bearings derived from a hash of the camera id as `headingConfidence: 'low'`, but nothing read the diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index edbd44565..892c85976 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -3422,7 +3422,7 @@ silently demoting every later lookup for the session. kill switch `CCTV_FINTRAFFIC_ENABLED=0`) are the fourth pack: one keyless GeoJSON station list covering the whole country, where one _preset_ (a station's fixed view) is one camera — 806 GATHERING stations carry 2,256 in-collection presets, prioritized to 300 against seven anchors - on the main road spine. `CCTV_MAX_SOURCES` is a 4,000 catalog-wide ceiling shared round-robin + on the main road spine. `CCTV_MAX_SOURCES` is a 4,500 catalog-wide ceiling (4,000 until the WSDOT pack) shared round-robin across packs, so a lower global cap thins every region instead of starving the last pack. ~1,100 cameras total, all RAW PRIOR poses, stills-first. - **CCTV v3 UX — viewshed + calibration gizmo** (built 2026-07-05 and field diff --git a/server/providers/cctv/constants.js b/server/providers/cctv/constants.js index 3ab8ce9cb..91e684be0 100644 --- a/server/providers/cctv/constants.js +++ b/server/providers/cctv/constants.js @@ -9,9 +9,12 @@ export const DEFAULT_AUSTIN_MAX_SOURCES = 250; * itself (nearest-to-anchor first); this bound only matters when the packs * together exceed it, and it is then filled round-robin across packs (see * cap.js) so no region is silently dropped. Sized above the sum of the - * default per-pack caps so a default install never trims. + * default per-pack caps so a default install never trims (enforced by + * cctvCatalogCap.test.mjs). The per-pack defaults reached 4,304 with WSDOT, + * so this is 4,500: room for one more small pack while staying under + * CCTV_MAX_SOURCES_CEILING, which also sizes the health map. */ -export const DEFAULT_CCTV_MAX_SOURCES = 4000; +export const DEFAULT_CCTV_MAX_SOURCES = 4500; /** Hard upper bound for CCTV_MAX_SOURCES; also sizes the health map. */ export const CCTV_MAX_SOURCES_CEILING = 5000; /** Reference point for Austin camera prioritization (Congress & 6th). */ diff --git a/src/data/cctvCalgary.test.mjs b/src/data/cctvCalgary.test.mjs index 1295c536b..4e7702059 100644 --- a/src/data/cctvCalgary.test.mjs +++ b/src/data/cctvCalgary.test.mjs @@ -12,7 +12,6 @@ import { CALGARY_IMAGE_ORIGIN, CALGARY_MAX_CATALOG_BYTES, DEFAULT_CALGARY_ROWS_URL, - DEFAULT_CCTV_MAX_SOURCES, } from '../../server/providers/cctv/constants.js'; import { CAMERA_CODE_MAX_CHARS } from '../../server/providers/cctv/normalize.js'; import { allocateSourceCap } from '../../server/providers/cctv/cap.js'; @@ -383,7 +382,3 @@ test('CCTV_CALGARY_ENABLED=0 keeps the lane from being loaded at all', async (t) Object.assign(process.env, saved); } }); - -test('the shipped catalog ceiling is not raised to make room for this pack', () => { - assert.equal(DEFAULT_CCTV_MAX_SOURCES, 4000); -}); diff --git a/src/data/cctvCatalogCap.test.mjs b/src/data/cctvCatalogCap.test.mjs index 6f1ff6c59..b0a603b4a 100644 --- a/src/data/cctvCatalogCap.test.mjs +++ b/src/data/cctvCatalogCap.test.mjs @@ -4,10 +4,9 @@ import { allocateSourceCap, resolveCatalogCap, } from '../../server/providers/cctv/cap.js'; -import { - CCTV_MAX_SOURCES_CEILING, - DEFAULT_CCTV_MAX_SOURCES, -} from '../../server/providers/cctv/constants.js'; +import * as constants from '../../server/providers/cctv/constants.js'; + +const { CCTV_MAX_SOURCES_CEILING, DEFAULT_CCTV_MAX_SOURCES } = constants; import { createCctvCatalog } from '../../server/providers/cctv/catalog.js'; const pack = (name, count, prefix = name) => ({ @@ -135,3 +134,24 @@ test('the catalog applies the fair cap to configured packs end to end', async () Object.assign(process.env, saved); } }); + +test('default per-pack camera caps fit inside the default catalog cap', () => { + // Every exported DEFAULT__MAX_SOURCES counts, so a new pack cannot + // slip past this check by not being listed (Warendorf ships 1 curated + // camera and has no cap constant). + const packCaps = Object.entries(constants).filter( + ([name]) => + /^DEFAULT_[A-Z_]+_MAX_SOURCES$/.test(name) && + name !== 'DEFAULT_CCTV_MAX_SOURCES', + ); + assert.ok(packCaps.length >= 13, 'every pack cap constant is found'); + const total = packCaps.reduce((sum, [, cap]) => sum + cap, 0) + 1; + assert.ok( + total <= DEFAULT_CCTV_MAX_SOURCES, + `default packs (${total}) would be thinned by the catalog cap (${DEFAULT_CCTV_MAX_SOURCES})`, + ); + assert.ok( + DEFAULT_CCTV_MAX_SOURCES <= CCTV_MAX_SOURCES_CEILING, + 'the default cap must sit inside the CCTV_MAX_SOURCES ceiling', + ); +}); diff --git a/src/data/cctvDriveBcSource.test.mjs b/src/data/cctvDriveBcSource.test.mjs index 617bb1350..080b1edf0 100644 --- a/src/data/cctvDriveBcSource.test.mjs +++ b/src/data/cctvDriveBcSource.test.mjs @@ -4,21 +4,7 @@ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { createCctvCatalog } from '../../server/providers/cctv/catalog.js'; -import { - DEFAULT_AUSTIN_MAX_SOURCES, - DEFAULT_CALTRANS_MAX_SOURCES, - DEFAULT_CCTV_MAX_SOURCES, - CCTV_MAX_SOURCES_CEILING, - DEFAULT_DRIVEBC_MAX_SOURCES, - DEFAULT_TFL_MAX_SOURCES, - DEFAULT_ONTARIO_MAX_SOURCES, - DEFAULT_FINTRAFFIC_MAX_SOURCES, - DEFAULT_TXDOT_MAX_SOURCES, - DEFAULT_TALLINN_MAX_SOURCES, - DEFAULT_TARKTEE_MAX_SOURCES, - DEFAULT_NSW_MAX_SOURCES, - DRIVEBC_WEBCAMS_URL, -} from '../../server/providers/cctv/constants.js'; +import { DRIVEBC_WEBCAMS_URL } from '../../server/providers/cctv/constants.js'; import { driveBcImageCredit, loadDriveBcSourcesFromOpenData, @@ -221,30 +207,6 @@ test('CCTV catalog merges DriveBC cameras and CCTV_DRIVEBC_ENABLED=0 skips the r ); }); -test('default per-pack camera caps fit inside the default catalog cap', () => { - // Every default pack at its own cap (Warendorf ships 1 curated camera). - const packs = - DEFAULT_AUSTIN_MAX_SOURCES + - DEFAULT_CALTRANS_MAX_SOURCES + - DEFAULT_TFL_MAX_SOURCES + - DEFAULT_ONTARIO_MAX_SOURCES + - DEFAULT_FINTRAFFIC_MAX_SOURCES + - DEFAULT_DRIVEBC_MAX_SOURCES + - DEFAULT_TXDOT_MAX_SOURCES + - DEFAULT_TALLINN_MAX_SOURCES + - DEFAULT_TARKTEE_MAX_SOURCES + - DEFAULT_NSW_MAX_SOURCES + - 1; - assert.ok( - packs <= DEFAULT_CCTV_MAX_SOURCES, - `default packs (${packs}) would be thinned by the catalog cap (${DEFAULT_CCTV_MAX_SOURCES})`, - ); - assert.ok( - DEFAULT_CCTV_MAX_SOURCES <= CCTV_MAX_SOURCES_CEILING, - 'the default cap must sit inside the CCTV_MAX_SOURCES ceiling', - ); -}); - test('DriveBC credit keeps partner attribution and drops operational notes', () => { assert.equal( driveBcImageCredit(