410 lines
20 KiB
Python
410 lines
20 KiB
Python
from datetime import datetime, date
|
||
|
||
class OperatingModePredictor:
|
||
"""
|
||
工况预测类(处理二维倒序数据,返回一维列表,仅保留各内嵌列表最新记录)
|
||
功能:根据输入的带时间序列的工况数据,推导每个监测点(point_id)的下一阶段工况
|
||
特性:
|
||
1. 输入为二维列表,每个内嵌列表对应一个point_id,且为倒序排列(最新记录在索引0)
|
||
2. 输出为一维列表,仅保留每个内嵌列表的最新记录,新增工况推导结果字段
|
||
3. 推导规则:有等效映射返回新工况,无等效保留旧工况;切换下一工况以base_periods为准
|
||
4. 时间计算仅按日期(天)维度,忽略时分秒
|
||
5. 冬休场景:仅以冬休前上一有效工况为判断基准,切换规则与非冬休完全一致
|
||
6. 适配中英文括号、逗号、空格,内部标准化匹配,外部返回规范名称
|
||
"""
|
||
|
||
def __init__(self):
|
||
"""初始化类,加载核心配置"""
|
||
# 1. 基础工况配置(最终返回的规范名称,含所有新旧工况)
|
||
self.base_periods = self._load_base_periods()
|
||
# 2. 旧→新等效映射(优先返回新工况)
|
||
self.old_to_new_map = self._load_old_to_new_map()
|
||
# 3. 工况分组(同义工况归为同一分组,复用切换规则)
|
||
self.condition_group = self._load_condition_group()
|
||
# 4. 切换触发规则(沿用原逻辑,触发天数+目标工况)
|
||
self.transition_rules = self._load_transition_rules()
|
||
# 5. 冬休标识
|
||
self.winter_break_labels = {"冬休"}
|
||
|
||
# 辅助映射:标准化名称→base_periods中的规范名称(用于最终返回)
|
||
self.std_to_canonical = {
|
||
self._standardize_name(name): name for name in self.base_periods.keys()
|
||
}
|
||
# 辅助映射:标准化名称→分组ID
|
||
self.std_to_group = {
|
||
self._standardize_name(name): group_id
|
||
for name, group_id in self.condition_group.items()
|
||
}
|
||
|
||
def _standardize_name(self, name):
|
||
"""
|
||
标准化工况名称(内部匹配用):去空格、统一中英文符号
|
||
:param name: 原始工况名称
|
||
:return: 标准化后的名称
|
||
"""
|
||
if not name:
|
||
return ""
|
||
# 去所有空格
|
||
std_name = name.replace(" ", "").strip()
|
||
# 统一中英文符号
|
||
replace_map = {
|
||
"(": "(", ")": ")", ",": ",", "。": ",", "~": "至",
|
||
";": ";", ":": ":", "'": "'", """: "\""
|
||
}
|
||
for old, new in replace_map.items():
|
||
std_name = std_name.replace(old, new)
|
||
return std_name
|
||
|
||
def _load_base_periods(self):
|
||
"""加载基础工况配置(最终返回的规范名称,无多余数字)"""
|
||
return {
|
||
# 路基工况(新工况优先)
|
||
"路基或预压土填筑,连续填筑": 1,
|
||
"路基或预压土填筑,两次填筑间隔时间较长": 7,
|
||
"预压土或路基填筑完成,第1~3个月": 7,
|
||
"预压土或路基填筑完成,第4~6个月": 14,
|
||
"预压土或路基填筑完成,6个月以后": 30,
|
||
"架桥机(运梁车)首次通过前": 1,
|
||
"架桥机(运梁车)首次通过后,前3天": 1,
|
||
"架桥机(运梁车)首次通过后": 7,
|
||
"轨道板(道床)铺设后,第1至3个月": 14,
|
||
"轨道板(道床)铺设后,第4至6个月": 30,
|
||
"轨道板(道床)铺设后,6个月以后": 90,
|
||
|
||
# 路基旧工况(保留,无等效则返回)
|
||
"填筑或堆载,一般情况": 1,
|
||
"填筑或堆载,两次填筑间隔时间较长情况": 7,
|
||
"堆载预压或路基填筑完成,6个月以后": 30,
|
||
"轨道板(道床)铺设后,第1个月": 14,
|
||
"轨道板(道床)铺设后,第2至3个月": 30,
|
||
"轨道板(道床)铺设后,3个月以后": 90,
|
||
|
||
# 桥梁工况(新工况优先)
|
||
"桥墩(台)地面处拆模后": 30,
|
||
"敦身混凝土施工": 30,
|
||
"预制梁桥,预制梁架设前": 1,
|
||
"预制梁桥,预制梁架设后": 7,
|
||
"现浇梁,浇筑前": 30,
|
||
"现浇梁上部结构施工中": 1,
|
||
"架桥机(运梁车)通过": 2,
|
||
"桥梁主体工程完工后,第1至3个月": 7,
|
||
"桥梁主体工程完工后,第4至6个月": 14,
|
||
"桥梁主体工程完工后,6个月以后": 30,
|
||
"轨道铺设,前": 30,
|
||
"轨道铺设,后": 14,
|
||
"轨道铺设完成后,第1个月": 14,
|
||
"轨道铺设完成后,2至3个月": 30,
|
||
"轨道铺设完成后,4至12个月": 90,
|
||
"轨道铺设完成后,12个月以后": 180,
|
||
|
||
# 桥梁旧工况(保留,无等效则返回)
|
||
"墩台施工到一定高度": 30,
|
||
"墩台混凝土施工": 30,
|
||
"预制梁桥,架梁前": 30,
|
||
"桥位施工桥梁,制梁前": 30,
|
||
"桥位施工桥梁,上部结构施工中": 1,
|
||
"桥梁主体工程完工后,第1至3个月": 7,
|
||
"轨道铺设期间,前": 30,
|
||
"轨道铺设期间,后": 14,
|
||
|
||
# 隧道工况
|
||
"仰拱(底板)施工完成后,第1个月": 7,
|
||
"仰拱(底板)施工完成后,第2至3个月": 14,
|
||
"仰拱(底板)施工完成后,3个月以后": 30,
|
||
"无砟轨道铺设后,第1至3个月": 30,
|
||
"无砟轨道铺设后,4至12个月": 90,
|
||
"无砟轨道铺设后,12个月以后": 180,
|
||
|
||
# 特殊工况
|
||
"冬休": 0
|
||
}
|
||
|
||
def _load_old_to_new_map(self):
|
||
"""加载旧→新等效映射(优先返回新工况)"""
|
||
return {
|
||
# 路基等效
|
||
"填筑或堆载,一般情况": "路基或预压土填筑,连续填筑",
|
||
"填筑或堆载,两次填筑间隔时间较长情况": "路基或预压土填筑,两次填筑间隔时间较长",
|
||
"预压土或路基填筑完成。第1~3个月": "预压土或路基填筑完成,第1~3个月",
|
||
"堆载预压或路基填筑完成,6个月以后": "预压土或路基填筑完成,6个月以后",
|
||
"轨道板(道床)铺设后,第1个月": "轨道板(道床)铺设后,第1至3个月",
|
||
"轨道板(道床)铺设后,第2至3个月": "轨道板(道床)铺设后,第4至6个月",
|
||
"轨道板(道床)铺设后,3个月以后": "轨道板(道床)铺设后,6个月以后",
|
||
|
||
# 桥梁等效
|
||
"墩台施工到一定高度": "桥墩(台)地面处拆模后",
|
||
"墩台混凝土施工": "敦身混凝土施工",
|
||
"桥位施工桥梁,制梁前": "现浇梁,浇筑前",
|
||
"桥位施工桥梁,上部结构施工中": "现浇梁上部结构施工中",
|
||
"桥梁主体工程完工后,第1至3个月": "桥梁主体工程完工后,第1至3个月",
|
||
"轨道铺设期间,前": "轨道铺设,前",
|
||
"轨道铺设期间,后": "轨道铺设,后"
|
||
}
|
||
|
||
def _load_condition_group(self):
|
||
"""加载工况分组(同义工况归为同一分组)"""
|
||
group_map = {
|
||
# 路基分组
|
||
"路基或预压土填筑,连续填筑": "DZ_CONTINUE",
|
||
"路基或预压土填筑,两次填筑间隔时间较长": "DZ_INTERVAL",
|
||
"预压土或路基填筑完成,第1~3个月": "DZ_FINISH_1_3",
|
||
"预压土或路基填筑完成,第4~6个月": "DZ_FINISH_4_6",
|
||
"预压土或路基填筑完成,6个月以后": "DZ_FINISH_AFTER_6",
|
||
"架桥机(运梁车)首次通过前": "JQJ_FIRST_BEFORE",
|
||
"架桥机(运梁车)首次通过后,前3天": "JQJ_FIRST_AFTER_3D",
|
||
"架桥机(运梁车)首次通过后": "JQJ_FIRST_AFTER",
|
||
"轨道板(道床)铺设后,第1至3个月": "GDB_FINISH_1_3",
|
||
"轨道板(道床)铺设后,第4至6个月": "GDB_FINISH_4_6",
|
||
"轨道板(道床)铺设后,6个月以后": "GDB_FINISH_AFTER_6",
|
||
|
||
# 路基旧工况分组(复用新工况分组)
|
||
"填筑或堆载,一般情况": "DZ_CONTINUE",
|
||
"填筑或堆载,两次填筑间隔时间较长情况": "DZ_INTERVAL",
|
||
"堆载预压或路基填筑完成,6个月以后": "DZ_FINISH_AFTER_6",
|
||
"轨道板(道床)铺设后,第1个月": "GDB_FINISH_1_3",
|
||
"轨道板(道床)铺设后,第2至3个月": "GDB_FINISH_4_6",
|
||
"轨道板(道床)铺设后,3个月以后": "GDB_FINISH_AFTER_6",
|
||
|
||
# 桥梁分组
|
||
"桥墩(台)地面处拆模后": "STATIC",
|
||
"敦身混凝土施工": "STATIC",
|
||
"预制梁桥,架梁前": "STATIC",
|
||
"预制梁桥,预制梁架设前": "YZLQ_BEFORE_JS",
|
||
"预制梁桥,预制梁架设后": "YZLQ_AFTER_JS",
|
||
"现浇梁,浇筑前": "STATIC",
|
||
"现浇梁上部结构施工中": "STATIC",
|
||
"架桥机(运梁车)通过": "STATIC",
|
||
"桥梁主体工程完工后,第1至3个月": "QL_ZHUTI_1_3",
|
||
"桥梁主体工程完工后,第4至6个月": "QL_ZHUTI_4_6",
|
||
"桥梁主体工程完工后,6个月以后": "QL_ZHUTI_AFTER_6",
|
||
"轨道铺设,前": "STATIC",
|
||
"轨道铺设,后": "STATIC",
|
||
"轨道铺设完成后,第1个月": "GD_FINISH_1",
|
||
"轨道铺设完成后,2至3个月": "GD_FINISH_2_3",
|
||
"轨道铺设完成后,4至12个月": "GD_FINISH_4_12",
|
||
"轨道铺设完成后,12个月以后": "GD_FINISH_AFTER_12",
|
||
|
||
# 桥梁旧工况分组(复用新工况分组)
|
||
"墩台施工到一定高度": "STATIC",
|
||
"墩台混凝土施工": "STATIC",
|
||
"桥位施工桥梁,制梁前": "STATIC",
|
||
"桥位施工桥梁,上部结构施工中": "STATIC",
|
||
"桥梁主体工程完工后,第1至3个月": "QL_ZHUTI_1_3",
|
||
"轨道铺设期间,前": "STATIC",
|
||
"轨道铺设期间,后": "STATIC",
|
||
|
||
# 隧道分组
|
||
"仰拱(底板)施工完成后,第1个月": "YG_DIBAN_1",
|
||
"仰拱(底板)施工完成后,第2至3个月": "YG_DIBAN_2_3",
|
||
"仰拱(底板)施工完成后,3个月以后": "YG_DIBAN_AFTER_3",
|
||
"无砟轨道铺设后,第1至3个月": "WZGD_1_3",
|
||
"无砟轨道铺设后,4至12个月": "WZGD_4_12",
|
||
"无砟轨道铺设后,12个月以后": "WZGD_AFTER_12",
|
||
|
||
# 特殊工况
|
||
"冬休": "STATIC"
|
||
}
|
||
return group_map
|
||
|
||
def _load_transition_rules(self):
|
||
"""加载切换触发规则(沿用原逻辑,以base_periods为准)"""
|
||
return {
|
||
# 路基切换规则
|
||
"DZ_FINISH_1_3": {"trigger_days": 90, "next": ["预压土或路基填筑完成,第4~6个月"]},
|
||
"DZ_FINISH_4_6": {"trigger_days": 90, "next": ["预压土或路基填筑完成,6个月以后"]},
|
||
"DZ_FINISH_AFTER_6": {"trigger_days": None, "next": None},
|
||
"JQJ_FIRST_BEFORE": {"trigger_days": 1, "next": ["架桥机(运梁车)首次通过后,前3天"]},
|
||
"JQJ_FIRST_AFTER_3D": {"trigger_days": 3, "next": ["架桥机(运梁车)首次通过后"]},
|
||
"JQJ_FIRST_AFTER": {"trigger_days": None, "next": None},
|
||
"GDB_FINISH_1_3": {"trigger_days": 30, "next": ["轨道板(道床)铺设后,第4至6个月"]},
|
||
"GDB_FINISH_4_6": {"trigger_days": 30, "next": ["轨道板(道床)铺设后,6个月以后"]},
|
||
"GDB_FINISH_AFTER_6": {"trigger_days": None, "next": None},
|
||
|
||
# 桥梁切换规则
|
||
"YZLQ_BEFORE_JS": {"trigger_days": 1, "next": ["架桥机(运梁车)通过"]},
|
||
"YZLQ_AFTER_JS": {"trigger_days": 7, "next": ["桥梁主体工程完工后,第1至3个月"]},
|
||
"QL_ZHUTI_1_3": {"trigger_days": 90, "next": ["桥梁主体工程完工后,第4至6个月"]},
|
||
"QL_ZHUTI_4_6": {"trigger_days": 90, "next": ["桥梁主体工程完工后,6个月以后"]},
|
||
"QL_ZHUTI_AFTER_6": {"trigger_days": None, "next": None},
|
||
"GD_FINISH_1": {"trigger_days": 30, "next": ["轨道铺设完成后,2至3个月"]},
|
||
"GD_FINISH_2_3": {"trigger_days": 60, "next": ["轨道铺设完成后,4至12个月"]},
|
||
"GD_FINISH_4_12": {"trigger_days": 240, "next": ["轨道铺设完成后,12个月以后"]},
|
||
"GD_FINISH_AFTER_12": {"trigger_days": None, "next": None},
|
||
|
||
# 隧道切换规则
|
||
"YG_DIBAN_1": {"trigger_days": 30, "next": ["仰拱(底板)施工完成后,第2至3个月"]},
|
||
"YG_DIBAN_2_3": {"trigger_days": 60, "next": ["仰拱(底板)施工完成后,3个月以后"]},
|
||
"YG_DIBAN_AFTER_3": {"trigger_days": None, "next": None},
|
||
"WZGD_1_3": {"trigger_days": 90, "next": ["无砟轨道铺设后,4至12个月"]},
|
||
"WZGD_4_12": {"trigger_days": 240, "next": ["无砟轨道铺设后,12个月以后"]},
|
||
"WZGD_AFTER_12": {"trigger_days": None, "next": None},
|
||
|
||
# 静态分组(无切换)
|
||
"DZ_CONTINUE": {"trigger_days": None, "next": None},
|
||
"DZ_INTERVAL": {"trigger_days": None, "next": None},
|
||
"STATIC": {"trigger_days": None, "next": None}
|
||
}
|
||
|
||
def _parse_to_date(self, time_str):
|
||
"""解析时间字符串为date对象(仅保留年月日)"""
|
||
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(self, data, target_workinfo):
|
||
"""从倒序数据中提取目标工况的时间统计信息"""
|
||
# 筛选目标工况的有效记录
|
||
target_records = [
|
||
d for d in data
|
||
if self._standardize_name(d.get("workinfoname")) == self._standardize_name(target_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
|
||
|
||
# 计算时间差(倒序数据:最新在0,最早在-1)
|
||
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):
|
||
"""提取冬休前的上一个有效工况"""
|
||
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:
|
||
# 优先返回等效新工况
|
||
return self.old_to_new_map.get(current_work, current_work)
|
||
return None
|
||
|
||
def _validate_point_id(self, inner_list):
|
||
"""校验内嵌列表内point_id是否一致"""
|
||
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 = []
|
||
|
||
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)
|
||
|
||
# 校验point_id
|
||
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.append(latest_record)
|
||
continue
|
||
|
||
# 获取当前工况并标准化
|
||
current_workinfo = latest_record.get("workinfoname")
|
||
if not current_workinfo or self._standardize_name(current_workinfo) not in self.std_to_canonical:
|
||
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.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.append(latest_record)
|
||
continue
|
||
else:
|
||
# 非冬休:优先使用等效新工况作为判断基准
|
||
judge_based_workinfo = self.old_to_new_map.get(current_workinfo, current_workinfo)
|
||
|
||
# 计算时间统计信息
|
||
first_dt, cumulative_days, days_to_today = self._get_time_statistics(
|
||
inner_data_list, judge_based_workinfo
|
||
)
|
||
first_measure_date = first_dt.strftime("%Y-%m-%d") if first_dt else None
|
||
|
||
# 推导下一工况(以base_periods中的名称为准)
|
||
std_judge_work = self._standardize_name(judge_based_workinfo)
|
||
group_id = self.std_to_group.get(std_judge_work, "STATIC")
|
||
rule = self.transition_rules.get(group_id, {})
|
||
trigger_days = rule.get("trigger_days")
|
||
next_candidates = rule.get("next", [])
|
||
|
||
if trigger_days is not None and cumulative_days >= trigger_days and next_candidates:
|
||
# 切换工况:取base_periods中的规范名称
|
||
next_workinfo = next_candidates[0]
|
||
else:
|
||
# 不切换:有等效则返回新工况,无则返回旧工况
|
||
next_workinfo = self.old_to_new_map.get(judge_based_workinfo, 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_workinfo, # 推导结果:有等效返回新工况,无则返回旧工况
|
||
"point_id": point_id,
|
||
"error_msg": ""
|
||
})
|
||
|
||
final_result.append(latest_record)
|
||
|
||
return final_result
|
||
|
||
|