0, 'PM2.5 threshold sane (> 0).'); check($P10 > 0, 'PM10 threshold sane (> 0).'); // 임계 상대 입력. PM 은 임계 "초과"(strict >)일 때만 발화한다. $hi25 = $P25 + 5.0; $lo25 = $P25 - 5.0; $hi10 = $P10 + 5.0; $lo10 = $P10 - 5.0; // ── 온습도 없이(순수 PM) — 후방호환: 앞 두 인자 null ────────── // 1) PM2.5 초과, PM10 정상 → high_pm25 만 (breaching case) $b = evaluate_metric_thresholds(null, null, $hi25, $lo10)['breaches']; check(in_array('high_pm25', $b, true), 'pm25 above threshold -> breaches contains high_pm25'); check(!in_array('high_pm10', $b, true), 'pm10 below threshold -> no high_pm10'); // 2) PM10 초과, PM2.5 정상 → high_pm10 만 (pm10 > 임계 필요) $b = evaluate_metric_thresholds(null, null, $lo25, $hi10)['breaches']; check(in_array('high_pm10', $b, true), 'pm10 above threshold -> breaches contains high_pm10'); check(!in_array('high_pm25', $b, true), 'pm25 below threshold -> no high_pm25'); // 3) 둘 다 초과 → 두 종류 모두 $b = evaluate_metric_thresholds(null, null, $hi25, $hi10)['breaches']; check(in_array('high_pm25', $b, true) && in_array('high_pm10', $b, true), 'both pm above -> both breaches'); // 4) 정상(둘 다 임계 이하) → PM 경보 없음 (normal case) $b = evaluate_metric_thresholds(null, null, $lo25, $lo10)['breaches']; eq($b, [], 'both pm below threshold -> no breaches'); // 5) 경계: 임계와 정확히 같으면 발화하지 않는다(strict >) $b = evaluate_metric_thresholds(null, null, $P25, $P10)['breaches']; check(!in_array('high_pm25', $b, true) && !in_array('high_pm10', $b, true), 'pm exactly at threshold -> no breach (strict greater-than)'); // 6) PM 결측(null)이면 PM 경보 없음 — 온습도 전용 호출(2인자)도 종전대로 동작 $b = evaluate_metric_thresholds(null, null)['breaches']; eq($b, [], 'null pm (2-arg call, backward compatible) -> no pm breach'); // 7) 온습도 경보와 독립 공존: 고온 + 고 PM2.5 → 두 종류 모두 포함 $b = evaluate_metric_thresholds((float)METRIC_TEMP_HIGH_C + 5, null, $hi25, $lo10)['breaches']; check(in_array('high_temp', $b, true) && in_array('high_pm25', $b, true), 'high temp + high pm25 -> both breaches present'); // 8) 기본 임계(75/150)에서의 명시 케이스(오버라이드 없을 때만) — 사양 예시 그대로. if ($P25 === 75.0) { check(in_array('high_pm25', evaluate_metric_thresholds(null, null, 80.0, 40.0)['breaches'], true), 'default: pm25=80 (>75) -> high_pm25, pm10=40 -> no high_pm10'); } // ── 집계 SELECT 에 high_pm 컬럼 포함 ──────────────────────── check(function_exists('sms_summary_select_sql'), 'sms_summary_select_sql() should exist.'); check(str_contains(sms_summary_select_sql(), 'AS high_pm'), "sms_summary_select_sql() should contain 'AS high_pm'"); // ── SMS_METRIC_TAGS 매핑: 두 PM 종류가 미세먼지 태그로 매핑 ── check(defined('SMS_METRIC_TAGS'), 'SMS_METRIC_TAGS should be defined.'); check((SMS_METRIC_TAGS['high_pm25'] ?? null) === SMS_TAG_HIGH_PM, 'high_pm25 -> SMS_TAG_HIGH_PM'); check((SMS_METRIC_TAGS['high_pm10'] ?? null) === SMS_TAG_HIGH_PM, 'high_pm10 -> SMS_TAG_HIGH_PM'); if ($failures) { fwrite(STDERR, "PM threshold test failures:\n"); foreach ($failures as $f) { fwrite(STDERR, "- {$f}\n"); } exit(1); } echo "PMS7003 PM threshold checks passed.\n";