feat(data-plane): add provider contracts and ontology delivery
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
export const FLEET_POSITIONS_CURRENT = Object.freeze({
|
||||
id: "fleet.positions.current.v1",
|
||||
version: "1.0.0",
|
||||
delivery: "snapshot+patch",
|
||||
semanticTypes: Object.freeze(["map.moving_object", "geo.position"]),
|
||||
});
|
||||
|
||||
export function toFleetPositionsSnapshot(rows, { tenantId, connectionId, generatedAt = new Date(), staleAfterMs }) {
|
||||
return {
|
||||
schemaVersion: "nodedc.data-product.snapshot.v1",
|
||||
dataProduct: FLEET_POSITIONS_CURRENT,
|
||||
scope: { tenantId, connectionId },
|
||||
generatedAt: iso(generatedAt),
|
||||
entities: rows.map((row) => toFleetPositionEntity(row, { generatedAt, staleAfterMs })),
|
||||
};
|
||||
}
|
||||
|
||||
export function toFleetPositionPatch(row, { generatedAt = new Date(), staleAfterMs }) {
|
||||
return {
|
||||
schemaVersion: "nodedc.data-product.patch.v1",
|
||||
dataProductId: FLEET_POSITIONS_CURRENT.id,
|
||||
operation: "upsert",
|
||||
emittedAt: iso(generatedAt),
|
||||
entity: toFleetPositionEntity(row, { generatedAt, staleAfterMs }),
|
||||
};
|
||||
}
|
||||
|
||||
export function toFleetPositionEntity(row, { generatedAt, staleAfterMs }) {
|
||||
const observedAt = date(row.observedAt);
|
||||
const receivedAt = date(row.receivedAt);
|
||||
const latitude = finiteNumber(row.latitude);
|
||||
const longitude = finiteNumber(row.longitude);
|
||||
const hasPosition = latitude !== null && longitude !== null;
|
||||
const ageMs = observedAt ? Math.max(0, generatedAt.getTime() - observedAt.getTime()) : null;
|
||||
const status = !hasPosition ? "no-position" : ageMs !== null && ageMs > staleAfterMs ? "stale" : "active";
|
||||
|
||||
return {
|
||||
subjectId: String(row.subjectId),
|
||||
semanticType: "map.moving_object",
|
||||
label: String(row.name || row.subjectId),
|
||||
status,
|
||||
observedAt: observedAt ? observedAt.toISOString() : null,
|
||||
receivedAt: receivedAt ? receivedAt.toISOString() : null,
|
||||
position: hasPosition ? { latitude, longitude } : null,
|
||||
motion: {
|
||||
speed: finiteNumber(row.speed),
|
||||
course: finiteNumber(row.course),
|
||||
},
|
||||
quality: {
|
||||
gpsValid: booleanOrNull(row.gpsValid),
|
||||
satellites: finiteNumber(row.satellites),
|
||||
hdop: finiteNumber(row.hdop),
|
||||
accuracyM: finiteNumber(row.accuracyM),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function iso(value) {
|
||||
const parsed = date(value);
|
||||
if (!parsed) throw new Error("data_product_timestamp_invalid");
|
||||
return parsed.toISOString();
|
||||
}
|
||||
|
||||
function date(value) {
|
||||
if (value instanceof Date && !Number.isNaN(value.getTime())) return value;
|
||||
const parsed = new Date(value);
|
||||
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
||||
}
|
||||
|
||||
function finiteNumber(value) {
|
||||
if (value === null || value === undefined || value === "") return null;
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
function booleanOrNull(value) {
|
||||
if (value === true || value === 1 || value === "1" || value === "true") return true;
|
||||
if (value === false || value === 0 || value === "0" || value === "false") return false;
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user