Skip to content

feat(health): add student health records and infirmary visit log (fixes #264) - #269

Open
MOHITKOURAV01 wants to merge 2 commits into
Sitaram8472:mainfrom
MOHITKOURAV01:feat/issue-264-health-records
Open

feat(health): add student health records and infirmary visit log (fixes #264)#269
MOHITKOURAV01 wants to merge 2 commits into
Sitaram8472:mainfrom
MOHITKOURAV01:feat/issue-264-health-records

Conversation

@MOHITKOURAV01

Copy link
Copy Markdown
Contributor

Related Issue

Closes #264

Description

Adds a health profile per student and an append-only infirmary visit log — blood group, allergies, chronic conditions, vaccinations and emergency contacts on one side; complaints, vitals, medication given and outcomes on the other.

A student opening /health sees their own card: a red critical-alerts strip at the top, blood group and BMI with a plain-language band rather than a bare number, allergy and condition lists colour-coded by severity, a vaccination timeline flagging what is now due, emergency contacts, and their own visit history.

The nurse gets a Dashboard tab: today's visits, a record-a-visit form with vitals and medication rows, a students-with-severe-allergies strip, the week's most common complaints, and follow-ups due.

Things worth a reviewer's attention

This is the most sensitive data the application would hold, so a few decisions are deliberately blunt rather than clever.

There is no "any authenticated user" read path in this module. Every read of someone else's record goes through a single assertMayAccess gate — the owning student, or staff/admin. Keeping it in one function means a new endpoint cannot ship without the check by forgetting to add it.

There is no DELETE route for a visit. The log is append-only. A medical log that can be tidied up after the fact is not worth having if it is ever questioned.

attendedBy is taken from the token and ignored if sent in the body. A record saying who treated a child is worthless if the child's own request can set it.

A child cannot be sent home without the parent being told. An outcome of sent-home or referred-to-hospital is rejected at the model level unless parentNotified is set. This is the specific failure the log exists to prevent, so it is a hard validation rather than a UI nudge.

Temperature is bounded to 30–45 °C. The point is not the physiological range — it is that 98.6 typed into a Celsius field gets rejected with a message naming the likely cause, instead of being filed as a normal observation and skewing every subsequent report.

criticalAlerts is a virtual over the allergy and condition lists rather than a stored field, so the strip the nurse reads before treating cannot drift from the data underneath it. The panel fetches it on blur of the student-id field, so severe allergies are on screen before the treatment is written, not after.

Exactly one primary emergency contact is enforced; two is a 400. With a single contact and none flagged, the model picks it rather than making the office tick a box.

A note on Mongoose 9

Mongoose 9 has dropped callback-style middleware. pre('validate', function (next) {...}) is silently skipped, and next(err) throws next is not a function. Every hook here is an async function that throws. Flagging it because the failure is silent — a hook written the old way looks fine and simply never runs, which for the parent-notification guard above would be a genuinely bad outcome.

Pages / Components Added or Modified

  • backend/models/HealthProfile.js — new; allergies, conditions, vaccinations, contacts, criticalAlerts/bmi/age virtuals
  • backend/models/InfirmaryVisit.js — new; vitals, medications, outcome guard
  • backend/controllers/healthController.js — new; 10 handlers
  • backend/routes/healthRoutes.js — new; mounted at /api/health, no delete route by design
  • backend/server.js — two lines to register the router
  • frontend/src/pages/HealthRecord.jsx — new; student page at /health
  • frontend/src/components/teacher/InfirmaryPanel.jsx — new; Dashboard tab
  • frontend/src/App.jsx — lazy import plus the /health route
  • frontend/src/pages/TeacherDashboard.jsx — one tab entry

Verification

  • node --check passes on every new backend file
  • npx eslint clean on the new frontend files
  • Model guards exercised directly: BMI and age derivation, criticalAlerts picking up severe allergies and active conditions only, two primary contacts rejected, lone contact auto-promoted, future date of birth, invalid blood group, a next-dose date before the administered date, overdue-vaccination filtering, both notification-requiring outcomes rejected without parentNotified, 98.6 rejected as a Celsius temperature, malformed blood pressure rejected while an omitted one passes, a future visit rejected, follow-up without a date rejected, and a missing attendedBy rejected

Checklist

  • Lint passes with no new errors
  • Model guards exercised directly, including every access and safety rule above
  • Tested against a live MongoDB instance — no database credentials available to me, so the HTTP layer has not been run end to end
  • Responsive layout (vitals tiles and the two-column sections collapse on mobile)
  • No new console errors or warnings
  • Screenshots — omitted, the pages need a seeded profile and visits to show anything

Notes

Purely additive; no existing model or controller is touched. If the project later adds a parent role, the assertMayAccess gate is the one function that needs to learn about it — which was the point of centralising it.

Adds a health profile per student and an append-only infirmary visit log.

Access is deliberately blunt because this is the most sensitive data the app
holds: the owning student, or staff/admin. Every read of someone else's record
goes through one `assertMayAccess` gate, so a new endpoint cannot ship without
the check. There is no delete route for a visit — the log is append-only,
which is the only way it stays worth anything if it is ever questioned.

`attendedBy` is taken from the token and ignored if sent in the body; an entry
saying who treated a child is worthless if the child's own request can set it.
An outcome of sent-home or referred-to-hospital is refused unless the parent
has been notified, which is the failure this log exists to prevent.

Temperature is bounded to 30-45°C so a Fahrenheit reading typed into a Celsius
field is rejected rather than filed as a normal observation. `criticalAlerts`
is a virtual over the allergy and condition lists, so the strip the nurse reads
before treating cannot drift from the data under it.

Closes Sitaram8472#264
@MOHITKOURAV01

Copy link
Copy Markdown
Contributor Author

Merge note for whoever lands these

This PR is one of five sibling feature PRs (#267#271). Each is independent in its own right — separate models, controllers, routers, pages and panels, with no shared logic — but all five register themselves in the same three files:

  • backend/server.js — one require and one app.use
  • frontend/src/App.jsx — one lazy import and one <Route>
  • frontend/src/pages/TeacherDashboard.jsx — one import, one tab entry, one render line

So the first of the five to merge will go in clean, and the remaining four will then conflict in exactly those three files. Nothing else conflicts.

I simulated the full sequential merge locally and verified the combined result. Two things worth passing on:

  1. The resolution is "keep both sides" in all cases — the additions are independent and order does not matter.

  2. App.jsx needs care. A naive keep-both resolution (or a union merge driver) silently produces invalid JSX: each route block ends with the same two lines

        </RoleProtectedRoute>
      } />

    which get treated as shared trailing context and kept only once, so the earlier routes lose their closing tags. The build then fails with Unexpected closing "Routes" tag does not match opening "RoleProtectedRoute" tag. Each <Route> needs its own closing pair. I hit this exactly while testing, so it is worth knowing before it looks like one of the PRs is broken.

Verified on the fully merged tree (all five together):

  • node --check backend/server.js passes; all five routers load and all nine new models register with no name collisions
  • npx vite build succeeds with all five pages code-split into their own chunks
  • npx eslint src/ reports 18 problems on the merged tree and 18 on main — these five modules add none

Happy to rebase and push the resolution on this branch as soon as the first sibling lands; just say which order you want them in.

The profile and visit list endpoints passed the raw query string to
`new RegExp`, so the search term was silently reinterpreted as a pattern.

On this module that matters more than most: a search of `.*` would return
every student who has a health profile, which is the roster of children with
recorded medical conditions. A term like `(a+)+$` triggered catastrophic
backtracking, roughly two minutes of pinned CPU for a 33-character query
string, and an unbalanced `[` threw and surfaced as a 500.

Metacharacters are now escaped so terms match literally, capped at 80
characters.
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]: Student Health Records & Infirmary Visit Log

1 participant