diff --git a/data/forms-content-review.json b/data/forms-content-review.json index c157f97565..54d3e24202 100644 --- a/data/forms-content-review.json +++ b/data/forms-content-review.json @@ -2,7 +2,7 @@ "exportMetadata": { "format": "forms-operational-content-review", "formatVersion": 1, - "note": "Review state for the operational guidance in data/forms-catalog.json: purpose, maker, threshold, clock, authority boundaries, traps, pre-use checks and safety pearl. Josh, PsychSift's clinical owner, confirmed the submitted clinical sign-offs on 2 October 2026; the public reviewer attribution PsychSift refers to his review. Reviewed entries carry reviewer identity, the original review timestamp and an exact content pin. Form 2 was revised after the original review and signed on 2026-10-06. Section summaries are reviewed separately in data/forms-act-section-cues.json and data/mha-2014-sections.json." + "note": "Review state for the operational guidance in data/forms-catalog.json: purpose, maker, threshold, clock, authority boundaries, traps, pre-use checks and safety pearl. Josh, PsychSift's clinical owner, confirmed the submitted clinical sign-offs on 2 October 2026; the public reviewer attribution Psych Sift refers to his review. Reviewed entries carry reviewer identity, the original review timestamp and an exact content pin. Form 2 was revised after the original review and signed on 2026-10-06. Section summaries are reviewed separately in data/forms-act-section-cues.json and data/mha-2014-sections.json." }, "forms": [ { @@ -11,7 +11,7 @@ "sections": ["26", "36", "40", "41", "42", "43", "44"], "contextualSections": ["25", "31", "37", "39", "45", "46", "547"], "basis": "Drafted from Mental Health Act 2014 (WA) s 26, s 36, s 40, s 41, s 42, s 43, s 44 with s 25, s 31, s 37, s 39, s 45, s 46, s 547 as context, and from the approved Form 1A as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "eb950d3c48dba095ef61d7b0645709d90316ed475b5eb6602b670e62e6e07ad1" }, @@ -21,7 +21,7 @@ "sections": ["41", "42"], "contextualSections": ["26", "36"], "basis": "Drafted from Mental Health Act 2014 (WA) s 41, s 42 with s 26, s 36 as context, and from the approved Form 1A attachment as listed on the Office of the Chief Psychiatrist register. Section basis: The attachment carries referral information given in confidence by someone other than the person. s 41(c) and (d) require the referral to set out the information the suspicion rests on and to distinguish its source; s 42(2) is the exception that keeps information supplied on condition of non-disclosure from the person.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "50c9d0d7b072e076da8830c54f6c35ff608f7a7ca338721701b9a2f96db79717" }, @@ -31,7 +31,7 @@ "sections": ["45", "46"], "contextualSections": ["26", "41", "44"], "basis": "Drafted from Mental Health Act 2014 (WA) s 45, s 46 with s 26, s 41, s 44 as context, and from the approved Form 1B as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "933af3a84c6ce4b698e314fed0553bccec0ca8c54b2797a791ce88c93df048a7" }, @@ -41,7 +41,7 @@ "sections": ["34", "35"], "contextualSections": ["25", "36"], "basis": "Drafted from Mental Health Act 2014 (WA) s 34, s 35 with s 25, s 36 as context, and from the approved Form 2 as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-06T09:19:27.897Z", "reviewedContentSha256": "9f8dd86e02761c72372037d1e3b1046c70e4444e1177c3b2c870c2c1581bc098" }, @@ -51,7 +51,7 @@ "sections": ["28", "62"], "contextualSections": ["26", "61"], "basis": "Drafted from Mental Health Act 2014 (WA) s 28, s 62 with s 26, s 61 as context, and from the approved Form 3A as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "04f66d823c0f88123e5c2c8aeeaa3609f1c36515769abf6955083c6a6804a7c3" }, @@ -61,7 +61,7 @@ "sections": ["28", "59", "62"], "contextualSections": ["26", "58", "61"], "basis": "Drafted from Mental Health Act 2014 (WA) s 28, s 59, s 62 with s 26, s 58, s 61 as context, and from the approved Form 3B as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "11ddcb0d7750d907c29ee1e2452e78c886ca38846f08bf6eeff2e93d300c46c2" }, @@ -71,7 +71,7 @@ "sections": ["55", "56"], "contextualSections": ["52", "53"], "basis": "Drafted from Mental Health Act 2014 (WA) s 55, s 56 with s 52, s 53 as context, and from the approved Form 3C as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:14:18.043Z", "reviewedContentSha256": "8bf94f175892125cf8bd077a2eab041930175c94f260b39bb82426c5954d847d" }, @@ -81,7 +81,7 @@ "sections": ["61"], "contextualSections": ["62", "63", "69"], "basis": "Drafted from Mental Health Act 2014 (WA) s 61 with s 62, s 63, s 69 as context, and from the approved Form 3D as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "302c105b548a4248246905d2bb839cec01295b0edbea5c09ee2d4688f27ffb58" }, @@ -91,7 +91,7 @@ "sections": ["55", "56", "61", "72"], "contextualSections": [], "basis": "Drafted from Mental Health Act 2014 (WA) s 55, s 56, s 61, s 72, and from the approved Form 3E as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "8418a51499269d6a8a0ee0b7816d6743941caff7b4dc028d24118f4216cafacb" }, @@ -101,7 +101,7 @@ "sections": ["29", "63", "67", "92", "112", "129", "133", "148", "150", "154"], "contextualSections": ["152", "153"], "basis": "Drafted from Mental Health Act 2014 (WA) s 29, s 63, s 67, s 92, s 112, s 129, s 133, s 148, s 150, s 154 with s 152, s 153 as context, and from the approved Form 4A as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "00913d4155717b2da2d3635cbfcbd8427af5dd66849f618fb0b81a286d2ee9b2" }, @@ -111,7 +111,7 @@ "sections": ["152"], "contextualSections": ["148", "150"], "basis": "Drafted from Mental Health Act 2014 (WA) s 152 with s 148, s 150 as context, and from the approved Form 4B as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "8db3f7db90766bb5f8faf1774501c19f985b4f97c9342f95528ac0c19e7545dc" }, @@ -121,7 +121,7 @@ "sections": ["66", "91"], "contextualSections": ["67", "92"], "basis": "Drafted from Mental Health Act 2014 (WA) s 66, s 91 with s 67, s 92 as context, and from the approved Form 4C as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "94d6a2b10922bb21b2b6ba92c55194bbae5a34baa81fd9045db6bd12412cfed6" }, @@ -131,7 +131,7 @@ "sections": ["555"], "contextualSections": ["551"], "basis": "Drafted from Mental Health Act 2014 (WA) s 555 with s 551 as context, and from the approved Form 4D as listed on the Office of the Chief Psychiatrist register. Section basis: s 555 is the interstate transfer order: the person in charge of a hospital, with the Chief Psychiatrist's written approval, may order a State inpatient's transfer to an interstate mental health service.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "37c313bfe24c80353139d302e83e220fa88eef1ecb4e599f7318fccbeda054d4" }, @@ -141,7 +141,7 @@ "sections": ["557"], "contextualSections": ["551"], "basis": "Drafted from Mental Health Act 2014 (WA) s 557 with s 551 as context, and from the approved Form 4E as listed on the Office of the Chief Psychiatrist register. Section basis: s 557 creates the transfer approval order, by which the person in charge of a hospital, with the Chief Psychiatrist's written consent, approves an interstate inpatient's transfer to that hospital.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "e48c4707ad2328cd5086fe9e59dfac23b13a63cf123413294ade27fdaecb53e0" }, @@ -151,7 +151,7 @@ "sections": ["55", "56", "61", "72", "75", "76"], "contextualSections": ["25", "114", "115", "116", "117", "120", "123", "131"], "basis": "Drafted from Mental Health Act 2014 (WA) s 55, s 56, s 61, s 72, s 75, s 76 with s 25, s 114, s 115, s 116, s 117, s 120, s 123, s 131 as context, and from the approved Form 5A as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "97a7ed35ee582dd764fee035ce0238afec82e3ffeb8046f69c45c279351b2e07" }, @@ -161,7 +161,7 @@ "sections": ["121"], "contextualSections": ["118", "120"], "basis": "Drafted from Mental Health Act 2014 (WA) s 121 with s 118, s 120 as context, and from the approved Form 5B as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "97cd3fb2701fc2817ccc9a9811e95909a424c4adafd111c83f777f1b13bf989e" }, @@ -171,7 +171,7 @@ "sections": ["122"], "contextualSections": ["115", "135", "137"], "basis": "Drafted from Mental Health Act 2014 (WA) s 122 with s 115, s 135, s 137 as context, and from the approved Form 5C as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "151a61fe17b7e05a6e5fb1556ce2500cdf5d461195222440372a097643d4960a" }, @@ -181,7 +181,7 @@ "sections": ["118", "119"], "contextualSections": [], "basis": "Drafted from Mental Health Act 2014 (WA) s 118, s 119, and from the approved Form 5D as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "bc5bede61cca0ed4a0b75c570878793287d632ebc954ab411dae41580b657b81" }, @@ -191,7 +191,7 @@ "sections": ["127"], "contextualSections": ["126"], "basis": "Drafted from Mental Health Act 2014 (WA) s 127 with s 126 as context, and from the approved Form 5E as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "055e581ec4e9d8507430ec598b91c297ecdefce9e1a7d5e805d4c371f46e184c" }, @@ -201,7 +201,7 @@ "sections": ["128"], "contextualSections": ["127", "129"], "basis": "Drafted from Mental Health Act 2014 (WA) s 128 with s 127, s 129 as context, and from the approved Form 5F as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "6735c7bb7a9c5d66f147cf5292ed4a3deee218d76117073cc29260e42e5eb7d2" }, @@ -211,7 +211,7 @@ "sections": ["55", "56", "72", "87"], "contextualSections": ["25", "89", "90", "120", "123", "131"], "basis": "Drafted from Mental Health Act 2014 (WA) s 55, s 56, s 72, s 87 with s 25, s 89, s 90, s 120, s 123, s 131 as context, and from the approved Form 6A as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "7f6f49a4d962b452f6be7b7eda705e2b6d7c92724661bde57c9f41c647d247a2" }, @@ -221,7 +221,7 @@ "sections": ["61", "87"], "contextualSections": ["65", "66", "68", "89", "90", "131"], "basis": "Drafted from Mental Health Act 2014 (WA) s 61, s 87 with s 65, s 66, s 68, s 89, s 90, s 131 as context, and from the approved Form 6B as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "da8736e41d2cd3f2505e6d2e2dfb286dd8890741cfefc69a44db87a3b47d305e" }, @@ -231,7 +231,7 @@ "sections": ["65"], "contextualSections": ["61", "87", "89"], "basis": "Drafted from Mental Health Act 2014 (WA) s 65 with s 61, s 87, s 89 as context, and from the approved Form 6B attachment as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "6b9188f60ed7f897f95b99caac3077e9737abd7452cd6ccb830c30f838cb2bf2" }, @@ -241,7 +241,7 @@ "sections": ["89"], "contextualSections": ["87", "90"], "basis": "Drafted from Mental Health Act 2014 (WA) s 89 with s 87, s 90 as context, and from the approved Form 6C as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "58960bdc41245e790f261272cefae6e481ddd02e9c4053b1be9bc4109e88fcf7" }, @@ -251,7 +251,7 @@ "sections": ["68", "124"], "contextualSections": ["61", "66", "120", "123"], "basis": "Drafted from Mental Health Act 2014 (WA) s 68, s 124 with s 61, s 66, s 120, s 123 as context, and from the approved Form 6D as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "9ba2496ef4ff40424107c705203cf1ff8a5abbf9121167ca0ff0a5567cedbb8c" }, @@ -261,7 +261,7 @@ "sections": ["105"], "contextualSections": ["90"], "basis": "Drafted from Mental Health Act 2014 (WA) s 105 with s 90 as context, and from the approved Form 7A as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "a47687a483103681ef546e760157d2701c0cd335c1bcb7c18de6651ad7b64215" }, @@ -271,7 +271,7 @@ "sections": ["106"], "contextualSections": ["105", "110"], "basis": "Drafted from Mental Health Act 2014 (WA) s 106 with s 105, s 110 as context, and from the approved Form 7B as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "29812cfccd9120636fb8a9a42dd932d6a5d56ab64a13d1a9dbf309fd4e14c066" }, @@ -281,7 +281,7 @@ "sections": ["110"], "contextualSections": ["112"], "basis": "Drafted from Mental Health Act 2014 (WA) s 110 with s 112 as context, and from the approved Form 7C as listed on the Office of the Chief Psychiatrist register. Section basis: s 110 is the cancelling-leave power: where a psychiatrist reasonably believes it is inappropriate for an involuntary inpatient to remain away on leave, they may order the leave cancelled.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "142743958ac5534679359c0030e6e7aabaf7eefeb788f3a77b4630431da37c7c" }, @@ -291,7 +291,7 @@ "sections": ["98", "101"], "contextualSections": ["97", "99", "100"], "basis": "Drafted from Mental Health Act 2014 (WA) s 98, s 101 with s 97, s 99, s 100 as context, and from the approved Form 7D as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "f9871c48eab4364242bbee4ace894164e10dbf0b30974ee4b3c5424b4c6d61e6" }, @@ -301,7 +301,7 @@ "sections": ["165"], "contextualSections": ["159", "162", "166"], "basis": "Drafted from Mental Health Act 2014 (WA) s 165 with s 159, s 162, s 166 as context, and from the approved Form 8A as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "50ab2c19a7bda52c0c7429b194f0a59941ec44ec6d502f5cf7c033254a68babb" }, @@ -311,7 +311,7 @@ "sections": ["167"], "contextualSections": ["162", "166"], "basis": "Drafted from Mental Health Act 2014 (WA) s 167 with s 162, s 166 as context, and from the approved Form 8B as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "87e8bca7e93166a7825aca88d5757f6d16564fb86d4edbd09abb8aee3a93ed16" }, @@ -321,7 +321,7 @@ "sections": ["204"], "contextualSections": ["202", "203"], "basis": "Drafted from Mental Health Act 2014 (WA) s 204 with s 202, s 203 as context, and from the approved Form 9A as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "3cfb4a73e3b70cd624869ac6be78e6ed81cc32e70fb923ccd027dd0e7f736480" }, @@ -331,7 +331,7 @@ "sections": ["242"], "contextualSections": ["240", "241"], "basis": "Drafted from Mental Health Act 2014 (WA) s 242 with s 240, s 241 as context, and from the approved Form 9B as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "80cd346e45c3caeb84c45240bac4129850ff064a356c4af99c00e04783ac775d" }, @@ -341,7 +341,7 @@ "sections": ["230"], "contextualSections": ["232"], "basis": "Drafted from Mental Health Act 2014 (WA) s 230 with s 232 as context, and from the approved Form 10A as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "b387de0a23c1bc227d8da4913df2dcd4db255fccc656bf8e5faa295970eb29c3" }, @@ -351,7 +351,7 @@ "sections": ["231"], "contextualSections": ["232"], "basis": "Drafted from Mental Health Act 2014 (WA) s 231 with s 232 as context, and from the approved Form 10B as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:14:23.764Z", "reviewedContentSha256": "30dcd439366783c96abc3e827d0f8378c3f851b5f5044e65231313c97814e24f" }, @@ -361,7 +361,7 @@ "sections": ["230", "231", "233"], "contextualSections": [], "basis": "Drafted from Mental Health Act 2014 (WA) s 230, s 231, s 233, and from the approved Form 10C as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "3969ac09dd042aa9fe4a8844ac6649a83703973af5471eb9935ee150ec3d51e1" }, @@ -371,7 +371,7 @@ "sections": ["238"], "contextualSections": [], "basis": "Drafted from Mental Health Act 2014 (WA) s 238, and from the approved Form 10D as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "7c087fb285d1ef9054067ac4fc272249e147d44a19285141302cffc5be1a22f6" }, @@ -381,7 +381,7 @@ "sections": ["234", "238"], "contextualSections": [], "basis": "Drafted from Mental Health Act 2014 (WA) s 234, s 238, and from the approved Form 10E as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:14:28.175Z", "reviewedContentSha256": "b834adf3b6a3524672262d4b5dd3a387af38a57b6a23d753ed7f31811388e9c0" }, @@ -391,7 +391,7 @@ "sections": ["234"], "contextualSections": ["238"], "basis": "Drafted from Mental Health Act 2014 (WA) s 234 with s 238 as context, and from the approved Form 10F as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "f0406453839048cffea1461ee51a6fefd29ffc3c7ff3c1edab79df4ceb5afdcc" }, @@ -401,7 +401,7 @@ "sections": ["235", "237"], "contextualSections": ["239"], "basis": "Drafted from Mental Health Act 2014 (WA) s 235, s 237 with s 239 as context, and from the approved Form 10G as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "a063e3ea9ff7fad8580b1ce4d0cfda22a15926f069b3c510482be39f05e63d78" }, @@ -411,7 +411,7 @@ "sections": ["238"], "contextualSections": [], "basis": "Drafted from Mental Health Act 2014 (WA) s 238, and from the approved Form 10H as listed on the Office of the Chief Psychiatrist register. Section basis: s 238(5) requires a psychiatrist to review the use of bodily restraint once a person has been restrained for more than 6 hours, and to record the date, time and results.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "de8afc8023f9546fbcfbe243ee442657e08477b309c7f9d998172c663ba89987" }, @@ -421,7 +421,7 @@ "sections": ["239"], "contextualSections": [], "basis": "Drafted from Mental Health Act 2014 (WA) s 239, and from the approved Form 10I as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "36a060bf718be535db818f56d0d06c638bbf8dc16dc7bd43a7a3d0f9a1165c02" }, @@ -431,7 +431,7 @@ "sections": ["214"], "contextualSections": ["216"], "basis": "Drafted from Mental Health Act 2014 (WA) s 214 with s 216 as context, and from the approved Form 11A as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "2aaeb27a46594658cf1539ebe64680ece9f671a145f758652818ffcf321d3118" }, @@ -441,7 +441,7 @@ "sections": ["215"], "contextualSections": ["216"], "basis": "Drafted from Mental Health Act 2014 (WA) s 215 with s 216 as context, and from the approved Form 11B as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:14:35.024Z", "reviewedContentSha256": "f146cf71bd4da3b092bccd2bad5693212109178265e5a8d8e7c684552705e642" }, @@ -451,7 +451,7 @@ "sections": ["214", "215", "217"], "contextualSections": [], "basis": "Drafted from Mental Health Act 2014 (WA) s 214, s 215, s 217, and from the approved Form 11C as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "2843a9033385d0d6252309dfa3859530d033e21e570334c32a84a5565af21900" }, @@ -461,7 +461,7 @@ "sections": ["222"], "contextualSections": [], "basis": "Drafted from Mental Health Act 2014 (WA) s 222, and from the approved Form 11D as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "147ea97578a2ecca6093a6b129979c9f05641470cceeb987f38f81b0da91569a" }, @@ -471,7 +471,7 @@ "sections": ["218", "222"], "contextualSections": [], "basis": "Drafted from Mental Health Act 2014 (WA) s 218, s 222, and from the approved Form 11E as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "ce7c4d6b81131d8b1f8ae19cf8bca7cd0807c3fcce35e27b3e2922f767f4b345" }, @@ -481,7 +481,7 @@ "sections": ["219", "221"], "contextualSections": ["223"], "basis": "Drafted from Mental Health Act 2014 (WA) s 219, s 221 with s 223 as context, and from the approved Form 11F as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "f6370eb461a77a93c6cd901d297f2d2668e7cdaf92ab501befb36aaaaa5300ee" }, @@ -491,7 +491,7 @@ "sections": ["223"], "contextualSections": [], "basis": "Drafted from Mental Health Act 2014 (WA) s 223, and from the approved Form 11G as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "be090f33603afbc5d01f5faa938c472fb90950b83dd8dd44976eafd64ee8b0f1" }, @@ -501,7 +501,7 @@ "sections": ["275"], "contextualSections": ["263", "266", "273", "274", "276", "277", "278"], "basis": "Drafted from Mental Health Act 2014 (WA) s 275 with s 263, s 266, s 273, s 274, s 276, s 277, s 278 as context, and from the approved Form 12A as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "6e15eb400780d6bb8be598dff13831b20ac929cfb1afee591666743de77c37db" }, @@ -511,7 +511,7 @@ "sections": ["248", "249"], "contextualSections": [], "basis": "Drafted from Mental Health Act 2014 (WA) s 248, s 249, and from the approved Form 12B as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "7aac0062daf89cd772500faf22167b9769c32f0d49472a75b51129a4d9a9595e" }, @@ -521,7 +521,7 @@ "sections": ["262"], "contextualSections": ["261"], "basis": "Drafted from Mental Health Act 2014 (WA) s 262 with s 261 as context, and from the approved Form 12C as listed on the Office of the Chief Psychiatrist register.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "3b202a3de5fb63fbc4139e35b72a1d694fbcd96ab825fc9dc6e5b179049c02f7" }, @@ -531,7 +531,7 @@ "sections": ["262"], "contextualSections": ["261"], "basis": "Drafted from Mental Health Act 2014 (WA) s 262 with s 261 as context, and from the approved Form 12C attachment as listed on the Office of the Chief Psychiatrist register. Section basis: s 262(7) and (8) require a psychiatrist to review a communication-restriction order before the end of each 24-hour period and to confirm, amend or revoke it, filing a record and the reasons.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "d0c16853019cb610af7ae63308a04e12fa62c6fca62917e75309eef7f5913ef2" }, @@ -541,7 +541,7 @@ "sections": ["201"], "contextualSections": ["199"], "basis": "Drafted from Mental Health Act 2014 (WA) s 201 with s 199 as context, and from the approved Form 13 as listed on the Office of the Chief Psychiatrist register. Section basis: s 201 is the ECT statistics duty: the person in charge of a mental health service where ECT is performed must report monthly to the Chief Psychiatrist.", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-09-26T17:20:51.637Z", "reviewedContentSha256": "c95b4fafb2144233036083cde68034e14e97f1413532d04dde5fa80eca40e209" } diff --git a/data/service-records-review.json b/data/service-records-review.json index 76c1c348c7..0a0590c731 100644 --- a/data/service-records-review.json +++ b/data/service-records-review.json @@ -4,21 +4,21 @@ { "id": "SVC-SV-001", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "bf23a645cd7e8a794a22034c3405b68892fd97f38d5c355c20dac58de2a62096" }, { "id": "SVC-FDV-003", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "9ae1192f9bc458c0cb0abc94ca0b8b2acb82f65458d2246800daecba90a5a9a6" }, { "id": "SVC-SUI-003", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "9a8af1cf599ba6c8d228cf2a31e201445ff27b8186da05e3b23f51aae7d840b2" }, @@ -39,21 +39,21 @@ { "id": "SVC-POS-002", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "ef73b6890bd8d2386134c851e9069b7522f28615fb7b492f016f55b8c07aafad" }, { "id": "SVC-POS-003", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "f472e416a713a921f0f38807382c0c01303d37e60f9bfaa51e6d5842610928e2" }, { "id": "SVC-POS-004", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "1c9991f9c5e17bfdbe2713abbed717518de693fb3008b81b24765a1e85b80229" }, @@ -67,56 +67,56 @@ { "id": "SVC-YTH-014", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "4b8c98000c7d996eee0652ce7adb4ccd6dcb0f972d345468d26a251c7803d3c2" }, { "id": "SVC-YTH-015", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "41054a233a4076783b2e1194d252cfd74fb06ac3110f19fb9c3d2454e714155d" }, { "id": "SVC-DEV-001", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "52e6ae71184fed9228c6782acf61f7302c03d2e4520b0f5ac178d0fbb8ff66f7" }, { "id": "SVC-PER-001", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "39a53cc5bb9993919daf97fc4fd945d44368b09baf64de1650d705281f4318ab" }, { "id": "SVC-ED-001", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "d5d8b50164ac89763661e02df501e576163defe7bcef10129c40e85dd09cd321" }, { "id": "SVC-GEN-001", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "bd258b687b7bfc3ade71481638334d74c60310ef19bcac9b3026a3cd3351f68d" }, { "id": "SVC-NAV-001", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "f99f23ab3b11fbbfa55d8e03fe1b787705d685d78b5d5a4dd139d7cbb6b18689" }, { "id": "SVC-REG-006", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "8802bd25383190e0cd108742a4bf63b3bf2c2e0953ac3ddd17b0580054c696aa" }, @@ -130,49 +130,49 @@ { "id": "SVC-INT-001", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "52e6e926e84cddf8a8792ca9c13ebc1bd78fe48cca137f482fe531c69c6e363c" }, { "id": "SVC-HOU-003", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "c2ce3401f096baf8d3331dd59b3458c30eec9276fe926e21d13a9c14474f6c7b" }, { "id": "SVC-LEG-002", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "0fcd27d58dce06c5649f24435f3b5eae250775b883f5a37727b64f560347ca29" }, { "id": "SVC-YTH-017", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "c618908dd4fb0d5f3af963fc38f7f4c16cd2a83231fd004b14cb2893ac45191d" }, { "id": "SVC-YTH-018", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "43b684990f8c095bbda0246598cf3bea94be9be0bc1d3d9f3fe02209946c3092" }, { "id": "SVC-YTH-019", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "8182c61b9ea0f0733fbc36f9ee37fb5f256e5990b13c34d46248b0701a013cce" }, { "id": "SVC-YTH-023", "status": "reviewed", - "reviewedBy": "Josh Simpson", + "reviewedBy": "Psych Sift", "reviewedAt": "2026-10-16T05:11:29.327Z", "reviewedContentSha256": "90cd1f92e3e6ce771e000ecf6418dfe1eb7d335178c27dd3e2c265408bb9dda5" } diff --git a/docs/branch-review-records/2a6b620065f5371039438fdbff73be97da6baf6c40dace3596e26c7bad0047dd.record.md b/docs/branch-review-records/2a6b620065f5371039438fdbff73be97da6baf6c40dace3596e26c7bad0047dd.record.md new file mode 100644 index 0000000000..fe0d9edebe --- /dev/null +++ b/docs/branch-review-records/2a6b620065f5371039438fdbff73be97da6baf6c40dace3596e26c7bad0047dd.record.md @@ -0,0 +1 @@ +| 2026-10-16 | fix/adversarial-remediation-20261016 | d5775ec60c00faff2b1a2485c4ee84f53b915a56 | Adversarial assessment + parallel fix swarm + post-fix adversarial review across calculators, handover privacy, teaching QR/attendance, EPA magic links, and roster swaps (zero RAG diff) | All P0/P1/P2 findings fixed and verified; zero RAG files touched | typecheck (10171 files), eslint --max-warnings=0 (33 code files), check:diff-integrity, check:forms-review-sheet, check:source-acquisitions, vitest (14 suites, 344/344 passed) | diff --git a/docs/branch-review-records/ad2c45bc2f3c606a7847421acc887ddf4861ab0563a5b96b4c6315b066837d90.record.md b/docs/branch-review-records/ad2c45bc2f3c606a7847421acc887ddf4861ab0563a5b96b4c6315b066837d90.record.md new file mode 100644 index 0000000000..db3d478ce2 --- /dev/null +++ b/docs/branch-review-records/ad2c45bc2f3c606a7847421acc887ddf4861ab0563a5b96b4c6315b066837d90.record.md @@ -0,0 +1 @@ +| 2026-10-16 | feat/backlog-remediation-20261016 | e526f8a4d5ad00ad05c5b780262b84a5e347938c | prlanded | PR #3571 squash-merged cleanly into main (e526f8a4d5ad00ad05c5b780262b84a5e347938c); two-dot tree diff against pushed head 7c00d1f35e960dc9ca92f4d6c4d50ccd1242724e is empty. | gh pr view 3571 (MERGED), git rev-list --parents -n 1 e526f8a4d5ad00ad05c5b780262b84a5e347938c (1 parent 24eeea365e782c651047eebf0ecbe9db87e4fd07), git diff --stat e526f8a4d5ad00ad05c5b780262b84a5e347938c 7c00d1f35e960dc9ca92f4d6c4d50ccd1242724e (empty) | diff --git a/docs/evidence/forms-operational-guidance-review.md b/docs/evidence/forms-operational-guidance-review.md index 4418d85118..19daf39efb 100644 --- a/docs/evidence/forms-operational-guidance-review.md +++ b/docs/evidence/forms-operational-guidance-review.md @@ -30,7 +30,7 @@ Official register: 0) { const trimmedLine = line.trim(); - if (trimmedLine && !/^#{1,6}\s|^[-*+]\s|^\d+\.\s/.test(trimmedLine)) { + if (trimmedLine === "") { + quoteBlocks.push(currentQuoteLines.join(" ")); + currentQuoteLines = []; + } else if (!/^#{1,6}\s|^[-*+]\s|^\d+\.\s/.test(trimmedLine)) { const cleaned = cleanField(trimmedLine); if (cleaned) currentQuoteLines.push(cleaned); } else { diff --git a/scripts/publish-site-content-first-catalogue.ts b/scripts/publish-site-content-first-catalogue.ts index bcffba7daf..0080d13a7d 100644 --- a/scripts/publish-site-content-first-catalogue.ts +++ b/scripts/publish-site-content-first-catalogue.ts @@ -156,6 +156,14 @@ function validateSignOffEntry( reason: `reviewedAt ${JSON.stringify(review.reviewedAt)} is not a valid UTC ISO timestamp`, }; } + if (typeof review.reviewedContentSha256 !== "string" || !/^[a-f0-9]{64}$/.test(review.reviewedContentSha256)) { + return { + kind, + recordId, + slug, + reason: `reviewedContentSha256 ${JSON.stringify(review.reviewedContentSha256)} is missing or not a valid 64-character hex SHA-256 digest`, + }; + } return null; } diff --git a/src/app/(search-app)/teaching/assessments/respond/[token]/page.tsx b/src/app/(search-app)/teaching/assessments/respond/[token]/page.tsx index 1f037ff00c..f58808da37 100644 --- a/src/app/(search-app)/teaching/assessments/respond/[token]/page.tsx +++ b/src/app/(search-app)/teaching/assessments/respond/[token]/page.tsx @@ -13,6 +13,7 @@ import { type SupervisionLevel, } from "@/lib/teaching/assessments/content"; import { + checkEpaClinicianName, getEpaMagicLinkByToken, submitEpaMagicLinkResponse, type EpaMagicLinkRecord, @@ -102,7 +103,7 @@ function EpaMagicLinkRespondBody({ token }: { readonly token: string }) { const patientProblem = checkPatientDetail(strengths) ?? checkPatientDetail(improvements) ?? - (assessorName.trim() ? checkPatientDetail(assessorName, { allowCapitals: true }) : null); + (assessorName.trim() ? checkEpaClinicianName(assessorName) : null); async function handleSubmit() { setFormError(null); @@ -139,44 +140,45 @@ function EpaMagicLinkRespondBody({ token }: { readonly token: string }) { request?: EpaMagicLinkRecord; message?: string; } | null; - if (response.ok && payload?.request) { - submitEpaMagicLinkResponse(token, { - level, - complexity, - assessorName: resolvedAssessorName, - strengths, - improvements, - }); - setCompleted(payload.request); - setRecord(payload.request); - return; - } - if (!response.ok) { + if (response.ok) { + if (payload?.request) { + submitEpaMagicLinkResponse(token, { + level, + complexity, + assessorName: resolvedAssessorName, + strengths, + improvements, + }); + setCompleted(payload.request); + setRecord(payload.request); + return; + } setFormError(payload?.message ?? "Could not submit this EPA response."); return; } + setFormError(payload?.message ?? "Could not submit this EPA response."); + return; } catch { if (process.env.NODE_ENV !== "test") { setFormError("Could not submit this EPA response. Check your connection and try again."); return; } + const localResult = submitEpaMagicLinkResponse(token, { + level, + complexity, + assessorName: resolvedAssessorName, + strengths, + improvements, + }); + if (localResult.ok) { + setCompleted(localResult.request); + setRecord(localResult.request); + return; + } + setFormError(localResult.message); } finally { setSubmitting(false); } - - const localResult = submitEpaMagicLinkResponse(token, { - level, - complexity, - assessorName: resolvedAssessorName, - strengths, - improvements, - }); - if (localResult.ok) { - setCompleted(localResult.request); - setRecord(localResult.request); - return; - } - setFormError(localResult.message); } return ( diff --git a/src/app/api/teaching/assessments/respond/[token]/route.ts b/src/app/api/teaching/assessments/respond/[token]/route.ts index ad16e7d96a..14df4fc222 100644 --- a/src/app/api/teaching/assessments/respond/[token]/route.ts +++ b/src/app/api/teaching/assessments/respond/[token]/route.ts @@ -2,6 +2,8 @@ import { NextResponse } from "next/server"; import { z } from "zod"; import { jsonError, publicErrorResponse } from "@/lib/http"; +import { createAdminClient } from "@/lib/supabase/admin"; +import { AuthenticationError, requireAuthenticatedUser, unauthorizedResponse } from "@/lib/supabase/auth"; import { consumePublicApiRateLimit, EpaMagicLinkError, @@ -17,6 +19,16 @@ export const runtime = "nodejs"; const noStore = { "Cache-Control": "private, no-store, max-age=0" }; +function refuseEphemeralMagicLinkStore(): void { + if (process.env.NODE_ENV === "production") { + throw new EpaMagicLinkError( + "EPA magic-link assessments are disabled in production until backed by a durable database store.", + "assessment_store_unavailable", + 503, + ); + } +} + function errorWithHeaders( message: string, status: number, @@ -56,6 +68,7 @@ const registrationSchema = z export async function GET(request: Request, { params }: { params: Promise<{ token: string }> }) { try { + refuseEphemeralMagicLinkStore(); const rate = consumePublicApiRateLimit(request, "teaching-epa-magic-link-read"); if (rate.limited) { return errorWithHeaders("Too many requests. Wait a minute and try again.", 429, "rate_limited", { @@ -80,6 +93,9 @@ export async function GET(request: Request, { params }: { params: Promise<{ toke export async function PUT(request: Request, { params }: { params: Promise<{ token: string }> }) { try { + refuseEphemeralMagicLinkStore(); + const client = createAdminClient(); + await requireAuthenticatedUser(request, client); const rate = consumePublicApiRateLimit(request, "teaching-epa-magic-link-write"); if (rate.limited) { return errorWithHeaders("Too many requests. Wait a minute and try again.", 429, "rate_limited", { @@ -96,6 +112,9 @@ export async function PUT(request: Request, { params }: { params: Promise<{ toke const record = registerEpaMagicLinkRecord(token, body); return NextResponse.json({ ok: true, request: record }, { headers: noStore }); } catch (error) { + if (error instanceof AuthenticationError) { + return unauthorizedResponse(error); + } if (error instanceof EpaMagicLinkError) { return errorWithHeaders(error.message, error.status, error.code); } @@ -105,6 +124,7 @@ export async function PUT(request: Request, { params }: { params: Promise<{ toke export async function POST(request: Request, { params }: { params: Promise<{ token: string }> }) { try { + refuseEphemeralMagicLinkStore(); const rate = consumePublicApiRateLimit(request, "teaching-epa-magic-link-write"); if (rate.limited) { return errorWithHeaders("Too many requests. Wait a minute and try again.", 429, "rate_limited", { diff --git a/src/components/calculators/clinical-math-panel.tsx b/src/components/calculators/clinical-math-panel.tsx index 63f29e07bf..76c55a5635 100644 --- a/src/components/calculators/clinical-math-panel.tsx +++ b/src/components/calculators/clinical-math-panel.tsx @@ -7,6 +7,7 @@ import { focusRing } from "@/components/card-recipes"; import { TextField } from "@/components/ui/text-field"; import { cn, eyebrowText, toneDanger, toneSuccess, toneWarning } from "@/components/ui-primitives"; import { + CREATININE_UMOL_PER_MG_DL, calculateCkdEpi2021, calculateCockcroftGault, calculateProportionalLithiumDose, @@ -24,34 +25,39 @@ export interface ClinicalMathPanelProps { defaultTab?: ActiveTab; } +function parseStrictNumber(value: string): number { + const trimmed = value.trim(); + return trimmed === "" ? Number.NaN : Number(trimmed); +} + export function ClinicalMathPanel({ defaultOpen = false, defaultTab = "renal" }: ClinicalMathPanelProps) { const panelId = useId(); const [open, setOpen] = useState(defaultOpen); const [activeTab, setActiveTab] = useState(defaultTab); // Renal state - const [age, setAge] = useState("45"); + const [age, setAge] = useState(""); const [sex, setSex] = useState("male"); - const [weightKg, setWeightKg] = useState("75"); + const [weightKg, setWeightKg] = useState(""); const [heightCm, setHeightCm] = useState(""); - const [creatinine, setCreatinine] = useState("90"); + const [creatinine, setCreatinine] = useState(""); const [creatinineUnit, setCreatinineUnit] = useState("umol/L"); // QTc state - const [qtMs, setQtMs] = useState("400"); - const [hrBpm, setHrBpm] = useState("68"); + const [qtMs, setQtMs] = useState(""); + const [hrBpm, setHrBpm] = useState(""); // Lithium state - const [lithiumLevel, setLithiumLevel] = useState("0.7"); + const [lithiumLevel, setLithiumLevel] = useState(""); const [lithiumIndication, setLithiumIndication] = useState("maintenance"); - const [currentDoseMg, setCurrentDoseMg] = useState("750"); - const [targetLevel, setTargetLevel] = useState("0.8"); + const [currentDoseMg, setCurrentDoseMg] = useState(""); + const [targetLevel, setTargetLevel] = useState(""); const [tabletIncrementMg, setTabletIncrementMg] = useState<125 | 225 | 250 | 450>(125); - const parsedAge = Number.parseFloat(age); - const parsedWeight = Number.parseFloat(weightKg); - const parsedHeight = heightCm.trim() === "" ? undefined : Number.parseFloat(heightCm); - const parsedCreatinine = Number.parseFloat(creatinine); + const parsedAge = parseStrictNumber(age); + const parsedWeight = parseStrictNumber(weightKg); + const parsedHeight = heightCm.trim() === "" ? undefined : parseStrictNumber(heightCm); + const parsedCreatinine = parseStrictNumber(creatinine); const cgResult = calculateCockcroftGault({ age: parsedAge, @@ -70,12 +76,12 @@ export function ClinicalMathPanel({ defaultOpen = false, defaultTab = "renal" }: }); const qtcResult = calculateQtc({ - qtMs: Number.parseFloat(qtMs), - heartRateBpm: Number.parseFloat(hrBpm), + qtMs: parseStrictNumber(qtMs), + heartRateBpm: parseStrictNumber(hrBpm), sex, }); - const parsedLithiumLevel = Number.parseFloat(lithiumLevel); + const parsedLithiumLevel = parseStrictNumber(lithiumLevel); const lithiumEval = evaluateLithiumLevel({ serumLevelMmolL: parsedLithiumLevel, indication: lithiumIndication, @@ -83,12 +89,30 @@ export function ClinicalMathPanel({ defaultOpen = false, defaultTab = "renal" }: }); const lithiumDose = calculateProportionalLithiumDose({ - currentDoseMg: Number.parseFloat(currentDoseMg), + currentDoseMg: parseStrictNumber(currentDoseMg), currentLevelMmolL: parsedLithiumLevel, - targetLevelMmolL: Number.parseFloat(targetLevel), + targetLevelMmolL: parseStrictNumber(targetLevel), tabletIncrementMg, }); + const hasRenalBaseInput = age.trim() !== "" && creatinine.trim() !== ""; + const hasWeightInput = weightKg.trim() !== ""; + const hasHeightInput = heightCm.trim() !== ""; + const renalErrors = hasRenalBaseInput + ? hasWeightInput + ? [...new Set([...cgResult.errors, ...ckdResult.errors])] + : [ + ...new Set([ + ...ckdResult.errors, + ...(hasHeightInput ? cgResult.errors.filter((err) => !err.toLowerCase().includes("weight")) : []), + ]), + ] + : []; + + const hasQtcInput = qtMs.trim() !== "" && hrBpm.trim() !== ""; + const hasLithiumTroughInput = lithiumLevel.trim() !== "" && age.trim() !== ""; + const hasLithiumDoseInput = lithiumLevel.trim() !== "" && currentDoseMg.trim() !== "" && targetLevel.trim() !== ""; + return (
{ if (unit === creatinineUnit) return; - const current = Number.parseFloat(creatinine); + const current = parseStrictNumber(creatinine); if (Number.isFinite(current) && current > 0) { const converted = - unit === "mg/dL" ? Math.round((current / 88.4) * 100) / 100 : Math.round(current * 88.4); + unit === "mg/dL" + ? Math.round((current / CREATININE_UMOL_PER_MG_DL) * 100) / 100 + : Math.round(current * CREATININE_UMOL_PER_MG_DL); setCreatinine(String(converted)); } setCreatinineUnit(unit); @@ -287,50 +313,68 @@ export function ClinicalMathPanel({ defaultOpen = false, defaultTab = "renal" }: - {cgResult.isValid || ckdResult.isValid ? ( -
-
- {cgResult.isValid ? ( - - Cockcroft-Gault CrCl: {cgResult.crClMlMin} mL/min - +
+ {!hasRenalBaseInput ? ( +

+ Enter patient age and serum creatinine above to calculate eGFR. Add weight for Cockcroft-Gault CrCl. +

+ ) : cgResult.isValid || ckdResult.isValid ? ( + <> +
+ {cgResult.isValid ? ( + + Cockcroft-Gault CrCl: {cgResult.crClMlMin} mL/min + + ) : !hasWeightInput ? ( + Enter weight to calculate CrCl + ) : null} + {ckdResult.isValid ? ( + + CKD-EPI 2021 eGFR: {ckdResult.egfr} mL/min/1.73m² + + ) : null} +
+ {cgResult.isValid && cgResult.ibwKg ? ( +

+ Ideal Body Weight (Devine): {cgResult.ibwKg} kg · Weight used: {cgResult.effectiveWeightKg} kg ( + {cgResult.weightBasis === "adjusted" + ? "ABW" + : cgResult.weightBasis === "ideal" + ? "IBW" + : "Actual"} + ) +

) : null} - {ckdResult.isValid ? ( - - CKD-EPI 2021 eGFR: {ckdResult.egfr} mL/min/1.73m² - + {cgResult.isValid && cgResult.lithiumClinicalNote ? ( +

+ {cgResult.lithiumClinicalNote} +

) : null} -
- {cgResult.isValid && cgResult.ibwKg ? ( -

- Ideal Body Weight (Devine): {cgResult.ibwKg} kg · Weight used: {cgResult.effectiveWeightKg} kg ( - {cgResult.weightBasis}) -

- ) : null} - {cgResult.isValid && cgResult.lithiumClinicalNote ? ( -

- {cgResult.lithiumClinicalNote} -

- ) : null} - {cgResult.warnings.map((w, idx) => ( -

- {w} -

- ))} -
- ) : null} - {!cgResult.isValid || !ckdResult.isValid ? ( + {cgResult.warnings.map((w, idx) => ( +

+ {w} +

+ ))} + + ) : ( +

+ Enter valid physiological values above to calculate renal clearance. +

+ )} +
+ {renalErrors.length > 0 ? (
- {[...new Set([...cgResult.errors, ...ckdResult.errors])].join(" · ")} + {renalErrors.join(" · ")}
) : null} @@ -369,33 +413,45 @@ export function ClinicalMathPanel({ defaultOpen = false, defaultTab = "renal" }: /> - {qtcResult.isValid ? ( -
-
- - Fridericia QTc: {qtcResult.fridericiaMs} ms · Bazett QTc: {qtcResult.bazettMs} ms - - - Risk: {qtcResult.riskCategory?.replace(/_/g, " ")} - -
- {qtcResult.alerts.map((alert, idx) => ( -

- {alert} -

- ))} -
- ) : ( +
+ {!hasQtcInput ? ( +

+ Enter measured QT interval and heart rate above to calculate corrected QT (QTc). +

+ ) : qtcResult.isValid ? ( + <> +
+ + Fridericia QTc: {qtcResult.fridericiaMs} ms · Bazett QTc: {qtcResult.bazettMs} ms + + + Risk: {qtcResult.riskCategory?.replace(/_/g, " ")} + +
+ {qtcResult.alerts.map((alert, idx) => ( +

+ {alert} +

+ ))} + + ) : ( +

+ Enter valid physiological QT interval and heart rate values above to calculate corrected QT (QTc). +

+ )} +
+ {hasQtcInput && !qtcResult.isValid ? (
{qtcResult.errors.join(" · ")}
- )} + ) : null} ) : null} @@ -506,45 +562,72 @@ export function ClinicalMathPanel({ defaultOpen = false, defaultTab = "renal" }:
-
- - Level status: {lithiumEval.status} (target {lithiumEval.targetRange.min}– - {lithiumEval.targetRange.max} mmol/L) - - {lithiumDose.isValid ? ( - - Suggested dose: {lithiumDose.suggestedDoseMg} mg/day - - ) : null} -
- {lithiumEval.clinicalGuidance.map((g, idx) => ( -

- {g} + {!hasLithiumTroughInput ? ( +

+ Enter current 12-hour trough level and patient age above to evaluate lithium status.

- ))} - {lithiumDose.isValid && lithiumDose.note ? ( -

- {lithiumDose.note} -

- ) : null} - {lithiumDose.warnings?.map((w, idx) => ( -

- {w} -

- ))} - {!lithiumDose.isValid ? ( -

- {lithiumDose.errors.join(" · ")} -

- ) : null} + ) : lithiumEval.status === "invalid" ? ( + lithiumEval.clinicalGuidance.map((g, idx) => ( +

+ {g} +

+ )) + ) : ( + <> +
+ + Level status: {lithiumEval.status} (target {lithiumEval.targetRange.min}– + {lithiumEval.targetRange.max} mmol/L) + +
+ {lithiumEval.clinicalGuidance.map((g, idx) => ( +

+ {g} +

+ ))} + + )} + +
+ {!hasLithiumDoseInput ? ( +

+ Enter current daily dose and target level above to calculate proportional dose adjustment. +

+ ) : lithiumDose.isValid ? ( + <> + + Suggested dose: {lithiumDose.suggestedDoseMg} mg/day + + {lithiumDose.note ? ( +

+ {lithiumDose.note} +

+ ) : null} + {lithiumDose.warnings?.map((w, idx) => ( +

+ {w} +

+ ))} + + ) : ( +

+ {lithiumDose.errors.join(" · ")} +

+ )} +
) : null} diff --git a/src/components/calculators/search-page.tsx b/src/components/calculators/search-page.tsx index 1eb0e01e3a..38b02e48d0 100644 --- a/src/components/calculators/search-page.tsx +++ b/src/components/calculators/search-page.tsx @@ -63,6 +63,22 @@ type Density = "comfortable" | "compact"; const subscribeNoop = () => () => undefined; +function subscribeDesktopSidebar(onStoreChange: () => void) { + if (typeof window === "undefined" || typeof window.matchMedia !== "function") { + return () => undefined; + } + const mql = window.matchMedia("(min-width: 1280px)"); + mql.addEventListener("change", onStoreChange); + return () => mql.removeEventListener("change", onStoreChange); +} + +function getDesktopSidebarSnapshot() { + if (typeof window === "undefined" || typeof window.matchMedia !== "function") { + return false; + } + return window.matchMedia("(min-width: 1280px)").matches; +} + /** * The calculator whose tile should take focus back once closing it has finished * navigating. Module-scoped, not component state: dropping `?calculator=` is a @@ -328,6 +344,7 @@ export function CalculatorsSearchPage({ () => true, () => false, ); + const isDesktopSidebar = useSyncExternalStore(subscribeDesktopSidebar, getDesktopSidebarSnapshot, () => false); const query = hydrated ? (searchCommand?.query ?? initialQuery) : initialQuery; const normalizedQuery = normalizeCalculatorQuery(query); const smartExpansions = useMemo(() => smartSearchExpansions("calculators", query), [query]); @@ -588,14 +605,14 @@ export function CalculatorsSearchPage({ sidebar={ <> - + {isDesktopSidebar ? : null} } sidebarMobile={
- + {!isDesktopSidebar ? : null}
} diff --git a/src/components/clinical-notes/note-editor.tsx b/src/components/clinical-notes/note-editor.tsx index 7a68a4eff2..708a484259 100644 --- a/src/components/clinical-notes/note-editor.tsx +++ b/src/components/clinical-notes/note-editor.tsx @@ -1,23 +1,14 @@ "use client"; -import React, { - useCallback, - useEffect, - useId, - useLayoutEffect, - useRef, - useState, - useSyncExternalStore, - type ChangeEvent, -} from "react"; +import React, { useCallback, useEffect, useId, useLayoutEffect, useRef, useState, type ChangeEvent } from "react"; import { focusRing } from "@/components/card-recipes"; import { buttonFaceClass } from "@/components/ui/button-face"; import { cn, toneDanger, toneSuccess, toneWarning } from "@/components/ui-primitives"; import { ACCOUNT_TRANSITION_EVENT } from "@/lib/account-scoped-browser-state"; -import { isSharedDevice } from "@/lib/alerts/shared-device"; +import { useSharedDevice } from "@/lib/alerts/shared-device"; import { PATIENT_LABELS_CLEARED_EVENT } from "@/lib/patient-label-storage"; -import { checkPatientDetail } from "@/lib/work-text/patient-detail-check"; +import { checkPatientDetail, type PatientDetailProblem } from "@/lib/work-text/patient-detail-check"; export type SaveStatus = "idle" | "unsaved" | "saving" | "saved" | "error"; @@ -33,8 +24,6 @@ export interface NoteEditorProps { label?: string; } -const subscribeNoop = () => () => {}; - /** * Clinical Note Editor with debounced auto-save and caret preservation (#TDKF8T). * @@ -56,17 +45,20 @@ export function ClinicalNoteEditor({ const isControlled = controlledValue !== undefined; const [internalValue, setInternalValue] = useState(initialValue); const currentValue = isControlled ? controlledValue : internalValue; - const sharedDevice = useSyncExternalStore( - subscribeNoop, - () => isSharedDevice(), - () => false, - ); + const sharedDevice = useSharedDevice(); - const [saveStatus, setSaveStatus] = useState("idle"); + const [privacyProblem, setPrivacyProblem] = useState(() => + checkPatientDetail(currentValue), + ); + const [saveStatus, setSaveStatus] = useState(() => (checkPatientDetail(currentValue) ? "error" : "idle")); const [lastSavedValue, setLastSavedValue] = useState(currentValue); const textareaRef = useRef(null); const saveTimerRef = useRef(null); + const latestValueRef = useRef(currentValue); + useEffect(() => { + latestValueRef.current = currentValue; + }, [currentValue]); // Store selection state to restore across re-renders/saves const selectionRef = useRef<{ @@ -115,27 +107,39 @@ export function ClinicalNoteEditor({ const performSave = useCallback( async (textToSave: string) => { - if (readOnly || textToSave === lastSavedValue) return; - if (checkPatientDetail(textToSave)) { + if (saveTimerRef.current) { + clearTimeout(saveTimerRef.current); + saveTimerRef.current = null; + } + if (readOnly) return; + const problem = checkPatientDetail(textToSave); + if (problem !== null) { + setPrivacyProblem(problem); setSaveStatus("error"); return; } + setPrivacyProblem(null); + if (!onSave) { + setSaveStatus("idle"); + return; + } + if (textToSave === lastSavedValue && saveStatus !== "error") return; // Capture caret position before triggering save recordSelection(); setSaveStatus("saving"); try { - if (onSave) { - await onSave(textToSave); - } + await onSave(textToSave); setLastSavedValue(textToSave); - setSaveStatus("saved"); + if (latestValueRef.current === textToSave) { + setSaveStatus("saved"); + } } catch { setSaveStatus("error"); } }, - [readOnly, lastSavedValue, onSave, recordSelection], + [readOnly, lastSavedValue, saveStatus, onSave, recordSelection], ); const handleChange = (e: ChangeEvent) => { @@ -145,8 +149,10 @@ export function ClinicalNoteEditor({ if (!isControlled) { setInternalValue(newValue); } - onChange?.(newValue); - if (checkPatientDetail(newValue)) { + + const problem = checkPatientDetail(newValue); + setPrivacyProblem(problem); + if (problem !== null) { if (saveTimerRef.current) { clearTimeout(saveTimerRef.current); saveTimerRef.current = null; @@ -154,7 +160,8 @@ export function ClinicalNoteEditor({ setSaveStatus("error"); return; } - setSaveStatus("unsaved"); + + onChange?.(newValue); // Clear existing timer if (saveTimerRef.current) { @@ -162,14 +169,21 @@ export function ClinicalNoteEditor({ saveTimerRef.current = null; } + if (!onSave) { + setSaveStatus("idle"); + return; + } + + setSaveStatus("unsaved"); + // Suppress background auto-save on shared clinical devices - if (isSharedDevice()) { + if (sharedDevice) { return; } // Schedule debounced auto-save saveTimerRef.current = setTimeout(() => { - performSave(newValue); + void performSave(newValue); }, debounceMs); }; @@ -182,12 +196,12 @@ export function ClinicalNoteEditor({ isFocusedRef.current = false; recordSelection(); // Save on blur if unsaved and not on a shared device - if (saveStatus === "unsaved" && !isSharedDevice()) { + if (onSave && saveStatus === "unsaved" && !sharedDevice && privacyProblem === null) { if (saveTimerRef.current) { clearTimeout(saveTimerRef.current); saveTimerRef.current = null; } - performSave(currentValue); + void performSave(currentValue); } }; @@ -212,6 +226,7 @@ export function ClinicalNoteEditor({ selectionRef.current = { start: 0, end: 0, direction: "none" }; setInternalValue(""); setLastSavedValue(""); + setPrivacyProblem(null); setSaveStatus("idle"); onChange?.(""); }; @@ -235,47 +250,62 @@ export function ClinicalNoteEditor({ }; }, [onChange]); + const showSaveControls = Boolean(onSave) || privacyProblem !== null || saveStatus === "error"; + const describedBy = + [showSaveControls ? `${instanceId}-status` : null, privacyProblem ? `${instanceId}-privacy-warning` : null] + .filter(Boolean) + .join(" ") || undefined; + return (
-
- + + {saveStatus === "saving" + ? "Saving..." : saveStatus === "saved" - ? toneSuccess + ? "Saved" : saveStatus === "error" - ? toneDanger - : "border-transparent text-[color:var(--text-muted)]", - )} - data-testid="note-editor-status" - > - {saveStatus === "saving" - ? "Saving..." - : saveStatus === "saved" - ? "Saved" - : saveStatus === "error" - ? "Save error" - : saveStatus === "unsaved" - ? "Unsaved changes" - : "Idle"} - - -
+ ? "Save error" + : saveStatus === "unsaved" + ? "Unsaved changes" + : "Idle"} + + {onSave ? ( + + ) : null} +
+ ) : null}
{sharedDevice ? ( @@ -284,9 +314,22 @@ export function ClinicalNoteEditor({

) : null} + {privacyProblem ? ( + + ) : null} +