Files
controls-web/overviews/data/main.php
2026-02-17 12:44:37 -06:00

951 lines
28 KiB
PHP

<?php
declare(strict_types=1);
// phpcs:disable
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
header('Content-Type: text/html; charset=UTF-8');
date_default_timezone_set('America/Chicago');
/**
* Capture the rendered output of a partial include and return it as a string.
*/
function capture_include(string $relativePath): string
{
ob_start();
include __DIR__ . '/' . $relativePath;
return trim(ob_get_clean());
}
/**
* Safely fetch a value from an array.
*
* @param array<mixed> $source
*/
function array_value(array $source, string $key)
{
return array_key_exists($key, $source) ? $source[$key] : null;
}
function numeric_or_null($value): ?float
{
if ($value === null) {
return null;
}
if (is_numeric($value)) {
return (float) $value;
}
$normalised = str_replace([',', ' '], '', (string) $value);
$normalised = preg_replace('/[^0-9.\-]/', '', $normalised ?? '');
if ($normalised === '' || !is_numeric($normalised)) {
return null;
}
return (float) $normalised;
}
function format_number($value, int $decimals = 0): string
{
$numeric = numeric_or_null($value);
return $numeric === null ? '—' : number_format($numeric, $decimals);
}
function format_with_unit($value, string $unit, int $decimals = 0): string
{
$formatted = format_number($value, $decimals);
return $formatted === '—' ? $formatted : $formatted . ' ' . $unit;
}
/**
* @param array<float|null> $values
*/
function sum_numeric(array $values): ?float
{
$total = 0.0;
$found = false;
foreach ($values as $value) {
$numeric = numeric_or_null($value);
if ($numeric === null) {
continue;
}
$total += $numeric;
$found = true;
}
return $found ? $total : null;
}
function clamp_percent(?float $value): float
{
if ($value === null) {
return 0.0;
}
return max(0.0, min(100.0, $value));
}
function percent_label($value, int $decimals = 0): string
{
$numeric = numeric_or_null($value);
return $numeric === null ? '—' : format_number($numeric, $decimals) . '%';
}
function html(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
/**
* @param array<mixed> $source
*/
function value_num(array $source, string $key): ?float
{
return numeric_or_null(array_value($source, $key));
}
/**
* @param array<mixed> $source
*/
function value_as_int(array $source, string $key): ?int
{
$numeric = value_num($source, $key);
return $numeric === null ? null : (int) round($numeric);
}
function build_status(?int $rawValue, bool $autoIsZero = true): ?array
{
if ($rawValue === null) {
return null;
}
$isAuto = $autoIsZero ? $rawValue === 0 : $rawValue === 1;
return [
'label' => $isAuto ? 'Auto' : 'Manual',
'class' => $isAuto ? 'status-pill--ok' : 'status-pill--alert',
];
}
function build_switch(?int $rawValue, string $labelOn = 'On', string $labelOff = 'Off'): ?array
{
if ($rawValue === null) {
return null;
}
$isOn = $rawValue === 0;
return [
'label' => $isOn ? $labelOn : $labelOff,
'class' => $isOn ? 'status-pill--ok' : 'status-pill--warn',
];
}
function classify_live_steam(?float $value): ?array
{
if ($value === null) {
return null;
}
if ($value <= 155) {
return ['label' => 'Low', 'class' => 'status-pill--alert'];
}
if ($value <= 165) {
return ['label' => 'Watch', 'class' => 'status-pill--warn'];
}
return ['label' => 'Stable', 'class' => 'status-pill--ok'];
}
function classify_exhaust(?float $value): ?array
{
if ($value === null) {
return null;
}
if ($value <= 9) {
return ['label' => 'Low', 'class' => 'status-pill--alert'];
}
if ($value <= 13) {
return ['label' => 'Watch', 'class' => 'status-pill--warn'];
}
return ['label' => 'Stable', 'class' => 'status-pill--ok'];
}
function tank_level_state(?float $value): ?string
{
if ($value === null) {
return null;
}
if ($value <= 20) {
return 'low';
}
if ($value >= 90) {
return 'caution';
}
return null;
}
include '../../items.php';
$valuesZero = $value ?? [];
$roundedZero = $rounded ?? [];
$idsZero = $ID ?? [];
$roundedOneZero = $rounded1 ?? [];
include '../../items2dec.php';
$valuesTwo = $value ?? [];
$idsTwo = $ID ?? [];
$value = $valuesZero;
$rounded = $roundedZero;
$ID = $idsZero;
$rounded1 = $roundedOneZero;
$now = new DateTimeImmutable('now', new DateTimeZone('America/Chicago'));
$generatedAt = $now->format(DateTimeInterface::RFC3339);
$cropDayRaw = capture_include('cropday.php');
$cropDay = $cropDayRaw !== '' ? $cropDayRaw : null;
$tonsIn = numeric_or_null(capture_include('tonsin.php'));
$recordTons = numeric_or_null(capture_include('record.php'));
$recordDateRaw = capture_include('recorddate.php');
$recordDate = $recordDateRaw !== '' ? $recordDateRaw : null;
$westAvg = numeric_or_null(capture_include('west15minavg.php'));
$groundToday = sum_numeric([
value_num($value, 'CANETOT'),
value_num($value, 'W TONS GROUND'),
]);
$groundYesterday = sum_numeric([
value_num($value, 'PREVTONS'),
value_num($value, 'WPREVGROUND'),
]);
$runHours = value_num($value, 'RUNHRSTODAY');
$prevRunHours = value_num($value, 'PREVTIME');
$totalTonsPerHour = sum_numeric([
value_num($value, 'TONS_PER_HOUR'),
value_num($value, 'RATE'),
]);
$lossTime = value_num($value, 'LOSSTIME');
$eastAverage = value_num($value, 'AVG_TONS_PER_HOUR');
$summaryCards = [
[
'label' => 'Cane In Today',
'value' => format_number($tonsIn),
'unit' => 'tons',
],
[
'label' => 'Ground Today',
'value' => format_number($groundToday),
'unit' => 'tons',
],
[
'label' => 'Total Ground Yesterday',
'value' => format_number($groundYesterday),
'unit' => 'tons',
],
[
'label' => 'Run Hours',
'value' => format_number($runHours, 1),
'unit' => 'hours',
],
[
'label' => 'Prev Run Hours',
'value' => format_number($prevRunHours, 1),
'unit' => 'hours',
],
[
'label' => 'Total Tons per Hour',
'value' => format_number($totalTonsPerHour),
'unit' => 'tons/hr',
],
[
'label' => 'East 15 Minute Avg',
'value' => format_number($eastAverage),
'unit' => 'tons/hr',
],
[
'label' => 'West 15 Minute Avg',
'value' => format_number($westAvg),
'unit' => 'tons/hr',
],
[
'label' => 'Loss Time Today',
'value' => format_number($lossTime, 2),
'unit' => 'hours',
],
[
'label' => 'Record Day',
'value' => format_number($recordTons),
'unit' => 'tons',
'note' => $recordDate ? 'on ' . $recordDate : null,
],
];
$levelThroughputSwitch = value_as_int($value, 'LEVEL_THROUGHPUT_SWITCH');
$millFourUnit = $levelThroughputSwitch === 1 ? 'tons/hr' : '%';
$eastStages = [
[
'label' => 'Mill 1',
'stage' => '1',
'level' => value_num($value, 'MILL1LVLPV'),
'setpoint' => value_num($value, 'MILL1SP'),
'setpointUnit' => '%',
'rpm' => value_num($value, 'EMILL1RPM'),
'outputLabel' => 'Mill 1 OP',
'outputValue' => value_num($value, 'MILL1OUTPUT'),
'outputUnit' => '%',
'mode' => build_status(value_as_int($value, 'MILL1AUTOMAN'), true),
],
[
'label' => 'Mill 2',
'stage' => '2',
'level' => value_num($value, 'M2LVL'),
'setpoint' => value_num($value, 'MILL2SP'),
'setpointUnit' => '%',
'rpm' => value_num($value, 'EMILL2RPM'),
'outputLabel' => 'MCC Output',
'outputValue' => value_num($value, 'MCCOUTPUT'),
'outputUnit' => '%',
'mode' => null,
],
[
'label' => 'Mill 3',
'stage' => '3',
'level' => value_num($value, 'MIL3LVL'),
'setpoint' => value_num($value, 'MILL3SP'),
'setpointUnit' => '%',
'rpm' => value_num($value, 'EMILL3RPM'),
'outputLabel' => 'MCC Tons',
'outputValue' => value_num($rounded1, 'MCCTONS'),
'outputUnit' => 'tons',
'mode' => null,
],
[
'label' => 'Mill 4',
'stage' => '4',
'level' => value_num($value, 'MIL4LVL'),
'setpoint' => value_num($value, 'MILL4SP'),
'setpointUnit' => '%',
'rpm' => value_num($value, 'EMILL4RPM'),
'outputLabel' => 'Mill 1 SP',
'outputValue' => value_num($value, 'WEIGHT_WSP'),
'outputUnit' => $millFourUnit,
'mode' => build_status(value_as_int($value, 'MILL1AUTOMAN1'), false),
],
[
'label' => 'Mill 5',
'stage' => '5',
'level' => value_num($value, 'MIL5LVL'),
'setpoint' => value_num($value, 'MILL5SP'),
'setpointUnit' => '%',
'rpm' => value_num($value, 'EMILL5RPM'),
'outputLabel' => 'Grinding Rate',
'outputValue' => value_num($value, 'TONS_PER_HOUR'),
'outputUnit' => 'tons/hr',
'mode' => null,
],
[
'label' => 'Mill 6',
'stage' => '6',
'level' => value_num($value, 'MIL6LVL'),
'setpoint' => value_num($value, 'MILL6SP'),
'setpointUnit' => '%',
'rpm' => value_num($value, 'EMILL6RPM'),
'outputLabel' => 'Average Rate',
'outputValue' => value_num($value, 'AVG_TONS_PER_HOUR'),
'outputUnit' => 'tons/15 min',
'mode' => null,
],
];
$eastFooterMetrics = [
['label' => 'Imbibition 6 Flow', 'value' => format_with_unit(value_num($value, 'IMBFLOW'), 'gpm')],
['label' => 'Imbibition 6 SP', 'value' => format_with_unit(value_num($value, 'IMBSP'), 'gpm')],
['label' => 'Imbibition 5 Flow', 'value' => format_with_unit(value_num($value, 'IMBFLOW'), 'gpm')],
['label' => 'Imbibition 5 SP', 'value' => format_with_unit(value_num($value, 'IMB5SP'), 'gpm')],
];
$westStages = [
[
'label' => 'Mill 1',
'stage' => '1',
'level' => value_num($ID, '00654'),
'setpoint' => null,
'setpointUnit' => '%',
'rpm' => value_num($ID, '00666'),
'outputLabel' => 'MCC FPM',
'outputValue' => value_num($value, 'Feet/Minute'),
'outputUnit' => 'fpm',
'mode' => null,
],
[
'label' => 'Mill 2',
'stage' => '2',
'level' => value_num($ID, '00656'),
'setpoint' => null,
'setpointUnit' => '%',
'rpm' => value_num($ID, '00670'),
'outputLabel' => 'MCC Output',
'outputValue' => value_num($ID, '00638'),
'outputUnit' => '%',
'mode' => null,
],
[
'label' => 'Mill 4',
'stage' => '4',
'level' => value_num($ID, '00658'),
'setpoint' => null,
'setpointUnit' => '%',
'rpm' => value_num($ID, '00674'),
'outputLabel' => 'Mill 1 SP',
'outputValue' => null,
'outputUnit' => '%',
'mode' => null,
],
[
'label' => 'Mill 5',
'stage' => '5',
'level' => value_num($ID, '00660'),
'setpoint' => null,
'setpointUnit' => '%',
'rpm' => value_num($ID, '00680'),
'outputLabel' => 'Grinding Rate',
'outputValue' => value_num($value, 'RATE'),
'outputUnit' => 'tons/hr',
'mode' => null,
],
];
$westFooterMetrics = [
['label' => 'Imbibition Flow', 'value' => format_with_unit(value_num($ID, '00624'), 'gpm')],
['label' => 'Average Rate', 'value' => format_with_unit($westAvg, 'tons/15 min')],
];
$steamFlow = value_num($value, 'TTL_STEAMFLOW2');
$liveSteam = value_num($value, 'PT_001');
$exhaustPressure = value_num($ID, '00244');
$exhaustMode = build_status(value_as_int($value, 'ExhaustAM'), true);
$vaporHeader = value_num($value, 'VAPOR HDR PRES');
$boilerOverview = [
[
'label' => 'Total Steam Flow',
'value' => format_number($steamFlow),
'unit' => 'kpph',
],
[
'label' => 'Live Steam Pressure',
'value' => format_number($liveSteam),
'unit' => 'psi',
'status' => classify_live_steam($liveSteam),
],
[
'label' => 'Exhaust Pressure',
'value' => format_number($exhaustPressure),
'unit' => 'psi',
'status' => classify_exhaust($exhaustPressure),
'mode' => $exhaustMode,
],
[
'label' => 'Vapor Header Pressure',
'value' => format_number($vaporHeader),
'unit' => 'psi',
],
];
$boilerFlows = [
['label' => 'Boiler 1', 'value' => format_number(value_num($ID, '00252'))],
['label' => 'Boiler 2', 'value' => format_number(value_num($ID, '00256'))],
['label' => 'Boiler 3', 'value' => format_number(value_num($value, 'FT_301'))],
['label' => 'Boiler 4', 'value' => format_number(value_num($value, 'FT_401'))],
['label' => 'Boiler 5', 'value' => format_number(value_num($value, 'FT_501'))],
['label' => 'Boiler 6', 'value' => format_number(value_num($value, 'FT_601'))],
['label' => 'Boiler 7', 'value' => format_number(value_num($ID, '00046'))],
['label' => 'Boiler 8', 'value' => format_number(value_num($ID, '00074'))],
];
$syrupOverflow = value_num($value, 'Syrup Overflow Lvl');
$syrupReceiver = value_num($value, 'Syrup RCVR');
$totalSyrup = null;
if ($syrupOverflow !== null || $syrupReceiver !== null) {
$totalSyrup = 0.0;
if ($syrupOverflow !== null) {
$totalSyrup += $syrupOverflow * 0.82569;
}
if ($syrupReceiver !== null) {
$totalSyrup += $syrupReceiver * 0.17431;
}
}
$tankLevels = [
['label' => 'Juice Tank 1', 'value' => value_num($value, 'Juice Tank1')],
['label' => 'Juice Tank 2', 'value' => value_num($value, 'Juice Tank2')],
['label' => 'Syrup Receiver', 'value' => $syrupReceiver],
['label' => 'Syrup Overflow', 'value' => $syrupOverflow],
['label' => 'Total Syrup', 'value' => $totalSyrup],
['label' => 'Condensate Water', 'value' => value_num($value, 'CondensateWater')],
['label' => 'Sweet Water', 'value' => value_num($value, 'SweetwaterTank')],
['label' => 'Cold Water INJ', 'value' => value_num($value, 'Cold Water Injection LVL')],
['label' => 'A1 Receiver', 'value' => value_num($value, 'A1 RCVR')],
['label' => 'A1 Overflow', 'value' => value_num($value, 'A1 Molasses Overflow Lvl')],
['label' => 'A2 Receiver', 'value' => value_num($value, 'A2 RCVR')],
['label' => 'A2 Overflow', 'value' => value_num($value, 'A2 Molasses Overflow Lvl')],
['label' => 'B Receiver', 'value' => value_num($value, 'B RCVR')],
['label' => 'B Overflow', 'value' => value_num($value, 'B Molasses Overflow Lvl')],
['label' => 'Flash Tank', 'value' => value_num($value, 'FlashTankLVL')],
];
$truckDumpState = build_status(value_as_int($value, 'LATCHON'), true);
$truckDumpMetrics = [
['label' => 'PV', 'value' => format_number(value_num($valuesTwo, 'TD INPUT'), 1)],
[
'label' => 'Setpoint',
'value' => format_number(
value_as_int($value, 'TD SP3 ON/OFF') === 1
? value_num($valuesTwo, 'TD SP3')
: value_num($valuesTwo, 'TD SP1'),
1
),
'suffix' => value_as_int($value, 'TD SP3 ON/OFF') === 1 ? 'SP3' : 'SP',
],
['label' => 'Output', 'value' => format_with_unit(value_num($valuesTwo, 'TD OUTPUT'), '%', 1)],
['label' => 'Ratio', 'value' => format_number(value_num($valuesTwo, 'TD RATIO'), 2)],
['label' => 'Belt Scale', 'value' => format_with_unit(value_num($valuesTwo, 'TDBELTSCALE'), 'lbs', 0)],
[
'label' => 'Belt State',
'status' => build_switch(value_as_int($value, 'TDBELTSCALEONOFF'), 'Scale On', 'Scale Off'),
],
[
'label' => 'Mode',
'value' => value_as_int($value, 'TD MILLGROUND') === null
? '—'
: (
value_as_int($value, 'TD MILLGROUND') === 0
? 'Dump Mill'
: 'Dump Ground'
),
],
];
$tables = [
[
'label' => 'S Table',
'pv' => format_number(value_num($valuesTwo, 'SE SCALE'), 1),
'sp' => format_number(
value_as_int($value, 'STABLE SP3 ON/OFF') === 1
? value_num($valuesTwo, 'STABLE SP1')
: value_num($valuesTwo, 'SE SETPOINT'),
1
),
'spLabel' => value_as_int($value, 'STABLE SP3 ON/OFF') === 1 ? 'SP3' : 'SP',
'output' => format_with_unit(value_num($valuesTwo, 'SE OUTPUT'), '%', 1),
'mode' => build_status(value_as_int($value, 'SAM'), true),
],
[
'label' => 'NE Table',
'pv' => format_number(value_num($valuesTwo, 'NE SCALE'), 1),
'sp' => format_number(
value_as_int($value, 'NE SP SWITCH') === 1
? value_num($valuesTwo, 'NE SETPOINT2')
: value_num($valuesTwo, 'NE SETPOINT'),
1
),
'spLabel' => value_as_int($value, 'NE SP SWITCH') === 1 ? 'SP3' : 'SP',
'output' => format_with_unit(value_num($valuesTwo, 'NE OUTPUT'), '%', 1),
'mode' => build_status(value_as_int($value, 'NAM'), true),
],
[
'label' => 'W Table',
'pv' => format_number(value_num($valuesTwo, 'W SCALE'), 1),
'sp' => format_number(
value_as_int($value, 'W SP SWITCH') === 1
? value_num($valuesTwo, 'W SETPOINT2')
: value_num($valuesTwo, 'W SETPOINT'),
1
),
'spLabel' => value_as_int($value, 'W SP SWITCH') === 1 ? 'SP3' : 'SP',
'output' => format_with_unit(value_num($valuesTwo, 'W OUTPUT'), '%', 1),
'mode' => build_status(value_as_int($value, 'WAM'), true),
],
];
$knives = [
[
'label' => 'Knife 1',
'pressure' => format_with_unit(value_num($valuesTwo, 'KNF1PRES'), 'psi', 1),
'speed' => format_with_unit(value_num($valuesTwo, 'K1SPD'), 'rpm', 0),
'output' => format_with_unit(value_num($valuesTwo, 'KNIFE1OP'), '%', 1),
'mode' => value_as_int($value, 'KC1') === null
? null
: (
value_as_int($value, 'KC1') === 1
? ['label' => 'Control', 'class' => 'status-pill--ok']
: ['label' => 'Knife', 'class' => 'status-pill--warn']
),
],
[
'label' => 'Knife 2',
'pressure' => format_with_unit(value_num($valuesTwo, 'KNF2PRES'), 'psi', 1),
'speed' => format_with_unit(value_num($valuesTwo, 'KNF2SPD'), 'rpm', 0),
'output' => format_with_unit(value_num($valuesTwo, 'KNIFE2OP'), '%', 1),
'mode' => value_as_int($value, 'KC2') === null
? null
: (
value_as_int($value, 'KC2') === 1
? ['label' => 'Control', 'class' => 'status-pill--ok']
: ['label' => 'Knife', 'class' => 'status-pill--warn']
),
],
[
'label' => 'Knife 3',
'pressure' => format_with_unit(value_num($valuesTwo, 'KNF3PRES'), 'psi', 1),
'speed' => format_with_unit(value_num($valuesTwo, 'KNF3SPD'), 'rpm', 0),
'output' => format_with_unit(value_num($valuesTwo, 'KNIFE3OP'), '%', 1),
'mode' => value_as_int($value, 'KC3') === null
? null
: (
value_as_int($value, 'KC3') === 1
? ['label' => 'Control', 'class' => 'status-pill--ok']
: ['label' => 'Knife', 'class' => 'status-pill--warn']
),
],
];
?>
<div class="board-stack" data-generated-at="<?php echo html($generatedAt); ?>">
<div class="meta-banner">
<?php if ($cropDay !== null) { ?>
<span>Crop Day <strong><?php echo html($cropDay); ?></strong></span>
<?php } ?>
<span>Server time <strong><?php echo html($now->format('M j, Y g:i:s A')); ?></strong></span>
</div>
<section class="section">
<header class="section__header">
<h2 class="section__title">Key Production Metrics</h2>
<p class="section__meta">Plant-wide summary</p>
</header>
<div class="stat-grid">
<?php foreach ($summaryCards as $card) { ?>
<article class="stat-card">
<p class="stat-card__label"><?php echo html($card['label']); ?></p>
<p class="stat-card__value"><?php echo html($card['value']); ?></p>
<?php if (!empty($card['unit'])) { ?>
<p class="stat-card__unit"><?php echo html($card['unit']); ?></p>
<?php } ?>
<?php if (!empty($card['note'])) { ?>
<p class="stat-card__note"><?php echo html($card['note']); ?></p>
<?php } ?>
</article>
<?php } ?>
</div>
</section>
<section class="section">
<header class="section__header">
<h2 class="section__title">East Tandem</h2>
<p class="section__meta">Six-mill train status</p>
</header>
<div class="tandem-grid">
<?php foreach ($eastStages as $stage) { ?>
<article class="stage-card">
<div class="stage-card__header">
<div class="stage-card__title">
<span class="stage-card__badge"><?php echo html($stage['stage']); ?></span>
<span><?php echo html($stage['label']); ?></span>
</div>
<?php if (!empty($stage['mode'])) { ?>
<span class="status-pill <?php echo html($stage['mode']['class']); ?>">
<?php echo html($stage['mode']['label']); ?>
</span>
<?php } ?>
</div>
<div class="progress">
<div class="progress__track">
<div
class="progress__bar"
style="width: <?php echo clamp_percent(numeric_or_null($stage['level'])); ?>%;"
></div>
</div>
<div class="progress__label">
<span>Level</span>
<span><?php echo html(percent_label($stage['level'])); ?></span>
</div>
</div>
<div class="metric-list">
<div class="metric">
<span class="metric__label">Setpoint</span>
<span class="metric__value">
<?php echo html(format_number($stage['setpoint'])); ?>
<em><?php echo html($stage['setpointUnit']); ?></em>
</span>
</div>
<div class="metric">
<span class="metric__label">RPM</span>
<span class="metric__value">
<?php echo html(format_with_unit($stage['rpm'], 'rpm')); ?>
</span>
</div>
<div class="metric">
<span class="metric__label"><?php echo html($stage['outputLabel']); ?></span>
<span class="metric__value">
<?php echo html(format_with_unit($stage['outputValue'], $stage['outputUnit'])); ?>
</span>
</div>
</div>
</article>
<?php } ?>
</div>
<div class="mini-grid">
<?php foreach ($eastFooterMetrics as $metric) { ?>
<div class="mini-card">
<span class="mini-card__label"><?php echo html($metric['label']); ?></span>
<span class="mini-card__value"><?php echo html($metric['value']); ?></span>
</div>
<?php } ?>
</div>
</section>
<section class="section">
<header class="section__header">
<h2 class="section__title">West Tandem</h2>
<p class="section__meta">Grinding performance</p>
</header>
<div class="tandem-grid">
<?php foreach ($westStages as $stage) { ?>
<article class="stage-card">
<div class="stage-card__header">
<div class="stage-card__title">
<span class="stage-card__badge"><?php echo html($stage['stage']); ?></span>
<span><?php echo html($stage['label']); ?></span>
</div>
</div>
<div class="progress">
<div class="progress__track">
<div
class="progress__bar"
style="width: <?php echo clamp_percent(numeric_or_null($stage['level'])); ?>%;"
></div>
</div>
<div class="progress__label">
<span>Level</span>
<span><?php echo html(percent_label($stage['level'])); ?></span>
</div>
</div>
<div class="metric-list">
<div class="metric">
<span class="metric__label">RPM</span>
<span class="metric__value"><?php echo html(format_with_unit($stage['rpm'], 'rpm')); ?></span>
</div>
<div class="metric">
<span class="metric__label"><?php echo html($stage['outputLabel']); ?></span>
<span class="metric__value"><?php echo html(format_with_unit($stage['outputValue'], $stage['outputUnit'])); ?></span>
</div>
</div>
</article>
<?php } ?>
</div>
<div class="mini-grid">
<?php foreach ($westFooterMetrics as $metric) { ?>
<div class="mini-card">
<span class="mini-card__label"><?php echo html($metric['label']); ?></span>
<span class="mini-card__value"><?php echo html($metric['value']); ?></span>
</div>
<?php } ?>
</div>
</section>
<section class="section">
<header class="section__header">
<h2 class="section__title">Boilers</h2>
<p class="section__meta">Steam generation health</p>
</header>
<div class="stat-grid">
<?php foreach ($boilerOverview as $card) { ?>
<article class="stat-card">
<p class="stat-card__label"><?php echo html($card['label']); ?></p>
<p class="stat-card__value"><?php echo html($card['value']); ?></p>
<p class="stat-card__unit"><?php echo html($card['unit']); ?></p>
<?php if (!empty($card['status'])) { ?>
<p class="stat-card__note">
<span class="status-pill <?php echo html($card['status']['class']); ?>">
<?php echo html($card['status']['label']); ?>
</span>
</p>
<?php } ?>
<?php if (!empty($card['mode'])) { ?>
<p class="stat-card__note">
<span class="status-pill <?php echo html($card['mode']['class']); ?>">
<?php echo html($card['mode']['label']); ?>
</span>
</p>
<?php } ?>
</article>
<?php } ?>
</div>
<div class="mini-grid">
<?php foreach ($boilerFlows as $boiler) { ?>
<div class="mini-card">
<span class="mini-card__label"><?php echo html($boiler['label']); ?></span>
<span class="mini-card__value"><?php echo html($boiler['value']); ?> <small>kpph</small></span>
</div>
<?php } ?>
</div>
</section>
<section class="section">
<header class="section__header">
<h2 class="section__title">Tank Levels</h2>
<p class="section__meta">Percent of capacity</p>
</header>
<div class="tank-grid">
<?php foreach ($tankLevels as $tank) { ?>
<?php $state = tank_level_state($tank['value']); ?>
<article
class="tank-card"
<?php if ($state !== null) { ?>data-level="<?php echo html($state); ?>"<?php } ?>
>
<div class="tank-card__title">
<span><?php echo html($tank['label']); ?></span>
<span class="tank-card__meta"><?php echo html(percent_label($tank['value'])); ?></span>
</div>
<div class="progress">
<div class="progress__track">
<div
class="progress__bar"
style="width: <?php echo clamp_percent(numeric_or_null($tank['value'])); ?>%;"
></div>
</div>
</div>
</article>
<?php } ?>
</div>
</section>
<section class="section">
<header class="section__header">
<h2 class="section__title">Truck Dump & Tables</h2>
<p class="section__meta">Front-end preparation</p>
</header>
<div class="equipment-grid">
<article class="equipment-card">
<div class="equipment-card__title">
<span>Truck Dump</span>
<?php if ($truckDumpState !== null) { ?>
<span class="status-pill <?php echo html($truckDumpState['class']); ?>">
<?php echo html($truckDumpState['label']); ?>
</span>
<?php } ?>
</div>
<div class="equipment-card__rows">
<?php foreach ($truckDumpMetrics as $metric) { ?>
<div class="equipment-row">
<span class="equipment-row__label"><?php echo html($metric['label']); ?></span>
<span class="equipment-row__value">
<?php if (!empty($metric['status'])) { ?>
<span class="status-pill <?php echo html($metric['status']['class']); ?>">
<?php echo html($metric['status']['label']); ?>
</span>
<?php } else { ?>
<?php echo html($metric['value'] ?? '—'); ?>
<?php if (!empty($metric['suffix'])) { ?>
<small><?php echo html($metric['suffix']); ?></small>
<?php } ?>
<?php } ?>
</span>
</div>
<?php } ?>
</div>
</article>
<?php foreach ($tables as $table) { ?>
<article class="equipment-card">
<div class="equipment-card__title">
<span><?php echo html($table['label']); ?></span>
<?php if ($table['mode'] !== null) { ?>
<span class="status-pill <?php echo html($table['mode']['class']); ?>">
<?php echo html($table['mode']['label']); ?>
</span>
<?php } ?>
</div>
<div class="equipment-card__rows">
<div class="equipment-row">
<span class="equipment-row__label">PV</span>
<span class="equipment-row__value"><?php echo html($table['pv']); ?></span>
</div>
<div class="equipment-row">
<span class="equipment-row__label">Setpoint</span>
<span class="equipment-row__value">
<?php echo html($table['sp']); ?>
<small><?php echo html($table['spLabel']); ?></small>
</span>
</div>
<div class="equipment-row">
<span class="equipment-row__label">Output</span>
<span class="equipment-row__value"><?php echo html($table['output']); ?></span>
</div>
</div>
</article>
<?php } ?>
</div>
<div class="equipment-card">
<div class="equipment-card__title">
<span>Knife Train</span>
</div>
<div class="equipment-subgrid">
<?php foreach ($knives as $knife) { ?>
<div class="equipment-mini">
<div class="equipment-mini__title">
<span><?php echo html($knife['label']); ?></span>
<?php if ($knife['mode'] !== null) { ?>
<span class="status-pill <?php echo html($knife['mode']['class']); ?>">
<?php echo html($knife['mode']['label']); ?>
</span>
<?php } ?>
</div>
<div class="equipment-row">
<span class="equipment-row__label">Pressure</span>
<span class="equipment-row__value"><?php echo html($knife['pressure']); ?></span>
</div>
<div class="equipment-row">
<span class="equipment-row__label">Speed</span>
<span class="equipment-row__value"><?php echo html($knife['speed']); ?></span>
</div>
<div class="equipment-row">
<span class="equipment-row__label">Output</span>
<span class="equipment-row__value"><?php echo html($knife['output']); ?></span>
</div>
</div>
<?php } ?>
</div>
</div>
</section>
</div>
<?php // phpcs:enable ?>