Files
railway_cloud/app/utils/get_operating_mode.py
2026-02-06 18:17:05 +08:00

403 lines
22 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.
from datetime import datetime, date
class OperatingModePredictor:
"""
工况预测类(处理二维倒序数据,返回一维列表,仅保留各内嵌列表最新记录)
功能根据输入的带时间序列的工况数据推导每个监测点point_id的下一阶段工况
特性:
1. 输入为二维列表每个内嵌列表对应一个point_id且为倒序排列最新记录在索引0
2. 输出为一维列表,仅保留每个内嵌列表的最新记录,新增工况推导结果字段
3. 无需切换工况时next_workinfo返回当前工况名称需要切换时返回目标工况名称
4. 时间计算仅按日期(天)维度,忽略时分秒
5. 支持冬休场景:当前工况为冬休时,以冬休前上一个有效工况作为切换判断依据
"""
def __init__(self):
"""初始化类,加载内置的工况配置、分组规则和切换触发规则"""
# 基础工况配置更新为最新的工况列表重复key保留后面的值
self.base_periods = self._load_base_periods()
# 工况分组(同步更新分组规则,匹配最新工况名称)
self.condition_group = self._load_condition_group()
# 工况切换触发规则分组ID触发天数+目标工况候选)
self.group_transition_rules = self._load_group_transition_rules()
# 冬休工况标识(仅保留“冬休”)
self.winter_break_labels = self._load_winter_break_labels()
def _load_base_periods(self):
"""加载基础工况配置更新为最新工况列表重复key自动保留后面的值"""
return {
"路基或预压土填筑,连续填筑":1,
"路基或预压土填筑,两次填筑间隔时间较长":7,
"预压土或路基填筑完成第1~3个月":7,
"预压土或路基填筑完成第4~6个月":14,
"仰拱底板施工完成后第1个月": 7,
"预压土或路基填筑完成6个月以后":30,
"仰拱底板施工完成后第2至3个月": 14,
"仰拱底板施工完成后3个月以后": 30,
"仰拱(底板)施工完成后第1个月": 7, # 重复key保留此值
"仰拱(底板)施工完成后第2至3个月": 14, # 重复key保留此值
"仰拱(底板)施工完成后3个月以后": 30, # 重复key保留此值
"无砟轨道铺设后第1至3个月": 30, # 重复key保留此值
"无砟轨道铺设后4至12个月": 90, # 重复key保留此值
"无砟轨道铺设后12个月以后": 180, # 重复key保留此值
"墩台施工到一定高度": 30,
"墩台混凝土施工": 30,
"预制梁桥,架梁前": 30, # 重复key保留此值
"预制梁桥,预制梁架设前": 1, # 重复key保留此值
"预制梁桥,预制梁架设后": 7,
"桥位施工桥梁,制梁前": 30,
"桥位施工桥梁,上部结构施工中": 1,
"架桥机(运梁车)通过": 2, # 重复key保留此值原7→2
"桥梁主体工程完工后,第1至3个月": 7,
"桥梁主体工程完工后第4至6个月": 14, # 重复key保留此值
"桥梁主体工程完工后,6个月以后": 30, # 重复key保留此值
"轨道铺设期间,前": 30,
"轨道铺设期间,后": 14,
"轨道铺设完成后第1个月": 14, # 重复key保留此值
"轨道铺设完成后2至3个月": 30, # 重复key保留此值
"轨道铺设完成后4至12个月": 90, # 重复key保留此值
"轨道铺设完成后12个月以后": 180, # 重复key保留此值
"铺路或堆载,一般情况": 1,
"填筑或堆载,一般情况": 1,
"铺路或堆载,沉降量突变情况": 1,
"填筑或堆载,两次填筑间隔时间较长情况":3,
"铺路或堆载,两次铺路间隔时间较长情况": 3,
"堆载预压或路基填筑完成第1至3个月":7,
"堆载预压或路基填筑完成第4至6个月": 14,
"堆载预压或路基填筑完成6个月以后": 30,
"架桥机(运梁车) 首次通过前": 1,
"架桥机(运梁车) 首次通过后前3天": 1,
"架桥机(运梁车) 首次通过后": 7,
"轨道板(道床)铺设后第1个月": 14,
"轨道板(道床)铺设后第2至3个月": 30,
"轨道板(道床)铺设后3个月以后": 90,
"架桥机(运梁车)首次通过前": 1,
"架桥机(运梁车)首次通过后前3天": 1,
"架桥机(运梁车)首次通过后": 7,
"轨道板铺设前": 14, # 新增工况
"轨道板(道床)铺设后第1至3个月": 14, # 新增工况
"轨道板(道床)铺设后第4至6个月": 30, # 新增工况
"轨道板(道床)铺设后6个月以后": 90, # 新增工况
"站场填方路基段填筑完成至静态验收": 14, # 新增工况
"桥墩(台)地面处拆模后": 30, # 新增工况
"敦身混凝土施工": 30, # 新增工况
"预制梁桥预制梁架设后": 7, # 新增工况
"现浇梁,浇筑前": 30, # 新增工况
"现浇梁上部结构施工中": 1, # 新增工况
"侨梁主体工程完工后6个月以后": 30, # 新增工况(注意错别字)
"轨道铺设,前": 30, # 新增工况
"轨道铺设,后": 14, # 新增工况
"仰拱(底板)施工完成后第2至3个月": 14, # 新增工况(标点位置异常)
"特殊地段隧道施工完成后至静态验收": 14, # 新增工况
"冬休": 0 # 保留冬休工况
}
def _load_condition_group(self):
"""加载工况分组规则(同步更新,匹配最新工况名称)"""
condition_group = {
# 仰拱相关
"仰拱底板施工完成后第1个月": "YG_DIBAN_1",
"仰拱(底板)施工完成后第1个月": "YG_DIBAN_1",
"仰拱底板施工完成后第2至3个月": "YG_DIBAN_2_3",
"仰拱(底板)施工完成后第2至3个月": "YG_DIBAN_2_3",
"仰拱底板施工完成后3个月以后": "YG_DIBAN_AFTER_3",
"仰拱(底板)施工完成后3个月以后": "YG_DIBAN_AFTER_3",
"仰拱(底板)施工完成后第2至3个月": "YG_DIBAN_2_3", # 新增异常格式工况分组
# 架桥机相关
"架桥机(运梁车) 首次通过前": "JQJ_FIRST_BEFORE",
"架桥机(运梁车)首次通过前": "JQJ_FIRST_BEFORE", # 新增无空格版本
"架桥机(运梁车) 首次通过后前3天": "JQJ_FIRST_AFTER_3D",
"架桥机(运梁车)首次通过后前3天": "JQJ_FIRST_AFTER_3D", # 新增无空格版本
"架桥机(运梁车) 首次通过后": "JQJ_FIRST_AFTER",
"架桥机(运梁车)首次通过后": "JQJ_FIRST_AFTER", # 新增无空格版本
"架桥机(运梁车)通过": "STATIC",
# 堆载预压/路基填筑相关
"堆载预压或路基填筑完成第1至3个月": "DZYY_1_3",
"堆载预压或路基填筑完成第4至6个月": "DZYY_4_6",
"堆载预压或路基填筑完成6个月以后": "DZYY_AFTER_6",
"路基或预压土填筑,连续填筑": "STATIC",
"路基或预压土填筑,两次填筑间隔时间较长": "STATIC",
"预压土或路基填筑完成第1~3个月": "DZYY_1_3",
"预压土或路基填筑完成第4~6个月": "DZYY_4_6",
"预压土或路基填筑完成6个月以后": "DZYY_AFTER_6",
# 轨道板相关
"轨道板(道床)铺设后第1个月": "GDB_1",
"轨道板(道床)铺设后第2至3个月": "GDB_2_3",
"轨道板(道床)铺设后3个月以后": "GDB_AFTER_3",
"轨道板铺设前": "STATIC",
"轨道板(道床)铺设后第1至3个月": "GDB_1",
"轨道板(道床)铺设后第4至6个月": "GDB_2_3",
"轨道板(道床)铺设后6个月以后": "GDB_AFTER_3",
# 预制梁桥相关
"预制梁桥,架梁前": "STATIC",
"预制梁桥,预制梁架设前": "YZLQ_BEFORE_JS",
"预制梁桥,预制梁架设后": "YZLQ_AFTER_JS",
"预制梁桥预制梁架设后": "YZLQ_AFTER_JS", # 新增工况分组
# 桥梁主体相关
"桥梁主体工程完工后,第1至3个月": "QL_ZHUTI_1_3",
"桥梁主体工程完工后第4至6个月": "QL_ZHUTI_4_6",
"桥梁主体工程完工后,6个月以后": "QL_ZHUTI_AFTER_6",
"侨梁主体工程完工后6个月以后": "QL_ZHUTI_AFTER_6", # 错别字工况分组
# 轨道铺设相关
"轨道铺设完成后第1个月": "GD_1",
"轨道铺设完成后2至3个月": "GD_2_3",
"轨道铺设完成后4至12个月": "GD_4_12",
"轨道铺设完成后12个月以后": "GD_AFTER_12",
"轨道铺设期间,前": "STATIC",
"轨道铺设期间,后": "STATIC",
"轨道铺设,前": "STATIC",
"轨道铺设,后": "STATIC",
# 无砟轨道相关
"无砟轨道铺设后第1至3个月": "WZGD_1_3",
"无砟轨道铺设后4至12个月": "WZGD_4_12",
"无砟轨道铺设后12个月以后": "WZGD_AFTER_12",
# 静态工况(无切换规则)
"墩台施工到一定高度": "STATIC",
"墩台混凝土施工": "STATIC",
"桥位施工桥梁,制梁前": "STATIC",
"桥位施工桥梁,上部结构施工中": "STATIC",
"铺路或堆载,一般情况": "STATIC",
"填筑或堆载,一般情况": "STATIC",
"铺路或堆载,沉降量突变情况": "STATIC",
"填筑或堆载,两次填筑间隔时间较长情况": "STATIC",
"铺路或堆载,两次铺路间隔时间较长情况": "STATIC",
"站场填方路基段填筑完成至静态验收": "STATIC",
"桥墩(台)地面处拆模后": "STATIC",
"敦身混凝土施工": "STATIC",
"现浇梁,浇筑前": "STATIC",
"现浇梁上部结构施工中": "STATIC",
"特殊地段隧道施工完成后至静态验收": "STATIC",
"冬休": "STATIC"
}
return condition_group
def _load_group_transition_rules(self):
"""加载工况切换触发规则(保持原有规则,适配新增工况)"""
return {
"YG_DIBAN_1": {"trigger_days": 30, "next_candidates": ["仰拱底板施工完成后第2至3个月", "仰拱(底板)施工完成后第2至3个月"]},
"YG_DIBAN_2_3": {"trigger_days": 60, "next_candidates": ["仰拱底板施工完成后3个月以后", "仰拱(底板)施工完成后3个月以后"]},
"YG_DIBAN_AFTER_3": {"trigger_days": None, "next_candidates": None},
"JQJ_FIRST_BEFORE": {"trigger_days": 1, "next_candidates": ["架桥机(运梁车) 首次通过后前3天"]},
"JQJ_FIRST_AFTER_3D": {"trigger_days": 3, "next_candidates": ["架桥机(运梁车) 首次通过后"]},
"JQJ_FIRST_AFTER": {"trigger_days": None, "next_candidates": None},
"DZYY_1_3": {"trigger_days": 90, "next_candidates": ["堆载预压或路基填筑完成第4至6个月"]},
"DZYY_4_6": {"trigger_days": 90, "next_candidates": ["堆载预压或路基填筑完成6个月以后"]},
"DZYY_AFTER_6": {"trigger_days": None, "next_candidates": None},
"GDB_1": {"trigger_days": 30, "next_candidates": ["轨道板(道床)铺设后第2至3个月"]},
"GDB_2_3": {"trigger_days": 30, "next_candidates": ["轨道板(道床)铺设后3个月以后"]},
"GDB_AFTER_3": {"trigger_days": None, "next_candidates": None},
"YZLQ_BEFORE_JS": {"trigger_days": 1, "next_candidates": ["架桥机(运梁车)通过"]},
"YZLQ_AFTER_JS": {"trigger_days": 7, "next_candidates": ["桥梁主体工程完工后,第1至3个月"]},
"QL_ZHUTI_1_3": {"trigger_days": 90, "next_candidates": ["桥梁主体工程完工后第4至6个月"]},
"QL_ZHUTI_4_6": {"trigger_days": 90, "next_candidates": ["桥梁主体工程完工后6个月以后"]},
"QL_ZHUTI_AFTER_6": {"trigger_days": None, "next_candidates": None},
"GD_1": {"trigger_days": 30, "next_candidates": ["轨道铺设完成后2至3个月"]},
"GD_2_3": {"trigger_days": 60, "next_candidates": ["轨道铺设完成后4至12个月"]},
"GD_4_12": {"trigger_days": 240, "next_candidates": ["轨道铺设完成后12个月以后"]},
"GD_AFTER_12": {"trigger_days": None, "next_candidates": None},
"WZGD_1_3": {"trigger_days": 90, "next_candidates": ["无砟轨道铺设后4至12个月"]},
"WZGD_4_12": {"trigger_days": 240, "next_candidates": ["无砟轨道铺设后12个月以后"]},
"WZGD_AFTER_12": {"trigger_days": None, "next_candidates": None},
"STATIC": {"trigger_days": None, "next_candidates": None}
}
def _load_winter_break_labels(self):
"""冬休标识(仅冬休)"""
return {"冬休"}
def _parse_to_date(self, time_str):
"""
私有辅助方法:将时间字符串解析为日期对象,仅保留年月日,忽略时分秒
:param time_str: 时间字符串,格式为 "YYYY-MM-DD HH:MM:SS"
:return: date对象 / None解析失败时
"""
if not time_str:
return None
try:
dt = datetime.strptime(str(time_str).strip(), "%Y-%m-%d %H:%M:%S")
return dt.date()
except ValueError:
return None
def _get_time_statistics_from_reversed(self, data, workinfo):
"""
私有辅助方法:从倒序数据中提取时间统计信息
:param data: 单个point_id的倒序数据列表
:param workinfo: 目标工况名称(冬休时为冬休前上一工况)
:return: 元组 (首次测量日期date对象, 首次到末次持续天数, 今日与首次测量天数差)
"""
# 筛选目标工况的有效记录(忽略冬休)
target_records = [
d for d in data
if d.get("workinfoname") == workinfo
and d.get("workinfoname") not in self.winter_break_labels
]
if not target_records:
return None, 0, 0
target_dates = []
for item in target_records:
d = self._parse_to_date(item.get("MTIME_W"))
if d:
target_dates.append(d)
if not target_dates:
return None, 0, 0
last_date = target_dates[0]
first_date = target_dates[-1]
cumulative_days = (last_date - first_date).days
today = date.today()
days_to_today = (today - first_date).days if first_date else 0
return first_date, cumulative_days, days_to_today
def _get_pre_winter_break_workinfo(self, inner_data_list):
"""
私有辅助方法:从倒序数据中提取冬休前的上一个有效工况
:param inner_data_list: 单个point_id的倒序数据列表
:return: 冬休前工况名称 / None未找到时
"""
if not inner_data_list:
return None
# 倒序遍历,跳过冬休,找第一个有效工况
for record in inner_data_list:
current_work = record.get("workinfoname")
if current_work and current_work not in self.winter_break_labels and current_work in self.base_periods:
return current_work
return None
def _match_next_condition(self, current_name, candidates):
"""
私有辅助方法:按符号风格匹配下一个工况名称(全角/半角括号对应)
:param current_name: 当前工况名称(冬休时为冬休前上一工况)
:param candidates: 目标工况候选列表
:return: 匹配的工况名称 / 当前工况名称(无候选时)
"""
if not candidates:
return current_name
if "" in current_name:
for cand in candidates:
if "" in cand:
return cand
if "(" in current_name:
for cand in candidates:
if "(" in cand:
return cand
return candidates[0]
def _validate_point_id(self, inner_list):
"""
私有辅助方法校验内嵌列表内所有元素的point_id是否一致
:param inner_list: 单个point_id的倒序数据列表
:return: point_id / None校验失败或列表为空时
"""
if not inner_list:
return None
base_point_id = inner_list[0].get("point_id")
for item in inner_list:
if item.get("point_id") != base_point_id:
return None
return base_point_id
def predict(self, data_2d_list):
"""
公有核心方法:执行工况预测,处理二维输入数据,返回一维结果列表
:param data_2d_list: 二维倒序数据列表,格式 [[{},{},{}], [{},{},{}]]
:return: 一维结果列表,格式 [{}, {}, {}],每个元素为对应内嵌列表的最新记录+推导字段
"""
final_result_1d = []
for inner_data_list in data_2d_list:
if not isinstance(inner_data_list, list) or len(inner_data_list) == 0:
continue
latest_record = inner_data_list[0].copy()
point_id = self._validate_point_id(inner_data_list)
if not point_id:
latest_record.update({
"status": "fail",
"current_workinfo": None,
"first_measure_date": None,
"days_from_first_to_today": None,
"next_workinfo": None,
"judge_based_workinfo": None,
"error_msg": "point_id不一致或缺失"
})
final_result_1d.append(latest_record)
continue
current_workinfo = latest_record.get("workinfoname")
if not current_workinfo or current_workinfo not in self.base_periods:
latest_record.update({
"status": "fail",
"current_workinfo": None,
"first_measure_date": None,
"days_from_first_to_today": None,
"next_workinfo": None,
"judge_based_workinfo": None,
"error_msg": "工况无效或缺失"
})
final_result_1d.append(latest_record)
continue
# 冬休逻辑:当前是冬休 → 取冬休前工况作为判断基准
if current_workinfo in self.winter_break_labels:
judge_based_workinfo = self._get_pre_winter_break_workinfo(inner_data_list)
if not judge_based_workinfo:
latest_record.update({
"status": "fail",
"current_workinfo": current_workinfo,
"first_measure_date": None,
"days_from_first_to_today": None,
"next_workinfo": None,
"judge_based_workinfo": None,
"error_msg": "冬休前未找到有效工况"
})
final_result_1d.append(latest_record)
continue
else:
judge_based_workinfo = current_workinfo
# 用判断基准工况计算时间
first_dt, cumulative_days, days_to_today = self._get_time_statistics_from_reversed(
inner_data_list, judge_based_workinfo
)
first_measure_date = first_dt.strftime("%Y-%m-%d") if first_dt else None
# 推导下一工况
group_id = self.condition_group.get(judge_based_workinfo, "STATIC")
rule = self.group_transition_rules.get(group_id, {})
trigger_days = rule.get("trigger_days")
next_candidates = rule.get("next_candidates", [])
if trigger_days is not None and cumulative_days >= trigger_days:
next_workname = self._match_next_condition(judge_based_workinfo, next_candidates)
else:
next_workname = judge_based_workinfo
# 组装结果
latest_record.update({
"status": "success",
"current_workinfo": current_workinfo,
"judge_based_workinfo": judge_based_workinfo,
"first_measure_date": first_measure_date,
"days_from_first_to_today": days_to_today,
"next_workinfo": next_workname,
"point_id": point_id,
"error_msg": ""
})
final_result_1d.append(latest_record)
return final_result_1d