feat(gps): valid=false от протокола H02 → точка сохраняется как LBS-примерная (не выбрасывается), сигнал потери GPS

This commit is contained in:
2026-08-31 15:13:03 +03:00
parent bec3cc054d
commit cbb659aed7
3 changed files with 52 additions and 10 deletions

View File

@@ -167,9 +167,9 @@ services:
image: osrm/osrm-backend:latest image: osrm/osrm-backend:latest
container_name: osrm container_name: osrm
restart: unless-stopped restart: unless-stopped
command: osrm-routed --algorithm mld /data/volga.osrm command: osrm-routed --algorithm mld /data/region.osrm
volumes: volumes:
- ./osrm-data:/data:ro - ./osrm-data-ru:/data:ro
volumes: volumes:
crm_data: crm_data:

View File

@@ -51,6 +51,8 @@ export interface ProcessPositionInput {
course?: number | null; course?: number | null;
accuracy?: number | null; accuracy?: number | null;
recordedAt?: Date; recordedAt?: Date;
/** Протокольный признак «GPS невалиден» (Traccar valid=false) — LBS-точка */
forceLbs?: boolean;
} }
// Вердикт фильтрации точки относительно предыдущей принятой // Вердикт фильтрации точки относительно предыдущей принятой
@@ -500,6 +502,50 @@ export async function processPosition(
? { lat: asset.lastLat, lng: asset.lastLng, recordedAt: asset.lastRecordedAt, speed: asset.lastSpeed } ? { lat: asset.lastLat, lng: asset.lastLng, recordedAt: asset.lastRecordedAt, speed: asset.lastSpeed }
: null; : null;
// Протокольный флаг «GPS невалиден» (Traccar valid=false): это точка по вышкам.
// Сохраняем как примерную (is_lbs), маркер двигаем, но walker/геозоны/пробег
// не трогаем. Заодно это сигнал потери GPS — как stale re-send.
if (input.forceLbs) {
assetLastStaleAt.set(assetId, Date.now());
if (!asset.gpsLost) {
await gpsStorage.setGpsLost(assetId, true);
console.log(`[GPS] gps_lost ON: asset=${assetId} (valid=false от протокола)`);
}
await gpsStorage.insertPosition({
assetId,
organizationId,
lat: input.lat,
lng: input.lng,
speed: input.speed ?? null,
course: input.course ?? null,
accuracy: input.accuracy ?? null,
isLbs: true,
recordedAt,
});
await gpsStorage.updateAssetLastPosition(assetId, {
lat: input.lat,
lng: input.lng,
speed: input.speed,
course: input.course,
recordedAt,
isLbs: true,
});
eventBus.publishEvent({
type: "gps_asset_moved",
data: {
assetId,
lat: input.lat,
lng: input.lng,
speed: input.speed ?? null,
course: input.course ?? null,
recordedAt: recordedAt.toISOString(),
isLbs: true,
},
organizationId,
});
return;
}
// Фильтры stale/jump/echo с гистерезисом подтверждения (устаревшие копии, // Фильтры stale/jump/echo с гистерезисом подтверждения (устаревшие копии,
// LBS-скачки, heartbeat-эхо стоянки) // LBS-скачки, heartbeat-эхо стоянки)
const walker = getAssetWalker(assetId, prev, settings); const walker = getAssetWalker(assetId, prev, settings);

View File

@@ -261,16 +261,12 @@ async function handleIngest(req: AuthenticatedRequest, res: Response) {
continue; continue;
} }
// Traccar пометил позицию как невалидную (valid=false) — не сохраняем,
// но считаем признаком жизни устройства
if (!point.valid) {
await gpsStorage.touchAssetSeen(asset.id);
processed++;
continue;
}
try { try {
await processPosition(asset.id, asset.organizationId, { await processPosition(asset.id, asset.organizationId, {
// Traccar пометил позицию как невалидную (valid=false) — протокольный
// признак «нет GPS-фикса»: сохраняем как примерную (LBS), в геозоны
// и пробег не пускаем
forceLbs: !point.valid,
lat: point.lat, lat: point.lat,
lng: point.lng, lng: point.lng,
speed: point.speed, speed: point.speed,