|
|
|
@ -1,9 +1,12 @@
|
|
|
|
from collections import defaultdict
|
|
|
|
from collections import defaultdict
|
|
|
|
from datetime import datetime
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
import io
|
|
|
|
from typing import Annotated, List, Optional
|
|
|
|
from typing import Annotated, List, Optional
|
|
|
|
from uuid import UUID
|
|
|
|
from uuid import UUID
|
|
|
|
|
|
|
|
import pandas as pd
|
|
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, background, status, Query
|
|
|
|
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, background, status, Query
|
|
|
|
|
|
|
|
from fastapi.responses import StreamingResponse
|
|
|
|
from sqlalchemy import select, text
|
|
|
|
from sqlalchemy import select, text
|
|
|
|
from temporalio.client import Client
|
|
|
|
from temporalio.client import Client
|
|
|
|
from src.aeros_contribution.service import update_contribution_bulk_mappings
|
|
|
|
from src.aeros_contribution.service import update_contribution_bulk_mappings
|
|
|
|
@ -405,6 +408,94 @@ async def get_critical_equipment(db_session:DbSession, simulation_id: str):
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/report/{simulation_id}", response_class=StreamingResponse)
|
|
|
|
|
|
|
|
async def get_simulation_report(db_session: DbSession, simulation_id: str):
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
Generate Excel report for the simulation.
|
|
|
|
|
|
|
|
Sheet 1: Summary result (ead, efor, etc for system)
|
|
|
|
|
|
|
|
Sheet 2: Asset/equipment data (equip name, location tag, sub system, downtime, uptime, failure)
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
if simulation_id == 'default':
|
|
|
|
|
|
|
|
simulation = await get_default_simulation(db_session=db_session)
|
|
|
|
|
|
|
|
if not simulation:
|
|
|
|
|
|
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Default simulation not found")
|
|
|
|
|
|
|
|
simulation_id = simulation.id
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
simulation = await get_simulation_by_id(db_session=db_session, simulation_id=simulation_id)
|
|
|
|
|
|
|
|
if not simulation:
|
|
|
|
|
|
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Simulation not found")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 1. Get system results (Sheet 1)
|
|
|
|
|
|
|
|
# The system result usually has node_name == "- TJB - Unit 3 -"
|
|
|
|
|
|
|
|
system_result = await get_plant_calc_result(db_session=db_session, simulation_id=simulation_id)
|
|
|
|
|
|
|
|
if not system_result:
|
|
|
|
|
|
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="System result not found")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 2. Get asset results (Sheet 2)
|
|
|
|
|
|
|
|
asset_results = await get_simulation_with_calc_result(
|
|
|
|
|
|
|
|
db_session=db_session,
|
|
|
|
|
|
|
|
simulation_id=simulation_id,
|
|
|
|
|
|
|
|
node_type="RegularNode"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 3. Prepare DataFrames
|
|
|
|
|
|
|
|
# Sheet 1: Summary
|
|
|
|
|
|
|
|
summary_data = [{
|
|
|
|
|
|
|
|
"Simulation Name": simulation.simulation_name,
|
|
|
|
|
|
|
|
"System Name": system_result.aeros_node.node_name,
|
|
|
|
|
|
|
|
"EAF (%)": system_result.eaf,
|
|
|
|
|
|
|
|
"EFOR (%)": system_result.efor,
|
|
|
|
|
|
|
|
"EAD (Hours)": system_result.derating_hours,
|
|
|
|
|
|
|
|
"SOF (Hours)": system_result.sof,
|
|
|
|
|
|
|
|
"Total Uptime (Hours)": system_result.total_uptime,
|
|
|
|
|
|
|
|
"Total Downtime (Hours)": system_result.total_downtime,
|
|
|
|
|
|
|
|
}]
|
|
|
|
|
|
|
|
df_summary = pd.DataFrame(summary_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Sheet 2: Equipment Data
|
|
|
|
|
|
|
|
equipment_data = []
|
|
|
|
|
|
|
|
for res in asset_results:
|
|
|
|
|
|
|
|
node = res.aeros_node
|
|
|
|
|
|
|
|
equip_name = node.node_name
|
|
|
|
|
|
|
|
# node.equipment is a relationship to MasterEquipment
|
|
|
|
|
|
|
|
if hasattr(node, "equipment") and node.equipment:
|
|
|
|
|
|
|
|
equip_name = node.equipment.name or node.node_name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
equipment_data.append({
|
|
|
|
|
|
|
|
"Equipment Name": equip_name,
|
|
|
|
|
|
|
|
"Location Tag": node.node_name,
|
|
|
|
|
|
|
|
"Sub System": node.structure_name,
|
|
|
|
|
|
|
|
"Uptime (Hours)": res.total_uptime,
|
|
|
|
|
|
|
|
"Downtime (Hours)": res.total_downtime,
|
|
|
|
|
|
|
|
"Failure (Events)": res.num_cm # num_cm is Corrective Maintenance / Failure
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
df_equipment = pd.DataFrame(equipment_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 4. Create Excel in memory
|
|
|
|
|
|
|
|
output = io.BytesIO()
|
|
|
|
|
|
|
|
# Use xlsxwriter if available, fallback to default
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
with pd.ExcelWriter(output, engine="xlsxwriter") as writer:
|
|
|
|
|
|
|
|
df_summary.to_excel(writer, sheet_name="Summary Result", index=False)
|
|
|
|
|
|
|
|
df_equipment.to_excel(writer, sheet_name="Asset Equipment Data", index=False)
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
|
|
with pd.ExcelWriter(output) as writer:
|
|
|
|
|
|
|
|
df_summary.to_excel(writer, sheet_name="Summary Result", index=False)
|
|
|
|
|
|
|
|
df_equipment.to_excel(writer, sheet_name="Asset Equipment Data", index=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
output.seek(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
filename = f"RBD_MONTHLY_REPORT_{datetime.now().strftime('%Y%m%d')}.xlsx"
|
|
|
|
|
|
|
|
headers = {
|
|
|
|
|
|
|
|
"Content-Disposition": f'attachment; filename="{filename}"'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return StreamingResponse(
|
|
|
|
|
|
|
|
output,
|
|
|
|
|
|
|
|
headers=headers,
|
|
|
|
|
|
|
|
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/custom_parameters", response_model=StandardResponse[list])
|
|
|
|
@router.get("/custom_parameters", response_model=StandardResponse[list])
|
|
|
|
|