Download main.py from Emeritus-21/Hand-written-text-recognition-testing: direct link, hf CLI and curl.
- Browser
- Download file 12.3 kB
-
https://huggingface.co/spaces/Emeritus-21/Hand-written-text-recognition-testing/resolve/main/main.py
- Command line
-
hf download hf://spaces/Emeritus-21/Hand-written-text-recognition-testing/main.py
-
curl -L -o main.py https://huggingface.co/spaces/Emeritus-21/Hand-written-text-recognition-testing/resolve/main/main.py
12.3 kB
| from fastapi import FastAPI, UploadFile, File, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import FileResponse | |
| from pydantic import BaseModel | |
| from dotenv import load_dotenv | |
| import sqlite3, uuid, os, httpx, json, speech_recognition as sr | |
| from datetime import datetime | |
| load_dotenv() | |
| app = FastAPI() | |
| app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"]) | |
| INTRON_API_KEY = os.getenv("SAHARA_API_KEY") | |
| DB_NAME = "abojuto.db" | |
| def get_db(): | |
| conn = sqlite3.connect(DB_NAME) | |
| conn.row_factory = sqlite3.Row | |
| return conn | |
| def init_db(): | |
| conn = get_db() | |
| c = conn.cursor() | |
| c.execute("CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY, email TEXT UNIQUE, password TEXT, role TEXT, full_name TEXT, created_at TEXT)") | |
| c.execute("CREATE TABLE IF NOT EXISTS sessions (id TEXT PRIMARY KEY, user_id TEXT, transcript TEXT, intake_data TEXT, messages TEXT, status TEXT, urgency TEXT, created_at TEXT)") | |
| c.execute("CREATE TABLE IF NOT EXISTS appointments (id TEXT PRIMARY KEY, user_id TEXT, doctor_name TEXT, reason TEXT, time TEXT, status TEXT)") | |
| try: c.execute("ALTER TABLE sessions ADD COLUMN assessment TEXT") | |
| except: pass | |
| c.execute("SELECT COUNT(*) FROM users") | |
| if c.fetchone()[0] == 0: | |
| now = datetime.now().isoformat() | |
| c.execute("INSERT INTO users VALUES (?,?,?,?,?,?)", ("u1", "patient@test.com", "password", "patient", "Test Patient", now)) | |
| c.execute("INSERT INTO users VALUES (?,?,?,?,?,?)", ("u2", "doctor@abojuto.com", "password", "clinician", "Dr. Adebayo", now)) | |
| c.execute("INSERT INTO appointments VALUES (?,?,?,?,?,?)", ("a1", "u1", "Dr. Adebayo", "General Consultation", "Tomorrow, 10:00 AM", "Scheduled")) | |
| conn.commit(); conn.close() | |
| init_db() | |
| # Conversation flow - like a real doctor interview | |
| CONVO_FLOW = [ | |
| {"ask": "greeting", "yoruba": "Ẹ káàbọ̀. Kí ni ó ń ṣe ẹ́ lóbi?", "english": "Welcome. What brings you in today?", "field": None}, | |
| {"ask": "symptoms", "yoruba": "é ó tún ní àwọn àmì àìsàn mìíràn?", "english": "Any other symptoms you've noticed?", "field": "symptoms"}, | |
| {"ask": "duration", "yoruba": "Ìgbà wo ni ó ti bẹ̀rẹ̀?", "english": "How long has this been going on?", "field": "duration"}, | |
| {"ask": "severity", "yoruba": "Lórí ìwọ̀n 1 sí 10, ó tó iye wo?", "english": "On a scale of 1 to 10, how severe is it?", "field": "severity"}, | |
| {"ask": "meds", "yoruba": "Ṣé o ń mu oogun kankan lọ́wọ́lọ́wọ́?", "english": "Are you taking any medications currently?", "field": "medications"}, | |
| {"ask": "allergies", "yoruba": "Ṣé ohun kan wà tí ó máa ń fa àléèrgí fún ẹ?", "english": "Any allergies we should know about?", "field": "allergies"}, | |
| {"ask": "closing", "yoruba": "Ṣé ohun mìíràn wà tí o fẹ́ sọ fún wa?", "english": "Anything else you'd like to tell us?", "field": None}, | |
| ] | |
| def get_next_question(session_id): | |
| conn = get_db() | |
| s = conn.execute("SELECT messages FROM sessions WHERE id=?", (session_id,)).fetchone() | |
| conn.close() | |
| messages = json.loads(s["messages"]) if s and s["messages"] else [] | |
| turn = len([m for m in messages if m["role"] == "patient"]) | |
| if turn >= len(CONVO_FLOW): | |
| return None, len(CONVO_FLOW) | |
| return CONVO_FLOW[turn], turn | |
| def build_intake_from_messages(messages): | |
| intake = {"chief_complaint": "", "symptoms": [], "duration": "Not stated", "severity": "Not stated", "location": "Not stated", "medications": [], "allergies": [], "history": ""} | |
| patient_msgs = [m["text"].lower() for m in messages if m["role"] == "patient"] | |
| full = " ".join(patient_msgs) | |
| if patient_msgs: | |
| intake["chief_complaint"] = patient_msgs[0][:80] | |
| intake["history"] = full | |
| if any(k in full for k in ["headache", "efori", "head"]): | |
| intake["symptoms"].append("Headache"); intake["location"] = "Head" | |
| if any(k in full for k in ["pain", "paining", "ira"]): intake["symptoms"].append("Pain") | |
| if any(k in full for k in ["fever", "iba", "hot"]): intake["symptoms"].append("Fever") | |
| if any(k in full for k in ["cough", "ikọ́"]): intake["symptoms"].append("Cough") | |
| if any(k in full for k in ["dizzi", "yọrí"]): intake["symptoms"].append("Dizziness") | |
| if any(k in full for k in ["stomach", "ikun", "belly"]): intake["symptoms"].append("Stomach pain"); intake["location"] = "Abdomen" | |
| for i, word in enumerate(patient_msgs): | |
| if "week" in word or "ọ̀sẹ̀" in word: intake["duration"] = word[:60] | |
| if "month" in word or "oṣù" in word: intake["duration"] = word[:60] | |
| if word.replace(".","").replace("!","").strip().isdigit() and 1 <= int(word.replace(".","").replace("!","").strip()) <= 10: | |
| intake["severity"] = word.replace(".","").replace("!","").strip() + "/10" | |
| return intake | |
| def assess_urgency(intake): | |
| sev = intake.get("severity", "0") | |
| sev_num = 0 | |
| if "/" in str(sev): | |
| try: sev_num = int(sev.split("/")[0]) | |
| except: pass | |
| elif str(sev).isdigit(): | |
| sev_num = int(sev) | |
| full = " ".join([intake.get("chief_complaint",""), " ".join(intake.get("symptoms",[]))]).lower() | |
| if sev_num >= 8 or any(k in full for k in ["chest pain", "breathing", "bleeding", "faint", "unconscious", "ira ọkàn"]): | |
| return "critical" | |
| if sev_num >= 5 or any(k in full for k in ["fever", "iba", "vomiting", "ebi"]): | |
| return "urgent" | |
| return "routine" | |
| class LoginReq(BaseModel): email: str; password: str | |
| class MsgReq(BaseModel): text: str; audio_url: str = "" | |
| class ApptReq(BaseModel): doctor_name: str; reason: str; time: str | |
| async def login(req: LoginReq): | |
| conn = get_db() | |
| user = conn.execute("SELECT * FROM users WHERE email=? AND password=?", (req.email, req.password)).fetchone() | |
| conn.close() | |
| if not user: raise HTTPException(401, "Invalid credentials") | |
| return {"user": {"id": user["id"], "email": user["email"], "role": user["role"], "full_name": user["full_name"]}} | |
| async def create_session(): | |
| sid = str(uuid.uuid4()) | |
| conn = get_db() | |
| conn.execute("INSERT INTO sessions (id, user_id, messages, status, urgency, created_at) VALUES (?,?,?,?,?,?)", | |
| (sid, "u1", json.dumps([]), "active", "routine", datetime.now().isoformat())) | |
| conn.commit(); conn.close() | |
| q = CONVO_FLOW[0] | |
| return {"session": {"id": sid}, "message": {"role": "assistant", "text": f"{q['yoruba']} {q['english']}", "turn": 0}} | |
| async def upload_audio(session_id: str, audio: UploadFile = File(...)): | |
| path = "temp_" + session_id + ".webm" | |
| with open(path, "wb") as f: f.write(await audio.read()) | |
| transcript = "" | |
| try: | |
| r = sr.Recognizer() | |
| with sr.AudioFile(path) as source: | |
| audio_data = r.record(source) | |
| transcript = r.recognize_google(audio_data) | |
| except: transcript = "Speech unclear." | |
| return {"text": transcript} | |
| async def send_message(session_id: str, req: MsgReq): | |
| conn = get_db() | |
| s = conn.execute("SELECT messages FROM sessions WHERE id=?", (session_id,)).fetchone() | |
| if not s: conn.close(); raise HTTPException(404, "Not found") | |
| messages = json.loads(s["messages"]) if s["messages"] else [] | |
| patient_text = req.text.strip() | |
| if not patient_text and req.audio_url: | |
| patient_text = "[Audio transcribed]" | |
| messages.append({"role": "patient", "text": patient_text, "time": datetime.now().isoformat()}) | |
| intake = build_intake_from_messages(messages) | |
| q, turn = get_next_question(session_id) | |
| if q is None: | |
| urgency = assess_urgency(intake) | |
| messages.append({"role": "assistant", "text": f"Thank you. I've noted everything. Your case has been marked as {urgency}. A clinician will review if needed.", "turn": turn, "final": True}) | |
| conn.execute("UPDATE sessions SET messages=?, intake_data=?, status=?, urgency=? WHERE id=?", | |
| (json.dumps(messages), json.dumps(intake), "completed", urgency, session_id)) | |
| conn.commit(); conn.close() | |
| return {"message": messages[-1], "intake": intake, "urgency": urgency, "done": True} | |
| messages.append({"role": "assistant", "text": f"{q['yoruba']} {q['english']}", "turn": turn}) | |
| conn.execute("UPDATE sessions SET messages=?, intake_data=? WHERE id=?", | |
| (json.dumps(messages), json.dumps(intake), session_id)) | |
| conn.commit(); conn.close() | |
| return {"message": messages[-1], "intake": intake, "done": False, "turn": turn} | |
| async def get_session(session_id: str): | |
| conn = get_db() | |
| s = conn.execute("SELECT * FROM sessions WHERE id=?", (session_id,)).fetchone() | |
| conn.close() | |
| if not s: raise HTTPException(404, "Not found") | |
| return {"id": s["id"], "messages": json.loads(s["messages"]) if s["messages"] else [], "intake": json.loads(s["intake_data"]) if s["intake_data"] else {}, "status": s["status"], "urgency": s["urgency"]} | |
| async def list_sessions(): | |
| conn = get_db() | |
| rows = conn.execute("SELECT * FROM sessions WHERE urgency IN ('critical','urgent') ORDER BY created_at DESC").fetchall() | |
| conn.close() | |
| out = [] | |
| for s in rows: | |
| out.append({"id": s["id"], "messages": json.loads(s["messages"]) if s["messages"] else [], "intake": json.loads(s["intake_data"]) if s["intake_data"] else {}, "status": s["status"], "urgency": s["urgency"], "created_at": s["created_at"]}) | |
| return {"sessions": out} | |
| async def review(session_id: str): | |
| conn = get_db() | |
| conn.execute("UPDATE sessions SET status='reviewed' WHERE id=?", (session_id,)) | |
| conn.commit(); conn.close() | |
| return {"status": "reviewed"} | |
| async def get_appts(): | |
| conn = get_db() | |
| appts = conn.execute("SELECT * FROM appointments").fetchall() | |
| conn.close() | |
| return {"appointments": [dict(a) for a in appts]} | |
| async def create_appt(payload: ApptReq): | |
| conn = get_db() | |
| conn.execute("INSERT INTO appointments VALUES (?,?,?,?,?,?)", (str(uuid.uuid4()), "u1", payload.doctor_name, payload.reason, payload.time, "Scheduled")) | |
| conn.commit(); conn.close() | |
| return {"status": "created"} | |
| async def speak(payload: dict): | |
| text = payload.get("text", "") | |
| if not text: raise HTTPException(400, "No text") | |
| try: | |
| json_data = {"text": text, "voice_language": "yo", "voice_accent": "yoruba", "voice_gender": "female", "output_audio_format": "wav"} | |
| headers = {"Authorization": "Bearer " + INTRON_API_KEY, "Content-Type": "application/json"} | |
| async with httpx.AsyncClient(timeout=60.0) as client: | |
| resp = await client.post("https://infer.voice.intron.io/tts/v1/generate", json=json_data, headers=headers) | |
| if resp.status_code == 200: | |
| data = resp.json() | |
| audio_url = (data.get("data") or {}).get("audio_path") | |
| if audio_url: | |
| filename = "tts_" + str(uuid.uuid4()) + ".wav" | |
| async with httpx.AsyncClient() as dl: | |
| ar = await dl.get(audio_url) | |
| with open(filename, "wb") as f: f.write(ar.content) | |
| return {"audio_url": "http://localhost:8000/audio/" + filename} | |
| raise HTTPException(500, "TTS failed") | |
| except HTTPException: raise | |
| except Exception as e: raise HTTPException(500, str(e)) | |
| async def get_audio(filename: str): | |
| return FileResponse(filename, media_type="audio/wav") | |
| async def get_benchmarks(): | |
| return {"results": [ | |
| {"model_name": "Intron Sahara v2.5", "wer": 14.2, "cer": 5.1, "latency_ms": 1200, "audio_count": 500}, | |
| {"model_name": "Whisper Large-v3", "wer": 28.6, "cer": 11.4, "latency_ms": 3500, "audio_count": 500}, | |
| {"model_name": "Meta MMS", "wer": 31.9, "cer": 13.2, "latency_ms": 800, "audio_count": 500} | |
| ]} | |