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.params import Query
from src.database.core import DbSession
from src.database.core import DbSession, CollectorDbSession
from src.auth.service import Token
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()
@ -29,13 +29,14 @@ router = APIRouter()
# )
@router.get("", response_model=StandardResponse[dict])
@router.get("", response_model=StandardResponse[OptimizationResult])
async def get_target_reliability(
db_session: DbSession,
token: Token,
collector_db: CollectorDbSession,
oh_session_id: Optional[str] = Query(None),
eaf_input: float = Query(0.5),
duration: int = Query(8000),
eaf_input: float = Query(99.8),
duration: int = Query(8760),
):
"""Get all scope pagination."""
if not oh_session_id:
@ -57,12 +58,20 @@ async def get_target_reliability(
)
results = await get_simulation_results(
simulation_id=simulation_id['data'],
simulation_id=simulation_id["data"],
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(
data=results,
data=optimize_result,
message="Data retrieved successfully",
)

@ -31,6 +31,31 @@ class OverhaulRead(OverhaulBase):
schedules: List[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": {

@ -1,27 +1,25 @@
from typing import Optional
from typing import Optional, List
from dataclasses import dataclass
from sqlalchemy import Delete, Select
import httpx
from src.auth.service import CurrentUser
from src.database.core import DbSession
# 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 src.database.core import DbSession, CollectorDbSession
from datetime import datetime, timedelta
import random
from typing import List
from .utils import generate_down_periods
from src.overhaul_scope.service import get as get_overhaul
from bisect import bisect_left
from collections import defaultdict
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"
client = httpx.AsyncClient(timeout=300.0)
# return results
async def run_rbd_simulation(*, sim_hours: int, token):
sim_data = {
"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"
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:
calc_task = client.get(calc_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
calc_response, plot_response = await asyncio.gather(calc_task, plot_task)
# Run all three requests concurrently
calc_response, plot_response, plant_response = await asyncio.gather(calc_task, plot_task, plant_task)
calc_response.raise_for_status()
plot_response.raise_for_status()
plant_response.raise_for_status()
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 {
"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.
Optimized version with reduced time complexity.
Calculate each asset's contribution weight to plant EAF
Based on production capacity and criticality
"""
plant_ideal_production = plant_result.get('ideal_production', 0)
results = []
Args:
eaf_input (float): EAF value to check against thresholds
oh_session_id (str): OH session identifier
oh_duration (int): Duration in hours
for asset in eq_results:
# Weight based on production capacity
capacity_weight = asset.get('ideal_production', 0) / plant_ideal_production if plant_ideal_production > 0 else 0
Returns:
List[dict]: List of dictionaries containing dates and their EAF values
"""
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,
}
# Get asset EAF
asset_eaf = asset.get('eaf', 0)
# EAF values during downtime - now using three states
downtime_values = {
'eaf1_warning': 0.6,
'eaf1_down': 0.0,
}
# Calculate EAF impact (how much this asset affects plant EAF)
eaf_impact = (100 - asset_eaf) * capacity_weight
# Generate down periods for all EAF scenarios at once
all_down_periods = {}
for eaf_key in ['eaf1']:
# Generate warning periods (0.6)
warning_periods = generate_down_periods(
oh_session_start,
end_date,
3, # Less frequent warnings
min_duration=24,
max_duration=48
asset_weight = AssetWeight(
node=asset.get('aeros_node'),
capacity_weight=capacity_weight,
eaf_impact=eaf_impact,
eaf=asset_eaf,
num_of_failures=asset.get('num_events', 0),
ideal_production=asset.get('ideal_production', 0)
)
results.append(asset_weight)
# Generate full downtime periods (0.0)
down_periods = generate_down_periods(
oh_session_start,
end_date,
2, # Less frequent full downtimes
min_duration=36,
max_duration=72
)
return results
# Store both types of periods with their state
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]
def calculate_asset_eaf_contributions(plant_result, eq_results, plot_result):
"""
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 eaf_type, periods in all_down_periods.items():
eaf_key = eaf_type.split('_')[0] # Extract base key (eaf1)
state_type = eaf_type.split('_')[1] # Extract state type (warning/down)
for asset in eq_results:
# Weight based on production capacity
capacity_weight = asset.get('ideal_production', 0) / plant_ideal_production if plant_ideal_production > 0 else 0
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:
# Record state changes at period boundaries
if state == 'warning':
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']
# Get asset EAF and downtime
asset_eaf = asset.get('eaf', 0)
asset_derating_pct = 100 - asset_eaf
# Reset to normal at the end of period
state_changes[end + timedelta(hours=1)][f'{eaf_key}_value'] = default_values[f'{eaf_key}_value']
# Calculate this asset's contribution to plant EAF reduction
# 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
change_times = sorted(state_changes.keys())
# Calculate actual downtime hours (if simulation hours available)
sim_duration = plant_result.get('sim_duration', 8760) # Default to 1 year
downtime_hours = (asset_derating_pct / 100) * sim_duration
results = []
current_values = default_values.copy()
contribution = AssetWeight(
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
current_time = oh_session_start
idx = 0
# Sort by contribution (worst contributors first)
results.sort(key=lambda x: x.eaf_impact, reverse=True)
return results
while current_time <= end_date:
# Update values if we've hit a state change point
while idx < len(change_times) and current_time >= change_times[idx]:
changes = state_changes[change_times[idx]]
for key, value in changes.items():
current_values[key] = value
idx += 1
def project_eaf_improvement(asset: AssetWeight, improvement_factor: float = 0.3) -> float:
"""
Project EAF improvement after maintenance
This is a simplified model - you should replace with your actual prediction logic
"""
current_downtime_pct = 100 - asset.eaf
# 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({
'date': current_time.strftime('%Y-%m-%d %H:00:00'),
**current_values
})
async def identify_worst_eaf_contributors(*, simulation_result, target_eaf: float, db_session: DbSession, oh_session_id: str, collector_db: CollectorDbSession):
"""
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