diff --git a/.env.example b/.env.example index dfc7703..b3948ba 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,8 @@ -BACKEND_PORT=8000 -CORS_ORIGINS=http://localhost:5173,http://localhost +BACKEND_PORT=8060 +CORS_ORIGINS=http://localhost:5173,http://localhost:8061 DATABASE_URL=sqlite:////data/jardin.db UPLOAD_DIR=/data/uploads -VITE_API_URL=http://localhost:8000 +VITE_API_URL=http://localhost:8060 +PLANTNET_API_KEY=2b1088cHCJ4c7Cn2Vqq67xfve +AI_SERVICE_URL=http://ai-service:8070 +REDIS_URL=redis://redis:6379 diff --git a/backend/app/services/plantnet.py b/backend/app/services/plantnet.py new file mode 100644 index 0000000..1a3edf1 --- /dev/null +++ b/backend/app/services/plantnet.py @@ -0,0 +1,35 @@ +import os +from typing import List + +import httpx + +PLANTNET_KEY = os.environ.get("PLANTNET_API_KEY", "2b1088cHCJ4c7Cn2Vqq67xfve") +PLANTNET_URL = "https://my-api.plantnet.org/v2/identify/all" + + +async def identify(image_bytes: bytes, filename: str = "photo.jpg") -> List[dict]: + """Appelle PlantNet et retourne les 3 meilleures identifications.""" + try: + async with httpx.AsyncClient(timeout=10.0) as client: + resp = await client.post( + PLANTNET_URL, + params={"api-key": PLANTNET_KEY, "nb-results": 3}, + files={"images": (filename, image_bytes, "image/jpeg")}, + data={"organs": ["auto"]}, + ) + resp.raise_for_status() + data = resp.json() + except Exception: + return [] + + results = [] + for r in data.get("results", [])[:3]: + species = r.get("species", {}) + common_names = species.get("commonNames", []) + results.append({ + "species": species.get("scientificNameWithoutAuthor", ""), + "common_name": common_names[0] if common_names else "", + "confidence": round(r.get("score", 0.0), 3), + "image_url": (r.get("images", [{}]) or [{}])[0].get("url", {}).get("m", ""), + }) + return results diff --git a/backend/app/services/yolo_service.py b/backend/app/services/yolo_service.py new file mode 100644 index 0000000..f0164e0 --- /dev/null +++ b/backend/app/services/yolo_service.py @@ -0,0 +1,45 @@ +import os +from typing import List + +import httpx + +AI_SERVICE_URL = os.environ.get("AI_SERVICE_URL", "http://localhost:8070") + +# Mapping class_name YOLO → nom commun français (partiel) +_NOMS_FR = { + "Tomato___healthy": "Tomate (saine)", + "Tomato___Early_blight": "Tomate (mildiou précoce)", + "Tomato___Late_blight": "Tomate (mildiou tardif)", + "Pepper__bell___healthy": "Poivron (sain)", + "Apple___healthy": "Pommier (sain)", + "Potato___healthy": "Pomme de terre (saine)", + "Grape___healthy": "Vigne (saine)", + "Corn_(maize)___healthy": "Maïs (sain)", + "Strawberry___healthy": "Fraisier (sain)", + "Peach___healthy": "Pêcher (sain)", +} + + +async def identify(image_bytes: bytes) -> List[dict]: + """Appelle l'ai-service interne et retourne les détections YOLO.""" + try: + async with httpx.AsyncClient(timeout=30.0) as client: + resp = await client.post( + f"{AI_SERVICE_URL}/detect", + files={"file": ("photo.jpg", image_bytes, "image/jpeg")}, + ) + resp.raise_for_status() + data = resp.json() + except Exception: + return [] + + results = [] + for det in data[:3]: + cls = det.get("class_name", "") + results.append({ + "species": cls.replace("___", " — ").replace("_", " "), + "common_name": _NOMS_FR.get(cls, cls.split("___")[0].replace("_", " ")), + "confidence": det.get("confidence", 0.0), + "image_url": "", + }) + return results