Files
ZDXX/2_1banben/models.py
DXC 783c633a15 feat: 时效判定结构化下沉到后端,修复状态映射漏掉 offline
背景:此前「滞后几天」在后端(calculate_offset)和前端(Dashboard.vue 自算
diffDays/diffHours)各有一套实现,阈值与粒度都不同,跨日必然打架 —— 昨天
23:00 的数据在次日 01:00 查看,后端说「滞后 1 天」,前端标「昨日数据」。

services/time_utils.py:
- 新增 staleness_of(),统一返回 {level, days, text},level 分
  unknown / ok / yesterday / lagging / severe 五级。
- 日期解析改用正则抠 YYYY-MM-DD,分隔符兼容 - / _,因此 ISO 的
  2026-09-15T08:30:00Z、2026/09/15、2026_09_15 都能识别。
  原来的 split(' ')[0] + strptime('%Y-%m-%d') 遇到 ISO T 或斜杠会直接返回
  「时间解析失败」,而 82 爬虫的 target_time 是原样透传接口返回值的,格式
  不可控。
- calculate_offset 保留为兼容入口,内部委托 staleness_of。

models.py:
- to_dict() 新增 stale_level / stale_days。
- 修复状态映射:白名单原来只有 ['离线','异常','已离线'],漏了 'offline',
  而 /add_device 创建手动设备写的正是 status='offline',导致手动添加的设备
  被映射成 online、在面板上永远显示「在线」。改为大小写归一后比对,并纳入
  unknown/未知。

实测:本地库 status 分布由 online:103/offline:2 修正为 online:99/offline:6。
2026-09-15 17:10:53 +08:00

106 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# models.py
from datetime import datetime
from extensions import db
# services.time_utils 只依赖 re/datetime不会与 extensions/models 形成循环导入
from services.time_utils import staleness_of
class Device(db.Model):
__tablename__ = 'devices'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True, index=True)
source = db.Column(db.String(50))
# 快照字段(爬虫更新)
status = db.Column(db.String(50))
current_value = db.Column(db.String(200))
latest_time = db.Column(db.String(50))
json_data = db.Column(db.Text)
check_time = db.Column(db.String(50))
reason = db.Column(db.String(255))
offset = db.Column(db.String(50))
# ✅ 新增字段:文件数量
file_count = db.Column(db.Integer, default=0)
# 手动录入字段受保护run_monitor 不主动覆盖)
install_site = db.Column(db.String(100), default="")
is_maintaining = db.Column(db.Boolean, default=False)
is_hidden = db.Column(db.Boolean, default=False)
# 白名单字段 (根据上下文可能存在,补全以防万一)
is_whitelist = db.Column(db.Boolean, default=False)
def to_dict(self):
# 状态归一:中英文各种写法都要覆盖。
# 注意 'offline' 是 /add_device 创建手动设备时写入的值,原先的白名单
# 漏了它,导致手动添加的设备被映射成 online、在面板上永远显示“在线”。
raw_status = str(self.status or '').strip().lower()
is_offline = raw_status in (
'离线', '异常', '已离线', 'offline', 'unknown', '未知'
)
api_status = 'offline' if is_offline else 'online'
# 数据时效在读取时实时计算,不读 offset 列。
# 该列是写入时刻的快照,采集一停摆就整体腐烂(库里停在 2026-02-06
# offset 却仍显示“当天”)。列保留但已废弃,见 services/db_ingest.py。
stale = staleness_of(self.latest_time)
return {
'id': self.id,
'name': self.name,
'source': self.source,
'latest_time': self.latest_time,
'status': api_status,
'status_text': self.status,
'value': self.current_value,
'reason': self.reason,
'install_site': self.install_site or '',
'is_maintaining': self.is_maintaining,
'is_hidden': self.is_hidden,
'is_whitelist': self.is_whitelist,
# 时效三件套:前端不再自己算滞后天数,统一消费这三个字段
'offset': stale['text'], # 中文描述,兼容旧调用方
'stale_level': stale['level'], # unknown/ok/yesterday/lagging/severe
'stale_days': stale['days'], # 滞后自然日数unknown 时为 None
'file_count': self.file_count # ✅ 返回给前端
}
class DeviceHistory(db.Model):
__tablename__ = 'device_history'
id = db.Column(db.Integer, primary_key=True)
device_id = db.Column(db.Integer, db.ForeignKey('devices.id'))
data_time = db.Column(db.String(50))
status = db.Column(db.String(50))
result_data = db.Column(db.String(200), default="")
json_data = db.Column(db.Text)
file_path = db.Column(db.String(255))
# ✅ 新增字段:历史记录文件数量
file_count = db.Column(db.Integer, default=0)
recorded_at = db.Column(db.DateTime, default=datetime.now)
class MaintenanceLog(db.Model):
__tablename__ = 'maintenance_logs'
id = db.Column(db.Integer, primary_key=True)
device_name = db.Column(db.String(100), nullable=False)
engineer = db.Column(db.String(50))
location = db.Column(db.String(100))
content = db.Column(db.Text)
timestamp = db.Column(db.DateTime, default=datetime.now)
def to_dict(self):
return {
'id': self.id,
'device_name': self.device_name,
'engineer': self.engineer or '',
'location': self.location or '',
'content': self.content,
'timestamp': self.timestamp.strftime('%Y-%m-%d %H:%M:%S')
}