""" Farmer.chat Alert Summary Generator - FastAPI Application """ from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from typing import Optional, Dict from datetime import datetime import os # Import pipeline components from src.pipeline import FarmerChatPipeline # Import MCP servers from src.servers.weather import WeatherServer from src.servers.soil import SoilPropertiesServer from src.servers.water import WaterServer from src.servers.elevation import ElevationServer from src.servers.pests import PestsServer # ============================================================================ # PYDANTIC MODELS # ============================================================================ class LocationRequest(BaseModel): """Request model for alert generation""" location_name: str district: Optional[str] = None class AlertResponse(BaseModel): """Response model for generated alerts""" location: str coordinates: Dict[str, float] district: Optional[str] alert_summary: str timestamp: str class QueryRequest(BaseModel): """Request model for specific queries""" query: str location_name: Optional[str] = None district: Optional[str] = None # ============================================================================ # BIHAR LOCATION DATA # ============================================================================ BIHAR_DATA = { "Araria": ["Araria", "Forbesganj", "Jokihat", "Raniganj"], "Arwal": ["Arwal", "Kaler", "Karpi", "Kurtha"], "Aurangabad": ["Aurangabad", "Daudnagar", "Obra", "Nabinagar"], "Banka": ["Banka", "Amarpur", "Barahat", "Belhar"], "Begusarai": ["Begusarai", "Bakhri", "Barauni", "Teghra"], "Bhagalpur": ["Bhagalpur", "Sabour", "Nathnagar", "Kahalgaon"], "Bhojpur": ["Arrah", "Jagdishpur", "Piro", "Shahpur"], "Buxar": ["Buxar", "Dumraon", "Chausa", "Simri"], "Darbhanga": ["Darbhanga", "Baheri", "Jale", "Benipur"], "East Champaran": ["Motihari", "Raxaul", "Chakia", "Dhaka"], "Gaya": ["Gaya", "Bodh Gaya", "Tekari", "Sherghati"], "Gopalganj": ["Gopalganj", "Barauli", "Baikunthpur", "Kateya"], "Jamui": ["Jamui", "Jhajha", "Sikandra", "Sono"], "Jehanabad": ["Jehanabad", "Ghoshi", "Makhdumpur", "Modanganj"], "Kaimur": ["Bhabua", "Mohania", "Ramgarh", "Chainpur"], "Katihar": ["Katihar", "Barsoi", "Manihari", "Pranpur"], "Khagaria": ["Khagaria", "Parbatta", "Alauli", "Beldaur"], "Kishanganj": ["Kishanganj", "Bahadurganj", "Thakurganj", "Dighalbank"], "Lakhisarai": ["Lakhisarai", "Halsi", "Suryagarha", "Pipariya"], "Madhepura": ["Madhepura", "Udakishanganj", "Murliganj", "Alamnagar"], "Madhubani": ["Madhubani", "Jhanjharpur", "Benipatti", "Jainagar"], "Munger": ["Munger", "Jamalpur", "Asarganj", "Tarapur"], "Muzaffarpur": ["Muzaffarpur", "Sitamarhi", "Minapur", "Bochaha"], "Nalanda": ["Bihar Sharif", "Rajgir", "Hilsa", "Biharsharif"], "Nawada": ["Nawada", "Rajauli", "Akbarpur", "Hisua"], "Patna": ["Patna", "Danapur", "Fatuha", "Khagaul"], "Purnia": ["Purnia", "Dhamdaha", "Kasba", "Banmankhi"], "Rohtas": ["Sasaram", "Dehri", "Bikramganj", "Nasriganj"], "Saharsa": ["Saharsa", "Sonbarsa", "Simri Bakhtiarpur", "Mahishi"], "Samastipur": ["Samastipur", "Rosera", "Dalsinghsarai", "Pusa"], "Saran": ["Chapra", "Marhaura", "Amnour", "Sonepur"], "Sheikhpura": ["Sheikhpura", "Barbigha", "Ariari", "Shekhopur"], "Sheohar": ["Sheohar", "Dumri Katsari", "Piprahi", "Tariyani"], "Sitamarhi": ["Sitamarhi", "Pupri", "Belsand", "Bathnaha"], "Siwan": ["Siwan", "Maharajganj", "Mairwa", "Darauli"], "Supaul": ["Supaul", "Nirmali", "Triveniganj", "Chhatapur"], "Vaishali": ["Hajipur", "Mahua", "Lalganj", "Desri"], "West Champaran": ["Bettiah", "Bagaha", "Narkatiaganj", "Lauriya"] } LOCATIONS = { "Araria": {"latitude": 26.1523, "longitude": 87.5167}, "Forbesganj": {"latitude": 26.3023, "longitude": 87.2664}, "Jokihat": {"latitude": 25.8998, "longitude": 87.2686}, "Raniganj": {"latitude": 26.0537, "longitude": 87.5333}, "Arwal": {"latitude": 25.2560, "longitude": 84.6819}, "Kaler": {"latitude": 25.1960, "longitude": 84.6219}, "Karpi": {"latitude": 25.2360, "longitude": 84.7019}, "Kurtha": {"latitude": 25.3160, "longitude": 84.6619}, "Aurangabad": {"latitude": 24.7521, "longitude": 84.3742}, "Daudnagar": {"latitude": 25.0337, "longitude": 84.4007}, "Obra": {"latitude": 24.9923, "longitude": 84.4342}, "Nabinagar": {"latitude": 24.6087, "longitude": 84.1269}, "Banka": {"latitude": 24.8893, "longitude": 86.9220}, "Amarpur": {"latitude": 25.0393, "longitude": 86.9020}, "Barahat": {"latitude": 24.8393, "longitude": 87.0020}, "Belhar": {"latitude": 24.9393, "longitude": 86.9620}, "Begusarai": {"latitude": 25.4182, "longitude": 86.1347}, "Bakhri": {"latitude": 25.4582, "longitude": 86.0547}, "Barauni": {"latitude": 25.4751, "longitude": 86.0458}, "Teghra": {"latitude": 25.5082, "longitude": 85.9347}, "Bhagalpur": {"latitude": 25.2425, "longitude": 86.9842}, "Sabour": {"latitude": 25.2375, "longitude": 87.0542}, "Nathnagar": {"latitude": 25.1225, "longitude": 87.0042}, "Kahalgaon": {"latitude": 25.1925, "longitude": 87.2142}, "Arrah": {"latitude": 25.5560, "longitude": 84.6631}, "Jagdishpur": {"latitude": 25.4660, "longitude": 84.4231}, "Piro": {"latitude": 25.3260, "longitude": 84.4031}, "Shahpur": {"latitude": 25.6060, "longitude": 84.4031}, "Buxar": {"latitude": 25.5641, "longitude": 83.9778}, "Dumraon": {"latitude": 25.5541, "longitude": 84.1478}, "Chausa": {"latitude": 25.5241, "longitude": 83.9178}, "Simri": {"latitude": 25.6141, "longitude": 84.0478}, "Darbhanga": {"latitude": 26.1542, "longitude": 85.8978}, "Baheri": {"latitude": 26.0442, "longitude": 85.8378}, "Jale": {"latitude": 26.2042, "longitude": 85.8578}, "Benipur": {"latitude": 26.1142, "longitude": 85.9478}, "Motihari": {"latitude": 26.6484, "longitude": 84.9194}, "Raxaul": {"latitude": 26.9784, "longitude": 84.8494}, "Chakia": {"latitude": 26.4184, "longitude": 85.0494}, "Dhaka": {"latitude": 26.6784, "longitude": 85.1694}, "Gaya": {"latitude": 24.7955, "longitude": 85.0002}, "Bodh Gaya": {"latitude": 24.6955, "longitude": 84.9902}, "Tekari": {"latitude": 24.9455, "longitude": 85.0402}, "Sherghati": {"latitude": 24.5655, "longitude": 84.7902}, "Gopalganj": {"latitude": 26.4685, "longitude": 84.4388}, "Barauli": {"latitude": 26.3785, "longitude": 84.5788}, "Baikunthpur": {"latitude": 26.5285, "longitude": 84.3588}, "Kateya": {"latitude": 26.4285, "longitude": 84.6388}, "Jamui": {"latitude": 24.9272, "longitude": 86.2231}, "Jhajha": {"latitude": 24.7772, "longitude": 86.3731}, "Sikandra": {"latitude": 24.9672, "longitude": 86.0631}, "Sono": {"latitude": 24.8372, "longitude": 86.1431}, "Jehanabad": {"latitude": 25.2078, "longitude": 84.9869}, "Ghoshi": {"latitude": 25.1478, "longitude": 84.9269}, "Makhdumpur": {"latitude": 25.2478, "longitude": 85.0469}, "Modanganj": {"latitude": 25.2678, "longitude": 84.9069}, "Bhabua": {"latitude": 25.0405, "longitude": 83.6074}, "Mohania": {"latitude": 25.1305, "longitude": 83.4774}, "Ramgarh": {"latitude": 24.9505, "longitude": 83.6874}, "Chainpur": {"latitude": 25.2005, "longitude": 83.7474}, "Katihar": {"latitude": 25.5394, "longitude": 87.5839}, "Barsoi": {"latitude": 25.3794, "longitude": 87.8839}, "Manihari": {"latitude": 25.3394, "longitude": 87.6239}, "Pranpur": {"latitude": 25.6894, "longitude": 87.7239}, "Khagaria": {"latitude": 25.5022, "longitude": 86.4665}, "Parbatta": {"latitude": 25.5422, "longitude": 86.5865}, "Alauli": {"latitude": 25.4622, "longitude": 86.3465}, "Beldaur": {"latitude": 25.5622, "longitude": 86.4265}, "Kishanganj": {"latitude": 26.1046, "longitude": 87.9475}, "Bahadurganj": {"latitude": 26.2646, "longitude": 88.1175}, "Thakurganj": {"latitude": 26.0446, "longitude": 87.8275}, "Dighalbank": {"latitude": 25.9046, "longitude": 87.9875}, "Lakhisarai": {"latitude": 25.1678, "longitude": 86.0927}, "Halsi": {"latitude": 25.2278, "longitude": 86.0327}, "Suryagarha": {"latitude": 25.1078, "longitude": 86.1527}, "Pipariya": {"latitude": 25.2078, "longitude": 86.1327}, "Madhepura": {"latitude": 25.9207, "longitude": 86.7940}, "Udakishanganj": {"latitude": 25.9807, "longitude": 86.6740}, "Murliganj": {"latitude": 25.8907, "longitude": 86.9940}, "Alamnagar": {"latitude": 25.9607, "longitude": 86.7340}, "Madhubani": {"latitude": 26.3561, "longitude": 86.0644}, "Jhanjharpur": {"latitude": 26.2661, "longitude": 86.2844}, "Benipatti": {"latitude": 26.5961, "longitude": 86.1444}, "Jainagar": {"latitude": 26.2061, "longitude": 86.1644}, "Munger": {"latitude": 25.3753, "longitude": 86.4734}, "Jamalpur": {"latitude": 25.3153, "longitude": 86.4934}, "Asarganj": {"latitude": 25.1453, "longitude": 86.6834}, "Tarapur": {"latitude": 25.0253, "longitude": 86.6334}, "Muzaffarpur": {"latitude": 26.1225, "longitude": 85.3906}, "Sitamarhi": {"latitude": 26.5925, "longitude": 85.4806}, "Minapur": {"latitude": 26.0625, "longitude": 85.2906}, "Bochaha": {"latitude": 26.0025, "longitude": 85.5306}, "Bihar Sharif": {"latitude": 25.1979, "longitude": 85.5238}, "Rajgir": {"latitude": 25.0279, "longitude": 85.4238}, "Hilsa": {"latitude": 25.3179, "longitude": 85.2838}, "Biharsharif": {"latitude": 25.1979, "longitude": 85.5238}, "Nawada": {"latitude": 24.8834, "longitude": 85.5387}, "Rajauli": {"latitude": 25.0634, "longitude": 85.6387}, "Akbarpur": {"latitude": 24.8234, "longitude": 85.4587}, "Hisua": {"latitude": 24.8334, "longitude": 85.4187}, "Patna": {"latitude": 25.5941, "longitude": 85.1376}, "Danapur": {"latitude": 25.6341, "longitude": 85.0476}, "Fatuha": {"latitude": 25.5041, "longitude": 85.3076}, "Khagaul": {"latitude": 25.5741, "longitude": 85.0476}, "Purnia": {"latitude": 25.7771, "longitude": 87.4753}, "Dhamdaha": {"latitude": 25.8871, "longitude": 87.5853}, "Kasba": {"latitude": 25.8471, "longitude": 87.5353}, "Banmankhi": {"latitude": 25.8871, "longitude": 87.1953}, "Sasaram": {"latitude": 24.9520, "longitude": 84.0328}, "Dehri": {"latitude": 24.9020, "longitude": 84.1828}, "Bikramganj": {"latitude": 25.2120, "longitude": 84.2628}, "Nasriganj": {"latitude": 25.0520, "longitude": 84.1228}, "Saharsa": {"latitude": 25.8769, "longitude": 86.5956}, "Sonbarsa": {"latitude": 25.9269, "longitude": 86.7356}, "Simri Bakhtiarpur": {"latitude": 25.9569, "longitude": 86.3556}, "Mahishi": {"latitude": 25.9969, "longitude": 86.4756}, "Samastipur": {"latitude": 25.8647, "longitude": 85.7817}, "Rosera": {"latitude": 25.7947, "longitude": 85.9317}, "Dalsinghsarai": {"latitude": 25.6647, "longitude": 85.8317}, "Pusa": {"latitude": 25.9847, "longitude": 85.6717}, "Chapra": {"latitude": 25.7805, "longitude": 84.7477}, "Marhaura": {"latitude": 25.9705, "longitude": 84.8677}, "Amnour": {"latitude": 25.8905, "longitude": 84.9077}, "Sonepur": {"latitude": 25.6905, "longitude": 85.1777}, "Sheikhpura": {"latitude": 25.1391, "longitude": 85.8354}, "Barbigha": {"latitude": 25.2191, "longitude": 85.7354}, "Ariari": {"latitude": 25.0591, "longitude": 85.9554}, "Shekhopur": {"latitude": 25.1391, "longitude": 85.8354}, "Sheohar": {"latitude": 26.5184, "longitude": 85.2959}, "Dumri Katsari": {"latitude": 26.5784, "longitude": 85.1959}, "Piprahi": {"latitude": 26.4684, "longitude": 85.4159}, "Tariyani": {"latitude": 26.5584, "longitude": 85.2359}, "Sitamarhi": {"latitude": 26.5925, "longitude": 85.4806}, "Pupri": {"latitude": 26.4725, "longitude": 85.7006}, "Belsand": {"latitude": 26.4425, "longitude": 85.4006}, "Bathnaha": {"latitude": 26.5925, "longitude": 85.5306}, "Siwan": {"latitude": 26.2195, "longitude": 84.3564}, "Maharajganj": {"latitude": 26.1095, "longitude": 84.5064}, "Mairwa": {"latitude": 26.2295, "longitude": 84.1664}, "Darauli": {"latitude": 26.1595, "longitude": 84.1464}, "Supaul": {"latitude": 26.1260, "longitude": 86.6050}, "Nirmali": {"latitude": 26.3160, "longitude": 86.5850}, "Triveniganj": {"latitude": 26.2160, "longitude": 87.0250}, "Chhatapur": {"latitude": 26.2160, "longitude": 86.9050}, "Hajipur": {"latitude": 25.6851, "longitude": 85.2095}, "Mahua": {"latitude": 25.9651, "longitude": 85.2895}, "Lalganj": {"latitude": 25.8751, "longitude": 85.1695}, "Desri": {"latitude": 25.6051, "longitude": 85.4895}, "Bettiah": {"latitude": 26.8022, "longitude": 84.5025}, "Bagaha": {"latitude": 27.0922, "longitude": 84.0925}, "Narkatiaganj": {"latitude": 26.4322, "longitude": 84.7925}, "Lauriya": {"latitude": 26.9822, "longitude": 84.3125} } # ============================================================================ # FASTAPI APPLICATION # ============================================================================ app = FastAPI( title="Farmer.chat Alert Summary API", description="Agricultural intelligence system with alert-focused MCP pipeline", version="2.0.0" ) # CORS configuration - allow all for development app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ============================================================================ # GLOBAL STATE # ============================================================================ pipeline: Optional[FarmerChatPipeline] = None servers: Dict = {} # ============================================================================ # STARTUP & SHUTDOWN (using lifespan - modern FastAPI pattern) # ============================================================================ from contextlib import asynccontextmanager @asynccontextmanager async def lifespan(app: FastAPI): """Initialize on startup, cleanup on shutdown""" global pipeline, servers # STARTUP print("\n" + "="*60) print("Initializing Farmer.chat Alert System") print("="*60 + "\n") try: # Initialize MCP servers print("Initializing MCP servers...") servers = { "weather": WeatherServer(), "soil": SoilPropertiesServer(), "water": WaterServer(), "elevation": ElevationServer(), "pests": PestsServer() } print("✓ All MCP servers initialized successfully\n") # Initialize pipeline with default location (Patna) default_location = {"latitude": 25.6093, "longitude": 85.1235} pipeline = FarmerChatPipeline( servers=servers, location=default_location ) print("\n" + "="*60) print("✓ Farmer.chat Alert System Ready") print("="*60 + "\n") except Exception as e: print(f"✗ Pipeline initialization failed: {str(e)}") raise yield # Application runs here # SHUTDOWN print("\nShutting down Farmer.chat Alert System...") # Apply lifespan to app app = FastAPI( title="Farmer.chat Alert Summary API", description="Agricultural intelligence system with alert-focused MCP pipeline", version="2.0.0", lifespan=lifespan ) # Re-add CORS after recreating app app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ============================================================================ # API ENDPOINTS # ============================================================================ @app.get("/") async def root(): """Root endpoint with API information""" return { "service": "Farmer.chat Alert Summary Generator", "version": "2.0.0", "status": "operational", "endpoints": { "locations": "/locations", "generate_alert": "/generate-alert", "query": "/query", "health": "/health" } } @app.get("/health") async def health_check(): """Health check endpoint""" if pipeline is None: raise HTTPException(status_code=503, detail="Pipeline not initialized") return { "status": "healthy", "pipeline": "operational", "servers": len(servers), "timestamp": datetime.utcnow().isoformat() } @app.get("/locations") async def get_locations(): """Get all available locations""" return { "districts": BIHAR_DATA, "total_locations": len(LOCATIONS), "coverage": "Bihar, India" } @app.post("/generate-alert", response_model=AlertResponse) async def generate_alert(request: LocationRequest): """ Generate comprehensive alert summary for a location. FIX: Now properly awaits the async pipeline method. """ if pipeline is None: raise HTTPException(status_code=503, detail="Pipeline not initialized") # Lookup location (case-insensitive) location_key = request.location_name.lower().strip() if location_key not in LOCATIONS: # Try partial match matches = [k for k in LOCATIONS.keys() if location_key in k.lower()] if matches: location_key = matches[0] else: raise HTTPException( status_code=404, detail=f"Location '{request.location_name}' not found" ) coordinates = LOCATIONS[location_key] try: # FIX: AWAIT the async method! result = await pipeline.generate_alert( location=coordinates, location_name=request.location_name ) return AlertResponse( location=request.location_name, coordinates=coordinates, district=request.district, alert_summary=result["alert_summary"], timestamp=datetime.utcnow().isoformat() ) except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to generate alert: {str(e)}" ) @app.post("/query") async def process_query(request: QueryRequest): """ Process a specific farmer query. FIX: Now properly awaits the async pipeline method. """ if pipeline is None: raise HTTPException(status_code=503, detail="Pipeline not initialized") # Determine location location = None if request.location_name: location_key = request.location_name.lower().strip() if location_key in LOCATIONS: location = LOCATIONS[location_key] else: matches = [k for k in LOCATIONS.keys() if location_key in k.lower()] if matches: location = LOCATIONS[matches[0]] try: # FIX: AWAIT the async method! result = await pipeline.process_query( query=request.query, location=location ) return { "query": request.query, "response": result["response"], "location": result["location"], "servers_queried": list(result["mcp_results"].keys()), "timestamp": datetime.utcnow().isoformat() } except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to process query: {str(e)}" ) # ============================================================================ # MAIN # ============================================================================ if __name__ == "__main__": import uvicorn port = int(os.getenv("PORT", 7860)) uvicorn.run(app, host="0.0.0.0", port=port)