From 553e59deb8ee7f0b1ecedd4b814b5673691d84f6 Mon Sep 17 00:00:00 2001 From: cw2007 Date: Sat, 11 Jul 2026 12:39:39 +0900 Subject: [PATCH] =?UTF-8?q?fix(report):=20=EC=9B=94=EA=B0=84=20=EC=84=BC?= =?UTF-8?q?=EC=84=9C=EB=B3=84=20=EC=9A=94=EC=95=BD=EC=9D=84=20=ED=98=84?= =?UTF-8?q?=EC=9E=AC=20=EA=B8=B0=EC=A4=80(=EB=9D=BC=EB=B2=A8=C2=B7?= =?UTF-8?q?=EB=88=84=EC=88=98=C2=B7sensor=5Fid=20=EA=B7=B8=EB=A3=B9)?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=A0=95=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 센서별 요약이 예전 보고명/이상값을 보이던 문제 수정. MockPDO 하네스로 CSV 출력 검증(각 센서 1행, 누수 컬럼, 범위 반영). - 센서 표시명/위치를 sensor_label()(SENSOR_LABELS 관리주체 라벨)로 통일 — 대시보드·SMS 와 동일 기준. 기존엔 Pi 보고 원본 sensor_name/device_location 을 그대로 노출(예전 값). - 센서별 집계를 GROUP BY sensor_id 로만 묶음(기존: sensor_id+sensor_name/location). 과거 보고명이 달라져도 한 센서가 여러 행으로 쪼개지지 않음(이상 행 제거). - 누수 감지/복구를 월간 요약·센서별 표·CSV 에 반영(누수 모듈 통합 기준). 쿼리는 이미 leak_alert/leak_recovery 를 가져왔으나 표시에서 누락돼 있었음. - 정렬을 온습도 경보 + 누수 감지 합계 기준으로. 범위 문구에 '누수 감지' 추가. - 미사용·구식 report_sms_tags() 제거(누수 태그 누락된 dead 매핑). --- php/monthly_report.php | 70 ++++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/php/monthly_report.php b/php/monthly_report.php index 28862e2..2d398cd 100644 --- a/php/monthly_report.php +++ b/php/monthly_report.php @@ -1,10 +1,14 @@ format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')]; } -// SMS 메시지 태그(prefix) → 집계 키 매핑. 태그 문자열은 config.php 의 SMS_TAG_* -// 단일 출처 상수에서 가져온다(sms_send.php 가 발송 시 붙이는 태그와 자동 일치). -function report_sms_tags(): array { - return [ - 'high_temp' => SMS_TAG_HIGH_TEMP, - 'low_temp' => SMS_TAG_LOW_TEMP, - 'high_humidity' => SMS_TAG_HIGH_HUMIDITY, - 'low_humidity' => SMS_TAG_LOW_HUMIDITY, - 'recovery' => SMS_TAG_RECOVERY, - 'device_offline' => SMS_TAG_DEVICE_OFFLINE, - 'device_online' => SMS_TAG_DEVICE_ONLINE, - ]; -} - function round_or_null($value, int $precision = 1): ?float { return $value !== null ? round((float)$value, $precision) : null; } @@ -74,7 +64,7 @@ function csv_cell($value): string { $db = get_db(); [$month, $start_at, $end_at] = month_range($_GET['month'] ?? date('Y-m')); $report_generated_at = date('Y-m-d H:i:s'); -$report_scope = '온습도 임계 경보, 장비 오프라인, SMS'; +$report_scope = '온습도 임계 경보, 누수 감지, 장비 오프라인, SMS'; // ── 월간 요약: SMS 태그별 건수 + 실패 건수 ─────────────────────────────────── $stmt = $db->prepare(" @@ -94,6 +84,8 @@ $summary = [ 'recovery' => (int)($row['recovery'] ?? 0), 'device_offline' => (int)($row['device_offline'] ?? 0), 'device_online' => (int)($row['device_online'] ?? 0), + 'leak_alert' => (int)($row['leak_alert'] ?? 0), + 'leak_recovery' => (int)($row['leak_recovery'] ?? 0), 'sms_fail' => (int)($row['sms_fail'] ?? 0), 'metrics' => 0, 'avg_temp' => null, @@ -130,12 +122,14 @@ $summary['min_humidity'] = round_or_null($row['min_humidity'] ?? null); $summary['max_humidity'] = round_or_null($row['max_humidity'] ?? null); // ── 센서별 SMS 태그별 건수 + 실패 건수 ─────────────────────────────────────── +// sensor_id 로만 묶는다(보고명 sensor_name 은 시기별로 다를 수 있어 GROUP 키에서 제외 — +// 한 센서가 여러 행으로 쪼개지는 것 방지). 표시명은 아래에서 sensor_label() 로 결정. $stmt = $db->prepare(" - SELECT sensor_id, sensor_name, + SELECT sensor_id, MAX(sensor_name) AS sensor_name, " . sms_summary_select_sql() . " FROM leak_sms_log WHERE sent_at >= :start_at AND sent_at < :end_at - GROUP BY sensor_id, sensor_name + GROUP BY sensor_id "); $stmt->execute([':start_at' => $start_at, ':end_at' => $end_at]); $sms_by_sensor = []; @@ -144,8 +138,11 @@ foreach ($stmt->fetchAll() as $r) { } // ── 센서별 측정 집계(온습도) ───────────────────────────────────────────────── +// 마찬가지로 sensor_id 로만 묶고, 보고명/위치는 최신 대표값(MAX)만 폴백용으로 가져온다. $stmt = $db->prepare(" - SELECT sensor_id, sensor_name, device_location, + SELECT sensor_id, + MAX(sensor_name) AS sensor_name, + MAX(device_location) AS device_location, COUNT(*) AS metrics, AVG(temperature_c) AS avg_temp, MIN(temperature_c) AS min_temp, @@ -156,7 +153,7 @@ $stmt = $db->prepare(" MAX(measured_at) AS last_measured_at FROM leak_sensor_metric WHERE measured_at >= :start_at AND measured_at < :end_at - GROUP BY sensor_id, sensor_name, device_location + GROUP BY sensor_id "); $stmt->execute([':start_at' => $start_at, ':end_at' => $end_at]); $metric_by_sensor = []; @@ -169,16 +166,22 @@ $sensor_rows = []; foreach (array_unique(array_merge(array_keys($metric_by_sensor), array_keys($sms_by_sensor))) as $sid) { $m = $metric_by_sensor[$sid] ?? []; $s = $sms_by_sensor[$sid] ?? []; + // 표시명/위치: SENSOR_LABELS(현재 관리주체) 우선, 없으면 보고 대표값 폴백 — 대시보드·SMS 와 동일 + [$loc, $name] = sensor_label((int)$sid, $m['device_location'] ?? null, $m['sensor_name'] ?? $s['sensor_name'] ?? null); + $threshold_alerts = (int)($s['high_temp'] ?? 0) + (int)($s['low_temp'] ?? 0) + + (int)($s['high_humidity'] ?? 0) + (int)($s['low_humidity'] ?? 0); + $leak_alert = (int)($s['leak_alert'] ?? 0); $sensor_rows[] = [ 'sensor_id' => $sid, - 'sensor_name' => $m['sensor_name'] ?? $s['sensor_name'] ?? ('센서' . $sid), - 'device_location' => $m['device_location'] ?? '', + 'sensor_name' => $name, + 'device_location' => $loc, 'high_temp' => (int)($s['high_temp'] ?? 0), 'low_temp' => (int)($s['low_temp'] ?? 0), 'high_humidity' => (int)($s['high_humidity'] ?? 0), 'low_humidity' => (int)($s['low_humidity'] ?? 0), - 'threshold_alerts' => (int)($s['high_temp'] ?? 0) + (int)($s['low_temp'] ?? 0) - + (int)($s['high_humidity'] ?? 0) + (int)($s['low_humidity'] ?? 0), + 'threshold_alerts' => $threshold_alerts, + 'leak_alert' => $leak_alert, + 'leak_recovery' => (int)($s['leak_recovery'] ?? 0), 'recovery' => (int)($s['recovery'] ?? 0), 'device_offline' => (int)($s['device_offline'] ?? 0), 'device_online' => (int)($s['device_online'] ?? 0), @@ -191,10 +194,11 @@ foreach (array_unique(array_merge(array_keys($metric_by_sensor), array_keys($sms 'min_humidity' => round_or_null($m['min_humidity'] ?? null), 'max_humidity' => round_or_null($m['max_humidity'] ?? null), 'last_measured_at' => $m['last_measured_at'] ?? '', + 'alert_sort' => $threshold_alerts + $leak_alert, // 정렬용(온습도 경보 + 누수 감지) ]; } usort($sensor_rows, function ($a, $b) { - return [$b['threshold_alerts'], $a['sensor_id']] <=> [$a['threshold_alerts'], $b['sensor_id']]; + return [$b['alert_sort'], $a['sensor_id']] <=> [$a['alert_sort'], $b['sensor_id']]; }); // ── CSV 다운로드 ───────────────────────────────────────────────────────────── @@ -210,6 +214,8 @@ if (($_GET['format'] ?? '') === 'csv') { fputcsv($out, ['low_temp_alerts', $summary['low_temp']]); fputcsv($out, ['high_humidity_alerts', $summary['high_humidity']]); fputcsv($out, ['low_humidity_alerts', $summary['low_humidity']]); + fputcsv($out, ['leak_alert_sms', $summary['leak_alert']]); + fputcsv($out, ['leak_recovery_sms', $summary['leak_recovery']]); fputcsv($out, ['recovery_sms', $summary['recovery']]); fputcsv($out, ['device_offline_sms', $summary['device_offline']]); fputcsv($out, ['device_online_sms', $summary['device_online']]); @@ -225,6 +231,7 @@ if (($_GET['format'] ?? '') === 'csv') { fputcsv($out, [ 'sensor_id', 'sensor_name', 'location', 'high_temp', 'low_temp', 'high_humidity', 'low_humidity', 'threshold_alerts', + 'leak_alert', 'leak_recovery', 'recovery', 'device_offline', 'device_online', 'sms_fail', 'metrics', 'avg_temp_c', 'min_temp_c', 'max_temp_c', 'avg_humidity_percent', 'min_humidity_percent', 'max_humidity_percent', @@ -234,6 +241,7 @@ if (($_GET['format'] ?? '') === 'csv') { fputcsv($out, [ $s['sensor_id'], csv_cell($s['sensor_name']), csv_cell($s['device_location']), $s['high_temp'], $s['low_temp'], $s['high_humidity'], $s['low_humidity'], $s['threshold_alerts'], + $s['leak_alert'], $s['leak_recovery'], $s['recovery'], $s['device_offline'], $s['device_online'], $s['sms_fail'], $s['metrics'], $s['avg_temp'] ?? '', $s['min_temp'] ?? '', $s['max_temp'] ?? '', @@ -304,6 +312,7 @@ if (($_GET['format'] ?? '') === 'csv') {
임계 경보(합계)건
고온/저온 경보/건
고습/저습 경보/건
+
누수 감지/복구/건
정상복귀건
장비 오프라인/복구/건
SMS 실패건
@@ -318,7 +327,7 @@ if (($_GET['format'] ?? '') === 'csv') { - + @@ -331,6 +340,7 @@ if (($_GET['format'] ?? '') === 'csv') { + @@ -342,7 +352,7 @@ if (($_GET['format'] ?? '') === 'csv') { - +
센서위치임계 경보고온/저온고습/저습임계 경보고온/저온고습/저습누수 감지/복구 정상복귀오프라인/복구SMS 실패 측정평균 온도/습도온도 최저/최고습도 최저/최고 최근 측정 / // /
해당 월 측정·경보 내역이 없습니다.
해당 월 측정·경보 내역이 없습니다.