Skip to content

v2.5.0

Latest

Choose a tag to compare

@ZoliQua ZoliQua released this 09 Sep 23:08
· 45 commits to main since this release

Added

  • Engine CodeSystem embedded in the FHIR export (issue #23 follow-up). The
    Bundle now carries the engine's own CodeSystem resource (canonical
    …/fhir/CodeSystem/odontogram, placed right after the Patient) enumerating
    every code the export can emit under the local system — finding types taken
    straight from the clinical-axis registry, every enum value (repeated bare codes
    such as none/mesial/temporary are merged, their alternative displays kept
    as designations), the periodontal panel/index/qualifier codes, the 2017 stage/
    grade/extent summaries and case-condition laterality. Validators can therefore
    resolve the local codes from the Bundle itself instead of timing out on an
    unpublished system. Opt out with includeCodeSystem: false in the FHIR export
    options. The same resource is published as fhir/CodeSystem-odontogram.json
    (npm run fhir:codesystem regenerates it; a test keeps it in step with the
    generator, and a maximal-export test proves every emitted local code is
    covered). Plugin-defined custom-state:<pluginId> codes stay open-ended by
    design.

  • Guided tour: Diagnoses steps. Two new tour steps introduce the per-tooth
    Diagnoses card and the Case / regional diagnoses pop-up button (18 steps in
    total), in all 12 UI languages.

  • README refresh in all 12 languages: the project logo at the top, a pointer
    to the official Angular port (Angular Advanced Odontogram) under the API-docs
    link, the author linked to LinkedIn in the license section, and the 2.5.0
    features (Diagnoses card controls, the case-diagnoses pop-up, the official
    NEAK BNO-10 titles, the validator-clean FHIR export with the embedded
    CodeSystem).

  • Dental diagnosis coding (DX-0 foundation). Charted findings now export as
    FHIR Condition resources with a WHO ICD-10 base coding, plus an optional
    pluggable national code pack (Settings → General → Diagnosis coding system;
    ships WHO-only + BNO-10). DX-0 covers caries (K02) and refactors the perio
    (K05) Condition to share the coding builder (default output unchanged). More
    findings, the diagnosis picker, and further packs (US ICD-10-CM, SNOMED) follow
    in later sub-projects.

  • Dental diagnosis coding: charted findings (DX-1). Beyond caries, the FHIR
    export now derives WHO ICD-10 Conditions for pulp (K04.0/.1), apical
    (K04.4-.9, radicular cyst K04.8), root caries (K02.2/.3), tooth wear (K03.0-.2,
    .8), resorption (K03.3), calculus (K03.6), discoloration (K00.3/.8, K03.7),
    tooth loss (K08.1) and retained root (K08.3) — one Condition per finding per
    tooth, presence-gated. Fractures and peri-implant disease are not yet coded
    (no WHO ICD-10 code). The active national pack (e.g. BNO-10) still overlays.

  • Dental diagnosis coding: per-tooth picker + overrides (DX-2). A new
    Diagnoses card lists a tooth's chart-derived ICD-10 diagnoses and lets the
    clinician suppress one or add a tooth-level diagnosis the chart does not
    represent; the effective coded set (derived − suppressed + added) drives the
    FHIR export and the coded-diagnoses summary/tooltip. Payload 2.21 (additive
    dxOverrides); the visual chart is unaffected.

  • Guided intro tour, reworked and extended. The tour now steps with the left
    and right arrow keys (a teardown bug used to unbind the keyboard handler after
    the first step), targets the real restoration control instead of a control that
    no longer exists, points the numbering step at Settings (where numbering now
    lives) and the language step at the language menu, and grows from 12 to 16 steps
    with new steps for root canal treatment and the periodontal view (it switches
    into the periodontal view and back, and skips those steps when the periodontal
    chart is turned off). The Intro toolbar icon is now a play in a circle.

  • About and credits popup. A new toolbar button after Import opens an About
    and credits dialog with a short program description, a contributor list that
    links each person to their GitHub profile, the external libraries the app is
    built with, an invitation to open a pull request, and a Star on GitHub badge. A
    GitHub link is also added to the toolbar. Translated into all 12 UI languages.

  • Community health files. Added CODE_OF_CONDUCT.md, CONTRIBUTING.md and
    SECURITY.md, and a Credits section to every language README in lang/.

  • Selectable tooth-anatomy profile (Settings → Odontogram → Tooth anatomy;
    #18/#22).
    A new session-level setting chooses the tooth-anatomy profile:
    classic (the default, unchanged) or measured. The measured profile
    renders nine literature-measured tooth templates in a two-arch, per-tooth-width
    layout (from the cognovis "candidate anatomy" work), switchable at runtime via
    the shared session. The tools/toothgen Python generator (dev-only, out of the
    npm package) regenerates the measured drawings from their hand-authored source
    SVGs. No payload/render change for the classic profile; goldens byte-identical.
    Known limitation: the measured molar templates 17/46 omit ~16 milktooth /
    pulp-inflammation layers, so those specific findings do not draw on those
    positions in the measured profile.

  • Composable UI: declarative control cards (issue #20, Tier 3). The control
    cards are being converted from imperative id-bound wiring to self-contained
    declarative React components (following PerioSidebar), one per release step,
    each reading engine state via a new useEngineState() hook and writing through
    new getters/selection-setters. Converted so far: the Orthodontics card
    (OrthodonticsCard; getActiveOrtho(), setOrtho{Appliance,Drift,Vertical,Rotation}ForSelection)
    the Statuses card body (StatusesCard; getEdentulous(), resetMouth(),
    reusing setEdentulous/applyPrimaryDentition/applyMixedDentition/getStatusExtras/applyStatusExtra)
    — also fully declarativizing the Edentulous toggle (its aria-pressed now derives
    from state, replacing the delegated handler + imperative syncs) — and the
    Caries card body (CariesCard + a reusable SurfaceCross per-surface grid
    component; getActiveCaries() + its selection setters), and the Fillings card
    body (FillingsCard, reusing SurfaceCross with the recurrent-caries + defect
    indicators and the simple/complex mode swap; getActiveFillings() + its setters),
    and the Root / periodontium card body (RootPeriodontiumCard;
    getActiveRootPerio() + its setters — pulp/endo optgroups with the endo↔pulpDx
    exclusion, apical/resorption/mobility/peri-implant/calculus/mods; the 6-site
    probing grid stays imperative for now), and the Tooth details card body
    (ToothDetailsCard; getActiveToothDetails() + its setters + resetTooth() —
    base/substrate/restoration with per-tooth option lists and ${type}|${material}
    encode/decode, wear/discoloration, and the full row-visibility sweep incl. the
    #extractionPlanRow reparenting). With this, all six control cards are
    declarative
    : wireControls()/syncControlsFromState() no longer build or sync
    any control card, and the imperative buildSelect machinery is removed (only the
    carved-out 6-site perio probing grid remains imperative). Behavior and rendered
    DOM are unchanged. No payload/FHIR/render change; all goldens byte-identical;
    payload version unchanged.

  • Composable UI: on-demand surfaces (issue #20, Tier 2). Control wiring is now
    re-runnable / idempotent, so a surface can unmount and remount (e.g. mount on
    demand in a drawer or tab) — the Tier 1 "mount every surface before init / hide
    with CSS rather than unmount" constraint is lifted. New exported rewireControls()
    and rebuildGrid() let a remounting surface rebind its fresh DOM (surfaces do this
    automatically on mount). Internally, per-element binding is deduplicated via a
    WeakMap marker (replacing the one-shot controlsWired guard), and the three
    former hide-instead-of-unmount workarounds (the control panel across the perio
    view, the chart column in the Dental Chart segment, and the Status|Plan toggle) are
    now real conditional mounts. OdontogramShell's default composition is unchanged
    and still renders byte-identical DOM. No payload/FHIR/render change; all goldens
    byte-identical; payload version unchanged.

  • Composable UI surfaces (issue #20, Tier 1). The shell's four UI regions are
    now exported as standalone components — OdontogramTopbar,
    OdontogramChartSurface, ToothInfoSurface, ToothControlsSurface — that a
    host can place in its own layout regions, together with a new
    OdontogramProvider and a useOdontogramUi() hook (plus the
    OdontogramUiContextValue type) for building custom surfaces. All surfaces
    share one package-owned session through the provider. OdontogramShell is
    unchanged and remains the supported all-in-one component — it is now a thin
    composition of these surfaces under the provider, and its rendered DOM is
    byte-identical to before (frozen by a new shell DOM-parity test). Tier 1
    keeps two documented constraints (one provider per document; surfaces mounted
    before initOdontogram() and hidden with CSS rather than unmounted); later
    tiers lift them. Session/composition only — no payload/FHIR/render change;
    SVG-fingerprint, FHIR-golden, and roundtrip fixtures byte-identical; payload
    version unchanged.

  • Fillings settings as controlled props (issue #17). <OdontogramShell>
    now accepts the four fillings-tab settings as optional props —
    fillingComplexity?: "complex" | "simple",
    fillingDefectEnabled?: boolean,
    fillingMaterialAvailability?: Record<string, boolean> (over
    "amalgam" | "composite" | "gic" | "temporary", unknown keys ignored), and
    fissureSealingEnabled?: boolean — plus their write-back callbacks
    onFillingComplexityChange(v), onFillingDefectEnabledChange(v),
    onFillingMaterialAvailabilityChange(material, enabled) (per-material,
    mirroring the engine setter) and onFissureSealingEnabledChange(v), fired
    from the Settings → Fillings tab. Unlike the pulpDetailLevel-style props,
    the sync is defined-gated: an omitted prop never writes the engine, so
    an imperative setFillingComplexity() call before mount is preserved and
    standalone mode is unchanged; a provided prop writes the engine and the
    React state together, so the Settings modal never shows a stale value. The
    fillingMaterialAvailability prop is dependency-keyed on a canonical
    serialized form (sorted keys) with a diff against the last applied record —
    an inline literal with identical content does not re-fire the engine writes.
    FillingComplexity is re-exported from the shell. Session-only state: no
    payload/FHIR/render change (SVG-fingerprint, FHIR-golden, and roundtrip
    fixtures byte-identical; payload version unchanged).

  • Fillings session-flag setters notify + are idempotent. The four
    fillings setters (setFillingDefectEnabled, setFillingComplexity,
    setFissureSealingEnabled, setFillingMaterialAvailability) now follow the
    same convention as setPulpDetailLevel/setSurfaceNotation: an early
    return when the value is unchanged (no redundant
    notifyStateChange()/re-render), and a notifyStateChange() on every real
    change so onStateChange subscribers (e.g. hosts persisting preferences)
    observe fillings-setting changes. setFillingMaterialAvailability also
    ignores unknown materials without notifying.

  • Dental diagnosis coding: tooth fracture + peri-implant (DX-3a). A broken
    tooth now codes to WHO ICD-10 S02.5 (Fracture of tooth). Peri-implant
    mucositis and peri-implantitis are recognized as diagnoses but have no WHO
    ICD-10 code, so they are surfaced as "no WHO code" in the tooltip and summary
    and emit no FHIR Condition — a coding follows when SNOMED CT is activated.
    Payload unchanged (2.21); the visual chart is unaffected.

  • Dental diagnosis coding: case/regional conditions (DX-3b). A new
    "Case / regional diagnoses" picker in the perio case panel authors whole-mouth /
    regional conditions (malocclusion & TMJ K07, oral cysts K09, salivary disease K11,
    stomatitis & oral mucosa K12/K13, arch-level developmental K00 — ~28 conditions),
    each optionally lateralized (left/right/bilateral). They export as patient-level
    FHIR Conditions and appear in the whole-mouth summary. Payload 2.22 (additive
    caseConditions); the visual chart and per-tooth data are unaffected.

  • Dental diagnosis coding: complete BNO-10 (Hungarian) pack (DX-4). The BNO-10
    coding pack now carries Hungarian displays for every coded tooth-level and
    case-level diagnosis (selectable in Settings → diagnosis coding); case Conditions
    localize under the BNO system too. A new CODING_PACKS.md guide documents how to
    add another country's pack. No payload change (2.22).

  • Dental diagnosis coding: US ICD-10-CM pack (DX-5). A selectable ICD-10-CM
    (Clinical Modification) coding pack — the first modification-class pack — that
    remaps the diagnosis code (not just the display) on tooth AND case Conditions
    (e.g. caries → K02.9, the K07 dentofacial anomalies → the M26 range), on top of the
    always-on WHO ICD-10 base. Reference/best-effort — verify against the official
    ICD-10-CM tabular list before US clinical use. No payload change (2.22).

  • Dental diagnosis coding: SNOMED CT overlay (DX-6). An opt-in Settings toggle
    (SNOMED CT, default off) adds a SNOMED CT coding to each FHIR Condition alongside
    the WHO ICD-10 (and any national pack) coding. It codes the peri-implant findings
    (which have no WHO ICD-10 code) as SNOMED-only Conditions and adds a SNOMED
    laterality qualifier to case-condition bodySites. The seeded concept IDs are
    provisional — verify against the official SNOMED CT browser before clinical use.
    No payload change (2.22).

  • Dental diagnosis coding: FHIR import round-trip (DX-7). parseFhirBundle now
    reconstructs the diagnosis layer from Condition resources — case/regional conditions
    (with laterality) and per-tooth diagnosis overrides (add/suppress, diffed against the
    re-derived chart) — completing the FHIR round-trip for our own exports. Chart-only
    bundles are unaffected. No payload change (2.22).

Fixed

  • FHIR export now passes the HL7 validator's Bundle identity rules (issue
    #23).
    validator.fhir.org flagged the collection Bundle on two counts: 32 of
    33 entries had no Bundle.entry.fullUrl (constraint bdl-15 requires one on
    every entry of a non-transaction Bundle), and the placeholder subject used
    urn:uuid:odontogram-subject, which is not a valid RFC 4122 UUID (the
    urn:uuid: scheme may only wrap a real UUID — the same latent fault sat on the
    Condition and evidence-Observation fullUrls). Every entry now carries a
    deterministic id plus an absolute, readable fullUrl under the engine's own
    FHIR base (https://github.com/ZoliQua/React-Odontogram-Modul/fhir/<Type>/<id>,
    a sibling of the local CodeSystem URL); per-tooth Observations derive their id
    from content (type + FDI tooth + finding code, ordinal-suffixed for per-surface
    repeats), and every subject/evidence reference points at those fullUrls. No
    random UUIDs, so the export stays golden-testable. The FHIR import is
    unaffected (it reads resource.id, never fullUrl); the JSON round-trip golden
    is byte-identical. The remaining validator messages are terminology-server
    validate-code timeouts on the engine's own (unpublished) CodeSystem — expected
    for a local code system, not a defect. Reported by @BabuBahir.
  • Dental diagnosis coding: verified the ICD-10-CM + SNOMED codes. Corrected two
    provisional SNOMED concept IDs (pulpitis → 32620007, peri-implantitis →
    699422003) and added a SNOMED concept for peri-implant mucositis (699684005),
    and fixed the ICD-10-CM tooth-fracture display title; all ICD-10-CM codes were
    confirmed against ICD-10-CM 2026 and the SNOMED concepts against SNOMED CT
    International (CSIRO Ontoserver).
  • FHIR Condition ICD-10 code for the molar-incisor pattern. A molar-incisor
    periodontitis emitted K05.2 with a "Acute periodontitis" display, which is
    the WHO ICD-10 meaning of K05.2 (an unrelated diagnosis; the ICD-10-CM
    "aggressive periodontitis" meaning does not belong on an R4/WHO bundle). It now
    emits K05.3 (Chronic periodontitis) like every other periodontitis; the
    molar-incisor pattern is still carried by the periodontal-extent stage entry.
  • Plugin customStates isolation between the status and plan charts. An
    object-valued plugin state was shared by reference across the two charts (they
    are cloned via serializeState -> hydrateState, which passed customStates
    by reference), so mutating it in one leaked into the other. Object values are
    now deep-copied on hydrate.

Changed

  • Case/regional diagnoses moved into a pop-up. The "Case / regional
    diagnoses" list left the Periodontal-status sidebar and now lives in its own
    focus-trapped dialog (CaseDiagnosesModal), opened from a new Diagnoses
    button beside the Odontogram / Periodontal-status view toggle. The add-picker
    in the pop-up is code-first (K07.6 …) and code-sorted, matching the rows.
    (Shell-DOM parity fixture re-frozen for the one new button; the SVG/FHIR/
    round-trip goldens are byte-identical.)
  • Diagnoses row controls: exclude toggle + delete, and a full-delete that
    clears the chart.
    Each diagnosis row is now three explicit parts — the
    code-first text, an exclude toggle (eye/eye-off icon with a hover tooltip;
    drops the finding from the FHIR export but keeps it on the chart), and a
    delete (×) button. Delete now calls a new removeDiagnosisFromSelection,
    which clears the underlying chart axis so the diagnosis and its glyph are
    removed (it also covers caries, clearing the surfaces). This replaces the old
    CSS-hidden suppress checkbox, whose "Exclude" label rendered as unclickable
    text. The periodontal Case/regional diagnoses list adopts the same code-first
    layout and an × delete icon, is sorted by ICD-10 code (getCaseConditions now
    code-sorts, so the list and its summary enumeration match the Diagnoses card),
    and its rows stay on one line (the × no longer wraps under a long label).
  • BNO-10 pack aligned to the official NEAK titles. The Hungarian (BNO-10)
    diagnosis-coding pack now carries the official NEAK BNO-10 display strings
    (verified against the NEAK "3- és 4-jegyű BNO kódok" törzs) instead of the
    earlier clinician-friendly paraphrases, and uses the standard ICD-10 system URI
    (http://hl7.org/fhir/sid/icd-10) rather than the previous stand-in — the
    Hungarian codes are identical to WHO ICD-10, so a consumer sees one ICD-10 code
    with both English and Hungarian displays. All 53 codes were confirmed present in
    the official törzs, with one documented exception: oral mucositis (K12.3) is a
    valid WHO code but absent from the Hungarian BNO-10, so it keeps the WHO code
    with our own Hungarian display. Default (WHO-only) FHIR export is unchanged.
  • Diagnoses card: code-first, sorted, and add-writes-the-chart. The per-tooth
    Diagnoses card now shows the ICD-10 code first (e.g. "K04.0 Pulpitis"), sorts the
    diagnoses by code, and — when you add a diagnosis from the picker — writes the
    underlying chart finding (so the glyph appears on the tooth and it derives/exports
    normally) instead of only tagging the coded layer. Plain caries stays authored per
    surface in the Caries UI. Suppressing a derived diagnosis remains coded-layer only.

Security

  • Prototype pollution on FHIR import. A crafted bundle whose bodySite
    carried a non-numeric tooth code (e.g. "__proto__") reached ensureTooth,
    where the naive if (!teeth[id]) guard returned Object.prototype and later
    writes leaked onto it. The importer now accepts only two-digit numeric tooth
    codes (rejecting __proto__/constructor/prototype) and ensureTooth uses
    an own-property check. Regression-tested.

CI

  • Run ESLint as a required CI step alongside type-check, test and build.