You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
13 KiB
Python
264 lines
13 KiB
Python
import re
|
|
|
|
with open('src/calculation_time_constrains/service.py', 'r') as f:
|
|
content = f.read()
|
|
|
|
# Replacement 1: run_simulation_with_spareparts
|
|
replacement_1 = """ finally:
|
|
await optimum_oh_model._close_session()
|
|
|
|
# Re-fetch calculation_data with equipment_results to ensure they are loaded
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import selectinload
|
|
calculation_query = await db_session.execute(
|
|
select(CalculationData)
|
|
.options(selectinload(CalculationData.equipment_results), selectinload(CalculationData.parameter))
|
|
.where(CalculationData.id == calculation.id)
|
|
)
|
|
scope_calculation = calculation_query.scalar_one_or_none()
|
|
|
|
data_num = scope_calculation.max_interval
|
|
all_equipment = scope_calculation.equipment_results
|
|
included_equipment = [eq for eq in all_equipment if eq.is_included]
|
|
|
|
calculation_results = []
|
|
fleet_statistics = {
|
|
'total_equipment': len(all_equipment),
|
|
'included_equipment': len(included_equipment),
|
|
'excluded_equipment': len(all_equipment) - len(included_equipment),
|
|
'equipment_with_sparepart_constraints': 0,
|
|
'total_procurement_items': 0,
|
|
'critical_procurement_items': 0,
|
|
'total_spareparts': 745
|
|
}
|
|
|
|
avg_failure_cost = sum((eq.material_cost or 0) + (3 * 111000 * 3) for eq in all_equipment) / len(all_equipment) if all_equipment else 0
|
|
|
|
rbd_marginal_fails = [0] * data_num
|
|
try:
|
|
if scope_calculation.rbd_simulation_id:
|
|
from src.config import RBD_SERVICE_API
|
|
import httpx
|
|
plant_result_url = f"{RBD_SERVICE_API}/aeros/simulation/result/calc/{scope_calculation.rbd_simulation_id}/plant"
|
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
response = await client.get(
|
|
plant_result_url,
|
|
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
|
)
|
|
if response.status_code == 200:
|
|
plant_data = response.json().get("data", {})
|
|
timestamp_outs = plant_data.get("timestamp_outs", [])
|
|
if timestamp_outs:
|
|
from src.calculation_time_constrains.utils import create_time_series_data, calculate_failures_per_month
|
|
hourly_data = create_time_series_data(timestamp_outs, max_hours=data_num * 720)
|
|
cumulative_rbd_fails = calculate_failures_per_month(hourly_data)
|
|
rbd_fails_map = {m['month']: m['failures'] for m in cumulative_rbd_fails}
|
|
prev_fail = 0
|
|
for m in range(1, data_num + 1):
|
|
curr_fail = rbd_fails_map.get(m, prev_fail)
|
|
rbd_marginal_fails[m-1] = curr_fail - prev_fail
|
|
prev_fail = curr_fail
|
|
except Exception as e:
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
logger.warning(f"Failed to fetch plant simulation: {e}")
|
|
|
|
cumulative_plant_failures = 0
|
|
import numpy as np
|
|
from .schema import CalculationResultsRead
|
|
for month_index in range(data_num):
|
|
historical_marginal_fail = 0
|
|
for eq in all_equipment:
|
|
if eq.is_actual and month_index < len(eq.is_actual) and eq.is_actual[month_index]:
|
|
curr_fail = eq.daily_failures[month_index] if month_index < len(eq.daily_failures) else 0
|
|
prev_fail = eq.daily_failures[month_index-1] if month_index > 0 and (month_index - 1) < len(eq.daily_failures) else 0
|
|
historical_marginal_fail += max(0, curr_fail - prev_fail)
|
|
|
|
marginal_fail = rbd_marginal_fails[month_index] + historical_marginal_fail
|
|
cumulative_plant_failures += marginal_fail
|
|
|
|
month_result = {
|
|
"overhaul_cost": 0.0,
|
|
"corrective_cost": 0.0,
|
|
"procurement_cost": 0.0,
|
|
"num_failures": cumulative_plant_failures,
|
|
"day": month_index + 1,
|
|
"month": month_index + 1,
|
|
"procurement_details": {},
|
|
"sparepart_summary": {
|
|
"total_procurement_cost": 0.0,
|
|
"equipment_requiring_procurement": 0,
|
|
"critical_shortages": 0,
|
|
"existing_orders_value": 0.0,
|
|
"new_orders_required": 0,
|
|
"urgent_procurements": 0
|
|
}
|
|
}
|
|
|
|
equipment_requiring_procurement = 0
|
|
total_existing_orders_value = 0.0
|
|
total_new_orders_value = 0.0
|
|
critical_shortages = 0
|
|
urgent_procurements = 0
|
|
|
|
for eq in all_equipment:
|
|
if month_index >= len(eq.procurement_details):
|
|
continue
|
|
procurement_detail = eq.procurement_details[month_index]
|
|
if (procurement_detail and isinstance(procurement_detail, dict) and procurement_detail.get("procurement_needed")):
|
|
equipment_requiring_procurement += 1
|
|
pr_po_summary = procurement_detail.get("pr_po_summary", {})
|
|
existing_orders_value = pr_po_summary.get("total_existing_value", 0)
|
|
total_existing_orders_value += existing_orders_value
|
|
new_orders_value = pr_po_summary.get("total_new_orders_value", 0)
|
|
total_new_orders_value += new_orders_value
|
|
critical_missing = procurement_detail.get("critical_missing_parts", 0)
|
|
if critical_missing > 0:
|
|
critical_shortages += 1
|
|
recommendations = procurement_detail.get("recommendations", [])
|
|
urgent_items = [r for r in recommendations if r.get("priority") == "CRITICAL"]
|
|
if urgent_items:
|
|
urgent_procurements += 1
|
|
is_included_eq = False if eq.is_initial else eq.is_included
|
|
month_result["procurement_details"][eq.location_tag] = {
|
|
"is_included": is_included_eq,
|
|
"location_tag": eq.location_tag,
|
|
"details": procurement_detail.get("procurement_needed", []),
|
|
"detailed_message": procurement_detail.get("detailed_message", ""),
|
|
"pr_po_summary": pr_po_summary,
|
|
"recommendations": recommendations,
|
|
"sparepart_available": procurement_detail.get("sparepart_available", True),
|
|
"can_proceed": procurement_detail.get("can_proceed_with_delays", True),
|
|
"critical_missing_parts": critical_missing,
|
|
"existing_orders_value": existing_orders_value,
|
|
"new_orders_value": new_orders_value
|
|
}
|
|
if eq.is_included:
|
|
if (month_index < len(eq.overhaul_costs) and month_index < len(eq.procurement_costs)):
|
|
month_result["overhaul_cost"] += float(eq.overhaul_costs[month_index])
|
|
month_result["procurement_cost"] += float(eq.procurement_costs[month_index])
|
|
|
|
month_result["corrective_cost"] = (cumulative_plant_failures * avg_failure_cost) / (month_index + 1)
|
|
month_result["sparepart_summary"].update({
|
|
"total_procurement_cost": month_result["procurement_cost"],
|
|
"equipment_requiring_procurement": equipment_requiring_procurement,
|
|
"critical_shortages": critical_shortages,
|
|
"existing_orders_value": total_existing_orders_value,
|
|
"new_orders_required": len([eq for eq in all_equipment if month_index < len(eq.procurement_details) and eq.procurement_details[month_index] and eq.procurement_details[month_index].get("procurement_needed")]),
|
|
"urgent_procurements": urgent_procurements
|
|
})
|
|
month_result["total_cost"] = month_result["corrective_cost"] + month_result["overhaul_cost"] + month_result["procurement_cost"]
|
|
calculation_results.append(month_result)
|
|
|
|
optimum_day = np.argmin([month["total_cost"] for month in calculation_results])
|
|
scope_calculation.optimum_oh_day = int(optimum_day)
|
|
|
|
fleet_statistics['equipment_with_sparepart_constraints'] = len([
|
|
eq for eq in all_equipment if any(detail and detail.get("procurement_needed") for detail in eq.procurement_details if detail)
|
|
])
|
|
fleet_statistics['total_procurement_items'] = sum([
|
|
len(detail.get("procurement_needed", [])) for eq in all_equipment for detail in eq.procurement_details if detail and isinstance(detail, dict)
|
|
])
|
|
|
|
analysis_metadata = {
|
|
"planned_month": (scope.start_date.year - prev_oh_scope.end_date.year) * 12 + (scope.start_date.month - prev_oh_scope.end_date.month) if prev_oh_scope and scope else 0,
|
|
"total_fleet_procurement_cost": sum([eq.procurement_costs[int(scope_calculation.optimum_oh_day)] for eq in all_equipment if eq.procurement_costs]),
|
|
"last_oh_date": prev_oh_scope.end_date.isoformat() if prev_oh_scope else None,
|
|
"next_oh_date": scope.start_date.isoformat() if scope else None,
|
|
"optimal_stat": None
|
|
}
|
|
|
|
calc_results_read = [CalculationResultsRead(**r) for r in calculation_results]
|
|
optimal_analysis = _analyze_optimal_timing(
|
|
calc_results_read, scope_calculation.optimum_oh_day, prev_oh_scope, scope
|
|
)
|
|
|
|
scope_calculation.plant_results = calculation_results
|
|
scope_calculation.fleet_statistics = fleet_statistics
|
|
scope_calculation.analysis_metadata = analysis_metadata
|
|
scope_calculation.optimum_analysis = optimal_analysis
|
|
|
|
await db_session.commit()
|
|
|
|
return {
|
|
"id": calculation.id,
|
|
"optimum": optimal_analysis
|
|
}"""
|
|
|
|
pattern_1 = re.compile(r" finally:\n await optimum_oh_model\._close_session\(\).*?return \{\n \"id\": calculation_data\.id,\n \"optimum\": stats\n \}", re.DOTALL)
|
|
if pattern_1.search(content):
|
|
content = pattern_1.sub(replacement_1, content)
|
|
else:
|
|
print("Could not find Replacement 1 target")
|
|
|
|
# Replacement 2: get_calculation_result
|
|
replacement_2 = """async def get_calculation_result(db_session: DbSession, calculation_id: str, token, include_risk_cost):
|
|
\"\"\"
|
|
Get calculation results from DB, returning pre-calculated plant and equipment results.
|
|
\"\"\"
|
|
try:
|
|
# Get calculation data with equipment results
|
|
calculation_query = await db_session.execute(
|
|
select(CalculationData)
|
|
.options(selectinload(CalculationData.equipment_results))
|
|
.where(CalculationData.id == calculation_id)
|
|
)
|
|
scope_calculation = calculation_query.scalar_one_or_none()
|
|
|
|
if not scope_calculation:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Calculation with id {calculation_id} does not exist.",
|
|
)
|
|
|
|
scope_overhaul = await get_scope(
|
|
db_session=db_session,
|
|
overhaul_session_id=scope_calculation.overhaul_session_id
|
|
)
|
|
if not scope_overhaul:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Overhaul scope for session {scope_calculation.overhaul_session_id} does not exist.",
|
|
)
|
|
|
|
# Parse pre-calculated plant results
|
|
plant_results_raw = scope_calculation.plant_results or []
|
|
calculation_results = [CalculationResultsRead(**r) for r in plant_results_raw]
|
|
|
|
# Return comprehensive result
|
|
return CalculationTimeConstrainsRead(
|
|
id=scope_calculation.id,
|
|
reference=scope_calculation.overhaul_session_id,
|
|
scope=scope_overhaul.maintenance_type.name,
|
|
results=calculation_results,
|
|
optimum_oh=scope_calculation.optimum_oh_day,
|
|
optimum_oh_month=scope_calculation.optimum_oh_day + 1 if scope_calculation.optimum_oh_day is not None else None,
|
|
equipment_results=scope_calculation.equipment_results,
|
|
fleet_statistics=scope_calculation.fleet_statistics or {},
|
|
optimal_analysis=scope_calculation.optimum_analysis or {},
|
|
analysis_metadata=scope_calculation.analysis_metadata or {}
|
|
)
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
logger.error(f"Error in get_calculation_result for calculation_id {calculation_id}: {str(e)}")
|
|
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Internal error processing calculation results: {str(e)}",
|
|
)"""
|
|
|
|
pattern_2 = re.compile(r"async def get_calculation_result\(db_session: DbSession, calculation_id: str, token, include_risk_cost\):.*?raise HTTPException\(\n status_code=status\.HTTP_500_INTERNAL_SERVER_ERROR,\n detail=f\"Internal error processing calculation results: \{str\(e\)\}\",\n \)", re.DOTALL)
|
|
if pattern_2.search(content):
|
|
content = pattern_2.sub(replacement_2, content)
|
|
else:
|
|
print("Could not find Replacement 2 target")
|
|
|
|
with open('src/calculation_time_constrains/service.py', 'w') as f:
|
|
f.write(content)
|
|
|
|
print("Patch applied.")
|