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