---
module: devices.map
title: Map
tab: Internal
order: 20
audience: internal
---

> **Internal · ops/support.** Grounded in `hiveops-devices/frontend/src/components/AtmMap/AtmMap.svelte` + the devices/incident backends, 2026-07-01.

## What this view actually does

The Fleet Map is a **read-only** Leaflet view. It makes no writes. On mount and on each auto-refresh it pulls three lists and does all clustering, colouring, and filtering **client-side**:

| Data | Source (frontend fn) | Backend |
|------|----------------------|---------|
| ATMs (pins) | `loadAtms()` → `atmAPI.getAll()` | `hiveops-devices` `GET /api/atms` |
| Open incidents (orange colour + Incident Type filter) | `loadIncidents()` → `incidentAPI.getAll()` | `hiveops-incident` `GET /api/incidents` |
| Groups (Group filter) | `loadAtmGroups()` → `atmGroupAPI.getAll()` | `hiveops-devices` `GET /api/atm-groups` |

**Key consequence:** the map straddles **two services**. Pins come from devices; the orange "Open Incident" colour and the Incident Type filter come from incident. They fail independently.

## Roles required

- **To view the map:** any authenticated JWT. The three GET endpoints above have **no `@PreAuthorize`** — reads are open to any logged-in role (incidents are still institution-scoped server-side via `InstitutionContext.resolveScope()`).
- **To fix an unmapped ATM (set lat/long):** `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` — coordinates live on the `atms` row and are edited through the admin-gated `PUT /api/atms/{id}` (ATM detail → properties).
- **To manage the groups** that populate the Group filter: `MSP_ADMIN`/`BCOS_ADMIN` (`/api/atm-groups` mutations).

## First things to check when it misbehaves

**"Pins are missing / fleet looks half-empty"**
1. Almost always **missing coordinates**. Only ATMs with non-null `latitude` AND `longitude` are plotted; the rest are silently dropped and counted in the amber "N ATMs have no coordinates" banner. Fix: open each via **View in ATM List →** and set lat/long (admin).
2. A **default group** is active. If the user has a default group set (browser `localStorage: hiveops_defaultGroupId`), `loadAtms()` switches to `GET /api/atms/paginated?size=9999&groupId=<gid>` — so the map only ever shows that group, capped at 9999 rows.
3. An **active status/type/group filter** is narrowing the set — check the "Filters:" tag bar and the legend count (`X of Y filtered`). Hit **Clear all**.

**"No ATMs show as Open Incident (nothing goes orange) / Incident Type filter is empty"**
- The incident backend call failed or returned nothing. Pins still render (devices is independent), but incident colouring/filtering is gone. Check `hiveops-incident` health and that `GET /api/incidents` returns 200 for this user's institution.
- Only incidents with `status === 'OPEN'` count — resolved/closed incidents never colour a pin.

**"Two ATMs at the same site aren't clustered into one pin"**
- Clustering keys on the **exact** `"lat,lon"` string. Coordinates that differ even slightly get separate pins. To force a shared count-badge pin, give the co-located ATMs identical lat/long.

**"My saved presets / filters vanished"**
- Presets and the last filter state are **browser-local only** (`localStorage: hiveops_map_filters_v2`), never stored server-side. Clearing site data, a different browser, or a different machine = presets gone. This is expected, not a bug.

**"Map won't stop reloading / reloads too often (or never)"**
- Auto-refresh is a client timer; interval = `settings.dashboardRefreshIntervalSeconds`. The toggle state persists in `localStorage: devices_map_autoRefresh`. Each tick re-runs `loadAtms()` + `loadIncidents()`.

**"401/403 loading the map"**
- Expired/again JWT, or the incident call is being blocked by nginx. The incident list is fetched at gateway path `/incident/incidents` (nginx injects the `/api` segment to reach the controller's `/api/incidents`); a stale nginx block for `incident.*`/`devices.*` is the usual culprit for a one-service failure.

## What you can NOT fix from this screen

- You can't edit coordinates, status, or incidents here — the map only reads. All corrections happen in **ATM Detail** (devices) or the **Incident** app.
