Aglimate / app /main.py
nexusbert's picture
Document weather endpoints and remove sports API
27fd445
import os
import sys
import logging
import uuid
from fastapi import FastAPI, Body, UploadFile, File, Form, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from typing import Optional
import uvicorn
import requests
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if BASE_DIR not in sys.path:
sys.path.insert(0, BASE_DIR)
from app.tasks.rag_updater import schedule_updates
from app.utils import config
from app.agents.crew_pipeline import run_pipeline
from app.agents.climate_agent import advise_climate_resilient
from app.utils.weather_api import fetch_forecast, alerts_for_q
logging.basicConfig(
format="%(asctime)s [%(levelname)s] %(message)s",
level=logging.INFO
)
app = FastAPI(
title="Aglimate Farmer-First Climate-Resilient Advisory Backend",
description=(
"Backend for Aglimate, a Farmer-First Climate-Resilient Advisory Agent for smallholder farmers. "
"Provides multilingual Qwen-based Q&A, RAG-powered updates, and a multimodal Qwen-VL endpoint for "
"text + photo + GPS-aware climate-smart advice."
),
version="2.0.0",
)
app.add_middleware(
CORSMiddleware,
allow_origins=getattr(config, "ALLOWED_ORIGINS", ["*"]),
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
def _resolve_weather_q(
q: Optional[str],
state: Optional[str],
lat: Optional[float],
lon: Optional[float],
) -> str:
if lat is not None and lon is not None:
return f"{lat},{lon}"
if q:
return q
if state:
return f"{state}, Nigeria"
raise HTTPException(
status_code=400,
detail="Provide q, state, or both lat and lon",
)
@app.on_event("startup")
def startup_event():
logging.info("Starting Aglimate AI backend...")
schedule_updates()
@app.get("/")
def home():
return {
"status": "Aglimate climate-resilient backend running",
"version": "2.0.0",
"vectorstore_path": config.VECTORSTORE_PATH
}
@app.get("/weather-test")
def weather_test(
q: Optional[str] = None,
state: Optional[str] = None,
lat: Optional[float] = None,
lon: Optional[float] = None,
aqi: str = "no",
):
if not config.WEATHER_API_KEY:
raise HTTPException(status_code=500, detail="WEATHER_API_KEY is not configured")
q_val = _resolve_weather_q(q, state, lat, lon)
try:
return fetch_forecast(
q=q_val,
days=getattr(config, "WEATHER_FORECAST_DAYS", 3),
alerts=getattr(config, "WEATHER_ALERTS", "yes"),
aqi=aqi,
)
except Exception as e:
raise HTTPException(status_code=502, detail=f"WeatherAPI request failed: {e}")
@app.get("/weather-alerts")
def weather_alerts(
q: Optional[str] = None,
state: Optional[str] = None,
lat: Optional[float] = None,
lon: Optional[float] = None,
):
if not config.WEATHER_API_KEY:
raise HTTPException(status_code=500, detail="WEATHER_API_KEY is not configured")
q_val = _resolve_weather_q(q, state, lat, lon)
try:
return alerts_for_q(q_val)
except Exception as e:
raise HTTPException(status_code=502, detail=f"WeatherAPI request failed: {e}")
@app.post("/ask")
def ask_farmbot(
query: str = Body(..., embed=True),
session_id: str = Body(None, embed=True)
):
"""
Ask Aglimate AI a farming-related question.
- Supports Hausa, Igbo, Yoruba, Swahili, Amharic, and English.
- Automatically detects user language, translates if needed,
and returns response in the same language.
- Maintains separate conversation memory per session_id.
"""
if not session_id:
session_id = str(uuid.uuid4()) # assign new session if missing
logging.info(f"Received query: {query} [session_id={session_id}]")
answer_data = run_pipeline(query, session_id=session_id)
detected_lang = answer_data.get("detected_language", "Unknown")
logging.info(f"Detected language: {detected_lang}")
return {
"query": query,
"answer": answer_data.get("answer"),
"session_id": answer_data.get("session_id"),
"detected_language": detected_lang
}
@app.post("/advise")
async def advise_climate_resilient_endpoint(
query: str = Form(..., description="Farmer question or situation description"),
session_id: Optional[str] = Form(None, description="Conversation session id"),
latitude: Optional[float] = Form(None, description="GPS latitude (optional)"),
longitude: Optional[float] = Form(None, description="GPS longitude (optional)"),
photo: Optional[UploadFile] = File(
None, description="Optional field photo (plants, soil, farm conditions)"
),
video: Optional[UploadFile] = File(
None,
description="Optional short field video of the farm (optional)",
),
):
"""
Multimodal Farmer-First Climate-Resilient advisory endpoint.
Accepts:
- Text description from the farmer
- Optional GPS coordinates (latitude, longitude)
- Optional field photo
All reasoning is handled by a multimodal Qwen-VL model (no Gemini).
"""
if not session_id:
session_id = str(uuid.uuid4())
image_bytes = await photo.read() if photo is not None else None
video_bytes = await video.read() if video is not None else None
result = advise_climate_resilient(
query=query,
session_id=session_id,
latitude=latitude,
longitude=longitude,
image_bytes=image_bytes,
video_bytes=video_bytes,
)
return result
@app.get("/weather/current")
def weather_current(
q: Optional[str] = None,
state: Optional[str] = None,
lat: Optional[float] = None,
lon: Optional[float] = None,
aqi: str = "yes",
):
if not config.WEATHER_API_KEY:
raise HTTPException(status_code=500, detail="WEATHER_API_KEY is not configured")
q_val = _resolve_weather_q(q, state, lat, lon)
url = "http://api.weatherapi.com/v1/current.json"
params = {"key": config.WEATHER_API_KEY, "q": q_val, "aqi": aqi}
try:
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
return resp.json()
except Exception as e:
raise HTTPException(status_code=502, detail=f"WeatherAPI request failed: {e}")
@app.get("/weather/forecast")
def weather_forecast(
q: Optional[str] = None,
state: Optional[str] = None,
lat: Optional[float] = None,
lon: Optional[float] = None,
days: Optional[int] = None,
aqi: str = "yes",
alerts: str = "yes",
):
if not config.WEATHER_API_KEY:
raise HTTPException(status_code=500, detail="WEATHER_API_KEY is not configured")
q_val = _resolve_weather_q(q, state, lat, lon)
try:
return fetch_forecast(
q=q_val,
days=days or getattr(config, "WEATHER_FORECAST_DAYS", 3),
alerts=alerts,
aqi=aqi,
)
except Exception as e:
raise HTTPException(status_code=502, detail=f"WeatherAPI request failed: {e}")
@app.get("/weather/history")
def weather_history(
q: Optional[str] = None,
state: Optional[str] = None,
lat: Optional[float] = None,
lon: Optional[float] = None,
dt: Optional[str] = None,
end_dt: Optional[str] = None,
hour: Optional[int] = None,
aqi: str = "yes",
):
if not config.WEATHER_API_KEY:
raise HTTPException(status_code=500, detail="WEATHER_API_KEY is not configured")
q_val = _resolve_weather_q(q, state, lat, lon)
url = "http://api.weatherapi.com/v1/history.json"
params = {"key": config.WEATHER_API_KEY, "q": q_val, "aqi": aqi}
if dt:
params["dt"] = dt
if end_dt:
params["end_dt"] = end_dt
if hour is not None:
params["hour"] = hour
try:
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
return resp.json()
except Exception as e:
raise HTTPException(status_code=502, detail=f"WeatherAPI request failed: {e}")
@app.get("/weather/marine")
def weather_marine(
q: Optional[str] = None,
lat: Optional[float] = None,
lon: Optional[float] = None,
days: int = 3,
tides: str = "no",
):
if not config.WEATHER_API_KEY:
raise HTTPException(status_code=500, detail="WEATHER_API_KEY is not configured")
q_val = _resolve_weather_q(q, None, lat, lon)
url = "http://api.weatherapi.com/v1/marine.json"
params = {"key": config.WEATHER_API_KEY, "q": q_val, "days": max(1, min(days, 7)), "tides": tides}
try:
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
return resp.json()
except Exception as e:
raise HTTPException(status_code=502, detail=f"WeatherAPI request failed: {e}")
@app.get("/weather/future")
def weather_future(
q: Optional[str] = None,
state: Optional[str] = None,
lat: Optional[float] = None,
lon: Optional[float] = None,
dt: str = "",
):
if not config.WEATHER_API_KEY:
raise HTTPException(status_code=500, detail="WEATHER_API_KEY is not configured")
if not dt:
raise HTTPException(status_code=400, detail="dt is required for future weather")
q_val = _resolve_weather_q(q, state, lat, lon)
url = "http://api.weatherapi.com/v1/future.json"
params = {"key": config.WEATHER_API_KEY, "q": q_val, "dt": dt}
try:
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
return resp.json()
except Exception as e:
raise HTTPException(status_code=502, detail=f"WeatherAPI request failed: {e}")
@app.get("/weather/timezone")
def weather_timezone(
q: Optional[str] = None,
state: Optional[str] = None,
lat: Optional[float] = None,
lon: Optional[float] = None,
):
if not config.WEATHER_API_KEY:
raise HTTPException(status_code=500, detail="WEATHER_API_KEY is not configured")
q_val = _resolve_weather_q(q, state, lat, lon)
url = "http://api.weatherapi.com/v1/timezone.json"
params = {"key": config.WEATHER_API_KEY, "q": q_val}
try:
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
return resp.json()
except Exception as e:
raise HTTPException(status_code=502, detail=f"WeatherAPI request failed: {e}")
@app.get("/weather/search")
def weather_search(q: str):
if not config.WEATHER_API_KEY:
raise HTTPException(status_code=500, detail="WEATHER_API_KEY is not configured")
url = "http://api.weatherapi.com/v1/search.json"
params = {"key": config.WEATHER_API_KEY, "q": q}
try:
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
return resp.json()
except Exception as e:
raise HTTPException(status_code=502, detail=f"WeatherAPI request failed: {e}")
@app.get("/weather/astronomy")
def weather_astronomy(
q: Optional[str] = None,
state: Optional[str] = None,
lat: Optional[float] = None,
lon: Optional[float] = None,
dt: Optional[str] = None,
):
if not config.WEATHER_API_KEY:
raise HTTPException(status_code=500, detail="WEATHER_API_KEY is not configured")
q_val = _resolve_weather_q(q, state, lat, lon)
url = "http://api.weatherapi.com/v1/astronomy.json"
params = {"key": config.WEATHER_API_KEY, "q": q_val}
if dt:
params["dt"] = dt
try:
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
return resp.json()
except Exception as e:
raise HTTPException(status_code=502, detail=f"WeatherAPI request failed: {e}")
@app.get("/weather/ip")
def weather_ip(ip: Optional[str] = None):
if not config.WEATHER_API_KEY:
raise HTTPException(status_code=500, detail="WEATHER_API_KEY is not configured")
url = "http://api.weatherapi.com/v1/ip.json"
q_val = ip or "auto:ip"
params = {"key": config.WEATHER_API_KEY, "q": q_val}
try:
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
return resp.json()
except Exception as e:
raise HTTPException(status_code=502, detail=f"WeatherAPI request failed: {e}")
if __name__ == "__main__":
uvicorn.run(
"app.main:app",
host="0.0.0.0",
port=getattr(config, "PORT", 7860),
reload=bool(getattr(config, "DEBUG", False))
)