153 lines
5.5 KiB
Python
153 lines
5.5 KiB
Python
"""Composition service for the Internal Alpha Miner Detail overview."""
|
|
|
|
from datetime import datetime, timezone
|
|
from math import isfinite
|
|
from uuid import UUID
|
|
|
|
from app.inventory.models import InventoryMinerDetail
|
|
from app.inventory.service import InventoryService
|
|
from app.locations.service import LocationMapService
|
|
from app.miner_detail.models import (
|
|
MinerDetailAccess,
|
|
MinerDetailCapabilities,
|
|
MinerDetailLocationSection,
|
|
MinerDetailOverviewData,
|
|
MinerDetailOverviewMeta,
|
|
MinerDetailOverviewResponse,
|
|
MinerDetailRepairEpisode,
|
|
MinerDetailRepairSection,
|
|
OperationalState,
|
|
)
|
|
from app.repairs.center_service import RepairCenterService
|
|
from app.repairs.models import RepairCenterAccess, RepairCenterItem
|
|
|
|
|
|
class MinerDetailService:
|
|
"""Coordinate domain-owned current projections without transport logic."""
|
|
|
|
def __init__(
|
|
self,
|
|
inventory_service: InventoryService,
|
|
location_service: LocationMapService,
|
|
repair_service: RepairCenterService,
|
|
):
|
|
self.inventory_service = inventory_service
|
|
self.location_service = location_service
|
|
self.repair_service = repair_service
|
|
|
|
@staticmethod
|
|
def _episode(item: RepairCenterItem) -> MinerDetailRepairEpisode:
|
|
return MinerDetailRepairEpisode(
|
|
repair_id=item.repair_id,
|
|
source=item.source,
|
|
active=item.active,
|
|
status=item.repair_status,
|
|
reason=item.reason,
|
|
opened_at=item.opened_at,
|
|
closed_at=item.closed_at,
|
|
latest_context=item.latest_context,
|
|
latest_context_at=item.latest_context_at,
|
|
parts_count=item.parts_count,
|
|
parts_summary=item.parts_summary,
|
|
)
|
|
|
|
@staticmethod
|
|
def _operational_state(
|
|
miner: InventoryMinerDetail,
|
|
active_repair: MinerDetailRepairEpisode | None,
|
|
) -> OperationalState:
|
|
if active_repair is not None or miner.lifecycle_status == "in_repair":
|
|
return "repair"
|
|
if miner.lifecycle_status == "retired":
|
|
return "retired"
|
|
if miner.lifecycle_status == "spare":
|
|
return "spare"
|
|
runtime = (miner.runtime_status or "").strip().lower()
|
|
if runtime == "offline":
|
|
return "offline"
|
|
if miner.hash_stable is False:
|
|
return "unstable"
|
|
if runtime == "online":
|
|
return "online"
|
|
return "unknown"
|
|
|
|
def get_overview(
|
|
self,
|
|
miner_id: UUID,
|
|
access: MinerDetailAccess,
|
|
) -> MinerDetailOverviewResponse:
|
|
miner = self.inventory_service.get_current_miner(miner_id)
|
|
# Location is owned by the permission-aware section below. Keep the
|
|
# shared projection from leaking location identifiers. Non-finite
|
|
# telemetry is not a factual display value and remains unavailable.
|
|
redacted = {
|
|
field: None
|
|
for field in (
|
|
"site_id", "site_name", "room_id", "room_name",
|
|
"location_position_id", "rack_label", "shelf_label",
|
|
"position_label", "position_index",
|
|
)
|
|
}
|
|
for field in ("hashrate_ths", "power_w", "chip_temp_max_c"):
|
|
value = getattr(miner, field)
|
|
if value is not None and not isfinite(value):
|
|
redacted[field] = None
|
|
miner = miner.model_copy(
|
|
update=redacted
|
|
)
|
|
location = (
|
|
self.location_service.get_miner_location(miner_id)
|
|
if access.can_view_locations
|
|
else None
|
|
)
|
|
repair_items = (
|
|
self.repair_service.list_miner_repairs(
|
|
miner_id,
|
|
RepairCenterAccess(
|
|
can_read_repairs=True,
|
|
can_view_miners=True,
|
|
),
|
|
)
|
|
if access.can_read_repairs
|
|
else []
|
|
)
|
|
episodes = [self._episode(item) for item in repair_items]
|
|
active_repair = next(
|
|
(
|
|
episode for episode in episodes
|
|
if episode.active and episode.source == "modular"
|
|
),
|
|
None,
|
|
)
|
|
query_count = 1 + int(access.can_view_locations) + int(access.can_read_repairs)
|
|
return MinerDetailOverviewResponse(
|
|
data=MinerDetailOverviewData(
|
|
miner=miner,
|
|
operational_state=self._operational_state(miner, active_repair),
|
|
location=MinerDetailLocationSection(
|
|
available=access.can_view_locations,
|
|
restricted=not access.can_view_locations,
|
|
location=location,
|
|
),
|
|
repair=MinerDetailRepairSection(
|
|
available=access.can_read_repairs,
|
|
restricted=not access.can_read_repairs,
|
|
active_repair=active_repair,
|
|
episodes=episodes,
|
|
),
|
|
capabilities=MinerDetailCapabilities(
|
|
can_view_map=bool(
|
|
access.can_view_locations and location is not None
|
|
and location.site_id and location.room_id
|
|
),
|
|
can_open_repair_center=access.can_view_repairs,
|
|
can_read_repair_history=access.can_read_repairs,
|
|
can_read_components=access.can_read_components,
|
|
),
|
|
),
|
|
meta=MinerDetailOverviewMeta(
|
|
generated_at=datetime.now(timezone.utc),
|
|
database_query_count=query_count,
|
|
),
|
|
)
|