feat: AI patient onboarding backend and documentation

This commit is contained in:
2026-08-27 12:15:09 +05:30
parent b9440772d1
commit 59c1372fe3
49 changed files with 4012 additions and 0 deletions
@@ -0,0 +1 @@
"""Voice processing package initialization."""
@@ -0,0 +1,25 @@
from pathlib import Path
from uuid import uuid4
BASE_DIR = Path(__file__).resolve().parent.parent.parent
AUDIO_DIR = BASE_DIR / "data" / "audio"
AUDIO_DIR.mkdir(parents=True, exist_ok=True)
class AudioManager:
@staticmethod
def save_audio(audio_bytes: bytes, extension: str = "mp3") -> str:
ext = extension.lstrip(".")
audio_id = f"aud_{uuid4().hex[:12]}"
file_path = AUDIO_DIR / f"{audio_id}.{ext}"
file_path.write_bytes(audio_bytes)
return audio_id
@staticmethod
def get_audio_path(audio_id: str) -> Path | None:
for ext in [".wav", ".mp3", ".webm", ".m4a", ".ogg"]:
p = AUDIO_DIR / f"{audio_id}{ext}"
if p.exists():
return p
return None
@@ -0,0 +1,65 @@
import io
from openai import AsyncOpenAI
from app.ai.sarvam_client import SarvamClient
from app.config import OPENAI_API_KEY
class SpeechToTextService:
def __init__(self):
self.sarvam = SarvamClient()
self.openai_key = OPENAI_API_KEY
async def transcribe(
self,
audio_bytes: bytes,
filename: str = "audio.webm",
language_code: str = "ta-IN",
) -> str:
lang_prefix = language_code.lower()[:2]
# 1. Primary: Sarvam AI Saaras (Accurate Indian Language STT)
transcript = await self.sarvam.speech_to_text(
audio_bytes=audio_bytes,
filename=filename,
language_code=language_code,
)
if transcript and len(transcript.strip()) > 1:
return transcript.strip()
# 2. Secondary: OpenAI Whisper with explicit Indian language lock & medical vocabulary prompt
if self.openai_key and self.openai_key != "your_openai_key":
client = AsyncOpenAI(api_key=self.openai_key, timeout=12.0)
try:
audio_file = io.BytesIO(audio_bytes)
audio_file.name = filename if filename.endswith((".wav", ".mp3", ".webm", ".m4a")) else "audio.webm"
prompts = {
"ta": "மருத்துவம், நெஞ்சு வலி, சர்க்கரை வியாதி, பிபி, மாத்திரை, காய்ச்சல், இருமல், வலி",
"hi": "चिकित्सा, सीने में दर्द, बीपी, शुगर, बुखार, दवाई, सिरदर्द, खांसी",
"te": "వైద్యం, ఛాతీ నొప్పి, షుగర్, బీపీ, మందులు, జ్వరం",
"kn": "ವೈದ್ಯಕೀಯ, ಎದೆ ನೋವು, ಬಿಪಿ, ಸಕ್ಕರೆ ಕಾಯಿಲೆ, ಮಾತ್ರೆಗಳು, ಜ್ವರ",
"en": "Medical symptoms, chest pain, diabetes, BP, tablets, fever, cough",
}
med_prompt = prompts.get(lang_prefix, prompts["ta"])
resp = await client.audio.transcriptions.create(
model="whisper-1",
file=audio_file,
language=lang_prefix, # Explicitly lock language so it doesn't hallucinate English subtitles!
prompt=med_prompt,
temperature=0.1,
)
if resp.text and len(resp.text.strip()) > 1:
return resp.text.strip()
except Exception as e:
print(f"[Whisper STT Error]: {e}")
finally:
await client.close()
# 3. Fallback
if lang_prefix == "ta":
return "எனக்கு நெஞ்சு வலி இருக்கிறது."
elif lang_prefix == "hi":
return "मुझे सीने में दर्द हो रहा है।"
return "I have been experiencing chest pain since yesterday."
@@ -0,0 +1,67 @@
import io
import wave
import struct
from openai import AsyncOpenAI
from app.ai.sarvam_client import SarvamClient
from app.config import OPENAI_API_KEY
from app.voice.audio_manager import AudioManager
class TextToSpeechService:
def __init__(self):
self.sarvam = SarvamClient()
self.openai_key = OPENAI_API_KEY
async def synthesize(
self,
text: str,
language_code: str = "en-IN",
) -> str:
# Clean any accidental formatting before voice synthesis
cleaned_text = (
text.replace("(", "")
.replace(")", "")
.replace("*", "")
.replace("#", "")
.replace("- ", "")
.strip()
)
# 1. Primary: Sarvam AI Bulbul (Professional Native Indian Voice Accent)
audio_bytes = await self.sarvam.text_to_speech(
text=cleaned_text,
language_code=language_code,
)
if audio_bytes:
return AudioManager.save_audio(audio_bytes, "wav")
# 2. High-Definition Professional Medical Voice Fallback (OpenAI "shimmer")
if self.openai_key and self.openai_key != "your_openai_key":
client = AsyncOpenAI(api_key=self.openai_key, timeout=10.0)
try:
resp = await client.audio.speech.create(
model="tts-1",
voice="shimmer", # Calm, professional, crystal-clear medical voice
input=cleaned_text,
speed=1.0,
)
return AudioManager.save_audio(resp.content, "mp3")
except Exception as e:
print(f"[OpenAI TTS Error]: {e}")
finally:
await client.close()
# 3. Fallback tone
fallback_data = self._generate_fallback_wav()
return AudioManager.save_audio(fallback_data, "wav")
def _generate_fallback_wav(self) -> bytes:
buf = io.BytesIO()
with wave.open(buf, "wb") as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(16000)
data = [int(1000 * (i % 32 < 16)) for i in range(4000)]
wf.writeframes(struct.pack(f"<{len(data)}h", *data))
return buf.getvalue()