init
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
from flask import Blueprint, json, jsonify, current_app, request
|
||||
from app.blueprints.incident_gen5.service import (
|
||||
aggregate_and_sort_incidents,
|
||||
aggregate_line_list,
|
||||
get_logging_list,
|
||||
process_incident_data,
|
||||
)
|
||||
from app.services.remote_service import DatabricksQuery
|
||||
from app.utils import convert_incident_time_format
|
||||
|
||||
|
||||
incident_bp = Blueprint("incident", __name__)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,333 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from queue import Queue
|
||||
import threading
|
||||
from flask import Blueprint, json, jsonify, current_app, request
|
||||
from app.blueprints.incident_gen6.service import aggregate_and_sort_incidents_gen6, aggregate_line_list_gen6, convert_to_histro_data_v2, get_logging_list_gen6, process_incident_data_gen6
|
||||
from app.link_wedata_utils import sync_query_tencent_cloud_data
|
||||
from app.utils import decrypt_code
|
||||
|
||||
incident_gen6_bp = Blueprint("incident_gen6_bp", __name__)
|
||||
|
||||
|
||||
@incident_gen6_bp.route("/list1", methods=["POST"])
|
||||
def acc_list1():
|
||||
encrypted = request.headers.get('X-Encrypted-Timestamp')
|
||||
if not encrypted:
|
||||
return jsonify({"msg": "Parameters are Incorrect"}), 400
|
||||
|
||||
encrypt_str=decrypt_code(encrypted)
|
||||
if not encrypt_str:
|
||||
return jsonify({"msg": "Invalid request"}), 400
|
||||
|
||||
data = request.get_json()
|
||||
incident_type = data.get("incident_type", "").strip() or None
|
||||
singal_name = data.get("singal_name", []) or None
|
||||
|
||||
# print("singal_name",singal_name)
|
||||
|
||||
import os
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
json_path = os.path.join(current_dir, "incident_sample.json")
|
||||
with open(json_path, "r", encoding="utf-8") as f:
|
||||
events_data = json.load(f)
|
||||
|
||||
histo_path = os.path.join(current_dir, "histro.json")
|
||||
with open(histo_path, "r", encoding="utf-8") as f_histro:
|
||||
histo_data = json.load(f_histro)
|
||||
|
||||
histo_data_new = []
|
||||
for item in histo_data:
|
||||
new_item = {
|
||||
'signal': item['signal'],
|
||||
'time': item['time'],
|
||||
'value': item['value'],
|
||||
'valueExplanation': item['valueExplanation'],
|
||||
'pre_value':item['pre_value'],
|
||||
'pre_valueExplanation':item['pre_valueExplanation'],
|
||||
'is_pre_value_needed':item['is_pre_value_needed']
|
||||
}
|
||||
histo_data_new.append(new_item)
|
||||
|
||||
histo_list=convert_to_histro_data_v2(histo_data_new)
|
||||
events_data_new=[]
|
||||
for item in events_data:
|
||||
new_item = {
|
||||
'oneid': item['oneid'],
|
||||
'incident_name': item['incident_name'],
|
||||
'incident_time': item['incident_time'],
|
||||
'key': item['key'],
|
||||
'signal_value': item['signal_value'],
|
||||
'latitude': item['latitude'],
|
||||
'longitude': item['longitude'],
|
||||
'odometer': item['odometer'],
|
||||
'speed': item['speed']
|
||||
}
|
||||
events_data_new.append(new_item)
|
||||
|
||||
if incident_type == "Parking":
|
||||
# 过滤出 incident_name 等于 Test_I_Park_Trip 的数据
|
||||
events_data_new = [
|
||||
item for item in events_data_new
|
||||
if item.get("incident_name") in ["Test_I_Park_Trip", "Test_I_RMA"]
|
||||
]
|
||||
elif incident_type == "Driving":
|
||||
# 过滤出 incident_name 不等于 Test_I_Park_Trip 的数据
|
||||
events_data_new = [
|
||||
item for item in events_data_new
|
||||
if item.get("incident_name") not in ["Test_I_Park_Trip", "Test_I_RMA"]
|
||||
]
|
||||
|
||||
# event数据
|
||||
events_list=process_incident_data_gen6(events_data_new) # 初始的event list数据,但是没有15分钟处理
|
||||
map_list = aggregate_and_sort_incidents_gen6(events_data_new)
|
||||
# print(f"共 {len(map_list)} 条:")
|
||||
line_list = aggregate_line_list_gen6(map_list)
|
||||
logging_list=get_logging_list_gen6(map_list)
|
||||
response = {
|
||||
"code": 200,
|
||||
"data": {
|
||||
# "events_data":events_data,
|
||||
"events_list": events_list,
|
||||
"map_list": map_list,
|
||||
"line_list": line_list,
|
||||
"logging_list": logging_list,
|
||||
"histo_list":histo_list
|
||||
},
|
||||
"msg": "success",
|
||||
}
|
||||
return jsonify(response)
|
||||
|
||||
|
||||
|
||||
@incident_gen6_bp.route("/list", methods=["POST"])
|
||||
def acc_list():
|
||||
# 验证请求头的信息,通过才继续
|
||||
encrypted = request.headers.get('X-Encrypted-Timestamp')
|
||||
if not encrypted:
|
||||
return jsonify({"msg": "Parameters are Incorrect"}), 400
|
||||
|
||||
encrypt_str=decrypt_code(encrypted)
|
||||
if not encrypt_str:
|
||||
return jsonify({"msg": "Invalid request"}), 400
|
||||
|
||||
data = request.get_json()
|
||||
incident_type = data.get("incident_type", "").strip() or None
|
||||
signal_name = data.get("signal_name", [])
|
||||
|
||||
if not data:
|
||||
return jsonify({"code": 400, "data": None, "msg": "请填写有效参数"}), 400
|
||||
|
||||
|
||||
required_fields = ["incident_time", "oneid"]
|
||||
for field in required_fields:
|
||||
if field not in data:
|
||||
return (
|
||||
jsonify({"code": 400, "data": None, "msg": f"缺少必需字段: {field}"}),
|
||||
400,
|
||||
)
|
||||
|
||||
incident_time = data["incident_time"]
|
||||
# 检查是否为非空列表
|
||||
if not isinstance(incident_time, list) or len(incident_time) != 2:
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"code": 400,
|
||||
"data": [],
|
||||
"msg": "incident_time必须是包含两个时间的数组",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
|
||||
oneid = data["oneid"]
|
||||
if not isinstance(oneid, str) or not oneid.strip():
|
||||
return jsonify({"code": 400, "data": None, "msg": "oneid必须是非空字符串"}), 400
|
||||
|
||||
|
||||
config = {
|
||||
'region': 'ap-shanghai', # 引擎所在地域
|
||||
'secret_id': current_app.config['WEDATA_SECRET_ID'],
|
||||
'secret_key': current_app.config['WEDATA_SECRET_KEY'],
|
||||
'engine': current_app.config['WEDATA_ENGINE'], # 引擎名称
|
||||
"database":current_app.config['WEDATA_DATABASE']
|
||||
}
|
||||
|
||||
# 生成sql
|
||||
try:
|
||||
incid_sql, incid_params = generate_incidents_sql(incident_time, oneid, signal_name, table_name='gen6_incidents')
|
||||
histo_sql, histo_params = generate_incidents_sql(incident_time, oneid, signal_name, table_name='gen6_histograms')
|
||||
except ValueError as e:
|
||||
return jsonify({"code": 400, "data": None, "msg": f"参数错误:{str(e)}"}), 400
|
||||
|
||||
print("incid_sql", incid_sql)
|
||||
print("histo_sql", histo_sql)
|
||||
|
||||
result_queue = Queue(maxsize=2) # 最多存放2个结果
|
||||
|
||||
# ========== 仅修改这里:给原函数加极简异常防护(不新增任何函数) ==========
|
||||
def safe_query(*args):
|
||||
"""极简包装:仅捕获异常,保证队列必有结果"""
|
||||
try:
|
||||
# 直接调用原函数,参数完全传透(args就是config, sql, params, queue, task_id)
|
||||
sync_query_tencent_cloud_data(*args)
|
||||
except Exception as e: # except Exception as e:
|
||||
# 异常时手动向队列写错误结果(args[3]是queue,args[4]是task_id)
|
||||
args[3].put({"task_id": args[4], "success": False, "data": None, "error": str(e)})
|
||||
|
||||
# try:
|
||||
# error_msg=(
|
||||
# f'数据库连接失败:{str(e)}'
|
||||
# if str(e)
|
||||
# else f'底层系统错误:{type(e).__name__}'
|
||||
# )
|
||||
# args[3].put({"task_id": args[4], "success": False, "data": None, "error":error_msg})
|
||||
# except Exception as query_err:
|
||||
# print(f'任务失败,无法写入队列:{query_err},原始错误是:{e}')
|
||||
|
||||
|
||||
# 5. 创建并启动两个查询线程
|
||||
thread1 = threading.Thread(
|
||||
target=safe_query,
|
||||
args=(config, incid_sql, incid_params, result_queue, "incidents") # task_id 标记为 incidents
|
||||
)
|
||||
thread2 = threading.Thread(
|
||||
target=safe_query,
|
||||
args=(config, histo_sql, histo_params, result_queue, "histo") # task_id 标记为 signals
|
||||
)
|
||||
|
||||
# 启动线程
|
||||
thread1.start()
|
||||
thread2.start()
|
||||
thread1.join()
|
||||
thread2.join()
|
||||
|
||||
# 从队列中提取结果
|
||||
results_dict = {}
|
||||
for _ in range(2):
|
||||
task_result = result_queue.get()
|
||||
results_dict[task_result["task_id"]] = task_result
|
||||
|
||||
# 8. 检查是否有查询失败
|
||||
has_error = False
|
||||
error_msg = ""
|
||||
for task_id, res in results_dict.items():
|
||||
if not res["success"]:
|
||||
has_error = True
|
||||
error_msg = f"查询异常:{res['error'][:80]}" # 错误信息保留前80
|
||||
|
||||
if has_error:
|
||||
return jsonify({
|
||||
"code": 500,
|
||||
"msg": f"查询失败:{error_msg}",
|
||||
"data": None
|
||||
}), 500
|
||||
|
||||
|
||||
# 9. 获取两个表的查询结果(业务处理核心)
|
||||
events_data = results_dict["incidents"]["data"]
|
||||
histo_data = results_dict["histo"]["data"]
|
||||
|
||||
histo_list=convert_to_histro_data_v2(histo_data)
|
||||
|
||||
if incident_type == "Parking":
|
||||
# 过滤出 incident_name 等于 Test_I_Park_Trip 的数据
|
||||
events_data = [
|
||||
item for item in events_data
|
||||
if item.get("incident_name") in ["Test_I_Park_Trip", "Test_I_RMA"]
|
||||
]
|
||||
elif incident_type == "Driving":
|
||||
# 过滤出 incident_name 不等于 Test_I_Park_Trip 的数据
|
||||
events_data = [
|
||||
item for item in events_data
|
||||
if item.get("incident_name") not in ["Test_I_Park_Trip", "Test_I_RMA"]
|
||||
]
|
||||
|
||||
# event数据
|
||||
events_list=process_incident_data_gen6(events_data) # 初始的event list数据,但是没有15分钟处理
|
||||
map_list = aggregate_and_sort_incidents_gen6(events_data)
|
||||
line_list = aggregate_line_list_gen6(map_list)
|
||||
logging_list=get_logging_list_gen6(map_list)
|
||||
response = {
|
||||
"code": 200,
|
||||
"data": {
|
||||
# "events_data":events_data,
|
||||
"events_list": events_list,
|
||||
"map_list": [],
|
||||
"line_list": line_list,
|
||||
"logging_list": logging_list,
|
||||
"histo_list":histo_list
|
||||
},
|
||||
"msg": "success",
|
||||
}
|
||||
return jsonify(response)
|
||||
|
||||
|
||||
def generate_incidents_sql(incident_time: list, oneid: str, signal_list: list, table_name: str) -> tuple:
|
||||
"""
|
||||
简化版:仅处理用到的两个表,字段名映射极简,去掉冗余扩展
|
||||
- gen6_incidents:时间=incident_time,ID字段=oneid
|
||||
- gen6_histograms:时间=time,ID字段=vin
|
||||
"""
|
||||
# 处理UTC时间(带T/Z)转本地时间(东八区)
|
||||
def convert_utc_to_cst(utc_time_str):
|
||||
try:
|
||||
# 解析UTC时间(支持带T/Z的格式)
|
||||
utc_dt = datetime.fromisoformat(utc_time_str.replace('Z', '+00:00'))
|
||||
# 转东八区(UTC+8)
|
||||
cst_tz = timezone(timedelta(hours=8))
|
||||
cst_dt = utc_dt.astimezone(cst_tz)
|
||||
# 转为数据库兼容的格式(YYYY-MM-DD HH:mm:ss.fff)
|
||||
return cst_dt.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] # 保留3位毫秒
|
||||
except Exception as e:
|
||||
# 解析失败则返回原时间(兼容测试参数)
|
||||
return utc_time_str
|
||||
|
||||
# 转换时间范围
|
||||
incident_time = [convert_utc_to_cst(t) for t in incident_time]
|
||||
|
||||
# 1. 极简字段映射(只保留用到的表,避免KeyError)
|
||||
FIELD_MAP = {
|
||||
"gen6_incidents": {"time": "incident_time",
|
||||
"id": "oneid",
|
||||
# "select_fields": "oneid, incident_name,incident_time,`key`,signal_value,latitude,longitude,odometer,speed" },
|
||||
"select_fields": "*" },
|
||||
"gen6_histograms": {"time": "time",
|
||||
"id": "vin",
|
||||
"select_fields": "signal, time, value, valueExplanation"},
|
||||
}
|
||||
|
||||
# 2. 基础校验(只校验用到的表,去掉冗余)
|
||||
if table_name not in FIELD_MAP:
|
||||
raise ValueError(f"仅支持查询表:{list(FIELD_MAP.keys())}")
|
||||
if len(incident_time) != 2 or not all(incident_time):
|
||||
raise ValueError("incident_time必须是包含两个非空时间的列表")
|
||||
if not isinstance(oneid, str) or not oneid.strip():
|
||||
raise ValueError("oneid必须是非空字符串")
|
||||
|
||||
# 3. 提取当前表的字段名(核心:只用两行完成字段替换)
|
||||
time_field = FIELD_MAP[table_name]["time"]
|
||||
id_field = FIELD_MAP[table_name]["id"]
|
||||
select_fields = FIELD_MAP[table_name]["select_fields"]
|
||||
|
||||
# 4. 拼接SQL条件(极简逻辑,去掉冗余注释)
|
||||
conditions = [
|
||||
f"{time_field} BETWEEN %s AND %s",
|
||||
f"{id_field} = %s"
|
||||
]
|
||||
params = [incident_time[0], incident_time[1], oneid]
|
||||
|
||||
# 仅gen6_histograms处理signal条件
|
||||
if table_name == "gen6_histograms":
|
||||
if signal_list:
|
||||
signal_placeholders = ",".join(["%s"] * len(signal_list))
|
||||
conditions.append(f"signal IN ({signal_placeholders})")
|
||||
params += signal_list
|
||||
else:
|
||||
conditions.append("signal = %s")
|
||||
params.append("DAS_DTR_UI_Stat_ST3")
|
||||
|
||||
# 5. 生成最终SQL(简化格式化)
|
||||
sql = f"SELECT {select_fields} FROM {table_name} WHERE {' AND '.join(conditions)}"
|
||||
return sql, params
|
||||
@@ -0,0 +1,427 @@
|
||||
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 ''
|
||||
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
{
|
||||
"l2p_function_state": {
|
||||
"0.0": "kOff",
|
||||
"1.0": "kStandby",
|
||||
"2.0": "kPassive",
|
||||
"3.0": "kActive",
|
||||
"4.0": "kOverrule",
|
||||
"5.0": "kError"
|
||||
},
|
||||
"mmt_l2pp_function_state": {
|
||||
"0.0": "Off",
|
||||
"1.0": "Deactivated",
|
||||
"2.0": "Preselected",
|
||||
"3.0": "Active",
|
||||
"4.0": "Overrule",
|
||||
"5.0": "Error",
|
||||
"6.0": "Unknown"
|
||||
},
|
||||
"adas_cua_funcroadclass_st3": {
|
||||
"0.0": "Unknown",
|
||||
"1.0": "Freeway",
|
||||
"2.0": "MainStreet/CitySpeedway",
|
||||
"3.0": "NationalRoad",
|
||||
"4.0": "ProvinceRoad/CountyRoad",
|
||||
"5.0": "MainRoad",
|
||||
"6.0": "SecondaryRoad/Commonroad/RuralRoad/InCountyRoad/Pathway",
|
||||
"7.0": "N/A"
|
||||
},
|
||||
"l2p_deactivation_reason": {
|
||||
"0.0": "kIdle",
|
||||
"1.0": "kDeactivatedByUser",
|
||||
"2.0": "kOutOfODD",
|
||||
"3.0": "kTollStation",
|
||||
"4.0": "kRouteEnd",
|
||||
"5.0": "kHeavyRain",
|
||||
"6.0": "kError",
|
||||
"7.0": "kOverSpeed",
|
||||
"8.0": "kOther"
|
||||
},
|
||||
"hoswd_handson_stat_master": {
|
||||
"0.0": "NOT_TOUCH",
|
||||
"1.0": "TOUCH",
|
||||
"2.0": "SLIGHTLY_TOUCH",
|
||||
"3.0": "GRASP",
|
||||
"4.0": "DOUBLE_GRABBED",
|
||||
"5.0": "SLIGHTLY_GRABBED",
|
||||
"6.0": "SLIGHTLY_DOUBLE_GRABBED",
|
||||
"7.0": "SNA"
|
||||
},
|
||||
"das_dtr_ui_stat_st3": {
|
||||
"0.0": "OFF",
|
||||
"1.0": "PRESEL",
|
||||
"2.0": "ACTV_SET_SPEED_CNTRL",
|
||||
"3.0": "ACTV_SPEED_LMT_CNTRL",
|
||||
"4.0": "ACTV_DSTNC_CNTRL_EGO_LNE",
|
||||
"5.0": "ACTV_DSTNC_CNTRL_NGHBR_LNE",
|
||||
"6.0": "ACTV_CRV_CTRL",
|
||||
"7.0": "ACTV_DRVAWAY_RDY",
|
||||
"8.0": "PASSIVE",
|
||||
"9.0": "ACTV_NOT_POSSBL",
|
||||
"13.0": "HIDDEN",
|
||||
"14.0": "ERROR",
|
||||
"15.0": "SNA"
|
||||
},
|
||||
"fcw_warning": {
|
||||
"0.0": "None",
|
||||
"1.0": "LEVEL_1",
|
||||
"2.0": "LEVEL_2",
|
||||
"3.0": "LEVEL_3",
|
||||
"4.0": "LEVEL_4",
|
||||
"5.0": "COUNT",
|
||||
"255.0": "FORCE32"
|
||||
},
|
||||
"aeb_aeb_event_type": {
|
||||
"0.0": "AEB_NONE",
|
||||
"1.0": "AEB_PARTIAL",
|
||||
"2.0": "AEB_FULL",
|
||||
"3.0": "AEB_HOLD",
|
||||
"4.0": "AEB_LIM",
|
||||
"5.0": "AEB_EH",
|
||||
"255.0": "AEB_FORCE32"
|
||||
},
|
||||
"pt4_ptcoor_drvposn_stat": {
|
||||
"0.0": "Unknown",
|
||||
"1.0": "D",
|
||||
"2.0": "N",
|
||||
"3.0": "R",
|
||||
"4.0": "P"
|
||||
},
|
||||
"das_cms_acustwarn_rq_st3": {
|
||||
"0.0": "NO_RQ",
|
||||
"1.0": "RQ",
|
||||
"3.0": "SNA"
|
||||
},
|
||||
"das_turnind_rq": {
|
||||
"0.0": "IDLE",
|
||||
"1.0": "LEFT",
|
||||
"2.0": "RIGHT"
|
||||
},
|
||||
"brkpdl_stat": {
|
||||
"0.0": "Pedal upstopped",
|
||||
"1.0": "Pedal pressed",
|
||||
"3.0": "not defined"
|
||||
},
|
||||
"aas_actvas_rq": {
|
||||
"0.0": "IDLE",
|
||||
"1.0": "NOT_ON_ROAD",
|
||||
"2.0": "DROW_LONG",
|
||||
"3.0": "MCRSLP_DSTRCT",
|
||||
"4.0": "SLP_UNRSP_DRIVER",
|
||||
"7.0": "SNA"
|
||||
},
|
||||
"aeb_state": {
|
||||
"0.0": "kStartUp",
|
||||
"1.0": "kAvailable",
|
||||
"2.0": "kActive",
|
||||
"3.0": "kActiveError",
|
||||
"4.0": "kAborting",
|
||||
"5.0": "kAbortingError",
|
||||
"6.0": "kInitSilent",
|
||||
"7.0": "kInit",
|
||||
"8.0": "kBlocked",
|
||||
"9.0": "kBlockedSilent",
|
||||
"10.0": "kError",
|
||||
"11.0": "kOff",
|
||||
"12.0": "kOffError",
|
||||
"13.0": "kNotAvailable",
|
||||
"14.0": "kCodingError"
|
||||
},
|
||||
"mpic_d_gaze_roi_idx": {
|
||||
"0.0": "NO_GAZE",
|
||||
"1.0": "ROI_01",
|
||||
"2.0": "ROI_02",
|
||||
"3.0": "ROI_03",
|
||||
"4.0": "ROI_04",
|
||||
"5.0": "ROI_05",
|
||||
"6.0": "ROI_06",
|
||||
"7.0": "ROI_07",
|
||||
"8.0": "ROI_08",
|
||||
"9.0": "ROI_09",
|
||||
"10.0": "ROI_10",
|
||||
"11.0": "ROI_11",
|
||||
"12.0": "ROI_12",
|
||||
"13.0": "ROI_13",
|
||||
"14.0": "ROI_14",
|
||||
"15.0": "ROI_15",
|
||||
"16.0": "ROI_16",
|
||||
"17.0": "ROI_17",
|
||||
"18.0": "ROI_18",
|
||||
"19.0": "ROI_19",
|
||||
"20.0": "ROI_20",
|
||||
"21.0": "ROI_21",
|
||||
"22.0": "ROI_22",
|
||||
"23.0": "ROI_23",
|
||||
"24.0": "ROI_24",
|
||||
"25.0": "ROI_25",
|
||||
"26.0": "ROI_26",
|
||||
"27.0": "ROI_27",
|
||||
"28.0": "ROI_28",
|
||||
"29.0": "ROI_29",
|
||||
"30.0": "ROI_30",
|
||||
"31.0": "SNA"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user