From 213fd0617ad7f080b937b64bc4dc8757fc5b0067 Mon Sep 17 00:00:00 2001 From: Cizz22 Date: Mon, 8 Sep 2025 11:12:41 +0700 Subject: [PATCH] add endpoint to get eaf contribution --- src/aeros_equipment/service.py | 3 +- src/aeros_simulation/model.py | 7 ++ src/aeros_simulation/router.py | 96 ++++++++++++++++++- .../simulation_save_service.py | 48 ++++++++-- src/aeros_simulation/utils.py | 51 +++++----- 5 files changed, 168 insertions(+), 37 deletions(-) diff --git a/src/aeros_equipment/service.py b/src/aeros_equipment/service.py index f317932..29bd4c8 100644 --- a/src/aeros_equipment/service.py +++ b/src/aeros_equipment/service.py @@ -376,7 +376,8 @@ async def update_equipment_for_simulation(*, db_session: DbSession, project_name "mttr": eq["cmDisP1"], "distribution": eq["relDisType"], "beta": eq["relDisP1"], - "eta": 0 + "eta": 0, + "parameters": {} } continue diff --git a/src/aeros_simulation/model.py b/src/aeros_simulation/model.py index db0a486..244eeaa 100644 --- a/src/aeros_simulation/model.py +++ b/src/aeros_simulation/model.py @@ -160,3 +160,10 @@ class AerosSimulationPlotResult(Base, DefaultMixin): aeros_simulation = relationship( "AerosSimulation", back_populates="plot_results", lazy="raise" ) + + +class EafContribution(Base, DefaultMixin): + __tablename__ = "rbd_ms_eaf_contribution" + + location_tag = Column(String, nullable=False) + eaf_contribution = Column(Float, nullable=False) \ No newline at end of file diff --git a/src/aeros_simulation/router.py b/src/aeros_simulation/router.py index 2e51ab2..09ddd69 100644 --- a/src/aeros_simulation/router.py +++ b/src/aeros_simulation/router.py @@ -1,9 +1,13 @@ +from collections import defaultdict from datetime import datetime from typing import List, Optional from uuid import UUID from fastapi import APIRouter, BackgroundTasks, HTTPException, background, status, Query +from sqlalchemy import select +from src.aeros_equipment.model import AerosEquipment +from src.aeros_simulation.model import EafContribution from src.auth.service import CurrentUser from src.database.core import DbSession from src.database.service import CommonParameters @@ -33,7 +37,7 @@ from .service import ( get_plant_calc_result ) -from .simulation_save_service import execute_simulation +from .simulation_save_service import calculate_plant_eaf, execute_simulation from src.aeros_equipment.schema import EquipmentWithCustomParameters @@ -76,6 +80,8 @@ async def run_simulations( db_session=db_session, simulation_in=simulation_in ) simulation_id = simulation.id + + #simulation_id = "2e0755bf-8cce-4743-9659-8d9920d556e7" project = await get_project(db_session=db_session) @@ -85,7 +91,7 @@ async def run_simulations( sim_data["projectName"] = project.project_name - ##background_tasks.add_task(execute_simulation, db_session=db_session ,simulation_id=simulation_id, sim_data=sim_data) + # ##background_tasks.add_task(execute_simulation, db_session=db_session ,simulation_id=simulation_id, sim_data=sim_data) results = await update_equipment_for_simulation( db_session=db_session, project_name=project.project_name, schematic_name=simulation_in.SchematicName, custom_input=simulation_in.CustomInput @@ -98,6 +104,8 @@ async def run_simulations( await execute_simulation( db_session=db_session, simulation_id=simulation_id, sim_data=sim_data, is_saved=True, eq_update=results ) + + await calculate_plant_eaf(db_session=db_session, simulation_id=simulation_id) return { "data": str(simulation_id), @@ -208,6 +216,90 @@ async def get_custom_parameters_controller(db_session: DbSession): "status": "success", "message": "Simulation result retrieved successfully", } + +@router.post("/calculate_eaf_contribution", response_model=StandardResponse[dict]) +async def calculate_contribution( + db_session: DbSession, + simulation_in: SimulationInput, + batch_num: int = Query(0, ge=0) +): + """RUN Simulation""" + + #simulation_id = "2e0755bf-8cce-4743-9659-8d9920d556e7" + project = await get_project(db_session=db_session) + main_edh = 43.00797663527534 + + try: + contribution_results = defaultdict() + simulations_eq = select(AerosEquipment) + + eqs = (await db_session.execute(simulations_eq)).scalars().all() + + batch_size = 100 + start_index = batch_num * batch_size + end_index = start_index + batch_size + + if end_index > len(eqs): + end_index = len(eqs) + + eqs = eqs[start_index:end_index] + for eq in eqs: + simulation = await create_simulation( + db_session=db_session, simulation_in=simulation_in + ) + + sim_data = simulation_in.model_dump(exclude={"SimulationName"}) + sim_data["HubCnnId"] = str(simulation.id) + sim_data["projectName"] = project.project_name + + custom_input = { + eq.node_name: { + "mttr": 8760, + "failure_rate": 0.1, + } + } + + results = await update_equipment_for_simulation( + db_session=db_session, project_name=project.project_name, schematic_name=simulation_in.SchematicName, custom_input=custom_input + ) + + # await update_simulation( + # db_session=db_session, simulation_id=simulation_id, data={"reliability": results} + # ) + + await execute_simulation( + db_session=db_session, simulation_id=simulation.id, sim_data=sim_data, is_saved=True, eq_update=results + ) + + eaf, edh = await calculate_plant_eaf(db_session=db_session, simulation_id=simulation.id) + + eaf_contribution = (main_edh - edh)/main_edh if main_edh else 0 + + contribution_results[eq.node_name] = { + "eaf": eaf, + "edh": edh, + "eaf_contribution": eaf_contribution + } + + eaf_conf = EafContribution( + location_tag=eq.node_name, + eaf_contribution=eaf_contribution + ) + + db_session.add(eaf_conf) + await db_session.commit() + await db_session.delete(simulation) + + return { + "data": contribution_results, + "status": "success", + "message": "Simulation created successfully", + } + + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e) + ) # @router.get("/status/{simulation_id}", response_model=StandardResponse[None]) diff --git a/src/aeros_simulation/simulation_save_service.py b/src/aeros_simulation/simulation_save_service.py index 7a9e1cf..2534fb1 100644 --- a/src/aeros_simulation/simulation_save_service.py +++ b/src/aeros_simulation/simulation_save_service.py @@ -12,7 +12,7 @@ import ijson from fastapi import HTTPException, status from src.aeros_simulation.model import AerosSimulationCalcResult, AerosSimulationPlotResult -from src.aeros_simulation.service import get_all_aeros_node, get_or_save_node, get_simulation_by_id +from src.aeros_simulation.service import get_all_aeros_node, get_or_save_node, get_plant_calc_result, get_simulation_by_id, get_simulation_with_plot_result from src.aeros_simulation.utils import calculate_eaf from src.config import AEROS_BASE_URL from src.database.core import DbSession @@ -189,13 +189,13 @@ async def create_calc_result_object( "eta": 0, "beta": 0, "mttr": 0, "parameters": {} }) - eaf, derating_hours = calculate_eaf( - available_hours=result["totalUpTime"], - period_hours=result["totalUpTime"] + result["totalDowntime"], - actual_production=result["production"], - ideal_production=result["idealProduction"], - downtime_hours=result["totalDowntime"] - ) + # eaf, derating_hours = calculate_eaf( + # available_hours=result["totalUpTime"], + # period_hours=result["totalUpTime"] + result["totalDowntime"], + # actual_production=result["production"], + # ideal_production=result["idealProduction"], + # downtime_hours=result["totalDowntime"] + # ) efor = (result["totalDowntime"] / (result["totalDowntime"] + result["totalUpTime"])) * 100 if (result["totalDowntime"] + result["totalUpTime"]) > 0 else 0 @@ -232,11 +232,39 @@ async def create_calc_result_object( stg_output=result["stgOutput"], average_level=result["averageLevel"], potential_production=result["potentialProduction"], - eaf=eaf, + eaf=0, efor=efor, - derating_hours=derating_hours, + derating_hours=0, beta=eq_reliability["beta"] if node_type == "RegularNode" else None, eta=eq_reliability["eta"] if node_type == "RegularNode" else None, mttr=eq_reliability["mttr"] if node_type == "RegularNode" else None, parameters=eq_reliability["parameters"] if node_type == "RegularNode" else None ) + + + +async def calculate_plant_eaf( + db_session: DbSession, simulation_id: UUID +): + """Calculate overall plant EAF from individual node results.""" + plant_calc_data = await get_plant_calc_result( + db_session=db_session, simulation_id=simulation_id + ) + + plant_plot_data = await get_simulation_with_plot_result( + db_session=db_session, simulation_id=simulation_id, node_id="plant" + ) + + eaf, derated_hours = calculate_eaf( + available_hours=plant_calc_data.total_uptime, + period_hours=plant_calc_data.total_uptime + plant_calc_data.total_downtime, + actual_production=plant_calc_data.production, + ideal_production=plant_calc_data.ideal_production, + downtime_hours=plant_calc_data.total_downtime, + plot_data=plant_plot_data.timestamp_outs + ) + + plant_calc_data.eaf = eaf + plant_calc_data.derating_hours = derated_hours + await db_session.commit() + return eaf, derated_hours \ No newline at end of file diff --git a/src/aeros_simulation/utils.py b/src/aeros_simulation/utils.py index 316f467..b5020b3 100644 --- a/src/aeros_simulation/utils.py +++ b/src/aeros_simulation/utils.py @@ -1,5 +1,11 @@ import json +import logging +from src.logging import setup_logging + + +log = logging.getLogger(__name__) +setup_logging(log) def calculate_eaf( available_hours: float, @@ -27,12 +33,12 @@ def calculate_eaf( try: # Calculate lost production max_capacity = 660 - derate_production = ideal_production - actual_production - (max_capacity * downtime_hours) + derate_production = ideal_production - actual_production # Calculate total equivalent derate and outage hours - derate_equivalent_hours = derate_production / max_capacity if max_capacity > 0 else 0 + edh = calculate_equivalent_derate_hours(plot_data, max_flow_rate=max_capacity) # Calculate EAF - effective_available_hours = available_hours - derate_equivalent_hours - return (effective_available_hours / period_hours) * 100 if period_hours > 0 else 0, derate_equivalent_hours + effective_available_hours = available_hours - edh + return (effective_available_hours / period_hours) * 100 if period_hours > 0 else 0, edh except Exception as e: print("Error calculating EAF:", e) raise @@ -61,33 +67,30 @@ def calculate_eaf( -def calculate_derating(data_list, max_flow_rate: float = 660) -> float: - """ - Calculate total time when flow rate is below maximum. +def calculate_equivalent_derate_hours(data_list, max_flow_rate: float = 660) -> float: + """ + Calculate Equivalent Forced Derated Hours (EFDH). - Method 2: Time intervals AFTER each measurement point. - This assumes each data point represents the start of a period with that flow rate. + Each data point represents the start of a period with that flow rate, + valid until the next cumulativeTime. """ - # Sort data by cumulative time to ensure proper order - sorted_data = sorted(data_list, key=lambda x: x['cumulativeTime']) - - total_time_below_max = 0.0 + sorted_data = sorted(data_list, key=lambda x: x['cumulativeTime']) + total_equivalent_derate_hours = 0.0 - print("=== Method 2: Time intervals AFTER each measurement ===") + for i in range(len(sorted_data) - 1): + current = sorted_data[i] + next_ = sorted_data[i + 1] - for i in range(len(sorted_data) - 1): - current_data = sorted_data[i] - next_data = sorted_data[i + 1] + time_interval = next_['cumulativeTime'] - current['cumulativeTime'] + derating = max_flow_rate - current['flowRate'] + - # If current flow rate is below max, add this time interval - if current_data['flowRate'] < max_flow_rate and current_data['flowRate'] != 0: - # Time interval until next measurement - time_interval = next_data['cumulativeTime'] - current_data['cumulativeTime'] + if derating > 0 and derating < max_flow_rate: # Only count when capacity is reduced + log.info(f"Time Interval: {time_interval}, Derating: {derating}, Max Flow Rate: {max_flow_rate}") + total_equivalent_derate_hours += (time_interval * derating / max_flow_rate) - total_time_below_max += time_interval - print(f"Derating hours: {time_interval:.2f}") + return total_equivalent_derate_hours - return total_time_below_max def stream_large_array(filepath, key):