187 lines
6.6 KiB
Python
187 lines
6.6 KiB
Python
"""
|
|
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"
|
|
]
|
|
} |