"""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, )