From 976b245c196b188c9d8b06d21a7ae7c4ff8622d3 Mon Sep 17 00:00:00 2001 From: pollutri Date: Wed, 29 Apr 2026 16:26:05 +0200 Subject: [PATCH 1/3] Fix 1 --- logica_reslevis/tracker_mode.py | 118 ++++++++++++++++++++++++++++++++ schemas/reslevis.py | 23 +++++++ 2 files changed, 141 insertions(+) create mode 100644 logica_reslevis/tracker_mode.py diff --git a/logica_reslevis/tracker_mode.py b/logica_reslevis/tracker_mode.py new file mode 100644 index 0000000..221e128 --- /dev/null +++ b/logica_reslevis/tracker_mode.py @@ -0,0 +1,118 @@ +import csv +import logging +from typing import Any, Dict, List, Optional + +import httpx + +log = logging.getLogger(__name__) + + +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 = (row.get("mac") or "").strip().lower() + 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 = (t.get("mac") or "").strip().lower() + 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/schemas/reslevis.py b/schemas/reslevis.py index 5269604..356c054 100644 --- a/schemas/reslevis.py +++ b/schemas/reslevis.py @@ -83,6 +83,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 From f8ca2b700fa2326acae0b9a9f2b8b49be6d19a6a Mon Sep 17 00:00:00 2001 From: pollutri Date: Wed, 29 Apr 2026 17:16:36 +0200 Subject: [PATCH 2/3] bug fix 2 --- app.py | 1 + logica_reslevis/tracker_mode.py | 10 ++++- routes/reslevis.py | 74 +++++++++++++++++++++++++++------ 3 files changed, 70 insertions(+), 15 deletions(-) diff --git a/app.py b/app.py index c922fd3..9667ecf 100644 --- a/app.py +++ b/app.py @@ -153,6 +153,7 @@ async def stop_mqtt_monitor(): async def local_then_core(request: Request, call_next): internal_core_proxy_paths = { "/reslevis/updateAlarm", + "/reslevis/updateCoreSettings", } # 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 index 221e128..f94452f 100644 --- a/logica_reslevis/tracker_mode.py +++ b/logica_reslevis/tracker_mode.py @@ -7,6 +7,12 @@ 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 @@ -68,7 +74,7 @@ def _read_infer_positions(infer_csv_path: str) -> Dict[str, Dict[str, Optional[f with open(infer_csv_path, newline="") as f: reader = csv.DictReader(f, delimiter=";") for row in reader: - mac = (row.get("mac") or "").strip().lower() + mac = _norm_mac(row.get("mac")) if not mac: continue try: @@ -93,7 +99,7 @@ def _ai_mode_trackers( result = [] for tracker in trackers: t = dict(tracker) - mac = (t.get("mac") or "").strip().lower() + mac = _norm_mac(t.get("mac")) if mac and mac in positions: t["x"] = positions[mac]["x"] t["y"] = positions[mac]["y"] diff --git a/routes/reslevis.py b/routes/reslevis.py index bcbc8f2..638637e 100644 --- a/routes/reslevis.py +++ b/routes/reslevis.py @@ -24,6 +24,8 @@ from schemas.reslevis import ( TrackerZoneItem, SettingItem, GuiConfigItem, + CoreSettingsItem, + CoreSettingsUpdateItem, ) from logica_reslevis.gateway import GatewayJsonRepository @@ -39,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 @@ -46,6 +49,7 @@ 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" +SETTINGS_CORE_BASE_URL = "http://127.0.0.1:1902" CORE_TIMEOUT = 2.0 # secondi async def sync_core_get(request: Request) -> None: @@ -123,16 +127,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") @@ -164,7 +158,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), } @@ -349,8 +342,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)]) @@ -622,3 +620,53 @@ def updateGuiConfig(item: GuiConfigItem): def removeGuiConfig(gui_config_id: str): gui_config_repo.remove(gui_config_id) return {"message": "OK"} + + +@router.get( + "/getCoreSettings", + response_model=List[CoreSettingsItem], + tags=["Reslevis"], + dependencies=[Depends(get_current_user)], +) +async def getCoreSettings(): + async with httpx.AsyncClient(timeout=CORE_TIMEOUT) as client: + resp = await client.get(f"{SETTINGS_CORE_BASE_URL}/reslevis/settings") + + if resp.status_code >= 400: + detail = resp.text.strip() or "CORE settings request failed" + raise HTTPException(status_code=resp.status_code, detail=detail) + + try: + payload = resp.json() + except ValueError as exc: + raise HTTPException(status_code=502, detail="Invalid CORE response") from exc + + if not isinstance(payload, list): + raise HTTPException(status_code=502, detail="Unexpected CORE response type") + + return payload + + +@router.put( + "/updateCoreSettings", + tags=["Reslevis"], + dependencies=[Depends(get_current_user)], +) +async def updateCoreSettings(item: CoreSettingsUpdateItem): + async with httpx.AsyncClient(timeout=CORE_TIMEOUT) as client: + resp = await client.patch( + f"{SETTINGS_CORE_BASE_URL}/reslevis/settings", + json=item.model_dump(exclude_none=True), + ) + + if resp.status_code >= 400: + detail = resp.text.strip() or "CORE settings update failed" + raise HTTPException(status_code=resp.status_code, detail=detail) + + if not resp.content: + return {"message": "OK"} + + try: + return resp.json() + except ValueError: + return {"message": "OK"} From 9c358eee3d18ad07358a10aa4b483b89d5c10afc Mon Sep 17 00:00:00 2001 From: root Date: Mon, 4 May 2026 16:06:33 +0200 Subject: [PATCH 3/3] Schema update --- schemas/reslevis.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/schemas/reslevis.py b/schemas/reslevis.py index 356c054..dae09b4 100644 --- a/schemas/reslevis.py +++ b/schemas/reslevis.py @@ -25,7 +25,6 @@ class FloorItem(BaseModel): class ZoneItem(BaseModel): id: UUID name: str - groups: List[UUID] floor: Optional[UUID] = None building: Optional[UUID] = None @@ -53,6 +52,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 @@ -155,6 +155,8 @@ class AlarmCoreItem(BaseModel): type: str status: str timestamp: str + operator: Optional[UUID] = None + resolution_timestamp: Optional[str] = None class AlarmStatusUpdateItem(BaseModel): @@ -201,6 +203,7 @@ class TrackHistoryItem(BaseModel): class TrackerZoneItem(BaseModel): id: UUID + name: Optional[str] = None zoneList: List[UUID] tracker: UUID days: Optional[str] = None