补齐设备HTTP定位坐标归一化
This commit is contained in:
@@ -130,6 +130,19 @@ class LocationService(DatabaseServiceBase):
|
||||
)
|
||||
return device_identity, None
|
||||
|
||||
payload = self._normalized_location_payload(
|
||||
coord_type=self._get_explicit_coord_type(payload),
|
||||
latitude=payload.lat,
|
||||
longitude=payload.lng,
|
||||
accuracy_m=payload.accuracy_m,
|
||||
altitude_m=payload.altitude_m,
|
||||
speed_mps=payload.speed_mps,
|
||||
heading_deg=payload.heading_deg,
|
||||
source=payload.source,
|
||||
battery_pct=payload.battery_pct,
|
||||
device_time=payload.device_time,
|
||||
)
|
||||
|
||||
db_session = await self.get_session()
|
||||
try:
|
||||
dao = LocationDAO(db_session)
|
||||
@@ -191,6 +204,60 @@ class LocationService(DatabaseServiceBase):
|
||||
finally:
|
||||
await db_session.close()
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _LocationPayload:
|
||||
coord_type: str
|
||||
lat: float
|
||||
lng: float
|
||||
accuracy_m: int | None
|
||||
altitude_m: float | None
|
||||
speed_mps: float | None
|
||||
heading_deg: int | None
|
||||
source: int
|
||||
battery_pct: int | None
|
||||
device_time: datetime
|
||||
|
||||
def _normalized_location_payload(
|
||||
self,
|
||||
*,
|
||||
coord_type: str | None,
|
||||
latitude: float,
|
||||
longitude: float,
|
||||
accuracy_m: int | None,
|
||||
altitude_m: float | None,
|
||||
speed_mps: float | None,
|
||||
heading_deg: int | None,
|
||||
source: int,
|
||||
battery_pct: int | None,
|
||||
device_time: datetime,
|
||||
) -> _LocationPayload:
|
||||
normalized_lat, normalized_lng, normalized_coord_type = normalize_location_coordinate(
|
||||
latitude=float(latitude),
|
||||
longitude=float(longitude),
|
||||
coord_type=coord_type,
|
||||
)
|
||||
return self._LocationPayload(
|
||||
coord_type=normalized_coord_type,
|
||||
lat=normalized_lat,
|
||||
lng=normalized_lng,
|
||||
accuracy_m=accuracy_m,
|
||||
altitude_m=altitude_m,
|
||||
speed_mps=speed_mps,
|
||||
heading_deg=heading_deg,
|
||||
source=source,
|
||||
battery_pct=battery_pct,
|
||||
device_time=device_time,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _get_explicit_coord_type(payload: DeviceLocationReportRequest) -> str | None:
|
||||
fields_set = getattr(payload, "model_fields_set", None)
|
||||
if fields_set is None:
|
||||
fields_set = getattr(payload, "__fields_set__", set())
|
||||
if "coord_type" not in fields_set:
|
||||
return None
|
||||
return payload.coord_type
|
||||
|
||||
async def report_mqtt_device_location(
|
||||
self,
|
||||
*,
|
||||
@@ -215,19 +282,6 @@ class LocationService(DatabaseServiceBase):
|
||||
)
|
||||
return None
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _MQTTLocationPayload:
|
||||
coord_type: str
|
||||
lat: float
|
||||
lng: float
|
||||
accuracy_m: int | None
|
||||
altitude_m: float | None
|
||||
speed_mps: float | None
|
||||
heading_deg: int | None
|
||||
source: int
|
||||
battery_pct: int | None
|
||||
device_time: datetime
|
||||
|
||||
source_value = self._normalize_location_source(source)
|
||||
|
||||
db_session = await self.get_session()
|
||||
@@ -237,16 +291,10 @@ class LocationService(DatabaseServiceBase):
|
||||
if not binding_row or binding_row["child_id"] is None:
|
||||
return None
|
||||
|
||||
normalized_lat, normalized_lng, normalized_coord_type = normalize_location_coordinate(
|
||||
payload = self._normalized_location_payload(
|
||||
coord_type=coord_type,
|
||||
latitude=float(latitude),
|
||||
longitude=float(longitude),
|
||||
coord_type=coord_type,
|
||||
)
|
||||
|
||||
payload = _MQTTLocationPayload(
|
||||
coord_type=normalized_coord_type,
|
||||
lat=normalized_lat,
|
||||
lng=normalized_lng,
|
||||
accuracy_m=accuracy_m,
|
||||
altitude_m=altitude_m,
|
||||
speed_mps=speed_mps,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from banban.schemas.location import DeviceLocationReportRequest
|
||||
from banban.service.location import (
|
||||
LocationService,
|
||||
normalize_location_coordinate,
|
||||
@@ -49,3 +50,68 @@ async def test_mqtt_location_report_ignores_zero_zero_before_normalization(monke
|
||||
|
||||
assert row is None
|
||||
assert sessions == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_http_location_report_without_coord_type_converts_before_persist(monkeypatch):
|
||||
service = LocationService()
|
||||
captured = {}
|
||||
|
||||
class FakeDeviceIdentity:
|
||||
child_id = 9
|
||||
child_name = "child"
|
||||
|
||||
class FakeSession:
|
||||
async def close(self):
|
||||
captured["closed"] = True
|
||||
|
||||
class FakeLocationDAO:
|
||||
def __init__(self, session):
|
||||
captured["session"] = session
|
||||
|
||||
async def report_device_location(self, *, device_id, child_id, payload):
|
||||
captured["device_id"] = device_id
|
||||
captured["child_id"] = child_id
|
||||
captured["payload"] = payload
|
||||
return {
|
||||
"child_id": child_id,
|
||||
"device_id": device_id,
|
||||
"coord_type": payload.coord_type,
|
||||
"lat": payload.lat,
|
||||
"lng": payload.lng,
|
||||
"should_resolve_address": False,
|
||||
}
|
||||
|
||||
async def fake_authenticate_device_identity(*, device_id, serial_number):
|
||||
captured["auth"] = (device_id, serial_number)
|
||||
return FakeDeviceIdentity()
|
||||
|
||||
async def fake_get_session():
|
||||
return FakeSession()
|
||||
|
||||
monkeypatch.setattr(
|
||||
"banban.service.location.im_service.authenticate_device_identity",
|
||||
fake_authenticate_device_identity,
|
||||
)
|
||||
monkeypatch.setattr("banban.service.location.LocationDAO", FakeLocationDAO)
|
||||
monkeypatch.setattr(service, "get_session", fake_get_session)
|
||||
|
||||
_, row = await service.report_device_location(
|
||||
device_id="TalkingQ_device001",
|
||||
serial_number="SN001",
|
||||
payload=DeviceLocationReportRequest(
|
||||
lat=30.223122166666666,
|
||||
lng=120.25837883333334,
|
||||
source=0,
|
||||
device_time="2026-06-29T00:00:00",
|
||||
),
|
||||
)
|
||||
|
||||
persisted_payload = captured["payload"]
|
||||
assert captured["auth"] == ("TalkingQ_device001", "SN001")
|
||||
assert captured["child_id"] == 9
|
||||
assert captured["closed"] is True
|
||||
assert row["coord_type"] == "gcj02"
|
||||
assert persisted_payload.coord_type == "gcj02"
|
||||
assert persisted_payload.lat == pytest.approx(30.22066658053894)
|
||||
assert persisted_payload.lng == pytest.approx(120.26284572518867)
|
||||
|
||||
Reference in New Issue
Block a user