From 46fce671050d215056bf1a44753850c6e74c209c Mon Sep 17 00:00:00 2001 From: pollutri Date: Tue, 14 Apr 2026 16:49:49 +0200 Subject: [PATCH] Add new API for Zone definition --- .../__pycache__/config.cpython-313.pyc | Bin 0 -> 1168 bytes .../zone_area_definition.cpython-313.pyc | Bin 0 -> 4657 bytes logica_reslevis/config.py | 1 + logica_reslevis/zone_area_definition.py | 72 ++++++++++++++++++ routes/__pycache__/reslevis.cpython-313.pyc | Bin 0 -> 21995 bytes routes/reslevis.py | 31 ++++++++ schemas/__pycache__/reslevis.cpython-313.pyc | Bin 0 -> 8285 bytes schemas/reslevis.py | 13 +++- 8 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 logica_reslevis/__pycache__/config.cpython-313.pyc create mode 100644 logica_reslevis/__pycache__/zone_area_definition.cpython-313.pyc create mode 100644 logica_reslevis/zone_area_definition.py create mode 100644 routes/__pycache__/reslevis.cpython-313.pyc create mode 100644 schemas/__pycache__/reslevis.cpython-313.pyc diff --git a/logica_reslevis/__pycache__/config.cpython-313.pyc b/logica_reslevis/__pycache__/config.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d58dfea0537ab995dc65cfb3fb78039b142b23c GIT binary patch literal 1168 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 literal 0 HcmV?d00001 diff --git a/logica_reslevis/config.py b/logica_reslevis/config.py index 417697a..8d5fa6b 100644 --- a/logica_reslevis/config.py +++ b/logica_reslevis/config.py @@ -18,6 +18,7 @@ GATEWAY_JSON_PATH = DATA_DIR / "gateway_list.json" BUILDING_JSON_PATH = DATA_DIR / "building.json" FLOOR_JSON_PATH = DATA_DIR / "floors.json" ZONE_JSON_PATH = DATA_DIR / "zone_list.json" +ZONE_AREA_DEFINITION_JSON_PATH = DATA_DIR / "zone_area_definition.json" TRACKER_JSON_PATH = DATA_DIR / "tracker_conf.json" OPERATOR_JSON_PATH = DATA_DIR / "oper_conf.json" SUBJECT_JSON_PATH = DATA_DIR / "subject.json" diff --git a/logica_reslevis/zone_area_definition.py b/logica_reslevis/zone_area_definition.py new file mode 100644 index 0000000..d4e36d6 --- /dev/null +++ b/logica_reslevis/zone_area_definition.py @@ -0,0 +1,72 @@ +import json +from typing import List, Dict, Any, Optional + +from fastapi.encoders import jsonable_encoder + +from schemas.reslevis import ZoneAreaDefinitionItem +from .config import ZONE_AREA_DEFINITION_JSON_PATH +from .gateway import _LockedFile, _atomic_write, _norm_str + + +class ZoneAreaDefinitionJsonRepository: + def __init__(self, json_path: str = ZONE_AREA_DEFINITION_JSON_PATH): + self.path = json_path + + def _read_all(self) -> List[Dict[str, Any]]: + with _LockedFile(self.path, "r") as fp: + try: + fp.seek(0) + data = fp.read().strip() + return json.loads(data) if data else [] + except json.JSONDecodeError: + return [] + + def _write_all(self, rows: List[Dict[str, Any]]) -> None: + payload = json.dumps(rows, ensure_ascii=False, indent=2) + _atomic_write(self.path, payload) + + def _index_by_uuid(self, rows: List[Dict[str, Any]], item_uuid: str) -> Optional[int]: + target = _norm_str(item_uuid) + for idx, row in enumerate(rows): + if _norm_str(row.get("UUID")) == target: + return idx + return None + + def list(self, item_uuid: Optional[str] = None) -> List[Dict[str, Any]]: + rows = self._read_all() + if item_uuid: + idx = self._index_by_uuid(rows, item_uuid) + return [rows[idx]] if idx is not None else [] + return rows + + def add(self, item: ZoneAreaDefinitionItem) -> None: + rows = self._read_all() + obj = jsonable_encoder(item) + obj_uuid = _norm_str(obj.get("UUID")) + + if self._index_by_uuid(rows, obj_uuid) is not None: + raise ValueError(f"ZoneAreaDefinition con UUID '{obj_uuid}' giĆ  presente") + + rows.append(obj) + self._write_all(rows) + + def update(self, item: ZoneAreaDefinitionItem) -> None: + rows = self._read_all() + obj = jsonable_encoder(item) + obj_uuid = _norm_str(obj.get("UUID")) + + idx = self._index_by_uuid(rows, obj_uuid) + if idx is None: + raise ValueError(f"ZoneAreaDefinition con UUID '{obj_uuid}' non trovato") + + rows[idx] = obj + self._write_all(rows) + + def remove(self, item_uuid: str) -> None: + rows = self._read_all() + idx = self._index_by_uuid(rows, item_uuid) + if idx is None: + raise ValueError(f"ZoneAreaDefinition con UUID '{item_uuid}' non trovato") + + del rows[idx] + self._write_all(rows) diff --git a/routes/__pycache__/reslevis.cpython-313.pyc b/routes/__pycache__/reslevis.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d556b21b1fbe31281d2c8718f9a0c128eefe2d67 GIT binary patch literal 21995 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