target eaf

feature/reliability_stat
Cizz22 5 months ago
parent a2812aea02
commit e4217a7d20

@ -1,152 +1 @@
{ {}
"editor.tabSize": 4,
"editor.rulers": [
120
],
"editor.renderWhitespace": "trailing",
"editor.suggestSelection": "first",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.stickyScroll.enabled": false,
"editor.bracketPairColorization.enabled": false,
"editor.cursorSmoothCaretAnimation": "on",
"editor.suggest.preview": true,
"terminal.integrated.defaultProfile.windows": "Command Prompt",
"debug.onTaskErrors": "debugAnyway",
"explorer.compactFolders": false,
"explorer.confirmDragAndDrop": false,
"explorer.confirmDelete": false,
"explorer.copyRelativePathSeparator": "/",
"files.autoSave": "onFocusChange",
"files.exclude": {
"node_modules/**/*": true,
"**/.classpath": true,
"**/.project": true,
"**/.settings": true,
"**/.factorypath": true
},
"files.associations": {
"*.pyx": "cython",
".clang*": "yaml",
"*.gpj": "jsonc",
"*.gvw": "jsonc",
"*.hpp.in": "cpp"
},
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true,
"workbench.startupEditor": "none",
"workbench.editorAssociations": {
"*.ipynb": "jupyter-notebook",
"*.md": "vscode.markdown.preview.editor",
"*.svg": "svgPreviewer.customEditor"
},
"workbench.colorTheme": "Dracula Theme Soft",
"git.enableSmartCommit": true,
"git.autofetch": true,
"git.confirmSync": false,
"git.openRepositoryInParentFolders": "always",
"partialDiff.enableTelemetry": false,
"prettier.tabWidth": 4,
"prettier.singleQuote": true,
"prettier.jsxSingleQuote": true,
"prettier.trailingComma": "all",
"prettier.useEditorConfig": true,
"prettier.bracketSpacing": false,
"markdown.validate.enabled": true,
"[markdown]": {
"files.trimTrailingWhitespace": false,
"editor.formatOnSave": false,
"editor.defaultFormatter": "yzhang.markdown-all-in-one",
"editor.wordWrap": "wordWrapColumn",
"editor.wordWrapColumn": 80
},
"[yaml]": {
"editor.formatOnSave": false,
"editor.defaultFormatter": "redhat.vscode-yaml",
"editor.wordWrap": "wordWrapColumn",
"editor.wordWrapColumn": 80
},
"[json]": {
"editor.formatOnSave": false,
"editor.defaultFormatter": "vscode.json-language-features"
},
"[jsonc]": {
"editor.formatOnSave": false
},
"[plaintext]": {
"editor.wordWrap": "wordWrapColumn",
"editor.wordWrapColumn": 120
},
"[toml]": {
"editor.wordWrap": "wordWrapColumn",
"editor.wordWrapColumn": 80,
"editor.defaultFormatter": "tamasfe.even-better-toml",
"editor.formatOnSave": true
},
"better-comments.tags": [
{
"tag": "XXX",
"color": "#F8C471"
},
{
"tag": "WARN",
"color": "#FF6961"
},
{
"tag": "NOTE",
"color": "#3498DB"
},
{
"tag": "TODO",
"color": "#77C3EC"
}
],
"vsintellicode.modify.editor.suggestSelection": "automaticallyOverrodeDefaultValue",
"codesnap.showWindowControls": false,
"codesnap.shutterAction": "copy",
"Workspace_Formatter.excludePattern": [
"**/build",
"**/.*",
"**/.vscode",
"**/html"
],
"svg.preview.autoOpen": true,
"remote.WSL.fileWatcher.polling": true,
"errorLens.delay": 1000,
"errorLens.enabledDiagnosticLevels": [
"error",
"warning"
],
"errorLens.enabled": false,
"[python]": {
"editor.formatOnSave": false,
"editor.defaultFormatter": "mikoz.black-py",
"editor.formatOnType": false
},
"python.languageServer": "Jedi",
"python.analysis.addImport.exactMatchOnly": true,
"python.analysis.autoImportCompletions": false,
"python.analysis.completeFunctionParens": false,
"python.analysis.autoFormatStrings": true,
"python.analysis.logLevel": "Error",
"python.createEnvironment.contentButton": "show",
"python.missingPackage.severity": "Error",
"mypy-type-checker.importStrategy": "fromEnvironment",
"black-formatter.importStrategy": "fromEnvironment",
"isort.check": true,
"isort.importStrategy": "fromEnvironment",
"ruff.organizeImports": false,
"ruff.fixAll": false,
"autoDocstring.generateDocstringOnEnter": true,
"autoDocstring.quoteStyle": "'''",
"jupyter.interactiveWindow.creationMode": "perFile",
"jupyter.askForKernelRestart": false,
"jupyter.themeMatplotlibPlots": true,
"jupyter.logging.level": "error",
"notebook.formatOnSave.enabled": false,
"notebook.output.textLineLimit": 20,
"notebook.compactView": false,
"notebook.diff.ignoreMetadata": true,
"notebook.diff.ignoreOutputs": true
}

