feat: add begin repair workflow
This commit is contained in:
1 parent
d738b8e27e
commit
f6a2512e23
21 files changed
+3108
-5
No files matched your search
@@ -0,0 +1,103 @@
|
||||
"""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"],
|
||||
)
|
||||
Reference in new issue
Block a user