mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix(voice): preserve analyst result and tracking contracts
keep contacts counts, rows, scope and provenance on one retained snapshot report capped cohorts as lower bounds separate sibling tool cancellation from newer user turns align track_entity validation across runtime and benchmark paths
This commit is contained in:
1 parent
a19897ad15
commit
095981eee7
27 files changed
+1155
-146
No files matched your search
Vendored
+54
@@ -75,6 +75,60 @@ export function createControls({ state: layerState, services, parts, source }) {
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the exact retained aircraft cohorts behind the current Contacts
|
||||
* panel snapshot. Voice reads this immutable copy instead of re-scanning
|
||||
* live layers, so panel and spoken counts share one evaluation timestamp.
|
||||
*/
|
||||
getAircraftQuerySnapshot() {
|
||||
if (!layerState.enabled || !layerState.subject || !layerState.results)
|
||||
return null;
|
||||
// Build every public count from this exact results object before
|
||||
// returning. A refresh may replace `layerState.results` as soon as the
|
||||
// caller awaits, so consumers must not make a second read later and
|
||||
// accidentally combine two evaluation timestamps.
|
||||
const panelSnapshot = parts.model.buildAwarenessContextSnapshot(
|
||||
layerState.results,
|
||||
parts.model.navigationState(),
|
||||
{
|
||||
subjectPresent: !layerState.subjectMissing,
|
||||
},
|
||||
);
|
||||
const cohorts = {};
|
||||
for (const cohort of layerState.results.cohorts || []) {
|
||||
if (!['flights', 'military'].includes(cohort?.id)) continue;
|
||||
cohorts[cohort.id] = {
|
||||
count: cohort.summary?.count ?? null,
|
||||
complete:
|
||||
Number.isFinite(cohort.summary?.count) &&
|
||||
cohort.summary?.complete !== false,
|
||||
truncated:
|
||||
!Number.isFinite(cohort.summary?.count) ||
|
||||
cohort.summary?.truncated === true,
|
||||
items: (cohort.summary?.navigationNearest || []).map((item) => ({
|
||||
...item,
|
||||
})),
|
||||
source: cohort.source || null,
|
||||
provenance: cohort.provenance
|
||||
? {
|
||||
...cohort.provenance,
|
||||
stats: cohort.provenance.stats
|
||||
? { ...cohort.provenance.stats }
|
||||
: undefined,
|
||||
}
|
||||
: null,
|
||||
reason: cohort.summary?.reason || null,
|
||||
};
|
||||
}
|
||||
return {
|
||||
subject: { ...layerState.results.subject },
|
||||
evaluatedAt: layerState.results.evaluatedAt,
|
||||
radiusM: layerState.results.radiusM,
|
||||
cohorts,
|
||||
contactsWindow: parts.model.contactsWindowFromSnapshot(panelSnapshot),
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Release Contact-owned camera tracking without discarding the selected
|
||||
* subject. Reset-to-globe uses this route so the normal Context FOCUS action
|
||||
|
||||
@@ -144,6 +144,9 @@ export function createModel({ state: layerState, services, parts, source }) {
|
||||
|
||||
function contactsWindowFromSnapshot(snapshot) {
|
||||
if (!snapshot?.subject) return null;
|
||||
const aircraftCohorts = (snapshot.cohorts || []).filter((cohort) =>
|
||||
['flights', 'military'].includes(cohort?.id),
|
||||
);
|
||||
const countFor = (cohortId) => {
|
||||
const cohort = Array.isArray(snapshot.cohorts)
|
||||
? snapshot.cohorts.find((item) => item?.id === cohortId)
|
||||
@@ -164,6 +167,12 @@ export function createModel({ state: layerState, services, parts, source }) {
|
||||
flights,
|
||||
military,
|
||||
vessels: countFor('ais-live-vessels'),
|
||||
complete:
|
||||
aircraftCohorts.length === 2 &&
|
||||
aircraftCohorts.every(
|
||||
(cohort) =>
|
||||
Number.isFinite(cohort.count) && cohort.complete !== false,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -190,6 +199,8 @@ export function createModel({ state: layerState, services, parts, source }) {
|
||||
coverage: cohort.coverage || null,
|
||||
relationship: cohort.summary.relationship,
|
||||
count: cohort.summary.count,
|
||||
complete: cohort.summary.complete !== false,
|
||||
truncated: cohort.summary.truncated === true,
|
||||
reason: cohort.summary.reason,
|
||||
nearest: cohort.summary.nearest.slice(),
|
||||
})),
|
||||
|
||||
@@ -25,6 +25,7 @@ export function createNavigation({
|
||||
{
|
||||
displayLimit = AWARENESS_MAX_EXAMPLES,
|
||||
navigationLimit = AWARENESS_MAX_NAVIGATION_EXAMPLES,
|
||||
complete = true,
|
||||
} = {},
|
||||
) {
|
||||
const summary = summarizeAwarenessCohort(items, {
|
||||
@@ -34,6 +35,8 @@ export function createNavigation({
|
||||
if (summary.count === null) return summary;
|
||||
return {
|
||||
...summary,
|
||||
complete: Boolean(complete),
|
||||
truncated: !complete,
|
||||
nearest: summary.nearest.slice(0, displayLimit),
|
||||
navigationNearest: summary.nearest,
|
||||
};
|
||||
|
||||
@@ -5,6 +5,15 @@ import {
|
||||
} from '../../data/militaryAwarenessEngine.js';
|
||||
import { AWARENESS_PAGE_SIZE, AWARENESS_PAGE_ROTATE_MS } from './policy.js';
|
||||
|
||||
/** Format a cohort total without presenting a retained cap as exact. */
|
||||
export function formatAwarenessCount(summary = {}) {
|
||||
if (!Number.isFinite(summary.count)) return '?';
|
||||
const count = summary.count.toLocaleString('en-US');
|
||||
return summary.complete === false || summary.truncated === true
|
||||
? `At least ${count}`
|
||||
: count;
|
||||
}
|
||||
|
||||
export function createPanel({ state: layerState, services, parts, source }) {
|
||||
function ensurePanel() {
|
||||
if (layerState.panel) return layerState.panel;
|
||||
@@ -61,7 +70,7 @@ export function createPanel({ state: layerState, services, parts, source }) {
|
||||
|
||||
function rowHtml(cohort) {
|
||||
const summary = cohort.summary;
|
||||
const count = summary.count === null ? '?' : String(summary.count);
|
||||
const count = formatAwarenessCount(summary);
|
||||
const page = layerState.cohortPages.get(cohort.id) || 0;
|
||||
const nearest = summary.nearest
|
||||
.slice(page, page + AWARENESS_PAGE_SIZE)
|
||||
|
||||
@@ -38,8 +38,8 @@ export const AWARENESS_MAX_EXAMPLES = 10;
|
||||
// so these caps sit far above any realistic in-range population rather than at
|
||||
// the display limit. They are still FINITE: an unbounded materialization would
|
||||
// let a pathological feed sort an eleven-thousand-contact array on every
|
||||
// refresh. Truthfulness does not depend on them — `summarizeAwarenessCohort`
|
||||
// derives `count` from the full in-range set before any slice.
|
||||
// refresh. Callers sample beyond this cap and carry explicit completeness, so
|
||||
// a saturated cohort is a lower bound rather than a silently exact count.
|
||||
|
||||
export const AWARENESS_QUERY_LIMIT = 20000;
|
||||
|
||||
|
||||
@@ -53,7 +53,12 @@ export function createQueries({ state: layerState, services, parts, source }) {
|
||||
neverAnswered ||
|
||||
['unavailable', 'zoom-in'].includes(stats.status) ||
|
||||
Boolean(stats.error && stats.count === 0);
|
||||
return { available: !unavailable, stale: Boolean(stats.stale), stats };
|
||||
return {
|
||||
available: !unavailable,
|
||||
enabled,
|
||||
stale: Boolean(stats.stale),
|
||||
stats,
|
||||
};
|
||||
}
|
||||
|
||||
function collectSourceStates() {
|
||||
@@ -147,8 +152,10 @@ export function createQueries({ state: layerState, services, parts, source }) {
|
||||
* @param {object} [options]
|
||||
* @param {number} [options.radiusM=AWARENESS_RADIUS_M] Window radius.
|
||||
* @param {object|null} [options.subject=null] Contact at the centre, excluded.
|
||||
* @returns {{flights: Array, military: Array, aircraft: number}|null} Cohorts
|
||||
* plus the combined aircraft count, or null without a position.
|
||||
* @returns {{flights: Array, military: Array, aircraft: number,
|
||||
* completeByLayer: {flights: boolean, military: boolean}}|null} Cohorts,
|
||||
* their lower-bound completeness, and the combined aircraft count, or
|
||||
* null without a position.
|
||||
*/
|
||||
|
||||
function collectAircraftProximityWindow(
|
||||
@@ -156,21 +163,35 @@ export function createQueries({ state: layerState, services, parts, source }) {
|
||||
{ radiusM = AWARENESS_RADIUS_M, subject = null } = {},
|
||||
) {
|
||||
if (!position) return null;
|
||||
const flights = flightsLayer
|
||||
.getNearby(position, radiusM, AWARENESS_QUERY_LIMIT, {
|
||||
// Ask for one usable row beyond the retained limit so a capped cohort is
|
||||
// reported as a lower bound instead of an exact count. The selected
|
||||
// subject can consume one slot before exclusion, hence the extra two.
|
||||
const sampleLimit = AWARENESS_QUERY_LIMIT + 2;
|
||||
const flightSample = flightsLayer
|
||||
.getNearby(position, radiusM, sampleLimit, {
|
||||
includeHidden: true,
|
||||
})
|
||||
.filter(
|
||||
(item) => !subject || !isSame(subject, item, 'flights', 'icao24'),
|
||||
);
|
||||
const military = militaryFlightsLayer
|
||||
.getNearby(position, radiusM, AWARENESS_QUERY_LIMIT, {
|
||||
const militarySample = militaryFlightsLayer
|
||||
.getNearby(position, radiusM, sampleLimit, {
|
||||
includeHidden: true,
|
||||
})
|
||||
.filter(
|
||||
(item) => !subject || !isSame(subject, item, 'military', 'icao24'),
|
||||
);
|
||||
return { flights, military, aircraft: flights.length + military.length };
|
||||
const flights = flightSample.slice(0, AWARENESS_QUERY_LIMIT);
|
||||
const military = militarySample.slice(0, AWARENESS_QUERY_LIMIT);
|
||||
return {
|
||||
flights,
|
||||
military,
|
||||
aircraft: flights.length + military.length,
|
||||
completeByLayer: {
|
||||
flights: flightSample.length <= AWARENESS_QUERY_LIMIT,
|
||||
military: militarySample.length <= AWARENESS_QUERY_LIMIT,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
sourceState,
|
||||
|
||||
@@ -6,6 +6,39 @@ import { createSubject } from './subject.js';
|
||||
import { createControls } from '../installations/controls.js';
|
||||
import { createModel } from '../installations/model.js';
|
||||
import { summarizeAwarenessCohort } from '../../data/militaryAwarenessEngine.js';
|
||||
import { AWARENESS_QUERY_LIMIT } from './policy.js';
|
||||
|
||||
test('aircraft window marks a capped layer as a lower bound', () => {
|
||||
const rows = Array.from({ length: AWARENESS_QUERY_LIMIT + 1 }, (_, index) => ({
|
||||
icao24: `f${index}`,
|
||||
distance: index,
|
||||
}));
|
||||
let requested = null;
|
||||
const services = {
|
||||
flights: {
|
||||
getNearby: (_position, _radius, limit) => {
|
||||
requested = limit;
|
||||
return rows.slice(0, limit);
|
||||
},
|
||||
},
|
||||
military: { getNearby: () => [] },
|
||||
};
|
||||
const parts = {
|
||||
navigation: {
|
||||
summarizeAwarenessCohortForNavigation: (items, source) =>
|
||||
summarizeAwarenessCohort(items, source),
|
||||
},
|
||||
};
|
||||
const queries = createQueries({ state: {}, services, parts });
|
||||
const window = queries.collectAircraftProximityWindow({ center: true });
|
||||
assert.equal(requested, AWARENESS_QUERY_LIMIT + 2);
|
||||
assert.equal(window.flights.length, AWARENESS_QUERY_LIMIT);
|
||||
assert.equal(window.aircraft, AWARENESS_QUERY_LIMIT);
|
||||
assert.deepEqual(window.completeByLayer, {
|
||||
flights: false,
|
||||
military: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('Contacts excludes square corners and measures from the current moving subject', () => {
|
||||
const records = [
|
||||
|
||||
@@ -37,11 +37,23 @@ export function createSubject({ state: layerState, services, parts, source }) {
|
||||
const militaryState = sourceStates.military;
|
||||
const vesselsState = sourceStates['ais-live-vessels'];
|
||||
const installationsState = sourceStates['military-installations'];
|
||||
const evaluatedAt = Date.now();
|
||||
const retainedProvenance = (id, label, sourceState, fallbackSource) => {
|
||||
const retainedSource = sourceState.stats.source || fallbackSource;
|
||||
return {
|
||||
id,
|
||||
name: label,
|
||||
enabled: sourceState.enabled,
|
||||
source: retainedSource,
|
||||
stats: { ...sourceState.stats, source: retainedSource },
|
||||
};
|
||||
};
|
||||
// Same engine the voice analyst calls — see collectAircraftProximityWindow.
|
||||
const { flights, military } = parts.queries.collectAircraftProximityWindow(
|
||||
const aircraftWindow = parts.queries.collectAircraftProximityWindow(
|
||||
position,
|
||||
{ subject },
|
||||
);
|
||||
const { flights, military, completeByLayer } = aircraftWindow;
|
||||
const vessels = aisLiveVesselsLayer
|
||||
.getNearby(position, AWARENESS_RADIUS_M, AWARENESS_QUERY_LIMIT)
|
||||
.filter(
|
||||
@@ -61,31 +73,57 @@ export function createSubject({ state: layerState, services, parts, source }) {
|
||||
);
|
||||
return {
|
||||
subject,
|
||||
evaluatedAt: Date.now(),
|
||||
evaluatedAt,
|
||||
radiusM: AWARENESS_RADIUS_M,
|
||||
cohorts: [
|
||||
{
|
||||
id: 'flights',
|
||||
label: 'Flights',
|
||||
source: flightsState.stats.source || SOURCE_LABEL.flights,
|
||||
provenance: retainedProvenance(
|
||||
'flights',
|
||||
'Flights',
|
||||
flightsState,
|
||||
SOURCE_LABEL.flights,
|
||||
),
|
||||
summary: parts.navigation.summarizeAwarenessCohortForNavigation(
|
||||
flights,
|
||||
flightsState,
|
||||
{
|
||||
navigationLimit: AWARENESS_QUERY_LIMIT,
|
||||
complete: completeByLayer.flights,
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'military',
|
||||
label: 'Military flights',
|
||||
source: militaryState.stats.source || SOURCE_LABEL.military,
|
||||
provenance: retainedProvenance(
|
||||
'military',
|
||||
'Military flights',
|
||||
militaryState,
|
||||
SOURCE_LABEL.military,
|
||||
),
|
||||
summary: parts.navigation.summarizeAwarenessCohortForNavigation(
|
||||
military,
|
||||
militaryState,
|
||||
{
|
||||
navigationLimit: AWARENESS_QUERY_LIMIT,
|
||||
complete: completeByLayer.military,
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'ais-live-vessels',
|
||||
label: 'AIS vessels',
|
||||
source: vesselsState.stats.source || SOURCE_LABEL['ais-live-vessels'],
|
||||
provenance: retainedProvenance(
|
||||
'ais-live-vessels',
|
||||
'AIS vessels',
|
||||
vesselsState,
|
||||
SOURCE_LABEL['ais-live-vessels'],
|
||||
),
|
||||
summary: parts.navigation.summarizeAwarenessCohortForNavigation(
|
||||
vessels,
|
||||
vesselsState,
|
||||
@@ -97,6 +135,12 @@ export function createSubject({ state: layerState, services, parts, source }) {
|
||||
source:
|
||||
installationsState.stats.source ||
|
||||
SOURCE_LABEL['military-installations'],
|
||||
provenance: retainedProvenance(
|
||||
'military-installations',
|
||||
'Mapped installations',
|
||||
installationsState,
|
||||
SOURCE_LABEL['military-installations'],
|
||||
),
|
||||
coverage:
|
||||
installationsState.stats.coverageLabel || 'CURRENT VIEWPORT ONLY',
|
||||
summary: parts.queries.summarizeInstallationViewport(
|
||||
|
||||
Reference in new issue
Block a user