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 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: def _get_number(data: dict, keys: list[str], default: float = 0) -> float:
for key in keys: for key in keys:
if key in data and data[key] is not None: 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 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: def _ramp_model_id_from_sim_data(sim_data: dict) -> str | None:
model_id = ( model_id = (
sim_data.get("RampModelId") sim_data.get("RampModelId")
@ -527,9 +539,11 @@ def _calc_result(
total_uptime = _get_number(metrics, ["total_uptime", "system_uptime", "uptime"]) total_uptime = _get_number(metrics, ["total_uptime", "system_uptime", "uptime"])
total_downtime = _get_number(metrics, ["total_downtime", "system_downtime", "downtime"]) total_downtime = _get_number(metrics, ["total_downtime", "system_downtime", "downtime"])
period_hours = total_uptime + total_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: 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 efor = total_downtime / period_hours * 100 if period_hours else 0
production = _get_number(metrics, ["production", "total_productions", "total_production", "actual_production"]) production = _get_number(metrics, ["production", "total_productions", "total_production", "actual_production"])
@ -571,16 +585,19 @@ def _calc_result(
stg_output=0, stg_output=0,
average_level=0, average_level=0,
potential_production=ideal_production, potential_production=ideal_production,
eaf=availability, eaf=eaf,
efor=efor, efor=efor,
derating_hours=effective_loss, derating_hours=effective_loss,
beta=reliability.get("beta"), beta=_result_number(reliability.get("beta")),
eta=reliability.get("eta"), eta=_result_number(reliability.get("eta")),
mttr=reliability.get("mttr"), mttr=int(_result_number(reliability.get("mttr"))),
parameters=parameters or reliability.get("parameters"), parameters=parameters or reliability.get("parameters") or {},
contribution=_get_number(metrics, ["contribution"]), contribution=_get_number(metrics, ["contribution"]),
criticality=_get_number(metrics, ["criticality"]), criticality=_get_number(metrics, ["criticality"]),
contribution_factor=_get_number(metrics, ["contribution_factor"]), 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 typing import List, Optional
from uuid import UUID from uuid import UUID
from pydantic import Field from pydantic import Field, field_validator
from src.database.schema import CommonParams from src.database.schema import CommonParams
from src.database.service import CommonParameters from src.database.service import CommonParameters
@ -86,6 +86,20 @@ class SimulationCalc(DefultBase):
contribution_factor: Optional[float] contribution_factor: Optional[float]
sof: 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): class SimulationPlot(DefultBase):
id: UUID id: UUID
max_flow_rate: float max_flow_rate: float

@ -8,6 +8,7 @@ from src.aeros_simulation.ramp_engine import (
build_ramp_distribution_overrides, build_ramp_distribution_overrides,
selected_simulation_engine, selected_simulation_engine,
) )
from src.aeros_simulation.schema import SimulationCalc
def test_selected_simulation_engine_defaults_to_aeros(): 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_input == 0
assert calc.stg_output == 0 assert calc.stg_output == 0
assert calc.average_level == 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(): def test_build_ramp_distribution_overrides_from_rbd_reliability():

Loading…
Cancel
Save