Commit Graph

11 Commits

Author SHA1 Message Date
Khalid Abdi 1c65f72ccf backend: render patient card from searchPatients on a unique match
"Show me <name>'s medical record" relied on the model chaining
searchPatients → getPatient, but Gemini Flash often calls searchPatients
then emits the canned closing line ("Here's the record.") without the
second tool call, so no data-patientCard part is ever written and no card
renders. (The prior Gemini fix only covered the empty-schema list tools.)

searchPatients now writes the record card (data-patientCard, or
data-recordGraph in graph mode) and a source directly when EXACTLY ONE
patient matches — mirroring getPatient — so the common name-lookup flow no
longer depends on a second tool call. Multiple/zero matches keep returning
the disambiguation list. The tool description and system prompt tell the
model the card is already shown on a unique match so it doesn't double-render.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 19:36:49 +03:00
Khalid Abdi 75910f7fb0 backend: fix AI chat cards not rendering on Gemini (empty tool schemas)
The card-emitting chat tools (listAppointments, listTasks,
listPrescriptions, getClinicInfo, getAnalytics, listInventory) used an
empty `z.object({})` parameter schema. Google Gemini can't emit a
function call for a tool whose JSON schema has no properties — it prints
the call as `tool_code` text instead of invoking it — so the tool never
ran and the frontend never received the data part to render a card.

Give those tools a shared `emptyToolArgs` schema with one optional,
ignored field so the schema is non-empty and Gemini calls them. execute
bodies are unchanged; other providers ignore the extra field. This is the
same fix already documented for previewImport.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:52:22 +03:00
Khalid Abdi b2ac27dda7 backend: render AI import approval card instead of raw tool_code
Give previewImport a concrete object schema (was z.array(z.unknown())) so
Google Gemini can emit a real function call — an array-of-unknown serializes
to an empty JSON schema, which made Gemini print the call as a `tool_code`
text block instead of invoking the tool. Validation stays lenient in
execute() via patientInputSchema. Also strengthen the system prompt: never
print tool calls/JSON or re-list record fields; keep prose to one sentence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 21:32:58 +03:00
Khalid Abdi 0fa2802723 feat(ai): inline source citations in the AI chat
Retrieval tools now register a PHI-free sourceId per record/list and stream a
data-source part with the real clinician-facing title; the system prompt asks
the model to cite facts inline as [[src:id]]. The chat renders those markers as
numbered inline citation chips (PreviewCard hover) via a Streamdown link
override, with a Sources footer fallback when the model omits markers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:41:48 +03:00
Khalid Abdi 1c52dcaf84 fix(chat): graph mode renders the record graph, not cards
In Graph mode, a natural-language request ("graph for John Smith") went
through the getPatient tool, which always emitted record cards — so the
clinician got Summary/Vitals cards instead of a graph (the mode prompt
even wrongly claimed the graph rendered automatically). Thread the
composer mode into the chat tools and have getPatient emit
data-recordGraph in graph mode (matching the client-side /patient
fast-path), with the cards kept for chat/analysis modes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 21:45:09 +03:00
Khalid Abdi f9615fa74e feat: edit parsed records before importing
The import preview only showed counts + skipped-row errors with no way to
fix anything. Now every parsed record is editable before import:

- backend: shared validatePatientImport() (used by the previewImport tool)
  + POST /api/ai/import/validate for dry-run re-validation; the import
  preview payload now carries the original records (and the source record on
  each invalid row) so the UI can edit them.
- frontend: the import card gets a "Review & edit" dialog listing every
  record with a ready / needs-fixing badge; opening one reuses the full
  PatientFormDialog in a new non-persisting review mode (editable file
  number) and re-validates on save, so skipped rows can be fixed and
  included.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 21:03:33 +03:00
