Spaces:
Sleeping
Sleeping
| import os | |
| import gradio as gr | |
| from fastapi import FastAPI | |
| from gradio.routes import mount_gradio_app | |
| from starlette.middleware import Middleware | |
| from starlette.middleware.base import BaseHTTPMiddleware | |
| from starlette.requests import Request | |
| from starlette.responses import JSONResponse | |
| # ==== FastMCP ==== | |
| from fastmcp import FastMCP | |
| # Import your existing Gradio app and analysis tools | |
| from gradio_app import create_gradio_app | |
| from data_loader import AgriculturalDataLoader | |
| from analysis_tools import AgriculturalAnalyzer | |
| # --------- Config --------- | |
| PORT = int(os.environ.get("PORT", 7860)) | |
| MCP_BEARER = os.getenv("MCP_BEARER", "") # ajouter dans Settings > Variables & secrets si besoin | |
| # Initialize agricultural components | |
| data_loader = AgriculturalDataLoader() | |
| analyzer = AgriculturalAnalyzer(data_loader) | |
| # --------- Auth middleware (optionnel) --------- | |
| class BearerAuthMiddleware(BaseHTTPMiddleware): | |
| def __init__(self, app, token: str | None): | |
| super().__init__(app) | |
| self.token = token | |
| async def dispatch(self, request: Request, call_next): | |
| if self.token: | |
| auth = request.headers.get("authorization", "") | |
| if not auth.startswith("Bearer ") or auth.split(" ", 1)[1] != self.token: | |
| return JSONResponse({"detail": "Unauthorized"}, status_code=401) | |
| return await call_next(request) | |
| # --------- Déclare le serveur MCP --------- | |
| mcp = FastMCP("Agricultural Analysis Tools") | |
| # --------- Outils MCP agricoles --------- | |
| def analyze_weed_pressure(years: list[int] | None = None, plots: list[str] | None = None) -> str: | |
| """Analyze weed pressure trends using IFT herbicide data from Kerguéhennec experimental station.""" | |
| try: | |
| trends = analyzer.analyze_weed_pressure_trends(years=years, plots=plots) | |
| summary_stats = trends['summary'] | |
| result = f"""🌿 ANALYSE DE LA PRESSION ADVENTICES (IFT Herbicides) | |
| 📊 Statistiques pour les années {years or 'toutes'} et parcelles {plots or 'toutes'}: | |
| • IFT moyen: {summary_stats['mean_ift']:.2f} | |
| • Écart-type: {summary_stats['std_ift']:.2f} | |
| • IFT minimum: {summary_stats['min_ift']:.2f} | |
| • IFT maximum: {summary_stats['max_ift']:.2f} | |
| • Total applications: {summary_stats['total_applications']} | |
| • Parcelles analysées: {summary_stats['unique_plots']} | |
| • Cultures analysées: {summary_stats['unique_crops']} | |
| 💡 Interprétation: | |
| • IFT < 1.0: Pression faible (adapté aux cultures sensibles) | |
| • IFT 1.0-2.0: Pression modérée | |
| • IFT > 2.0: Pression élevée""" | |
| return result | |
| except Exception as e: | |
| return f"❌ Erreur lors de l'analyse: {str(e)}" | |
| def predict_future_pressure(target_years: list[int] | None = None, max_ift: float = 1.0) -> str: | |
| """Predict future weed pressure and identify suitable plots for sensitive crops.""" | |
| try: | |
| year_list = target_years or [2025, 2026, 2027] | |
| predictions = analyzer.predict_weed_pressure(target_years=year_list) | |
| model_perf = predictions['model_performance'] | |
| result = f"""🔮 PRÉDICTION DE LA PRESSION ADVENTICES | |
| 🤖 Performance du modèle: | |
| • R² Score: {model_perf['r2']:.3f} | |
| • Erreur quadratique moyenne: {model_perf['mse']:.3f} | |
| 📈 Prédictions par année: | |
| """ | |
| for year in year_list: | |
| if year in predictions['predictions']: | |
| year_pred = predictions['predictions'][year] | |
| result += f"\n📅 {year}:\n" | |
| for _, row in year_pred.iterrows(): | |
| result += f"• {row['plot_name']}: IFT {row['predicted_ift']:.2f} (Risque: {row['risk_level']})\n" | |
| suitable_plots = analyzer.identify_suitable_plots_for_sensitive_crops( | |
| target_years=year_list, max_ift_threshold=max_ift | |
| ) | |
| result += f"\n🌱 Parcelles adaptées aux cultures sensibles (IFT < {max_ift}):\n" | |
| for year, plots in suitable_plots.items(): | |
| if plots: | |
| result += f"• {year}: {', '.join(plots)}\n" | |
| else: | |
| result += f"• {year}: Aucune parcelle adaptée\n" | |
| return result | |
| except Exception as e: | |
| return f"❌ Erreur lors de la prédiction: {str(e)}" | |
| def analyze_crop_rotation() -> str: | |
| """Analyze the impact of crop rotations on weed pressure at Kerguéhennec station.""" | |
| try: | |
| rotation_impact = analyzer.analyze_crop_rotation_impact() | |
| if rotation_impact.empty: | |
| return "📊 Pas assez de données pour analyser les rotations" | |
| result = "🔄 IMPACT DES ROTATIONS CULTURALES\n\n🏆 Meilleures rotations (IFT moyen le plus bas):\n\n" | |
| best_rotations = rotation_impact.head(10) | |
| for i, (_, row) in enumerate(best_rotations.iterrows(), 1): | |
| result += f"{i}. **{row['rotation_type']}**\n" | |
| result += f" • IFT moyen: {row['mean_ift']:.2f}\n" | |
| result += f" • Écart-type: {row['std_ift']:.2f}\n" | |
| result += f" • Observations: {row['count']}\n\n" | |
| result += "💡 Les rotations avec les IFT les plus bas sont généralement plus durables." | |
| return result | |
| except Exception as e: | |
| return f"❌ Erreur lors de l'analyse des rotations: {str(e)}" | |
| def get_dataset_summary() -> str: | |
| """Get a comprehensive summary of the agricultural dataset from Kerguéhennec experimental station.""" | |
| try: | |
| df = data_loader.load_all_files() | |
| if df.empty: | |
| return "❌ Aucune donnée disponible" | |
| summary = f"""📊 RÉSUMÉ DU DATASET AGRICOLE - STATION DE KERGUÉHENNEC | |
| 📈 Statistiques générales: | |
| • Total d'enregistrements: {len(df):,} | |
| • Parcelles uniques: {df['plot_name'].nunique()} | |
| • Types de cultures: {df['crop_type'].nunique()} | |
| • Années couvertes: {', '.join(map(str, sorted(df['year'].unique())))} | |
| • Applications herbicides: {len(df[df['is_herbicide'] == True]):,} | |
| 🌱 Top 5 des cultures: | |
| {df['crop_type'].value_counts().head(5).to_string()} | |
| 📍 Top 5 des parcelles: | |
| {df['plot_name'].value_counts().head(5).to_string()} | |
| 🏢 Source: Station Expérimentale de Kerguéhennec""" | |
| return summary | |
| except Exception as e: | |
| return f"❌ Erreur lors du chargement des données: {str(e)}" | |
| def dataset_resource() -> str: | |
| """Agricultural dataset summary resource.""" | |
| return get_dataset_summary() | |
| # Crée l'app ASGI FastMCP et protège-la éventuellement | |
| mcp_app = mcp.http_app( # cf. doc: http_app() retourne une app ASGI montable | |
| path="/", # endpoint interne de l'app MCP => "/" | |
| custom_middleware=[ | |
| Middleware(BearerAuthMiddleware, token=MCP_BEARER) | |
| ] if MCP_BEARER else [] | |
| ) | |
| # --------- App FastAPI parente (lifespan important) --------- | |
| app = FastAPI(title="Agricultural Analysis - Gradio + FastMCP", lifespan=mcp_app.lifespan) | |
| # Health simple | |
| def health(): | |
| return {"ok": True, "service": "agricultural-mcp-server", "version": "1.0.0"} | |
| # Monte MCP sous /mcp/ (final: /mcp/…) | |
| app.mount("/mcp", mcp_app) | |
| # --------- UI Gradio (utilise ton interface existante) --------- | |
| demo = create_gradio_app() | |
| # Monte Gradio à la racine | |
| app = mount_gradio_app(app, demo, path="/") | |
| # --------- Entrée locale facultative --------- | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=PORT) | |
| # ========= Tests FastMCP (exemples curl) ========= | |
| # Health | |
| # curl -s https://hackathoncra-mcp.hf.space/health | |
| # MCP tools list | |
| # curl -s -X POST https://hackathoncra-mcp.hf.space/mcp/ \ | |
| # -H "Content-Type: application/json" \ | |
| # -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}' | |
| # MCP tool call | |
| # curl -s -X POST https://hackathoncra-mcp.hf.space/mcp/ \ | |
| # -H "Content-Type: application/json" \ | |
| # -d '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "get_dataset_summary", "arguments": {}}}' | |
| # MCP avec Bearer (si MCP_BEARER défini) | |
| # curl -s -X POST https://hackathoncra-mcp.hf.space/mcp/ \ | |
| # -H "Authorization: Bearer VOTRE_TOKEN" \ | |
| # -H "Content-Type: application/json" \ | |
| # -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}' |