修复告警定位无效坐标处理

This commit is contained in:
stu2not
2026-06-22 14:33:10 +08:00
parent 9ae5bc8f83
commit 8aeb813a68
5 changed files with 105 additions and 15 deletions

View File

@@ -56,9 +56,7 @@ function formatOptionalNumber(value?: number | null, digits = 1): string | null
function getLocationAddress(point: DeviceLocation | DeviceTrajectoryPoint): string {
const address = String(point.address || '').trim()
if (address) return address
if (point.address_resolve_status === 0) return '地解析中'
if (point.address_resolve_status === 2) return '地点暂时无法解析'
return '当前地点未知'
return '地解析中'
}
function buildLocationDetailLines(

View File

@@ -1,4 +1,5 @@
from collections.abc import Mapping
from datetime import datetime
from typing import Any
from sqlalchemy import text
@@ -17,6 +18,12 @@ class DeviceAlarmDAO(BaseDAO):
location: Mapping[str, Any] | None = None,
) -> int:
location = location or {}
address = str(location.get("address") or "").strip() or None
address_status = location.get("address_resolve_status")
if address and address_status is None:
address_status = 1
elif address_status is None and location.get("lat") is not None and location.get("lng") is not None:
address_status = 0
result = await self.execute(
"""
INSERT INTO device_alarm_events (
@@ -28,6 +35,8 @@ class DeviceAlarmDAO(BaseDAO):
lat,
lng,
location_updated_at,
address,
address_resolved_at,
address_resolve_status,
created_at
)
@@ -40,6 +49,8 @@ class DeviceAlarmDAO(BaseDAO):
:lat,
:lng,
:location_updated_at,
:address,
:address_resolved_at,
:address_resolve_status,
CURRENT_TIMESTAMP
)
@@ -53,7 +64,9 @@ class DeviceAlarmDAO(BaseDAO):
"lat": location.get("lat"),
"lng": location.get("lng"),
"location_updated_at": location.get("updated_at"),
"address_resolve_status": 0 if location.get("lat") is not None and location.get("lng") is not None else None,
"address": address,
"address_resolved_at": location.get("address_resolved_at"),
"address_resolve_status": address_status,
},
)
await self.commit()
@@ -100,7 +113,10 @@ class DeviceAlarmDAO(BaseDAO):
coord_type,
lat,
lng,
updated_at
updated_at,
address,
address_resolved_at,
address_resolve_status
FROM child_location_current
WHERE child_id = :child_id
LIMIT 1
@@ -144,6 +160,15 @@ class DeviceAlarmDAO(BaseDAO):
) -> bool:
lat = location.get("lat")
lng = location.get("lng")
address = str(location.get("address") or "").strip() or None
address_status = location.get("address_resolve_status")
address_resolved_at = location.get("address_resolved_at")
if address and address_status is None:
address_status = 1
elif address_status is None and lat is not None and lng is not None:
address_status = 0
if address and address_resolved_at is None:
address_resolved_at = datetime.now()
result = await self.execute(
text(
"""
@@ -152,8 +177,8 @@ class DeviceAlarmDAO(BaseDAO):
lat = :lat,
lng = :lng,
location_updated_at = :location_updated_at,
address = NULL,
address_resolved_at = NULL,
address = :address,
address_resolved_at = :address_resolved_at,
address_resolve_status = :address_resolve_status
WHERE alarm_id = :alarm_id
"""
@@ -164,7 +189,9 @@ class DeviceAlarmDAO(BaseDAO):
"lat": lat,
"lng": lng,
"location_updated_at": location.get("updated_at"),
"address_resolve_status": 0 if lat is not None and lng is not None else None,
"address": address,
"address_resolved_at": address_resolved_at,
"address_resolve_status": address_status,
},
)
await self.commit()

View File

@@ -33,6 +33,7 @@ except ModuleNotFoundError:
router = APIRouter(prefix="/devices", tags=["devices"])
logger = logging.getLogger("app.devices")
LOCATION_ADDRESS_PENDING_TEXT = "地址解析中"
class DeviceMessageItem(BaseModel):
@@ -236,6 +237,11 @@ def _row_to_current_location_response(
stale: bool = False,
realtime: bool = True,
) -> DeviceLocationCurrentResponse:
address = str(row.get("address") or "").strip() or None
address_resolve_status = int(row["address_resolve_status"]) if row.get("address_resolve_status") is not None else None
if address is None and address_resolve_status != 1:
address = LOCATION_ADDRESS_PENDING_TEXT
return DeviceLocationCurrentResponse(
child_id=int(row["child_id"]),
child_name=row.get("child_name"),
@@ -252,9 +258,9 @@ def _row_to_current_location_response(
device_time=row["device_time"],
server_time=row["server_time"],
updated_at=row["updated_at"],
address=row.get("address"),
address=address,
address_resolved_at=row.get("address_resolved_at"),
address_resolve_status=int(row["address_resolve_status"]) if row.get("address_resolve_status") is not None else None,
address_resolve_status=address_resolve_status,
stale=stale,
realtime=realtime,
)

View File

@@ -3,6 +3,7 @@ from typing import Any
from banban.dao.device_alarm import DeviceAlarmDAO
from banban.service.amap_geocode import amap_geocode_service
from banban.service.location import is_valid_coordinate_pair
from services.task_manager import task_manager
from services.database_service_base import DatabaseServiceBase
from utils.logger import session_logger
@@ -34,6 +35,13 @@ class DeviceAlarmService(DatabaseServiceBase):
location = None
if child_id is not None:
location = await dao.get_current_location_snapshot(child_id=child_id)
if location and not is_valid_coordinate_pair(location.get("lat"), location.get("lng")):
session_logger.warning(
device_id,
"alarm_address",
f"忽略无效当前定位快照: child_id={child_id}, lat={location.get('lat')}, lng={location.get('lng')}",
)
location = None
alarm_id = await dao.create(
device_id=device_id,
@@ -42,7 +50,13 @@ class DeviceAlarmService(DatabaseServiceBase):
source_msg_id=source_msg_id,
location=location,
)
if resolve_address and alarm_id and location and location.get("lat") is not None and location.get("lng") is not None:
if (
resolve_address
and alarm_id
and location
and is_valid_coordinate_pair(location.get("lat"), location.get("lng"))
and not str(location.get("address") or "").strip()
):
await task_manager.create_task(
self.resolve_alarm_address(
alarm_id=alarm_id,
@@ -90,7 +104,12 @@ class DeviceAlarmService(DatabaseServiceBase):
device_id: str,
location: Mapping[str, Any],
) -> bool:
if location.get("lat") is None or location.get("lng") is None:
if not is_valid_coordinate_pair(location.get("lat"), location.get("lng")):
session_logger.warning(
device_id,
"alarm_address",
f"忽略无效告警定位: alarm_id={alarm_id}, lat={location.get('lat')}, lng={location.get('lng')}",
)
return False
updated = await self.update_alarm_location(alarm_id=alarm_id, location=location)
@@ -116,7 +135,13 @@ class DeviceAlarmService(DatabaseServiceBase):
async def resolve_alarm_current_location(self, *, alarm_id: int, device_id: str) -> bool:
alarm = await self.get_alarm_event(alarm_id=alarm_id, device_id=device_id)
if not alarm or alarm.get("lat") is None or alarm.get("lng") is None:
if not alarm:
return False
if str(alarm.get("address") or "").strip():
return True
if not is_valid_coordinate_pair(alarm.get("lat"), alarm.get("lng")):
return False
await self.resolve_alarm_address(
@@ -135,6 +160,8 @@ class DeviceAlarmService(DatabaseServiceBase):
lat: float,
lng: float,
) -> None:
if not is_valid_coordinate_pair(lat, lng):
return
try:
result = await amap_geocode_service.reverse_geocode(lng=lng, lat=lat)
except Exception as exc:

View File

@@ -22,6 +22,20 @@ except ModuleNotFoundError:
ADDRESS_RESOLVE_PENDING = 0
ADDRESS_RESOLVE_SUCCESS = 1
ADDRESS_RESOLVE_FAILED = 2
INVALID_COORDINATE_EPSILON = 0.0000001
def is_valid_coordinate_pair(latitude: float | None, longitude: float | None) -> bool:
if latitude is None or longitude is None:
return False
try:
lat = float(latitude)
lng = float(longitude)
except (TypeError, ValueError):
return False
if lat < -90 or lat > 90 or lng < -180 or lng > 180:
return False
return not (abs(lat) < INVALID_COORDINATE_EPSILON and abs(lng) < INVALID_COORDINATE_EPSILON)
class LocationService(DatabaseServiceBase):
@@ -42,11 +56,19 @@ class LocationService(DatabaseServiceBase):
device_id: str,
serial_number: str,
payload: DeviceLocationReportRequest,
) -> tuple[Any, Mapping[str, Any]]:
) -> tuple[Any, Mapping[str, Any] | None]:
device_identity = await im_service.authenticate_device_identity(
device_id=device_id,
serial_number=serial_number,
)
if not is_valid_coordinate_pair(payload.lat, payload.lng):
session_logger.warning(
device_id,
"location",
f"忽略无效设备定位: lat={payload.lat}, lng={payload.lng}",
)
return device_identity, None
db_session = await self.get_session()
try:
dao = LocationDAO(db_session)
@@ -123,7 +145,13 @@ class LocationService(DatabaseServiceBase):
battery_pct: int | None = None,
device_time: datetime | None = None,
) -> Mapping[str, Any] | None:
if latitude is None or longitude is None:
if not is_valid_coordinate_pair(latitude, longitude):
if latitude is not None and longitude is not None:
session_logger.warning(
device_id,
"location",
f"忽略无效MQTT定位: lat={latitude}, lng={longitude}",
)
return None
@dataclass(frozen=True)
@@ -173,6 +201,8 @@ class LocationService(DatabaseServiceBase):
async def _queue_location_address_resolution(self, row: Mapping[str, Any] | None) -> None:
if not row or row.get("lat") is None or row.get("lng") is None:
return
if not is_valid_coordinate_pair(row.get("lat"), row.get("lng")):
return
if not row.get("should_resolve_address"):
return
await task_manager.create_task(
@@ -194,6 +224,8 @@ class LocationService(DatabaseServiceBase):
lat: float,
lng: float,
) -> None:
if not is_valid_coordinate_pair(lat, lng):
return
try:
result = await amap_geocode_service.reverse_geocode(lng=lng, lat=lat)
except Exception as exc: