docs: add Mine Map v1 design
This commit is contained in:
1 parent
b741fd799b
commit
548051899e
1 file changed
+792
@@ -0,0 +1,792 @@
|
|||||||
|
# MinerOps Mine Map v1
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
This document is the implementation-ready design baseline for Mine Map v1. It
|
||||||
|
defines the smallest useful spatial operations view that can be built from the
|
||||||
|
current MinerOps location, inventory, telemetry, and repair foundations. It
|
||||||
|
does not claim that the described v1 interface is implemented.
|
||||||
|
|
||||||
|
The current application exposes a legacy Location Map at `/locations`, a Mine
|
||||||
|
Map placeholder at `/monitoring/map`, and a bulk location response at
|
||||||
|
`GET /api/location/map`. Mine Map v1 replaces the placeholder with a read-only,
|
||||||
|
permission-aware operational view. Existing location-management workflows
|
||||||
|
remain available separately and are not redesigned by this specification.
|
||||||
|
|
||||||
|
## 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. Lifecycle `retired`.
|
||||||
|
3. Active repair or lifecycle `in_repair`.
|
||||||
|
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
|
||||||
|
|
||||||
|
V1 filters are allowlisted and based on returned facts:
|
||||||
|
|
||||||
|
- explicit runtime status;
|
||||||
|
- lifecycle;
|
||||||
|
- model or miner type;
|
||||||
|
- authoritative active repair presence;
|
||||||
|
- location state;
|
||||||
|
- position kind/status; and
|
||||||
|
- presence or absence of current telemetry.
|
||||||
|
|
||||||
|
Search supports Asset ID, miner serial, current MAC, current IP, model, room,
|
||||||
|
rack, shelf, and position label where those fields are available. Asset ID and
|
||||||
|
lifecycle require the map response addition described below.
|
||||||
|
|
||||||
|
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 last seen time;
|
||||||
|
- lifecycle;
|
||||||
|
- active repair status, reason, and repair UUID;
|
||||||
|
- latest hashrate in TH/s;
|
||||||
|
- latest power in watts;
|
||||||
|
- latest chip maximum and average 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}&rack={label}&layer=status&miner_id={uuid}
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional repeated or encoded parameters hold allowlisted filters and search.
|
||||||
|
Rack is a label because racks do not have UUIDs. Unknown, unauthorized, or
|
||||||
|
malformed parameters are ignored with a nonblocking notice; they never broaden
|
||||||
|
data access.
|
||||||
|
|
||||||
|
The URL preserves:
|
||||||
|
|
||||||
|
- selected site and room;
|
||||||
|
- optional rack focus;
|
||||||
|
- layer;
|
||||||
|
- filters and search; and
|
||||||
|
- selected miner.
|
||||||
|
|
||||||
|
History state or session state preserves room scroll position, focused tile,
|
||||||
|
and any future map viewport that is too transient for a URL. Selecting or
|
||||||
|
closing a miner updates history so browser Back and Forward restore predictable
|
||||||
|
selection. Refresh and bookmarked links reconstruct the same shareable view.
|
||||||
|
|
||||||
|
When full miner detail opens in the same tab, the return URL contains the map
|
||||||
|
query state. Returning restores the prior scroll position when it remains
|
||||||
|
valid. A new tab may be offered as a normal browser link, not forced.
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
|
||||||
|
- no site ID or site name;
|
||||||
|
- no room UUID in each position;
|
||||||
|
- room ordering joins positions to rooms by case-insensitive room name, which is
|
||||||
|
ambiguous when different sites use the same room name;
|
||||||
|
- no Asset ID;
|
||||||
|
- no lifecycle status;
|
||||||
|
- no authoritative active repair UUID/status/reason;
|
||||||
|
- no tags;
|
||||||
|
- no environment temperature;
|
||||||
|
- no generated-at or response freshness metadata;
|
||||||
|
- no server-side site/room filter; and
|
||||||
|
- integrated `main.py` implementation with no typed response model.
|
||||||
|
|
||||||
|
The endpoint is already efficient in shape. V1 should preserve its path for
|
||||||
|
compatibility and implement an additive, typed map read model through the
|
||||||
|
required Router -> Service -> Repository -> PostgreSQL boundaries. It should
|
||||||
|
accept allowlisted `site_id` and `room_id` filters and add the missing identity,
|
||||||
|
lifecycle, site, and authoritative repair fields. A second competing map
|
||||||
|
endpoint is not required solely for v1.
|
||||||
|
|
||||||
|
### `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 is required by the route, but the current legacy endpoint does
|
||||||
|
not enforce `miners.view`. Mine Map must not treat authentication alone as
|
||||||
|
sufficient authorization. Any v1 use of this supplemental endpoint requires a
|
||||||
|
backend permission correction to `miners.view` first.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Required Map Read Model Additions
|
||||||
|
|
||||||
|
Before the full v1 behavior is enabled, the additive `GET /api/location/map`
|
||||||
|
contract must provide:
|
||||||
|
|
||||||
|
- 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 must remain set-based, parameterized, permission-controlled, and
|
||||||
|
bounded by selected scope. The repository owns SQL, the service owns response
|
||||||
|
composition and any future status policy, 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. Before implementing the additive map
|
||||||
|
response, decide whether users with only `locations.view` receive a reduced
|
||||||
|
miner shape or whether the enriched response requires both permissions. The API
|
||||||
|
must not expose fields merely because the standard role matrix usually grants
|
||||||
|
both.
|
||||||
|
|
||||||
|
## 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, partial, and refresh-failure 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.
|
||||||
|
|
||||||
|
## Backend And Data Gaps
|
||||||
|
|
||||||
|
These gaps block parts of the acceptance criteria:
|
||||||
|
|
||||||
|
1. **Multi-site location identity:** The current map response omits site and
|
||||||
|
room UUIDs and joins room ordering by name. Same-named rooms across sites
|
||||||
|
cannot be represented reliably until the query is site-scoped.
|
||||||
|
2. **Asset and lifecycle context:** Asset ID and lifecycle are not in the map
|
||||||
|
response. Without them, the map cannot truthfully distinguish spare,
|
||||||
|
retired, and in-repair lifecycle states.
|
||||||
|
3. **Authoritative repair context:** No bulk modular repair projection is joined
|
||||||
|
to map miners. The legacy repair queue is not an equivalent source.
|
||||||
|
4. **Freshness policy:** No single approved stale/down threshold exists. V1 can
|
||||||
|
show explicit offline state and observation age, but cannot classify stale
|
||||||
|
consistently until policy is centralized.
|
||||||
|
5. **Health and thermal policy:** Hashrate, rejection, power, and temperature
|
||||||
|
measurements exist, but model-aware degraded and hot thresholds do not.
|
||||||
|
6. **Location vocabularies:** Position and location-state values are not
|
||||||
|
database-constrained. Unknown legacy values require truthful fallback and a
|
||||||
|
later preflight before constraints are added.
|
||||||
|
7. **Rack and shelf identity/order:** Rack and shelf are labels, and racks lack
|
||||||
|
an explicit sort field. V1 can render them but cannot provide durable rack or
|
||||||
|
shelf links or guaranteed operator-defined rack ordering.
|
||||||
|
8. **Map contract:** The current response has no typed model or generated-at
|
||||||
|
metadata and cannot be server-filtered by site or room.
|
||||||
|
|
||||||
|
## 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 must be made before implementation begins:
|
||||||
|
|
||||||
|
1. Whether the enriched map response requires both `locations.view` and
|
||||||
|
`miners.view`, or returns a reduced miner shape to location-only users.
|
||||||
|
2. Which backend-owned policy defines stale, down, hot, and degraded states,
|
||||||
|
including scope, thresholds, collector cadence, and configuration source.
|
||||||
|
3. Whether rack ordering may use a documented natural-label sort for v1 or
|
||||||
|
requires an additive persisted sort field before rollout.
|
||||||
|
4. 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.
|
||||||
Reference in new issue
Block a user