From d1e882a53377d950f67a1ab7f1108f7e80febe65 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 10:35:35 +0000 Subject: [PATCH 1/4] Specifiers builder: show each catalogue option's source-review status Two gaps left by the catalogue-driven builder, both about what the screen implies rather than what it computes. The builder shows a catalogue option at the moment the clinician picks it and copies it into a note, but showed nothing about that item's review state. Of the 585 catalogued specifiers, 71 have verified sources and every one is still pending qualified clinician review. The detail and reference pages already carry ReviewStatusBadge for exactly this reason, so carry it on the option rows too: the state is visible when it is acted on, not only if the record is opened afterwards. Generated definitions stay withheld, as before. The single-select step also read as though its one-of rule came from the manual. It does not. singleSelectGroupLabels is an aide-memoire grouping of the dataset's 50 group labels, so a group wrongly marked exclusive would quietly block a valid combination. Say so in the step body rather than letting the radio imply authority it does not have. No behaviour change: the curated mood path is untouched and shows no badges, selection rules and wording output are unchanged. Verified: 20 unit tests pass with a new case pinning that every catalogue item carries a renderable status and that the needs-review state is present. In the running app, Autism Spectrum Disorder shows 3 badges on its 3 severity options and 5 on its 5 co-occurring options, the curated MDD path shows none, and there is no horizontal overflow at 320, 390, 768 or 1440. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011RJFStNCUPhDrEdAJe2r4i --- .../specifiers/specifier-builder-page.tsx | 13 ++++++++++--- tests/specifier-builder-diagnoses.test.ts | 12 ++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/components/specifiers/specifier-builder-page.tsx b/src/components/specifiers/specifier-builder-page.tsx index dfb1b4c19e..72bb876204 100644 --- a/src/components/specifiers/specifier-builder-page.tsx +++ b/src/components/specifiers/specifier-builder-page.tsx @@ -18,6 +18,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { InformationPageHeader } from "@/components/information-page-shell"; import { CategoryTag, + ReviewStatusBadge, SpecifierPageShell, SpecifierSafetyNote, specifierCard, @@ -127,7 +128,7 @@ function catalogStep(group: BuilderCatalogGroup, index: number): BuilderStep { title: group.selection === "single" ? `Choose the ${label}` : `Add ${label}`, body: group.selection === "single" - ? "Select one option, and only when it is established." + ? "Select one option, and only when it is established. These are offered as one-of because the group reads as a single graded axis, which is an aide-memoire grouping rather than a verified manual rule." : "Select only what the current presentation supports.", }; } @@ -731,8 +732,14 @@ export function SpecifierBuilderPage({ initialSpecifiers = [] }: { initialSpecif )} - - {item.label} + + + {item.label} + + {/* The detail and reference pages already carry this badge. Carrying it here too + means the clinician sees an item's source-review state at the point of choosing + it, not only if they open its record afterwards. */} + Recorded for {item.disorder}. Confirm the wording against the current manual before diff --git a/tests/specifier-builder-diagnoses.test.ts b/tests/specifier-builder-diagnoses.test.ts index 85c1438c07..4ab4ef2797 100644 --- a/tests/specifier-builder-diagnoses.test.ts +++ b/tests/specifier-builder-diagnoses.test.ts @@ -127,6 +127,18 @@ describe("specifier builder base diagnoses", () => { }); }); + it("carries a source-review status on every catalogue option the builder can offer", () => { + // The builder shows these rows at the moment of choosing, so each must have a status + // the ReviewStatusBadge can render. Most of the catalogue is still awaiting formal + // source review, and that has to stay visible rather than being implied as verified. + const statuses = new Set(specifierIndexItems.map((item) => item.src)); + for (const status of statuses) { + expect(["source-verified", "source-needs-formal-review", "source-not-applicable"]).toContain(status); + } + expect(specifierIndexItems.every((item) => Boolean(item.src))).toBe(true); + expect(statuses.has("source-needs-formal-review")).toBe(true); + }); + it("lowers an ordinary leading capital for the wording line but leaves structured labels alone", () => { expect(catalogWordingSegment("With catatonia")).toBe("with catatonia"); expect(catalogWordingSegment("Mild (BMI 17 or above)")).toBe("mild (BMI 17 or above)"); From 0532772c30cd18e6561eccb7a5fb8fbbb272eada Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 10:51:33 +0000 Subject: [PATCH 2/4] Address the two Codex findings on the review-status badge Both are correct and both are in the code this PR added. 1. The single-select step said the group "reads as a single graded axis". That is false for the nominal groups the rule also covers - Type (erotomanic, grandiose, jealous), Attraction, Classes and Current Episode are categorical, not degrees - so it handed clinicians a wrong rationale for the enforced one-of behaviour. Drop the claim about why and keep only what is true: that treating the group as one-of is an aide-memoire grouping, not a verified manual rule. 2. The option inputs set aria-label, which overrides the label's descendant text in the accessible-name computation, so the new badge was visible but silent. A screen-reader user picking a specifier heard the label alone and none of the review state, which is the whole point of showing it there. Wire aria-describedby to the badge so the status is announced with the control. Verified: the computed description on each Autism Spectrum Disorder severity radio now resolves to its badge text, checked in the running app - for example "Level 2: Requiring substantial support" described by "Review due". The step copy was confirmed against Delusional Disorder, whose Type group is nominal. A new DOM test asserts every catalogue option's aria-describedby resolves to a renderable status, so the silent-badge regression cannot return. 21 unit tests pass, typecheck, lint and formatting clean, drift ratchet unchanged at 233/233. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011RJFStNCUPhDrEdAJe2r4i --- .../specifiers/specifier-builder-page.tsx | 11 ++++-- ...ecifier-builder-review-status.dom.test.tsx | 38 +++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 tests/specifier-builder-review-status.dom.test.tsx diff --git a/src/components/specifiers/specifier-builder-page.tsx b/src/components/specifiers/specifier-builder-page.tsx index 72bb876204..6999617d17 100644 --- a/src/components/specifiers/specifier-builder-page.tsx +++ b/src/components/specifiers/specifier-builder-page.tsx @@ -128,7 +128,7 @@ function catalogStep(group: BuilderCatalogGroup, index: number): BuilderStep { title: group.selection === "single" ? `Choose the ${label}` : `Add ${label}`, body: group.selection === "single" - ? "Select one option, and only when it is established. These are offered as one-of because the group reads as a single graded axis, which is an aide-memoire grouping rather than a verified manual rule." + ? "Select one option, and only when it is established. Treating this group as one-of is an aide-memoire grouping, not a verified manual rule." : "Select only what the current presentation supports.", }; } @@ -705,6 +705,7 @@ export function SpecifierBuilderPage({ initialSpecifiers = [] }: { initialSpecif type={single ? "radio" : "checkbox"} name={single ? activeStep.group.id : undefined} aria-label={item.label} + aria-describedby={`${item.slug}-review-status`} checked={checked} onChange={() => single ? chooseCatalogSingle(activeStep.group, item.slug) : toggle(item.slug) @@ -738,8 +739,12 @@ export function SpecifierBuilderPage({ initialSpecifiers = [] }: { initialSpecif {/* The detail and reference pages already carry this badge. Carrying it here too means the clinician sees an item's source-review state at the point of choosing - it, not only if they open its record afterwards. */} - + it, not only if they open its record afterwards. The input sets aria-label, which + overrides the label's descendant text, so the status reaches assistive tech only + through the aria-describedby wired to this id. */} + + + Recorded for {item.disorder}. Confirm the wording against the current manual before diff --git a/tests/specifier-builder-review-status.dom.test.tsx b/tests/specifier-builder-review-status.dom.test.tsx new file mode 100644 index 0000000000..4de1368d21 --- /dev/null +++ b/tests/specifier-builder-review-status.dom.test.tsx @@ -0,0 +1,38 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; + +vi.mock("next/navigation", () => ({ + usePathname: () => "/specifiers/builder", + useSearchParams: () => new URLSearchParams(), +})); + +import { SpecifierBuilderPage } from "@/components/specifiers/specifier-builder-page"; +import { builderCatalogGroups, catalogDiagnosisId } from "@/lib/specifier-builder-diagnoses"; + +// The catalogue option inputs set `aria-label`, which overrides the surrounding +// label's descendant text. Without an explicit description the review badge would be +// visible but silent, which defeats the point of showing it at the moment of choosing. +describe("catalogue option review status", () => { + it("reaches assistive tech through the control's description, not only the badge", async () => { + const user = userEvent.setup(); + render(); + + await user.selectOptions( + screen.getByRole("combobox", { name: "Diagnostic phrase" }), + catalogDiagnosisId("ndv", "Autism Spectrum Disorder"), + ); + + const group = builderCatalogGroups(catalogDiagnosisId("ndv", "Autism Spectrum Disorder")).find( + (candidate) => candidate.label === "Severity", + )!; + await user.click(screen.getByRole("button", { name: new RegExp(`Step \\d+ of \\d+: ${group.label}`) })); + + for (const item of group.items) { + const control = screen.getByRole("radio", { name: item.label }); + const describedBy = control.getAttribute("aria-describedby"); + expect(describedBy).toBe(`${item.slug}-review-status`); + expect(document.getElementById(describedBy!)?.textContent).toMatch(/Review due|Source reviewed|Source n\/a/); + } + }); +}); From b00714cb6e4f1ca3e50b7c1904c46dcfdbf01731 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 11:02:19 +0000 Subject: [PATCH 3/4] Correct the specifier one-of mapping and let a group be reopened The catalogue builder decides whether a specifier group is one-of from a hand-authored list of group labels. The dataset carries no exclusivity field, so that list is a judgement, and a wrong entry makes a valid combination unreachable with nothing on screen to say why. Four labels were wrong against the manual and are now multi-select: * Classes - concurrent alcohol, cannabis and tobacco use disorders are ordinary and each is coded in its own right. * Clusters - personality disorders across clusters are common and are coded as both. * Aetiology - the neurocognitive list pairs a certainty term with the aetiology itself, so probable Alzheimer's disease needs two picks, and delirium has to allow more than one contributor. * Attraction - limited to incest is a separate axis, applied alongside the attraction type rather than instead of it. Type stays one-of, but specific phobia opts out of it: the manual asks for every applicable phobia type to be coded. That needed a per-disorder exception, so builderGroupSelection now takes the disorder too. The wider fix is that the mapping is no longer able to block anything. Every single-select group carries a control that reopens it as a checkbox list, so a mapping error costs a default rather than a combination the clinician cannot record. The step copy says so. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011RJFStNCUPhDrEdAJe2r4i --- .../specifiers/specifier-builder-page.tsx | 51 +++++++++++++++---- src/lib/specifier-builder-diagnoses.ts | 51 ++++++++++++++++--- tests/specifier-builder-diagnoses.test.ts | 46 +++++++++++++++++ 3 files changed, 132 insertions(+), 16 deletions(-) diff --git a/src/components/specifiers/specifier-builder-page.tsx b/src/components/specifiers/specifier-builder-page.tsx index 6999617d17..af3f277949 100644 --- a/src/components/specifiers/specifier-builder-page.tsx +++ b/src/components/specifiers/specifier-builder-page.tsx @@ -30,6 +30,7 @@ import { copyTextToClipboard } from "@/lib/copy-to-clipboard"; import { applyBuilderGroupRules, builderCatalogGroups, + relaxBuilderGroups, builderDiagnosisGroups, catalogWordingSegment, findBuilderDiagnosis, @@ -128,14 +129,14 @@ function catalogStep(group: BuilderCatalogGroup, index: number): BuilderStep { title: group.selection === "single" ? `Choose the ${label}` : `Add ${label}`, body: group.selection === "single" - ? "Select one option, and only when it is established. Treating this group as one-of is an aide-memoire grouping, not a verified manual rule." + ? "Select one option, and only when it is established. One-of is this builder's default for the group, not a verified manual rule, so reopen it if more than one applies." : "Select only what the current presentation supports.", }; } -function builderStepsFor(diagnosis: BuilderDiagnosis): BuilderStep[] { +function builderStepsFor(diagnosis: BuilderDiagnosis, catalogGroups: BuilderCatalogGroup[]): BuilderStep[] { if (diagnosis.kind === "guided") return [baseStep, ...guidedSteps]; - return [baseStep, ...builderCatalogGroups(diagnosis.id).map((group, index) => catalogStep(group, index + 1))]; + return [baseStep, ...catalogGroups.map((group, index) => catalogStep(group, index + 1))]; } function continueLabelFor(steps: BuilderStep[], index: number) { @@ -257,15 +258,18 @@ export function SpecifierBuilderPage({ initialSpecifiers = [] }: { initialSpecif const [selected, setSelected] = useState(initialState.selected); const [diagnosisFilter, setDiagnosisFilter] = useState(""); const [copyState, setCopyState] = useState("idle"); + // Single-select groups the clinician has reopened because more than one option + // applies. Keyed by group id, which already carries the diagnosis. + const [relaxedGroupIds, setRelaxedGroupIds] = useState>(() => new Set()); const copyTimer = useRef(null); const focusStageHeading = useRef(false); const diagnosis = findBuilderDiagnosis(diagnosisId) ?? guidedBuilderDiagnoses[0]; - const steps = useMemo(() => builderStepsFor(diagnosis), [diagnosis]); const catalogGroups = useMemo( - () => (diagnosis.kind === "catalog" ? builderCatalogGroups(diagnosis.id) : []), - [diagnosis], + () => (diagnosis.kind === "catalog" ? relaxBuilderGroups(builderCatalogGroups(diagnosis.id), relaxedGroupIds) : []), + [diagnosis, relaxedGroupIds], ); + const steps = useMemo(() => builderStepsFor(diagnosis, catalogGroups), [catalogGroups, diagnosis]); const firstStepId = useMemo(() => { const seeded = initialState.selected[0]; @@ -351,6 +355,8 @@ export function SpecifierBuilderPage({ initialSpecifiers = [] }: { initialSpecif function changeDiagnosis(nextId: string) { const next = findBuilderDiagnosis(nextId); if (!next) return; + const nextGroups = + next.kind === "catalog" ? relaxBuilderGroups(builderCatalogGroups(next.id), relaxedGroupIds) : []; setDiagnosisId(nextId); setSelected((current) => { // Curated selections survive a move between mood presets when they remain @@ -363,17 +369,16 @@ export function SpecifierBuilderPage({ initialSpecifiers = [] }: { initialSpecif }); } if (next.kind === "catalog" && diagnosis.kind === "catalog") { - const groups = builderCatalogGroups(next.id); - const known = new Set(groups.flatMap((group) => group.items.map((item) => item.slug))); + const known = new Set(nextGroups.flatMap((group) => group.items.map((item) => item.slug))); return applyBuilderGroupRules( - groups, + nextGroups, current.filter((slug) => known.has(slug)), ); } return []; }); - const nextSteps = builderStepsFor(next); + const nextSteps = builderStepsFor(next, nextGroups); setActiveView((current) => (nextSteps.some((step) => step.id === current) ? current : "base")); setVisited((current) => current.filter((id) => nextSteps.some((step) => step.id === id))); setCopyState("idle"); @@ -388,6 +393,16 @@ export function SpecifierBuilderPage({ initialSpecifiers = [] }: { initialSpecif setCopyState("idle"); } + function relaxGroup(groupId: string) { + setRelaxedGroupIds((current) => { + if (current.has(groupId)) return current; + const next = new Set(current); + next.add(groupId); + return next; + }); + setCopyState("idle"); + } + function chooseCatalogSingle(group: BuilderCatalogGroup, slug: string | null) { setSelected((current) => { const cleared = current.filter((item) => !group.items.some((candidate) => candidate.slug === item)); @@ -756,6 +771,22 @@ export function SpecifierBuilderPage({ initialSpecifiers = [] }: { initialSpecif })} + {activeStep.group.selection === "single" ? ( +
+ +

+ Reopens this group so every applicable option can be recorded. Use it when the manual allows the + combination; the one-of default is this builder’s grouping, not a rule. +

+
+ ) : null} ) : null} diff --git a/src/lib/specifier-builder-diagnoses.ts b/src/lib/specifier-builder-diagnoses.ts index 5040ac66c2..7e6810b1bd 100644 --- a/src/lib/specifier-builder-diagnoses.ts +++ b/src/lib/specifier-builder-diagnoses.ts @@ -81,12 +81,26 @@ const guidedCatalogDisorders = new Set([ * options contradict each other, and blocking a valid combination (for example * "in sustained remission, on maintenance therapy, in a controlled environment") * is the worse failure for a documentation aid. + * + * The dataset carries no exclusivity field — a group is only a label and a list — so + * this mapping is authored here and every entry has to earn its place against the + * items it actually governs. Four labels were removed on review because the manual + * permits the combination the radio was blocking: + * * "Classes" — the substance-class list. Concurrent alcohol, cannabis and tobacco + * use disorders are ordinary, and each is coded in its own right. + * * "Clusters" — personality-disorder clusters. Meeting criteria across clusters + * (borderline with avoidant, say) is common and is coded as both. + * * "Aetiology" — the neurocognitive list pairs a certainty term ("probable or + * possible") with the aetiology itself, so "probable Alzheimer's disease" needs + * two picks, and the delirium list has to allow more than one contributor. + * * "Attraction" — "limited to incest" is a separate axis in the manual, applied + * alongside the attraction type rather than instead of it. + * + * Even a correct entry is a default rather than a rule: the builder can relax any + * single-select group on request (see relaxBuilderGroups), so a mapping error costs a + * default, never an unreachable combination. */ export const singleSelectGroupLabels: ReadonlySet = new Set([ - "Aetiology", - "Attraction", - "Classes", - "Clusters", "Course", "Course & Status", "Current Episode", @@ -104,10 +118,35 @@ export const singleSelectGroupLabels: ReadonlySet = new Set([ "Types & Severity", ]); -export function builderGroupSelection(groupLabel: string): BuilderGroupSelection { +/** + * `disorder::group` pairs where the label-wide rule above is wrong for one disorder. + * Specific phobia is the case in the current dataset: the manual asks for every + * applicable phobia type to be coded, while every other "Type" group is one-of. + */ +export const multiSelectGroupExceptions: ReadonlySet = new Set(["Specific Phobia::Type"]); + +export function builderGroupSelection(groupLabel: string, disorder?: string): BuilderGroupSelection { + if (disorder && multiSelectGroupExceptions.has(`${disorder}::${groupLabel}`)) return "multiple"; return singleSelectGroupLabels.has(groupLabel) ? "single" : "multiple"; } +/** + * Reopen named single-select groups as checkbox lists. This is what keeps the mapping + * above a default: when a clinician says more than one option applies, the group stops + * enforcing one-of instead of leaving the combination unreachable. + */ +export function relaxBuilderGroups( + groups: BuilderCatalogGroup[], + relaxedGroupIds: ReadonlySet, +): BuilderCatalogGroup[] { + if (!relaxedGroupIds.size) return groups; + return groups.map((group) => + group.selection === "single" && relaxedGroupIds.has(group.id) + ? { ...group, selection: "multiple" as const } + : group, + ); +} + function diagnosisSlug(value: string) { return value .toLowerCase() @@ -150,7 +189,7 @@ function buildCatalog() { group = { id: `${id}::${diagnosisSlug(item.group)}`, label: item.group, - selection: builderGroupSelection(item.group), + selection: builderGroupSelection(item.group, item.disorder), items: [], }; entry.groups.push(group); diff --git a/tests/specifier-builder-diagnoses.test.ts b/tests/specifier-builder-diagnoses.test.ts index 4ab4ef2797..c196ffd14c 100644 --- a/tests/specifier-builder-diagnoses.test.ts +++ b/tests/specifier-builder-diagnoses.test.ts @@ -10,6 +10,7 @@ import { catalogWordingSegment, findBuilderDiagnosis, guidedBuilderDiagnoses, + relaxBuilderGroups, resolveInitialBuilderState, stripSpecifierOptionList, toggleBuilderCatalogSlug, @@ -91,6 +92,51 @@ describe("specifier builder base diagnoses", () => { expect(builderGroupSelection("Co-occurring")).toBe("multiple"); }); + it("leaves a group multi-select where the manual allows the combination", () => { + // Each of these was a single-select label until review. A radio there makes an + // ordinary presentation unrecordable rather than merely awkward. + expect(builderGroupSelection("Classes")).toBe("multiple"); + expect(builderGroupSelection("Clusters")).toBe("multiple"); + expect(builderGroupSelection("Aetiology")).toBe("multiple"); + expect(builderGroupSelection("Attraction")).toBe("multiple"); + }); + + it("lets one disorder opt out of a label-wide one-of rule", () => { + expect(builderGroupSelection("Type", "Specific Phobia")).toBe("multiple"); + expect(builderGroupSelection("Type", "Delusional Disorder")).toBe("single"); + expect(builderGroupSelection("Type")).toBe("single"); + + const phobia = builderCatalogGroups(catalogDiagnosisId("anx", "Specific Phobia")); + const type = phobia.find((group) => group.label === "Type")!; + expect(type.selection).toBe("multiple"); + const both = applyBuilderGroupRules(phobia, [type.items[0].slug, type.items[1].slug]); + expect(both).toHaveLength(2); + }); + + it("records concurrent substance use disorders instead of replacing the last one", () => { + const classes = builderCatalogGroups(catalogDiagnosisId("sub", "Substance Classes with Use Disorders")); + const group = classes.find((entry) => entry.label === "Classes")!; + let selected = toggleBuilderCatalogSlug(classes, [], group.items[0].slug); + selected = toggleBuilderCatalogSlug(classes, selected, group.items[1].slug); + expect(selected).toEqual([group.items[0].slug, group.items[1].slug]); + }); + + it("reopens a single-select group on request so no combination is unreachable", () => { + const asd = catalogDiagnosisId("ndv", "Autism Spectrum Disorder"); + const groups = builderCatalogGroups(asd); + const severity = groups.find((group) => group.label === "Severity")!; + + const relaxed = relaxBuilderGroups(groups, new Set([severity.id])); + expect(relaxed.find((group) => group.label === "Severity")!.selection).toBe("multiple"); + // Every other group keeps its own rule, and the original list is not mutated. + expect(relaxed.find((group) => group.label === "Co-occurring")!.selection).toBe("multiple"); + expect(severity.selection).toBe("single"); + + let selected = toggleBuilderCatalogSlug(relaxed, [], severity.items[0].slug); + selected = toggleBuilderCatalogSlug(relaxed, selected, severity.items[1].slug); + expect(selected).toHaveLength(2); + }); + it("keeps one pick inside a single-select group and many inside the rest", () => { const asd = catalogDiagnosisId("ndv", "Autism Spectrum Disorder"); const groups = builderCatalogGroups(asd); From 7d26e41100df74c9f98b19670c91975d529cfb72 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 11:11:53 +0000 Subject: [PATCH 4/4] Record the two specifier review debts in the issues inbox Neither can be closed by code, and both would otherwise be lost when the session that found them ends. * P2 - the DSM-5-TR specifier catalogue has no clinician sign-off. 585 items, 71 source-verified, none reviewed, dataset last updated 2026-05-09. PR #2726 makes that state visible at the point of selection, which is as far as code reaches. * P3 - the fifteen remaining one-of group labels are authored here rather than reviewed. The reopen control added in #2726 bounds the consequence to a default rather than an unrecordable combination, so this is a confirmation pass, not a blocker. Both are inbox requests, not ledger rows, so they stay merge-safe until a reconcile runs. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011RJFStNCUPhDrEdAJe2r4i --- .../0ecf9856-7f74-40d4-94e4-fee50c7b4a38.json | 14 ++++++++++++++ .../bcd23f66-16fc-47b7-8b6e-d3f2ed1569c4.json | 14 ++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 docs/outstanding-issues-inbox/0ecf9856-7f74-40d4-94e4-fee50c7b4a38.json create mode 100644 docs/outstanding-issues-inbox/bcd23f66-16fc-47b7-8b6e-d3f2ed1569c4.json diff --git a/docs/outstanding-issues-inbox/0ecf9856-7f74-40d4-94e4-fee50c7b4a38.json b/docs/outstanding-issues-inbox/0ecf9856-7f74-40d4-94e4-fee50c7b4a38.json new file mode 100644 index 0000000000..baaffca8ca --- /dev/null +++ b/docs/outstanding-issues-inbox/0ecf9856-7f74-40d4-94e4-fee50c7b4a38.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "id": "0ecf9856-7f74-40d4-94e4-fee50c7b4a38", + "createdOn": "2026-09-07", + "action": "add", + "payload": { + "pri": "P3", + "type": "task", + "summary": "Fifteen specifier one-of group labels are authored, not clinician-reviewed", + "detail": "singleSelectGroupLabels in src/lib/specifier-builder-diagnoses.ts decides which specifier groups the builder offers as radios. The dataset carries no exclusivity field, so the mapping is authored in this repo against the item lists. PR #2726 corrected four labels that were blocking valid combinations (Classes, Clusters, Aetiology, Attraction) and added a per-disorder exception for Specific Phobia under Type, but the remaining fifteen labels (Course, Course and Status, Current Episode, Insight, Pattern, Presentation, Prognosis, Remission, Severity, Severity (Major NCD), Severity/Course, Subtype, Subtypes, Type, Types and Severity) have only had that same authored read, not a qualified one. Consequence is now bounded rather than silent: every single-select group carries a control that reopens it as a checkbox list, so a wrong entry costs a default the clinician can override, not an unrecordable combination. Wanted: a psychiatrist confirms each of the fifteen against DSM-5-TR, and any that fail move out of the set or gain a disorder-level exception. Pinned by tests/specifier-builder-diagnoses.test.ts.", + "source": "PR #2726 (BigSimmo/Database), specifier builder one-of mapping correction", + "issueUlid": "01M1XS1ZNVFYSQGK03ER892KEZ" + } +} diff --git a/docs/outstanding-issues-inbox/bcd23f66-16fc-47b7-8b6e-d3f2ed1569c4.json b/docs/outstanding-issues-inbox/bcd23f66-16fc-47b7-8b6e-d3f2ed1569c4.json new file mode 100644 index 0000000000..2fa8a404a1 --- /dev/null +++ b/docs/outstanding-issues-inbox/bcd23f66-16fc-47b7-8b6e-d3f2ed1569c4.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "id": "bcd23f66-16fc-47b7-8b6e-d3f2ed1569c4", + "createdOn": "2026-09-07", + "action": "add", + "payload": { + "pri": "P2", + "type": "task", + "summary": "DSM-5-TR specifier catalogue has no clinician sign-off: 585 items, 71 source-verified, 0 reviewed", + "detail": "The Specifiers mode (search, detail routes, Map, and now the Build catalogue path) serves 585 specifier items across 131 disorders from data/specifiers-search-index.json. 71 carry a verified source; every one of the 585 is still clinician-review-pending, and the dataset lastUpdated is 2026-05-09. Generated definitions are correctly withheld and PR #2726 now renders ReviewStatusBadge on each option at the point of selection, so the state is honest on screen, but the underlying review has never been done. This is a qualified-clinician task, not a code task: a psychiatrist has to read each entry against current DSM-5-TR / ICD-11 materials and record the outcome in the review fields (sourceVerificationStatus, clinicianReviewStatus) that data/specifiers-content.json already carries. Scope it in tranches by category rather than as one pass. Until it is done, the builder must keep its aide-memoire framing and must not be described as decision support.", + "source": "PR #2726 (BigSimmo/Database), specifier builder review-status work", + "issueUlid": "01M1XS1N1YZ3GZ5PDNEACPWDY8" + } +}