From a63864bcaaea7a1e325de65f6b8f2093ab9142a9 Mon Sep 17 00:00:00 2001 From: rhinemann Date: Sun, 22 Feb 2026 12:17:03 +0100 Subject: [PATCH 1/8] updated compose file --- store/docker/docker-compose.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/store/docker/docker-compose.yaml b/store/docker/docker-compose.yaml index 8f9c32a..dff34e5 100644 --- a/store/docker/docker-compose.yaml +++ b/store/docker/docker-compose.yaml @@ -1,8 +1,7 @@ -version: "3.9" name: "road_vision__database" services: postgres_db: - image: postgres:latest + image: postgres:17 container_name: postgres_db restart: always environment: -- 2.49.1 From 1e7516fe7bb88b46a1c8feca1636c51a5e4805d3 Mon Sep 17 00:00:00 2001 From: rhinemann Date: Sun, 22 Feb 2026 12:19:33 +0100 Subject: [PATCH 2/8] update requirements --- store/requirements.txt | Bin 758 -> 450 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/store/requirements.txt b/store/requirements.txt index f920843a968a3ddc817af19433867a2ab1f2a0af..b70b2974d512b1220c5c1f801d0529d2d046a579 100644 GIT binary patch literal 450 zcmZ9Ju@1s83`B26;!_yl57PtO$Hz_-^@ zYps&s5owsE;yX^h)0Wth4hvXH<{fgT?e3muGfq<*ci6yE>j3u3YF*-UdCCXs*1c`e ZrMe!jm=sLe_BH;69_xhdiM7}<<6lW)PKf{j literal 758 zcmZva!A`?K3`BiK;!{9Ps0ti7^bQh;6Q`=AX&bd{q9#!C^S~Q#mQ)C>tVBCA_SoL< zucZY}ZJjNN(pGlCx3`A>*6uhHyR;cGv2#0v6+kN#!mhZ#CNjqg+k+IgbL__Ng-chU z*b@gbyb&q$6xPfcYi?Wj(U2wK8Ff`9cg_ZV$qD2t;U*9A82xSWlAWlPKOr58hN#`k zf8AB|9|m`-I`ygzzIyMVu?65$=o4?ZNIHjk`*jT2z&-IcaB7tG76nXL~|7XPyQ1N_Nx# zI#P{%@E1~9QtmD>? jOty1>>Oz|zcbBv6l<5fa3Rc72@!t(Ry$EsCx3={IFM(~< -- 2.49.1 From 69e679eccff3c30575ed358ab34dd3af98ba00b8 Mon Sep 17 00:00:00 2001 From: AndriiJushchenko Date: Tue, 24 Feb 2026 22:18:36 +0200 Subject: [PATCH 3/8] =?UTF-8?q?SCRUM-[49,=2054]=20=D0=A0=D0=B5=D0=B0=D0=BB?= =?UTF-8?q?=D1=96=D0=B7=D1=83=D0=B2=D0=B0=D1=82=D0=B8=20POST=20=D1=82?= =?UTF-8?q?=D0=B0=20=D0=B2=D1=96=D0=B4=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D1=83?= =?UTF-8?q?=20=D0=BF=D0=BE=20websoket=20=D0=BF=D1=96=D0=B4=20=D1=87=D0=B0?= =?UTF-8?q?=D1=81=20=D0=B2=D0=B8=D0=BA=D0=BE=D0=BD=D0=B0=D0=BD=D0=BD=D1=8F?= =?UTF-8?q?=20POST?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- store/main.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/store/main.py b/store/main.py index 272a646..3edd1af 100644 --- a/store/main.py +++ b/store/main.py @@ -2,6 +2,7 @@ import asyncio import json from typing import Set, Dict, List, Any from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect, Body +from fastapi.encoders import jsonable_encoder from sqlalchemy import ( create_engine, MetaData, @@ -126,9 +127,34 @@ async def send_data_to_subscribers(user_id: int, data): @app.post("/processed_agent_data/") async def create_processed_agent_data(data: List[ProcessedAgentData]): - # Insert data to database - # Send data to subscribers - pass + session = SessionLocal() + try: + created_data = [ + { + "road_state": item.road_state, + "x": item.agent_data.accelerometer.x, + "y": item.agent_data.accelerometer.y, + "z": item.agent_data.accelerometer.z, + "latitude": item.agent_data.gps.latitude, + "longitude": item.agent_data.gps.longitude, + "timestamp": item.agent_data.timestamp, + } + for item in data + ] + stmt = processed_agent_data.insert().values(created_data).returning(processed_agent_data) + result = session.execute(stmt) + created_records = [dict(row._mapping) for row in result.fetchall()] + session.commit() + + for record in created_records: + await send_data_to_subscribers(jsonable_encoder(record)) + return created_records + except Exception as err: + session.rollback() + print(f"Database error: {err}") + raise HTTPException(status_code=500, detail="Internal Server Error") + finally: + session.close() @app.get( -- 2.49.1 From f3512e4afb26739c92cdc7bcbb3274c9ac688a55 Mon Sep 17 00:00:00 2001 From: AndriiJushchenko Date: Wed, 25 Feb 2026 19:05:25 +0200 Subject: [PATCH 4/8] =?UTF-8?q?=D0=A2=D1=80=D0=BE=D1=85=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=84=D1=96=D0=BA=D1=81=D0=B8=D0=B2=20=D1=84=D1=83=D0=BD?= =?UTF-8?q?=D0=BA=D1=86=D1=96=D1=8E=20post=20=D1=96=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D1=96=D0=B2=20=D1=82=D0=B5=D1=81=D1=82=D0=B8=20post=20?= =?UTF-8?q?=D1=96=20websoket.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- store/main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/store/main.py b/store/main.py index 3edd1af..81d51a1 100644 --- a/store/main.py +++ b/store/main.py @@ -126,12 +126,13 @@ async def send_data_to_subscribers(user_id: int, data): @app.post("/processed_agent_data/") -async def create_processed_agent_data(data: List[ProcessedAgentData]): +async def create_processed_agent_data(data: List[ProcessedAgentData], user_id: int = Body(..., embed=True)): session = SessionLocal() try: created_data = [ { "road_state": item.road_state, + "user_id": user_id, "x": item.agent_data.accelerometer.x, "y": item.agent_data.accelerometer.y, "z": item.agent_data.accelerometer.z, @@ -147,7 +148,7 @@ async def create_processed_agent_data(data: List[ProcessedAgentData]): session.commit() for record in created_records: - await send_data_to_subscribers(jsonable_encoder(record)) + await send_data_to_subscribers(user_id, jsonable_encoder(record)) return created_records except Exception as err: session.rollback() -- 2.49.1 From 98fb6aa12a4961c440ac40c3f8903822421a7466 Mon Sep 17 00:00:00 2001 From: Senya Date: Thu, 26 Feb 2026 11:20:47 +0200 Subject: [PATCH 5/8] SCRUM-48: feature with CRUD (GET) --- .idea/.gitignore | 3 + .idea/IoT-Systems.iml | 15 + .idea/dbnavigator.xml | 425 ++++++++++++++++++ .idea/inspectionProfiles/Project_Default.xml | 16 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + store/.gitignore | 4 +- store/__init__.py | 0 store/database.py | 15 + store/main.py | 103 ++--- store/schemas.py | 51 +++ 13 files changed, 584 insertions(+), 75 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/IoT-Systems.iml create mode 100644 .idea/dbnavigator.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 store/__init__.py create mode 100644 store/database.py create mode 100644 store/schemas.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/IoT-Systems.iml b/.idea/IoT-Systems.iml new file mode 100644 index 0000000..688aa87 --- /dev/null +++ b/.idea/IoT-Systems.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml new file mode 100644 index 0000000..3b61c61 --- /dev/null +++ b/.idea/dbnavigator.xml @@ -0,0 +1,425 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..977601b --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..df29e35 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5aef1d1 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/store/.gitignore b/store/.gitignore index 75b0912..f810ad9 100644 --- a/store/.gitignore +++ b/store/.gitignore @@ -1,3 +1,5 @@ venv __pycache__ -.idea \ No newline at end of file +.idea + +.idea/ diff --git a/store/__init__.py b/store/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/store/database.py b/store/database.py new file mode 100644 index 0000000..7d86055 --- /dev/null +++ b/store/database.py @@ -0,0 +1,15 @@ +from sqlalchemy import MetaData +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, declarative_base + +from config import POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_HOST, POSTGRES_PORT, POSTGRES_DB + + +DATABASE_URL = f"postgresql+psycopg2://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}" +engine = create_engine(DATABASE_URL) + +Base = declarative_base() + +metadata = MetaData() + +SessionLocal = sessionmaker(bind=engine) diff --git a/store/main.py b/store/main.py index 81d51a1..fb61018 100644 --- a/store/main.py +++ b/store/main.py @@ -1,11 +1,8 @@ -import asyncio import json -from typing import Set, Dict, List, Any +from typing import Set, Dict, List from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect, Body from fastapi.encoders import jsonable_encoder from sqlalchemy import ( - create_engine, - MetaData, Table, Column, Integer, @@ -13,25 +10,14 @@ from sqlalchemy import ( Float, DateTime, ) -from sqlalchemy.orm import sessionmaker from sqlalchemy.sql import select -from datetime import datetime -from pydantic import BaseModel, field_validator -from config import ( - POSTGRES_HOST, - POSTGRES_PORT, - POSTGRES_DB, - POSTGRES_USER, - POSTGRES_PASSWORD, -) + +from database import metadata, SessionLocal +from schemas import ProcessedAgentData, ProcessedAgentDataInDB # FastAPI app setup app = FastAPI() -# SQLAlchemy setup -DATABASE_URL = f"postgresql+psycopg2://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}" -engine = create_engine(DATABASE_URL) -metadata = MetaData() -# Define the ProcessedAgentData table + processed_agent_data = Table( "processed_agent_data", metadata, @@ -45,57 +31,6 @@ processed_agent_data = Table( Column("longitude", Float), Column("timestamp", DateTime), ) -SessionLocal = sessionmaker(bind=engine) - - -# SQLAlchemy model -class ProcessedAgentDataInDB(BaseModel): - id: int - road_state: str - user_id: int - x: float - y: float - z: float - latitude: float - longitude: float - timestamp: datetime - - -# FastAPI models -class AccelerometerData(BaseModel): - x: float - y: float - z: float - - -class GpsData(BaseModel): - latitude: float - longitude: float - - -class AgentData(BaseModel): - user_id: int - accelerometer: AccelerometerData - gps: GpsData - timestamp: datetime - - @classmethod - @field_validator("timestamp", mode="before") - def check_timestamp(cls, value): - if isinstance(value, datetime): - return value - try: - return datetime.fromisoformat(value) - except (TypeError, ValueError): - raise ValueError( - "Invalid timestamp format. Expected ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)." - ) - - -class ProcessedAgentData(BaseModel): - road_state: str - agent_data: AgentData - # WebSocket subscriptions subscriptions: Dict[int, Set[WebSocket]] = {} @@ -163,14 +98,34 @@ async def create_processed_agent_data(data: List[ProcessedAgentData], user_id: i response_model=ProcessedAgentDataInDB, ) def read_processed_agent_data(processed_agent_data_id: int): - # Get data by id - pass + session = SessionLocal() + try: + stmt = select(processed_agent_data).where( + processed_agent_data.c.id == processed_agent_data_id + ) + res = session.execute(stmt).fetchone() + if not res: + raise HTTPException(status_code=404, detail="Not found") + + return dict(res._mapping) + + finally: + session.close() @app.get("/processed_agent_data/", response_model=list[ProcessedAgentDataInDB]) def list_processed_agent_data(): - # Get list of data - pass + session = SessionLocal() + try: + stmt = select(processed_agent_data) + res = session.execute(stmt).fetchall() + if not res: + raise HTTPException(status_code=404, detail="Not found") + + return [dict(r._mapping) for r in res] + + finally: + session.close() @app.put( diff --git a/store/schemas.py b/store/schemas.py new file mode 100644 index 0000000..3d13dee --- /dev/null +++ b/store/schemas.py @@ -0,0 +1,51 @@ +from datetime import datetime + +from pydantic import BaseModel, field_validator + + +class ProcessedAgentDataInDB(BaseModel): + id: int + road_state: str + user_id: int + x: float + y: float + z: float + latitude: float + longitude: float + timestamp: datetime + + +# FastAPI models +class AccelerometerData(BaseModel): + x: float + y: float + z: float + + +class GpsData(BaseModel): + latitude: float + longitude: float + + +class AgentData(BaseModel): + user_id: int + accelerometer: AccelerometerData + gps: GpsData + timestamp: datetime + + @classmethod + @field_validator("timestamp", mode="before") + def check_timestamp(cls, value): + if isinstance(value, datetime): + return value + try: + return datetime.fromisoformat(value) + except (TypeError, ValueError): + raise ValueError( + "Invalid timestamp format. Expected ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)." + ) + + +class ProcessedAgentData(BaseModel): + road_state: str + agent_data: AgentData -- 2.49.1 From f9ef916331455a3578905bae657c2992e4783fea Mon Sep 17 00:00:00 2001 From: Senya Date: Thu, 26 Feb 2026 12:11:07 +0200 Subject: [PATCH 6/8] SCRUM-34: slobodeniuk parking signals --- .gitignore | 4 ++- agent/src/__init__.py | 0 agent/src/domain/aggregated_data.py | 7 +++-- agent/src/domain/parking.py | 9 ++++++ agent/src/file_datasource.py | 49 +++++++++++++++++++++++++++-- agent/src/main.py | 4 +-- agent/src/schema/parking_schema.py | 8 +++++ 7 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 agent/src/__init__.py create mode 100644 agent/src/domain/parking.py create mode 100644 agent/src/schema/parking_schema.py diff --git a/.gitignore b/.gitignore index 24a7bbe..3f20040 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ agent/docker/mosquitto/data/ -agent/docker/mosquitto/log/ \ No newline at end of file +agent/docker/mosquitto/log/ + +.idea/ \ No newline at end of file diff --git a/agent/src/__init__.py b/agent/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/agent/src/domain/aggregated_data.py b/agent/src/domain/aggregated_data.py index 0387ee2..22de049 100644 --- a/agent/src/domain/aggregated_data.py +++ b/agent/src/domain/aggregated_data.py @@ -1,13 +1,16 @@ from dataclasses import dataclass from datetime import datetime -from domain.accelerometer import Accelerometer -from domain.gps import Gps + +from agent.src.domain.accelerometer import Accelerometer +from agent.src.domain.gps import Gps +from agent.src.domain.parking import Parking @dataclass class AggregatedData: accelerometer: Accelerometer gps: Gps + parking: Parking timestamp: datetime user_id: int diff --git a/agent/src/domain/parking.py b/agent/src/domain/parking.py new file mode 100644 index 0000000..1f84043 --- /dev/null +++ b/agent/src/domain/parking.py @@ -0,0 +1,9 @@ +from dataclasses import dataclass + +from agent.src.domain.gps import Gps + + +@dataclass +class Parking: + empty_count: int + gps: Gps diff --git a/agent/src/file_datasource.py b/agent/src/file_datasource.py index 363f1df..de6d8a9 100644 --- a/agent/src/file_datasource.py +++ b/agent/src/file_datasource.py @@ -4,6 +4,7 @@ from datetime import datetime from pathlib import Path from typing import Optional, List +from domain.parking import Parking from domain.accelerometer import Accelerometer from domain.gps import Gps from domain.aggregated_data import AggregatedData @@ -12,12 +13,22 @@ import config class FileDatasource: - def __init__(self, accelerometer_filename: str, gps_filename: str) -> None: + def __init__( + self, + accelerometer_filename: str, + gps_filename: str, + park_filename: str, + ) -> None: + self.accelerometer_filename = accelerometer_filename + self.park_filename = park_filename self.gps_filename = gps_filename + self._park_f = None self._acc_f = None self._gps_f = None + + self._park_reader: Optional[csv.reader] = None self._acc_reader: Optional[csv.reader] = None self._gps_reader: Optional[csv.reader] = None @@ -30,6 +41,8 @@ class FileDatasource: if not Path(self.accelerometer_filename).exists(): raise FileNotFoundError(f"Accelerometer file not found: {self.accelerometer_filename}") + if not Path(self.park_filename).exists(): + raise FileNotFoundError(f"Accelerometer file not found: {self.park_filename}") if not Path(self.gps_filename).exists(): raise FileNotFoundError(f"GPS file not found: {self.gps_filename}") @@ -47,9 +60,11 @@ class FileDatasource: raise RuntimeError("Datasource is not started. Call startReading() before read().") acc_row = self._get_next_row(self._acc_reader, source="acc") + park_row = self._get_next_row(self._park_reader, source="park") gps_row = self._get_next_row(self._gps_reader, source="gps") acc = self._parse_acc(acc_row) + park = self._parse_park(park_row) gps = self._parse_gps(gps_row) # IMPORTANT: timing belongs to datasource (not MQTT / main.py) @@ -59,6 +74,7 @@ class FileDatasource: return AggregatedData( accelerometer=acc, gps=gps, + parking=park, timestamp=datetime.utcnow(), user_id=config.USER_ID, ) @@ -69,14 +85,17 @@ class FileDatasource: self._close_files() self._acc_f = open(self.accelerometer_filename, "r", newline="", encoding="utf-8") + self._park_f = open(self.park_filename, "r", newline="", encoding="utf-8") self._gps_f = open(self.gps_filename, "r", newline="", encoding="utf-8") self._acc_reader = csv.reader(self._acc_f, skipinitialspace=True) + self._park_reader = csv.reader(self._park_f, skipinitialspace=True) self._gps_reader = csv.reader(self._gps_f, skipinitialspace=True) # File pointer is already at 0 right after open(), so no need to rewind here. # Skip header row once. next(self._acc_reader, None) + next(self._park_reader, None) next(self._gps_reader, None) def _close_files(self) -> None: @@ -88,8 +107,10 @@ class FileDatasource: pass self._acc_f = None + self._park_f = None self._gps_f = None self._acc_reader = None + self._park_reader = None self._gps_reader = None def _rewind_acc(self) -> None: @@ -106,6 +127,13 @@ class FileDatasource: self._gps_reader = csv.reader(self._gps_f, skipinitialspace=True) next(self._gps_reader, None) # skip header row + def _rewind_park(self) -> None: + if self._park_f is None: + raise RuntimeError("GPS file is not open.") + self._park_f.seek(0) + self._park_reader = csv.reader(self._park_f, skipinitialspace=True) + next(self._park_reader, None) # skip header row + def _get_next_row(self, reader, source: str) -> List[str]: """Get the next valid row from the reader.""" if reader is None: @@ -118,6 +146,10 @@ class FileDatasource: if source == "acc": self._rewind_acc() reader = self._acc_reader + + elif source == 'park': + self._rewind_park() + reader = self._park_reader else: self._rewind_gps() reader = self._gps_reader @@ -148,4 +180,17 @@ class FileDatasource: raise ValueError(f"GPS row must have 2 values (longitude,latitude). Got: {row}") lon = float(row[0]) lat = float(row[1]) - return Gps(longitude=lon, latitude=lat) \ No newline at end of file + return Gps(longitude=lon, latitude=lat) + + @staticmethod + def _parse_park(row: List[str]) -> Parking: + if len(row) < 2: + raise ValueError(f"GPS row must have 2 values (longitude,latitude). Got: {row}") + lon = float(row[0]) + lat = float(row[1]) + empty_count = int(row[2]) + + return Parking( + gps=Gps(longitude=lon, latitude=lat), + empty_count=empty_count, + ) diff --git a/agent/src/main.py b/agent/src/main.py index 5513bf4..ce17d6b 100644 --- a/agent/src/main.py +++ b/agent/src/main.py @@ -37,10 +37,10 @@ def run(): # Prepare mqtt client client = connect_mqtt(config.MQTT_BROKER_HOST, config.MQTT_BROKER_PORT) # Prepare datasource - datasource = FileDatasource("data/accelerometer.csv", "data/gps.csv") + datasource = FileDatasource("data/accelerometer.csv", "data/gps.csv", "data/parking.csv") # Infinity publish data publish(client, config.MQTT_TOPIC, datasource) if __name__ == "__main__": - run() \ No newline at end of file + run() diff --git a/agent/src/schema/parking_schema.py b/agent/src/schema/parking_schema.py new file mode 100644 index 0000000..c6ecb1b --- /dev/null +++ b/agent/src/schema/parking_schema.py @@ -0,0 +1,8 @@ +from marshmallow import Schema, fields + +from agent.src.schema.gps_schema import GpsSchema + + +class ParkingSchema(Schema): + gps = fields.Nested(GpsSchema) + empty_count = fields.Int() -- 2.49.1 From af94c007a27adc7ea59e828bd42700114795e2fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=86=D0=9C-24=20=D0=92=D0=BB=D0=B0=D0=B4=D0=B8=D1=81?= =?UTF-8?q?=D0=BB=D0=B0=D0=B2=20=D0=9A=D0=BE=D0=B2=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=BA=D0=BE?= Date: Thu, 26 Feb 2026 10:54:34 +0000 Subject: [PATCH 7/8] fixes in imports and schemas --- agent/src/data/parking.csv | 22 ++++++++++++++++++++++ agent/src/domain/aggregated_data.py | 6 +++--- agent/src/domain/parking.py | 2 +- agent/src/file_datasource.py | 2 +- agent/src/schema/aggregated_data_schema.py | 2 ++ agent/src/schema/parking_schema.py | 2 +- 6 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 agent/src/data/parking.csv diff --git a/agent/src/data/parking.csv b/agent/src/data/parking.csv new file mode 100644 index 0000000..c006344 --- /dev/null +++ b/agent/src/data/parking.csv @@ -0,0 +1,22 @@ +longitude,latitude,empty_count +50.450386085935094,30.524547100067142,10 +50.450386085935094,30.524547100067142,11 +50.450386085935094,30.524547100067142,13 +50.450386085935094,30.524547100067142,15 +50.450386085935094,30.524547100067142,7 +50.450386085935094,30.524547100067142,9 +50.450386085935094,30.524547100067142,4 +50.450386085935094,30.524547100067142,0 +50.450386085935094,30.524547100067142,0 +50.450386085935094,30.524547100067142,3 +50.450386085935094,30.524547100067142,4 +50.450069433207545,30.52406822530458,16 +50.450069433207545,30.52406822530458,20 +50.450069433207545,30.52406822530458,25 +50.450069433207545,30.52406822530458,30 +50.450069433207545,30.52406822530458,29 +50.450069433207545,30.52406822530458,12 +50.450069433207545,30.52406822530458,10 +50.450069433207545,30.52406822530458,14 +50.450069433207545,30.52406822530458,3 +50.450069433207545,30.52406822530458,2 diff --git a/agent/src/domain/aggregated_data.py b/agent/src/domain/aggregated_data.py index 22de049..8d76335 100644 --- a/agent/src/domain/aggregated_data.py +++ b/agent/src/domain/aggregated_data.py @@ -2,9 +2,9 @@ from dataclasses import dataclass from datetime import datetime -from agent.src.domain.accelerometer import Accelerometer -from agent.src.domain.gps import Gps -from agent.src.domain.parking import Parking +from domain.accelerometer import Accelerometer +from domain.gps import Gps +from domain.parking import Parking @dataclass diff --git a/agent/src/domain/parking.py b/agent/src/domain/parking.py index 1f84043..9783148 100644 --- a/agent/src/domain/parking.py +++ b/agent/src/domain/parking.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -from agent.src.domain.gps import Gps +from domain.gps import Gps @dataclass diff --git a/agent/src/file_datasource.py b/agent/src/file_datasource.py index de6d8a9..0425ca6 100644 --- a/agent/src/file_datasource.py +++ b/agent/src/file_datasource.py @@ -192,5 +192,5 @@ class FileDatasource: return Parking( gps=Gps(longitude=lon, latitude=lat), - empty_count=empty_count, + empty_count=empty_count ) diff --git a/agent/src/schema/aggregated_data_schema.py b/agent/src/schema/aggregated_data_schema.py index e6fa482..74fe497 100644 --- a/agent/src/schema/aggregated_data_schema.py +++ b/agent/src/schema/aggregated_data_schema.py @@ -1,10 +1,12 @@ from marshmallow import Schema, fields from schema.accelerometer_schema import AccelerometerSchema from schema.gps_schema import GpsSchema +from schema.parking_schema import ParkingSchema class AggregatedDataSchema(Schema): accelerometer = fields.Nested(AccelerometerSchema) gps = fields.Nested(GpsSchema) + parking = fields.Nested(ParkingSchema) timestamp = fields.DateTime("iso") user_id = fields.Int() diff --git a/agent/src/schema/parking_schema.py b/agent/src/schema/parking_schema.py index c6ecb1b..eabf208 100644 --- a/agent/src/schema/parking_schema.py +++ b/agent/src/schema/parking_schema.py @@ -1,6 +1,6 @@ from marshmallow import Schema, fields -from agent.src.schema.gps_schema import GpsSchema +from schema.gps_schema import GpsSchema class ParkingSchema(Schema): -- 2.49.1 From 185b0aae58dbd450adb1ca52724a8aa442ecae5c Mon Sep 17 00:00:00 2001 From: anastasia-sl Date: Thu, 26 Feb 2026 14:55:51 +0200 Subject: [PATCH 8/8] implemented update and delete endpoints --- store/main.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/store/main.py b/store/main.py index 272a646..4b20dfe 100644 --- a/store/main.py +++ b/store/main.py @@ -152,7 +152,41 @@ def list_processed_agent_data(): ) def update_processed_agent_data(processed_agent_data_id: int, data: ProcessedAgentData): # Update data - pass + session = SessionLocal() + + try: + query = select(processed_agent_data).where( + processed_agent_data.c.id == processed_agent_data_id + ) + result = session.execute(query).fetchone() + + if not result: + raise HTTPException(status_code=404, detail="Data not found") + + update_query = ( + processed_agent_data.update() + .where(processed_agent_data.c.id == processed_agent_data_id) + .values( + road_state=data.road_state, + user_id=data.agent_data.user_id, + x=data.agent_data.accelerometer.x, + y=data.agent_data.accelerometer.y, + z=data.agent_data.accelerometer.z, + latitude=data.agent_data.gps.latitude, + longitude=data.agent_data.gps.longitude, + timestamp=data.agent_data.timestamp, + ) + ) + + session.execute(update_query) + session.commit() + + updated_result = session.execute(query).fetchone() + + return ProcessedAgentDataInDB(**updated_result._mapping) + + finally: + session.close() @app.delete( @@ -161,8 +195,28 @@ def update_processed_agent_data(processed_agent_data_id: int, data: ProcessedAge ) def delete_processed_agent_data(processed_agent_data_id: int): # Delete by id - pass + session = SessionLocal() + try: + query = select(processed_agent_data).where( + processed_agent_data.c.id == processed_agent_data_id + ) + result = session.execute(query).fetchone() + + if not result: + raise HTTPException(status_code=404, detail="Data not found") + + delete_query = processed_agent_data.delete().where( + processed_agent_data.c.id == processed_agent_data_id + ) + + session.execute(delete_query) + session.commit() + + return ProcessedAgentDataInDB(**result._mapping) + + finally: + session.close() if __name__ == "__main__": import uvicorn -- 2.49.1