spoolman.py 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. from typing import Annotated
  2. from pydantic import AfterValidator, BaseModel, Field, model_validator
  3. from backend.app.utils.tray_ids import AMS3H_UNIT_ID, EXTERNAL_AMS_ID
  4. def _a_slot_ams_id(ams_id: int) -> int:
  5. """The ams_ids spoolman_slot_assignments' CHECK admits: AMS units 0-7, the
  6. AMS 3H 32, AMS-HT 128-191, the external slot 255. Checked on the request,
  7. so a write to Spoolman that goes with the slot row is never made for a row
  8. the database then refuses (SSA-9)."""
  9. if 0 <= ams_id <= 7 or 128 <= ams_id <= 191 or ams_id in (AMS3H_UNIT_ID, EXTERNAL_AMS_ID):
  10. return ams_id
  11. raise ValueError("ams_id must be 0-7 (AMS), 32 (AMS 3H), 128-191 (AMS-HT) or 255 (external)")
  12. SlotAmsId = Annotated[int, AfterValidator(_a_slot_ams_id)]
  13. # 4-8 only for ams_id 32 / 255: a request with both fields also runs check_slot_tray_id.
  14. SlotTrayId = Annotated[int, Field(ge=0, le=8)]
  15. def check_slot_tray_id(ams_id: int | None, tray_id: int | None) -> None:
  16. """Refuse slots 4-8 except on the AMS 3H (32) and the external inputs (255), as the table's CHECK does."""
  17. if ams_id is None or tray_id is None or tray_id <= 3:
  18. return
  19. if ams_id not in (AMS3H_UNIT_ID, EXTERNAL_AMS_ID):
  20. raise ValueError(
  21. f"tray_id must be 0-3 for ams_id {ams_id}: only the AMS 3H (32) and the external inputs (255) have slots 4-8"
  22. )
  23. class SpoolmanFilamentPatch(BaseModel):
  24. name: str | None = Field(None, min_length=1, max_length=200)
  25. spool_weight: float | None = Field(None, ge=0.0, le=10_000.0)
  26. keep_existing_spools: bool = False
  27. @model_validator(mode="after")
  28. def keep_existing_requires_weight(self) -> "SpoolmanFilamentPatch":
  29. if self.keep_existing_spools and self.spool_weight is None:
  30. raise ValueError("keep_existing_spools=True requires spool_weight to be provided")
  31. return self
  32. class SpoolmanSlotAssignmentEnriched(BaseModel):
  33. """Slot assignment row enriched with printer name and AMS label.
  34. ``printer_name`` is null only in the cascade-deleted edge case where the
  35. Printer relation has been removed. ``ams_label`` is null when no
  36. ``ams_labels`` row matches the slot's MQTT serial (or the synthetic
  37. ``f"p{printer_id}a{ams_id}"`` fallback key).
  38. """
  39. printer_id: int
  40. printer_name: str | None
  41. ams_id: int
  42. tray_id: int
  43. spoolman_spool_id: int
  44. ams_label: str | None
  45. # e.g. "H3C": labels its AMS 3H slots "3H-n" and its external inputs "Ext-n"
  46. printer_model: str | None = None