From fe875a36773526acc82ae4c8906e72cffe9e9641 Mon Sep 17 00:00:00 2001 From: Cizz22 Date: Fri, 19 Dec 2025 14:12:57 +0700 Subject: [PATCH] add yearly simulation run --- src/aeros_simulation/router.py | 105 ++++++++++++++++++++++++++++++++- src/aeros_simulation/schema.py | 5 +- src/aeros_simulation/utils.py | 19 ++++++ 3 files changed, 127 insertions(+), 2 deletions(-) diff --git a/src/aeros_simulation/router.py b/src/aeros_simulation/router.py index 1fd4528..57869d3 100644 --- a/src/aeros_simulation/router.py +++ b/src/aeros_simulation/router.py @@ -9,6 +9,7 @@ from temporalio.client import Client from src.aeros_contribution.service import update_contribution_bulk_mappings from src.aeros_equipment.model import AerosEquipment from src.aeros_simulation.model import AerosSimulationCalcResult, EafContribution, AerosNode +from src.aeros_simulation.utils import date_to_utc, hours_between, year_window_utc from src.auth.service import CurrentUser from src.config import TEMPORAL_URL from src.database.core import CollectorDbSession, DbSession @@ -26,7 +27,8 @@ from .schema import ( SimulationPlotResult, SimulationCalc, SimulationData, - SimulationRankingParameters + SimulationRankingParameters, + YearlySimulationInput ) from .service import ( create_simulation, @@ -107,6 +109,107 @@ async def run_simulations( "status": "success", "message": "Simulation started successfully", } + + +@router.post("/run/yearly", response_model=StandardResponse[str]) +async def run_yearly_simulation( + db_session: DbSession, + yearly_in: YearlySimulationInput, + current_user: CurrentUser +): + year = yearly_in.year + + sim_start, sim_end = year_window_utc(year) + sim_duration_hours = hours_between(sim_start, sim_end) + + # Fetch last overhaul data + last_oh_query = text(""" + SELECT start_date, end_date, duration_oh + FROM public.oh_ms_overhaul + WHERE end_date <= :sim_start + ORDER BY end_date DESC + LIMIT 1; + """) + + next_oh_query = text(""" + SELECT start_date, end_date, duration_oh + FROM public.oh_ms_overhaul + WHERE start_date >= :sim_start + ORDER BY start_date ASC + LIMIT 1; + """) + + last_overhaul = (await db_session.execute( + last_oh_query, + {"sim_start": sim_start.date()} + )).mappings().first() + + next_overhaul = (await db_session.execute( + next_oh_query, + {"sim_start": sim_start.date()} + )).mappings().first() + + + if last_overhaul: + last_oh_dt = date_to_utc(last_overhaul["end_date"]) + offset_hours = max( + int((sim_start - last_oh_dt).total_seconds() // 3600), + 0 + ) + + + if next_overhaul: + next_oh_start = date_to_utc(next_overhaul["start_date"]) + next_oh_duration_hours = next_overhaul["duration_oh"] * 24 + + + overhaul_interval = int( + (next_oh_start - last_oh_dt).total_seconds() // 3600 + ) + overhaul_duration = next_oh_duration_hours + + simulation_input = SimulationInput( + SimDuration=sim_duration_hours, + DurationUnit="UHour", + OffSet=offset_hours, + OverhaulInterval=overhaul_interval, + MaintenanceOutages=0, + SimulationName=f"Simulation {year} LCCA", + IsDefault=False, + OverhaulDuration=overhaul_duration, + AhmJobId=None + ) + + temporal_client = await Client.connect(TEMPORAL_URL) + + simulation = await create_simulation( + db_session=db_session, + simulation_in=simulation_input, + current_user=current_user + ) + + project = await get_project(db_session=db_session) + + sim_data = simulation_input.model_dump() + sim_data["HubCnnId"] = str(simulation.id) + sim_data["SimulationStart"] = sim_start.isoformat() + sim_data["SimulationEnd"] = sim_end.isoformat() + sim_data["HubCnnId"] = str(simulation.id) + sim_data["projectName"] = project.project_name + + await temporal_client.start_workflow( + SimulationWorkflow.run, + sim_data, + id=f"simulation-{simulation.id}", + task_queue="simulation-task-queue", + ) + + return { + "data": str(simulation.id), + "status": "success", + "message": f"Yearly simulation {year} started (UHour mode)", + } + @router.get( "/result/calc/{simulation_id}", diff --git a/src/aeros_simulation/schema.py b/src/aeros_simulation/schema.py index 32a23c2..22b11be 100644 --- a/src/aeros_simulation/schema.py +++ b/src/aeros_simulation/schema.py @@ -119,4 +119,7 @@ class SimulationPagination(Pagination): class AhmMetricInput(BaseModel): target_simulation_id: str - baseline_simulation_id: Optional[str] = Field(None) \ No newline at end of file + baseline_simulation_id: Optional[str] = Field(None) + +class YearlySimulationInput(BaseModel): + year: int \ No newline at end of file diff --git a/src/aeros_simulation/utils.py b/src/aeros_simulation/utils.py index 430e219..efb9d5d 100644 --- a/src/aeros_simulation/utils.py +++ b/src/aeros_simulation/utils.py @@ -1,3 +1,4 @@ +from datetime import datetime import json import logging @@ -7,6 +8,24 @@ from src.logging import setup_logging log = logging.getLogger(__name__) setup_logging(log) + +def date_to_utc(date_val): + return datetime.combine( + date_val, + datetime.min.time(), + ) + + +def hours_between(start: datetime, end: datetime) -> int: + return int((end - start).total_seconds() // 3600) + + +def year_window_utc(year: int): + start = datetime(year, 1, 1, 0, 0, 0) + end = datetime(year + 1, 1, 1, 0, 0, 0) + return start, end + + def calculate_eaf( available_hours: float, period_hours: float,