"""Models for the Signal command and control bot.""" from __future__ import annotations from datetime import datetime # noqa: TC003 - pydantic needs this at runtime from enum import StrEnum from typing import Any from pydantic import BaseModel, ConfigDict from sqlalchemy.engine import Engine # noqa: TC002 - pydantic needs this at runtime class TrustLevel(StrEnum): """Device trust level.""" VERIFIED = "verified" UNVERIFIED = "unverified" BLOCKED = "blocked" class Role(StrEnum): """RBAC roles — one per command, plus admin which grants all.""" ADMIN = "admin" STATUS = "status" INVENTORY = "inventory" LOCATION = "location" class MessageStatus(StrEnum): """Dead letter queue message status.""" UNPROCESSED = "unprocessed" PROCESSED = "processed" class Device(BaseModel): """A registered device tracked by safety number.""" phone_number: str safety_number: str trust_level: TrustLevel = TrustLevel.UNVERIFIED first_seen: datetime last_seen: datetime class SignalMessage(BaseModel): """An incoming Signal message.""" source: str timestamp: int message: str = "" attachments: list[str] = [] group_id: str | None = None is_receipt: bool = False class SignalEnvelope(BaseModel): """Raw envelope from signal-cli-rest-api.""" envelope: dict[str, Any] account: str | None = None class InventoryItem(BaseModel): """An item in the van inventory.""" name: str quantity: float = 1 unit: str = "each" category: str = "" notes: str = "" class InventoryUpdate(BaseModel): """Result of processing an inventory update.""" items: list[InventoryItem] = [] raw_response: str = "" source_type: str = "" # "receipt_photo" or "text_list" class BotConfig(BaseModel): """Top-level bot configuration.""" model_config = ConfigDict(arbitrary_types_allowed=True) signal_api_url: str phone_number: str inventory_api_url: str ha_url: str | None = None ha_token: str | None = None engine: Engine reconnect_delay: int = 5 max_reconnect_delay: int = 300 max_retries: int = 10 max_message_attempts: int = 3