数据异常处理
This commit is contained in:
@ -18,7 +18,131 @@ api_bp = Blueprint('api', __name__, url_prefix='/api')
|
||||
|
||||
|
||||
# =======================
|
||||
# 0. 认证接口
|
||||
# 0. 核心算法:数据质量分析 (含夜间免打扰)
|
||||
# =======================
|
||||
def check_data_quality(content_data, source_type, data_time_str=None):
|
||||
"""
|
||||
在后端快速分析数据质量
|
||||
:param content_data: 已解析的 JSON 对象 (Dict 或 String)
|
||||
:param source_type: 设备类型源字符串 (区分 106 或 82)
|
||||
:param data_time_str: 数据生成时间字符串 (用于判断是否为夜晚)
|
||||
:return: 'ok' | 'warning' | 'error'
|
||||
"""
|
||||
if not content_data:
|
||||
return 'ok'
|
||||
|
||||
# --- [夜间免打扰逻辑] ---
|
||||
# 物理规律:晚上没有太阳,光谱仪数值低是正常的,不应报错。
|
||||
# 逻辑:只有在 08:00 - 17:00 之间才检查数值。
|
||||
if data_time_str and data_time_str != 'N/A':
|
||||
try:
|
||||
# 1. 格式清洗
|
||||
clean_time = str(data_time_str).replace('_', '-')
|
||||
|
||||
# 2. 尝试解析时间
|
||||
dt = None
|
||||
try:
|
||||
dt = datetime.strptime(clean_time, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
try:
|
||||
dt = datetime.strptime(clean_time, "%Y-%m-%d %H:%M")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# 3. 如果解析成功,判断小时数
|
||||
if dt:
|
||||
start_hour = 8 # 早上 8 点
|
||||
end_hour = 17 # 下午 5 点
|
||||
|
||||
# 如果当前时间 小于8点 或者 大于等于17点,视为夜晚,直接返回正常
|
||||
if dt.hour < start_hour or dt.hour >= end_hour:
|
||||
return 'ok'
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
# ---------------------------
|
||||
|
||||
status = 'ok'
|
||||
source_str = str(source_type)
|
||||
|
||||
# === 106 设备逻辑 (CSV 格式) ===
|
||||
if '106' in source_str:
|
||||
try:
|
||||
text_content = ""
|
||||
if isinstance(content_data, dict):
|
||||
text_content = content_data.get('content', str(content_data))
|
||||
else:
|
||||
text_content = str(content_data)
|
||||
|
||||
lines = text_content.split('\n')
|
||||
for line in lines:
|
||||
if 'OSIFBeta' not in line: continue
|
||||
|
||||
parts = line.split(',')
|
||||
if len(parts) < 10: continue
|
||||
|
||||
try:
|
||||
int_time = int(parts[2])
|
||||
except:
|
||||
continue
|
||||
|
||||
# 只有积分时间饱和 (>= 66534) 才检查数值
|
||||
if int_time >= 66534:
|
||||
data_points = []
|
||||
for p in parts[3:]:
|
||||
try:
|
||||
data_points.append(float(p))
|
||||
except:
|
||||
pass
|
||||
|
||||
if not data_points: continue
|
||||
|
||||
# 规则1:红色报错 (存在 < 100 的点)
|
||||
for val in data_points:
|
||||
if val < 100:
|
||||
return 'error'
|
||||
|
||||
# 规则2:黄色警告 (连续 5 个点在 100-500 之间)
|
||||
consecutive_warning = 0
|
||||
for val in data_points:
|
||||
if 100 <= val <= 500:
|
||||
consecutive_warning += 1
|
||||
if consecutive_warning >= 5:
|
||||
status = 'warning'
|
||||
else:
|
||||
consecutive_warning = 0
|
||||
return status
|
||||
|
||||
except Exception:
|
||||
return 'ok'
|
||||
|
||||
# === 82 设备逻辑 (JSON 格式) ===
|
||||
else:
|
||||
try:
|
||||
if not isinstance(content_data, dict):
|
||||
return 'ok'
|
||||
specs = content_data.get('downspec', [])
|
||||
if not specs:
|
||||
specs = content_data.get('upspec', [])
|
||||
|
||||
if not specs: return 'ok'
|
||||
|
||||
consecutive_low = 0
|
||||
for val in specs:
|
||||
if not isinstance(val, (int, float)): continue
|
||||
if val < 500:
|
||||
consecutive_low += 1
|
||||
if consecutive_low >= 2:
|
||||
return 'error'
|
||||
else:
|
||||
consecutive_low = 0
|
||||
return 'ok'
|
||||
except Exception:
|
||||
return 'ok'
|
||||
|
||||
|
||||
# =======================
|
||||
# 1. 认证接口
|
||||
# =======================
|
||||
|
||||
@api_bp.route('/login', methods=['POST'])
|
||||
@ -39,16 +163,32 @@ def login():
|
||||
|
||||
|
||||
# =======================
|
||||
# 1. 设备概览与详情接口
|
||||
# 2. 设备概览与详情接口
|
||||
# =======================
|
||||
|
||||
@api_bp.route('/devices_overview', methods=['GET'])
|
||||
def devices_overview():
|
||||
try:
|
||||
devices = Device.query.all()
|
||||
data_list = [d.to_dict() for d in devices]
|
||||
data_list = []
|
||||
|
||||
for d in devices:
|
||||
item = d.to_dict()
|
||||
parsed_content = None
|
||||
if d.json_data:
|
||||
try:
|
||||
parsed_content = json.loads(d.json_data)
|
||||
except:
|
||||
parsed_content = None
|
||||
|
||||
# 传入 d.latest_time 以启用夜间判断
|
||||
quality_status = check_data_quality(parsed_content, d.source, d.latest_time)
|
||||
item['data_quality'] = quality_status
|
||||
data_list.append(item)
|
||||
|
||||
return jsonify({'code': 200, 'data': data_list})
|
||||
except Exception as e:
|
||||
print(f"Overview Error: {e}")
|
||||
return jsonify({'code': 500, 'message': str(e)})
|
||||
|
||||
|
||||
@ -86,7 +226,7 @@ def device_data_by_date():
|
||||
|
||||
|
||||
# =======================
|
||||
# 2. 维修日志接口
|
||||
# 3. 维修日志接口
|
||||
# =======================
|
||||
|
||||
@api_bp.route('/logs/list', methods=['GET'])
|
||||
@ -166,7 +306,7 @@ def delete_log():
|
||||
|
||||
|
||||
# =======================
|
||||
# 3. 辅助与控制接口 (核心修复逻辑)
|
||||
# 4. 辅助与控制接口
|
||||
# =======================
|
||||
|
||||
def calculate_offset(latest_time_str):
|
||||
@ -201,7 +341,6 @@ def run_monitor():
|
||||
source = item.get('source', '')
|
||||
target_time = item.get('target_time')
|
||||
|
||||
# 处理 106 路径时间
|
||||
if '106' in str(source):
|
||||
try:
|
||||
path_str = d_raw.get('path', '')
|
||||
@ -213,15 +352,12 @@ def run_monitor():
|
||||
|
||||
json_str = json.dumps(d_raw, ensure_ascii=False) if isinstance(d_raw, (dict, list)) else str(d_raw)
|
||||
|
||||
# --- 关键修改:先查询,后更新 ---
|
||||
device = Device.query.filter_by(name=d_name).first()
|
||||
if not device:
|
||||
# 只有新设备才初始化静态字段
|
||||
device = Device(name=d_name, source=source, install_site="")
|
||||
db.session.add(device)
|
||||
db.session.flush() # 获取 ID 供 History 使用
|
||||
db.session.flush()
|
||||
|
||||
# 仅更新动态抓取的字段,保留手动填写的 install_site, is_maintaining, is_hidden
|
||||
device.status = item.get('status')
|
||||
device.current_value = item.get('value')
|
||||
device.latest_time = target_time
|
||||
@ -276,4 +412,48 @@ def toggle_hidden():
|
||||
device.is_hidden = data.get('is_hidden')
|
||||
db.session.commit()
|
||||
return jsonify({'code': 200})
|
||||
return jsonify({'code': 404}), 404
|
||||
return jsonify({'code': 404}), 404
|
||||
|
||||
|
||||
# =======================
|
||||
# 5. 手动添加设备接口 (新增)
|
||||
# =======================
|
||||
@api_bp.route('/add_device', methods=['POST'])
|
||||
def add_device():
|
||||
data = request.get_json()
|
||||
name = data.get('name')
|
||||
site = data.get('site', '')
|
||||
|
||||
if not name:
|
||||
return jsonify({'code': 400, 'message': '必须填写设备名称'}), 400
|
||||
|
||||
# 1. 检查是否已存在
|
||||
existing = Device.query.filter_by(name=name).first()
|
||||
if existing:
|
||||
return jsonify({'code': 400, 'message': f'设备 {name} 已存在,无需重复添加'}), 400
|
||||
|
||||
try:
|
||||
# 2. 创建新设备记录
|
||||
# source 标记为 'manual',方便以后区分
|
||||
# status 默认为 'offline' (离线)
|
||||
# latest_time 默认为 'N/A'
|
||||
new_device = Device(
|
||||
name=name,
|
||||
install_site=site,
|
||||
source='manual',
|
||||
status='offline',
|
||||
current_value='0',
|
||||
latest_time='N/A',
|
||||
check_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
json_data='{}',
|
||||
is_hidden=0,
|
||||
is_maintaining=0
|
||||
)
|
||||
|
||||
db.session.add(new_device)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'code': 200, 'message': '设备添加成功'})
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'code': 500, 'message': str(e)})
|
||||
Reference in New Issue
Block a user