feat: add endpoint to generate and download simulation reports in Excel format

main
Cizz22 3 months ago
parent 91b2b92ae3
commit 59a2001a21

29
poetry.lock generated

@ -565,6 +565,18 @@ files = [
dnspython = ">=2.0.0" dnspython = ">=2.0.0"
idna = ">=2.0.0" idna = ">=2.0.0"
[[package]]
name = "et-xmlfile"
version = "2.0.0"
description = "An implementation of lxml.xmlfile for the standard library"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"},
{file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"},
]
[[package]] [[package]]
name = "factory-boy" name = "factory-boy"
version = "3.3.3" version = "3.3.3"
@ -1781,6 +1793,21 @@ files = [
{file = "numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae"}, {file = "numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae"},
] ]
[[package]]
name = "openpyxl"
version = "3.1.5"
description = "A Python library to read/write Excel 2010 xlsx/xlsm files"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"},
{file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"},
]
[package.dependencies]
et-xmlfile = "*"
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "26.0" version = "26.0"
@ -3649,4 +3676,4 @@ propcache = ">=0.2.1"
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = "^3.11" python-versions = "^3.11"
content-hash = "b4e773fcdabafe5f4a3af6c5e80afb34756e1ef321b0ef0b8b7580b24c575434" content-hash = "6d755b7ed4092cd5d467c6abfca033fbb0dc0907babb717a3b42526054d64f88"

@ -36,6 +36,7 @@ redis = "^7.1.0"
clamd = "^1.0.2" clamd = "^1.0.2"
licaeros = "^0.1.7" licaeros = "^0.1.7"
hvac = "^2.4.0" hvac = "^2.4.0"
openpyxl = "^3.1.5"

@ -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
@ -399,12 +402,100 @@ async def get_critical_equipment(db_session:DbSession, simulation_id: str):
equipments = data.scalars().all() equipments = data.scalars().all()
return { return {
"data": equipments, "data": equipments,
"status": "success", "status": "success",
"message": "Success", "message": "Success",
} }
@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])

Loading…
Cancel
Save