Repository navigation
feat(consultations): consultation note sign-off with append-only addenda - #144
Oyinkans0la12 wants to merge 9 commits into
Conversation
Clinical notes are legal records: a consultation is a draft until it is signed off, and immutable afterwards. Corrections are ConsultationAddendum rows, not edits -- the same reasoning as hard rule 1 for prescriptions and lab orders. Adds Consultation.signedAt + doctorProfileId, the ConsultationAddendum model, and POST /consultations, GET/PATCH /consultations/:id, POST /consultations/:id/sign-off and POST /consultations/:id/addenda. Immutability is enforced twice on purpose: repository writes are scoped to signedAt IS NULL (a signed note simply doesn't match), and a BEFORE UPDATE trigger in the migration rejects any change to notes/outcome/signedAt on a signed row -- including a direct Prisma update(), which the integration test does exactly as the issue's reviewer would. signedAt is part of the trigger's guard so a signed note can't be un-signed back into mutability. Only the authoring doctor can read, edit, sign off or add an addendum. An interval job (src/jobs, a plain interval per the no-queue-yet rule) auto-signs-off drafts older than CONSULTATION_AUTO_SIGNOFF_HOURS. Flagged rather than invented: the auto-sign-off window is measured from createdAt because the appointment link (DocForum#4) isn't modelled yet, and the referral-snapshot rule -- referrals may only be created from a signed-off consultation -- is documented for Phase 3 to implement. Docs updated in the same change: ARCHITECTURE.md §1/§4.3/§4.4, a new hard rule 7 in ARCHITECTURE_ESSENTIALS.md, docs/api/README.md, ROADMAP.md. Refs DocForum#64
|
@Oyinkans0la12 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
@Oyinkans0la12 pls fix CI |
|
CI is failing on this PR. Typecheck fails: Please push a fix and CI will re-run. Before pushing, run |
|
This PR has merge conflicts with |
|
@Oyinkans0la12 Thanks for the PR. It can't be merged yet. To unblock it: CI is failing. Here's what broke:
Please fix these errors and run the same steps locally before pushing ( Merge conflicts: this branch conflicts with Push to this same branch and CI will re-run. Once it's green and conflict-free, it'll be reviewed for merge. |
|
@Oyinkans0la12 This PR now has merge conflicts with git fetch upstream # upstream = https://github.com/DocForum/docforum-core.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-leaseThe usual culprits are shared files like Automated message. It won't repeat unless the PR conflicts again after your next push. |
|
@Oyinkans0la12 CI still fails at typecheck. The Prisma models this PR uses aren't in the schema, and the config section is missing: Add the Push to this branch and CI will re-run. Once it's green it can be merged. |
The main merge resolution dropped the Consultation doctorProfileId/signedAt fields, the ConsultationAddendum model, the config block, the /consultations router mount and the auto-sign-off job startup, so Prisma could not generate and the controllers were unreachable. Consultations are now created against the appointment they close (the FK added in DocForum#170): creation resolves it through the appointments service so only the authoring doctor can open a note, and orders/fixtures seed it.
|
@Oyinkans0la12 Thanks for rebasing. The malware scan passed, but the backend CI now fails at
Then run |
…off routes The merge with main duplicated outcome/notes/createdAt on Consultation and left ConsultationAddendum undefined, which fails `prisma generate` (P1012) and therefore CI. Also drop the duplicated consultationsRouter import/mount in app.ts left by the same merge. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
…with-append-only' into feat/consultation-note-sign-off-with-append-only
|
@Oyinkans0la12 CI is failing on this branch, so it can't be merged yet. The errors from the latest run: Backend (typecheck + test + build)
|
|
@Oyinkans0la12 This PR now has merge conflicts with git fetch upstream # upstream = https://github.com/DocForum/docforum-core.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-leaseThe usual culprits are shared files like Automated message. It won't repeat unless the PR conflicts again after your next push. |
What changed
Implements consultation note sign-off from #64:
Consultation.signedAt+doctorProfileId, aConsultationAddendummodel, andPOST /consultations,GET /consultations/:id,PATCH /consultations/:id,POST /consultations/:id/sign-off,POST /consultations/:id/addenda, plus anauto-sign-off job for drafts left editable too long.
Closes #64
Why
Clinical notes are legal records. Editing a note in place after sign-off
destroys the record of what the doctor knew at the time — the same reason
prescriptions are append-only. Doctors still need to correct mistakes, which is
what addenda are for.
Implementation notes
WHERE id = ? AND signedAt IS NULL, so a signed note simply doesn't match —but that only binds callers who go through the repository. The migration adds
a
BEFORE UPDATEtrigger onConsultationthat rejects any change tonotes,outcome, orsignedAtonce the row is signed off. The integrationtest does exactly what the issue's review section asks: it updates a signed
note through Prisma directly and expects a failure.
signedAtis in the trigger's guard too, so a signed note can't beun-signed back into mutability.
only, read back oldest-first. Adding one before sign-off is a
409: while thenote is still a draft, an addendum would just be a confusing second draft.
Another doctor's consultation id is a
404(not403) so an id can't be usedto confirm whose care thread it is.
WHERE signedAt IS NULL), so a secondsign-off — or a concurrent one — is a 0-row update and a
409, never anoverwrite of the original timestamp.
src/server.ts(src/jobs,per the "no queue until a real job needs one" rule), and is exposed as a
function so tests drive it deterministically instead of waiting on a timer.
Schema change (proposed)
Consultation:+ doctorProfileId (FK -> DoctorProfile),+ signedAt(nullable) — ARCHITECTURE.md §4.3.
ConsultationAddendum:id, consultationId, authorDoctorProfileId, body, createdAt— §4.3.Migration:
backend/prisma/migrations/20261015182926_consultation_note_sign_off/— generated with
scripts/gen-migration.mjs(real Postgres diff), with thetrigger SQL appended by hand and commented in place.
New hard rule recorded in
ARCHITECTURE_ESSENTIALS.md(rule 7): never edit aconsultation note/outcome once signed off; corrections are addenda.
Decisions the issue left open (documented, not guessed silently)
consultation's notes at creation (hard rule 3), so allowing it on a draft
would freeze a moving note — or let two referrals capture different contents.
The rule is: a referral may only be created from a signed-off consultation.
Recorded in ARCHITECTURE.md §4.3 and §4.4. It is not enforced in code yet
because referral creation doesn't exist yet (Phase 3, [Phase 3] Referral model + creation from consultation outcome #5) — flagged as the
thing [Phase 3] Referral model + creation from consultation outcome #5 must implement rather than pretended done here.
createdAt, not from appointmentcompletion.
Consultation.appointmentIddoesn't exist (issue [Phase 2] Consultation record + outcome #4 owns it),so "N hours after the appointment completes" isn't literally implementable
yet. Flagged in the service, ARCHITECTURE.md §4.3 and
ARCHITECTURE_ESSENTIALS.md known gaps.
link; today the routes are doctor-only and author-only. Flagged, not invented.
Docs updated in the same PR
ARCHITECTURE.md(§1 principle 4, §4.3 with the addendum model and design note,§4.4 referral/sign-off rule),
ARCHITECTURE_ESSENTIALS.md(hard rule 7, datamodel map, three new known gaps),
docs/api/README.md(all five endpoints),ROADMAP.md(Phase 2 item + changelog).backend/src/jobs/README.mddocumentsthe first real job.
Tests
npm run typecheckandnpm run buildclean.npm run test→ 21 passed.npm run test:integration→ 12 passed (1 existing booking + 11 new).Integration (
tests/integration/consultations.test.mts, real Postgres):draft is editable; empty PATCH is a
400; another doctor gets404for read,sign-off and addendum (and the note is demonstrably untouched by those
attempts); addendum before sign-off is
409; sign-off works, a second one is409, editing afterwards is409; direct Prisma updates ofnotes,outcomeandsignedAton a signed row all throw, and the row is verifiedunchanged afterwards; addenda after sign-off append in order and leave the
note itself alone; auto sign-off picks up a backdated draft and makes it
genuinely immutable; and a fresh draft is left alone by the same job.
Cross-PR note
ARCHITECTURE.md§4 anddocs/api/README.mdare also touched by the openpassword-reset (#140), 2FA (#142) and session-management (#143) PRs — none of
them touches §4.3, so this one should rebase cleanly, but the §4 subsection
numbering may need a line moved.