Khalid Abdi 6e5598262e feat(chat): file attachments as chips, CoT closed, AI add-to-inventory
Chat fixes (#1/#6/#7):
- chat-input passes raw File[] up instead of inlining file text
- chat-panel sends files as FileUIPart and renders them with the
  ai-elements Attachments component (no more raw text dumps)
- backend extracts text-like file content for the model in routes/chat
- Chain-of-Thought now defaults to collapsed
- file upload works regardless of whether the input has text

AI add-to-inventory (#2):
- new proposeInventory tool (validates items, streams an approval card)
- system prompt distinguishes stocking inventory vs. billing a patient
- ActionPreviewCard commits inventory via POST /api/inventory

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 18:28:05 +03:00
Khalid Abdi 3aa699aefe feat: clinic-wide AI (analytics/earnings/inventory + invoice-from-file),
real Live card, persistent chat history

Analytics & earnings:
- Analytics now carries real money computed from invoices (billed/paid/
  outstanding + by-month); new Earnings section on the Analysis page drawn with
  the project's Bklit chart components (shared EarningsChart).

AI agent reaches the whole clinic:
- new read tools getClinicInfo / getAnalytics / listInventory render clinic,
  analytics (with a Bklit earnings chart) and inventory cards in chat
- proposeInvoice turns an uploaded purchase/medication list into an invoice
  (new "invoice" action-preview kind → createInvoice); invoices/appointments
  auto-create/link a patient (ensurePatient) so they hit the Patients page

Live card:
- plots real data — patients checked in today — via GET /api/analytics/live
  (polled); value pill clamped and margins widened so nothing spills the card

Persistent AI chat history (Claude-style):
- ai_chat_threads + ai_chat_messages (migration 0016); per-user, org-scoped
  thread CRUD under /api/chat/threads
- chat panel owns a thread id, loads /?thread=<id>, and auto-saves after each
  exchange; sidebar lists past chats (open/delete), "New chat" starts fresh

Verified with backend typecheck + frontend tsc + next build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 22:25:15 +03:00
Khalid Abdi 929bec8f31 feat: AI-added records save with placeholders + "Added by AI" provenance
Stop blocking AI imports/proposals on missing non-critical fields. Records
the chat agent drafts now save with safe placeholders, auto-generated file
numbers, and a source="ai" marker that surfaces an "Added by AI" badge so a
clinician can review/edit them later.

Backend:
- add `source` (manual|ai) column to patients/appointments/prescriptions
  (migration 0014) + canonical types, services, validation schemas
- relax patient/appointment validation: empty file number allowed, demographic
  + type/provider/initials fall back to placeholders (initials derived from name)
- patients.generateFileNumber() auto-assigns an MRN when one is missing
- proposeAppointment accepts a name when no file number resolves; AI commits +
  /api/ai/import stamp source="ai"

Frontend:
- `source` on Appointment/Patient/Prescription types; AI commits send source="ai"
- reusable <AiBadge> shown on the Patients table/detail and prescriptions list

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:27:17 +03:00
Khalid Abdi fa2e499440 feat: live AI chat (thinking + inline Veil) and display/add actions
Make the chat feel alive and let the agent act on the clinic — safely.

UX (frontend):
- Stream `data-step` parts from each tool into an inline Chain-of-Thought
  trace, plus a "Thinking…" shimmer while a request is in flight (works even
  on the non-streamed external+Veil path).
- Replace the modal Veil consent Dialog with an inline, once-per-session
  "Veil" confirmation above the input (with a "Use local model" option).
- Render new list + action-preview cards; chat-input now uses COSS tokens.

Agent (backend):
- Add display tools (listAppointments / listTasks / listPrescriptions) and
  propose tools (proposeAppointment / proposeTask / proposePrescription) that
  validate as a dry run and stream an approval card — nothing is written until
  the clinician approves, via the existing RBAC-gated create endpoints.
- previewImport now also covers single-patient add + migration.
- System prompt: display + add only, never edit/delete or alter the schema;
  stronger migration guidance. ToolContext carries the viewer for task scoping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 23:49:18 +03:00
Khalid Abdi 3a7378e00d backend: AI chat agent with Veil PHI safeguard (providers, /chat, import)
Real LLM chat replacing the mock, backend-centric per the plan:

- Multi-provider API-key mode (OpenAI / Anthropic / Gemini via the AI SDK) plus
  local Ollama (OpenAI-compatible endpoint). Provider is derived from the
  picked model id; the matching stored key is used. New user_ai_settings table
  holds per-user config with provider API keys encrypted at rest (AES-256-GCM,
  src/lib/crypto.ts, keyed by AI_CREDENTIALS_KEY).
- POST /api/ai/config (get/put, secrets never returned), POST /api/ai/test
  (Ollama ping / key presence), POST /api/ai/import (approved migration commit,
  re-validated server-side, reuses the audited patient service).
- POST /api/chat: streamText agent with tools (getPatient, getPatientLabs,
  searchPatients, previewImport). Real record data streams to the clinician as
  custom data parts (cards) while the model sees only Veil-redacted results.
- Veil (src/services/ai/veil.ts): de-identifies patient identifiers to tokens
  before external calls, resolves tokens on tool args, and rehydrates the final
  answer. Bypassed for local Ollama. External mode runs non-streamed so the
  rehydrated text is correct. Every call is audited (provider + Veil level).
- Shared role-scoping helpers extracted to src/lib/role-scope.ts (reused by the
  patient routes and chat tools so visibility rules match).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 18:35:16 +03:00