File size: 3,200 Bytes
76db545
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
"""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,
    )