RC_ALERT,
'warn' => RC_WARN,
default => RC_NAVY, // ok(안정)은 신뢰감 있는 네이비
};
}
// '높을수록 좋은' 지표(SMS 신뢰성 등) 자체 기준 색 — 종합 판정과 독립.
// 실패 0 = 완전(네이비) / 경미한 실패 = 주의(황) / 저하 = 경보(적).
// 종합 판정이 경보여도 SMS 전달이 100%면 이 게이지는 네이비로 정확히 표현한다.
function rc_reliability_color(int $pct, int $fail_count): string {
if ($fail_count <= 0) { return RC_NAVY; } // 실패 0 → 완전 신뢰
if ($pct >= 95) { return RC_WARN; } // 경미
return RC_ALERT; // 저하
}
// 짧은 수치 포맷(소수 1자리, 불필요한 0 제거)
function rc_fmt(float $v): string {
return rtrim(rtrim(number_format($v, 1, '.', ''), '0'), '.');
}
// =============================================================================
// 1) 판정 씰 — 결재 도장형 스탬프. verdict 색·라벨 자동. 표지/경영요약 공유.
// =============================================================================
function render_verdict_seal(string $verdict, string $label): string {
$c = rc_verdict_color($verdict);
$lab = htmlspecialchars($label);
ob_start(); ?>
$v !== null)) > 0;
$has_hum = count(array_filter($hums, fn($v) => $v !== null)) > 0;
if ($n === 0 || (!$has_temp && !$has_hum)) {
return '
당월 측정 데이터가 없습니다.
';
}
$W = 920; $H = 300;
$pad_l = 46; $pad_r = 48; $pad_t = 22; $pad_b = 40;
$plot_w = $W - $pad_l - $pad_r;
$plot_h = $H - $pad_t - $pad_b;
// 임계 상수(밴드/스케일에 포함해 항상 보이게)
$t_high = (float)METRIC_TEMP_HIGH_C;
$t_low = (float)METRIC_TEMP_LOW_C;
// 온도 축: 데이터 + 임계 포함해 범위 산정
$t_vals = array_filter($temps, fn($v) => $v !== null);
$t_pool = array_merge($t_vals, [$t_high, $t_low]);
$t_min = min($t_pool); $t_max = max($t_pool);
if ($t_min === $t_max) { $t_min -= 1; $t_max += 1; }
$t_span = ($t_max - $t_min) * 0.12;
$t_min -= $t_span; $t_max += $t_span;
// 습도 축: 데이터 기준
$h_vals = array_filter($hums, fn($v) => $v !== null);
if ($h_vals) {
$h_min = min($h_vals); $h_max = max($h_vals);
if ($h_min === $h_max) { $h_min -= 1; $h_max += 1; }
$h_span = ($h_max - $h_min) * 0.12;
$h_min -= $h_span; $h_max += $h_span;
} else {
$h_min = 0; $h_max = 100;
}
$x_at = function (int $i) use ($pad_l, $plot_w, $n): float {
if ($n <= 1) { return $pad_l + $plot_w / 2; }
return $pad_l + ($plot_w * $i / ($n - 1));
};
$y_temp = function (?float $v) use ($pad_t, $plot_h, $t_min, $t_max): ?float {
if ($v === null) { return null; }
$r = ($t_max - $t_min) ?: 1;
return $pad_t + $plot_h * (1 - (($v - $t_min) / $r));
};
$y_hum = function (?float $v) use ($pad_t, $plot_h, $h_min, $h_max): ?float {
if ($v === null) { return null; }
$r = ($h_max - $h_min) ?: 1;
return $pad_t + $plot_h * (1 - (($v - $h_min) / $r));
};
$clampy = fn(float $y) => max($pad_t, min($pad_t + $plot_h, $y));
// 결측(null)은 구간을 끊어 polyline 을 만든다
$build_segments = function (array $vals, callable $y_fn) use ($x_at): array {
$segments = []; $current = [];
foreach ($vals as $i => $v) {
if ($v === null) {
if (count($current) >= 2) { $segments[] = $current; }
$current = [];
continue;
}
$y = $y_fn((float)$v);
$current[] = sprintf('%.1f,%.1f', $x_at($i), $y);
}
if (count($current) >= 2) { $segments[] = $current; }
return $segments;
};
$temp_segments = $build_segments($temps, $y_temp);
$hum_segments = $build_segments($hums, $y_hum);
// 임계 밴드 경계(온도축)
$y_high = $clampy($y_temp($t_high));
$y_low = $clampy($y_temp($t_low));
$band_top = $pad_t; $band_bottom = $pad_t + $plot_h;
ob_start(); ?>
평균 온도(℃, 좌축·실선)
평균 습도(%, 우축·파선)
안전대
고온대
저온대
당월 일자 축이 없습니다.';
}
$totals = [];
for ($i = 0; $i < $n; $i++) {
$totals[$i] = (int)($metric[$i] ?? 0) + (int)($leak[$i] ?? 0);
}
$peak = max(1, max($totals));
// Y 눈금 상한을 보기 좋은 정수로 올림(최대 4단계)
$step = (int)max(1, ceil($peak / 4));
$y_top = $step * 4;
$W = 920; $H = 240;
$pad_l = 40; $pad_r = 16; $pad_t = 20; $pad_b = 40;
$plot_w = $W - $pad_l - $pad_r;
$plot_h = $H - $pad_t - $pad_b;
$slot = $plot_w / $n;
$bar_w = min(22, $slot * 0.6);
ob_start(); ?>
온습도 임계 경보
누수 감지