Compare commits

..

21 Commits

Author SHA1 Message Date
f5486f0469 [P] Add database update after marker deletion.
All checks were successful
Component testing / Hub testing (push) Successful in 23s
Component testing / Store testing (push) Successful in 34s
Component testing / Integration smoke testing (push) Successful in 2m54s
2026-03-27 16:41:39 +01:00
51fca8436a [P] Code cleanup. 2026-03-27 16:41:39 +01:00
c5f6fbf7af [L4] Fix relative module imports 2026-03-27 16:41:39 +01:00
SimonSanich
7b02884b10 Add test for new records in DB 2026-03-27 16:41:38 +01:00
6850454711 [P] Fix several crashes triggered in one call sequence 2026-03-27 16:40:21 +01:00
abbc703b1b [P] Improve marker removal architecture to allow for future extensions 2026-03-27 16:40:21 +01:00
79a58f1737 [P] Add touch handling (untested). 2026-03-27 16:40:21 +01:00
39fca87025 Correct pothole deletion function 2026-03-27 16:40:21 +01:00
3bc39e011d Use lat lon validation 2026-03-27 16:40:21 +01:00
b2780c1c6b Add lat lon to pothole mapper 2026-03-27 16:40:21 +01:00
1e607729e2 Add type hints 2026-03-27 16:40:21 +01:00
8dc8a1f4ba [P] Add bump remove function 2026-03-27 16:40:21 +01:00
a578a96347 Merge branch 'github/dev' into dev
All checks were successful
Component testing / Hub testing (push) Successful in 24s
Component testing / Store testing (push) Successful in 35s
Component testing / Integration smoke testing (push) Successful in 2m51s
2026-03-27 16:28:41 +02:00
VladiusVostokus
5749dd825f Merge pull request #30 from Rhinemann/lab4/gryshaiev-SCRUM-82-test-new-records-in-db
Add test for new records in DB
2026-03-27 16:14:51 +02:00
86c6c14502 [L4] Correct road state string 2026-03-27 16:03:41 +02:00
561004a49c [L4] Fix relative module imports 2026-03-27 16:03:38 +02:00
SimonSanich
7d75a15de1 Add test for new records in DB 2026-03-27 15:14:17 +02:00
ІМ-24 Владислав Коваленко
eca98c4469 fix: actually use session variable in websocket GET
All checks were successful
Component testing / Hub testing (push) Successful in 23s
Component testing / Store testing (push) Successful in 24s
Component testing / Integration smoke testing (push) Successful in 2m42s
2026-03-26 16:52:00 +00:00
ІМ-24 Владислав Коваленко
c553384ce7 fix: add session rollback in DELETE
All checks were successful
Component testing / Hub testing (push) Successful in 17s
Component testing / Store testing (push) Successful in 28s
Component testing / Integration smoke testing (push) Successful in 2m44s
2026-03-26 15:59:57 +00:00
ІМ-24 Владислав Коваленко
1bf5687505 fix: add session rollback in PUT 2026-03-26 15:56:34 +00:00
ІМ-24 Владислав Коваленко
0d9dcef994 fix: close connection after GET in websocket 2026-03-26 15:52:33 +00:00
4 changed files with 147 additions and 21 deletions

View File

