| @@ -153,7 +153,11 @@ async def stop_mqtt_monitor(): | |||
| async def local_then_core(request: Request, call_next): | |||
| internal_core_proxy_paths = { | |||
| "/reslevis/updateAlarm", | |||
| <<<<<<< HEAD | |||
| "/reslevis/updateCoreSettings", | |||
| ======= | |||
| "/reslevis/updateCoreSettings", | |||
| >>>>>>> Tracker_bug_fix_20260429 | |||
| } | |||
| # only proxy CRUD for Reslevis (change prefix or methods if needed) | |||
| if ( | |||
| @@ -0,0 +1,124 @@ | |||
| import csv | |||
| import logging | |||
| from typing import Any, Dict, List, Optional | |||
| import httpx | |||
| log = logging.getLogger(__name__) | |||
| def _norm_mac(v: Any) -> str: | |||
| if v is None: | |||
| return "" | |||
| return "".join(ch for ch in str(v).strip().lower() if ch.isalnum()) | |||
| def _none_if_empty(v: Any) -> Any: | |||
| return None if v in ("", None, 0, "0") else v | |||
| def _str_or_none(v: Any) -> Optional[str]: | |||
| if v in ("", None): | |||
| return None | |||
| if isinstance(v, (int, float, bool)): | |||
| return str(v) | |||
| return v | |||
| def _normalize_tracker(row: dict) -> dict: | |||
| row = dict(row) | |||
| row["floor"] = _none_if_empty(row.get("floor")) | |||
| row["building"] = _none_if_empty(row.get("building")) | |||
| row["battery"] = _str_or_none(row.get("battery")) | |||
| row["temperature"] = _str_or_none(row.get("temperature")) | |||
| row["acceleration"] = _str_or_none(row.get("acceleration")) | |||
| row["heartRate"] = _str_or_none(row.get("heartRate")) | |||
| return row | |||
| async def _fetch_algorithm(core_base_url: str, timeout: float) -> Optional[str]: | |||
| try: | |||
| async with httpx.AsyncClient(timeout=timeout) as client: | |||
| resp = await client.get(f"{core_base_url}/reslevis/settings") | |||
| if 200 <= resp.status_code < 300: | |||
| payload = resp.json() | |||
| if isinstance(payload, list) and payload: | |||
| value = payload[0].get("current_algorithm") | |||
| if value is not None: | |||
| return str(value).lower() | |||
| except (httpx.RequestError, ValueError): | |||
| pass | |||
| return None | |||
| async def _filter_mode_trackers( | |||
| tracker_repo, core_base_url: str, timeout: float | |||
| ) -> List[Dict[str, Any]]: | |||
| try: | |||
| async with httpx.AsyncClient(timeout=timeout) as client: | |||
| resp = await client.get(f"{core_base_url}/reslevis/getTrackers") | |||
| if 200 <= resp.status_code < 300: | |||
| data = resp.json() | |||
| if isinstance(data, list): | |||
| normalized = [_normalize_tracker(r) for r in data if isinstance(r, dict)] | |||
| tracker_repo._write_all(normalized) | |||
| return normalized | |||
| except (httpx.RequestError, ValueError): | |||
| pass | |||
| return tracker_repo.list() | |||
| def _read_infer_positions(infer_csv_path: str) -> Dict[str, Dict[str, Optional[float]]]: | |||
| positions: Dict[str, Dict[str, Optional[float]]] = {} | |||
| try: | |||
| with open(infer_csv_path, newline="") as f: | |||
| reader = csv.DictReader(f, delimiter=";") | |||
| for row in reader: | |||
| mac = _norm_mac(row.get("mac")) | |||
| if not mac: | |||
| continue | |||
| try: | |||
| positions[mac] = { | |||
| "x": int(row["x"]) if row.get("x") not in (None, "") else None, | |||
| "y": int(row["y"]) if row.get("y") not in (None, "") else None, | |||
| } | |||
| except (KeyError, ValueError): | |||
| continue | |||
| except OSError: | |||
| log.warning("BLE-AI infer CSV not found: %s", infer_csv_path) | |||
| return positions | |||
| def _ai_mode_trackers( | |||
| tracker_repo, infer_csv_path: str | |||
| ) -> List[Dict[str, Any]]: | |||
| trackers = tracker_repo.list() | |||
| positions = _read_infer_positions(infer_csv_path) | |||
| if not positions: | |||
| return trackers | |||
| result = [] | |||
| for tracker in trackers: | |||
| t = dict(tracker) | |||
| mac = _norm_mac(t.get("mac")) | |||
| if mac and mac in positions: | |||
| t["x"] = positions[mac]["x"] | |||
| t["y"] = positions[mac]["y"] | |||
| result.append(t) | |||
| return result | |||
| async def get_mode_aware_trackers( | |||
| tracker_repo, | |||
| core_base_url: str, | |||
| infer_csv_path: str, | |||
| timeout: float, | |||
| ) -> List[Dict[str, Any]]: | |||
| algorithm = await _fetch_algorithm(core_base_url, timeout) | |||
| if algorithm == "filter": | |||
| return await _filter_mode_trackers(tracker_repo, core_base_url, timeout) | |||
| if algorithm == "ai": | |||
| return _ai_mode_trackers(tracker_repo, infer_csv_path) | |||
| return tracker_repo.list() | |||
| @@ -41,6 +41,7 @@ from logica_reslevis.subject import SubjectJsonRepository | |||
| from logica_reslevis.alarm import AlarmJsonRepository | |||
| from logica_reslevis.track import TrackJsonRepository | |||
| from logica_reslevis.tracker_zone import TrackerZoneJsonRepository | |||
| from logica_reslevis.tracker_mode import get_mode_aware_trackers | |||
| from security import get_current_user | |||
| @@ -48,7 +49,11 @@ from security import get_current_user | |||
| CORE_BASE_URL = config_env.CORE_API_URL.rstrip("/") | |||
| ALERTS_CORE_BASE_URL = "http://localhost:1902" | |||
| TRACKS_CORE_BASE_URL = "http://localhost:1902" | |||
| <<<<<<< HEAD | |||
| SETTINGS_CORE_BASE_URL = "http://localhost:1902" | |||
| ======= | |||
| SETTINGS_CORE_BASE_URL = "http://127.0.0.1:1902" | |||
| >>>>>>> Tracker_bug_fix_20260429 | |||
| CORE_TIMEOUT = 2.0 # secondi | |||
| async def sync_core_get(request: Request) -> None: | |||
| @@ -126,16 +131,6 @@ def _normalize_gateway(row: dict) -> dict: | |||
| row["building"] = _none_if_empty(row.get("building")) | |||
| return row | |||
| def _normalize_tracker(row: dict) -> dict: | |||
| row = dict(row) | |||
| row["floor"] = _none_if_empty(row.get("floor")) | |||
| row["building"] = _none_if_empty(row.get("building")) | |||
| row["battery"] = _str_or_none(row.get("battery")) | |||
| row["temperature"] = _str_or_none(row.get("temperature")) | |||
| row["acceleration"] = _str_or_none(row.get("acceleration")) | |||
| row["heartRate"] = _str_or_none(row.get("heartRate")) | |||
| return row | |||
| def _normalize_track(row: dict) -> dict: | |||
| row = dict(row) | |||
| row["ID"] = row.get("ID") | |||
| @@ -167,7 +162,6 @@ def _normalize_zone(row: dict) -> dict: | |||
| CORE_GET_SYNC = { | |||
| "/reslevis/getGateways": (gateway_repo, _normalize_gateway), | |||
| "/reslevis/getZones": (zone_repo, _normalize_zone), | |||
| "/reslevis/getTrackers": (tracker_repo, _normalize_tracker), | |||
| } | |||
| @@ -352,8 +346,13 @@ def removeZoneAreaDefinition(zone_area_definition_uuid: str): | |||
| tags=["Reslevis"], | |||
| dependencies=[Depends(get_current_user)], | |||
| ) | |||
| def getTrackers(): | |||
| return tracker_repo.list() | |||
| async def getTrackers(): | |||
| return await get_mode_aware_trackers( | |||
| tracker_repo, | |||
| SETTINGS_CORE_BASE_URL, | |||
| config_env.BLE_AI_INFER_CSV, | |||
| CORE_TIMEOUT, | |||
| ) | |||
| @router.post("/postTracker", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) | |||
| @@ -25,7 +25,10 @@ class FloorItem(BaseModel): | |||
| class ZoneItem(BaseModel): | |||
| id: UUID | |||
| name: str | |||
| <<<<<<< HEAD | |||
| groups: Optional[List[UUID]] = None | |||
| ======= | |||
| >>>>>>> Tracker_bug_fix_20260429 | |||
| floor: Optional[UUID] = None | |||
| building: Optional[UUID] = None | |||
| @@ -53,6 +56,7 @@ class GatewayItem(BaseModel): | |||
| notes: Optional[str] = None | |||
| floor: Optional[UUID] = None | |||
| building: Optional[UUID] = None | |||
| zone: Optional[UUID] = None | |||
| class TrackerItem(BaseModel): | |||
| id: UUID | |||
| @@ -83,6 +87,29 @@ class SettingItem(BaseModel): | |||
| language: str | |||
| class CoreSettingsItem(BaseModel): | |||
| ID: int | |||
| current_algorithm: str | |||
| location_confidence: int | |||
| last_seen_threshold: int | |||
| beacon_metric_size: int | |||
| HA_send_interval: int | |||
| HA_send_changes_only: bool | |||
| RSSI_enforce_threshold: bool | |||
| RSSI_min_threshold: int | |||
| class CoreSettingsUpdateItem(BaseModel): | |||
| current_algorithm: str | |||
| last_seen_threshold: int | |||
| beacon_metric_size: int | |||
| HA_send_interval: int | |||
| HA_send_changes_only: bool | |||
| RSSI_enforce_threshold: bool | |||
| RSSI_min_threshold: int | |||
| location_confidence: Optional[int] = None | |||
| class GuiConfigItem(BaseModel): | |||
| id: str | |||
| name: Optional[str] = None | |||
| @@ -132,6 +159,8 @@ class AlarmCoreItem(BaseModel): | |||
| type: str | |||
| status: str | |||
| timestamp: str | |||
| operator: Optional[UUID] = None | |||
| resolution_timestamp: Optional[str] = None | |||
| class AlarmStatusUpdateItem(BaseModel): | |||
| @@ -178,7 +207,12 @@ class TrackHistoryItem(BaseModel): | |||
| class TrackerZoneItem(BaseModel): | |||
| id: UUID | |||
| <<<<<<< HEAD | |||
| name: str | |||
| ======= | |||
| name: Optional[str] = None | |||
| zoneList: List[UUID] | |||
| >>>>>>> Tracker_bug_fix_20260429 | |||
| tracker: UUID | |||
| zoneList: list[UUID] | |||
| days: Optional[str] = None | |||