Spaces:
Sleeping
Sleeping
File size: 8,334 Bytes
7ca901a a764c8f a507ec3 53bf046 7ca901a a764c8f a507ec3 7ca901a a507ec3 7ca901a a764c8f 53bf046 a764c8f 53bf046 a507ec3 53bf046 a764c8f a507ec3 53bf046 a764c8f a507ec3 53bf046 a507ec3 eb1a639 a507ec3 eb1a639 a507ec3 eb1a639 a507ec3 eb1a639 a507ec3 eb1a639 a507ec3 53bf046 a507ec3 a764c8f a507ec3 a764c8f a507ec3 53bf046 a764c8f a507ec3 53bf046 a764c8f 7ca901a 53bf046 a764c8f 53bf046 a507ec3 a764c8f eb1a639 a764c8f 53bf046 a764c8f 53bf046 a764c8f 53bf046 a764c8f 53bf046 a764c8f 53bf046 a764c8f |
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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
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 ---------
@mcp.tool
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)}"
@mcp.tool
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)}"
@mcp.tool
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)}"
@mcp.tool
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)}"
@mcp.resource("agricultural://dataset/summary")
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
@app.get("/health")
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": {}}' |