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(); ?> 관제 판정 MONTHLY REVIEW $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(); ?> 0 ? ($plot_h * $mv / $y_top) : 0; $l_h = $lv > 0 ? ($plot_h * $lv / $y_top) : 0; $m_y = $base - $m_h; $l_y = $m_y - $l_h; ?> 0): ?> 0): ?> 일(당월) · 발생 건수
온습도 임계 경보 누수 감지