(null);
useEffect(() => {
const interval = setInterval(async () => {
const newState = await fetchPHIState(sessionId);
setState(newState);
}, 1000); // Poll every second
return () => clearInterval(interval);
}, [sessionId]);
if (!state) return null;
return (
{getIcon(state.indicatorIcon)}
{state.tooltip}
);
}
```
#### Backend API for Frontend State
```python
# In your FastAPI router
from app.services.phi_stt_router import get_phi_stt_router
@router.get("/api/voice/phi-state/{session_id}")
async def get_phi_state(session_id: str):
"""Get current PHI routing state for frontend indicator."""
router = get_phi_stt_router()
state = router.get_frontend_state(session_id)
if state is None:
raise HTTPException(404, "Session not found")
return state
```
### Telemetry Event Types
| Event Type | Description | Payload |
| ---------------------- | -------------------------------------- | ------------------------------ |
| `phi.routing_decision` | New routing decision made | Full PHI state + previous mode |
| `phi.mode_change` | PHI mode changed (e.g., cloud → local) | From/to modes, reason |
| `phi.phi_detected` | PHI entities detected in audio | Score, entity types |
| `phi.session_start` | New PHI session initialized | Initial state |
| `phi.session_end` | PHI session ended | Final mode, had PHI flag |
## Audit Logging
All PHI routing decisions are logged for compliance:
```python
logger.info("PHI routing decision", extra={
"session_id": session_id,
"phi_score": 0.85,
"routing_decision": "local",
"detection_signals": ["medication_mention", "condition_name"],
"provider": "local_whisper",
"processing_time_ms": 234,
"model": "whisper-large-v3"
})
```
### Prometheus Metrics
```python
# Routing distribution
stt_routing_total.labels(routing="local").inc()
stt_routing_total.labels(routing="cloud").inc()
stt_routing_total.labels(routing="hybrid").inc()
# PHI detection accuracy
phi_detection_score_histogram.observe(phi_score)
# Latency by routing type
stt_latency_ms.labels(routing="local").observe(234)
```
## Testing
### Unit Tests
```python
@pytest.mark.asyncio
async def test_phi_routing_high_score():
"""High PHI score routes to local Whisper."""
router = PHISTTRouter()
# Mock audio with PHI content
audio = generate_test_audio("I take metformin for my diabetes")
result = await router.transcribe(audio)
assert result.routing == "local"
assert result.phi_score >= 0.7
assert result.provider == "local_whisper"
@pytest.mark.asyncio
async def test_phi_routing_low_score():
"""Low PHI score routes to cloud."""
router = PHISTTRouter()
# Mock audio without PHI
audio = generate_test_audio("What is the weather today?")
result = await router.transcribe(audio)
assert result.routing == "cloud"
assert result.phi_score < 0.3
```
### Integration Tests
```bash
# Run PHI routing tests
pytest tests/services/test_phi_stt_router.py -v
# Test with real audio samples
pytest tests/integration/test_phi_routing_e2e.py -v --audio-samples ./test_audio/
```
## Best Practices
1. **Default to local for medical context**: If session involves health topics, bias toward local processing
2. **Cache PHI decisions per session**: Avoid re-evaluating the same session repeatedly
3. **Monitor latency impact**: Local Whisper adds ~200ms; account for this in latency budgets
4. **Regular model updates**: Update Whisper model quarterly for accuracy improvements
5. **Audit trail**: Maintain logs of all routing decisions for compliance audits
## Related Documentation
- [Voice Mode v4.1 Overview](./voice-mode-v4-overview.md)
- [Latency Budgets Guide](./latency-budgets-guide.md)
- [HIPAA Compliance Matrix](../HIPAA_COMPLIANCE_MATRIX.md)
6:["slug","voice/phi-aware-stt-routing","c"]
0:["X7oMT3VrOffzp0qvbeOas",[[["",{"children":["docs",{"children":[["slug","voice/phi-aware-stt-routing","c"],{"children":["__PAGE__?{\"slug\":[\"voice\",\"phi-aware-stt-routing\"]}",{}]}]}]},"$undefined","$undefined",true],["",{"children":["docs",{"children":[["slug","voice/phi-aware-stt-routing","c"],{"children":["__PAGE__",{},[["$L1",["$","div",null,{"children":[["$","div",null,{"className":"mb-6 flex items-center justify-between gap-4","children":[["$","div",null,{"children":[["$","p",null,{"className":"text-sm text-gray-500 dark:text-gray-400","children":"Docs / Raw"}],["$","h1",null,{"className":"text-3xl font-bold text-gray-900 dark:text-white","children":"PHI-Aware STT Routing"}],["$","p",null,{"className":"text-sm text-gray-600 dark:text-gray-400","children":["Sourced from"," ",["$","code",null,{"className":"font-mono text-xs","children":["docs/","voice/phi-aware-stt-routing.md"]}]]}]]}],["$","a",null,{"href":"https://github.com/mohammednazmy/VoiceAssist/edit/main/docs/voice/phi-aware-stt-routing.md","target":"_blank","rel":"noreferrer","className":"inline-flex items-center gap-2 rounded-md border border-gray-200 dark:border-gray-700 px-3 py-1.5 text-sm text-gray-700 dark:text-gray-200 hover:border-primary-500 dark:hover:border-primary-400 hover:text-primary-700 dark:hover:text-primary-300","children":"Edit on GitHub"}]]}],["$","div",null,{"className":"rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6","children":["$","$L2",null,{"content":"$3"}]}],["$","div",null,{"className":"mt-6 flex flex-wrap gap-2 text-sm","children":[["$","$L4",null,{"href":"/reference/all-docs","className":"inline-flex items-center gap-1 rounded-md bg-gray-100 px-3 py-1 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700","children":"← All documentation"}],["$","$L4",null,{"href":"/","className":"inline-flex items-center gap-1 rounded-md bg-gray-100 px-3 py-1 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700","children":"Home"}]]}]]}],null],null],null]},[null,["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children","docs","children","$6","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children","docs","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/7f586cdbbaa33ff7.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"__className_f367f3 h-full bg-white dark:bg-gray-900","children":[["$","a",null,{"href":"#main-content","className":"skip-to-content","children":"Skip to main content"}],["$","$L8",null,{"children":[["$","$L9",null,{}],["$","$La",null,{}],["$","main",null,{"id":"main-content","className":"lg:pl-64","role":"main","aria-label":"Documentation content","children":["$","$Lb",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]]}]]}]}]],null],null],["$Lc",null]]]]
c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"PHI-Aware STT Routing | Docs | VoiceAssist Docs"}],["$","meta","3",{"name":"description","content":"Guide to PHI-aware speech-to-text routing for HIPAA compliance"}],["$","meta","4",{"name":"keywords","content":"VoiceAssist,documentation,medical AI,voice assistant,healthcare,HIPAA,API"}],["$","meta","5",{"name":"robots","content":"index, follow"}],["$","meta","6",{"name":"googlebot","content":"index, follow"}],["$","link","7",{"rel":"canonical","href":"https://assistdocs.asimo.io"}],["$","meta","8",{"property":"og:title","content":"VoiceAssist Documentation"}],["$","meta","9",{"property":"og:description","content":"Comprehensive documentation for VoiceAssist - Enterprise Medical AI Assistant"}],["$","meta","10",{"property":"og:url","content":"https://assistdocs.asimo.io"}],["$","meta","11",{"property":"og:site_name","content":"VoiceAssist Docs"}],["$","meta","12",{"property":"og:type","content":"website"}],["$","meta","13",{"name":"twitter:card","content":"summary"}],["$","meta","14",{"name":"twitter:title","content":"VoiceAssist Documentation"}],["$","meta","15",{"name":"twitter:description","content":"Comprehensive documentation for VoiceAssist - Enterprise Medical AI Assistant"}],["$","meta","16",{"name":"next-size-adjust"}]]
1:null