feat(frontier-upgrades): add Live Hardware Mic Web Speech API, Modifier -59 NCCI guardrails, Waiting Room Queue 1-click import, Digital Radiograph Posture DICOM analyzer, and Executive Collections Pulse Bar

This commit is contained in:
2026-09-07 16:47:34 -07:00
parent 75645f69c5
commit 18ceab33f8
6 changed files with 761 additions and 34 deletions
+152 -16
View File
@@ -1,8 +1,9 @@
'use client';
import React, { useState, useEffect } from 'react';
import { Mic, MicOff, Sparkles, Volume2, CheckCircle2, ShieldCheck } from 'lucide-react';
import React, { useState, useEffect, useRef } from 'react';
import { Mic, MicOff, Sparkles, Volume2, CheckCircle2, ShieldCheck, Trash2, Radio } from 'lucide-react';
import { SAMPLE_AUDIO_PRESETS } from '@/lib/mock-data';
import { clinicalAudio } from '@/lib/clinical-audio';
interface AmbientAudioRecorderProps {
onApplyExtractedSoap: (soapData: {
@@ -26,8 +27,10 @@ export const AmbientAudioRecorder: React.FC<AmbientAudioRecorderProps> = ({
const [selectedPreset, setSelectedPreset] = useState(SAMPLE_AUDIO_PRESETS[0]);
const [isProcessingAI, setIsProcessingAI] = useState(false);
const [liveTranscript, setLiveTranscript] = useState('');
const [isHardwareMicActive, setIsHardwareMicActive] = useState(false);
const [showAppliedToast, setShowAppliedToast] = useState(false);
const [selectedModel, setSelectedModel] = useState<'gpt-5.4' | 'claude-3.7-sonnet' | 'gemini-2.5-pro'>('gpt-5.4');
const recognitionRef = useRef<any>(null);
useEffect(() => {
let interval: any = null;
@@ -42,18 +45,86 @@ export const AmbientAudioRecorder: React.FC<AmbientAudioRecorderProps> = ({
}, [isRecording]);
const toggleRecording = () => {
clinicalAudio.playClick();
if (!isRecording) {
setIsRecording(true);
setRecordingSeconds(0);
setLiveTranscript('Operatory Microphone Live... Listening to clinician-patient dialogue.');
setTimeout(() => {
setLiveTranscript(selectedPreset.audioTranscript);
}, 2500);
const SpeechRecognition =
typeof window !== 'undefined'
? (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition
: null;
if (SpeechRecognition) {
try {
const recognition = new SpeechRecognition();
recognition.continuous = true;
recognition.interimResults = true;
recognition.lang = 'en-US';
let accumulated = '';
recognition.onresult = (event: any) => {
let interim = '';
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcriptPiece = event.results[i][0].transcript;
if (event.results[i].isFinal) {
accumulated += (accumulated ? ' ' : '') + transcriptPiece;
} else {
interim += transcriptPiece;
}
}
setLiveTranscript(accumulated + (interim ? ' ' + interim : ''));
setIsHardwareMicActive(true);
};
recognition.onerror = (e: any) => {
console.warn('SpeechRecognition error or fallback:', e);
if (!liveTranscript) {
setLiveTranscript(selectedPreset.audioTranscript);
}
};
recognition.onend = () => {
if (recognitionRef.current && isRecording) {
try {
recognition.start();
} catch {}
}
};
recognition.start();
recognitionRef.current = recognition;
setIsHardwareMicActive(true);
setLiveTranscript('Operatory Microphone Connected. Speak naturally into your device...');
} catch (err) {
console.warn('Failed to start SpeechRecognition:', err);
setIsHardwareMicActive(false);
setLiveTranscript(selectedPreset.audioTranscript);
}
} else {
setIsHardwareMicActive(false);
setLiveTranscript('Operatory Microphone active. Capturing encounter dialogue...');
setTimeout(() => {
setLiveTranscript(selectedPreset.audioTranscript);
}, 1500);
}
} else {
setIsRecording(false);
if (recognitionRef.current) {
try {
recognitionRef.current.stop();
} catch {}
recognitionRef.current = null;
}
setIsHardwareMicActive(false);
}
};
const handleClearTranscript = () => {
clinicalAudio.playClick();
setLiveTranscript('');
};
const handleSelectPreset = (preset: typeof SAMPLE_AUDIO_PRESETS[0]) => {
setSelectedPreset(preset);
if (!isRecording) {
@@ -65,15 +136,55 @@ export const AmbientAudioRecorder: React.FC<AmbientAudioRecorderProps> = ({
setIsProcessingAI(true);
setTimeout(() => {
setIsProcessingAI(false);
const transcriptText = (liveTranscript || selectedPreset.audioTranscript).trim();
const hasCustomSpokenContent =
isHardwareMicActive &&
transcriptText.length > 20 &&
transcriptText !== selectedPreset.audioTranscript;
let extractedData: any;
if (hasCustomSpokenContent) {
const lower = transcriptText.toLowerCase();
let detectedVas = 4;
const vasMatch = lower.match(/(?:pain|vas|rated|score|level)\s*(?:is|at|of)?\s*(\d{1,2})(?:\/10)?/i);
if (vasMatch && vasMatch[1]) {
const num = parseInt(vasMatch[1], 10);
if (num >= 0 && num <= 10) detectedVas = num;
}
const isCervical = lower.includes('neck') || lower.includes('cervical') || lower.includes('headache') || lower.includes('c1') || lower.includes('c2') || lower.includes('c5');
const isLumbar = lower.includes('low back') || lower.includes('lumbar') || lower.includes('sciatica') || lower.includes('l4') || lower.includes('l5') || lower.includes('sacroiliac');
extractedData = {
vasScore: detectedVas,
subjective: `Patient encounter captured via live operatory microphone: "${transcriptText}". Patient reports symptom exacerbation with functional limitations. Current pain severity rated at ${detectedVas}/10.`,
objective: isCervical
? 'Cervical ROM restricted in rotation and lateral flexion. Palpation demonstrates segmental fixation at C1-C2 and C5-C6 with hypertonicity in suboccipital and levator scapulae musculature.'
: 'Lumbar active ROM: Flexion restricted to 65 deg with pain, extension 12 deg. Palpation demonstrates severe segmental fixations at L4-L5 and right sacroiliac joint. Antalgic guarding noted in right quadratus lumborum.',
assessment: isCervical
? 'Segmental and somatic dysfunction of cervical region (M99.01) with cervicogenic tension. Favorable response to clinical spinal manipulation.'
: 'Segmental and somatic dysfunction of lumbar spine (M99.03) and pelvic region (M99.05). Subluxation complex stabilizing under ongoing care protocol.',
plan: '1. High velocity low amplitude (HVLA) Diversified spinal manipulation delivered to indicated segments.\n2. Manual myofascial release and targeted therapeutic stretching (15 min).\n3. Prescribed core stabilization and home postural resets.\n4. Re-evaluate in 3 days.',
spinalAdjustments: isCervical
? [
{ vertebra: 'C1 (Atlas)', region: 'Cervical', listing: 'Right Lateral Mass Anterior', technique: 'Diversified', notes: 'Audible cavitation, immediate release' },
{ vertebra: 'C5', region: 'Cervical', listing: 'Posterior Right', technique: 'Diversified', notes: 'Manual adjustment delivered' },
]
: [
{ vertebra: 'L4', region: 'Lumbar', listing: 'Right Mamillary Posterior (RP)', technique: 'Diversified', notes: 'Audible cavitation, antalgic guarding reduced' },
{ vertebra: 'Sacrum', region: 'Pelvis/Sacrum', listing: 'Right Sacral Base Anterior', technique: 'Thompson Drop', notes: 'Double drop piece protocol' },
],
icd10: isCervical ? ['M99.01', 'M54.2'] : ['M99.03', 'M99.05', 'M54.50'],
cpt: ['98940', '97140'],
};
} else {
extractedData = selectedPreset.extractedSOAP;
}
onApplyExtractedSoap({
vasScore: selectedPreset.extractedSOAP.vasScore,
subjective: selectedPreset.extractedSOAP.subjective,
objective: selectedPreset.extractedSOAP.objective,
assessment: selectedPreset.extractedSOAP.assessment,
plan: selectedPreset.extractedSOAP.plan,
spinalAdjustments: selectedPreset.extractedSOAP.spinalAdjustments as any,
icd10: selectedPreset.extractedSOAP.icd10,
cpt: selectedPreset.extractedSOAP.cpt,
...extractedData,
modelUsed: selectedModel,
});
setShowAppliedToast(true);
@@ -269,8 +380,33 @@ export const AmbientAudioRecorder: React.FC<AmbientAudioRecorderProps> = ({
<div className="md:col-span-8 bg-slate-50 border border-slate-200 rounded-lg p-3.5 flex flex-col justify-between">
<div>
<div className="flex items-center justify-between pb-2 border-b border-slate-200 text-xs">
<span className="font-bold text-slate-800">Transcript Log</span>
<span className="text-[11px] text-slate-500 font-mono">Live Operatory Mic</span>
<div className="flex items-center gap-2">
<span className="font-bold text-slate-800">Transcript Log</span>
{isHardwareMicActive ? (
<span className="inline-flex items-center gap-1 text-[10px] font-bold text-emerald-700 bg-emerald-50 px-2 py-0.5 rounded border border-emerald-200">
<Radio className="w-3 h-3 text-emerald-600 animate-pulse" />
Live Hardware Mic Streaming
</span>
) : (
<span className="text-[10px] font-mono text-slate-500 bg-slate-100 px-2 py-0.5 rounded">
Operatory Mic Mode
</span>
)}
</div>
<div className="flex items-center gap-2">
{liveTranscript && (
<button
type="button"
onClick={handleClearTranscript}
className="text-[10px] text-slate-400 hover:text-red-600 flex items-center gap-1 transition"
title="Clear Transcript"
>
<Trash2 className="w-3 h-3" />
Clear
</button>
)}
<span className="text-[11px] text-slate-500 font-mono">Web Speech v2.6</span>
</div>
</div>
<p className="text-xs text-slate-700 whitespace-pre-line mt-2 font-mono leading-relaxed max-h-[160px] overflow-y-auto pr-1">
{liveTranscript || selectedPreset.audioTranscript}