Document buildings and levels where the old single-map model was still taught
The 0.11.0 release changed what a map position means and six documents still described the model it replaced. Each of these could have caused a real mistake rather than being merely out of date: - IMPORT-API mapped legacy mapleft/maptop to mapx/mapy with no mention of the level, so a scripted import - including the classic-ASP one still to run against production - would have produced markers the map shows as "level unknown". It now maps levelid too and says how to resolve the default level. - API-REFERENCE enumerates the unauthenticated surface in full, because that is what a deploy reviewer reads, and the three public /api/maplevels reads were missing from it. Also records why the write split is asymmetric: repositioning needs assets.edit, creating a level needs admin, since a level's dimensions are the coordinate space every marker on it is expressed in. - CONFIG still presented the four map_* settings as live, telling the reader to re-upload a blueprint in a settings page that no longer drives the map. They are marked superseded and kept for downgrade. - UPGRADE gained a 0.11.0 section: nothing moves on screen, and replacing a blueprint with one of different dimensions moves every marker on that level, so recalibrate from landmarks rather than editing width and height. - PLUGIN-HOOKS now states that a map overlay keys on assetid and must not return coordinates or a level - a second copy of a position is one that can disagree. Adds FLOOR-MAP.md, the operator's page: loading a plan, placing markers, and what to do when the plan changes, with the reasoning left in ADR-017. START-HERE routes to it from the new-site path, and specifically as the page to read BEFORE a floor plan changes.
This commit is contained in:
@@ -96,6 +96,13 @@ Two auth patterns dominate the reads:
|
||||
Every mutating endpoint (POST / PUT / PATCH / DELETE) requires a JWT and is
|
||||
gated by `require_role` or `require_permission`; none are public.
|
||||
|
||||
The map write surface splits along that line. Repositioning markers
|
||||
(`/api/mappositions/*`) needs `assets.edit` - the same permission the old
|
||||
per-asset `PUT /api/assets/<id>` always needed, so bulk editing grants nobody
|
||||
anything new. Creating buildings and levels or uploading a blueprint
|
||||
(`POST|PUT|PATCH|DELETE /api/maplevels/*`) needs `role:admin`, because a level's
|
||||
dimensions are the coordinate space every marker on it is expressed in.
|
||||
|
||||
Two GETs deliberately break the optional-auth convention and require a
|
||||
permission, because what they return is not asset metadata but a file that
|
||||
carries value or liability of its own:
|
||||
@@ -115,7 +122,10 @@ like any other mutation.
|
||||
| `POST /api/auth/login` | Obtain a JWT. |
|
||||
| `GET /api/setup/needs-admin` | First-run check: does the instance have zero users. |
|
||||
| `POST /api/setup/create-admin` | First-run only; creates the first admin, then 403s forever. |
|
||||
| `GET /api/settings/map-blueprint/<filename>` | Serve the floor-map blueprint image. |
|
||||
| `GET /api/settings/map-blueprint/<filename>` | Serve a floor-map blueprint image. Pre-0.11.0 single-map path, retained; new uploads are served per level below. |
|
||||
| `GET /api/maplevels` | Buildings and their levels, each with blueprint paths, native pixel size and marker count. Public because the printer-installer map draws a floor plan before anyone logs in; it exposes level names and image paths, nothing about assets. |
|
||||
| `GET /api/maplevels/<levelid>` | One level's name, building, blueprints and size. |
|
||||
| `GET /api/maplevels/<levelid>/blueprint/<filename>` | Serve a level's blueprint image, with sandbox headers so an SVG floor plan cannot execute as script. |
|
||||
| `GET /api/settings/branding/<filename>` | Serve site branding assets (logo, etc.). |
|
||||
| `GET /api/settings` and `GET /api/settings/<key>` | Read-only, and only the public allowlist: the `branding` and `map` categories, a few named site keys, plus any key a plugin declares `public` in `get_settings_defaults` (e.g. `printedparts_label_prefix`, which the logged-out parts kiosk renders). Every other key answers 404 to an anonymous caller. |
|
||||
| `GET /api/models/image/<filename>` | Serve a model image. |
|
||||
|
||||
@@ -196,12 +196,21 @@ The batch generator at `/print/asset-label-batch/<assettype>` (reached from the
|
||||
|
||||
### map
|
||||
|
||||
**SUPERSEDED as of 0.11.0.** A blueprint and its pixel dimensions are properties
|
||||
of a LEVEL now, not of the site (ADR-017), because a site can have more than one
|
||||
building and a building more than one floor. Manage them in
|
||||
**Settings > Buildings and levels**, or through `/api/maplevels`.
|
||||
|
||||
These four keys are still present so that downgrading finds the blueprint it had,
|
||||
and the levels migration copied their values onto the first level. Editing them
|
||||
changes nothing on the map.
|
||||
|
||||
| Key | Default | Notes |
|
||||
|-----|---------|-------|
|
||||
| `map_blueprint_light` | `/static/images/floorplan-placeholder.svg` | Floor-map blueprint (light theme). Re-upload your own in Settings > Floor Map. |
|
||||
| `map_blueprint_dark` | `/static/images/floorplan-placeholder.svg` | Floor-map blueprint (dark theme). |
|
||||
| `map_width` | `3300` | Blueprint native width in pixels. |
|
||||
| `map_height` | `2550` | Blueprint native height in pixels. |
|
||||
| `map_blueprint_light` | `/static/images/floorplan-placeholder.svg` | Superseded by `maplevels.blueprintlight`. Retained for downgrade only. |
|
||||
| `map_blueprint_dark` | `/static/images/floorplan-placeholder.svg` | Superseded by `maplevels.blueprintdark`. |
|
||||
| `map_width` | `3300` | Superseded by `maplevels.mapwidth`. |
|
||||
| `map_height` | `2550` | Superseded by `maplevels.mapheight`. |
|
||||
|
||||
### integrations
|
||||
|
||||
|
||||
135
docs/FLOOR-MAP.md
Normal file
135
docs/FLOOR-MAP.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# The floor map: buildings, levels and marker positions
|
||||
|
||||
The map shows where things physically are. This page is for whoever maintains it:
|
||||
loading a floor plan, adding a second floor or a second building, and moving the
|
||||
markers when the plan changes.
|
||||
|
||||
The reasoning behind the model is in
|
||||
[ADR-017](adr/ADR-017-buildings-and-levels.md). This page is the operator's view.
|
||||
|
||||
## The model in three sentences
|
||||
|
||||
A **building** holds one or more **levels**. A level owns a floor-plan image (one
|
||||
per theme) and the pixel size of that image. A marker's position is an X and Y in
|
||||
the pixels of ONE level, so every position names the level it belongs to.
|
||||
|
||||
That last point is the whole design. Coordinates alone are meaningless: `1200,900`
|
||||
is a spot on the ground floor and a different spot on the second floor. A marker
|
||||
whose level is unknown shows as **level unknown** and is not drawn, because
|
||||
drawing it on the default plan would look completely correct while pointing at
|
||||
the wrong part of the building.
|
||||
|
||||
## Loading a floor plan
|
||||
|
||||
**Settings > Buildings and levels.**
|
||||
|
||||
1. Add a building if you do not have one. A single-building site needs exactly
|
||||
one, and it can be called anything.
|
||||
2. Add a level. Give it a name people use out loud - "Ground floor", "Mezzanine",
|
||||
"Second floor" - and a sort order that puts the list in the order someone
|
||||
would walk it. Name and order are separate on purpose: levels are not reliably
|
||||
numbered, and leaving gaps in the order lets you insert one later without
|
||||
renumbering everything.
|
||||
3. Upload the plan, per theme. A light-theme image is enough to start; if there
|
||||
is no dark version, the light one is used in both.
|
||||
|
||||
**The level takes its pixel size from the image you upload, while the level is
|
||||
still empty.** Once markers are on it, an upload whose size does not match reports
|
||||
the mismatch and changes nothing. That is deliberate - see below.
|
||||
|
||||
Supported: PNG, JPEG, GIF, WEBP and SVG. An SVG is served with headers that stop
|
||||
it executing as script, so a plan exported from a CAD tool is safe to use.
|
||||
|
||||
## Placing markers
|
||||
|
||||
Two ways, both fine:
|
||||
|
||||
- **One at a time**, from the asset's own form: the map-position picker opens on
|
||||
the level the marker is already on, and you can switch level inside the picker.
|
||||
The saved position states which drawing it is on.
|
||||
- **Many at once**, in the map editor. Filter to what you are working through -
|
||||
unplaced, needs-review, or one level - search for a specific thing, place it,
|
||||
and confirm. Placing a marker by hand counts as confirming it.
|
||||
|
||||
A location can carry a position too, which acts as the default for assets in it.
|
||||
|
||||
## When the floor plan changes
|
||||
|
||||
This is the case that needs care, and the reason the tooling exists.
|
||||
|
||||
**A new plan of the SAME dimensions**: upload it and you are done. Markers keep
|
||||
their coordinates and still line up.
|
||||
|
||||
**A new plan of DIFFERENT dimensions, or a real move**: the coordinates are pixels
|
||||
of the old image, so every marker is now in the wrong place relative to the
|
||||
drawing. Do NOT fix this by editing the level's width and height - that just
|
||||
reinterprets every existing position against a new scale.
|
||||
|
||||
Use **Recalibrate** in the map editor:
|
||||
|
||||
1. Pick two or more **landmarks** - features you can find on both the old and the
|
||||
new plan. Corners of the building, a doorway, a column. Two is the minimum;
|
||||
three or four spread across the plan is better.
|
||||
2. For each, give the old coordinates and the new ones.
|
||||
3. Review the **dry run**. It lists every marker's old and new position and flags
|
||||
anything that would land off the drawing. Nothing has changed yet.
|
||||
4. Apply it. The previous positions are snapshotted first.
|
||||
|
||||
The transform is worked out separately for the X and Y axes from your landmarks,
|
||||
and never guessed from the image dimensions. That matters in the exact case this
|
||||
was built for: a plan that gained a floor below it got taller without anything
|
||||
being rescaled, and a size-derived scale would have stretched everything
|
||||
vertically.
|
||||
|
||||
After a recalibration every affected marker is marked **needs review**, because a
|
||||
transform is a good starting guess and nothing in the coordinates can tell you
|
||||
which machines actually moved. Work the needs-review filter, correct what is off,
|
||||
and confirm as you go.
|
||||
|
||||
## Undo
|
||||
|
||||
Every bulk change snapshots the positions first, and a restore snapshots before
|
||||
it restores, so an undo is itself undoable. The history is in the map editor:
|
||||
what caused each snapshot, when, and whether it has been restored. A restore puts
|
||||
back the level and the review state, not only the coordinates.
|
||||
|
||||
Assets deleted since a snapshot are reported and skipped rather than failing the
|
||||
whole restore.
|
||||
|
||||
## Adding a second building or a second floor
|
||||
|
||||
Add it, upload its plan, then move the markers that belong on it. In the map
|
||||
editor, markers on other levels are badged, so you can see what still needs
|
||||
moving.
|
||||
|
||||
Do not stack two floors onto one tall image. It looks simpler and it makes the
|
||||
level implicit in the Y coordinate - "above 2550 means upstairs" - which nothing
|
||||
can validate and which breaks the moment either plan is redrawn.
|
||||
|
||||
## Things that are not levels
|
||||
|
||||
- **Locations** are where something is in words ("Cell 4", "Gage lab"). A level is
|
||||
which drawing. A location can name a level, but they are different things.
|
||||
- **Business units** are organisational, not physical.
|
||||
|
||||
## Known limits
|
||||
|
||||
- The printer-installer map (the standalone program that installs printers by
|
||||
clicking a floor plan) has one plan compiled into it and does not know about
|
||||
levels. It is accurate for the default level only. See
|
||||
[PRINTER-INSTALLER](PRINTER-INSTALLER.md) section 6.
|
||||
- The first-run setup wizard still asks for one site-wide blueprint; those
|
||||
settings no longer drive the map. Use Settings > Buildings and levels.
|
||||
- Map PDF export covers the level you are looking at.
|
||||
|
||||
## For integrators
|
||||
|
||||
`GET /api/maplevels` is unauthenticated - the printer installer draws a plan
|
||||
before anyone logs in - and returns every building with its levels, each level's
|
||||
blueprint paths, native size and marker count, plus which level is the default.
|
||||
Writes require an admin. Repositioning is `/api/mappositions/*` and needs
|
||||
`assets.edit`. Full parameters in [API-REFERENCE](API-REFERENCE.md) and the
|
||||
generated spec at `/api/docs`.
|
||||
|
||||
If you import positions from another system, send `levelid` with every
|
||||
coordinate - see [IMPORT-API](IMPORT-API.md).
|
||||
@@ -190,12 +190,28 @@ Common `machines` columns -> core Asset fields (same for every target plugin):
|
||||
| `serialnumber` | `serialnumber` | |
|
||||
| `machinestatusid` | `statusid` | remap via `machinestatus` -> asset statuses |
|
||||
| `businessunitid` | `businessunitid` | remap via imported business units |
|
||||
| `mapleft` | `mapx` | |
|
||||
| `maptop` | `mapy` | |
|
||||
| `mapleft` | `mapx` | pixels of a LEVEL; send `levelid` with it |
|
||||
| `maptop` | `mapy` | as above |
|
||||
| n/a | `levelid` | which floor plan the coordinates belong to (ADR-017) |
|
||||
| `machinenotes` | `notes` | |
|
||||
| `dateadded` | `createddate` | import mode only |
|
||||
| `lastupdated` | `modifieddate` | import mode only |
|
||||
|
||||
**A position needs its level.** `mapx`/`mapy` are absolute pixels in the native
|
||||
coordinate space of ONE level (ADR-017), so an import that sends coordinates
|
||||
without `levelid` produces markers the map renders as "level unknown" rather than
|
||||
drawing them on the default level. A legacy source that predates levels has one
|
||||
floor plan, so the right value is that site's default level:
|
||||
|
||||
```bash
|
||||
curl -s http://server/api/maplevels | python -c "import json,sys; print(json.load(sys.stdin)['data']['defaultlevelid'])"
|
||||
```
|
||||
|
||||
Send that `levelid` on every row carrying coordinates. If an import already ran
|
||||
without it, the levels migration's backfill is not retroactive - assign the rows
|
||||
with `POST /api/mappositions/positions`, which requires `levelid` per row for
|
||||
exactly this reason.
|
||||
|
||||
Per-plugin extension fields:
|
||||
|
||||
- **computers** (`POST /api/computers`): `hostname` <- `machines.hostname`,
|
||||
|
||||
@@ -369,6 +369,12 @@ class MeasuringToolsPlugin(BasePlugin):
|
||||
Consumed by `GET /api/pluginui/map-overlays` (disabled plugins skipped; broken
|
||||
plugin isolated in prod, re-raised in dev/test).
|
||||
|
||||
An overlay keys on `assetid` and never on coordinates, so it needs to know
|
||||
nothing about buildings or levels (ADR-017): it decorates whichever marker that
|
||||
asset already has, on whatever level that marker is on. Do not return `mapx` /
|
||||
`mapy` / `levelid` from an overlay endpoint - a position belongs to the asset,
|
||||
and a second copy of it in an overlay is one that can disagree.
|
||||
|
||||
### `get_asset_presentation() -> List[Dict]`
|
||||
|
||||
Returns asset-type presentation/routing definitions. Added in contract 0.7.0
|
||||
|
||||
@@ -13,6 +13,9 @@ exists.
|
||||
2. [OPERATE-WINDOWS](OPERATE-WINDOWS.md) - restart, logs, backups, upgrades.
|
||||
3. [CSV-IMPORT](CSV-IMPORT.md) if the site's data is in spreadsheets, or
|
||||
[IMPORT-API](IMPORT-API.md) if there is a source database to script against.
|
||||
4. [FLOOR-MAP](FLOOR-MAP.md) - load the site's floor plans and place markers.
|
||||
Also the page to read BEFORE a plan changes: it covers moving every marker
|
||||
onto a redrawn plan without doing it one at a time.
|
||||
|
||||
**Do not** follow [INSTALL-WINDOWS-IIS](INSTALL-WINDOWS-IIS.md) for a new site.
|
||||
That is the manual procedure, kept for hand-built servers that predate the
|
||||
|
||||
@@ -89,6 +89,36 @@ Confirm the app is healthy (login page renders, `/api/auth/login` returns a
|
||||
|
||||
## Version-specific notes
|
||||
|
||||
### Upgrading to v0.11.0: the floor map became buildings and levels
|
||||
|
||||
Nothing to do, and nothing moves on screen. Run both migration steps as always:
|
||||
|
||||
```bash
|
||||
flask db upgrade
|
||||
flask plugin upgrade-all
|
||||
```
|
||||
|
||||
`7d33_buildings_and_levels` creates one building ("Main") and one default level
|
||||
("Ground floor") carrying the blueprint paths and dimensions from your `map_*`
|
||||
settings, then assigns every already-placed asset and location to that level.
|
||||
Unplaced things stay unplaced. The old settings rows are deliberately left in
|
||||
place, so a downgrade still finds the blueprint.
|
||||
|
||||
Two things to know afterwards:
|
||||
|
||||
- **A second floor or building is a Settings task**, not a migration: add it under
|
||||
Settings > Buildings and levels, upload its blueprint, then move the markers
|
||||
that belong on it. `POST /api/mappositions/transform` moves a whole level's
|
||||
markers onto a redrawn plan from landmark pairs, dry run by default.
|
||||
- **Replacing a blueprint with one of DIFFERENT dimensions moves every marker on
|
||||
that level**, because the coordinates are pixels of the old size. Upload it, then
|
||||
recalibrate with landmarks rather than editing the level's width and height by
|
||||
hand. The API refuses to adopt a new size on a level that already has markers,
|
||||
for this reason.
|
||||
|
||||
If you wrote a plugin that stores map positions, see CONTRACT-STABILITY.md: the
|
||||
contract is 0.20.0 and a position must now carry its `levelid`.
|
||||
|
||||
### Upgrading to v0.5.0 or later: bundled the reference site floor plan removed
|
||||
|
||||
Versions before 0.5 shipped the the reference site facility floor-plan PNGs as the
|
||||
|
||||
Reference in New Issue
Block a user