ground-zero / src /api /routes /iot.py
jefffffff9
Initial commit: Sahel-Agri Voice AI
76db545
Raw
History Blame Contribute Delete
3.2 kB
"""POST /api/v1/query — full pipeline: audio → transcription → intent → sensor → voice response."""
from __future__ import annotations
import logging
import os
import tempfile
import time
from typing import Annotated, Optional
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
from src.api.dependencies import get_sensor_bridge, get_transcriber
from src.api.schemas import IoTQueryResponse
from src.engine.transcriber import Transcriber
from src.iot.intent_parser import IntentParser
from src.iot.sensor_bridge import SensorBridge
from src.iot.voice_responder import VoiceResponder
logger = logging.getLogger(__name__)
router = APIRouter()
_intent_parser = IntentParser()
_voice_responder = VoiceResponder(language="fr")
SUPPORTED_LANGUAGES = {"bam", "ful"}
MAX_AUDIO_BYTES = 10 * 1024 * 1024
@router.post("/query", response_model=IoTQueryResponse)
async def agricultural_query(
audio_file: Annotated[UploadFile, File(description="Audio file with farmer's voice query")],
language: Annotated[str, Form(description="Language code: 'bam' or 'ful'")] = "bam",
field_id: Annotated[Optional[str], Form(description="Field/location ID for sensor lookup")] = None,
transcriber: Transcriber = Depends(get_transcriber),
sensor_bridge: SensorBridge = Depends(get_sensor_bridge),
) -> IoTQueryResponse:
t0 = time.perf_counter()
if language not in SUPPORTED_LANGUAGES:
raise HTTPException(
status_code=422,
detail=f"Unsupported language '{language}'. Supported: {sorted(SUPPORTED_LANGUAGES)}",
)
audio_bytes = await audio_file.read()
if len(audio_bytes) > MAX_AUDIO_BYTES:
raise HTTPException(status_code=413, detail="Audio file too large. Max 10 MB.")
ext = os.path.splitext(audio_file.filename or "audio.wav")[1].lower() or ".wav"
tmp_path = None
try:
# Step 1: Transcribe
with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as tmp:
tmp.write(audio_bytes)
tmp_path = tmp.name
transcription_result = transcriber.transcribe_file(tmp_path, language)
# Step 2: Parse intent
intent = _intent_parser.parse(transcription_result.text, language)
# Step 3: Fetch sensor data
sensor_data = await sensor_bridge.fetch(intent, field_id=field_id)
# Step 4: Generate voice response
voice_response = _voice_responder.generate_response(intent, sensor_data)
except HTTPException:
raise
except Exception as e:
logger.error("IoT query failed: %s", e, exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
finally:
if tmp_path and os.path.exists(tmp_path):
os.unlink(tmp_path)
elapsed_ms = int((time.perf_counter() - t0) * 1000)
return IoTQueryResponse(
transcription=transcription_result.text,
language=language,
intent={
"action": intent.action,
"entity": intent.entity,
"confidence": intent.confidence,
},
sensor_data=sensor_data.values,
voice_response=voice_response,
processing_time_ms=elapsed_ms,
)