add passenger telemetry
This commit is contained in:
@@ -1013,6 +1013,8 @@
|
||||
|
||||
## ETA
|
||||
|
||||
Текущая ETA-логика в коде остается schedule-based: расписание + `time_offset_minutes` + `historical_delays`. Realtime-позиции, включая `source: "crowdsourced"`, публикуются через realtime API как дополнительный сигнал, но низкоуверенные passenger clusters не ухудшают schedule fallback. TODO для будущего realtime ETA: использовать official telemetry как основной источник, а crowdsourced virtual vehicles только как fallback/secondary signal с учетом `confidence`.
|
||||
|
||||
### [POST] /api/v1/eta/calculate
|
||||
|
||||
- Описание: считает ETA для одной пары `routeId + stopId`
|
||||
@@ -1320,7 +1322,8 @@
|
||||
|
||||
### [POST] /api/v1/telemetry/ingest
|
||||
|
||||
- Описание: принимает GPS/телеметрию, при необходимости автоматически создает транспорт по `vehicleNumber`, обновляет `vehicle_live_state` и пушит событие в realtime-канал маршрута
|
||||
- Описание: принимает официальную GPS/телеметрию транспорта, при необходимости автоматически создает транспорт по `vehicleNumber`, обновляет `vehicle_live_state` и пушит событие в realtime-канал маршрута
|
||||
- Важно: этот endpoint предназначен только для водительского приложения, GPS-трекера или официальной транспортной телеметрии. Пассажирское приложение не должно отправлять сюда пользовательские геосигналы.
|
||||
- Авторизация: `Authorization: Bearer <JWT>` или `X-API-Key: <key>`
|
||||
- Параметры path: нет
|
||||
- Query-параметры: нет
|
||||
@@ -1369,9 +1372,57 @@
|
||||
}
|
||||
```
|
||||
|
||||
### [POST] /api/v1/passenger-telemetry/ingest
|
||||
|
||||
- Описание: принимает обезличенный пассажирский геосигнал только при явном opt-in пользователя. Сигналы сохраняются только во временной in-memory очереди с TTL около 10 минут и используются для агрегированных approximate realtime-позиций.
|
||||
- Авторизация: `Authorization: Bearer <JWT>` или `X-API-Key: <key>`
|
||||
- Важно по приватности:
|
||||
- не принимает и не хранит `user id`, `account id`, телефон, device id или постоянные идентификаторы;
|
||||
- `session_id` трактуется как временный анонимный идентификатор и в памяти хранится только в хешированном виде;
|
||||
- сырые пассажирские точки не отдаются через публичный REST API и WebSocket;
|
||||
- пассажирская точка никогда не показывается напрямую как транспорт;
|
||||
- realtime может отдать только агрегированную виртуальную позицию `source: "crowdsourced"` / `is_estimated: true`, если достаточно уверенности.
|
||||
- Rate limit: отдельный лимит поверх общего API rate limit по `session_id`, IP и API key.
|
||||
- Request Body:
|
||||
- `session_id: string` — временный анонимный id сессии
|
||||
- `latitude: number`
|
||||
- `longitude: number`
|
||||
- `accuracy_m?: number | null`
|
||||
- `speed_kmh?: number | null`
|
||||
- `heading?: number | null`
|
||||
- `observed_at?: string (ISO datetime)`
|
||||
- `route_id?: number | string | null` — optional hint, сервер проверяет существование и матчинг к маршруту
|
||||
- `stop_id?: number | string | null`
|
||||
- `app_state?: 'foreground' | 'background'`
|
||||
- Response 202:
|
||||
- `{ accepted: true }`
|
||||
- Ошибки:
|
||||
- `400` — невалидные координаты, `accuracy_m`, `speed_kmh`, `heading`, `observed_at`, `route_id`, `stop_id`, `app_state`
|
||||
- `404` — `Route not found`, если переданный `route_id` не существует
|
||||
- `429` — превышен passenger telemetry rate limit
|
||||
- `500` — `Failed to ingest passenger telemetry`
|
||||
- Где найдено в коде: `backend/src/routes/api.js` -> `passengerTelemetryController.ingest`, `backend/src/controllers/passengerTelemetryController.js`, `backend/src/services/passengerTelemetryAggregator.js`
|
||||
|
||||
Пример запроса:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "temporary-anonymous-id",
|
||||
"latitude": 55.485,
|
||||
"longitude": 28.768,
|
||||
"accuracy_m": 18.5,
|
||||
"speed_kmh": 32.0,
|
||||
"heading": 91.0,
|
||||
"observed_at": "2026-05-20T12:30:00Z",
|
||||
"route_id": "12",
|
||||
"stop_id": "123",
|
||||
"app_state": "foreground"
|
||||
}
|
||||
```
|
||||
|
||||
### [GET] /api/v1/realtime/routes/:routeId/vehicles
|
||||
|
||||
- Описание: отдает snapshot живых транспортных единиц по маршруту
|
||||
- Описание: отдает snapshot живых транспортных единиц по маршруту. Ответ может включать official live vehicles и агрегированные approximate crowdsourced virtual vehicles, если official telemetry рядом отсутствует и passenger confidence достаточный.
|
||||
- Авторизация: `Authorization: Bearer <JWT>` или `X-API-Key: <key>`
|
||||
- Параметры path: `routeId`
|
||||
- Query-параметры:
|
||||
@@ -1380,6 +1431,14 @@
|
||||
- Headers: `Authorization` или `X-API-Key`
|
||||
- Request Body: нет
|
||||
- Response 200/201: `LiveVehicleState[]`
|
||||
- Backward-compatible optional fields:
|
||||
- `source?: 'official' | 'crowdsourced'`
|
||||
- `confidence?: number`
|
||||
- `confidence_label?: 'LOW' | 'MEDIUM' | 'HIGH'`
|
||||
- `accuracy_m?: number`
|
||||
- `is_estimated?: boolean`
|
||||
- Для crowdsourced транспорта `vehicle_id` имеет synthetic формат `crowd:{routeId}:{clusterId}` и не содержит `session_id`.
|
||||
- Сырые passenger positions в этом endpoint не публикуются.
|
||||
- Ошибки:
|
||||
- `400` — `routeId must be a positive integer`
|
||||
- `500` — `Failed to fetch route live vehicles`
|
||||
@@ -1454,8 +1513,27 @@
|
||||
"latitude": 55.4869,
|
||||
"longitude": 28.7856,
|
||||
"speed_kmh": 24,
|
||||
"source": "official",
|
||||
"confidence": 1,
|
||||
"accuracy_m": 5,
|
||||
"is_estimated": false,
|
||||
"is_online": true,
|
||||
"seconds_since_update": 3
|
||||
},
|
||||
{
|
||||
"vehicle_id": "crowd:7:ab12cd34ef56",
|
||||
"vehicle_number": null,
|
||||
"route_id": 7,
|
||||
"latitude": 55.4871,
|
||||
"longitude": 28.786,
|
||||
"speed_kmh": 22,
|
||||
"source": "crowdsourced",
|
||||
"confidence": 0.63,
|
||||
"confidence_label": "MEDIUM",
|
||||
"accuracy_m": 27,
|
||||
"is_estimated": true,
|
||||
"is_online": true,
|
||||
"seconds_since_update": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ tags:
|
||||
- name: Alerts
|
||||
- name: Users
|
||||
- name: Telemetry
|
||||
- name: Passenger Telemetry
|
||||
- name: Realtime
|
||||
paths:
|
||||
/health:
|
||||
@@ -1694,7 +1695,8 @@ paths:
|
||||
/api/v1/telemetry/ingest:
|
||||
post:
|
||||
tags: [Telemetry]
|
||||
summary: Ingest GPS telemetry
|
||||
summary: Ingest official vehicle GPS telemetry
|
||||
description: For driver apps, GPS trackers, and official telemetry only. Passenger apps must use /api/v1/passenger-telemetry/ingest instead.
|
||||
security:
|
||||
- BearerAuth: []
|
||||
- ApiKeyAuth: []
|
||||
@@ -1733,10 +1735,58 @@ paths:
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalError'
|
||||
/api/v1/passenger-telemetry/ingest:
|
||||
post:
|
||||
tags: [Passenger Telemetry]
|
||||
summary: Ingest anonymized passenger telemetry
|
||||
description: |
|
||||
Accepts opt-in, temporary, anonymous passenger geo-signals for short-lived aggregation.
|
||||
Raw passenger points are not exposed through public REST APIs or WebSocket messages.
|
||||
The server may emit only aggregate approximate virtual vehicles with source=crowdsourced.
|
||||
security:
|
||||
- BearerAuth: []
|
||||
- ApiKeyAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PassengerTelemetryIngestRequest'
|
||||
example:
|
||||
session_id: temporary-anonymous-id
|
||||
latitude: 55.485
|
||||
longitude: 28.768
|
||||
accuracy_m: 18.5
|
||||
speed_kmh: 32
|
||||
heading: 91
|
||||
observed_at: '2026-05-20T12:30:00Z'
|
||||
route_id: '12'
|
||||
stop_id: '123'
|
||||
app_state: foreground
|
||||
responses:
|
||||
'202':
|
||||
description: Passenger signal accepted for aggregation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PassengerTelemetryIngestResponse'
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'429':
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalError'
|
||||
/api/v1/realtime/routes/{routeId}/vehicles:
|
||||
get:
|
||||
tags: [Realtime]
|
||||
summary: Live vehicles snapshot for route
|
||||
description: Returns official live vehicles plus aggregate approximate crowdsourced virtual vehicles when confidence is sufficient. Raw passenger points are never returned.
|
||||
security:
|
||||
- BearerAuth: []
|
||||
- ApiKeyAuth: []
|
||||
@@ -2879,11 +2929,66 @@ components:
|
||||
sourceRef:
|
||||
type: string
|
||||
nullable: true
|
||||
PassengerTelemetryIngestRequest:
|
||||
type: object
|
||||
required: [session_id, latitude, longitude]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
session_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 128
|
||||
description: Temporary anonymous session id; not a user, account, phone, or device id.
|
||||
latitude:
|
||||
type: number
|
||||
minimum: -90
|
||||
maximum: 90
|
||||
longitude:
|
||||
type: number
|
||||
minimum: -180
|
||||
maximum: 180
|
||||
accuracy_m:
|
||||
type: number
|
||||
minimum: 0
|
||||
maximum: 1000
|
||||
nullable: true
|
||||
speed_kmh:
|
||||
type: number
|
||||
minimum: 0
|
||||
maximum: 120
|
||||
nullable: true
|
||||
heading:
|
||||
type: number
|
||||
minimum: 0
|
||||
maximum: 360
|
||||
nullable: true
|
||||
observed_at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
route_id:
|
||||
oneOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
nullable: true
|
||||
description: Optional client hint; server verifies it before aggregation.
|
||||
stop_id:
|
||||
oneOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
nullable: true
|
||||
app_state:
|
||||
type: string
|
||||
enum: [foreground, background]
|
||||
nullable: true
|
||||
LiveVehicleState:
|
||||
type: object
|
||||
properties:
|
||||
vehicle_id:
|
||||
type: integer
|
||||
oneOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Official vehicles use numeric ids; crowdsourced virtual vehicles use crowd:{routeId}:{clusterId}.
|
||||
vehicle_number:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -2917,12 +3022,31 @@ components:
|
||||
accuracy_meters:
|
||||
type: number
|
||||
nullable: true
|
||||
accuracy_m:
|
||||
type: number
|
||||
nullable: true
|
||||
source_type:
|
||||
type: string
|
||||
nullable: true
|
||||
source_ref:
|
||||
type: string
|
||||
nullable: true
|
||||
source:
|
||||
type: string
|
||||
enum: [official, crowdsourced]
|
||||
nullable: true
|
||||
confidence:
|
||||
type: number
|
||||
minimum: 0
|
||||
maximum: 1
|
||||
nullable: true
|
||||
confidence_label:
|
||||
type: string
|
||||
enum: [LOW, MEDIUM, HIGH]
|
||||
nullable: true
|
||||
is_estimated:
|
||||
type: boolean
|
||||
nullable: true
|
||||
last_seen_at:
|
||||
type: string
|
||||
format: date-time
|
||||
@@ -2942,3 +3066,10 @@ components:
|
||||
type: string
|
||||
vehicle:
|
||||
$ref: '#/components/schemas/LiveVehicleState'
|
||||
PassengerTelemetryIngestResponse:
|
||||
type: object
|
||||
required: [accepted]
|
||||
properties:
|
||||
accepted:
|
||||
type: boolean
|
||||
example: true
|
||||
|
||||
Reference in New Issue
Block a user