feat(dynamics): Dataverse FS layer + WW-DEMO sample GeoJSON

Sample-first Cesium overlay via createLocalGeoJsonLayer; normalize unit tests; docs pointer. No secrets.
This commit is contained in:
dr eggbot 2026-09-09 16:16:07 -04:00
parent 4d13854d0d
commit 4ab771ac65
5 changed files with 231 additions and 1 deletions

View File

@ -30,4 +30,10 @@ This is not a closed ISV black box. It is a community-shaped bridge from OSINT-g
Early public versioning. Demo data only until security pass. No customer PHI. No live tech GPS in public builds until consent model is locked.
Repo: https://github.com/NukaSoft/gods-eye-view
Repo: https://github.com/NukaSoft/gods-eye-view
## Layer module (eggbot 2026-09-09)
- src/data/dataverseFs.js — Cesium overlay via local GeoJSON loader
- Sample: public/samples/dataverse-fs-ww-demo.geojson`r
- Env: VITE_DATAVERSE_FS_GEOJSON_URL (adapter URL); default = sample
- Toggle appears with other local data layers (Dynamics Field Service)

View File

@ -0,0 +1,96 @@
{
"type": "FeatureCollection",
"name": "dataverse-fs-ww-demo",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"properties": {
"source": "sample",
"org": "nukasoft.crm.dynamics.com",
"theme": "WeatherWyze",
"disclaimer": "Synthetic demo coordinates for GEV layer spike. Not live GPS. No real customer PHI. Scrub before any public PR."
},
"features": [
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [-85.6681, 42.9634] },
"properties": {
"kind": "customer",
"entity": "account",
"id": "00000000-0000-0000-0000-000000000101",
"name": "WW Demo Municipal Utilities",
"status": "active"
}
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [-85.6702, 42.9610] },
"properties": {
"kind": "site",
"entity": "msdyn_functionallocation",
"id": "00000000-0000-0000-0000-000000000201",
"name": "WW Site — River Pump Station",
"status": "active"
}
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [-85.6655, 42.9662] },
"properties": {
"kind": "site",
"entity": "msdyn_functionallocation",
"id": "00000000-0000-0000-0000-000000000202",
"name": "WW Site — North Substation",
"status": "active"
}
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [-85.6702, 42.9611] },
"properties": {
"kind": "asset",
"entity": "msdyn_customerasset",
"id": "00000000-0000-0000-0000-000000000301",
"name": "Pump P-12",
"status": "active",
"locationSource": "site"
}
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [-85.6700, 42.9608] },
"properties": {
"kind": "workorder",
"entity": "msdyn_workorder",
"id": "00000000-0000-0000-0000-000000000401",
"name": "WW-DEMO-001",
"status": "open",
"priority": "high"
}
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [-85.6658, 42.9660] },
"properties": {
"kind": "workorder",
"entity": "msdyn_workorder",
"id": "00000000-0000-0000-0000-000000000402",
"name": "WW-DEMO-002",
"status": "open",
"priority": "normal"
}
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [-85.6670, 42.9645] },
"properties": {
"kind": "resource",
"entity": "bookableresource",
"id": "00000000-0000-0000-0000-000000000501",
"name": "WW Tech A (demo)",
"status": "available",
"locationSource": "start",
"updated": "2026-09-09T18:00:00Z",
"staleSec": 3600
}
}
]
}

93
src/data/dataverseFs.js Normal file
View File

@ -0,0 +1,93 @@
/**
* @module dataverseFs
* Dynamics 365 Field Service / Dataverse dispatch layer for God's Eye View.
* Default path: GeoJSON sample or VITE_DATAVERSE_FS_GEOJSON_URL (adapter).
* Browser does not call Dataverse OData by default secrets stay in the adapter.
*/
import { createLocalGeoJsonLayer } from './localGeojson.js';
export const DATAVERSE_FS_OVERLAY_SOURCE_ID = 'dataverse-fs';
const ALLOWED_KINDS = new Set(['customer', 'site', 'asset', 'workorder', 'resource', 'contact']);
/**
* Normalize a GeoJSON FeatureCollection for the FS layer (unit-testable, no Cesium).
* Drops features without finite lon/lat; keeps kind/id/name when present.
* @param {object} collection
* @returns {{type:string, features:object[]}}
*/
export function normalizeDataverseFsCollection(collection) {
if (!collection || collection.type !== 'FeatureCollection' || !Array.isArray(collection.features)) {
return { type: 'FeatureCollection', features: [] };
}
const features = [];
for (const f of collection.features) {
const coords = f?.geometry?.coordinates;
if (!Array.isArray(coords) || coords.length < 2) continue;
if (coords[0] == null || coords[1] == null) continue;
const lon = Number(coords[0]);
const lat = Number(coords[1]);
if (!Number.isFinite(lon) || !Number.isFinite(lat)) continue;
const props = f.properties && typeof f.properties === 'object' ? { ...f.properties } : {};
if (props.kind && !ALLOWED_KINDS.has(String(props.kind))) {
// keep unknown kinds out of semantic success counts, but still allow render if coords valid
// for v1 we skip unknown kinds to avoid false greens
continue;
}
features.push({
type: 'Feature',
geometry: { type: 'Point', coordinates: [lon, lat] },
properties: {
kind: props.kind ? String(props.kind) : 'site',
entity: props.entity ? String(props.entity) : 'unknown',
id: props.id != null ? String(props.id) : undefined,
name: props.name != null ? String(props.name) : 'Untitled',
status: props.status,
priority: props.priority,
href: props.href,
updated: props.updated,
staleSec: props.staleSec,
},
});
}
return { type: 'FeatureCollection', features };
}
function resolveGeoJsonUrl() {
const fromEnv = typeof import.meta !== 'undefined' && import.meta.env
? import.meta.env.VITE_DATAVERSE_FS_GEOJSON_URL
: undefined;
if (fromEnv && String(fromEnv).trim()) return String(fromEnv).trim();
return '/samples/dataverse-fs-ww-demo.geojson';
}
const dataverseFsLayer = createLocalGeoJsonLayer({
id: DATAVERSE_FS_OVERLAY_SOURCE_ID,
url: resolveGeoJsonUrl(),
name: 'Dynamics Field Service',
color: '#742774',
icon: 'DV',
source: 'Dataverse FS',
labels: true,
labelMax: 800,
labelGridPx: 140,
});
export function getDataverseFsLayer() {
return dataverseFsLayer;
}
export async function setDataverseFsEnabled(viewer, enabled) {
if (!viewer) return;
if (enabled) await dataverseFsLayer.enable(viewer);
else await dataverseFsLayer.disable(viewer);
}
export async function refreshDataverseFs(viewer) {
if (!viewer) return;
await dataverseFsLayer.disable(viewer);
await dataverseFsLayer.enable(viewer);
}
export default dataverseFsLayer;

View File

@ -0,0 +1,33 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { normalizeDataverseFsCollection } from './dataverseFs.js';
test('normalize drops null coords and empty input', () => {
assert.deepEqual(normalizeDataverseFsCollection(null).features, []);
assert.deepEqual(normalizeDataverseFsCollection({ type: 'FeatureCollection', features: [] }).features, []);
const bad = {
type: 'FeatureCollection',
features: [
{ type: 'Feature', geometry: { type: 'Point', coordinates: [null, 1] }, properties: { kind: 'site', id: '1', name: 'x' } },
{ type: 'Feature', geometry: { type: 'Point', coordinates: [-85.67, 42.96] }, properties: { kind: 'site', id: '2', name: 'ok' } },
],
};
const out = normalizeDataverseFsCollection(bad);
assert.equal(out.features.length, 1);
assert.equal(out.features[0].properties.id, '2');
assert.equal(out.features[0].properties.name, 'ok');
assert.equal(out.features[0].properties.kind, 'site');
});
test('normalize skips unknown kinds', () => {
const fc = {
type: 'FeatureCollection',
features: [
{ type: 'Feature', geometry: { type: 'Point', coordinates: [1, 2] }, properties: { kind: 'spaceship', id: '9', name: 'nope' } },
{ type: 'Feature', geometry: { type: 'Point', coordinates: [3, 4] }, properties: { kind: 'workorder', id: '8', name: 'WO' } },
],
};
const out = normalizeDataverseFsCollection(fc);
assert.equal(out.features.length, 1);
assert.equal(out.features[0].properties.kind, 'workorder');
});

View File

@ -1,4 +1,5 @@
import { createLocalGeoJsonLayer } from './localGeojson.js';
import dataverseFsLayer from './dataverseFs.js';
import { createFirmsHeatmapLayer } from './firmsHeatmap.js';
import submarineCablesLayer from './telegeographySubmarineCables.js';
@ -45,6 +46,7 @@ const fires = createFirmsHeatmapLayer({
});
export default [
dataverseFsLayer,
datacenters,
dams,
submarineCablesLayer,