|
|
|
@ -1,20 +1,31 @@
|
|
|
|
from typing import List, Optional, Tuple
|
|
|
|
from typing import List
|
|
|
|
|
|
|
|
from typing import Optional
|
|
|
|
|
|
|
|
from typing import Tuple
|
|
|
|
from uuid import UUID
|
|
|
|
from uuid import UUID
|
|
|
|
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
import numpy as np
|
|
|
|
from sqlalchemy import and_, func, select
|
|
|
|
from fastapi import HTTPException
|
|
|
|
from sqlalchemy.engine import result
|
|
|
|
from fastapi import status
|
|
|
|
|
|
|
|
from sqlalchemy import and_
|
|
|
|
|
|
|
|
from sqlalchemy import case
|
|
|
|
|
|
|
|
from sqlalchemy import func
|
|
|
|
|
|
|
|
from sqlalchemy import select
|
|
|
|
|
|
|
|
from sqlalchemy import update
|
|
|
|
from sqlalchemy.orm import joinedload
|
|
|
|
from sqlalchemy.orm import joinedload
|
|
|
|
|
|
|
|
|
|
|
|
from src.database.core import DbSession
|
|
|
|
from src.database.core import DbSession
|
|
|
|
from src.overhaul_activity.service import get_all_by_session_id
|
|
|
|
from src.overhaul_activity.service import get_all_by_session_id
|
|
|
|
from src.scope_equipment.model import ScopeEquipment
|
|
|
|
|
|
|
|
from src.workorder.model import MasterWorkOrder
|
|
|
|
|
|
|
|
from .schema import CalculationTimeConstrainsParametersCreate, CalculationTimeConstrainsRead, EquipmentResult, OptimumResult
|
|
|
|
|
|
|
|
from .model import CalculationParam, OverhaulReferenceType, CalculationData, CalculationResult, CalculationEquipmentResult
|
|
|
|
|
|
|
|
from fastapi import HTTPException, status
|
|
|
|
|
|
|
|
from src.overhaul_scope.service import get_by_scope_name, get
|
|
|
|
|
|
|
|
from src.scope_equipment.service import get_by_assetnum
|
|
|
|
|
|
|
|
from src.overhaul_scope.service import get as get_scope
|
|
|
|
from src.overhaul_scope.service import get as get_scope
|
|
|
|
|
|
|
|
from src.workorder.model import MasterWorkOrder
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from .model import CalculationData
|
|
|
|
|
|
|
|
from .model import CalculationEquipmentResult
|
|
|
|
|
|
|
|
from .model import CalculationResult
|
|
|
|
from .schema import CalculationResultsRead
|
|
|
|
from .schema import CalculationResultsRead
|
|
|
|
|
|
|
|
from .schema import CalculationTimeConstrainsParametersCreate
|
|
|
|
|
|
|
|
from .schema import CalculationTimeConstrainsRead
|
|
|
|
|
|
|
|
from .schema import OptimumResult
|
|
|
|
|
|
|
|
from .schema import CalculationSelectedEquipmentUpdate
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_overhaul_cost_by_time_chart(overhaul_cost: float, days: int,numEquipments:int ,decay_base: float = 1.01) -> np.ndarray:
|
|
|
|
def get_overhaul_cost_by_time_chart(overhaul_cost: float, days: int,numEquipments:int ,decay_base: float = 1.01) -> np.ndarray:
|
|
|
|
@ -22,7 +33,7 @@ def get_overhaul_cost_by_time_chart(overhaul_cost: float, days: int,numEquipment
|
|
|
|
raise ValueError("Overhaul cost cannot be negative")
|
|
|
|
raise ValueError("Overhaul cost cannot be negative")
|
|
|
|
if days <= 0:
|
|
|
|
if days <= 0:
|
|
|
|
raise ValueError("Days must be positive")
|
|
|
|
raise ValueError("Days must be positive")
|
|
|
|
|
|
|
|
|
|
|
|
exponents = np.arange(0, days)
|
|
|
|
exponents = np.arange(0, days)
|
|
|
|
cost_per_equipment = overhaul_cost / numEquipments
|
|
|
|
cost_per_equipment = overhaul_cost / numEquipments
|
|
|
|
# Using a slower decay base to spread the budget depletion over more days
|
|
|
|
# Using a slower decay base to spread the budget depletion over more days
|
|
|
|
@ -36,14 +47,14 @@ def get_overhaul_cost_by_time_chart(overhaul_cost: float, days: int,numEquipment
|
|
|
|
# raise ValueError("Overhaul cost cannot be negative")
|
|
|
|
# raise ValueError("Overhaul cost cannot be negative")
|
|
|
|
# if days <= 0:
|
|
|
|
# if days <= 0:
|
|
|
|
# raise ValueError("Days must be positive")
|
|
|
|
# raise ValueError("Days must be positive")
|
|
|
|
|
|
|
|
|
|
|
|
# exponents = np.arange(0, days)
|
|
|
|
# exponents = np.arange(0, days)
|
|
|
|
# cost_per_equipment = overhaul_cost / numEquipments
|
|
|
|
# cost_per_equipment = overhaul_cost / numEquipments
|
|
|
|
|
|
|
|
|
|
|
|
# # Introduce randomness by multiplying with a random factor
|
|
|
|
# # Introduce randomness by multiplying with a random factor
|
|
|
|
# random_factors = np.random.normal(1.0, 0.1, numEquipments) # Mean 1.0, Std Dev 0.1
|
|
|
|
# random_factors = np.random.normal(1.0, 0.1, numEquipments) # Mean 1.0, Std Dev 0.1
|
|
|
|
# results = np.array([cost_per_equipment * factor / (decay_base ** exponents) for factor in random_factors])
|
|
|
|
# results = np.array([cost_per_equipment * factor / (decay_base ** exponents) for factor in random_factors])
|
|
|
|
|
|
|
|
|
|
|
|
# results = np.where(np.isfinite(results), results, 0)
|
|
|
|
# results = np.where(np.isfinite(results), results, 0)
|
|
|
|
# return results
|
|
|
|
# return results
|
|
|
|
|
|
|
|
|
|
|
|
@ -57,7 +68,7 @@ def get_corrective_cost_time_chart(material_cost: float, service_cost: float, da
|
|
|
|
|
|
|
|
|
|
|
|
# Calculate daily failure rate using sigmoid function
|
|
|
|
# Calculate daily failure rate using sigmoid function
|
|
|
|
daily_failure_rate = base_rate / (1 + np.exp(-acceleration * (day_points - grace_period)/days))
|
|
|
|
daily_failure_rate = base_rate / (1 + np.exp(-acceleration * (day_points - grace_period)/days))
|
|
|
|
|
|
|
|
|
|
|
|
# Calculate cumulative failures
|
|
|
|
# Calculate cumulative failures
|
|
|
|
failure_counts = np.cumsum(daily_failure_rate)
|
|
|
|
failure_counts = np.cumsum(daily_failure_rate)
|
|
|
|
|
|
|
|
|
|
|
|
@ -121,14 +132,14 @@ async def get_calculation_result(db_session: DbSession, calculation_id: str):
|
|
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
|
|
detail="A data with this id does not exist.",
|
|
|
|
detail="A data with this id does not exist.",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
scope_overhaul = await get_scope(db_session=db_session, overhaul_session_id=scope_calculation.overhaul_session_id)
|
|
|
|
scope_overhaul = await get_scope(db_session=db_session, overhaul_session_id=scope_calculation.overhaul_session_id)
|
|
|
|
if not scope_overhaul:
|
|
|
|
if not scope_overhaul:
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
|
|
detail="A data with this id does not exist.",
|
|
|
|
detail="A data with this id does not exist.",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
calculation_results = []
|
|
|
|
calculation_results = []
|
|
|
|
for i in range(days):
|
|
|
|
for i in range(days):
|
|
|
|
@ -138,15 +149,15 @@ async def get_calculation_result(db_session: DbSession, calculation_id: str):
|
|
|
|
"num_failures": 0,
|
|
|
|
"num_failures": 0,
|
|
|
|
"day": i + 1
|
|
|
|
"day": i + 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for eq in scope_calculation.equipment_results:
|
|
|
|
for eq in scope_calculation.equipment_results:
|
|
|
|
if not eq.is_included:
|
|
|
|
if not eq.is_included:
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
result["corrective_cost"] += float(eq.corrective_costs[i])
|
|
|
|
result["corrective_cost"] += float(eq.corrective_costs[i])
|
|
|
|
result["overhaul_cost"] += float(eq.overhaul_costs[i])
|
|
|
|
result["overhaul_cost"] += float(eq.overhaul_costs[i])
|
|
|
|
result["num_failures"] += int(eq.daily_failures[i])
|
|
|
|
result["num_failures"] += int(eq.daily_failures[i])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
calculation_results.append(CalculationResultsRead(**result))
|
|
|
|
calculation_results.append(CalculationResultsRead(**result))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -167,8 +178,8 @@ async def get_calculation_data_by_id(db_session: DbSession, calculation_id) -> C
|
|
|
|
).options(
|
|
|
|
).options(
|
|
|
|
joinedload(CalculationData.equipment_results), joinedload(CalculationData.parameter)
|
|
|
|
joinedload(CalculationData.equipment_results), joinedload(CalculationData.parameter)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result = await db_session.execute(stmt)
|
|
|
|
result = await db_session.execute(stmt)
|
|
|
|
return result.unique().scalar()
|
|
|
|
return result.unique().scalar()
|
|
|
|
|
|
|
|
|
|
|
|
@ -177,7 +188,7 @@ async def get_calculation_data_by_id(db_session: DbSession, calculation_id) -> C
|
|
|
|
# days = 360
|
|
|
|
# days = 360
|
|
|
|
# calculation = await get_calculation_data_by_id(db_session=db_session, calculation_id=calculation_id)
|
|
|
|
# calculation = await get_calculation_data_by_id(db_session=db_session, calculation_id=calculation_id)
|
|
|
|
# # reference = await get_by_assetnum(db_session=db_session, assetnum=calculation.reference_id) if calculation.overhaul_reference_type == OverhaulReferenceType.ASSET else await get(db_session=db_session, scope_id=calculation.reference_id)
|
|
|
|
# # reference = await get_by_assetnum(db_session=db_session, assetnum=calculation.reference_id) if calculation.overhaul_reference_type == OverhaulReferenceType.ASSET else await get(db_session=db_session, scope_id=calculation.reference_id)
|
|
|
|
|
|
|
|
|
|
|
|
# # Multiple Eequipment
|
|
|
|
# # Multiple Eequipment
|
|
|
|
# equipments_scope = get_all_by_session_id(db_session=db_session, overhaul_session_id=calculation.overhaul_session_id)
|
|
|
|
# equipments_scope = get_all_by_session_id(db_session=db_session, overhaul_session_id=calculation.overhaul_session_id)
|
|
|
|
|
|
|
|
|
|
|
|
@ -237,15 +248,15 @@ async def create_calculation_result_service(
|
|
|
|
calculation: CalculationData,
|
|
|
|
calculation: CalculationData,
|
|
|
|
) -> CalculationTimeConstrainsRead:
|
|
|
|
) -> CalculationTimeConstrainsRead:
|
|
|
|
days = 365 # Changed to 365 days as per requirement
|
|
|
|
days = 365 # Changed to 365 days as per requirement
|
|
|
|
|
|
|
|
|
|
|
|
# Get all equipment for this calculation session
|
|
|
|
# Get all equipment for this calculation session
|
|
|
|
equipments = await get_all_by_session_id(db_session=db_session, overhaul_session_id=calculation.overhaul_session_id)
|
|
|
|
equipments = await get_all_by_session_id(db_session=db_session, overhaul_session_id=calculation.overhaul_session_id)
|
|
|
|
scope = await get_scope(db_session=db_session, overhaul_session_id=calculation.overhaul_session_id)
|
|
|
|
scope = await get_scope(db_session=db_session, overhaul_session_id=calculation.overhaul_session_id)
|
|
|
|
|
|
|
|
|
|
|
|
calculation_data = await get_calculation_data_by_id(db_session=db_session, calculation_id=calculation.id)
|
|
|
|
calculation_data = await get_calculation_data_by_id(db_session=db_session, calculation_id=calculation.id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Store results for each equipment
|
|
|
|
# Store results for each equipment
|
|
|
|
equipment_results: List[CalculationEquipmentResult] = []
|
|
|
|
equipment_results: List[CalculationEquipmentResult] = []
|
|
|
|
@ -259,18 +270,18 @@ async def create_calculation_result_service(
|
|
|
|
service_cost=eq.service_cost,
|
|
|
|
service_cost=eq.service_cost,
|
|
|
|
days=days
|
|
|
|
days=days
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
overhaul_cost_points = get_overhaul_cost_by_time_chart(
|
|
|
|
overhaul_cost_points = get_overhaul_cost_by_time_chart(
|
|
|
|
calculation_data.parameter.overhaul_cost,
|
|
|
|
calculation_data.parameter.overhaul_cost,
|
|
|
|
days=days,
|
|
|
|
days=days,
|
|
|
|
numEquipments=len(equipments)
|
|
|
|
numEquipments=len(equipments)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Calculate individual equipment optimum points
|
|
|
|
# Calculate individual equipment optimum points
|
|
|
|
equipment_total_cost = corrective_costs + overhaul_cost_points
|
|
|
|
equipment_total_cost = corrective_costs + overhaul_cost_points
|
|
|
|
equipment_optimum_index = np.argmin(equipment_total_cost)
|
|
|
|
equipment_optimum_index = np.argmin(equipment_total_cost)
|
|
|
|
equipment_failure_sum = sum(daily_failures[:equipment_optimum_index])
|
|
|
|
equipment_failure_sum = sum(daily_failures[:equipment_optimum_index])
|
|
|
|
|
|
|
|
|
|
|
|
equipment_results.append(CalculationEquipmentResult(
|
|
|
|
equipment_results.append(CalculationEquipmentResult(
|
|
|
|
corrective_costs=corrective_costs.tolist(),
|
|
|
|
corrective_costs=corrective_costs.tolist(),
|
|
|
|
overhaul_costs=overhaul_cost_points.tolist(),
|
|
|
|
overhaul_costs=overhaul_cost_points.tolist(),
|
|
|
|
@ -281,13 +292,13 @@ async def create_calculation_result_service(
|
|
|
|
optimum_day=int(equipment_optimum_index + 1),
|
|
|
|
optimum_day=int(equipment_optimum_index + 1),
|
|
|
|
calculation_data_id=calculation.id
|
|
|
|
calculation_data_id=calculation.id
|
|
|
|
))
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
# Add to totals
|
|
|
|
# Add to totals
|
|
|
|
total_corrective_costs += corrective_costs
|
|
|
|
total_corrective_costs += corrective_costs
|
|
|
|
total_daily_failures += daily_failures
|
|
|
|
total_daily_failures += daily_failures
|
|
|
|
|
|
|
|
|
|
|
|
db_session.add_all(equipment_results)
|
|
|
|
db_session.add_all(equipment_results)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Calculate optimum points using total costs
|
|
|
|
# Calculate optimum points using total costs
|
|
|
|
total_cost = total_corrective_costs + overhaul_cost_points
|
|
|
|
total_cost = total_corrective_costs + overhaul_cost_points
|
|
|
|
@ -316,7 +327,7 @@ async def create_calculation_result_service(
|
|
|
|
|
|
|
|
|
|
|
|
# Update calculation with optimum day
|
|
|
|
# Update calculation with optimum day
|
|
|
|
calculation.optimum_oh_day = optimum.days
|
|
|
|
calculation.optimum_oh_day = optimum.days
|
|
|
|
|
|
|
|
|
|
|
|
await db_session.commit()
|
|
|
|
await db_session.commit()
|
|
|
|
|
|
|
|
|
|
|
|
# Return results including individual equipment data
|
|
|
|
# Return results including individual equipment data
|
|
|
|
@ -360,3 +371,38 @@ async def get_avg_cost_by_asset(*, db_session: DbSession, assetnum: str):
|
|
|
|
|
|
|
|
|
|
|
|
result = await db_session.execute(stmt)
|
|
|
|
result = await db_session.execute(stmt)
|
|
|
|
return result.scalar_one_or_none()
|
|
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def bulk_update_equipment(*, db: DbSession, selected_equipments: List[CalculationSelectedEquipmentUpdate], calculation_data_id:UUID):
|
|
|
|
|
|
|
|
# Assuming your model is named Asset
|
|
|
|
|
|
|
|
# and updates is a list of dictionaries containing assetnum and updated values
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Create a case statement for each field you want to update
|
|
|
|
|
|
|
|
stmt = update(CalculationEquipmentResult)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Using the case method to match assetnums with their updates
|
|
|
|
|
|
|
|
case_mappings = {
|
|
|
|
|
|
|
|
asset.assetnum: asset.is_included
|
|
|
|
|
|
|
|
for asset in selected_equipments
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Get all assetnums that need to be updated
|
|
|
|
|
|
|
|
assetnums = list(case_mappings.keys())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Build the update statement
|
|
|
|
|
|
|
|
stmt = (
|
|
|
|
|
|
|
|
update(CalculationEquipmentResult)
|
|
|
|
|
|
|
|
.where(CalculationEquipmentResult.calculation_data_id == calculation_data_id)
|
|
|
|
|
|
|
|
.where(CalculationEquipmentResult.assetnum.in_(assetnums))
|
|
|
|
|
|
|
|
.values({
|
|
|
|
|
|
|
|
"is_included": case(
|
|
|
|
|
|
|
|
(CalculationEquipmentResult.assetnum == assetnum, is_included)
|
|
|
|
|
|
|
|
for assetnum, is_included in case_mappings.items()
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await db.execute(stmt)
|
|
|
|
|
|
|
|
await db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return assetnums
|
|
|
|
|