ADD: Measurements
This commit is contained in:
parent
078fed7155
commit
6518faaff1
3
db.py
3
db.py
@ -1,6 +1,9 @@
|
|||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlmodel import SQLModel, Session
|
from sqlmodel import SQLModel, Session
|
||||||
|
|
||||||
|
from models.station import Station
|
||||||
|
from models.measurement import IndoorMeasurement, OutdoorMeasurement
|
||||||
|
|
||||||
engine = create_engine("sqlite:///database.db")
|
engine = create_engine("sqlite:///database.db")
|
||||||
SQLModel.metadata.create_all(engine)
|
SQLModel.metadata.create_all(engine)
|
||||||
|
|
||||||
|
|||||||
19
main.py
19
main.py
@ -1,12 +1,14 @@
|
|||||||
import functools
|
import functools
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from fastapi import FastAPI, Depends
|
from fastapi import FastAPI, Depends, Query
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
|
|
||||||
from db import get_session
|
from db import get_session
|
||||||
|
from models.measurement import IndoorMeasurementCreateRequest, MeasurementListResponse
|
||||||
from models.station import StationCreateRequest, StationCreateResponse, Station, StationListResponse, \
|
from models.station import StationCreateRequest, StationCreateResponse, Station, StationListResponse, \
|
||||||
StationUpdateResponse, StationUpdateRequest
|
StationUpdateResponse, StationUpdateRequest
|
||||||
from services import stationService
|
from services import stationService, measurementService
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
@ -30,6 +32,19 @@ async def update_station(station_data: StationUpdateRequest, session: Session =
|
|||||||
async def delete_station(station_id: int, session: Session = Depends(get_session)):
|
async def delete_station(station_id: int, session: Session = Depends(get_session)):
|
||||||
stationService.delete_station(session, station_id)
|
stationService.delete_station(session, station_id)
|
||||||
|
|
||||||
|
@app.post("/measurements/indoor", status_code=204)
|
||||||
|
async def create_indoor_measurement(data: IndoorMeasurementCreateRequest, session: Session = Depends(get_session)):
|
||||||
|
measurementService.push_indoor_measurement(session, data)
|
||||||
|
|
||||||
|
@app.get("/measurements", response_model=MeasurementListResponse, status_code=200)
|
||||||
|
async def get_measurements(
|
||||||
|
station_ids: list[int] | None = Query(default=None),
|
||||||
|
indoor: bool | None = Query(default=None),
|
||||||
|
from_timestamp: datetime | None = None,
|
||||||
|
to_timestamp: datetime | None = None,
|
||||||
|
limit: int = 100,
|
||||||
|
session: Session = Depends(get_session)):
|
||||||
|
return measurementService.get_measurements(session, station_ids, indoor, from_timestamp, to_timestamp, limit)
|
||||||
@app.get("/hello/{name}")
|
@app.get("/hello/{name}")
|
||||||
async def say_hello(name: str):
|
async def say_hello(name: str):
|
||||||
return {"message": f"Hello {name}"}
|
return {"message": f"Hello {name}"}
|
||||||
|
|||||||
52
models/measurement.py
Normal file
52
models/measurement.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from sqlmodel import SQLModel, Field
|
||||||
|
|
||||||
|
from models.station import StationListResponse
|
||||||
|
|
||||||
|
|
||||||
|
def utc_now() -> datetime:
|
||||||
|
return datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
|
class IndoorMeasurement(SQLModel, table=True):
|
||||||
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
station_id: int = Field(foreign_key="station.id")
|
||||||
|
timestamp: datetime = Field(default_factory=utc_now)
|
||||||
|
temperature: float | None= Field(default=None)
|
||||||
|
humidity: float| None = Field(default=None)
|
||||||
|
|
||||||
|
class OutdoorMeasurement(SQLModel, table=True):
|
||||||
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
station_id : int= Field(foreign_key="station.id")
|
||||||
|
temperature: float| None = Field(default=None)
|
||||||
|
humidity: float | None = Field(default=None)
|
||||||
|
pressure: float| None = Field(default=None)
|
||||||
|
timestamp: datetime = Field(default_factory=utc_now)
|
||||||
|
|
||||||
|
class IndoorMeasurementCreateRequest(SQLModel):
|
||||||
|
mac: str
|
||||||
|
temperature: float
|
||||||
|
humidity: float
|
||||||
|
|
||||||
|
|
||||||
|
class OutdoorMeasurementCreateRequest(SQLModel):
|
||||||
|
mac: str
|
||||||
|
temperature: float
|
||||||
|
humidity: float | None = None
|
||||||
|
pressure: float | None = None
|
||||||
|
|
||||||
|
class MeasurementResponse(SQLModel):
|
||||||
|
temperature: float
|
||||||
|
humidity: float | None = None
|
||||||
|
pressure: float | None = None
|
||||||
|
timestamp: datetime
|
||||||
|
|
||||||
|
class StationMeasurementResponse(SQLModel):
|
||||||
|
station: StationListResponse
|
||||||
|
measurements: list[MeasurementResponse]
|
||||||
|
indoor: bool
|
||||||
|
|
||||||
|
class MeasurementListResponse(SQLModel):
|
||||||
|
stations: list[StationMeasurementResponse]
|
||||||
@ -1,14 +1,17 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from sqlmodel import SQLModel, Field
|
from sqlmodel import SQLModel, Field
|
||||||
|
|
||||||
|
def utc_now() -> datetime:
|
||||||
|
return datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
class Station(SQLModel, table=True):
|
class Station(SQLModel, table=True):
|
||||||
id: int = Field(default=None, primary_key=True)
|
id: int = Field(default=None, primary_key=True)
|
||||||
mac: str = Field(unique=True)
|
mac: str = Field(unique=True)
|
||||||
name: Optional[str] = Field(default=None)
|
name: Optional[str] = Field(default=None)
|
||||||
created_at: datetime = Field(default_factory=datetime.now)
|
created_at: datetime = Field(default_factory=utc_now)
|
||||||
|
|
||||||
class StationCreateRequest(SQLModel):
|
class StationCreateRequest(SQLModel):
|
||||||
name: str
|
name: str
|
||||||
|
|||||||
116
services/measurementService.py
Normal file
116
services/measurementService.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from sqlmodel import Session, select
|
||||||
|
|
||||||
|
from models.measurement import IndoorMeasurement, IndoorMeasurementCreateRequest, OutdoorMeasurementCreateRequest, \
|
||||||
|
OutdoorMeasurement, MeasurementListResponse, StationMeasurementResponse, MeasurementResponse
|
||||||
|
from models.station import Station, StationCreateRequest, StationListResponse
|
||||||
|
from services import stationService
|
||||||
|
from coolname import generate_slug
|
||||||
|
|
||||||
|
|
||||||
|
def push_indoor_measurement(session: Session, raw_measurement: IndoorMeasurementCreateRequest):
|
||||||
|
statement = select(Station).where(Station.mac == raw_measurement.mac)
|
||||||
|
station = session.exec(statement).first()
|
||||||
|
|
||||||
|
if not station:
|
||||||
|
station = stationService.create_station(session, StationCreateRequest(
|
||||||
|
mac=raw_measurement.mac,
|
||||||
|
name=generate_slug(2)
|
||||||
|
))
|
||||||
|
|
||||||
|
measurement = IndoorMeasurement(
|
||||||
|
station_id=station.id,
|
||||||
|
temperature=raw_measurement.temperature,
|
||||||
|
humidity=raw_measurement.humidity
|
||||||
|
)
|
||||||
|
|
||||||
|
session.add(IndoorMeasurement.model_validate(measurement))
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
def push_outdoor_measurement(session: Session, raw_measurement: OutdoorMeasurementCreateRequest):
|
||||||
|
statement = select(Station).where(Station.mac == raw_measurement.mac)
|
||||||
|
station = session.exec(statement).first()
|
||||||
|
|
||||||
|
if not station:
|
||||||
|
station = stationService.create_station(session, StationCreateRequest(
|
||||||
|
mac=raw_measurement.mac,
|
||||||
|
name=generate_slug(2)
|
||||||
|
))
|
||||||
|
|
||||||
|
measurement = OutdoorMeasurement(
|
||||||
|
station_id=station.id,
|
||||||
|
temperature=raw_measurement.temperature,
|
||||||
|
humidity=raw_measurement.humidity,
|
||||||
|
pressure=raw_measurement.pressure
|
||||||
|
)
|
||||||
|
|
||||||
|
session.add(OutdoorMeasurement.model_validate(measurement))
|
||||||
|
session.commit()
|
||||||
|
from typing import Type, Union
|
||||||
|
|
||||||
|
def _query_measurements(
|
||||||
|
session: Session,
|
||||||
|
model: Type[Union[IndoorMeasurement, OutdoorMeasurement]],
|
||||||
|
indoor: bool,
|
||||||
|
station_ids: list[int] | None,
|
||||||
|
from_timestamp: datetime | None = None,
|
||||||
|
to_timestamp: datetime | None = None,
|
||||||
|
limit: int | None = None
|
||||||
|
) -> list[StationMeasurementResponse]:
|
||||||
|
statement = select(model)
|
||||||
|
if station_ids:
|
||||||
|
statement = statement.where(model.station_id.in_(station_ids))
|
||||||
|
if from_timestamp:
|
||||||
|
statement = statement.where(model.timestamp >= from_timestamp)
|
||||||
|
if to_timestamp:
|
||||||
|
statement = statement.where(model.timestamp <= to_timestamp)
|
||||||
|
statement = statement.order_by(model.timestamp.desc()).limit(limit)
|
||||||
|
results = session.exec(statement).all()
|
||||||
|
|
||||||
|
grouped: dict[int, list[MeasurementResponse]] = {}
|
||||||
|
for m in results:
|
||||||
|
if m.station_id not in grouped:
|
||||||
|
grouped[m.station_id] = []
|
||||||
|
grouped[m.station_id].append(MeasurementResponse.model_validate(m))
|
||||||
|
|
||||||
|
return [
|
||||||
|
StationMeasurementResponse(
|
||||||
|
station=StationListResponse.model_validate(session.get(Station, station_id)),
|
||||||
|
measurements=measurements,
|
||||||
|
indoor=indoor
|
||||||
|
)
|
||||||
|
for station_id, measurements in grouped.items()
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_indoor_measurements(session: Session, station_ids: list[int] | None, from_timestamp: datetime | None = None, to_timestamp: datetime | None = None, limit: int | None = None) -> list[StationMeasurementResponse]:
|
||||||
|
return _query_measurements(session, IndoorMeasurement, True, station_ids, from_timestamp, to_timestamp, limit)
|
||||||
|
|
||||||
|
def get_outdoor_measurements(session: Session, station_ids: list[int] | None, from_timestamp: datetime | None = None, to_timestamp: datetime | None = None, limit: int | None = None) -> list[StationMeasurementResponse]:
|
||||||
|
return _query_measurements(session, OutdoorMeasurement, False, station_ids, from_timestamp, to_timestamp, limit)
|
||||||
|
|
||||||
|
def get_measurements(session: Session, station_ids: list[int] | None, indoor: bool | None, from_timestamp: datetime | None = None, to_timestamp: datetime | None = None, limit: int | None = None):
|
||||||
|
if indoor is None:
|
||||||
|
indoor_results = get_indoor_measurements(session, station_ids, from_timestamp, to_timestamp, limit)
|
||||||
|
outdoor_results = get_outdoor_measurements(session, station_ids, from_timestamp, to_timestamp, limit)
|
||||||
|
return MeasurementListResponse(
|
||||||
|
stations=[
|
||||||
|
*[StationMeasurementResponse(station=indoor_result.station, measurements=indoor_result.measurements, indoor=True) for indoor_result in indoor_results],
|
||||||
|
*[StationMeasurementResponse(station=outdoor_result.station, measurements=outdoor_result.measurements, indoor=False) for outdoor_result in outdoor_results],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if indoor:
|
||||||
|
indoor_results = get_indoor_measurements(session, station_ids, from_timestamp, to_timestamp, limit)
|
||||||
|
return MeasurementListResponse(
|
||||||
|
stations=[
|
||||||
|
*[StationMeasurementResponse(station=indoor_result.station, measurements=indoor_result.measurements, indoor=True) for indoor_result in indoor_results],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
outdoor_results = get_outdoor_measurements(session, station_ids, from_timestamp, to_timestamp, limit)
|
||||||
|
return MeasurementListResponse(
|
||||||
|
stations=[
|
||||||
|
*[StationMeasurementResponse(station=outdoor_result.station, measurements=outdoor_result.measurements, indoor=False) for outdoor_result in outdoor_results],
|
||||||
|
]
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue
Block a user