@ -3,12 +3,12 @@ from typing import Dict, List, Optional
from fastapi import APIRouter, HTTPException, status from fastapi import APIRouter, HTTPException, status
from fastapi.params import Query from fastapi.params import Query
from src.database.core import DbSession from src.database.core import DbSession, CollectorDbSession
from src.auth.service import Token from src.auth.service import Token
from src.models import StandardResponse from src.models import StandardResponse
from .service import get_eaf_timeline, run_rbd_simulation, get_simulation_results from .service import run_rbd_simulation, get_simulation_results, identify_worst_eaf_contributors
from .schema import OptimizationResult
router = APIRouter() router = APIRouter()
@ -29,13 +29,14 @@ router = APIRouter()
# ) # )
@router.get("", response_model=StandardResponse[dict]) @router.get("", response_model=StandardResponse[OptimizationResult])
async def get_target_reliability( async def get_target_reliability(
db_session: DbSession, db_session: DbSession,
token: Token, token: Token,
collector_db: CollectorDbSession,
oh_session_id: Optional[str] = Query(None), oh_session_id: Optional[str] = Query(None),
eaf_input: float = Query(0.5), eaf_input: float = Query(99.8),
duration: int = Query(8000), duration: int = Query(8760),
): ):
"""Get all scope pagination.""" """Get all scope pagination."""
if not oh_session_id: if not oh_session_id:
@ -57,12 +58,20 @@ async def get_target_reliability(
) )
results = await get_simulation_results( results = await get_simulation_results(
simulation_id=simulation_id['data'], simulation_id=simulation_id["data"],
token=token token=token
) )
optimize_result = await identify_worst_eaf_contributors(
simulation_result=results,
target_eaf=eaf_input,
db_session=db_session,
oh_session_id=oh_session_id,
collector_db=collector_db
)
return StandardResponse( return StandardResponse(
data=results, data=optimize_result,
message="Data retrieved successfully", message="Data retrieved successfully",
) )

@ -31,6 +31,31 @@ class OverhaulRead(OverhaulBase):
schedules: List[Dict[str, Any]] schedules: List[Dict[str, Any]]
systemComponents: Dict[str, Any] systemComponents: Dict[str, Any]
class AssetWeight(OverhaulBase):
node: dict
capacity_weight: float
eaf_impact: float
eaf: float
num_of_failures: int
downtime_hours: float
plot_data: dict
class MaintenanceScenario(OverhaulBase):
location_tag: str
current_eaf: float
projected_eaf_improvement: float
priority_score: float
plant_level_benefit: float = 0.0
capacity_weight : float
class OptimizationResult(OverhaulBase):
current_plant_eaf: float
target_plant_eaf: float
eaf_gap: float
asset_contributions: List[AssetWeight]
optimization_success: bool = False
# { # {
# "overview": { # "overview": {