@@ -4,6 +4,8 @@ from datetime import datetime
import websockets import websockets
from kivy import Logger from kivy import Logger
from pydantic import BaseModel, field_validator from pydantic import BaseModel, field_validator
from websockets.asyncio.connection import Connection
from config import STORE_HOST, STORE_PORT from config import STORE_HOST, STORE_PORT
@@ -33,10 +35,12 @@ class ProcessedAgentData(BaseModel):
class Datasource: class Datasource:
def __init__(self, user_id: int): def __init__(self, user_id: int):
self.websocket: Connection | None = None
self.index = 0 self.index = 0
self.user_id = user_id self.user_id = user_id
self.connection_status = None self.connection_status = None
self._new_points = [] self._new_points = []
self._active_markers = []
asyncio.ensure_future(self.connect_to_server()) asyncio.ensure_future(self.connect_to_server())
def get_new_points(self): def get_new_points(self):
@@ -51,6 +55,7 @@ class Datasource:
Logger.debug("CONNECT TO SERVER") Logger.debug("CONNECT TO SERVER")
async with websockets.connect(uri) as websocket: async with websockets.connect(uri) as websocket:
self.connection_status = "Connected" self.connection_status = "Connected"
self.websocket = websocket
try: try:
while True: while True:
data = await websocket.recv() data = await websocket.recv()
@@ -60,6 +65,26 @@ class Datasource:
self.connection_status = "Disconnected" self.connection_status = "Disconnected"
Logger.debug("SERVER DISCONNECT") Logger.debug("SERVER DISCONNECT")
async def update_db_records(self):
if self.websocket:
data = json.dumps({"id": self.user_id})
await self.websocket.send(data, True)
def map_lat_lon_to_processed_agent_data(self, lat: float, lon: float) -> ProcessedAgentData | None:
distances = tuple((abs(lon - marker.latitude) ** 2 + abs(lat - marker.longitude) ** 2) ** 0.5 for marker in
self._active_markers)
if len(distances) == 0:
return None
min_distance = min(distances)
marker = self._active_markers[distances.index(min_distance)]
if min_distance < 0.005:
return marker
else:
return None
def handle_received_data(self, data): def handle_received_data(self, data):
# Update your UI or perform actions with received data here # Update your UI or perform actions with received data here
Logger.debug(f"Received data: {data}") Logger.debug(f"Received data: {data}")
@@ -70,6 +95,9 @@ class Datasource:
], ],
key=lambda v: v.timestamp, key=lambda v: v.timestamp,
) )
self._active_markers += [i for i in processed_agent_data_list if i.road_state != 'normal']
new_points = [ new_points = [
( (
processed_agent_data.longitude, processed_agent_data.longitude,

View File

@@ -15,18 +15,31 @@ line_layer_colors = [
[1, 0, 1, 1], [1, 0, 1, 1],
] ]
def get_lat_lon(point: dict[str, float] | list[float] | tuple[float, float]) -> tuple[float, float] | None:
if isinstance(point, dict):
lat = point.get("lat")
lon = point.get("lon")
else:
lat, lon = point
if lat is None or lon is None:
return None
return lat, lon
class MapViewApp(App): class MapViewApp(App):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.mapview = None self.mapview: MapView | None = None
self.datasource = Datasource(user_id=1) self.datasource = Datasource(user_id=1)
self.line_layers = dict() self.line_layers = dict()
self.car_markers = dict() self.car_markers = dict()
# додати необхідні змінні # додати необхідні змінні
self.bump_markers = [] self.bump_markers: list[MapMarker] = []
self.pothole_markers = [] self.pothole_markers: list[MapMarker] = []
def on_start(self): def on_start(self):
""" """
@@ -50,7 +63,7 @@ class MapViewApp(App):
# Оновлює лінію маршрута # Оновлює лінію маршрута
if user_id not in self.line_layers: if user_id not in self.line_layers:
self.line_layers[user_id] = LineMapLayer(color = line_layer_colors[user_id % len(line_layer_colors)]) self.line_layers[user_id] = LineMapLayer(color=line_layer_colors[user_id % len(line_layer_colors)])
self.mapview.add_layer(self.line_layers[user_id]) self.mapview.add_layer(self.line_layers[user_id])
self.line_layers[user_id].add_point((lat, lon)) self.line_layers[user_id].add_point((lat, lon))
@@ -91,13 +104,16 @@ class MapViewApp(App):
if user_id == config.TRACK_ID: if user_id == config.TRACK_ID:
self.mapview.center_on(lat, lon) self.mapview.center_on(lat, lon)
def set_pothole_marker(self, point): def map_lat_lon_to_marker(self, lat: float, lon: float) -> MapMarker | None:
if isinstance(point, dict): flt = filter(lambda marker: lon == marker.lat and lat == marker.lon, self.pothole_markers + self.bump_markers)
lat = point.get("lat")
lon = point.get("lon")
else:
lat, lon = point
try:
return next(flt)
except StopIteration:
return None
def set_pothole_marker(self, point):
lat, lon = get_lat_lon(point)
if lat is None or lon is None: if lat is None or lon is None:
return return
@@ -111,12 +127,7 @@ class MapViewApp(App):
self.pothole_markers.append(marker) self.pothole_markers.append(marker)
def set_bump_marker(self, point): def set_bump_marker(self, point):
if isinstance(point, dict): lat, lon = get_lat_lon(point)
lat = point.get("lat")
lon = point.get("lon")
else:
lat, lon = point
if lat is None or lon is None: if lat is None or lon is None:
return return
@@ -129,6 +140,35 @@ class MapViewApp(App):
self.mapview.add_marker(marker) self.mapview.add_marker(marker)
self.bump_markers.append(marker) self.bump_markers.append(marker)
def delete_pothole_marker(self, point):
lat, lon = get_lat_lon(point)
if lat is None or lon is None:
return
clicked_marker_data = self.datasource.map_lat_lon_to_processed_agent_data(lat, lon)
if not clicked_marker_data:
return
clicked_marker = self.map_lat_lon_to_marker(clicked_marker_data.latitude, clicked_marker_data.longitude)
if clicked_marker is None:
return
self.mapview.remove_marker(clicked_marker)
if clicked_marker in self.pothole_markers:
self.pothole_markers.pop(self.pothole_markers.index(clicked_marker))
elif clicked_marker in self.bump_markers:
self.bump_markers.pop(self.bump_markers.index(clicked_marker))
self.datasource.update_db_records()
def on_touch_down(self, widget, touch):
if touch.button == "right":
coordinate = self.mapview.get_latlon_at(touch.x, touch.y, self.mapview.zoom)
self.delete_pothole_marker(coordinate)
return True
def build(self): def build(self):
""" """
@@ -141,6 +181,8 @@ class MapViewApp(App):
lon=30.5234 lon=30.5234
) )
self.mapview.bind(on_touch_down=self.on_touch_down)
return self.mapview return self.mapview

View File

@@ -48,7 +48,9 @@ async def websocket_endpoint(websocket: WebSocket, user_id: int):
try: try:
# send already available data # send already available data
r = processed_agent_data.select().order_by(processed_agent_data.c.timestamp) r = processed_agent_data.select().order_by(processed_agent_data.c.timestamp)
stored_data = SessionLocal().execute(r).fetchall() session = SessionLocal()
stored_data = session.execute(r).fetchall()
session.close()
jsonable_data = [{c.name: getattr(i, c.name) for c in processed_agent_data.columns} for i in stored_data] jsonable_data = [{c.name: getattr(i, c.name) for c in processed_agent_data.columns} for i in stored_data]
for i in jsonable_data: for i in jsonable_data:
@@ -198,9 +200,13 @@ def update_processed_agent_data(processed_agent_data_id: int, data: ProcessedAge
session.commit() session.commit()
updated_result = session.execute(query).fetchone() updated_result = session.execute(query).fetchone()
return ProcessedAgentDataInDB(**updated_result._mapping) return ProcessedAgentDataInDB(**updated_result._mapping)
except Exception as err:
session.rollback()
print(f"Database error: {err}")
raise HTTPException(status_code=500, detail="Internal Server Error")
finally: finally:
session.close() session.close()
@@ -231,6 +237,11 @@ def delete_processed_agent_data(processed_agent_data_id: int):
return ProcessedAgentDataInDB(**result._mapping) return ProcessedAgentDataInDB(**result._mapping)
except Exception as err:
session.rollback()
print(f"Database error: {err}")
raise HTTPException(status_code=500, detail="Internal Server Error")
finally: finally:
session.close() session.close()

45
store/test_db.py Normal file
View File

@@ -0,0 +1,45 @@
from fastapi.testclient import TestClient
from main import app, processed_agent_data
from database import SessionLocal
from sqlalchemy import select
from datetime import datetime
client = TestClient(app)
def test_create_processed_agent_data():
db = SessionLocal()
before = db.execute(select(processed_agent_data)).fetchall()
before_count = len(before)
payload = {
"user_id": 123,
"data": [
{
"road_state": "good",
"agent_data": {
"user_id": 999,
"accelerometer": {
"x": 1.1,
"y": 2.2,
"z": 3.3
},
"gps": {
"latitude": 50.45,
"longitude": 30.52
},
"timestamp": datetime.now().isoformat()
}
}
]
}
response = client.post("/processed_agent_data/", json=payload)
assert response.status_code == 200
after = db.execute(select(processed_agent_data)).fetchall()
after_count = len(after)
assert after_count == before_count + 1
db.close()