物联网数据质量控制系统设计:质控算法与实现
创建日期 : 2026-03-25
更新日期 : 2026-03-25
作者 : zry
标签: 物联网, 数据质量, 质控算法, 气象数据, 异常检测, 数据清洗
📋 目录
引言
在物联网应用中,传感器数据的质量直接影响决策的准确性。自动气象站系统采集的温度、湿度、气压等数据,必须经过严格的质量控制才能投入使用。数据质量控制(QC, Quality Control) 是确保数据可靠性的关键环节。
本文基于AIDC自动气象站数据收集系统的实践,深入讲解物联网数据质量控制的设计与实现。
数据质量的重要性
数据质量问题的影响
数据质量问题链
传感器故障
异常数据
错误分析结果
错误决策
经济损失
| 数据问题 | 影响 | 案例 |
|---|---|---|
| 缺失数据 | 统计偏差 | 月平均温度计算错误 |
| 异常值 | 告警误报 | 误报极端天气 |
| 重复数据 | 资源浪费 | 重复存储和处理 |
| 时序错乱 | 趋势分析错误 | 无法识别真实趋势 |
质控目标
数据质控目标
完整性
准确性
一致性
及时性
无缺失
无异常值
时间连续
低延迟
质控系统架构
整体架构
质控系统架构
是
否
原始数据
一级质控
格式检查
二级质控
范围检查
三级质控
逻辑检查
四级质控
空间检查
通过?
标记为正常
标记为异常
异常处理
存储到数据库
告警/人工审核
质控流程
数据采集
实时质控
延迟质控
历史质控
即时反馈
小时级处理
日/月级分析
质控算法详解
1. 范围检查(Range Check)
是
否
观测值
在范围内?
通过
异常
超过上限
低于下限
cpp
/**
* @brief 范围检查算法
*
* 检查数据是否在物理合理范围内
*/
class RangeCheck {
public:
struct Range {
double min;
double max;
std::string description;
};
/**
* @brief 温度范围检查
*
* 极端温度范围参考:
* - 全球最低:-89.2°C(南极)
* - 全球最高:56.7°C(死亡谷)
*/
static Range GetTemperatureRange() {
return {-90.0, 60.0, "Global temperature range"};
}
/**
* @brief 湿度范围检查
*/
static Range GetHumidityRange() {
return {0.0, 100.0, "Relative humidity (%)"};
}
/**
* @brief 气压范围检查
*
* 极端气压范围参考:
* - 最低:870 hPa(台风眼)
* - 最高:1085 hPa(西伯利亚高压)
*/
static Range GetPressureRange() {
return {870.0, 1085.0, "Atmospheric pressure (hPa)"};
}
/**
* @brief 执行范围检查
* @param value 观测值
* @param range 有效范围
* @return QCResult 质控结果
*/
static QCResult Check(double value, const Range& range) {
if (std::isnan(value)) {
return QCResult::Error(QCCode::MISSING_DATA, "数据缺失");
}
if (value < range.min) {
return QCResult::Error(QCCode::BELOW_MINIMUM,
fmt::format("值 {} 低于下限 {}", value, range.min));
}
if (value > range.max) {
return QCResult::Error(QCCode::ABOVE_MAXIMUM,
fmt::format("值 {} 超过上限 {}", value, range.max));
}
return QCResult::Ok();
}
};
2. 时间一致性检查(Temporal Consistency)
是
否
当前值
历史值
变化率合理?
通过
突变异常
cpp
/**
* @brief 时间一致性检查
*
* 检查数据变化率是否在合理范围内
*/
class TemporalConsistencyCheck {
public:
struct ChangeRateLimit {
double max_increase; // 最大上升速率
double max_decrease; // 最大下降速率
int time_window_minutes; // 时间窗口
};
/**
* @brief 获取温度变化率限制
*
* 参考:
* - 正常情况:1小时温度变化 < 5°C
* - 极端情况:1小时温度变化 < 15°C(冷锋过境)
*/
static ChangeRateLimit GetTemperatureChangeLimit() {
return {15.0, 15.0, 60}; // °C/小时
}
/**
* @brief 获取气压变化率限制
*
* 参考:
* - 正常情况:3小时变化 < 10 hPa
* - 极端情况:3小时变化 < 30 hPa(快速气旋发展)
*/
static ChangeRateLimit GetPressureChangeLimit() {
return {10.0, 10.0, 180}; // hPa/3小时
}
/**
* @brief 检查变化率
* @param current 当前值
* @param previous 历史值
* @param time_diff_minutes 时间差(分钟)
* @param limit 变化率限制
*/
static QCResult CheckChangeRate(double current,
double previous,
int time_diff_minutes,
const ChangeRateLimit& limit) {
if (time_diff_minutes <= 0) {
return QCResult::Ok(); // 无法计算变化率
}
double change = current - previous;
double rate = change / time_diff_minutes * limit.time_window_minutes;
if (rate > limit.max_increase) {
return QCResult::Warning(QCCode::RAPID_INCREASE,
fmt::format("变化率过快: {:.2f}/{}分钟", rate, limit.time_window_minutes));
}
if (rate < -limit.max_decrease) {
return QCResult::Warning(QCCode::RAPID_DECREASE,
fmt::format("下降率过快: {:.2f}/{}分钟", rate, limit.time_window_minutes));
}
return QCResult::Ok();
}
};
3. 空间一致性检查(Spatial Consistency)
是
否
本站数据
周边站点数据
空间一致性?
通过
空间异常
cpp
/**
* @brief 空间一致性检查
*
* 通过与周边站点数据比较,检查本站数据的空间一致性
*/
class SpatialConsistencyCheck {
public:
struct NeighborStation {
std::string station_id;
double distance_km; // 距离(公里)
double correlation; // 历史相关系数
};
/**
* @brief 执行空间一致性检查
* @param value 本站观测值
* @param neighbors 周边站点数据
* @param threshold 偏差阈值
*/
template<typename T>
static QCResult Check(double value,
const std::vector<std::pair<NeighborStation, T>>& neighbors,
double threshold) {
if (neighbors.empty()) {
return QCResult::Ok(); // 无参考站点
}
// 计算加权平均值
double weighted_sum = 0.0;
double weight_sum = 0.0;
for (const auto& [station, neighbor_value] : neighbors) {
// 权重与距离成反比,与相关系数成正比
double weight = station.correlation / (station.distance_km + 1.0);
weighted_sum += neighbor_value * weight;
weight_sum += weight;
}
double expected = weighted_sum / weight_sum;
double deviation = std::abs(value - expected);
// 考虑距离衰减的阈值
double avg_distance = 0.0;
for (const auto& [station, _] : neighbors) {
avg_distance += station.distance_km;
}
avg_distance /= neighbors.size();
double adjusted_threshold = threshold * (1.0 + avg_distance / 100.0);
if (deviation > adjusted_threshold) {
return QCResult::Warning(QCCode::SPATIAL_INCONSISTENCY,
fmt::format("与周边站点偏差过大: {:.2f} (期望: {:.2f}, 实际: {:.2f})",
deviation, expected, value));
}
return QCResult::Ok();
}
};
4. 逻辑一致性检查(Logical Consistency)
cpp
/**
* @brief 逻辑一致性检查
*
* 检查多个要素之间的逻辑关系是否合理
*/
class LogicalConsistencyCheck {
public:
/**
* @brief 温度露点一致性检查
* 露点温度 <= 实际温度
*/
static QCResult CheckTemperatureDewpoint(double temperature,
double dewpoint) {
if (dewpoint > temperature) {
return QCResult::Error(QCCode::LOGICAL_ERROR,
fmt::format("露点温度({:.1f})高于气温({:.1f})", dewpoint, temperature));
}
// 检查差值是否合理(通常<30°C)
double diff = temperature - dewpoint;
if (diff > 40.0) {
return QCResult::Warning(QCCode::LARGE_DEWPOINT_DEPRESSION,
fmt::format("温度露点差过大: {:.1f}°C", diff));
}
return QCResult::Ok();
}
/**
* @brief 风速风向一致性检查
* 风速为0时,风向应为缺失或固定值
*/
static QCResult CheckWindConsistency(double wind_speed,
int wind_direction) {
if (wind_speed == 0.0) {
// 静风时风向应为缺失或999
if (wind_direction >= 0 && wind_direction <= 360) {
return QCResult::Warning(QCCode::WIND_INCONSISTENCY,
"静风时不应有有效风向");
}
} else if (wind_speed > 0.0) {
// 有风时风向应在0-360范围内
if (wind_direction < 0 || wind_direction > 360) {
return QCResult::Error(QCCode::WIND_INCONSISTENCY,
fmt::format("无效风向: {}", wind_direction));
}
}
return QCResult::Ok();
}
/**
* @brief 降水与天气现象一致性
*/
static QCResult CheckPrecipitationWeather(double precipitation,
int weather_code) {
// 降水代码:50-59(毛毛雨),60-69(雨),70-79(雪),80-89(阵雨)
bool should_have_precip = (weather_code >= 50 && weather_code <= 99);
if (should_have_precip && precipitation == 0.0) {
return QCResult::Warning(QCCode::PRECIP_WEATHER_MISMATCH,
"天气现象指示有降水,但降水量为0");
}
if (!should_have_precip && precipitation > 0.0) {
return QCResult::Warning(QCCode::PRECIP_WEATHER_MISMATCH,
"有降水但天气现象代码不匹配");
}
return QCResult::Ok();
}
};
核心代码实现
质控结果定义
cpp
/**
* @file qc_types.hpp
* @brief 质控类型定义
*/
#ifndef ZRY_QC_TYPES_HPP
#define ZRY_QC_TYPES_HPP
#include <string>
namespace zry::qc {
/**
* @brief 质控码
*/
enum class QCCode {
// 正常状态
OK = 0, // 通过质控
// 警告级别
WARNING = 1, // 一般警告
RAPID_INCREASE = 11, // 快速上升
RAPID_DECREASE = 12, // 快速下降
SPATIAL_INCONSISTENCY = 13, // 空间不一致
LARGE_DEWPOINT_DEPRESSION = 14, // 温度露点差大
WIND_INCONSISTENCY = 15, // 风速风向不一致
PRECIP_WEATHER_MISMATCH = 16, // 降水与天气不匹配
// 错误级别
ERROR = 2, // 一般错误
MISSING_DATA = 21, // 数据缺失
BELOW_MINIMUM = 22, // 低于下限
ABOVE_MAXIMUM = 23, // 超过上限
LOGICAL_ERROR = 24, // 逻辑错误
FORMAT_ERROR = 25, // 格式错误
// 人工审核
MANUAL_CHECK = 3 // 需要人工审核
};
/**
* @brief 获取质控码描述
*/
inline std::string QCCodeToString(QCCode code) {
switch (code) {
case QCCode::OK: return "通过";
case QCCode::WARNING: return "警告";
case QCCode::ERROR: return "错误";
case QCCode::MANUAL_CHECK: return "人工审核";
case QCCode::MISSING_DATA: return "数据缺失";
case QCCode::BELOW_MINIMUM: return "低于下限";
case QCCode::ABOVE_MAXIMUM: return "超过上限";
case QCCode::RAPID_INCREASE: return "快速上升";
case QCCode::RAPID_DECREASE: return "快速下降";
case QCCode::SPATIAL_INCONSISTENCY: return "空间不一致";
case QCCode::LOGICAL_ERROR: return "逻辑错误";
default: return "未知";
}
}
/**
* @brief 质控结果
*/
struct QCResult {
QCCode code;
std::string message;
int level; // 0=正常, 1=警告, 2=错误, 3=人工
static QCResult Ok() {
return {QCCode::OK, "", 0};
}
static QCResult Warning(QCCode code, const std::string& msg) {
return {code, msg, 1};
}
static QCResult Error(QCCode code, const std::string& msg) {
return {code, msg, 2};
}
bool IsOk() const { return code == QCCode::OK; }
bool IsWarning() const { return level == 1; }
bool IsError() const { return level >= 2; }
std::string ToString() const {
return fmt::format("[{}] {}: {}",
level, QCCodeToString(code), message);
}
};
} // namespace zry::qc
#endif // ZRY_QC_TYPES_HPP
质控处理器
cpp
/**
* @file qc_processor.hpp
* @brief 质控处理器
*/
#ifndef ZRY_QC_PROCESSOR_HPP
#define ZRY_QC_PROCESSOR_HPP
#include "qc_types.hpp"
#include "qc_algorithms.hpp"
#include "../db/device/db_temp.hpp"
namespace zry::qc {
/**
* @brief 气温数据质控处理器
*/
class TemperatureQCProcessor {
public:
struct QCContext {
std::string station_num;
std::string timestamp;
std::vector<double> history_values; // 历史值,用于时间一致性检查
std::vector<std::pair<std::string, double>> neighbor_values; // 周边站点数据
};
/**
* @brief 执行气温数据质控
*/
static std::vector<QCResult> Process(double temperature,
const QCContext& context) {
std::vector<QCResult> results;
// 1. 范围检查
auto range_result = RangeCheck::Check(temperature,
RangeCheck::GetTemperatureRange());
results.push_back(range_result);
if (range_result.IsError()) {
// 范围错误,后续检查可能无意义
return results;
}
// 2. 时间一致性检查
if (!context.history_values.empty()) {
auto temporal_result = TemporalConsistencyCheck::CheckChangeRate(
temperature,
context.history_values.back(),
10, // 假设10分钟间隔
TemporalConsistencyCheck::GetTemperatureChangeLimit()
);
results.push_back(temporal_result);
}
// 3. 空间一致性检查
if (!context.neighbor_values.empty()) {
std::vector<std::pair<SpatialConsistencyCheck::NeighborStation, double>> neighbors;
for (const auto& [id, val] : context.neighbor_values) {
neighbors.push_back({{id, 10.0, 0.8}, val}); // 简化示例
}
auto spatial_result = SpatialConsistencyCheck::Check(temperature,
neighbors,
5.0);
results.push_back(spatial_result);
}
return results;
}
/**
* @brief 综合质控结果
*/
static QCCode AggregateResults(const std::vector<QCResult>& results) {
QCCode final_code = QCCode::OK;
int max_level = 0;
for (const auto& result : results) {
if (result.level > max_level) {
max_level = result.level;
final_code = result.code;
}
}
return final_code;
}
};
/**
* @brief 质控处理器管理器
*/
class QCProcessorManager {
public:
/**
* @brief 处理观测数据
*/
template<typename T>
static QCCode ProcessObservation(const T& observation) {
// 根据数据类型选择处理器
if constexpr (std::is_same_v<T, pre_YTEMP00_N01>) {
TemperatureQCProcessor::QCContext ctx;
ctx.station_num = observation.station_num;
ctx.timestamp = observation.pre_time;
// 填充历史数据和周边数据...
auto results = TemperatureQCProcessor::Process(observation.TEMPA, ctx);
return TemperatureQCProcessor::AggregateResults(results);
}
// 其他类型...
return QCCode::OK;
}
};
} // namespace zry::qc
#endif // ZRY_QC_PROCESSOR_HPP
多级质控体系
质控码编码规则
质控码结构(两位数)
质控码
十位数
质控级别
个位数
质控类型
1: 格式质控
2: 范围质控
3: 时间质控
4: 空间质控
5: 逻辑质控
0: 通过
1: 警告
2: 错误
质控结果存储
cpp
/**
* @brief 质控结果存储结构(对应数据库表)
*/
struct ObservationWithQC {
// 原始观测值
double value;
// 各级质控码
int qc_format; // 格式质控
int qc_range; // 范围质控
int qc_temporal; // 时间质控
int qc_spatial; // 空间质控
int qc_logical; // 逻辑质控
// 综合质控码(取最大值)
int qc_final;
// 质控说明
std::string qc_message;
/**
* @brief 计算综合质控码
*/
void CalculateFinalQC() {
qc_final = std::max({qc_format, qc_range, qc_temporal,
qc_spatial, qc_logical});
}
};
异常数据处理
处理策略
cpp
/**
* @brief 异常数据处理策略
*/
class AnomalyHandler {
public:
enum class Strategy {
MARK_ONLY, // 仅标记,保留原始值
FILTER, // 过滤,不存储
INTERPOLATE, // 插值修复
USE_DEFAULT // 使用默认值
};
/**
* @brief 处理异常数据
*/
template<typename T>
static void Handle(T& observation, QCCode qc_code, Strategy strategy) {
switch (strategy) {
case Strategy::MARK_ONLY:
// 仅设置质控码,保留值
observation.qc_final = static_cast<int>(qc_code);
break;
case Strategy::FILTER:
// 标记为缺失
observation.qc_final = static_cast<int>(QCCode::MISSING_DATA);
SetMissingValue(observation);
break;
case Strategy::INTERPOLATE:
// 线性插值修复
InterpolateValue(observation);
break;
case Strategy::USE_DEFAULT:
// 使用默认值
SetDefaultValue(observation);
break;
}
}
private:
template<typename T>
static void SetMissingValue(T& observation) {
if constexpr (std::is_same_v<T, pre_YTEMP00_N01>) {
observation.TEMPA = std::numeric_limits<double>::quiet_NaN();
}
// 其他类型...
}
template<typename T>
static void InterpolateValue(T& observation) {
// 从历史数据获取前后值进行线性插值
// ...
}
template<typename T>
static void SetDefaultValue(T& observation) {
// 设置合理的默认值
// ...
}
};
性能优化
批量质控处理
cpp
/**
* @brief 批量质控处理器
*/
class BatchQCProcessor {
public:
/**
* @brief 批量处理观测数据
*/
template<typename T>
static void ProcessBatch(std::vector<T>& observations) {
// 1. 按站点分组,准备空间质控数据
std::map<std::string, std::vector<T*>> station_groups;
for (auto& obs : observations) {
station_groups[obs.station_num].push_back(&obs);
}
// 2. 并行处理各站点数据
std::vector<std::future<void>> futures;
for (auto& [station_num, group] : station_groups) {
futures.push_back(std::async(std::launch::async, [&]() {
for (auto* obs : group) {
auto qc_code = QCProcessorManager::ProcessObservation(*obs);
obs->qc_final = static_cast<int>(qc_code);
}
}));
}
// 3. 等待所有任务完成
for (auto& f : futures) {
f.wait();
}
}
};
最佳实践
1. 质控阈值配置
yaml
# qc_config.yaml - 质控阈值配置
quality_control:
temperature:
range:
min: -90.0
max: 60.0
change_rate:
max_increase: 15.0 # °C/小时
max_decrease: 15.0
spatial:
max_deviation: 5.0 # 与周边站点最大偏差
humidity:
range:
min: 0.0
max: 100.0
pressure:
range:
min: 870.0
max: 1085.0
change_rate:
max_change: 10.0 # hPa/3小时
2. 告警规则
cpp
/**
* @brief 质控告警规则
*/
class QCAlertRule {
public:
/**
* @brief 检查是否需要告警
*/
static bool ShouldAlert(QCCode code, int consecutive_errors) {
// 错误级别立即告警
if (static_cast<int>(code) >= 20) {
return true;
}
// 警告级别,连续3次告警
if (static_cast<int>(code) >= 10 && consecutive_errors >= 3) {
return true;
}
return false;
}
};
总结
本文详细介绍了物联网数据质量控制系统的设计与实现:
- 质控算法:范围检查、时间一致性、空间一致性、逻辑一致性
- 多级质控:格式、范围、时间、空间、逻辑五级质控体系
- 核心实现:质控码定义、质控处理器、批量处理
- 异常处理:标记、过滤、插值、默认值策略
- 性能优化:批量处理、并行计算
关键设计决策
| 决策 | 选择 | 理由 |
|---|---|---|
| 质控级别 | 5级 | 覆盖从格式到逻辑的全面检查 |
| 质控码 | 两位数编码 | 清晰表示级别和类型 |
| 处理策略 | 可配置 | 适应不同业务需求 |
| 性能 | 批量+并行 | 支撑高并发数据流 |
该质控系统已在AIDC项目中稳定运行,日均处理百万级 观测数据,质控准确率达到99.5%。
本文基于AIDC项目数据质量控制实践编写,完整代码可在 src_refactored/include/db/ 和 src_refactored/modules/isos/ 目录查看。