$card):
$type = $card['type'];
$s = $card['s'];
$type_label = $type === 'metric' ? '온습도' : '누수';
// 미수신 placeholder
if ($s === null): ?>
(온습도 경보 | 누수) > 정상
if ($type === 'metric') {
$status_code = $s['metric_status'] ?? 'normal';
if ($s['is_offline']) {
$card_class = 'offline'; $badge_class = 'offline';
$badge_text = $s['is_alerting'] ? ('오프라인 (' . metric_status_label($status_code) . ' 중 끊김)') : '오프라인';
} elseif ($s['is_alerting']) {
$card_class = 'alert'; $badge_class = 'alert';
$badge_text = metric_status_label($status_code) . ' 경보!';
} else {
$card_class = ''; $badge_class = 'ok'; $badge_text = '정상';
}
} else {
// 디바운스 확정 상태 기준(SMS 와 일치). leak_pending: 확인창 대기 중(블립 평가).
$is_leaking = (int)($s['leak_confirmed'] ?? $s['is_leak'] ?? 0) === 1;
$is_pending = !empty($s['leak_pending']);
if ($s['is_offline']) {
$card_class = 'offline'; $badge_class = 'offline';
$badge_text = $is_leaking ? '오프라인 (누수 중 끊김)' : '오프라인';
} elseif ($is_leaking) {
$card_class = 'leak'; $badge_class = 'leak'; $badge_text = '누수';
} elseif ($is_pending) {
// raw 감지됐으나 확인창 미충족 → 확정 전(SMS 아직 안 나감)임을 명시
$card_class = 'pending'; $badge_class = 'pending'; $badge_text = '확인중(디바운스)';
} else {
$card_class = ''; $badge_class = 'ok'; $badge_text = '정상';
}
}
?>
= htmlspecialchars($s['sensor_name']) ?>
= $type_label ?>
= htmlspecialchars($s['device_location']) ?> / = htmlspecialchars($s['device_id']) ?>
= htmlspecialchars($badge_text) ?>
온도 = htmlspecialchars(metric_display($metric['temperature_c'], '°C')) ?>
습도 = htmlspecialchars(metric_display($metric['humidity_percent'], '%')) ?>
측정: = htmlspecialchars($metric['measured_at']) ?>
측정값 없음
최종확인: = htmlspecialchars($s['last_seen']) ?> (= $s['offline_elapsed_min'] ?>분 전)
near>safe)
if ($type === 'metric'):
$fc = (!$s['is_offline'] && isset($forecasts[$sid])) ? $forecasts[$sid] : null;
if ($fc):
$chips = [];
foreach ($fc['headroom'] as $h) {
$chips[] = ['state' => $h['state'], 'text' => $h['label'] . ' 여유 ' . $h['margin'] . $h['unit']];
}
foreach (['temp', 'hum'] as $vk) {
$e = $fc['trend'][$vk]['eta'] ?? null;
if ($e) { $chips[] = ['state' => 'near', 'text' => '↗ ~' . (int)$e['min'] . '분 뒤 ' . metric_status_label($e['key'])]; }
}
foreach ($fc['rate'] as $r) {
$chips[] = ['state' => 'near', 'text' => ($r['spike'] === 'rise' ? '급상승 ' : '급락 ') . ($r['per10'] > 0 ? '+' : '') . $r['per10'] . '/10분'];
}
if (!empty($fc['dew']['risk'])) {
$chips[] = ['state' => 'near', 'text' => '결로주의 (마진 ' . $fc['dew']['margin'] . '℃)'];
}
$chip_rank = ['over' => 0, 'near' => 1, 'safe' => 2];
usort($chips, fn($a, $b) => ($chip_rank[$a['state']] ?? 3) <=> ($chip_rank[$b['state']] ?? 3));
?>
= htmlspecialchars($c['text']) ?>
예보: 임계 여유 충분
아직 수신된 센서 데이터가 없습니다.