""" Endpoints API pour la génération de diagrammes PH. """ from fastapi import APIRouter, HTTPException, status from typing import Dict, Any from app.models.diagram import ( DiagramRequest, DiagramResponse, DiagramError ) from app.services.diagram_generator import DiagramGenerator from app.core.refrigerant_loader import get_refrigerant from collections import OrderedDict import json router = APIRouter(prefix="/diagrams", tags=["diagrams"]) @router.post( "/ph", response_model=DiagramResponse, summary="Générer un diagramme Pression-Enthalpie", description="Génère un diagramme PH avec courbe de saturation et isothermes", status_code=status.HTTP_200_OK ) async def generate_ph_diagram(request: DiagramRequest) -> DiagramResponse: """ Génère un diagramme Pression-Enthalpie. Args: request: Paramètres du diagramme Returns: DiagramResponse avec image et/ou données Raises: HTTPException: Si erreur lors de la génération """ try: # Charger le réfrigérant refrigerant = get_refrigerant(request.refrigerant) if refrigerant is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Réfrigérant '{request.refrigerant}' non disponible" ) # Créer le générateur generator = DiagramGenerator(refrigerant) # Configurer dimensions si PNG if request.format in ["png", "both"]: generator.fig_width = request.width / 100 generator.fig_height = request.height / 100 generator.dpi = request.dpi # Convertir cycle_points en tuples cycle_points_tuples = None if request.cycle_points: cycle_points_tuples = [ (pt["enthalpy"], pt["pressure"]) for pt in request.cycle_points ] # Simple in-memory LRU cache for diagram JSON results (keyed by request body) # Cache only JSON responses to avoid storing large PNG blobs in memory if not hasattr(generate_ph_diagram, "_diagram_cache"): generate_ph_diagram._diagram_cache = OrderedDict() generate_ph_diagram._cache_size = 50 def _make_cache_key(req: DiagramRequest) -> str: # Use a stable JSON string of important fields as cache key def _serialize(v): # Pydantic BaseModel expose model_dump in v2; fall back to dict try: if hasattr(v, 'model_dump'): return v.model_dump() elif hasattr(v, 'dict'): return v.dict() except Exception: pass return v key_obj = { "refrigerant": req.refrigerant, "pressure_range": _serialize(req.pressure_range), "include_isotherms": req.include_isotherms, "cycle_points": _serialize(req.cycle_points), "format": req.format, } return json.dumps(key_obj, sort_keys=True, separators=(",", ":")) cache_key = _make_cache_key(request) # If client requested JSON or both, try cache cached_result = None if request.format in ["json", "both"]: cached_result = generate_ph_diagram._diagram_cache.get(cache_key) if cached_result is not None: # move to end (most recently used) generate_ph_diagram._diagram_cache.move_to_end(cache_key) result = cached_result generation_time = 0.0 else: import time start_time = time.time() result = generator.generate_complete_diagram( cycle_points=cycle_points_tuples, title=request.title, export_format=request.format ) generation_time = (time.time() - start_time) * 1000 # ms # store JSON part in cache if present and format includes json if request.format in ["json", "both"] and "data" in result: # store only the data dictionary to keep cache small cache_entry = {"data": result.get("data")} generate_ph_diagram._diagram_cache[cache_key] = cache_entry # enforce cache size if len(generate_ph_diagram._diagram_cache) > generate_ph_diagram._cache_size: generate_ph_diagram._diagram_cache.popitem(last=False) # Construire la réponse response_data = { "success": True, "metadata": { "generation_time_ms": round(generation_time, 2), "refrigerant": request.refrigerant, "export_format": request.format }, "message": f"Diagramme PH généré pour {request.refrigerant}" } # Ajouter l'image si générée if "image_base64" in result: response_data["image"] = result["image_base64"] response_data["metadata"]["image_format"] = "png" response_data["metadata"]["image_width"] = request.width response_data["metadata"]["image_height"] = request.height # Ajouter les données si générées if "data" in result: response_data["data"] = result["data"] return DiagramResponse(**response_data) except HTTPException: raise except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Paramètres invalides: {str(e)}" ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Erreur génération diagramme: {str(e)}" ) @router.get( "/", summary="Types de diagrammes disponibles", response_model=Dict[str, Any] ) async def list_diagram_types(): """Liste les types de diagrammes disponibles.""" return { "diagram_types": [ { "type": "ph", "name": "Pression-Enthalpie", "description": "Diagramme log(P) vs h avec courbe de saturation et isothermes", "endpoint": "/api/v1/diagrams/ph" } ], "export_formats": ["png", "json", "both"], "supported_refrigerants": [ "R12", "R22", "R32", "R134a", "R290", "R404A", "R410A", "R452A", "R454A", "R454B", "R502", "R507A", "R513A", "R515B", "R744", "R1233zd", "R1234ze" ] }