htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); $month_dot = str_replace('-', '.', $month); // 2026-06 → 2026.06 $year_n = (int)substr($month, 0, 4); $month_n = (int)substr($month, 5, 2); $doc_no = 'ENV-' . $month; // 문서번호 try { $dt_last = new DateTime($end_at); $dt_last->modify('-1 day'); $period_end = $dt_last->format('Y-m-d'); } catch (Throwable $e) { $period_end = substr($end_at, 0, 10); } $period_start = substr($start_at, 0, 10); // 관제 대상(설정 기준) $metric_ids = defined('METRIC_SENSOR_IDS') ? METRIC_SENSOR_IDS : []; $leak_ids = defined('LEAK_SENSOR_IDS') ? LEAK_SENSOR_IDS : []; $scope_total = count(array_unique(array_merge($metric_ids, $leak_ids))); $total_alerts = $summary['threshold_alerts'] + $summary['leak_alert']; $incident_count = $has_incident_table ? count($incidents) : $summary['leak_alert']; // verdict 색 클래스(요약 카드/문장) $verdict_cls = 'is-' . ($verdict === 'alert' ? 'alert' : ($verdict === 'warn' ? 'warn' : 'ok')); $verdict_color = rc_verdict_color($verdict); // 자동 총평 한 줄 $auto_summary = sprintf( '당월 온습도 측정 %s건 · 임계 경보 %d건 · 누수 감지 %d건이 집계되었으며, SMS 발송 신뢰성은 %d%%(%d건 중 실패 %d건)입니다.', number_format($summary['metrics']), $summary['threshold_alerts'], $summary['leak_alert'], $summary['sms_reliability_pct'], $summary['total_sms'], $summary['sms_fail'] ); // 센서(지점)별 판정 — 데이터 층 verdict 규칙과 동일(미복구 누수는 월 단위라 여기선 누수감지로 근사) $sensor_verdict = function (array $s): array { if ($s['leak_alert'] > 0 || $s['sms_fail'] > 0) { return ['alert', '경보']; } if ($s['threshold_alerts'] > 0 || $s['device_offline'] > 0) { return ['warn', '주의']; } return ['ok', '안정']; }; // 임계 기준 문자열 $rh_low_txt = (METRIC_RH_LOW >= 0) ? (rc_fmt((float)METRIC_RH_LOW) . '%') : '비활성'; ?>
서버실 · 문서고 온습도 및 누수 모니터링 / SMS 경보 운영 결과
= $h($auto_summary) ?>
| 고온 경보 | = (int)$summary['high_temp'] ?>건 |
|---|---|
| 저온 경보 | = (int)$summary['low_temp'] ?>건 |
| 고습 경보 | = (int)$summary['high_humidity'] ?>건 |
| 저습 경보 | = (int)$summary['low_humidity'] ?>건 |
| 임계 경보 합계 | = (int)$summary['threshold_alerts'] ?>건 |
| 누수 감지 / 복구 | = (int)$summary['leak_alert'] ?> / = (int)$summary['leak_recovery'] ?>건 |
| 정상 복귀 | = (int)$summary['recovery'] ?>건 |
| 장비 오프라인 / 복구 | = (int)$summary['device_offline'] ?> / = (int)$summary['device_online'] ?>건 |
| SMS 실패 / 총 발송 | = (int)$summary['sms_fail'] ?> / = (int)$summary['total_sms'] ?>건 |
| 측정 건수 | = number_format($summary['metrics']) ?>건 |
|---|---|
| 평균 온도 | = fmt_num($summary['avg_temp'], '℃') ?> |
| 온도 최저 / 최고 | = fmt_num($summary['min_temp'], '℃') ?> / = fmt_num($summary['max_temp'], '℃') ?> |
| 평균 습도 | = fmt_num($summary['avg_humidity'], '%') ?> |
| 습도 최저 / 최고 | = fmt_num($summary['min_humidity'], '%') ?> / = fmt_num($summary['max_humidity'], '%') ?> |
| SMS 신뢰성 | = (int)$summary['sms_reliability_pct'] ?>% |
| 고온 / 저온 임계 | = $h(rc_fmt((float)METRIC_TEMP_HIGH_C)) ?>℃ / = $h(rc_fmt((float)METRIC_TEMP_LOW_C)) ?>℃ |
| 고습 / 저습 임계 | = $h(rc_fmt((float)METRIC_RH_HIGH)) ?>% / = $h($rh_low_txt) ?> |
임계 밴드(고온 적 · 안전 녹 · 저온 청)를 배경에 인쇄. 온도 실선(좌축) · 습도 파선(우축). 결측일은 선이 끊깁니다.
= render_daily_temp_hum_line($cal_days, $daily_temp, $daily_hum) ?>온습도 임계 경보(네이비)와 누수 감지(적 · 사선)를 당월 1일~말일 축에 스택으로 표시합니다.
= render_daily_alert_bars($cal_days, $daily_metric_alerts, $daily_leak_alerts) ?>| 발생 일시 | 지점 | 위치 | 지속 | 상태 |
|---|---|---|---|---|
| = $h($inc['started_at']) ?> | = $h($inc['sensor_name']) ?> | = $h($inc['device_location']) ?> | = 60 ? intdiv($mm, 60) . '시간 ' . ($mm % 60) . '분' : $mm . '분'; } else { echo '-'; } ?> | = $inc['ongoing'] ? '미복구' : '복구' ?> |