fix(bikeshare): preserve unknown availability counts

This commit is contained in:
Rudy Celekli committed 2026-10-15 16:56:11 -04:00
1 parent c6f92da962
commit 9bee985abb
4 files changed
+58

No files matched your search

+2
View File
@@ -74,6 +74,8 @@
- Tool string length validation counts Unicode characters correctly, including characters outside the basic multilingual plane. - Tool string length validation counts Unicode characters correctly, including characters outside the basic multilingual plane.
- Director timeline seeks cross the dateline along the short longitude arc. - Director timeline seeks cross the dateline along the short longitude arc.
- Keep unknown GBFS availability counts neutral instead of turning null or empty
values into zero bikes.
- Recent Imagery continues past malformed records in full catalog pages, respects the requested record limit, and reports results as truncated when that limit drops valid records. - Recent Imagery continues past malformed records in full catalog pages, respects the requested record limit, and reports results as truncated when that limit drops valid records.
+3
View File
@@ -1275,6 +1275,9 @@ and stale/error responses remain unchanged. Each has a Node-only package entry
under `gods-eye-view/server/providers/`. Portable terrain mechanics, traffic tile under `gods-eye-view/server/providers/`. Portable terrain mechanics, traffic tile
math and GBFS source rules are available under `gods-eye-view/sources/`. math and GBFS source rules are available under `gods-eye-view/sources/`.
The browser layers and their rendering remain in their existing modules. The browser layers and their rendering remain in their existing modules.
GBFS null or empty availability counts retain an unknown value through station
parsing and rendering; they use the neutral station color rather than reporting
an empty dock. Explicit zero and caller-supplied numeric fallbacks remain valid.
## Terrain height cache bound ## Terrain height cache bound
+52
View File
@@ -394,3 +394,55 @@ for (const hook of ['configureServer', 'configurePreviewServer']) {
); );
}); });
} }
test('unknown GBFS counts stay unknown through the source, model and station color', async () => {
const { createRendering } = await import('./rendering.js');
const { COLOR_NEUTRAL, COLOR_RED } = await import('./policy.js');
for (const missing of [null, '']) {
const source = createBikeshareSource({
fetchImpl: async () =>
Response.json({
data: {
stations: [
{
station_id: '2537',
num_bikes_available: missing,
num_docks_available: 4,
is_installed: true,
is_renting: true,
is_returning: true,
},
],
},
}),
});
const model = createModel({});
const status = model
.parseStationStatus(await source.getStations(AUSTIN.stationStatusUrl))
.get('2537');
const rendering = createRendering({
state: {},
services: { render: {} },
parts: { model },
});
assert.equal(status.bikesAvailable, null);
assert.equal(
model.toNonNegativeInteger(status.bikesAvailable),
null,
'render-map normalization preserves unknown',
);
assert.equal(rendering.statusToColor(status, 12), COLOR_NEUTRAL);
assert.equal(rendering.resolveCapacity(missing, status), 4);
assert.equal(
rendering.statusToColor({ ...status, bikesAvailable: 0 }, 12),
COLOR_RED,
'reported zero is still empty',
);
assert.equal(
model.toNonNegativeInteger(missing, 7),
7,
'caller fallback is preserved',
);
assert.equal(model.toNonNegativeInteger('3'), 3);
}
});
+1
View File
@@ -25,6 +25,7 @@ export function normalizeGbfsBool(value, fallback = true) {
* @returns {number|null} * @returns {number|null}
*/ */
export function toNonNegativeInteger(value, fallback = null) { export function toNonNegativeInteger(value, fallback = null) {
if (value == null || value === '') return fallback;
const n = Number(value); const n = Number(value);
if (!Number.isFinite(n) || n < 0) return fallback; if (!Number.isFinite(n) || n < 0) return fallback;
return Math.round(n); return Math.round(n);