mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
Accept authoritative empty vessel coverage without an outage
This commit is contained in:
1 parent
89e566aa9b
commit
27a986f40f
5 files changed
+67
-4
No files matched your search
@@ -1,5 +1,8 @@
|
||||
# Changelog
|
||||
|
||||
- Treat explicitly current, complete empty vessel coverage as a successful update,
|
||||
clearing obsolete contacts while retaining stale or incomplete snapshots.
|
||||
|
||||
- Custom Street Level provider switches survive share links and stored state
|
||||
using URL-safe stable provider IDs and a shared boolean-switch codec; existing
|
||||
Mapillary links keep their meaning. Explicit provider toggles reach durable
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# God's Eye View Current State
|
||||
|
||||
Vessel sources can report healthy empty coverage using a current, complete
|
||||
snapshot with zero raw rows, a healthy transport and a positive last-message
|
||||
time. This clears obsolete contacts and settles startup without manufacturing
|
||||
position timestamps. Stale, incomplete and malformed empty results retain the
|
||||
existing recovery behavior.
|
||||
|
||||
Catalog source contracts and their consuming layers are declared in
|
||||
`src/app/sourceComposition.js`. Omitted or null acquisition sources preserve layer
|
||||
registration and serialization, report unavailable, and are refused by the existing
|
||||
|
||||
@@ -109,7 +109,7 @@ export function createIngestion({
|
||||
feed.acceptedRowCount = snapshot.acceptedRowCount;
|
||||
feed.partial = payload?.complete === false;
|
||||
|
||||
if (snapshot.acceptedRowCount === 0) {
|
||||
if (snapshot.acceptedRowCount === 0 && !snapshot.emptyCoverage) {
|
||||
feed.count = readCount();
|
||||
feed.stale = feed.count > 0 || Boolean(payload?.refreshing);
|
||||
if (isDefinitiveTransportFailure(snapshot.transportStatus)) {
|
||||
|
||||
@@ -361,3 +361,47 @@ test('partial vessel snapshots preserve freshness and recover after a complete u
|
||||
layer.destroy();
|
||||
assert.equal(layer.getStats().partial, false);
|
||||
});
|
||||
|
||||
test('authoritative empty coverage clears old contacts without declaring an outage', async () => {
|
||||
let current = snapshot([observation('111')]);
|
||||
const { layer } = setup({
|
||||
async getSnapshot() {
|
||||
return current;
|
||||
},
|
||||
});
|
||||
await layer.update();
|
||||
current = snapshot([], { observedAtMs: null, lastMessageAt: 500 });
|
||||
await layer.update();
|
||||
assert.ok(!layer.hasContact('111'));
|
||||
assert.equal(layer.getStats().count, 0);
|
||||
assert.equal(layer.getStats().error, null);
|
||||
assert.equal(layer.getStats().stale, false);
|
||||
assert.equal(layer.getStats().lastUpdate, null);
|
||||
});
|
||||
|
||||
test('empty coverage cannot hide stale, incomplete or rejected source data', async () => {
|
||||
for (const patch of [
|
||||
{ freshness: 'unknown' },
|
||||
{ freshness: 'stale' },
|
||||
{ complete: false },
|
||||
{ rawRowCount: 1 },
|
||||
{ transportStatus: 'stale' },
|
||||
{ lastMessageAt: null },
|
||||
]) {
|
||||
let current = snapshot([observation('111')]);
|
||||
const { layer } = setup({
|
||||
async getSnapshot() {
|
||||
return current;
|
||||
},
|
||||
});
|
||||
await layer.update();
|
||||
current = snapshot([], {
|
||||
observedAtMs: null,
|
||||
lastMessageAt: 500,
|
||||
...patch,
|
||||
});
|
||||
await layer.update();
|
||||
assert.equal(layer.hasContact('111'), true, JSON.stringify(patch));
|
||||
assert.ok(layer.getStats().error, JSON.stringify(patch));
|
||||
}
|
||||
});
|
||||
@@ -119,7 +119,16 @@ export function createQueries({
|
||||
typeof payload?.status === 'string' ? payload.status : null;
|
||||
const lastMessageAt = payload?.lastMessageAt ?? null;
|
||||
const acceptedRowCount = acceptedRows.length;
|
||||
const emptyCoverage =
|
||||
rawRows.length === 0 &&
|
||||
payload?.rawRowCount === 0 &&
|
||||
payload?.complete === true &&
|
||||
payload?.freshness === 'current' &&
|
||||
AIS_HEALTHY_STATUSES.has(transportStatus) &&
|
||||
Number.isFinite(Number(lastMessageAt)) &&
|
||||
Number(lastMessageAt) > 0;
|
||||
return {
|
||||
emptyCoverage,
|
||||
transportStatus,
|
||||
lastMessageAt,
|
||||
rawRows,
|
||||
@@ -130,9 +139,10 @@ export function createQueries({
|
||||
? payload.rawRowCount
|
||||
: rawRows.length,
|
||||
acceptedRowCount,
|
||||
error:
|
||||
deriveAisFeedError(payload, acceptedRowCount) ||
|
||||
(acceptedRowCount === 0 ? 'awaiting usable AIS positions…' : null),
|
||||
error: emptyCoverage
|
||||
? null
|
||||
: deriveAisFeedError(payload, acceptedRowCount) ||
|
||||
(acceptedRowCount === 0 ? 'awaiting usable AIS positions…' : null),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user