NODEDC_PLATFORM/services/gelios-gateway/src/normalize.mjs

93 lines
3.4 KiB
JavaScript

import { createHash } from "node:crypto";
export function normalizeUnit(unit, { tenantId, connectionId, receivedAt = new Date() }) {
const sourceUnitId = positiveInteger(first(unit?.id, unit?.unit_id, unit?.unitId));
if (!sourceUnitId) return null;
const lastMessage = firstObject(unit?.lmsg, unit?.last_msg, unit?.lastMsg, unit?.lastMessage) || {};
const latitude = finiteNumber(first(lastMessage.lat, lastMessage.latitude, unit?.lat, unit?.latitude));
const longitude = finiteNumber(first(lastMessage.lon, lastMessage.lng, lastMessage.longitude, unit?.lon, unit?.lng, unit?.longitude));
const observedAt = timestamp(first(lastMessage.time, lastMessage.ts, lastMessage.timestamp, unit?.last_msg_time, unit?.lmsg_time), receivedAt);
const telemetry = {
sourceUnitId,
name: optionalString(first(unit?.name, unit?.unit_name)),
observedAt: observedAt.toISOString(),
latitude,
longitude,
speed: finiteNumber(first(lastMessage.speed, unit?.speed)),
course: finiteNumber(first(lastMessage.course, lastMessage.angle, unit?.course)),
gpsValid: booleanOrNull(first(lastMessage.gps_valid, lastMessage.gpsValid, unit?.gps_valid)),
satellites: finiteNumber(first(lastMessage.sats, lastMessage.satellites, unit?.sats)),
hdop: finiteNumber(first(lastMessage.hdop, unit?.hdop)),
accuracyM: finiteNumber(first(lastMessage.accuracy, lastMessage.accuracy_m, unit?.accuracy_m)),
};
return {
tenantId,
connectionId,
sourceUnitId,
stableSubjectId: `gelios.unit:${connectionId}:${sourceUnitId}`,
name: telemetry.name || `Gelios unit ${sourceUnitId}`,
observedAt,
receivedAt,
latitude,
longitude,
speed: telemetry.speed,
course: telemetry.course,
gpsValid: telemetry.gpsValid,
satellites: telemetry.satellites,
hdop: telemetry.hdop,
accuracyM: telemetry.accuracyM,
telemetry,
fingerprint: hash(telemetry),
};
}
export function extractUnitItems(payload) {
if (Array.isArray(payload?.items)) return payload.items;
if (Array.isArray(payload?.units)) return payload.units;
if (Array.isArray(payload?.data)) return payload.data;
return [];
}
export function hash(value) {
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
}
function first(...values) {
return values.find((value) => value !== undefined && value !== null && value !== "");
}
function firstObject(...values) {
return values.find((value) => value && typeof value === "object" && !Array.isArray(value));
}
function positiveInteger(value) {
const parsed = Number.parseInt(value, 10);
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
}
function finiteNumber(value) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}
function optionalString(value) {
const normalized = String(value ?? "").trim();
return normalized || 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;
}
function timestamp(value, fallback) {
const numeric = Number(value);
if (!Number.isFinite(numeric) || numeric <= 0) return fallback;
const milliseconds = numeric < 1_000_000_000_000 ? numeric * 1000 : numeric;
const parsed = new Date(milliseconds);
return Number.isNaN(parsed.getTime()) ? fallback : parsed;
}