pediatric-ai-scribe-v3/shared/clinical/correction-tracker.ts
Daniel e6d0e5ef8c feat(notes): editable AI output + automatic correction tracking
Restores two pieces of vanilla behavior the React port silently dropped:

1. The output divs were contenteditable in vanilla — clinicians fix
   AI mistakes inline before saving the encounter or copying out.
   The early React port rendered the output as a read-only div, so
   any edit forced a copy-paste workflow.

2. correctionTracker.js (public/js/correctionTracker.js) saved every
   meaningful inline edit to user_memories under category
   correction_<section> so the AI learns user preferences. Settings
   → Corrections still showed them, but nothing in React was *writing*
   them — the loop was broken.

New EditableResult component wraps the result body in a textarea,
captures the AI baseline on first render / after refine|shorten, and
on blur posts to /api/memories/correction (only when the edit clears
the noise threshold from vanilla — wordDiff ≥ 2 OR charDiff ≥ 20 on
outputs > 100 chars).

Wired into all 7 note pages:
  Encounter   → section: 'encounter'
  Dictation   → section: 'hpi'
  SOAP        → section: 'soap'
  SickVisit   → section: 'sickvisit'
  WellVisit   → section: 'wellvisit'
  HospitalCourse → section: null   (server enum has no correction_hospital)
  ChartReview    → section: null   (server enum has no correction_chart)

Tests:
  shared/clinical/correction-tracker.ts (+ .test.ts) — pure heuristic
  with vectors covering the noise floor (single-word swap on long
  text → skipped; new sentence → tracked; large char diff → tracked).
  Lives in shared/ so the root vitest config picks it up; the React
  component imports from there.
2026-04-24 05:24:09 +02:00

19 lines
829 B
TypeScript

// Pure heuristic from public/js/correctionTracker.js — used by
// EditableResult to decide whether a user's inline edit to AI output
// is "meaningful" enough to push to /api/memories/correction.
//
// The threshold matches vanilla exactly so we don't drift behaviour
// between the two clients during the migration window.
export function isMeaningfulChange(original: string, current: string): boolean {
if (!original || !current) return false;
if (original.trim() === current.trim()) return false;
const origWords = original.split(/\s+/).length;
const currWords = current.split(/\s+/).length;
const wordDiff = Math.abs(origWords - currWords);
if (wordDiff < 2 && original.length > 100) {
const charDiff = Math.abs(original.length - current.length);
if (charDiff < 20) return false;
}
return true;
}