feat: Add availability fraction calculation and default numeric handling in simulation models

main
Cizz22 2 months ago
parent c50ccc5a67
commit b4e952ebda

@ -323,6 +323,13 @@ def _availability_percent(value: Any) -> float:
return availability
def _availability_fraction(value: Any) -> float:
availability = _as_float(value, 0) or 0
if abs(availability) > 1:
availability /= 100
return availability
def _get_number(data: dict, keys: list[str], default: float = 0) -> float:
for key in keys:
if key in data and data[key] is not None:
@ -332,6 +339,11 @@ def _get_number(data: dict, keys: list[str], default: float = 0) -> float:
return default
def _result_number(value: Any, default: float = 0) -> float:
parsed = _as_float(value)
return default if parsed is None else parsed
def _ramp_model_id_from_sim_data(sim_data: dict) -> str | None:
model_id = (
sim_data.get("RampModelId")
@ -527,9 +539,11 @@ def _calc_result(
total_uptime = _get_number(metrics, ["total_uptime", "system_uptime", "uptime"])
total_downtime = _get_number(metrics, ["total_downtime", "system_downtime", "downtime"])
period_hours = total_uptime + total_downtime
availability = _availability_percent(_first_present(metrics, "availability", "system_availability", "production_availability"))
availability_source = _first_present(metrics, "availability", "system_availability", "production_availability")
availability = _availability_fraction(availability_source)
if not availability and period_hours:
availability = total_uptime / period_hours * 100
availability = total_uptime / period_hours
eaf = _availability_percent(availability)
efor = total_downtime / period_hours * 100 if period_hours else 0
production = _get_number(metrics, ["production", "total_productions", "total_production", "actual_production"])
@ -571,16 +585,19 @@ def _calc_result(
stg_output=0,
average_level=0,
potential_production=ideal_production,
eaf=availability,
eaf=eaf,
efor=efor,
derating_hours=effective_loss,
beta=reliability.get("beta"),
eta=reliability.get("eta"),
mttr=reliability.get("mttr"),
parameters=parameters or reliability.get("parameters"),
beta=_result_number(reliability.get("beta")),
eta=_result_number(reliability.get("eta")),
mttr=int(_result_number(reliability.get("mttr"))),
parameters=parameters or reliability.get("parameters") or {},
contribution=_get_number(metrics, ["contribution"]),
criticality=_get_number(metrics, ["criticality"]),
contribution_factor=_get_number(metrics, ["contribution_factor"]),
total_mo_downtime=_get_number(metrics, ["total_mo_downtime"]),
total_po_downtime=_get_number(metrics, ["total_po_downtime"]),
sof=_get_number(metrics, ["sof"]),
)

@ -2,7 +2,7 @@ from datetime import datetime
from typing import List, Optional
from uuid import UUID
from pydantic import Field
from pydantic import Field, field_validator
from src.database.schema import CommonParams
from src.database.service import CommonParameters
@ -86,6 +86,20 @@ class SimulationCalc(DefultBase):
contribution_factor: Optional[float]
sof: Optional[float]
@field_validator(
"eaf_konkin",
"efor",
"derating_hours",
"contribution",
"criticality",
"contribution_factor",
"sof",
mode="before",
)
@classmethod
def default_numeric_compat_fields(cls, value):
return 0 if value is None else value
class SimulationPlot(DefultBase):
id: UUID
max_flow_rate: float

@ -8,6 +8,7 @@ from src.aeros_simulation.ramp_engine import (
build_ramp_distribution_overrides,
selected_simulation_engine,
)
from src.aeros_simulation.schema import SimulationCalc
def test_selected_simulation_engine_defaults_to_aeros():
@ -83,7 +84,70 @@ def test_ramp_calc_result_uses_numeric_defaults_for_aeros_only_fields():
assert calc.stg_input == 0
assert calc.stg_output == 0
assert calc.average_level == 0
assert calc.availability == 95.0
assert calc.availability == 0.95
assert calc.eaf == 95.0
assert calc.sof == 0
assert calc.total_mo_downtime == 0
assert calc.total_po_downtime == 0
def test_simulation_calc_response_defaults_nullable_numbers_for_fe():
payload = {
"id": uuid4(),
"total_downtime": 5,
"total_uptime": 95,
"num_events": 2,
"production": 10,
"production_std": 0,
"ideal_production": 12,
"availability": 0.95,
"efficiency": 83.3,
"effective_loss": 2,
"num_cm": 2,
"cm_waiting_time": 0,
"total_cm_downtime": 5,
"num_pm": 0,
"total_pm_downtime": 0,
"num_ip": 0,
"total_ip_downtime": 0,
"num_oh": 0,
"total_oh_downtime": 0,
"t_wait_for_crew": 0,
"t_wait_for_spare": 0,
"duration_at_full": 95,
"duration_above_hh": 0,
"duration_above_h": 0,
"duration_below_l": 0,
"duration_below_ll": 0,
"duration_at_empty": 5,
"stg_input": 0,
"eaf": 95,
"eaf_konkin": None,
"efor": None,
"derating_hours": None,
"aeros_node": {
"id": uuid4(),
"node_type": "SchematicNode",
"node_id": 1,
"node_name": "- TJB - Unit 3 -",
"structure_name": "- TJB - Unit 3 -",
"schematic_name": "- TJB - Unit 3 -",
"schematic_id": uuid4(),
"model_image": [],
"equipment": None,
},
"contribution": None,
"criticality": None,
"contribution_factor": None,
"sof": None,
}
calc = SimulationCalc.model_validate(payload)
assert calc.eaf_konkin == 0
assert calc.efor == 0
assert calc.derating_hours == 0
assert calc.sof == 0
def test_build_ramp_distribution_overrides_from_rbd_reliability():

Loading…
Cancel
Save