'green'|'yellow'|'red', 'text' => string] * albo null gdy nie da się ocenić (brak danych) - jak legacy. */ class WellAssessmentService { private const T_OPT = 'Praca optymalna'; private const T_Q_YELLOW = 'Praca na granicy przedziału stosowalności'; private const T_Q_RED = 'Praca poza przedziałem stosowalności'; private const T_Q_NODATA = 'Brak danych w systemie'; private const T_HFQ_YELLOW = 'Charakterystyka pompy ulega odkształcaniu, możliwe przecieki na złączach rurociągu'; private const T_HFQ_RED = 'Odkształcona charakterystyka pompy, możliwa utrata szczelności rurociągu'; private const T_THROT_YELLOW = 'Wyraźne dławienie w układzie pompowym'; private const T_THROT_RED = 'Silne dławienie w układzie pompowym'; private const T_LOSS_YELLOW = 'Wyraźne straty liniowe w rurociągu'; private const T_LOSS_RED = 'Wysokie straty liniowe w rurociągu'; /** Przedział Q: porównanie wydajności do nominału. */ public function q(float $qn, ?float $q): ?array { if ($q === null || $q <= 0) { return ['zone' => 'red', 'text' => self::T_Q_NODATA]; } if (0.7 * $qn <= $q && $q <= 1.22 * $qn) { return ['zone' => 'green', 'text' => self::T_OPT]; } if ((0.55 * $qn <= $q && $q < 0.7 * $qn) || (1.22 * $qn < $q && $q <= 1.35 * $qn)) { return ['zone' => 'yellow', 'text' => self::T_Q_YELLOW]; } return ['zone' => 'red', 'text' => self::T_Q_RED]; } /** H=f(Q): odchyłka wysokości podnoszenia ze stacji prób (HPS) od (Pp-Hh). */ public function hfq(?float $hps, ?float $pp, ?float $hh): ?array { if (! $pp || $hps === null || $hps == 0.0) { return null; } $dev = ($hps - ($pp - $hh)) / $hps; if ($dev >= -0.05 && $dev <= 0.05) { return ['zone' => 'green', 'text' => self::T_OPT]; } if (($dev < -0.05 && $dev >= -0.07) || ($dev > 0.05 && $dev <= 0.07)) { return ['zone' => 'yellow', 'text' => self::T_HFQ_YELLOW]; } return ['zone' => 'red', 'text' => self::T_HFQ_RED]; } /** Dławienie: spadek P1-P2 na zasuwie dławiącej, progi wg zwierciadła HD. */ public function throttling(?float $hd, ?float $p1, ?float $p2): ?array { if ($hd === null || $p1 === null || $p2 === null) { return null; } $hsa = $p1 - $p2; $green = $this->pick($hd, [[50, 6], [100, 8], [200, 10], [350, 13], [450, 16]]); $yellow = $this->pick($hd, [[50, 15], [100, 20], [200, 28], [350, 35], [450, 39]]); if ($green === null) { return null; // HD poza zakresem - brak oceny (jak legacy) } if ($hsa <= $green) { return ['zone' => 'green', 'text' => self::T_OPT]; } if ($hsa <= $yellow) { return ['zone' => 'yellow', 'text' => self::T_THROT_YELLOW]; } return ['zone' => 'red', 'text' => self::T_THROT_RED]; } /** Straty liniowe: HSL = Pp - Lp - P1, progi wg średnicy rurociągu DR i Lp. */ public function lineLoss(?float $p1, ?float $pp, ?float $lp, ?float $dr): ?array { if (! $pp || $p1 === null || $lp === null || $dr === null) { return null; } $hsl = $pp - $lp - $p1; // Progi [green, yellow] zależne od DR. Każdy próg to pick() po Lp. if ($dr > 0.04 && $dr < 0.145) { $green = $this->pick($lp, [[50, 3.5], [100, 6.4], [200, 10], [300, 18], [450, 25]]); $yellow = $this->pick($lp, [[50, 4.9], [100, 9.5], [200, 16], [300, 24], [450, 35]]); // legacy: powyżej "yellow" -> wciąż YELLOW + wymóg weryfikacji p1/p2 $overText = self::T_LOSS_RED.'. Wymagana weryfikacja p1 i p2'; $overZone = 'yellow'; } elseif ($dr > 0.145 && $dr <= 0.160) { $green = $this->pick($lp, [[50, 2.3], [100, 4.3], [200, 8.6], [300, 12], [450, 21]]); $yellow = $this->pick($lp, [[50, 4.5], [100, 7.2], [200, 14], [300, 18], [450, 27]]); $overText = self::T_LOSS_RED; $overZone = 'red'; } elseif ($dr > 0.195 && $dr <= 0.215) { $green = $this->pick($lp, [[50, 2], [100, 3], [200, 6.5], [300, 9.5], [450, 12]]); $yellow = $this->pick($lp, [[50, 3], [100, 5.5], [200, 11], [300, 15], [450, 18]]); $overText = self::T_LOSS_RED; $overZone = 'red'; } else { return null; // DR poza obsługiwanymi zakresami - brak oceny (jak legacy) } if ($green === null) { return null; } if ($hsl <= $green) { return ['zone' => 'green', 'text' => self::T_OPT]; } if ($hsl <= $yellow) { return ['zone' => 'yellow', 'text' => self::T_LOSS_YELLOW]; } return ['zone' => $overZone, 'text' => $overText]; } /** Granica czerwony/żółty energochłonności w funkcji Q (próg sprawności). */ public function redYellowLimit(float $q): float { return match (true) { $q > 0 && $q <= 25 => 0.00210526315789473 * $q + 0.267368421052632, $q > 25 && $q <= 40 => 0.00133333333333334 * $q + 0.286666666666667, $q > 40 && $q <= 60 => 0.000749999999999997 * $q + 0.31, $q > 60 && $q <= 260 => 0.000225000000000001 * $q + 0.3415, $q > 260 && $q <= 500 => 4.16666666666662e-5 * $q + 0.389166666666667, $q > 500 => 1.99999999999997e-5 * $q + 0.4, default => 0.0, }; } /** Granica żółty/zielony energochłonności w funkcji Q. */ public function yellowGreenLimit(float $q): float { return match (true) { $q > 0 && $q <= 8 => 0.00999999999999998 * $q + 0.3, $q > 8 && $q <= 19 => 0.00545454545454544 * $q + 0.336363636363637, $q > 19 && $q <= 38 => 0.00210526315789474 * $q + 0.4, $q > 38 && $q <= 60 => 0.000454545454545451 * $q + 0.462727272727273, $q > 60 && $q <= 500 => 0.000170454545454545 * $q + 0.479772727272727, $q > 500 => 9.99999999999959e-6 * $q + 0.560000000000001, default => 0.0, }; } /** Sprawność układu η = Q·(HD+P2) / (367·Pe). Pe<=0 traktujemy jak 1 (jak legacy). */ public function efficiency(?float $q, ?float $hd, ?float $p2, ?float $pe): ?float { if ($q === null || $hd === null || $p2 === null) { return null; } $pe = ($pe === null || $pe <= 0) ? 1.0 : $pe; return ($q * ($hd + $p2)) / (367 * $pe); } /** Strefa energochłonności: η>=yg zielona, >=ry żółta, inaczej czerwona. */ public function energyZone(float $eta, float $ry, float $yg): string { if ($eta >= $yg) { return 'green'; } return $eta >= $ry ? 'yellow' : 'red'; } /** Wybór progu po przedziałach: [[górna granica wykluczna, wartość], ...]. */ private function pick(float $x, array $bands): ?float { foreach ($bands as [$limit, $value]) { if ($x < $limit) { return (float) $value; } } return null; } }