Skip to content

feat(safety): add drill and incident log with enforced roll-call reconciliation (fixes #299) - #304

Open
MOHITKOURAV01 wants to merge 1 commit into
Sitaram8472:mainfrom
MOHITKOURAV01:feat/issue-299-safety-drills
Open

feat(safety): add drill and incident log with enforced roll-call reconciliation (fixes #299)#304
MOHITKOURAV01 wants to merge 1 commit into
Sitaram8472:mainfrom
MOHITKOURAV01:feat/issue-299-safety-drills

Conversation

@MOHITKOURAV01

Copy link
Copy Markdown
Contributor

Related Issue

Closes #299

Description

A fire drill is currently recorded as one line in a register — "14 March, 4 min 20 s, satisfactory" — and that line omits the only number that matters: how many children were not accounted for, and for how long. This adds drills and real incidents as one module, arranged entirely around a single rule.

An event cannot be closed while anybody is unaccounted for

closeEvent refuses with a 409 and the names, and there is no force flag and no admin bypass. The only way past it is to resolve each outstanding person with a note saying what happened to them — "was at the dentist, confirmed with the office", "found in the music room, escorted out". Those notes are the record. The same check guards the all-clear.

I deliberately did not add an override. An override would be used every time, by the person under the most pressure to make the number look right, and a system that can produce a clean drill report while a child is unaccounted for produces exactly the document that turns out to be worthless afterwards. If one thing in this PR gets reviewed, please make it that this rule cannot be talked out of.

The roll call has to be internally consistent

Roll calls are submitted per class by whoever is standing with that class, so counts arrive in parallel instead of through one person with a clipboard. unaccountedCount is recomputed server-side from expected − present − authorised absences, and the named list has to agree with it: a roll call claiming everybody is present while naming two missing children is rejected rather than stored. Known absences from the register are counted separately, because the child who was off sick this morning is not missing, and counting them as missing is how a real missing child gets lost in the noise.

A recount is normal under pressure, so resubmitting replaces the class's earlier roll call — but anybody already resolved keeps their resolution, since a resolution is a fact about what happened to a child and a recount must not quietly discard it.

Reconciliation is a state the event arrives at, not a button somebody presses: it flips when every named person has been accounted for, and flips back if a later roll call adds somebody.

Timings are measurements, not claims

alarmRaisedAt is stamped by the server on start, and start is conditional on the event still being planned so a second tap cannot restart the clock and shrink the evacuation time. evacuationSeconds is derived from the last roll call to arrive; reconciliationSeconds from the last resolution — that second number is the one the register line never contains. The statistics report the median evacuation time rather than the mean, so one drill in a thunderstorm does not move the figure everybody is judged against.

One model for drills and incidents

Deliberately. The evacuation, the roll call and the reconciliation are identical whether the alarm was planned or not, and two implementations would mean the code path used in a real emergency is the one that has never been exercised. Drills exist to rehearse the response; the software should be rehearsed too. A real incident is created already in progress with the alarm stamped, because nobody is going to schedule one and then press start.

Follow-up actions carry an owner and a due date, and overdue ones are counted in the statistics — a drill that produces "the west stairwell door sticks" and nothing else is a drill that will produce it again next term.

Pages / Components Added or Modified

  • backend/models/SafetyEvent.js — new
  • backend/controllers/safetyController.js — new
  • backend/routes/safetyRoutes.js — new
  • frontend/src/pages/SafetyDrills.jsx — new
  • backend/server.js — mounts /api/safety (additions only)
  • frontend/src/App.jsx — adds the /safety route (additions only)

Screenshots

/safety gives a class teacher one thing during an event: the roll call form for their class, submittable in about four taps, because on a field at 09:12 nothing else is usable. The coordinator gets the live board — classes in, classes still to report, a running evacuation clock, and unaccounted names at the top in red with an Account for button each. The close button is disabled whenever the server says somebody is outstanding, with the reason printed underneath rather than hidden in an error toast; the point is to show what has to happen first, not just to block the click.

Images omitted because the board needs a live event to show anything; happy to add them.

Checklist

  • npm run lint — 18 problems, identical to main, all pre-existing and outside this PR's files
  • npx vite build succeeds
  • node --check clean on every backend file added or changed
  • Responsive layout verified (the board stacks under the event list on mobile, the roll-call table scrolls)
  • No console errors or warnings
  • Additions-only in the shared files, on anchors no other open PR uses; merged against main and each of my other open PRs to confirm no conflict

…nciliation

An event cannot be closed while anybody is unaccounted for. closeEvent
refuses with the names and there is no force flag and no admin bypass; the
only way forward is to resolve each person with a note saying what happened
to them. An override would be used every time, by whoever is under the most
pressure to make the number look right.

Roll calls are submitted per class so counts arrive in parallel rather than
through one clipboard. unaccountedCount is recomputed server-side from
expected minus present minus authorised absences, and the named list has to
agree with it - a roll call claiming everyone is present while naming two
missing children is refused rather than filed. A recount keeps resolutions
already recorded instead of discarding them.

alarmRaisedAt is stamped by the server on start, and start is conditional on
the event still being planned, so evacuation and reconciliation times are
measurements rather than claims. Drills and real incidents share one model
so the code path used in an emergency is the one that gets rehearsed.

Closes Sitaram8472#299
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Emergency drills & safety incidents with enforced roll-call reconciliation

1 participant