补齐设备HTTP定位坐标归一化

This commit is contained in:
stu2not
2026-06-29 15:55:37 +08:00
parent cc67e8877a
commit 0283aa2fec
2 changed files with 135 additions and 21 deletions

View File

@@ -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,

View File

@@ -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)