@ -1,27 +1,25 @@
from typing import Optional from typing import Optional, List
from dataclasses import dataclass
from sqlalchemy import Delete, Select from sqlalchemy import Delete, Select
import httpx import httpx
from src.auth.service import CurrentUser from src.auth.service import CurrentUser
from src.database.core import DbSession from src.database.core import DbSession, CollectorDbSession
# from src.scope_equipment.model import ScopeEquipment
# from src.scope_equipment.service import get_by_scope_name
# from src.scope_equipment_job.service import get_equipment_level_by_no
from datetime import datetime, timedelta from datetime import datetime, timedelta
import random import random
from typing import List
from .utils import generate_down_periods from .utils import generate_down_periods
from src.overhaul_scope.service import get as get_overhaul from src.overhaul_scope.service import get as get_overhaul
from bisect import bisect_left from bisect import bisect_left
from collections import defaultdict from collections import defaultdict
import asyncio import asyncio
from .schema import AssetWeight,MaintenanceScenario,OptimizationResult
from src.overhaul_activity.service import get_standard_scope_by_session_id
RBD_SERVICE_API = "http://192.168.1.82:8000/rbd" RBD_SERVICE_API = "http://192.168.1.82:8000/rbd"
client = httpx.AsyncClient(timeout=300.0) client = httpx.AsyncClient(timeout=300.0)
# return results
async def run_rbd_simulation(*, sim_hours: int, token): async def run_rbd_simulation(*, sim_hours: int, token):
sim_data = { sim_data = {
"SimulationName": "Simulation OH Reliability Target", "SimulationName": "Simulation OH Reliability Target",
@ -51,129 +49,248 @@ async def get_simulation_results(*, simulation_id: str, token: str):
calc_result_url = f"{RBD_SERVICE_API}/aeros/simulation/result/calc/{simulation_id}?nodetype=RegularNode" calc_result_url = f"{RBD_SERVICE_API}/aeros/simulation/result/calc/{simulation_id}?nodetype=RegularNode"
plot_result_url = f"{RBD_SERVICE_API}/aeros/simulation/result/plot/{simulation_id}?nodetype=RegularNode" plot_result_url = f"{RBD_SERVICE_API}/aeros/simulation/result/plot/{simulation_id}?nodetype=RegularNode"
calc_plant_result = f"{RBD_SERVICE_API}/aeros/simulation/result/calc/{simulation_id}/plant"
async with httpx.AsyncClient(timeout=300.0) as client: async with httpx.AsyncClient(timeout=300.0) as client:
calc_task = client.get(calc_result_url, headers=headers) calc_task = client.get(calc_result_url, headers=headers)
plot_task = client.get(plot_result_url, headers=headers) plot_task = client.get(plot_result_url, headers=headers)
plant_task = client.get(calc_plant_result, headers=headers)
# Run both requests concurrently # Run all three requests concurrently
calc_response, plot_response = await asyncio.gather(calc_task, plot_task) calc_response, plot_response, plant_response = await asyncio.gather(calc_task, plot_task, plant_task)
calc_response.raise_for_status() calc_response.raise_for_status()
plot_response.raise_for_status() plot_response.raise_for_status()
plant_response.raise_for_status()
calc_data = calc_response.json()["data"] calc_data = calc_response.json()["data"]
plot_data = plot_response.json()["data"] plot_data = plot_response.json()["data"]["plot_results"]
plant_data = plant_response.json()["data"]
return { return {
"calc_result": calc_data, "calc_result": calc_data,
"plot_result": plot_data "plot_result": plot_data,
"plant_result": plant_data
} }
async def get_eaf_timeline(*, db_session, eaf_input: float, oh_session_id: str, oh_duration = 8000) -> List[dict]: def calculate_asset_weights(plant_result, eq_results):
""" """
Generate a timeline of EAF values based on input parameters. Calculate each asset's contribution weight to plant EAF
Optimized version with reduced time complexity. Based on production capacity and criticality
"""
plant_ideal_production = plant_result.get('ideal_production', 0)
results = []
Args: for asset in eq_results:
eaf_input (float): EAF value to check against thresholds # Weight based on production capacity
oh_session_id (str): OH session identifier capacity_weight = asset.get('ideal_production', 0) / plant_ideal_production if plant_ideal_production > 0 else 0
oh_duration (int): Duration in hours
Returns: # Get asset EAF
List[dict]: List of dictionaries containing dates and their EAF values asset_eaf = asset.get('eaf', 0)
"""
MIN_EAF = 30
MAX_EAF = 80
oh_session = await get_overhaul(db_session=db_session, overhaul_session_id=oh_session_id)
oh_session_start = datetime.fromisoformat(oh_session.start_date.isoformat())
# Determine date range
if MIN_EAF <= eaf_input <= MAX_EAF:
end_date = oh_session_start + timedelta(hours=oh_duration)
elif eaf_input < MIN_EAF:
end_date = oh_session_start + timedelta(hours=oh_duration, days=360)
else: # eaf_input > MAX_EAF
end_date = oh_session_start + timedelta(hours=oh_duration) - timedelta(days=180)
# Default EAF values when system is up
default_values = {
'eaf1_value': 1.0,
}
# EAF values during downtime - now using three states # Calculate EAF impact (how much this asset affects plant EAF)
downtime_values = { eaf_impact = (100 - asset_eaf) * capacity_weight
'eaf1_warning': 0.6,
'eaf1_down': 0.0,
}
# Generate down periods for all EAF scenarios at once asset_weight = AssetWeight(
all_down_periods = {} node=asset.get('aeros_node'),
for eaf_key in ['eaf1']: capacity_weight=capacity_weight,
# Generate warning periods (0.6) eaf_impact=eaf_impact,
warning_periods = generate_down_periods( eaf=asset_eaf,
oh_session_start, num_of_failures=asset.get('num_events', 0),
end_date, ideal_production=asset.get('ideal_production', 0)
3, # Less frequent warnings
min_duration=24,
max_duration=48
) )
results.append(asset_weight)
# Generate full downtime periods (0.0) return results
down_periods = generate_down_periods(
oh_session_start,
end_date,
2, # Less frequent full downtimes
min_duration=36,
max_duration=72
)
# Store both types of periods with their state def calculate_asset_eaf_contributions(plant_result, eq_results, plot_result):
all_down_periods[f'{eaf_key}_warning'] = [(start, end, 'warning') for start, end in warning_periods] """
all_down_periods[f'{eaf_key}_down'] = [(start, end, 'down') for start, end in down_periods] Calculate each asset's negative contribution to plant EAF
Higher contribution = more impact on reducing plant EAF
"""
plant_ideal_production = plant_result.get('ideal_production', 0)
results = []
# Create a list of all state change times
state_changes = defaultdict(dict)
# Process both warning and down periods for asset in eq_results:
for eaf_type, periods in all_down_periods.items(): # Weight based on production capacity
eaf_key = eaf_type.split('_')[0] # Extract base key (eaf1) capacity_weight = asset.get('ideal_production', 0) / plant_ideal_production if plant_ideal_production > 0 else 0
state_type = eaf_type.split('_')[1] # Extract state type (warning/down) plot_data = next((item for item in plot_result if item['aeros_node']['node_name'] == asset['aeros_node']['node_name']), None)
for start, end, state in periods: # Get asset EAF and downtime
# Record state changes at period boundaries asset_eaf = asset.get('eaf', 0)
if state == 'warning': asset_derating_pct = 100 - asset_eaf
state_changes[start][f'{eaf_key}_value'] = downtime_values[f'{eaf_key}_warning']
else: # state == 'down'
state_changes[start][f'{eaf_key}_value'] = downtime_values[f'{eaf_key}_down']
# Reset to normal at the end of period # Calculate this asset's contribution to plant EAF reduction
state_changes[end + timedelta(hours=1)][f'{eaf_key}_value'] = default_values[f'{eaf_key}_value'] # This is how much this asset alone reduces the overall plant EAF
eaf_contribution = asset_derating_pct * capacity_weight
# Convert state_changes to sorted list of times # Calculate actual downtime hours (if simulation hours available)
change_times = sorted(state_changes.keys()) sim_duration = plant_result.get('sim_duration', 8760) # Default to 1 year
downtime_hours = (asset_derating_pct / 100) * sim_duration
results = [] contribution = AssetWeight(
current_values = default_values.copy() node=asset.get('aeros_node'),
eaf=asset_eaf,
capacity_weight=capacity_weight,
eaf_impact=eaf_contribution,
downtime_hours=downtime_hours,
num_of_failures=asset.get('num_events', 0),
plot_data=plot_data
)
results.append(contribution)
# Process changes between state change points # Sort by contribution (worst contributors first)
current_time = oh_session_start results.sort(key=lambda x: x.eaf_impact, reverse=True)
idx = 0 return results
while current_time <= end_date: def project_eaf_improvement(asset: AssetWeight, improvement_factor: float = 0.3) -> float:
# Update values if we've hit a state change point """
while idx < len(change_times) and current_time >= change_times[idx]: Project EAF improvement after maintenance
changes = state_changes[change_times[idx]] This is a simplified model - you should replace with your actual prediction logic
for key, value in changes.items(): """
current_values[key] = value current_downtime_pct = 100 - asset.eaf
idx += 1 # Assume maintenance reduces downtime by improvement_factor
improved_downtime_pct = current_downtime_pct * (1 - improvement_factor)
projected_eaf = 100 - improved_downtime_pct
return min(projected_eaf, 99.9) # Cap at 99.9%
results.append({ async def identify_worst_eaf_contributors(*, simulation_result, target_eaf: float, db_session: DbSession, oh_session_id: str, collector_db: CollectorDbSession):
'date': current_time.strftime('%Y-%m-%d %H:00:00'), """
**current_values Identify equipment that contributes most to plant EAF reduction
})
current_time += timedelta(hours=1) Args:
simulation_result: Dictionary containing calc_result and plant_result
target_eaf: Target plant EAF percentage (for gap calculation)
return results Returns:
OptimizationResult with asset contributions ranked by impact
"""
# Calculate current plant EAF and asset contributions
calc_result = simulation_result['calc_result']
plant_result = simulation_result['plant_result']
plot_result = simulation_result['plot_result']
# Get equipment results from calc_result
eq_results = calc_result if isinstance(calc_result, list) else [calc_result]
asset_contributions = calculate_asset_eaf_contributions(plant_result, eq_results, plot_result)
current_plant_eaf = plant_result.get("eaf", 0)
eaf_gap = target_eaf - current_plant_eaf
# Verify our calculation by summing contributions
total_calculated_downtime = sum(contrib.eaf_impact for contrib in asset_contributions)
calculated_plant_eaf = 100 - total_calculated_downtime
standard_scope = await get_standard_scope_by_session_id(
db_session=db_session,
overhaul_session_id=oh_session_id,
collector_db=collector_db
)
standard_scope_location_tags = [tag.location_tag for tag in standard_scope]
# Select only standard Scope(array)
selected_asset = [asset for asset in asset_contributions if asset.node['node_name'] in standard_scope_location_tags]
print(f"Plant EAF from API: {current_plant_eaf:.2f}%")
print(f"Plant EAF calculated: {calculated_plant_eaf:.2f}%")
print(f"Target EAF: {target_eaf:.2f}%")
print(f"EAF Gap: {eaf_gap:.2f}%")
optimization_success = current_plant_eaf >= target_eaf
return OptimizationResult(
current_plant_eaf=current_plant_eaf,
target_plant_eaf=target_eaf,
eaf_gap=eaf_gap,
asset_contributions=selected_asset,
optimization_success=optimization_success
)
# def optimize_maintenance_priority(*, simulation_result, target_eaf: float):
# """
# Optimize maintenance priorities to achieve target plant EAF
# Args:
# simulation_result: Dictionary containing calc_result and plant_result
# target_eaf: Target plant EAF percentage
# Returns:
# OptimizationResult with recommendations
# """
# # Calculate current plant EAF and asset weights
# calc_result = simulation_result['calc_result']
# plant_result = simulation_result['plant_result']
# # Get equipment results from calc_result
# eq_results = calc_result if isinstance(calc_result, list) else [calc_result]
# equipment_eaf_weights = calculate_asset_weights(plant_result, eq_results)
# current_plant_eaf = plant_result.get("eaf", 0)
# if current_plant_eaf >= target_eaf:
# return OptimizationResult(
# current_plant_eaf=current_plant_eaf,
# target_plant_eaf=target_eaf,
# eaf_gap=0.0,
# recommended_assets=[],
# projected_plant_eaf=current_plant_eaf,
# optimization_success=True
# )
# # Create maintenance scenarios for all assets
# scenarios = []
# for asset in equipment_eaf_weights:
# if asset.eaf < 99.9: # Only consider assets with improvement potential
# projected_eaf = project_eaf_improvement(asset)
# eaf_improvement = projected_eaf - asset.eaf
# # Calculate plant-level benefit (how much this asset improvement affects plant EAF)
# plant_benefit = eaf_improvement * asset.capacity_weight
# # Priority score combines multiple factors
# priority_score = (
# plant_benefit * 0.8 + # Plant impact (50%)
# (100 - asset.eaf) * asset.capacity_weight * 0.2 # Current performance gap (30%)
# # asset.num_of_failures * asset.capacity_weight * 0.1 # Failure frequency (20%)
# )
# scenario = MaintenanceScenario(
# location_tag=asset.node['node_name'], # Placeholder - replace with actual name
# current_eaf=asset.eaf,
# projected_eaf_improvement=eaf_improvement,
# priority_score=priority_score,
# plant_level_benefit=plant_benefit,
# capacity_weight=asset.capacity_weight
# )
# scenarios.append(scenario)
# # Sort by priority score (highest first)
# scenarios.sort(key=lambda x: x.priority_score, reverse=True)
# # Select assets for maintenance to achieve target EAF
# selected_scenarios = []
# cumulative_benefit = 0.0
# eaf_gap = target_eaf - current_plant_eaf
# for scenario in scenarios:
# if cumulative_benefit < eaf_gap:
# selected_scenarios.append(scenario)
# cumulative_benefit += scenario.plant_level_benefit
# if cumulative_benefit >= eaf_gap:
# break
# projected_plant_eaf = current_plant_eaf + cumulative_benefit
# optimization_success = projected_plant_eaf >= target_eaf
# return OptimizationResult(
# current_plant_eaf=current_plant_eaf,
# target_plant_eaf=target_eaf,
# eaf_gap=eaf_gap,
# recommended_assets=selected_scenarios,
# projected_plant_eaf=projected_plant_eaf,
# optimization_success=optimization_success
# )

Loading…
Cancel
Save