# Business rules — crimemapping

Evidence date: 2026-10-18. Source: `git@bitbucket.org:overdrive/crimemapping.git`, `master` at `5927ab4` (2023-12-06).

Rules below are what this client enforces or displays. Server rules live in `crimemapping_website` and are cited only where this client can violate them or where the two trees disagree. Nothing here is a new product decision.

## Access

| ID | Rule | Evidence | Provenance |
|---|---|---|---|
| BR-001 | The app shows public routes when `currentUser` is unset and private routes when it is set | `App.js` | **Implemented** |
| BR-002 | Signed-out visits to `/`, `/report-crime`, `/all-crimes`, or `/view-crime` go to `/unauth`, then to `/login` after 1.5 seconds | `PublicRoutes.js`, `UnAuth.js` | **Implemented** |
| BR-003 | A signed-in visit to `/login`, `/signup`, or `/forgot-password` goes to `/` | `PrivateRoutes.js` | **Implemented** |
| BR-004 | `PrivateRoute` sends a signed-out user to `/login` | `PrivateRoute.js` | **Implemented**. On the live tree `App.js` already withholds private routes when `currentUser` is unset, so this redirect is a second check |
| BR-005 | Login stores `res.data.token` in `localStorage` under `loginToken` when `res.data.id !== ""` | `AuthContext.loginCallback` | **Implemented** |
| BR-006 | Logout removes `loginToken` only after the logout request's callback runs | `AuthContext.logoutCallback` | **Implemented** |
| BR-007 | Home does not block a user whose `active` flag is false. The flag is stored and not read | `PrivateRoutes.getUserDetailsHandler`, `Home.js` | **Implemented**. **Inferred** that inactive users are not rejected by this client |
| BR-008 | The live home screen does not check `role_id` | `Home.js` | **Implemented** |

## Signup and password

| ID | Rule | Evidence | Provenance |
|---|---|---|---|
| BR-009 | First name, last name, email, phone, password, and confirm password are required by the form controls | `Signup.js` | **Implemented** |
| BR-010 | Signup does not submit when the two password fields differ. Message: "Passwords do not match" | `Signup.js` | **Implemented** |
| BR-011 | A password with length less than 7 is invalid. Length 7 or more is valid. The message says "Password length should be more than 7" | `src/lib/validation.js` | **Implemented**. The message and the comparison disagree |
| BR-012 | The signup button stays disabled until that validation has resolved valid | `Signup.js` (`isNotValid` starts `true`) | **Implemented** |
| BR-013 | When the phone country code is `+27` and the national number length is less than 10, the client inserts a leading `0` | `AuthContext.signupUser` | **Implemented**. **Inferred** South African local format. The code does not name the country beyond `+27` and the PhoneInput default `isoCode: "za"` |
| BR-014 | Login cellphone input `maxLength` is 10. Signup does not apply that maxlength | `LoginForm.js`, `Signup.js` | **Implemented** |
| BR-015 | Signup posts `organisation_id` from the selected API organisation. It does not use the Silicon Overdrive row in `appConfig.js` | `Signup.js` | **Implemented** |
| BR-016 | HTTP 201 shows `messages.success` and leaves the page after 3 seconds. HTTP 403 shows that same field as an error | `Signup.signupUserCallback` | **Implemented** |

## Crime report

