预警工单处理优化和空调自控调整

parent 05624613
...@@ -920,24 +920,34 @@ export async function processFaultStatus() { ...@@ -920,24 +920,34 @@ export async function processFaultStatus() {
return; return;
} }
// ② 去重 device_id + 取最早故障时间(扫描范围起点) // ② 去重 device_id + 建立每个设备的最早故障时间映射
// deviceFaultTimeMap: 每个设备最早的活跃故障发生时间,用于内存中精确过滤
// globalMinOccurredTime: 全局最早故障时间,仅作为 SQL 查询范围下限
const deviceSet = new Set<string>(); const deviceSet = new Set<string>();
let minOccurredTime: Date | null = null; let globalMinOccurredTime: Date | null = null;
const deviceFaultTimeMap = new Map<string, Date>();
for (const f of activeFaults as any[]) { for (const f of activeFaults as any[]) {
deviceSet.add(f.device_id); deviceSet.add(f.device_id);
const t = f.occurred_time ? new Date(f.occurred_time) : null; const t = f.occurred_time ? new Date(f.occurred_time) : null;
if (t && (!minOccurredTime || t < minOccurredTime)) { if (!t) continue;
minOccurredTime = t; // 全局最早时间(SQL 查询范围)
if (!globalMinOccurredTime || t < globalMinOccurredTime) {
globalMinOccurredTime = t;
}
// 每个设备的最早故障时间(内存过滤用)
const existing = deviceFaultTimeMap.get(f.device_id);
if (!existing || t < existing) {
deviceFaultTimeMap.set(f.device_id, t);
} }
} }
const deviceIds = [...deviceSet]; const deviceIds = [...deviceSet];
// ③ 一次拉回所有相关设备的开机记录(故障最早时间之后) // ③ 一次拉回所有相关设备的运行记录(全局最早故障时间之后)
// 不做时间兜底,宕机重启后也能覆盖全部历史 // 不做时间兜底,宕机重启后也能覆盖全部历史
const deviceDataRecords = await deviceDataModel.findAll({ const deviceDataRecords = await deviceDataModel.findAll({
where: { where: {
device_id: { [Op.in]: deviceIds }, device_id: { [Op.in]: deviceIds },
...(minOccurredTime ? { device_time: { [Op.gte]: minOccurredTime } } : {}), ...(globalMinOccurredTime ? { device_time: { [Op.gte]: globalMinOccurredTime } } : {}),
}, },
attributes: ['device_id', 'data', 'device_time'], attributes: ['device_id', 'data', 'device_time'],
order: [['device_id', 'ASC'], ['device_time', 'ASC']], order: [['device_id', 'ASC'], ['device_time', 'ASC']],
...@@ -945,8 +955,10 @@ export async function processFaultStatus() { ...@@ -945,8 +955,10 @@ export async function processFaultStatus() {
}); });
console.log(`[故障状态更新] 共 ${deviceDataRecords.length} 条相关设备数据记录`); console.log(`[故障状态更新] 共 ${deviceDataRecords.length} 条相关设备数据记录`);
// ④ 内存过滤:只保留开机记录(power='on'),按 device_id 分组取最早开机时间 // ④ 内存过滤:只保留各设备最早故障时间之后的开机记录(power='on')
const powerOnMap = new Map<string, Date>(); // 关键修复:不以全局最早时间过滤,而是用每个设备自己的最早故障时间,
// 避免将故障发生前的开机记录纳入比对,导致永远无法判定"已解决"
const powerOnTimesMap = new Map<string, Date[]>();
for (const r of deviceDataRecords as any[]) { for (const r of deviceDataRecords as any[]) {
let power: string | undefined; let power: string | undefined;
const rawData = r.data; const rawData = r.data;
...@@ -960,22 +972,31 @@ export async function processFaultStatus() { ...@@ -960,22 +972,31 @@ export async function processFaultStatus() {
const dt = r.device_time ? new Date(r.device_time) : null; const dt = r.device_time ? new Date(r.device_time) : null;
if (!dt) continue; if (!dt) continue;
if (!powerOnMap.has(r.device_id) || dt < powerOnMap.get(r.device_id)!) { // 只保留该设备最早故障时间之后的记录
powerOnMap.set(r.device_id, dt); const deviceFaultTime = deviceFaultTimeMap.get(r.device_id);
if (deviceFaultTime && dt <= deviceFaultTime) continue;
if (!powerOnTimesMap.has(r.device_id)) {
powerOnTimesMap.set(r.device_id, []);
} }
powerOnTimesMap.get(r.device_id)!.push(dt);
} }
// ⑤ 内存比对:开机时间 > 故障时间 → 标记已解决 // ⑤ 逐条故障比对:查找该故障发生时间之后最早的开机记录
// 关键修复:不再拿"全量最早开机时间"与故障时间对比,
// 而是在该设备故障后出现的开机记录中找第一条,确保 isAfter 判定正确
const toUpdate: { id: number; resolved_time: Date }[] = []; const toUpdate: { id: number; resolved_time: Date }[] = [];
for (const fault of activeFaults as any[]) { for (const fault of activeFaults as any[]) {
const powerOnTime = powerOnMap.get(fault.device_id); const powerOnTimes = powerOnTimesMap.get(fault.device_id);
if (!powerOnTime) continue; if (!powerOnTimes || powerOnTimes.length === 0) continue;
const occurredTime = fault.occurred_time ? moment(fault.occurred_time) : null; const occurredTime = fault.occurred_time ? new Date(fault.occurred_time) : null;
if (!occurredTime) continue; if (!occurredTime) continue;
if (moment(powerOnTime).isAfter(occurredTime)) { // 数据已按 device_time ASC 排序,找第一条在故障时间之后的记录
toUpdate.push({ id: fault.id, resolved_time: powerOnTime }); const resolvedTime = powerOnTimes.find(dt => dt > occurredTime);
if (resolvedTime) {
toUpdate.push({ id: fault.id, resolved_time: resolvedTime });
} }
} }
......
...@@ -102,7 +102,7 @@ export async function getFeiYiToken(): Promise<string> { ...@@ -102,7 +102,7 @@ export async function getFeiYiToken(): Promise<string> {
} catch (err) { } catch (err) {
throw new BizError(ERRORENUM.网络错误, `获取 token 网络失败: ${err.message}`); throw new BizError(ERRORENUM.网络错误, `获取 token 网络失败: ${err.message}`);
} }
console.log('获取 token 响应:', result); console.log('获取 token 响应:', result?.code);
// 检查响应结构:文档中返回 { code, data: { access_token } } // 检查响应结构:文档中返回 { code, data: { access_token } }
if (!result || result.code !== '00000') { if (!result || result.code !== '00000') {
...@@ -143,7 +143,7 @@ async function feiYiPost(path: string, body: any, retry: boolean = true): Promis ...@@ -143,7 +143,7 @@ async function feiYiPost(path: string, body: any, retry: boolean = true): Promis
let result: any; let result: any;
try { try {
result = await post(url, body, headers); result = await post(url, body, headers);
console.log(`请求 ${path} 成功,响应:`, result); console.log(`请求 ${path} 成功,响应:`, result?.code);
} catch (err) { } catch (err) {
throw new BizError(ERRORENUM.网络错误, `请求 ${path} 失败: ${err.message}`); throw new BizError(ERRORENUM.网络错误, `请求 ${path} 失败: ${err.message}`);
} }
...@@ -155,7 +155,7 @@ async function feiYiPost(path: string, body: any, retry: boolean = true): Promis ...@@ -155,7 +155,7 @@ async function feiYiPost(path: string, body: any, retry: boolean = true): Promis
// 可选:清除 token 缓存,重新获取 // 可选:清除 token 缓存,重新获取
return feiYiPost(path, body, false); return feiYiPost(path, body, false);
} }
throw new BizError(ERRORENUM.第三方接口错误, `接口 ${path} 调用失败: ${result.message || JSON.stringify(result)}`); throw new BizError(ERRORENUM.第三方接口错误, `接口 ${path} 调用失败: ${result.message}`); // || JSON.stringify(result)
} }
return result; return result;
} }
...@@ -228,7 +228,7 @@ export async function controlIndoorUnit(params: any): Promise<any> { ...@@ -228,7 +228,7 @@ export async function controlIndoorUnit(params: any): Promise<any> {
} }
const body = { ...params }; const body = { ...params };
const result = await feiYiPost(path, body); const result = await feiYiPost(path, body);
console.log('控制内机响应:', result); console.log('控制内机响应:', result?.code);
let success = result.code === '00000'? true : false; let success = result.code === '00000'? true : false;
return { success, data: result.message }; return { success, data: result.message };
} }
......
...@@ -36,7 +36,7 @@ export async function processDeviceData(message) { ...@@ -36,7 +36,7 @@ export async function processDeviceData(message) {
console.error('消息不是合法JSON,跳过:', msgStr); console.error('消息不是合法JSON,跳过:', msgStr);
return; return;
} }
console.log('收到数据:', data); console.log('收到环境数据:', data.length);
// 提取关键字段 // 提取关键字段
const devEUI = data.devEUI ? data.devEUI.toUpperCase() : null; const devEUI = data.devEUI ? data.devEUI.toUpperCase() : null;
...@@ -89,6 +89,12 @@ export async function processDeviceData(message) { ...@@ -89,6 +89,12 @@ export async function processDeviceData(message) {
console.log(`数据入库成功: 设备 ${devEUI}`); console.log(`数据入库成功: 设备 ${devEUI}`);
// 晚上 23:00 ~ 次日 05:00 不执行空调自控(休眠时段)
const currentHour = new Date().getHours();
if (currentHour >= 23 || currentHour < 5) {
console.log(`当前时间 ${currentHour}:00,处于休眠时段(23:00-05:00),跳过空调自控`);
return;
}
// 根据regionKey查询区域表,若包含文创,则禁用空调控制 // 根据regionKey查询区域表,若包含文创,则禁用空调控制
if (deviceRow.data && deviceRow.data.region_key) { if (deviceRow.data && deviceRow.data.region_key) {
const regionRow = await selectOneDataByParam( const regionRow = await selectOneDataByParam(
...@@ -96,13 +102,13 @@ export async function processDeviceData(message) { ...@@ -96,13 +102,13 @@ export async function processDeviceData(message) {
["name"] ["name"]
); );
// 调用空调控制(非文创区域才允许自动控温) // 调用空调控制(非文创区域才允许自动控温)
if (regionRow?.data && !regionRow.data.name?.includes("文创") // if (regionRow?.data && !regionRow.data.name?.includes("文创")
&& !regionRow.data.name?.includes("原料厅") // && !regionRow.data.name?.includes("原料厅")
&& !regionRow.data.name?.includes("社会责任厅") // && !regionRow.data.name?.includes("社会责任厅")
&& !regionRow.data.name?.includes("新包装") // && !regionRow.data.name?.includes("新包装")
) { // ) {
await controlAcByEnvironment(devEUI, data); await controlAcByEnvironment(devEUI, data);
} // }
} }
} catch (error) { } catch (error) {
...@@ -146,7 +152,7 @@ export async function customerDeviceData(message) { ...@@ -146,7 +152,7 @@ export async function customerDeviceData(message) {
console.error('消息不是合法JSON,跳过:', msgStr); console.error('消息不是合法JSON,跳过:', msgStr);
return; return;
} }
console.log('收到客流数据:', data); console.log('收到客流数据:', data.length);
// 提取设备信息 // 提取设备信息
const deviceInfo = data.device_info; const deviceInfo = data.device_info;
...@@ -223,14 +229,14 @@ export async function customerDeviceData(message) { ...@@ -223,14 +229,14 @@ export async function customerDeviceData(message) {
updated_at: now, updated_at: now,
} }
); );
console.log(`客流设备已创建: ${deviceSn}, 线路 ${item.triggerKey}`); // console.log(`客流设备已创建: ${deviceSn}, 线路 ${item.triggerKey}`);
} }
} }
// 遍历每条线路的客流数据,逐条入库 // 遍历每条线路的客流数据,逐条入库
for (const item of triggerItems) { for (const item of triggerItems) {
const inCount = item.total?.in ?? 0; // const inCount = item.total?.in ?? 0;
const outCount = item.total?.out ?? 0; // const outCount = item.total?.out ?? 0;
await addData( await addData(
TABLENAME.设备数据表, TABLENAME.设备数据表,
...@@ -243,7 +249,7 @@ export async function customerDeviceData(message) { ...@@ -243,7 +249,7 @@ export async function customerDeviceData(message) {
} }
); );
console.log(`客流数据入库成功: 设备 ${deviceSn}, ${item.triggerName || item.triggerUuid}, 进 ${inCount}${outCount}`); // console.log(`客流数据入库成功: 设备 ${deviceSn}, ${item.triggerName || item.triggerUuid}, 进 ${inCount} 出 ${outCount}`);
} }
console.log(`客流数据处理完成: 设备 ${deviceSn}, 共入库 ${triggerItems.length} 条数据`); console.log(`客流数据处理完成: 设备 ${deviceSn}, 共入库 ${triggerItems.length} 条数据`);
......
...@@ -50,7 +50,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) { ...@@ -50,7 +50,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
} }
let regionInfo: any = await selectDataListByParam(TABLENAME.区域表, regionParam, ["id"]); let regionInfo: any = await selectDataListByParam(TABLENAME.区域表, regionParam, ["id"]);
let regionKeys = regionInfo.data.map((r: any) => r.id); let regionKeys = regionInfo.data.map((r: any) => r.id);
console.log("筛选区域Key:", regionKeys); console.log("筛选区域Key:", regionKeys.length);
// 1.1 获取所选区域三相电表及电能监测设备的用电数据(用于能耗管理) // 1.1 获取所选区域三相电表及电能监测设备的用电数据(用于能耗管理)
let powerParams: any = {device_type: { "%in%": ["三相电表", "电能监测"] }}; let powerParams: any = {device_type: { "%in%": ["三相电表", "电能监测"] }};
...@@ -591,7 +591,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) { ...@@ -591,7 +591,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let d = new Date(rec.device_time); let d = new Date(rec.device_time);
let hourKey = `${d.getHours()}时`; let hourKey = `${d.getHours()}时`;
let data = rec.data; let data = rec.data;
console.log(data); // console.log(data);
if (!hourlyData.has(hourKey)) { if (!hourlyData.has(hourKey)) {
let totalNum = 1; let totalNum = 1;
let onlineNum = data.power === "on" ? 1 : 0; let onlineNum = data.power === "on" ? 1 : 0;
...@@ -603,7 +603,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) { ...@@ -603,7 +603,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
hourlyData.set(hourKey, { online: onlineNum, total: totalNum + 1 }); hourlyData.set(hourKey, { online: onlineNum, total: totalNum + 1 });
} }
} }
console.log("空调在线数据(过去24小时):", hourlyData); console.log("空调在线数据(过去24小时):", hourlyData.size);
// 2.2.3 填充24小时空调在线数据,没有在线数据的小时段则设为零,并计算每小时的空调在线率 // 2.2.3 填充24小时空调在线数据,没有在线数据的小时段则设为零,并计算每小时的空调在线率
for (let i = 0; i < 24; i++) { for (let i = 0; i < 24; i++) {
let key = `${i}时`; let key = `${i}时`;
...@@ -906,9 +906,9 @@ function getSeason(): 'summer' | 'winter' { ...@@ -906,9 +906,9 @@ function getSeason(): 'summer' | 'winter' {
function calcAcTempSet(temperature: number, season: 'summer' | 'winter'): { tempSet: number; fanSpeed: number; onOff: number } { function calcAcTempSet(temperature: number, season: 'summer' | 'winter'): { tempSet: number; fanSpeed: number; onOff: number } {
if (season === 'summer') { if (season === 'summer') {
// 夏季制冷:温度越高,设定越低 // 夏季制冷:温度越高,设定越低
if (temperature > 26) return { tempSet: 22, fanSpeed: 1, onOff: 1 }; // 未达需求:高风制冷 if (temperature > 23) return { tempSet: 19, fanSpeed: 1, onOff: 1 }; // 未达需求:高风制冷
if (temperature >= 22) return { tempSet: 24, fanSpeed: 4, onOff: Math.round(temperature) === 24 ? 0 : 1 }; // 舒适区:四舍五入24℃待机 if (temperature >= 19) return { tempSet: 21, fanSpeed: 4, onOff: Math.round(temperature) === 21 ? 0 : 1 }; // 舒适区:四舍五入21℃待机
return { tempSet: 26, fanSpeed: 4, onOff: 1 }; // 已达需求:低风维持 return { tempSet: 23, fanSpeed: 4, onOff: 1 }; // 已达需求:低风维持
} else { } else {
// 冬季制热:温度越低,设定越高 // 冬季制热:温度越低,设定越高
if (temperature < 18) return { tempSet: 21, fanSpeed: 1, onOff: 1 }; // 未达需求:高风制热 if (temperature < 18) return { tempSet: 21, fanSpeed: 1, onOff: 1 }; // 未达需求:高风制热
......
...@@ -165,7 +165,7 @@ export const TablesConfig = [ ...@@ -165,7 +165,7 @@ export const TablesConfig = [
}, },
device_state: { device_state: {
type: DataTypes.TINYINT, type: DataTypes.TINYINT,
allowNull: false, allowNull: true,
defaultValue: 0, defaultValue: 0,
comment: '设备状态:0=正常 1=硬件故障 2=通信故障 3=离线' comment: '设备状态:0=正常 1=硬件故障 2=通信故障 3=离线'
}, },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment