"""Business rules and orchestration for repair workflows.""" from uuid import UUID from app.repairs.exceptions import ( ActiveRepairConflict, IncompatibleMinerLifecycle, InconsistentRepairState, RepairActorRequired, RepairMinerNotFound, ) from app.repairs.models import ( BeginRepairRequest, BeginRepairResult, InitialRepairNotePayload, RepairStartedPayload, ) from app.repairs.repository import RepairRepository ELIGIBLE_LIFECYCLE_STATUSES = frozenset({"active", "spare"}) REPAIR_LIFECYCLE_STATUS = "in_repair" class RepairService: """Explicit operational repair workflows independent of FastAPI.""" def __init__(self, repository: RepairRepository): self.repository = repository @staticmethod def normalize_note(note: str | None) -> str | None: if note is None: return None normalized = str(note).strip() return normalized or None def begin_repair( self, request: BeginRepairRequest, *, actor_user_id: UUID | None, ) -> BeginRepairResult: if actor_user_id is None: raise RepairActorRequired() note = self.normalize_note(request.notes) with self.repository.transaction() as repository: miner = repository.lock_miner_for_update(request.miner_id) if not miner: raise RepairMinerNotFound(request.miner_id) lifecycle_status = str(miner["lifecycle_status"]) active_repair = repository.get_active_repair(request.miner_id) if active_repair: raise ActiveRepairConflict(request.miner_id) if lifecycle_status == REPAIR_LIFECYCLE_STATUS: raise InconsistentRepairState(request.miner_id) if lifecycle_status not in ELIGIBLE_LIFECYCLE_STATUSES: raise IncompatibleMinerLifecycle( request.miner_id, lifecycle_status, ) repair = repository.create_repair( request.miner_id, request.reason, actor_user_id, ) started_payload = RepairStartedPayload( reason=request.reason, from_lifecycle=lifecycle_status, ) repository.create_event( repair["id"], sequence=1, event_type="REPAIR_STARTED", created_by=actor_user_id, payload=started_payload.model_dump(mode="json"), ) if note is not None: note_payload = InitialRepairNotePayload(note=note) repository.create_event( repair["id"], sequence=2, event_type="INITIAL_NOTE", created_by=actor_user_id, payload=note_payload.model_dump(mode="json"), ) repository.set_miner_lifecycle( request.miner_id, REPAIR_LIFECYCLE_STATUS, ) return BeginRepairResult( repair_id=repair["id"], miner_id=repair["miner_id"], status=repair["status"], reason=repair["reason"], opened_at=repair["opened_at"], opened_by=repair["opened_by"], )