feat(api): add backend

routes and WebSockets
This commit is contained in:
2025-12-26 18:19:06 +03:00
parent cfec8d0ff6
commit 1b864228d4
28 changed files with 631 additions and 2 deletions

View File

@@ -0,0 +1,16 @@
from datetime import datetime
from app.repositories.audio_repository import AudioRepository
from app.schemas.audio import AudioPoint
class AudioService:
def __init__(self, repo: AudioRepository):
self.repo = repo
async def latest(self, limit: int) -> list[AudioPoint]:
rows = await self.repo.latest(limit)
return [AudioPoint.model_validate(r, from_attributes=True) for r in rows]
async def range(self, time_from: datetime, time_to: datetime) -> list[AudioPoint]:
rows = await self.repo.range(time_from, time_to)
return [AudioPoint.model_validate(r, from_attributes=True) for r in rows]

View File

@@ -0,0 +1,54 @@
from __future__ import annotations
from datetime import datetime
from app.repositories.audio_repository import AudioRepository
from app.schemas.events import LoudEvent
class EventsService:
def __init__(self, repo: AudioRepository):
self.repo = repo
async def loud_events(
self,
threshold: float,
time_from: datetime | None,
time_to: datetime | None,
max_gap_sec: float = 1.0,
) -> list[LoudEvent]:
samples = await self.repo.loud_samples(threshold, time_from, time_to)
if not samples:
return []
events: list[LoudEvent] = []
start = samples[0].time
end = samples[0].time
max_db = samples[0].rms_db
freq = samples[0].frequency_hz
for s in samples[1:]:
gap = (s.time - end).total_seconds()
if gap <= max_gap_sec:
end = s.time
if s.rms_db > max_db:
max_db = s.rms_db
else:
events.append(
LoudEvent(
time=start,
rms_db=round(float(max_db), 2),
frequency_hz=int(freq),
duration_sec=round((end - start).total_seconds(), 2),
)
)
start, end, max_db, freq = s.time, s.time, s.rms_db, s.frequency_hz
events.append(
LoudEvent(
time=start,
rms_db=round(float(max_db), 2),
frequency_hz=int(freq),
duration_sec=round((end - start).total_seconds(), 2),
)
)
return events

View File

@@ -0,0 +1,35 @@
from datetime import datetime, timedelta
from app.repositories.audio_repository import AudioRepository
from app.schemas.stats import StatsSummary
_PERIODS = {
"10s": timedelta(seconds=10),
"1m": timedelta(minutes=1),
"1h": timedelta(hours=1),
"6h": timedelta(hours=6),
"24h": timedelta(hours=24),
"7d": timedelta(days=7),
"30d": timedelta(days=30),
}
class StatsService:
def __init__(self, repo: AudioRepository):
self.repo = repo
async def summary(self, period: str) -> StatsSummary:
if period not in _PERIODS:
raise ValueError(f"Unsupported period: {period}")
since = datetime.utcnow() - _PERIODS[period]
raw = await self.repo.summary_since(since)
total = raw["total_count"]
silence_percent = (raw["silence_count"] / total * 100.0) if total else 0.0
return StatsSummary(
avg_db=round(raw["avg_db"], 2),
max_db=round(raw["max_db"], 2),
dominant_freq=raw["dominant_freq"],
silence_percent=round(silence_percent, 2),
)