| ID | Rule | Evidence | Provenance |
|---|---|---|---|
| BR-017 | "Create Crime Event" stays disabled until at least one incident is selected | `CreateEvent.js` (`logEvent`) | **Implemented** |
| BR-018 | Create asks for confirmation. The modal text is "Are you sue you want to report this crime?" | `CreateEvent.js` | **Implemented** |
| BR-019 | Cancel reports "Event creation Cancelled" and returns home after 3 seconds. It does not post | `CreateEvent.js` | **Implemented** |
| BR-020 | Success text is "Event created". Navigation to `/` waits 3 seconds | `CreateEvent.js` | **Implemented** |
| BR-021 | If `res.data.error !== null`, the client also shows "Something went wrong" and navigates home immediately, and still sets the success flag | `addCrimeEntryCallback` | **Implemented**. A body without an `error` property takes this path because `undefined !== null` |
| BR-022 | `report_date` is the picker value formatted with `toLocaleString('en-US', { hour12: false })` | `CreateEvent.js` | **Implemented** |
| BR-023 | Selected incidents are sent as a JSON object keyed by index, not as an array | `CreateEvent.js` | **Implemented** |
| BR-024 | Notes are optional. An empty note is still appended | `CreateEvent.js` | **Implemented** |
| BR-025 | The map marker is the centre of the map. Dragging updates latitude and longitude | `MapMain.updateMarkerPosition` | **Implemented** |
| BR-026 | Address lookup asks for `ROOFTOP`. On error it sets the geocoder to `APPROXIMATE` and does not request again | `CreateEvent.getAddress` | **Implemented** |
| BR-027 | If geolocation fails, coordinates are latitude `-29.555`, longitude `25.107`, accuracy `0`, `trueposition` false, and the map zoom is 4 instead of 15 | `PrivateRoutes.js`, `MapMain.js` | **Implemented** |
| BR-028 | The report screen does not restrict photo type or size. Files are appended as `files[]` | `CreateEvent.js` | **Implemented** |
| BR-029 | The report is sent with the signed-in user's `user_id` and `organisation_id` | `CreateEvent.js` | **Implemented** |

The sibling API accepts jpeg, jpg, and png up to 12097152 bytes and splits `report_date` on a comma. Those checks are not in this client. Provenance of the server checks: **Documented** in `crimemapping_website`. They are not restated as rules of this repository.

## Roles that this client does not apply

| ID | Rule | Evidence | Provenance |
|---|---|---|---|
| BR-030 | `appConfig.js` names role `1` "Field Operator" and role `2` "Data Manager", and lists one organisation, "Silicon Overdrive", id `1`, label `silicon_overdrive` | `src/lib/appConfig.js` | **Implemented** constant. No routed screen reads it |
| BR-031 | A commented home block would show "Review Crimes" only when `role_id === "2"` (string compare) | `Home.js` | **Implemented** comment. Not executed |
| BR-032 | Unrouted review allows the page only when Firestore `accounts.userRole` is number `2` | `AllEvents.js` | **Implemented** in an unrouted file. Disagrees with BR-031's string compare and with the admin role ids in `crimemapping_website` |

Which catalogue is authoritative is **Unknown / Requires Confirmation**.

## Unrouted review rules

These run only if the commented routes are restored, and the current files would throw before the rules because `useFirestore` is not provided. Recorded so the leftover behaviour is not mistaken for a live rule.

| ID | Rule | Evidence | Provenance |
|---|---|---|---|
| BR-033 | Merge stays disabled until more than one event is selected | `AllEvents.js` | **Implemented** in unrouted code |
| BR-034 | A merge stores the average of the selected latitudes and the average of the selected longitudes, `review: true`, `merged: true`, `edited: false` | `AllEvents.mergeSubmitHandler` | **Implemented** in unrouted code |
| BR-035 | Merged sources are written to Firestore collection `archive` and deleted from `events` | `AllEvents.js` | **Implemented** in unrouted code |
| BR-036 | The accordion image control accepts `image/jpeg` only. The error text says "please use a different file (*.jpeg/*.png)" | `components/Accordion/Default.js` | **Implemented** in unrouted code. The message mentions png. The check does not |
| BR-037 | Setting the review control writes `review` on the Firestore `events` document | `Default.js` `updateEntryState` | **Implemented** in unrouted code |

## Disagreements

- Password message versus `length < 7` (BR-011).
- README Firebase setup versus empty `firebase-config.js` and API auth.
- `appConfig` role names versus `crimemapping_website` (Developer at id 1, organisation scope at id 4).
- This client's signup sends no bearer token. The admin feature note says user create is behind JWT.
- Forgot-password screen versus commented `resetPassword` and empty API methods.
- Crime-create callback treats a missing `error` field as failure and still shows success (BR-021).

## Human confirmation

- The password rule that should be tested: 7 characters, or more than 7.
- Whether an inactive user may log in.
- Whether role 2 is still "Data Manager", and whether review should obey it.
- Whether a failed geolocation may submit the fixed fallback coordinates as the crime location.
- Whether a crime response without `error` is success.
