Files
Incidents-Data-Portal/backend/app/blueprints/incident_gen6/service.py
T
2026-07-10 18:55:55 +08:00

428 lines
17 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 collections import defaultdict
from datetime import datetime, timedelta
import json
def process_incident_data_gen6(test_val):
"""
处理事故数据,按照incident_name去重(移除原incident_time组合去重),移除idc_tickcount_ms排序,其他逻辑不变。
Args:
test_val (list): 原始数据列表
Returns:
dict: 格式化后的数据
"""
# 1. 仅按照 incident_name 去重(核心修改点1:移除incident_time组合)
unique_data_dict = {}
for item in test_val:
name = item.get("incident_name")
# 仅判断incident_name是否存在,不再依赖incident_time
if name:
# 仅用incident_name作为去重键
key = name
if key not in unique_data_dict:
unique_data_dict[key] = item
# 获取去重后的数据列表(核心修改点2:移除排序步骤)
unique_data_list = list(unique_data_dict.values())
# 3. 提取指定字段(原逻辑完全保留,仅将遍历对象从sorted_data改为unique_data_list
processed_events_data = []
for index, item in enumerate(unique_data_list, 1):
incident_time_str = item.get("incident_time")
if not incident_time_str:
continue
try:
# 解析新的时间格式 "2025-11-30 20:51:35.684000"
# 先尝试直接解析完整格式
try:
# 移除毫秒部分,只保留到秒
base_time_str = incident_time_str.split('.')[0]
incident_datetime = datetime.strptime(base_time_str, "%Y-%m-%d %H:%M:%S")
except ValueError:
# 如果失败,尝试其他可能的格式
try:
incident_datetime = datetime.fromisoformat(incident_time_str.replace('Z', '+00:00'))
except:
# 再次失败,使用当前时间
incident_datetime = datetime.now()
# 格式化日期和时间
date_str_result = incident_datetime.strftime("%Y-%m-%d")
time_str_result = incident_datetime.strftime("%H:%M:%S")
except (ValueError, TypeError) as e:
print(f"Warning: Failed to parse incident_time '{incident_time_str}': {e}")
# 使用默认值
date_str_result = item.get("dt", "").split(' ')[0] if item.get("dt") else datetime.now().strftime("%Y-%m-%d")
time_str_result = "00:00:00"
# 创建新的字典
processed_item = {
"id": index,
"oneid": item.get("oneid", ""),
"date": date_str_result,
"time": time_str_result,
"incident_name": item.get("incident_name", ""),
"incident_description": "",
}
processed_events_data.append(processed_item)
# 4. 构建最终返回的字典
total_count = len(processed_events_data)
result = {"data": processed_events_data, "total": total_count}
return result
def aggregate_and_sort_incidents_gen6(raw_data):
"""
简化版本:按(incident_time + incident_name)精确聚合
适用于同一事件的incident_time完全一致的情况
"""
aggregate_dict = {}
for item in raw_data:
incident_time = item.get("incident_time", "1970-01-01 00:00:00.000")
incident_name = item.get("incident_name", "unknown_incident")
aggregate_key = (incident_time, incident_name)
if aggregate_key not in aggregate_dict:
aggregate_dict[aggregate_key] = {
"base_info": {
"oneid": item.get("oneid"),
"incident_time": incident_time,
"speed": item.get("speed"),
"gps_heading": item.get("gps_heading"),
"incident_name": incident_name,
"latitude": item.get("latitude"),
"longitude": item.get("longitude"),
"odometer": item.get("odometer"),
"sw_version": item.get("sw_version"),
"carmodel": item.get("carmodel"),
"session_id": item.get("session_id"),
"dt": item.get("dt")
},
"signal_data": {}
}
signal_key = item.get("key", "unknown_key")
aggregate_dict[aggregate_key]["signal_data"][signal_key] = {
'signal_value': item.get('signal_value', '0.0'),
"odometer": item.get('odometer', '0.0')
}
# 按incident_time排序
sorted_result = sorted(
aggregate_dict.values(),
key=lambda x: x["base_info"]["incident_time"]
)
return sorted_result
def aggregate_line_list_gen6(map_list):
"""
按incident_time聚合事件数据,生成时间线列表
改进点:
1. 使用正确的时间字段名
2. 支持精确的时间排序
3. 处理同一时间点的多条记录
4. 添加健壮的错误处理
"""
aggregated_data = {}
# 遍历所有事件
for item in map_list:
try:
base_info = item['base_info']
# 提取需要的字段
incident_time_str = base_info.get('incident_time', '1970-01-01 00:00:00.000')
speed = base_info.get('speed', '0')
oneid = base_info.get('oneid', '')
latitude = base_info.get('latitude', '0')
longitude = base_info.get('longitude', '0')
incident_name = base_info.get('incident_name', 'Unknown Incident')
# 创建聚合键(使用完整时间字符串)
aggregate_key = incident_time_str
# 聚合处理
if aggregate_key not in aggregated_data:
# 新的时间点,初始化记录
aggregated_data[aggregate_key] = {
'incident_time': incident_time_str, # 使用正确的字段名
'speed': speed,
'oneid': oneid,
'latitude': latitude,
'longitude': longitude,
'incident_names': [incident_name], # 使用复数形式,表示可能有多个
'record_count': 1 # 记录该时间点的记录数
}
else:
# 已存在的时间点
existing = aggregated_data[aggregate_key]
# 可选:处理同一时间点不同记录的字段冲突
# 这里选择保留第一条的速度,但如果需要可以取平均值或最新值
# existing['speed'] = str((float(existing['speed']) + float(speed)) / 2)
# 添加事件名称(去重)
if incident_name not in existing['incident_names']:
existing['incident_names'].append(incident_name)
existing['record_count'] += 1
except (KeyError, TypeError) as e:
print(f"Warning: Skipping item due to missing field: {e}")
continue
# 转换为列表形式,并按时间正序排序
def get_sort_time(record):
try:
time_str = record['incident_time']
if '.' in time_str:
# 处理带毫秒的时间
return datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S.%f")
else:
# 处理不带毫秒的时间
return datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
except (ValueError, TypeError) as e:
print(f"Warning: Failed to parse time '{record['incident_time']}' for sorting: {e}")
return datetime(1970, 1, 1) # 返回默认时间
# 按incident_time正序排序(从小到大)
result = sorted(aggregated_data.values(), key=get_sort_time)
return result
def get_logging_list_gen6(map_list):
"""
处理map_list数据,生成logging列表,并按incident_time正序排序
同时对每个记录的signals按name字段进行字符顺序排序
"""
# 1. 首先对map_list按incident_time进行排序
def get_sort_key(item):
try:
incident_time_str = item['base_info']['incident_time']
# 处理时间字符串,统一格式
if '.' in incident_time_str:
# 保留到毫秒级别进行排序
base_time_str = incident_time_str.split('.')[0]
milliseconds = incident_time_str.split('.')[1][:6] # 取最多6位毫秒
full_time_str = f"{base_time_str}.{milliseconds}"
return datetime.strptime(full_time_str, "%Y-%m-%d %H:%M:%S.%f")
else:
return datetime.strptime(incident_time_str, "%Y-%m-%d %H:%M:%S")
except (ValueError, TypeError, KeyError) as e:
print(f"Warning: Failed to parse incident_time for sorting: {e}")
# 解析失败时返回一个很早的时间,确保这些记录排在最后
return datetime(1970, 1, 1)
# 按incident_time正序排序(从小到大)
sorted_map_list = sorted(map_list, key=get_sort_key)
# 2. 处理排序后的数据
result = []
id = 0
for item in sorted_map_list:
id += 1
row_data = {}
row_data['id'] = id
base_info = item['base_info']
# 处理时间格式 - 新格式为 "2025-11-30 20:23:05.394000"
incident_time_str = base_info['incident_time']
try:
# 尝试解析新格式的时间
if '.' in incident_time_str:
# 移除毫秒部分,只保留到秒
base_time_str = incident_time_str.split('.')[0]
dt = datetime.strptime(base_time_str, "%Y-%m-%d %H:%M:%S")
else:
dt = datetime.strptime(incident_time_str, "%Y-%m-%d %H:%M:%S")
row_data['time'] = dt.strftime("%H:%M:%S")
except (ValueError, TypeError) as e:
print(f"Warning: Failed to parse incident_time '{incident_time_str}': {e}")
# 使用默认时间
row_data['time'] = "00:00:00"
# 处理经纬度 - 可能为 "NULL" 字符串
longitude = str(base_info.get('longitude', '')).strip()
latitude = str(base_info.get('latitude', '')).strip()
# 处理 "NULL" 字符串
if longitude.lower() == "null" or longitude == "":
longitude = "N/A"
if latitude.lower() == "null" or latitude == "":
latitude = "N/A"
# 格式化坐标显示
row_data['coordinates'] = f"{longitude[:12]}\n{latitude[:12]}"
# 处理idc_tickcount_ms
row_data['idc_tickcount_ms'] = base_info.get('idc_tickcount_ms_int', 0)
# 处理速度 - 可能为 "NULL" 字符串
speed = str(base_info.get('speed', '')).strip()
if speed.lower() == "null" or speed == "":
speed = "0"
row_data['speed'] = speed
# 处理incident信息 - 新格式没有Incident__description
row_data['incident'] = {
"name": base_info.get('incident_name', 'Unknown Incident'),
"description": "" # 使用incident_name作为描述
}
# 处理信号数据
row_data['signals'] = []
for key, value in item["signal_data"].items():
signals_name_value = {}
signals_name_value["name"] = key
# 新格式使用signal_value字段
signal_value = value.get('signal_value', '')
if signal_value is None or signal_value == "":
signal_value = "N/A"
signals_name_value["value"] = signal_value
# 匹配到meanning的值
meaning_result=match_meaning(key,signal_value)
# 新格式没有Meaning字段,使用空字符串
signals_name_value["meaning"] = meaning_result
row_data['signals'].append(signals_name_value)
# === 新增:对signals列表按name字段进行字符顺序排序 ===
row_data['signals'] = sorted(row_data['signals'], key=lambda x: x['name'])
result.append(row_data)
return result
def convert_to_histro_data(raw_data):
"""
转换原始数据为前端折线图格式(time保留%Y-%m-%d %H:%M:%S.%f格式)
:param raw_data: 原始字典列表
:return: 前端所需格式的列表
"""
# 步骤1:按signal分组
signal_groups = defaultdict(list)
for item in raw_data:
signal_groups[item["signal"]].append(item)
# 步骤2:处理每个分组,构造最终数据
chart_data = []
for signal_name, items in signal_groups.items():
# 修复1:统一使用start_ts字段进行排序(因为这是时间轴数据)
def sort_by_start_ts(item):
return datetime.strptime(item["time"], "%Y-%m-%d %H:%M:%S.%f")
sorted_items = sorted(items, key=sort_by_start_ts)
# 修复3:统一使用time作为X轴时间数据(与排序字段一致)
time_list = [item["time"] for item in sorted_items] # 使用start_ts作为时间轴
values_list = [float(item["value"]) for item in sorted_items] # value转数值
# 子步骤3:构造当前signal的折线图数据
chart_item = {
"signalName": signal_name,
"time": time_list, # X轴时间数据
"values": values_list # Y轴数值数据
}
chart_data.append(chart_item)
return chart_data
def convert_to_histro_data_v2(raw_data):
"""
转换原始数据为前端图表格式 {x: 时间, y: 数值, value: 数值}
:param raw_data: 原始字典列表,需包含 "signal"、"time"、"value" 字段
:return: 字典(key=signal名称,value=对应{x,y,value}格式的列表);
若需合并所有signal为单列表,可取消注释对应逻辑
"""
# 步骤1:按signal字段分组
signal_groups = defaultdict(list)
for item in raw_data:
signal_groups[item["signal"]].append(item)
# 步骤2:处理每个分组,构造目标格式数据
chart_data = {}
for signal_name, items in signal_groups.items():
# 按time字段排序(保证时间轴顺序)
def sort_by_time(item):
return datetime.strptime(item["time"], "%Y-%m-%d %H:%M:%S.%f")
sorted_items = sorted(items, key=sort_by_time)
# 构造 {x: 时间, y: 数值, value: 数值} 格式的列表
signal_item_list = []
for item in sorted_items:
val = float(item["value"]) # 确保数值类型为浮点数
signal_item_list.append({
"x": item["time"], # x轴:时间字符串(对应示例中的"销量4"类标签)
"y": item['valueExplanation'], # y轴:原始value数值
"value": val # value字段:与y轴数值一致(匹配示例格式)
})
# ========== 新增逻辑开始 ==========
# 获取排序后第一条原始数据
first_raw_item = sorted_items[0]
# 判断is_pre_value_needed是否非空(处理空字符串、None、"NULL"等情况)
is_needed = first_raw_item.get("is_pre_value_needed", "")
if is_needed and is_needed.strip() and is_needed != "NULL":
try:
# 提取pre相关值并转换类型
pre_val = float(first_raw_item["pre_value"])
pre_explanation = first_raw_item["pre_valueExplanation"]
first_time = first_raw_item["time"] # 在第一条数据的时间基础上减10秒
first_time_dt = datetime.strptime(first_time, "%Y-%m-%d %H:%M:%S.%f")
pre_time_dt = first_time_dt - timedelta(seconds=10)
pre_time_str = pre_time_dt.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
# 构造前置数据项
pre_item = {
"x": '', # x轴:第一条数据的时间
"y": pre_explanation, # y轴:pre_value对应的说明
"value": pre_val, # value字段:pre_value的数值
"show":1
}
# 插入到列表最前面
signal_item_list.insert(0, pre_item)
except KeyError as e:
print(f"警告:第一条数据缺少{e}字段,跳过前置数据插入")
except ValueError as e:
print(f"警告:pre_value转换浮点数失败({e}),跳过前置数据插入")
# ========== 新增逻辑结束 ==========
# 按signal名称存储结果
chart_data[signal_name] = signal_item_list
return chart_data
def match_meaning(singla_name,value):
import os
current_dir = os.path.dirname(os.path.abspath(__file__))
json_path = os.path.join(current_dir, "singal_value.json")
with open(json_path, "r", encoding="utf-8") as f:
mean_data = json.load(f)
singla=singla_name.split('.')[-1]
meaning_state=mean_data.get(singla,None)
if meaning_state:
return meaning_state[value] if meaning_state[value] else ''
else:
return ''