Commit graph

318 commits

Author SHA1 Message Date
github-actions[bot]
4e1a870fe2 Release v6.14.0 2026-04-22 17:35:12 +00:00
Daniel
33bfc0bfbc feat(pe-guide): teaching-focused redesign — grading scales, pearls, significance
User feedback: the exam steps were too generic (e.g. "Shoulder abduction
— 5/5 bilaterally" never explained what 5/5 means or HOW to test it).
The guide needs to serve as a teaching tool, not just a checkbox list.

Three changes:

1. GRADING SCALES reference card (new). Collapsible panel at the top of
   each system showing the relevant scales:
   - Neuro: MRC strength (0-5), DTR (0-4+), Plantar response
   - MSK: Scoliometer ATR, Beighton hypermobility score
   Each scale shows the grade AND its clinical meaning in a compact
   table. No more orphan "5/5 bilaterally" without definition.

2. Per-component SIGNIFICANCE + PEARL fields (optional). Adolescent
   neuro components enriched with:
   - Significance: one-line clinical relevance (what this component is
     actually for — what pathologies it detects)
   - Teaching pearl: a Hutchison/Bates/Nelson-style tip that helps the
     learner see past the mechanics to the reasoning
   Visually distinct — pearl gets a warm amber accent, significance is
   a crosshair icon under the name.

3. Method strings REWRITTEN for every adolescent strength step. Before:
   "Shoulder abduction — 5/5 bilaterally". After: "Patient abducts both
   arms to 90°. Examiner pushes down on each arm just above the elbow
   while patient resists. Compare sides. — Holds against full resistance
   — MRC 5/5 bilaterally". Same treatment for all 14 strength steps,
   all 8 DTR steps, and tone/pronator-drift.

UI redesign:
- Accent bars on cards (cyan for MSK, purple for neuro) for visual
  anchor
- Numbered step circles instead of "1." prefix
- HOW / NORMAL label badges on each step
- Watch-for block with red left-border for red-flag grouping
- System-level header with icon (bone for MSK, brain for neuro)

Other age groups (newborn through school-age) keep the old data shape
(steps without pearls) — they still render correctly, just without the
pearl/significance blocks. Enriching them is an incremental follow-up.
2026-04-22 19:35:03 +02:00
github-actions[bot]
31507a4f09 Release v6.13.1 2026-04-22 17:30:03 +00:00
Daniel
b37c565cf0 fix(ui): replace native alert/confirm with showConfirm/showToast helpers
Daniel flagged the native browser confirm() dialogs in Extensions as ugly
and incompatible with the app's design. There was also a stray alert()
in calculators.js resus-meds weight validation.

Replaced:
- extensions.js: confirmDelete → showConfirm(..., {confirmText: 'Move to trash'})
- extensions.js: confirmPurge  → showConfirm(..., {danger: true, confirmText: 'Delete permanently'})
- calculators.js:2060 alert() → showToast(..., 'error')

All three helpers (showConfirm, showToast) are already defined as globals
in public/js/app.js. The design already had a modal — I should have used
it from the start.

Audit confirmation: `grep -rnE '\b(alert|confirm|prompt)\s*\(' public/`
now returns only comment references and the showConfirm definition
itself. No native dialogs remain anywhere in the frontend.

Playwright test updated to click the in-app modal's #confirm-modal-ok
and #confirm-modal-cancel buttons instead of intercepting page.on('dialog').
2026-04-22 19:29:53 +02:00
Daniel
8ea79c7f30 test(e2e): shared fixture with pageerror + console-error guards + Extensions CRUD suite
Two additions:

1. e2e/fixtures.js — shared test infrastructure
   - Custom `test` extending @playwright/test with two auto-fixtures on
     every page: page.on('pageerror') and page.on('console') of type
     'error'. Any uncaught JS error fails the test. This is the SSO-
     bug-class safety net: if we'd had this earlier, the silent
     admin.js ReferenceError would have failed CI instead of shipping.
   - `authedPage` fixture — logs in via API, injects session cookie,
     provides pre-authed page ready to drive.
   - `mockAI(page)` helper — intercepts generate-* and transcribe
     endpoints with canned JSON responses. Enables fast deterministic
     CI runs. Opt-out via E2E_USE_REAL_AI=1 to hit real LiteLLM.
   - Console-error allowlist for known noise (favicon 404, lazy-loaded
     Whisper models, etc.).

2. e2e/tests/extensions-crud.spec.js — 11 tests covering the new
   Pagers & Extensions feature end-to-end:
   - empty state, add extension, add pager (correct grouping)
   - edit persists, search by location + by number
   - soft-delete with confirm → moves to trash
   - restore from trash → reappears in active
   - purge from trash → permanent
   - cancel dialog keeps item, cancel form keeps nothing, validation
     on required fields

Known follow-up: the e2e container (pediatric-ai-scribe-e2e) is still
on the pre-entrypoint image from before the OpenBao migration, so it
doesn't have the Extensions routes yet. Rebuilding it needs a small
entrypoint enhancement to honor docker-compose-level env overrides
vs OpenBao-fetched values (e.g. TURNSTILE_SECRET_KEY="" for the e2e
instance). That's separate work — this commit just lays in the tests.
2026-04-22 19:27:05 +02:00
github-actions[bot]
651f799c17 Release v6.13.0 2026-04-22 16:54:37 +00:00
Daniel
d859c8c5a9 feat(extensions): personal Pagers & Extensions directory with soft-delete
New top-level tab positioned after Physical Exam Guide. Per-user
directory of hospital phone extensions and pagers — grouped by location
then type, searchable, soft-deleted.

Data:
- New table user_phone_extensions (id, user_id, location, name, number,
  type CHECK (extension|pager), notes, trashed_at, timestamps).
  Partial indexes on active vs trashed rows for fast filtering.
- Not PHI — hospital internal phone directory. Plaintext.

API (all user-scoped, all params validated):
- GET    /api/extensions?trash=1&q=text  — list active or trash, optional search
- POST   /api/extensions                  — create
- PUT    /api/extensions/:id              — update (requires all three core fields)
- DELETE /api/extensions/:id              — soft-delete (sets trashed_at)
- POST   /api/extensions/:id/restore      — un-trash
- DELETE /api/extensions/:id/purge        — hard-delete (only if trashed)

All :id params parsed + validated (positive integer) before query.
All queries parameterized, every WHERE includes user_id scoping.

UI (public/js/extensions.js + components/extensions.html):
- Search bar with 200ms debounce, server-side LIKE on location/name/number/notes
- Add button expands inline form — location (with datalist of existing
  locations for autocomplete), name/dept, number, type, optional notes
- Each entry renders as a card: big monospace number, dept, type badge,
  edit + delete inline
- Grouped by location → type (Extensions / Pagers subheaders)
- Trash view: toggle shows trashed items with Restore + Purge actions
- Trash count badge on the Trash button updates after every delete/restore
- Delete requires confirm() dialog, then soft-delete (easy to undo)
- Purge from trash requires a second confirm() ("cannot be undone")
- Esc closes the form; form resets between Add and Edit
2026-04-22 18:54:26 +02:00
Daniel
917d4f8115 chore(pe-guide): restore source-citation comment block
Daniel clarified the earlier feedback: not "never cite in code", but
"ask before citing". He confirmed this block should stay.
2026-04-22 18:08:19 +02:00
Daniel
ee4940e0a6 chore(pe-guide): remove source-citation comment block
Daniel prefers no citations embedded in the code. Keeps the data-model
comment which is load-bearing for future edits.
2026-04-22 17:49:36 +02:00
github-actions[bot]
c35b05fc5a Release v6.12.0 2026-04-22 15:15:37 +00:00
Daniel
c13ba04955 feat(pe-guide): step-by-step OSCE checklist, Bates/Nelson/Hutchison sourced
Replaces the generic one-line-per-component format with a step-level
checklist. Each exam component now contains 3–13 discrete steps, each
with its own Normal/Abnormal/Skip toggle and optional abnormal note.
Physician ticks the exam off step-by-step; report generation
summarises at the component level but knows exactly which steps were
performed.

Example — previously the adolescent "Cranial nerves (II–XII)" was a
single row: "How to perform: Full formal adult-pattern exam. Expected:
All cranial nerves intact." That's unhelpful. Now it's 14 discrete
steps: CN I, CN II acuity, CN II fields, CN II fundoscopy, CN II/III
pupils, CN III/IV/VI EOM, CN V sensation V1/V2/V3, CN V motor, CN V
corneal, CN VII forehead/eye-close/smile/puff, CN VIII, CN IX/X, CN
XI, CN XII — each with specific method and expected finding. Same
depth for MSK: scoliosis = 5 discrete steps (standing inspection,
Adam forward-bend, rib-hump check, scoliometer, plumb-line), joint
stability = 8 named tests (Lachman, anterior drawer, varus/valgus,
McMurray, apprehension, Neer/Hawkins, anterior drawer ankle, talar
tilt), Beighton = 5 per-joint measurements, etc.

Sources cited in code header: Bates' Guide 13th ed, Nelson Textbook
22nd ed, Hutchison's Clinical Methods 25th ed, Fenichel Clinical
Pediatric Neurology 8th ed.

Backend route accepts the flat step array (grouped by component on
the server), passes structured text to the AI with methods and
expected findings per step. Prompts updated to summarise at the
component level rather than step-by-step, so output is clinically
readable.

Scope: MSK + Neuro × 6 age groups (newborn, infant, toddler, preschool,
school-age, adolescent). More systems follow the same pattern —
append to PE_DATA.
2026-04-22 17:15:27 +02:00
github-actions[bot]
22dd5cc8f4 Release v6.11.0 2026-04-22 14:52:24 +00:00
Daniel
ac5292b015 feat(pe-guide): Physical Exam Guide tab — OSCE reference + narrative report
New top-level tab (positioned after Catch-Up Schedule) combining two
functions:
1. Study reference — for each (age group, system) shows OSCE-style
   components with technique, expected normal finding, and abnormal-
   feature watch-list.
2. Documentation generator — physician marks each component
   Normal / Abnormal (with free-text detail) / Skip; AI produces a
   two-section report (Technique + Findings), narrative or structured
   list format.

Scope v1: MSK + Neuro × 6 age groups (newborn, infant, toddler,
preschool, school-age, adolescent). More systems can be added to the
embedded PE_DATA in peGuide.js without route changes.

Files:
- src/routes/peGuide.js      — POST /api/generate-pe-narrative (mirrors
                                milestone-narrative pattern: AppRole-level
                                injection guard, clinical audit category,
                                PHI redaction upstream already in place)
- src/utils/prompts.js       — peGuideNarrative + peGuideList prompts,
                                structured two-section output
- public/components/pe-guide.html — demographics bar + sub-pills + cards
- public/js/peGuide.js       — embedded PE_DATA (all clinical content),
                                render + state + AI call
- public/index.html          — tab button, section, script include
- server.js                  — mount route at /api

No schema change. No PHI stored — findings live in memory only, exported
via existing copy/read-aloud/Nextcloud actions.
2026-04-22 16:52:13 +02:00
github-actions[bot]
1bb8918b46 Release v6.10.3 2026-04-22 11:11:43 +00:00
Daniel
749aa23e87 fix(bilirubin-ui): clarify neurotoxicity-risk-factor label + expandable AAP 2022 list
The dropdown was labeled just "Risk Factors" with option "None (lower risk)"
— ambiguous because AAP 2022 uses "risk factors" in two distinct senses:
(a) risk factors for developing hyperbilirubinemia (screening-only, do not
change thresholds) and (b) neurotoxicity risk factors (do change thresholds).
Only (b) belongs on the threshold nomogram, and a clinician glancing at the
form could easily pick wrong.

Changes:
- Label: "Neurotoxicity risk factors"
- Options: "Absent" / "Present (any one qualifies)" — removes the misleading
  "None (lower risk)" phrasing (no-risk curve actually has HIGHER thresholds)
- Expandable details listing the 6 specific AAP 2022 neurotoxicity risk
  factors (isoimmune hemolysis, G6PD, other hemolysis, sepsis, albumin <3.0,
  clinical instability <24h) with explicit note that GA <38w is handled by
  the per-week curve, not by this checkbox — prevents double-counting.

No data changes. Cite: Kemper et al., Pediatrics 2022;150(3):e2022058859, Box 2.
2026-04-22 13:11:34 +02:00
github-actions[bot]
2709595793 Release v6.10.2 2026-04-22 10:41:25 +00:00
Daniel
456101a28a fix(bilirubin): AAP 2022 per-week thresholds, exact match to peditools
The previous tables grouped all ≥38-week infants into one table (using
38w values) and 36-37 into another. AAP 2022 actually has separate
phototherapy curves per completed week for 35, 36, 37, 38, 39, 40+.

Worst real-world impact: a 40-week infant at 72h of life got the 38w
threshold (18.8 mg/dL) instead of the correct 40w threshold
(19.8 mg/dL) — a 1.0 mg/dL error at exactly the clinical decision point.
Borderline infants could be started on phototherapy unnecessarily, or
the reverse (miss a true threshold) depending on the direction.

Replaced the block with 18 distinct per-week tables extracted directly
from peditools.org/bili2022 API:
  - Phototherapy no-risk: 35, 36, 37, 38, 39, 40+ (all differ)
  - Phototherapy with-risk: 35, 36, 37, 38+ (38-41 identical)
  - Exchange (both risk states): 35, 36, 37, 38+ (38-41 identical)

Selection logic updated to map gaNum → correct table. HTML dropdown
label clarified to "completed weeks" with a one-line note that days
don't change the curve per AAP 2022.

Validation: exhaustive roundtrip — 7 GA weeks × 85 hours × 2 risk
profiles = 1190 cases, all match peditools within 0.01 mg/dL (zero
mismatches). See commit message footer.
2026-04-22 12:41:15 +02:00
github-actions[bot]
c4da879336 Release v6.10.1 2026-04-22 03:52:10 +00:00
Daniel
508530eda8 fix(neonatal): replace rounded Fenton 2013 LMS with peditools-validated values
The previous LMS table (L rounded to 2 decimals, ~0 near term) underfit
the skew of Fenton 2013 and drifted ~0.05 z-score units from peditools
and Epic at term. Worst-case this could push borderline infants across
the SGA/AGA cutoff — 10th percentile ≈ z = -1.28, a 0.05-SD drift is
enough to flip the classification.

New LMS: empirically fit against 6 probe weights per week at
peditools.org/fenton2013 (widely-used Fenton 2013 calculator). Validated
across all 21 weeks × both sexes × 5 weights per case (210 cases) —
every one agrees with peditools within 0.01 z-score units, mean
difference 0.002.

Example — 40 5/7 wk male, 3070 g:
  BEFORE: z = -1.38  (matched only one of three external sources)
  AFTER:  z = -1.42  (matches Epic -1.43 and third-source -1.44)
  Peditools at integer 40w: z = -1.10 (exact match with new table at
    integer-week input)

LMS fit RMSE < 0.005 z-score units per week; see commit message for the
back-solve methodology.
2026-04-22 05:52:00 +02:00
github-actions[bot]
887ef04de7 Release v6.10.0 2026-04-22 01:59:19 +00:00
Daniel
42e59fa958 feat(security): OpenBao-backed secret injection via entrypoint
Adds an optional secret-fetch step at container boot. When OPENBAO_ADDR,
OPENBAO_ROLE_ID, and OPENBAO_SECRET_ID are set, the entrypoint
authenticates to OpenBao via AppRole, pulls kv/ped-ai/prod, and exports
each key as a process env var before exec'ing node. When OPENBAO_ADDR
is unset the entrypoint is a no-op — the legacy .env flow continues to
work unchanged (e2e container, local dev, rollback).

Changes:
- docker-entrypoint.sh: new — AppRole login + KV fetch + env inject +
  exec. Fails fast on missing/invalid creds; unsets bootstrap vars
  before launching node so they don't linger in the process env.
- Dockerfile: multi-stage copy of /bin/bao from openbao/openbao:2.5.3
  (multi-arch handled automatically by buildx manifest-list resolution).
  Adds jq for JSON parsing. Wires ENTRYPOINT to the script; CMD
  remains ["node", "server.js"].
- .env.example: documents the three vault-bootstrap variables at the
  top and notes that everything below is vault-sourced when OPENBAO_ADDR
  is set.

Rollout is two-phase for safety: rebuild image with unchanged .env
(proves no regression in legacy mode), then add the three OpenBao vars
and restart to cut over to vault-sourced secrets. Rollback at any point
is blanking OPENBAO_ADDR in .env + restart.
2026-04-22 03:59:10 +02:00
github-actions[bot]
f1802d66f4 Release v6.9.0 2026-04-21 23:01:09 +00:00
Daniel
250646110f fix(security): timing-safe forgot-password + redact log file writer
Two independent PHI-leak hardenings folded together:

1. forgot-password timing oracle
   The hit path previously did SELECT + token gen + UPDATE + SMTP send
   before responding; the miss path returned after the SELECT. An
   attacker could distinguish registered emails by response latency
   (SMTP RTT is hundreds of ms). Response is now sent immediately after
   Turnstile, with the DB and email work fired-and-forgotten in a
   background async block. Hit and miss take identical wall-clock time.

   Also hardened req.body.email to tolerate missing/non-string input
   instead of throwing 500.

2. logger.file redaction
   logger.info/warn/error wrote straight to /app/data/logs/YYYY-MM-DD.log
   without going through redact(). Current callers are metadata-only and
   safe, but any future caller writing logger.error('boom', req.body)
   would silently drop PHI to disk. Route both message and optional data
   through redact() — same helper the audit path already uses. Benign
   startup messages pass through unchanged; SSN/phone/email/DOB patterns
   are tokenised, long note-body-shaped text is truncated.
2026-04-22 01:01:00 +02:00
Daniel
6dc7870a1b feat(security): AES-256-GCM at-rest encryption for encounters and memories
Extends the existing crypto helper (already used for audio backups and the
Nextcloud token) to cover every column that can hold PHI:

- saved_encounters.transcript, .generated_note, .partial_data
- user_memories.content (templates + Dragon-style corrections)
- user_memories.name (auto-derived from original snippet on corrections,
  so effectively PHI)

Reads decrypt transparently. Legacy plaintext rows continue to work —
decryptString passes non-enc1: values through unchanged — so no migration
is required; rows re-encrypt on their next save.

The encounters list query previously used LEFT(transcript, 200) for a
preview. With ciphertext that slice is meaningless, so the route now
fetches the full columns, decrypts in Node, then slices. At 7-day auto-
delete the row count is bounded and the cost is a handful of GCM
decrypts per list call.

user_memories ORDER BY moved from (category, name) to (category, id)
since SQL can no longer order on encrypted names.

Closes the HHS breach-notification safe-harbor gap on at-rest PHI.
2026-04-22 01:01:00 +02:00
github-actions[bot]
cc035e7d8d Release v6.8.0 2026-04-21 20:20:21 +00:00
Daniel
07d7b42efc feat(wellvisit): add Expected Reflexes section to By Visit Age
Adds an age-appropriate reflex reference after the Expected Growth /
Feeding block for each well-visit age. Each entry shows the reflex
name, an expected-status chip (Present / Fading / Integrated /
Up-going / Down-going), and a short clinical note covering how to
elicit it, when it should fade, and what abnormal persistence means.

Covers primitive reflexes for newborn-6mo (rooting, sucking, Moro,
palmar/plantar grasp, tonic neck, stepping, Galant, tongue thrust,
Babinski), the transition at 6-18mo (protective extension, parachute,
plantar-response switch), and adult-pattern DTRs/frontal-release
screening from age 2 through 21.
2026-04-21 22:20:07 +02:00
Daniel
f39f906fa5 fix(admin): restore SSO settings loading on admin panel
loadOidcConfig() was called from the first IIFE in admin.js but declared
in the second IIFE. Function declarations don't cross IIFE boundaries,
so the call threw ReferenceError and left the SSO form empty with
"Disabled" selected — even when SSO was configured and working. Moved
the call into the tabChanged handler of the IIFE where the function
lives.
2026-04-21 22:19:50 +02:00
Daniel
d8504392a5 docs: add Testing section (unit + Playwright e2e) 2026-04-21 02:14:54 +02:00
Daniel
29f37b331e test(e2e): stage 2 — auth-gated pages (11 tabs × 2 viewports, 22 tests)
Adds a second containerized instance of the app with Turnstile + SMTP
disabled so Playwright can log in without a bot challenge.

- docker-compose.e2e.yml: pediatric-ai-scribe-e2e on port 3553. Shares
  postgres + pgdata with main so seeded test users (*@ped-ai.test) persist.
- Test user: e2e-user@ped-ai.test (created once via /api/auth/register
  against the e2e container — SMTP is off so register auto-verifies).
- Tests log in once per worker via /api/auth/login (module-scoped token
  cache) then inject the ped_auth cookie into each test's browser context.
  This avoids the 10-per-15-min login rate-limit.
- Mobile viewport opens the sidebar via #btn-menu-toggle before clicking
  tab buttons (which are hidden behind the hamburger <=768px).

Coverage: encounter, wellvisit, chart, vaxschedule, catchup, learning,
dictation, settings, calculators, faq + landing-page-after-login. Each
test clicks the tab, waits for the lazy component to render (>100 chars),
and asserts a known anchor string is present.

Total suite: 128 tests passing (53 desktop + 53 mobile Bedside/top-calcs +
11 desktop + 11 mobile auth-gated).
2026-04-21 01:48:17 +02:00
Daniel
146ac73da2 test(e2e): run full suite at mobile viewport too (Pixel 5)
Adds a second Playwright project so every calculator test runs at both
Desktop Chrome and Pixel 5 (~375 px). Catches mobile layout regressions
automatically. 106 tests passing (53 × 2 viewports).
2026-04-20 23:21:32 +02:00
github-actions[bot]
ed8948e539 Release v6.7.0 2026-04-20 21:20:01 +00:00
Daniel
fe7b3687ee test(e2e): add 27 Playwright smoke tests for top-row calculator tabs
Covers BP / BMI / BSA / Weight-Based Dosing / Growth / Bilirubin / Vitals /
Resus Meds / GCS / Equipment. Each tab gets: panel-loads test + one
full input→calculate→result flow. Extras for BP (Clear), Dose (max cap),
Growth (sub-pill), Bili (Bhutani sub-pill), GCS (motor change, infant switch),
Equipment (empty re-select hides).

All 53 e2e tests pass (26 Bedside + 27 top-calculators).
2026-04-20 23:19:50 +02:00
Daniel
7e7d469172 fix: scope CORS middleware to /api so static assets aren't rejected
ES module script tags (<script type="module">) always send an Origin
header on fetch, even for same-origin requests. The global cors()
middleware was rejecting /js/bedside/index.js with 500 in the e2e
harness because the container's internal origin
(http://pediatric-ai-scribe:3000) is not in APP_URL/CORS_ORIGINS.

Production was unaffected (real users hit APP_URL, which is allowed),
but the fix is architecturally correct either way: CORS belongs on
the API boundary, not on static file serving. All protected routes
are under /api/*.

Unblocks the bedside smoke suite — now 26/26 green.
2026-04-20 23:19:50 +02:00
Daniel
2742a2a130 feat: C — extract Bedside reference into ES modules
Split the Bedside clinical reference section out of calculators.js
(~1220 lines) into 20 focused ES modules under public/js/bedside/.
Each module owns one clinical topic (cardiac, seizure, sepsis, burns,
etc.) and exports an init() wired up by bedside/index.js. Shared
helpers live in shared.js and also set window._EM for back-compat
with the 4 remaining call sites in calculators.js.

Load order: classic defer calculators.js first, then module script
bedside/index.js. Handlers bind at DOM-ready; runtime _EM lookups
resolve after both are evaluated.

Makes bedside content editable per-section, shrinks calculators.js
from 4111 to 2891 lines, and keeps the existing Playwright smoke
suite (e2e/tests/bedside-smoke.spec.js) as the behavioral contract.
2026-04-20 23:19:50 +02:00
github-actions[bot]
28118c4493 Release v6.6.0 2026-04-20 02:23:33 +00:00
Daniel
f26687df50 feat: B — extract drug data to public/data/drugs.json (schema v1.0)
Moved 43 weight-based drug entries out of calculators.js string literals
into a structured JSON reference. Renderers now iterate JSON → S.drugRow.

Sections extracted (43 drugs):
- anaphylaxis (7), sedation (11), agitation (11), antiemetics (7), seizure (7)

Out of scope: seizure refractory drips (compound strings), NRP, antimicrobial
empiric regimens (already structured), airway/cardiac/tox/trauma/respiratory
/sepsis/burns sections (fine as-is or no drugs).

New:
- public/data/drugs.json — { version, last_reviewed, sections.<key>.drugs[] }
- public/js/drugs-loader.js — fetches JSON on boot, exposes window._DRUGS +
  window._DRUGS_READY Promise. Non-fatal: each calc function has a matching
  *_FALLBACK constant so a 404 on drugs.json doesn't break anything.

Schema:
- dose_mg_per_kg | dose_mg_per_kg_low/high (for ranges) | max_mg | unit |
  route | notes | source | optional special-cases (weight bands, age-dep text)

Added one unit test asserting drugs.json loads + has all 5 sections with
non-empty drugs arrays. 37/37 unit tests + 26/26 Playwright e2e tests pass.
2026-04-20 04:23:24 +02:00
github-actions[bot]
9603a8fcf8 Release v6.5.0 2026-04-20 01:51:30 +00:00
Daniel
abdbaa3507 feat: A1+A2 — Playwright smoke suite + index.html mtime-based caching
Safety net for upcoming refactors:
- 26 Playwright smoke tests via @playwright/test 1.50.0 in an official
  Playwright container (no host Node needed). Covers every Bedside sub-pill,
  the age→weight estimator, dose calculators (seizure/sepsis/anaphylaxis/
  burns/airway), and interactive widgets (lightbox, vent SVG).
- `npm run e2e` wrapper runs tests inside mcr.microsoft.com/playwright:v1.50.0-noble
  on the ped-ai_default Docker network so no host port mapping is needed.
- public/e2e-harness.html + public/js/e2e-bootstrap.js load the calculators
  component without the SPA auth wall (scripts external to satisfy CSP).

Server:
- server.js now re-reads public/index.html on mtime change instead of
  caching at boot. Fixes the "edit HTML, restart container" friction.
- CSP upgradeInsecureRequests disabled in helmet config; Caddy still
  enforces HTTPS at the reverse-proxy layer in production.
2026-04-20 03:51:22 +02:00
github-actions[bot]
30cfc9700b Release v6.4.0 2026-04-20 00:49:55 +00:00
Daniel
ea3a3533e6 feat: Bedside clinical reference module + age→weight estimator + dose-math unit tests
Calculators:
- Bedside tab consolidating emergency protocols (Neonatal+Apgar+NRP, Airway/RSI,
  Cardiac Arrest/PALS, Respiratory, O2 & Ventilation, Status Epilepticus,
  Sepsis & Fever with PECARN/Aronson/Rochester/Step-by-Step, Anaphylaxis,
  Procedural Sedation, Agitation, Antiemetics, Antimicrobials, Burns with
  Lund-Browder body-parts TBSA + Parkland, Toxicology, Trauma).
- Global age→weight estimator at top of Calculators tab (APLS + Best Guess).
- Pressure-time waveform SVG teaching graphic for Ventilation.
- Algorithm image lightbox (fullscreen, Esc/tap-to-close).
- Every weight-based dose shows mg/kg inline for clinician verification.
- Drug tables wrapped in overflow-x:auto for mobile.

Infrastructure:
- Pure dose math extracted to public/js/calc-math.js (dual-export Node+browser).
- 36 unit tests in test/calc-math.test.js via node:test (zero new deps).
- "npm test" added to package.json.
2026-04-20 02:49:42 +02:00
github-actions[bot]
936ecbd113 Release v6.3.1 2026-04-19 19:26:56 +00:00
Daniel
e5f7167b8d fix: auth/API logging to Loki, TTS voice auto-detection, STT ElevenLabs support
- Add logger.audit/access calls to auth route (login, login_failed,
  login_blocked, register, password_changed, 2fa_backup_code_used,
  2fa_backup_codes_regenerated) — these previously only wrote to DB
  via raw SQL, bypassing Loki shipper
- Replace logger.info with logger.apiCall in callAI() so every AI call
  ships to Loki with model, tokens, cost, duration
- Add device identifier (parsed user agent) to audit and access logs
- Fix TTS voice/model provider mismatch: auto-detect Vertex voices
  (Puck, Charon, Kore, etc.) and ElevenLabs voice IDs, override model
  to match provider regardless of what model was previously set
- Fix TTS discovery: model IDs saved to tts.voice are detected and
  redirected to tts.model (regex for openai-tts, elevenlabs, vertex-tts)
- Fix STT transcription route: add scribe/elevenlabs/transcri to the
  isTranscriptionModel regex so ElevenLabs Scribe uses /audio/transcriptions
  endpoint instead of chat completions
- Remove OpenObserve/SigNoz code from logger (reverted to Loki-only)
2026-04-19 21:26:49 +02:00
github-actions[bot]
b09276faf5 Release v6.3.0 2026-04-19 00:17:21 +00:00
Daniel
2f6e5a7d8f feat: neonatal calculator, DOCX/PPTX/ODT/EPUB support, gateway-agnostic URL helper, TTS/STT fixes
- Add neonatal assessment calculator: GA classification (extremely preterm through
  post term), weight-for-GA percentile (AGA/SGA/LGA) using Fenton 2013 LMS data,
  birth weight category (ELBW/VLBW/LBW/normal/macrosomia)
- Add DOCX support via mammoth, PPTX/ODT/EPUB via jszip in Learning Hub content
  generator file upload
- Add gatewayUrl() helper for consistent API URL construction — handles
  LITELLM_API_BASE with or without /v1 suffix, works with any OpenAI-compatible
  gateway (LiteLLM, Bifrost, etc.)
- Fix TTS model/voice separation: discovery now tags items as MODEL or VOICE,
  auto-detects provider from voice name (Vertex, ElevenLabs, OpenAI)
- Fix STT discovery to include ElevenLabs Scribe and Chirp models
- Fix TTS discovery to include ElevenLabs and Vertex voices alongside models
- Fix admin model test to bypass allowlist check (skipAllowlistCheck) so
  discovered models can be tested before adding
- Fix Nextcloud token decryption in learningAI.js WebDAV browse and file import
- Fix admin embedding test to show DB model name instead of hardcoded default
- Fix admin STT test to use correct endpoint for Whisper models
- Add AI gateway migration guide to configuration docs
- Add Grafana dashboard JSON for Loki log visualization
2026-04-19 02:17:06 +02:00
Daniel
857ed341f5 docs: rewrite architecture, authentication, configuration, deployment, ai-providers, speech, database, learning-hub, migrations, developer-guide for public audience
- Drop first/second-person voice; reference-style prose throughout
- Remove stale information; align with current code (argon2id primary, hybrid cookie/Bearer auth, sliding 24h idle, AES-256-GCM PHI at rest, backup codes, node-pg-migrate, collation-drift guard, multi-arch Docker, auto-version pipeline)
- Preserve all technical accuracy and code examples
- Remove any remaining references to separate PedsHub Quiz app
- Keep consistent tone across files (tables + code blocks, imperatives where needed)
- api-reference.md and developer-guide.md route tables expanded to reflect current routes (billing, sessions)
2026-04-15 00:26:38 +02:00
Daniel
957ba531bc docs: clarify com.pedshub.scribe is the Android applicationId, not a quiz-app ref 2026-04-15 00:13:17 +02:00
github-actions[bot]
895caa2093 Release v6.2.1 2026-04-14 22:10:45 +00:00
Daniel
231a86509f fix: verify auto-version → android + docker pipeline end-to-end 2026-04-15 00:10:35 +02:00
Daniel
7ad0c84789 ci: multi-arch Docker via native runners + Node 24 opt-in + PAT for tag-trigger chain
docker-publish.yml:
  - Rewrote as matrix build + manifest merge.
  - amd64 on ubuntu-latest, arm64 on ubuntu-24.04-arm (free for public
    repos). No QEMU — argon2 and every other native dep compile on
    their target CPU, no more SIGILL / exit 132.
  - Per-arch GHA cache scopes so builds don't thrash each other.
  - Final step merges both digests under one tag (vX.Y.Z + latest),
    publishing a real multi-arch manifest. `docker pull` from either
    arch gets the right variant automatically.

auto-version.yml, version-bump.yml:
  - Checkout now uses `secrets.RELEASE_PAT || secrets.GITHUB_TOKEN`.
    With RELEASE_PAT set, the tag push this workflow does DOES
    trigger downstream (android-release, docker-publish). Without
    it, falls back to GITHUB_TOKEN (no downstream trigger, what we
    have today).

All workflows (auto-version, version-bump, android-release,
docker-publish):
  - Added FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' so actions
    still on Node 20 runtime (checkout/cache/setup-*) opt in to
    Node 24 early. GitHub makes Node 24 default 2026-06-02 and
    removes Node 20 2026-09-16.

To finish the chain (one-time user step): create a fine-grained
PAT with "Contents: Read and write" on this repo and add as
RELEASE_PAT secret. After that `feat:` / `fix:` commits auto-tag
AND auto-build with zero manual intervention.
2026-04-15 00:05:34 +02:00
Daniel
fbc7890378 docs: strip PedsHub Quiz refs from mobile-build + terser CONTRIBUTING
mobile-build.md:
  - Removed "PedsHub Quiz" sections. That app lives in a separate
    repo (quiz/mobile/) and has its own build pipeline. Docs here
    are PedScribe-only now.
  - Reorganized around CI as the primary flow, local build as
    fallback. Added explicit secret names, JDK requirement, single-
    quote-password caveat, QEMU/argon2 note.
  - File-map section at the end so the native sources are
    discoverable without grepping.

CONTRIBUTING.md:
  - Cut the narrative prose. Dev-facing tables + single-line
    commands only. Decision-tree removed (the table suffices).
  - Release pipeline and mobile build link out rather than
    duplicating content.
2026-04-14 23:54:41 +02:00