| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- """API routes for printer heater (nozzle / bed / chamber) sensor history."""
- from datetime import datetime, timedelta, timezone
- from fastapi import APIRouter, Depends, Query
- from pydantic import BaseModel
- from sqlalchemy import and_, func, select
- from sqlalchemy.ext.asyncio import AsyncSession
- from backend.app.core.auth import RequirePermissionIfAuthEnabled
- from backend.app.core.database import get_db
- from backend.app.core.permissions import Permission
- from backend.app.models.printer_sensor_history import PrinterSensorHistory
- from backend.app.models.user import User
- router = APIRouter(prefix="/printer-sensor-history", tags=["printer-sensor-history"])
- VALID_KINDS = {"nozzle", "nozzle_2", "bed", "chamber"}
- class HeaterHistoryPoint(BaseModel):
- recorded_at: datetime
- value: float | None
- target: float | None
- class HeaterSeries(BaseModel):
- sensor_kind: str
- data: list[HeaterHistoryPoint]
- min_value: float | None
- max_value: float | None
- avg_value: float | None
- class PrinterSensorHistoryResponse(BaseModel):
- printer_id: int
- series: list[HeaterSeries]
- @router.get("/{printer_id}", response_model=PrinterSensorHistoryResponse)
- async def get_printer_sensor_history(
- printer_id: int,
- hours: int = Query(default=24, ge=1, le=168, description="Hours of history (1-168)"),
- kinds: str | None = Query(
- default=None,
- description="Comma-separated list of sensor kinds (nozzle, nozzle_2, bed, chamber). All by default.",
- ),
- db: AsyncSession = Depends(get_db),
- _: User | None = RequirePermissionIfAuthEnabled(Permission.PRINTER_SENSOR_HISTORY_READ),
- ):
- """Return per-sensor heater history for a printer."""
- since = datetime.now(timezone.utc) - timedelta(hours=hours)
- if kinds:
- requested = {k.strip() for k in kinds.split(",") if k.strip()}
- kinds_to_fetch = sorted(requested & VALID_KINDS)
- else:
- kinds_to_fetch = sorted(VALID_KINDS)
- series_out: list[HeaterSeries] = []
- for kind in kinds_to_fetch:
- result = await db.execute(
- select(PrinterSensorHistory)
- .where(
- and_(
- PrinterSensorHistory.printer_id == printer_id,
- PrinterSensorHistory.sensor_kind == kind,
- PrinterSensorHistory.recorded_at >= since,
- )
- )
- .order_by(PrinterSensorHistory.recorded_at)
- )
- records = result.scalars().all()
- stats_result = await db.execute(
- select(
- func.min(PrinterSensorHistory.value).label("min_v"),
- func.max(PrinterSensorHistory.value).label("max_v"),
- func.avg(PrinterSensorHistory.value).label("avg_v"),
- ).where(
- and_(
- PrinterSensorHistory.printer_id == printer_id,
- PrinterSensorHistory.sensor_kind == kind,
- PrinterSensorHistory.recorded_at >= since,
- )
- )
- )
- stats = stats_result.one()
- series_out.append(
- HeaterSeries(
- sensor_kind=kind,
- data=[
- HeaterHistoryPoint(
- recorded_at=r.recorded_at,
- value=r.value,
- target=r.target,
- )
- for r in records
- ],
- min_value=stats.min_v,
- max_value=stats.max_v,
- avg_value=round(stats.avg_v, 1) if stats.avg_v is not None else None,
- )
- )
- return PrinterSensorHistoryResponse(printer_id=printer_id, series=series_out)
- @router.delete("/{printer_id}")
- async def delete_old_history(
- printer_id: int,
- days: int = Query(default=30, ge=1, le=365, description="Delete data older than X days"),
- db: AsyncSession = Depends(get_db),
- _: User | None = RequirePermissionIfAuthEnabled(Permission.PRINTER_SENSOR_HISTORY_READ),
- ):
- """Delete old printer sensor history for a printer."""
- cutoff = datetime.now(timezone.utc) - timedelta(days=days)
- result = await db.execute(
- select(func.count(PrinterSensorHistory.id)).where(
- and_(
- PrinterSensorHistory.printer_id == printer_id,
- PrinterSensorHistory.recorded_at < cutoff,
- )
- )
- )
- count = result.scalar()
- await db.execute(
- PrinterSensorHistory.__table__.delete().where(
- and_(
- PrinterSensorHistory.printer_id == printer_id,
- PrinterSensorHistory.recorded_at < cutoff,
- )
- )
- )
- await db.commit()
- return {"deleted": count, "message": f"Deleted {count} records older than {days} days"}
|