Age parser (src/routes/billing.js):
- Now sums year + month + week + day matches so "4 yr 11 mo"
(59 months) correctly maps to the 5-11y billing bracket instead
of being billed as 1-4y. Added bounds sanity check.
Graceful SIGTERM shutdown (server.js):
- Closes the HTTP listener first, then drains batched audit queues,
then ends the Postgres pool. 9-second hard deadline to beat
Docker's 10-second SIGKILL. Previously an in-flight note save
during a container restart could truncate the write.
Explicit LLM fallback opt-in (src/utils/ai.js):
- The OpenRouter / LiteLLM silent fallback now requires admin
setting `ai.allow_model_fallback = true` (default: false). If
primary fails and fallback is disabled, the error is surfaced
to the caller. Prevents silent spillover from a BAA-covered
primary to a non-covered fallback.
Prompt injection delimiters (src/utils/promptSafe.js):
- Wraps user transcripts, dictations, refine-instructions, and
pasted documents in <UNTRUSTED_*>...</UNTRUSTED_*> tags and
appends an explicit system instruction telling the model to
treat the wrapped content as data rather than commands.
- Applied to soap.js, hpi.js, refine.js. Extend to other AI
routes incrementally.
Cross-tab logout sync (public/js/authFetch.js, auth.js):
- BroadcastChannel('pedscribe-auth') — logout in one tab posts
a message; all sibling tabs clear state and reload, dropping
any PHI-containing UI immediately.
Backup code race-free consumption (src/routes/auth.js):
- tryConsumeBackupCode() now uses a Postgres transaction with
SELECT ... FOR UPDATE so concurrent login attempts using the
same code serialize. First wins, second sees the already-
shortened array.
Optimistic encounter locking (migrations/...add-encounter-version):
- saved_encounters.version INTEGER NOT NULL DEFAULT 1
- POST /api/encounters/saved accepts an expected_version and
rejects with 409 if the row has advanced. Falls back to
last-write-wins if the client doesn't pass one (backward compat).
Audit log batching (src/utils/auditQueue.js):
- Audit / api_log / access_log writes are buffered in memory and
flushed every 1s or every 50 entries via one multi-row INSERT.
Under load this reduces DB pressure by ~50x. On SIGTERM the
shutdown path drains the queue before exiting.
- JWT_SECRET fails fast at startup in production
- CORS fails closed if APP_URL + CORS_ORIGINS are both missing
- Explicit HSTS (1y, includeSubDomains, preload)
- Rate limit sensitive auth endpoints (change-password, 2FA)
- /api/health now returns {ok:true}; details gated behind admin auth
- Login enumeration removed — generic 401 + dummy bcrypt on miss
- ReDoS guard: 20KB input cap on /suggest-codes
- showToast uses textContent, no innerHTML
- clearSession() clears service worker caches on logout
- OIDC state is now HMAC-signed and stateless (survives restart)
- SSRF guard on admin-set OIDC issuer (blocks private IPs, requires HTTPS)
Adds docs/mobile-build.md covering APK build, release, git push,
keystore, and troubleshooting for both PedScribe and PedsHub apps.
New feature: after generating any clinical note, the app automatically
suggests relevant billing codes displayed as clickable chips below the output.
Backend (src/routes/billing.js):
- POST /api/suggest-codes endpoint analyzes note text
- Extracts diagnoses from Assessment section via regex
- Looks up ICD-10 codes: local common pediatric map (40+ conditions)
first, then NLM Clinical Tables API for unknown terms
- Suggests CPT E/M codes based on note type, visit complexity,
ROS/PE system counts, and MDM level estimation
- Supports: outpatient (new/established), well visit (age-based),
ED, inpatient (admit/subsequent/discharge)
Frontend (public/js/app.js):
- suggestBillingCodes() renders collapsible card with ICD-10 and CPT chips
- Click any chip to copy the code to clipboard
- Shows E/M level assessment (diagnosis count, ROS, PE, MDM complexity)
- Disclaimer: "Suggestions only. Always verify codes."
Integration: called after note generation in all 6 tabs
(encounter, SOAP, sick visit, well visit, hospital course, chart review)