diff --git a/app.py b/app.py index f1b234b..b49af05 100644 --- a/app.py +++ b/app.py @@ -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 ( diff --git a/logica_reslevis/tracker_mode.py b/logica_reslevis/tracker_mode.py new file mode 100644 index 0000000..f94452f --- /dev/null +++ b/logica_reslevis/tracker_mode.py @@ -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() diff --git a/routes/reslevis.py b/routes/reslevis.py index 0812c32..ccaa395 100644 --- a/routes/reslevis.py +++ b/routes/reslevis.py @@ -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)]) diff --git a/schemas/reslevis.py b/schemas/reslevis.py index 4971fc5..b76d4c3 100644 --- a/schemas/reslevis.py +++ b/schemas/reslevis.py @@ -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