修复告警定位无效坐标处理
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user