7 User Stories — M0 (Platform Foundation)
Delivered stories, keyed to PRD Appendix A and grouped by capability
Milestone M0 — Platform Foundation (Jul 2025) delivered the tenant model, the imagery intake, and the three surfaces an operator lives in — the 3D scene, the dashboard and the gallery. These stories were written after delivery, on 2026-09-10, so that every Delivered requirement has a behaviour someone can hold it to; they describe what the platform does today, in the persona’s words, and each criterion names something a test or a certification row can check. Status labels mirror PRD Appendix A.
8 Evidence Intake
8.0.1 US-M0-01 — Ingest a campaign’s imagery with its provenance intact
FR-EVI-01 (Delivered)
As a Data Explorer, I want to load a campaign’s RGB inspection imagery into one dataset with each image’s capture time and GPS position preserved, so that everything downstream — the scene, the gallery, an engineer’s finding — can say where and when a picture was taken.
- Given a set of drone images with EXIF headers, when I ingest them into a dataset, then each image record carries its capture timestamp and GPS position from the header, and the dataset reports how many images it holds.
- Given an image with no GPS in its header, when it is ingested, then it is kept in the dataset and marked as unpositioned — never dropped silently, never given a fabricated position.
- Given two datasets in one organisation, when I browse either, then I see only that dataset’s images; an image belongs to exactly one dataset.
9 Application Surface
9.0.1 US-M0-02 — See the campaign in space
FR-APP-07 (Delivered)
As an Integrity Engineer, I want a browser-based 3D geospatial scene of the site with the campaign’s imagery placed where it was captured, so that I can orient a picture to the plant without leaving the application.
- Given a dataset with positioned images, when I open its 3D view, then the scene renders in the browser with each positioned image marked at its GPS location.
- Given a marked image in the scene, when I select it, then I see the image and its metadata in place, and can continue to the gallery or the viewer from there.
- Given a dataset whose images carry no position, when I open the 3D view, then the scene loads with a clear statement that nothing could be placed, rather than an empty globe with no explanation.
9.0.2 US-M0-03 — Know where every campaign stands
FR-APP-08 (Delivered)
As a Data Explorer, I want one dashboard listing my organisations, their campaigns and the anomalies found so far, so that I can see at a glance which campaign needs attention without opening each one.
- Given membership of one or more organisations, when I open the dashboard, then I see every campaign I can access with its image count and anomaly count, and nothing from organisations I am not a member of.
- Given a campaign on the dashboard, when I select it, then I land in that campaign’s workspace with the same counts.
- Given an organisation with no campaigns yet, when I open the dashboard, then it is listed as empty rather than omitted.
9.0.3 US-M0-04 — Review what the analysis found, picture by picture
FR-APP-09 (Delivered)
As an On-call Integrity Engineer, I want a gallery of a campaign’s images with the detected anomalies drawn as bounding boxes and their annotations alongside, so that I can review findings against the evidence rather than against a list.
- Given a campaign with analysed images, when I open its gallery, then each image shows its anomaly bounding boxes with the annotation’s category and confidence.
- Given the gallery, when I filter by anomaly category, then only images carrying that category remain and the count updates to match.
- Given an image with no detections, when I open it, then it is shown clean with an explicit “no anomalies detected” state — an empty overlay is not an error.
10 Security & Compliance
10.0.1 US-M0-05 — Sign in once; see only my organisations
FR-SEC-04 (Delivered)
As an Operations Supervisor, I want every user to sign in to one account whose organisation memberships and roles decide what they can see and change, so that a contractor on one site cannot reach another site’s data by accident or by crafting a request.
- Given a signed-in user, when any page or API route loads campaign data, then only rows from organisations that user belongs to are returned — enforced in the database by row-level security, not only in the interface.
- Given a user with a viewer role in an organisation, when they attempt a write (an annotation, a finding, an export), then it is refused with a permission error and nothing changes.
- Given an expired or tampered session token, when a request is made, then it is rejected and the user is sent to sign in again — never served another user’s data.