Every version of CURA. From a single appointments tool to an AI-first Care Operating System.
March 2026
v11.1 deepens every layer introduced in v11.0. The pharmacy module gains OCR invoice scanning (GPT-4o vision), bulk receiving for up to 100 batches, a redesigned multi-item receiving table with HSN/GST columns, a drug interaction checker with severity-coded banners, LASA badges, thermal print receipts, a WhatsApp dispense receipt, a 4-band expiry hub, quarantine controls, and a full 4-report suite (Expiry, Sales, GRN, GST Summary). AI key management goes per-tenant with BYOK support, usage logging, and a super admin AI panel. The Dental module gets 11 power-user tools including PatientContextStrip, click-to-paint, multi-tooth BatchActionBar, and an end-of-visit workflow. Ambient Scribe gets hardened for Safari/iOS with an HTTP fallback, rate-limit recovery, and a first-chunk fast path. Insights gains a transparent WhatsApp billing breakdown with category-aware billing.
OCR invoice scan — GPT-4o vision extracts line items + invoice number from photo/PDF
POST /api/pharmacy/ocr-invoice: accepts multipart image or PDF, sends to GPT-4o vision with structured extraction prompt. Returns { invoice_number, items[] } with drug name, batch, expiry, qty, MRP, HSN code, GST%. PharmacyReceivingView pre-fills all columns automatically from the scan. Backward-compatible: bare array response still handled. Falls back to env key if tenant key absent.
Bulk receiving API — up to 100 batches in one call with per-row validation
POST /api/pharmacy/stock-bulk: receives array of up to 100 batch rows. Per-row validation (required fields, qty > 0, valid expiry). On partial failure: successful rows committed, failed rows returned with error detail. Top-level supplier_invoice_no applies to all rows; per-row override supported. Idempotency via supplier_invoice_no + batch_no uniqueness check.
Multi-item receiving table — redesigned with HSN, GST%, computed totals footer
PharmacyReceivingView fully redesigned: per-row catalog autocomplete, barcode scan fills first empty row, OCR fills entire table. Added HSN code and GST% columns (editable, OCR-populated). Computed read-only columns: GST Amt and Net Amt per row. Totals footer: aggregate GST and Net across all rows. Invoice No. header field auto-populated by OCR.
Drug interaction checker — severity-colour-coded banner with dismiss
PharmacyDispenseView: debounced drug interaction check fires on cart change via /api/drug-interactions/check. Returns interactions with severity (contraindicated / major / moderate / minor). Banner colour: red/orange/yellow/blue by severity. Dismissible per session. LASA (Look-Alike Sound-Alike) badge rendered on drug search results and in cart item rows.
Thermal print receipt + WhatsApp dispense receipt
Print receipt: popout window with thermal-style layout (80mm column, drug list, totals, clinic header). WhatsApp receipt: useWhatsAppSend hook fires on dispense confirm. Receipt modal carries tenant prop and cart items for both print and WA paths.
Expiry Hub — 4-band view with quarantine and CSV export
PharmacyInventoryView: view toggle pill (All Inventory / Expiry Alerts) with count badge. Expiry hub shows batches in 4 bands: Expired (red), 0–30 days (orange), 31–60 days (amber), 61–90 days (yellow). Quarantine button per batch calls PUT /api/pharmacy/stock to flip status. Export Expiry CSV downloads filtered view.
Reports Suite — 4 CSV reports covering all pharmacy compliance needs
PharmacyReportsView: 2×2 card grid (responsive). Card 1: Expiry Report — configurable day window (30/60/90/180). Card 2: Sales/Dispense Report — date range, itemised with GST. Card 3: GRN/Purchase Register — date range, supplier + invoice detail. Card 4: GST Summary — month/year picker, CGST/SGST/IGST by rate slab with TOTAL row. All downloads use Bearer token auth.
Per-tenant OpenAI key — BYOK with platform fallback removed
getTenantOpenAI() now returns { client, keySource } ('byok' | 'platform'). Shared OPENAI_API_KEY env fallback removed from all AI routes. All AI routes (scribe, dental-ai, ocr-invoice) migrated to getTenantOpenAI() with 402 + user-facing error when no key configured. Fixes latent bug in dental/ambient-scribe/chunk.js where bare OpenAI instance was returned but { client } destructured.
AI usage logging — tokens, cost estimate, key_source per call
logAiUsage() helper writes fire-and-forget to new ai_usage_logs table: tenant_id, feature, model, prompt_tokens, completion_tokens, estimated_cost_inr, key_source. New migration: ai_usage_logs table with indexes on tenant_id + created_at for admin queries. One-time migration script moves legacy openai_api_key plaintext to ai_config.openai_key_enc.
Super admin AI panel — key status, MTD usage, per-feature breakdown
New AI KEYS tab in /admin dashboard: tenant list with key status (BYOK / Platform / None) and MTD usage. Detail panel: key provisioning (set/rotate/remove), usage breakdown by feature, model, and key_source (billable vs BYOK). API: GET/POST/DELETE /api/super-admin/ai/keys and GET /api/super-admin/ai/usage/[tenantId].
PatientContextStrip — last visit, plan status, advance balance, lab cases, overdue count always visible
Persistent strip at top of odontogram: last visit date, active plan status (total / paid / balance), advance balance, active lab cases count, overdue follow-up count. Eliminates tab-switching to get patient context during consultation.
Click-to-paint condition palette + Shift+click multi-tooth BatchActionBar
Condition palette above chart: select a condition, click any tooth to apply directly — no modal for fast single-tooth work. Shift+click selects multiple teeth; floating BatchActionBar appears to apply the same condition to all selected teeth at once. OverdueFollowUpBanner: dismissible amber banner listing past-due follow-ups by tooth.
Phase chips, AI Proposal card, active lab cases inline, end-of-visit workflow
Phase chips (P1/P2/P3) rendered on each tooth in the chart when an active plan has phase data. AI Proposal summary card in sidebar shows phase breakdown + cost estimate immediately after AI Propose — no modal needed. Active lab cases inline in sidebar with overdue warnings. End-of-Visit section: Collect Payment, Generate Invoice, Visit Summary + WhatsApp as a single linear flow when completed treatments exist.
DentalVisitCard trigger strip + ToothActionModal price badge
DentalVisitCard trigger strip shows procedure names, tooth numbers, and advance balance on first expand. ToothActionModal: price displayed as a prominent colour badge in search results and selected-service block. Ambient scribe live status chip in header shows recording duration, extracted findings count, and plan items in real-time.
Safari/iOS support — audio/mp4 MediaRecorder fallback
Safari does not support WebM MediaRecorder. v11.1 adds MediaRecorder format detection: prefers audio/webm;codecs=opus, falls back to audio/mp4 (Safari/iOS). Removed audioBitsPerSecond and sampleRate constraints that caused Safari to throw on recorder start.
HTTP fallback, stale duration fix, server chunk_interval_ms honored
HTTP fallback: when WebSocket is disconnected, chunk.js HTTP endpoint receives findings and applies them to session state. Fixed stale duration in stopSession — uses durationRef instead of state closure. chunk_interval_ms now read from /start response instead of hardcoded 8000ms.
KEY_INVALID permanent stop, RATE_LIMITED 30s pause, first-chunk fast path
KEY_INVALID server event: stops extraction permanently (no more retry loop). RATE_LIMITED server event: pauses extraction for 30s then auto-resumes. First-chunk fast path: fires extraction after ~2s if ≥8 words accumulated — eliminates initial dead-air delay. VAD removed — all audio sent directly to Deepgram for maximum reliability.
Fixed double session_id in WS URL and null send race condition
session_id was being appended twice to the WebSocket URL query string. Fixed null send race: sendChunk() now guards against ws.readyState !== OPEN before calling ws.send(). Both bugs caused silent session failures on slow connections.
WhatsApp usage breakdown report — category-aware billing with stacked bar chart
New WhatsAppUsageReport component in InsightsTab: 4 summary cards (total spend, marketing ₹0.88, utility ₹0.15, free service). Recharts stacked bar chart by day: amber = marketing, teal = utility, slate = free. Receipt-style table: category badge, message count, rate, subtotal. Grand total row. Driven by existing date-range selector.
Accurate category billing — NPS reclassified as marketing, manual inbox free
NPS/feedback request messages reclassified from service to marketing bucket (₹0.88). Manual inbox messages (internal bucket) kept free — only automated messages billed. Category mapping: marketing ₹0.88, utility ₹0.15, service ₹0.00, authentication ₹0.15.
Paginated WA fetch — bypasses PostgREST 1,000-row cap
PostgREST enforces server-side row limit that silently truncates at 1,000 rows. Replaced single query with paginated loop using .range() fetching 1,000 rows at a time until exhausted. Clinics with high message volume now get accurate monthly totals and cost breakdowns.
All Changes