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

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