# MinerOps Mine Map v1 ## Status This document is the design baseline for the implemented Mine Map v1. The current application exposes the read-only operational view at `/monitoring/map` and retains the legacy Location Map at `/locations` for existing location management workflows. Those mutation workflows are not redesigned by v1. ## Purpose Mine Map v1 answers one primary question: > What is happening physically in the mine right now, and where do I need to > look? It must let a technician or manager scan a physical room, identify explicit runtime and work states, locate affected miners, understand empty capacity, and inspect one miner without losing spatial context. It is an operational tool, not a geographic GIS map and not a decorative dashboard. ## Current Data Model ### Physical Hierarchy The implemented hierarchy is: ```text Site (UUID) -> Room (UUID, belongs to Site) -> Rack (text label on a location position) -> Shelf (text label and optional numeric index) -> Position (UUID and numeric index) -> Miner (UUID, optional occupant) ``` `sites` and `rooms` are normalized entities. A room belongs to one site. `location_positions` stores `room_id`, denormalized `room_name`, `rack_name`, `shelf_name`, optional `shelf_index`, `position_index`, and optional `position_label`. Rack and shelf are not independent entities and do not have durable UUIDs. Mine Map must therefore treat rack and shelf values as ordered labels within a room, not as globally addressable assets. A position may reference one miner. `miner_location_states` separately stores the miner's current location state and optional position reference. The miner's durable identity remains `miners.id`; neither its position, IP address, nor MAC address is asset identity. ### Position Vocabulary The current code uses these position kinds: - `miner_slot` - `switch` - `blocked` The current code uses these position statuses: - `available` - `occupied` - `switch` - `blocked` - `conflict` These values are used by the active implementation but are not constrained by the database. V1 must render unknown values as **Unknown position state** and must not silently treat them as empty or occupied. A later schema task should constrain the vocabulary after legacy-data preflight. The current miner location-state vocabulary is: - `shelved` - `waiting_placement` - `storage` - `repair` - `decommissioned` These values are also not database-constrained. They describe location or placement state, not miner lifecycle and not authoritative repair status. ### Lifecycle And Repair State Miner lifecycle is independently constrained to: - `active` - `spare` - `retired` - `in_repair` The modular Repairs domain uses `repairs` and immutable `repair_events` as the authoritative repair workflow. Active repair statuses are `OPEN`, `IN_PROGRESS`, `WAITING_FOR_PARTS`, and `READY_FOR_TEST`. `COMPLETED` and `CANCELLED` are not active repairs. The legacy location API currently exposes `miner_location_states.asset_state` but does not expose lifecycle or the authoritative active repair. Mine Map v1 must not equate legacy `asset_state = repair` with a modular repair record. ## User Outcomes V1 must support these outcomes without requiring location mutation: - Select a site and room and see its known physical layout. - Scan racks, shelves, and positions in their stored order. - Distinguish occupied, explicitly empty, blocked, switch, conflict, and incomplete positions. - Identify miners explicitly reported offline or hash-unstable. - Identify miners in the authoritative repair lifecycle or an active repair. - Distinguish active, spare, retired, and in-repair assets. - See latest available hashrate, power, and temperature values with timestamps. - Inspect a miner and open its existing detail page. - Return to the same room, rack, layer, filters, selection, and scroll context. ## Page Composition Mine Map uses the current authenticated application shell at `/monitoring/map`. The page contains four functional regions. ### Context Bar The context bar contains: - site selector; - room selector; - layer selector, defaulting to **Operational Status**; - search; - filters; - data freshness summary; and - a link to the existing location-management page when the user has `locations.manage`. The selected site limits the room selector. The selected room is required for the primary rack layout. An optional all-room summary may show room counts, but v1 must not render every tile from every room by default at large sites. ### Spatial Layout The main region renders the selected room as ordered rack groups. Each rack contains shelf rows, and each row contains positions ordered by `position_index`. The layout uses ordinary semantic DOM elements and CSS grid. Canvas, WebGL, pan physics, and a geographic basemap are not required. The visual order follows stored room `sort_order`, shelf `shelf_index` where present, and position index. Rack ordering is label-based because no rack sort field exists. The UI must not infer missing racks, shelves, or positions from gaps in names or numbers. ### Legend And Summary The legend is always visible for the active layer and pairs color with text and an icon or pattern. A compact summary reports only counts derivable from the loaded response, such as occupied positions, empty positions, conflicts, explicit offline miners, in-repair miners, and unknown-status miners. Counts must identify their scope and freshness. They must not claim site-wide coverage when only one room is loaded or when data is partial. ### Miner Inspector Selecting an occupied miner opens a side panel on desktop. On narrow screens it opens a full-height sheet with an obvious close control. The map remains the underlying context and selection is visible on the selected tile. Closing the inspector restores focus to the tile that opened it. Opening the full miner detail view uses the existing durable route: ```text /miners/{miner_id}/detail ``` ## Position And Miner Rendering ### Occupied Positions An occupied miner tile shows, in priority order: - position label or `Position {position_index}`; - Asset ID when available, otherwise current MAC, otherwise **Unidentified miner**; - operational condition text and icon; - the active layer's value; and - observation age or **Never observed**. The position remains the visible frame; filtering and status styling must not make its physical address ambiguous. ### Empty And Non-Miner Positions Empty positions are operationally meaningful and remain visible. | Condition | V1 presentation | | --- | --- | | `miner_slot`, no miner, no expected MAC, `available` | **Empty position** | | `miner_slot`, expected MAC, no miner | **Expected miner not resolved**; not empty | | `blocked` kind or status | **Blocked position** with reason when available | | `switch` kind or status | **Switch/equipment position** with equipment label | | `conflict` or `conflict_reason` | **Location conflict**; never shown as empty | | Unknown kind or status | **Unknown position state** | | Filtered occupied miner | Tile remains in place but is visually deemphasized | | Missing response data | **Unavailable**, distinct from an empty position | An absent position record is not an empty position. V1 must not synthesize a tile merely because a numeric position is missing from a shelf sequence. ### Unracked And Operationally Removed Miners Miners returned in `virtual_miners` appear outside the rack canvas in an **Unplaced and operational areas** section grouped by the stored location state. V1 maps the stored values to plain labels without inventing workflow: - `waiting_placement`: Waiting for placement - `storage`: Storage - `repair`: Legacy repair location state - `decommissioned`: Decommissioned location state Lifecycle is shown separately. A spare miner is not automatically in storage, and a retired miner is not automatically decommissioned. Where lifecycle and location state disagree, both are displayed and the discrepancy is not hidden. ### Repair Miners A miner with `lifecycle_status = in_repair` or an authoritative active repair is visually unmistakable through text, icon, and a repair treatment independent of the selected data layer. V1 distinguishes: - in repair while still assigned to a physical position; - in repair with no rack position; - repair complete with lifecycle `active` or `spare`, awaiting placement; and - legacy `asset_state = repair` without an authoritative active repair. The last case is labelled **Legacy repair location state** rather than being presented as a confirmed active repair. ## Operational Status Model Operational Status is the default layer. It composes current facts with this display precedence: 1. Location conflict, blocked position, switch, or empty position. 2. Active repair or lifecycle `in_repair`. 3. Lifecycle `retired` or decommissioned location state. 4. Lifecycle `spare`. 5. Runtime status explicitly equal to `offline`. 6. `hash_stable = false`, labelled **Hash unstable**. 7. Runtime status explicitly equal to `online`. 8. Unknown or unrecognized runtime status. Lifecycle and repair treatments do not erase runtime facts. For example, an in-repair miner may also show that its last runtime observation was online. `miners.status` is free text and not database-constrained. V1 must not infer that arbitrary values containing `error`, `warn`, or `fault` mean a particular severity. Unknown values may be displayed as secondary raw context after safe formatting. ### Freshness `last_seen_at` and latest telemetry `observed_at` are available, but MinerOps has no single authoritative staleness policy. Existing legacy screens use inconsistent thresholds, including 10 and 30 minutes. Therefore: - v1 always displays observation time or age; - missing timestamps are **Never observed** or **Freshness unknown**; - explicit runtime `offline` may be shown as Offline; - v1 does not label a miner Stale based on age until a centralized policy is approved and supplied by the backend; and - late or failed refreshes keep the last successful values visible with a prominent **Update failed** or **Data may be stale** state. ### Conditions Requiring Policy The following labels require an approved backend policy and are not inferred by v1: - stale based on observation age; - down based on missing hashrate or elapsed time; - hot or overheating based on temperature; - degraded based on hashrate, rejection rate, power, or temperature; and - low hashrate relative to model expectation. The legacy Location Map and dashboard endpoints contain hard-coded thresholds, but they are not a consistent policy contract. V1 may display raw measurements and the explicit `hash_stable` boolean without adopting those thresholds. ## Layer Model The layer selector is extensible, but the first implementation is not required to enable every layer. | Layer | V1 status | Current data | Required behavior | | --- | --- | --- | --- | | Operational Status | Required, default | Position kind/status, runtime status, lifecycle after API addition, `hash_stable`, timestamps | Uses the factual precedence above; no invented thresholds | | Repair State | Required presentation, optional standalone layer | Lifecycle and modular repair after API addition; legacy location state exists now | Repair treatment remains visible on every layer | | Temperature | Optional v1 value layer | Latest chip maximum and average are already in the map response; environment temperature exists in snapshots but is omitted | Show numeric value and timestamp; no hot classification without policy | | Hashrate | Optional v1 value layer | Latest average hashrate in TH/s is already in the map response | Show value and missing-data state; no low-hashrate threshold | | Power | Optional v1 value layer | Latest power in watts is already in the map response | Show value and missing-data state; no density or efficiency inference | | Thermal heat map | Future | Point temperatures exist for some miners | Requires approved thresholds, aggregation semantics, coverage disclosure, and legend | The frontend implements layers through a shared tile contract rather than separate maps. Adding a future layer changes tile content and legend, not the physical hierarchy or selection model. ## Filtering And Search The implemented filters are allowlisted and use returned miner facts: derived operational status, lifecycle, model, and authoritative active-repair presence. Search covers Asset ID, miner serial, current MAC, current IP, and model. Location context remains visible while nonmatching occupied tiles are deemphasized. Users without `miners.view` receive none of these miner fields; miner search, miner filters, and non-status layers are disabled rather than operating on redacted values. Filtering preserves physical context. Positions remain in the layout; nonmatching occupied tiles are deemphasized and marked as filtered. A separate **Show only matches** density option may collapse nonmatching rack groups, but it is off by default and clearly indicates that context is hidden. Empty, blocked, switch, and conflict positions remain visible unless explicitly filtered by position type. No-results differs from load failure and from an empty room: - **No positions configured**: the selected room has no position records. - **No miners match these filters**: layout remains visible, with miners deemphasized. - **Map unavailable**: the request failed or returned an invalid response. ## Miner Inspector Fields Where authorized and available, the inspector shows: - Asset ID; - model or miner type; - miner serial number; - current MAC and IP address; - full site, room, rack, shelf, and position path; - runtime status and telemetry observation age; - lifecycle; - active repair status and reason; - latest hashrate in TH/s; - latest power in watts; - latest chip maximum temperature; - telemetry observation time; and - a link to full miner detail. Null values render as **Unavailable**. Numeric zero is displayed as zero, not as missing. The panel must not fetch one API resource per visible tile. A single detail request for the selected miner is acceptable when it supplies fields not needed by the map response. ## Context Preservation Shareable state is URL-backed. The initial query contract is: ```text /monitoring/map?site_id={uuid}&room_id={uuid}&layer=status&miner_id={uuid} ``` Optional encoded parameters hold allowlisted filters and search. Rack and shelf do not have URL identifiers because they are labels rather than durable entities. Unknown, unauthorized, or malformed parameters are discarded and the canonical URL is replaced; they never broaden data access. The URL preserves: - selected site and room; - layer; - filters and search; and - selected miner. Selecting or closing a miner updates history so browser Back and Forward restore predictable URL-backed selection. Refresh and bookmarked links reconstruct the same valid shareable state. Scroll position is not persisted by v1. When full miner detail opens in the same tab, the return URL contains the map query state. A new tab remains available through normal browser behavior. ## Responsive Behavior ### Desktop - Context controls remain visible above the map. - The room layout occupies the main content width. - The inspector uses a fixed-width side panel without resizing rack tiles. - Horizontal scrolling is contained within an individual wide rack or map viewport, not the whole page. ### Tablet And Phone - Site, room, layer, and filter controls stack without hiding the active scope. - One rack is shown at a time when the full room cannot remain legible. - Shelf rows retain physical position order and allow contained horizontal scrolling. - The inspector becomes a full-screen sheet. - Closing the sheet restores the selected tile and scroll location. - Touch targets meet the shared UX guide sizing rules. The mobile view does not turn positions into an unrelated flat miner list. Rack and shelf context remains visible. ## Accessibility Mine Map v1 must: - communicate every state with text and an icon or pattern, not color alone; - provide one page heading and semantic site, room, rack, shelf, and position labels; - expose each rack as a labelled region and each shelf as a labelled group; - expose miner tiles as buttons when they open the inspector and noninteractive elements when they do not; - include an accessible name containing position, miner identity, condition, and freshness; - support logical keyboard movement through positions in stored spatial order; - preserve visible focus and return focus after closing the inspector; - provide keyboard-operable filters, layer selection, scrolling, and zoom or density controls; - avoid hover-only values; - support browser zoom without clipping labels or controls; and - avoid required motion and respect reduced-motion preferences if motion is introduced later. A screen-reader summary provides counts and a structured rack/shelf/position list. It describes empty and blocked positions rather than omitting them. ## Loading, Failure, And Partial Data The map has explicit states: - **Loading**: layout skeletons preserve the eventual page structure. - **Ready**: layout and response scope are known. - **Empty room**: valid response with no configured positions. - **No configured sites or rooms**: directs authorized users to existing location management without implying a load failure. - **Denied**: permission failure without leaking counts or miner previews. - **Error**: request failed or response shape is invalid; remains visible until retry or navigation. - **Partial data**: physical layout is usable, but one or more miner fields or telemetry observations are unavailable. - **Refresh failed**: last successful map remains visible, clearly marked with the failure time. The frontend validates the response envelope and required position identifiers. A malformed response is an error, not an empty room. ## Performance And Refresh V1 is designed for hundreds or thousands of positions without prematurely adopting canvas or WebGL. - Fetch a map-shaped bulk response; never issue one request per position. - Load one selected site and room where backend filtering is available. - Render the visible room first and defer secondary summaries. - Build rack and shelf fragments off-DOM, then attach them in batches. - Update changed tile content without replacing the full page or shell. - Keep layout refresh separate from telemetry refresh because positions change less frequently than observations. - Do not overlap refresh requests; cancel or ignore superseded responses. - Pause automatic telemetry refresh while the page is hidden. - Base any polling interval on collector cadence or backend guidance; this document does not invent an interval. - Preserve last successful values during transient failure and expose their observation timestamps. - Introduce list virtualization only after measurement shows the selected-room DOM is too large; accessibility and keyboard order must be preserved. The current bulk location query selects the latest snapshot per miner and is a useful foundation. Before production use at large scale, its query plan and indexes must be measured with representative site data. ## Current API Assessment ### `GET /api/location/map` Permission: `locations.view`. This is the primary existing map-shaped response and avoids N+1 tile requests. It returns: - `data.positions`: position UUID, room/rack/shelf labels, shelf and position indexes, position label/kind/status, expected MAC, equipment metadata, conflict information, miner UUID, current MAC/IP, runtime status, hostname, serials, model/type, firmware, last seen, legacy location state, and latest hashrate, power, chip temperatures, hash stability, rejection percentage, and observation time; - `data.virtual_miners`: miners in waiting-placement, storage, legacy repair, or decommissioned location states; and - counts in `meta`. The implemented additive response also supplies site and room UUIDs, Asset ID, lifecycle, authoritative active repair context, generated-at metadata, typed contracts, and optional server-side site and room filters. Its read path follows Router -> Service -> Repository -> PostgreSQL. Users with `locations.view` but without `miners.view` receive physical layout and occupancy with miner identity, telemetry, repair, and virtual-miner details removed. Tags and environment temperature are not included because v1 does not require them. Rack and shelf remain labels. The frontend renders legacy positions without `room_id` in a separate **Unscoped legacy positions** section; it does not attach their denormalized room labels to a selected site or room. ### `GET /api/sites` Permission: `locations.view`. Returns site UUID, name, description, and timestamps. It supplies the site selector. It does not include room or map counts. ### `GET /api/rooms` Permission: `locations.view`. Returns room UUID, site UUID/name, room name/description, sort order, and timestamps. It supplies site-scoped room selection. ### `GET /api/location/rooms-order` Permission: `locations.view`. Returns room UUID/name/order and position, miner-slot, switch, blocked, and assigned-miner counts. Its location join is name-based and not site-scoped, so it cannot be the sole source for a correct multi-site selector. ### `GET /api/inventory/miners` Permission: `miners.view`. Returns durable miner UUID, Asset ID, current MAC/IP, model, lifecycle, runtime status, site/room UUIDs, tags, and timestamps. Search covers Asset ID, MAC, IP, model, miner serial, control-board serial, PSU serial, and notes. The collection is bounded to 500 records per request and has no physical position or telemetry. It can validate the missing identity fields during development, but Mine Map must not join paginated inventory calls in the browser for every room. The map read model should include the small identity and lifecycle subset it needs. ### `GET /api/inventory/miners/{miner_id}` Permission: `miners.view`. Adds hostname, miner serial, control-board serial, PSU serial, firmware, notes, and first-seen time. One call after selection is acceptable when the inspector needs these fields, but it is not used to populate tiles. ### `GET /api/miners/{miner_id}/status-snapshots` Authentication and `miners.view` are required. Admin is allowed by the existing admin override; Manager, Technician, Read Only, and custom roles are authorized according to their effective `miners.view` permission. Mine Map does not call this endpoint per tile. Returns up to 100 recent snapshots with hashrate, power, efficiency, environment and chip temperatures, fans, uptime, stability, rejection rate, performance mode, timestamps, and raw JSON. It is too heavy for every tile and must not be called per visible miner. It may support a future selected-miner trend, which is outside v1. ### Repair APIs `GET /api/repairs/{repair_id}` and its event route expose authoritative modular repair detail when a repair UUID is already known. There is no current modular read endpoint that returns the active repair for each map miner. `GET /api/repairs/queue` is a bulk legacy queue based on `miner_location_states.asset_state = repair` and legacy maintenance events. It does not represent the modular `repairs` table and must not be used as the authoritative Mine Map repair source. The additive map read model therefore needs a set-based join to at most one active modular repair per miner. It must not perform per-miner repair lookups. ### Dashboard APIs `GET /api/dashboard/miner-table` exposes latest telemetry in a fleet table. The hot, unstable, and stale endpoints expose useful operational lists, but they use endpoint-specific or caller-supplied thresholds. Mine Map does not need these endpoints because the location map already includes the relevant raw latest measurements. V1 must not combine their classifications into a new status policy by accident. ## Implemented Map Read Model The additive `GET /api/location/map` contract provides: - site UUID and name; - room UUID as well as room name; - Asset ID; - lifecycle status; - a nullable active repair object containing repair UUID, status, and reason; - response generation time; - explicit scope metadata for selected site and room; - per-miner latest telemetry observation time; and - truthful nulls for unavailable fields. The query is set-based, parameterized, permission-controlled, and uses four fixed database executions independent of fleet size. Optional site and room UUID filters are supported by the API; the current frontend performs one bulk request and renders the selected room locally. The repository owns SQL, the service owns response composition, and the router owns HTTP validation, authorization, and the typed response envelope. V1 does not require a write endpoint. Reading the map, changing layer, applying filters, selecting a miner, or opening the inspector must never mutate location, lifecycle, repair, or telemetry state. ## Permissions - The page and location layout require `locations.view`. - Supplemental Inventory detail requires `miners.view`. - The existing miner detail link is shown only when the user can view miners. - The location-management link is shown only with `locations.manage`. - No placement or repair command is exposed by Mine Map v1. - Frontend permission checks control presentation only; backend dependencies remain authoritative. All default roles currently receive both `locations.view` and `miners.view`, but custom role combinations are possible. The backend implements the boundary: `locations.view` alone returns layout and occupancy while redacting Asset ID, miner UUID, expected/current MAC, IP, serials, model, runtime/lifecycle/location state, telemetry, active repair, and virtual-miner data. This is authoritative server-side redaction rather than frontend hiding. ## Architecture Boundary Mine Map v1 follows: ```text Frontend page -> FastAPI router -> Location map service/read model -> Location repository -> PostgreSQL ``` The frontend contains no SQL, status policy, repair reconstruction, or location mutation. The read repository may join location, Inventory-owned projections, latest telemetry, and active repair state for this explicit read model. It does not write those domains. The service maps persistence results into the typed map contract and preserves null and unknown values. The existing integrated route may be migrated incrementally behind the same path. The legacy `/locations` page remains available during rollout. Mine Map v1 must not copy its inline HTML/JavaScript or hard-coded classification rules into the new frontend shell. ## V1 Implementation Boundary The smallest useful v1 includes: - real site and room selection; - real racks, shelves, positions, and miners; - explicit empty, blocked, switch, conflict, and unknown positions; - unplaced and operational-area miners; - Operational Status as the default layer; - repair and lifecycle treatments based on authoritative data; - raw latest telemetry values and freshness; - status legend and scoped counts; - basic allowlisted filters and search; - selected-miner inspector; - existing miner-detail navigation; - URL-backed context and predictable browser history; - loading, empty, denied, error, and partial-data states; - desktop, tablet, and phone behavior; - keyboard, screen-reader, touch, zoom, and reduced-motion support; - permission-aware presentation; and - focused route, contract, rendering, permission, accessibility, and context preservation tests. Location creation, movement, import, rack/shelf editing, and position-type changes remain in existing management workflows. V1 may link to those workflows but does not reproduce them. ## Deferred Capabilities The following are explicitly outside v1: - advanced thermal heat maps; - historical map playback; - drag-and-drop or click-to-move relocation; - map, rack, shelf, or position editing; - predictive failure visualization; - animated telemetry; - power-density and electrical-capacity modelling; - saved map views; - multi-site geographic maps; - multi-selection and bulk commands; - repair, lifecycle, or component mutations; - alerts and notifications; - selected-miner telemetry charts; and - generalized infrastructure modelling beyond current position records. ## Remaining Backend And Data Gaps 1. **Freshness policy:** No single approved stale/down threshold exists. V1 shows explicit offline state and observation age but does not classify stale. 2. **Health and thermal policy:** Hashrate, rejection, power, and temperature measurements exist, but model-aware degraded and hot thresholds do not. 3. **Location vocabularies:** Position and location-state values are not database-constrained. Unknown legacy values require truthful fallback. 4. **Rack and shelf identity/order:** Rack and shelf are labels, and racks lack an explicit sort field. V1 cannot provide durable rack or shelf links or operator-defined rack ordering. ## Acceptance Criteria Mine Map v1 is accepted when all of the following are true: - An authorized user can select a site and room and see every configured position in stored spatial order. - Occupied, empty, expected-but-unresolved, blocked, switch, conflict, unknown, and unavailable states are visually and semantically distinct. - Filtering does not silently erase physical context. - Explicit offline, hash-unstable, spare, retired, and in-repair miners are identifiable without inventing thresholds. - Every telemetry value is tied to an observation time or marked unavailable. - A user can identify whether apparent problems cluster by room, rack, or shelf from factual tile states and measurements. - A selected position identifies its miner or states why no miner is present. - The inspector shows the key current identity, location, lifecycle, repair, runtime, and telemetry details available to the user. - The full miner detail page can be opened using the durable miner UUID. - Closing the inspector or returning from detail restores the exact shareable map context and practical scroll/focus context. - Browser Back, Forward, refresh, bookmarks, and shared URLs behave predictably. - Permission failures expose no unauthorized counts, previews, search results, or inspector fields. - The page remains usable with keyboard, screen reader, browser zoom, touch, narrow viewports, missing telemetry, partial records, and transient refresh failures. - The implementation performs no per-tile or per-miner request fan-out. - The map is read-only; existing location management remains available. Until the freshness and health-policy gaps are resolved, the map can answer which miners are explicitly offline, hash-unstable, in repair, or associated with location conflicts. It cannot truthfully claim that every down, stale, hot, or degraded miner has been identified. The interface must state that boundary rather than implying complete health classification. ## Unresolved Decisions The following decisions remain for later map and data work: 1. Which backend-owned policy defines stale, down, hot, and degraded states, including scope, thresholds, collector cadence, and configuration source. 2. Whether rack ordering may use a documented natural-label sort or requires an additive persisted sort field. 3. Whether `location_positions.room_id` can be made required after legacy-data preflight, eliminating name-only room association for production map data. These decisions do not require changing the durable miner identity, existing repair workflow, or v1 read-only boundary.