数据集成-电表关联区域;运行分析客流监控和空气质量优化

parent ae21dd9b
......@@ -36,6 +36,7 @@
"tencentcloud-sdk-nodejs": "^4.0.562",
"winston": "^3.17.0",
"ws": "^5.2.2",
"xlsx": "^0.18.5",
"xml2js": "^0.4.23",
"xmlrpc": "^1.3.2"
},
......@@ -292,16 +293,10 @@
}
},
"node_modules/adler-32": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz",
"integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=",
"dependencies": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
},
"bin": {
"adler32": "bin/adler32.njs"
},
"version": "1.3.1",
"resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz",
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
......@@ -637,13 +632,13 @@
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"node_modules/cfb": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.0.tgz",
"integrity": "sha512-sXMvHsKCICVR3Naq+J556K+ExBo9n50iKl6LGarlnvuA2035uMlGA/qVrc0wQtow5P1vJEw9UyrKLCbtIKz+TQ==",
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz",
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
"license": "Apache-2.0",
"dependencies": {
"adler-32": "~1.2.0",
"crc-32": "~1.2.0",
"printj": "~1.1.2"
"adler-32": "~1.3.0",
"crc-32": "~1.2.0"
},
"engines": {
"node": ">=0.8"
......@@ -658,25 +653,14 @@
}
},
"node_modules/codepage": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.14.0.tgz",
"integrity": "sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k=",
"dependencies": {
"commander": "~2.14.1",
"exit-on-epipe": "~1.0.1"
},
"bin": {
"codepage": "bin/codepage.njs"
},
"version": "1.15.0",
"resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz",
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/codepage/node_modules/commander": {
"version": "2.14.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz",
"integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw=="
},
"node_modules/color": {
"version": "3.2.1",
"resolved": "https://registry.npmmirror.com/color/-/color-3.2.1.tgz",
......@@ -730,8 +714,9 @@
},
"node_modules/commander": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg=="
"resolved": "https://registry.npmmirror.com/commander/-/commander-2.17.1.tgz",
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
"license": "MIT"
},
"node_modules/compressible": {
"version": "2.0.18",
......@@ -847,13 +832,10 @@
}
},
"node_modules/crc-32": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz",
"integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==",
"dependencies": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
},
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"license": "Apache-2.0",
"bin": {
"crc32": "bin/crc32.njs"
},
......@@ -1010,6 +992,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz",
"integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
......@@ -1200,8 +1183,9 @@
},
"node_modules/fflate": {
"version": "0.3.11",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.3.11.tgz",
"integrity": "sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A=="
"resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.3.11.tgz",
"integrity": "sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A==",
"license": "MIT"
},
"node_modules/finalhandler": {
"version": "1.3.1",
......@@ -2172,6 +2156,68 @@
"node": ">=4.0.0"
}
},
"node_modules/node-xlsx/node_modules/adler-32": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.2.0.tgz",
"integrity": "sha512-/vUqU/UY4MVeFsg+SsK6c+/05RZXIHZMGJA+PX5JyWI0ZRcBpupnRuPLU/NXXoFwMYCPCoxIfElM2eS+DUXCqQ==",
"license": "Apache-2.0",
"dependencies": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
},
"bin": {
"adler32": "bin/adler32.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/node-xlsx/node_modules/codepage": {
"version": "1.14.0",
"resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.14.0.tgz",
"integrity": "sha512-iz3zJLhlrg37/gYRWgEPkaFTtzmnEv1h+r7NgZum2lFElYQPi0/5bnmuDfODHxfp0INEfnRqyfyeIJDbb7ahRw==",
"license": "Apache-2.0",
"dependencies": {
"commander": "~2.14.1",
"exit-on-epipe": "~1.0.1"
},
"bin": {
"codepage": "bin/codepage.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/node-xlsx/node_modules/codepage/node_modules/commander": {
"version": "2.14.1",
"resolved": "https://registry.npmmirror.com/commander/-/commander-2.14.1.tgz",
"integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==",
"license": "MIT"
},
"node_modules/node-xlsx/node_modules/xlsx": {
"version": "0.16.9",
"resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.16.9.tgz",
"integrity": "sha512-gxi1I3EasYvgCX1vN9pGyq920Ron4NO8PNfhuoA3Hpq6Y8f0ECXiy4OLrK4QZBnj1jx3QD+8Fq5YZ/3mPZ5iXw==",
"license": "Apache-2.0",
"dependencies": {
"adler-32": "~1.2.0",
"cfb": "^1.1.4",
"codepage": "~1.14.0",
"commander": "~2.17.1",
"crc-32": "~1.2.0",
"exit-on-epipe": "~1.0.1",
"fflate": "^0.3.8",
"ssf": "~0.11.2",
"wmf": "~1.0.1",
"word": "~0.3.0"
},
"bin": {
"xlsx": "bin/xlsx.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/nodemailer": {
"version": "6.5.0",
"resolved": "https://registry.npm.taobao.org/nodemailer/download/nodemailer-6.5.0.tgz?cache=0&sync_timestamp=1614329161993&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnodemailer%2Fdownload%2Fnodemailer-6.5.0.tgz",
......@@ -2261,6 +2307,7 @@
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/printj/-/printj-1.1.2.tgz",
"integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==",
"license": "Apache-2.0",
"bin": {
"printj": "bin/printj.njs"
},
......@@ -3290,17 +3337,15 @@
}
},
"node_modules/xlsx": {
"version": "0.16.9",
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.16.9.tgz",
"integrity": "sha512-gxi1I3EasYvgCX1vN9pGyq920Ron4NO8PNfhuoA3Hpq6Y8f0ECXiy4OLrK4QZBnj1jx3QD+8Fq5YZ/3mPZ5iXw==",
"dependencies": {
"adler-32": "~1.2.0",
"cfb": "^1.1.4",
"codepage": "~1.14.0",
"commander": "~2.17.1",
"crc-32": "~1.2.0",
"exit-on-epipe": "~1.0.1",
"fflate": "^0.3.8",
"version": "0.18.5",
"resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz",
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
"license": "Apache-2.0",
"dependencies": {
"adler-32": "~1.3.0",
"cfb": "~1.2.1",
"codepage": "~1.15.0",
"crc-32": "~1.2.1",
"ssf": "~0.11.2",
"wmf": "~1.0.1",
"word": "~0.3.0"
......@@ -3604,13 +3649,9 @@
}
},
"adler-32": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz",
"integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
"version": "1.3.1",
"resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz",
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A=="
},
"agent-base": {
"version": "6.0.2",
......@@ -3860,13 +3901,12 @@
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"cfb": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.0.tgz",
"integrity": "sha512-sXMvHsKCICVR3Naq+J556K+ExBo9n50iKl6LGarlnvuA2035uMlGA/qVrc0wQtow5P1vJEw9UyrKLCbtIKz+TQ==",
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz",
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
"requires": {
"adler-32": "~1.2.0",
"crc-32": "~1.2.0",
"printj": "~1.1.2"
"adler-32": "~1.3.0",
"crc-32": "~1.2.0"
}
},
"charenc": {
......@@ -3875,20 +3915,9 @@
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
},
"codepage": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.14.0.tgz",
"integrity": "sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k=",
"requires": {
"commander": "~2.14.1",
"exit-on-epipe": "~1.0.1"
},
"dependencies": {
"commander": {
"version": "2.14.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz",
"integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw=="
}
}
"version": "1.15.0",
"resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz",
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA=="
},
"color": {
"version": "3.2.1",
......@@ -3940,7 +3969,7 @@
},
"commander": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
"resolved": "https://registry.npmmirror.com/commander/-/commander-2.17.1.tgz",
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg=="
},
"compressible": {
......@@ -4021,13 +4050,9 @@
}
},
"crc-32": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz",
"integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="
},
"crypt": {
"version": "0.0.2",
......@@ -4276,7 +4301,7 @@
},
"fflate": {
"version": "0.3.11",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.3.11.tgz",
"resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.3.11.tgz",
"integrity": "sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A=="
},
"finalhandler": {
......@@ -5002,6 +5027,50 @@
"@babel/runtime": "^7.12.5",
"buffer-from": "^1.1.1",
"xlsx": "^0.16.9"
},
"dependencies": {
"adler-32": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.2.0.tgz",
"integrity": "sha512-/vUqU/UY4MVeFsg+SsK6c+/05RZXIHZMGJA+PX5JyWI0ZRcBpupnRuPLU/NXXoFwMYCPCoxIfElM2eS+DUXCqQ==",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
},
"codepage": {
"version": "1.14.0",
"resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.14.0.tgz",
"integrity": "sha512-iz3zJLhlrg37/gYRWgEPkaFTtzmnEv1h+r7NgZum2lFElYQPi0/5bnmuDfODHxfp0INEfnRqyfyeIJDbb7ahRw==",
"requires": {
"commander": "~2.14.1",
"exit-on-epipe": "~1.0.1"
},
"dependencies": {
"commander": {
"version": "2.14.1",
"resolved": "https://registry.npmmirror.com/commander/-/commander-2.14.1.tgz",
"integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw=="
}
}
},
"xlsx": {
"version": "0.16.9",
"resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.16.9.tgz",
"integrity": "sha512-gxi1I3EasYvgCX1vN9pGyq920Ron4NO8PNfhuoA3Hpq6Y8f0ECXiy4OLrK4QZBnj1jx3QD+8Fq5YZ/3mPZ5iXw==",
"requires": {
"adler-32": "~1.2.0",
"cfb": "^1.1.4",
"codepage": "~1.14.0",
"commander": "~2.17.1",
"crc-32": "~1.2.0",
"exit-on-epipe": "~1.0.1",
"fflate": "^0.3.8",
"ssf": "~0.11.2",
"wmf": "~1.0.1",
"word": "~0.3.0"
}
}
}
},
"nodemailer": {
......@@ -5832,17 +5901,14 @@
}
},
"xlsx": {
"version": "0.16.9",
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.16.9.tgz",
"integrity": "sha512-gxi1I3EasYvgCX1vN9pGyq920Ron4NO8PNfhuoA3Hpq6Y8f0ECXiy4OLrK4QZBnj1jx3QD+8Fq5YZ/3mPZ5iXw==",
"requires": {
"adler-32": "~1.2.0",
"cfb": "^1.1.4",
"codepage": "~1.14.0",
"commander": "~2.17.1",
"crc-32": "~1.2.0",
"exit-on-epipe": "~1.0.1",
"fflate": "^0.3.8",
"version": "0.18.5",
"resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz",
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
"requires": {
"adler-32": "~1.3.0",
"cfb": "~1.2.1",
"codepage": "~1.15.0",
"crc-32": "~1.2.1",
"ssf": "~0.11.2",
"wmf": "~1.0.1",
"word": "~0.3.0"
......
......@@ -31,6 +31,7 @@
"tencentcloud-sdk-nodejs": "^4.0.562",
"winston": "^3.17.0",
"ws": "^5.2.2",
"xlsx": "^0.18.5",
"xml2js": "^0.4.23",
"xmlrpc": "^1.3.2"
},
......
......@@ -7,6 +7,8 @@ import {
} from "./feiyiClient";
import { mysqlModelMap } from "../model/sqlModelBind";
import moment from "moment";
import XLSX from "xlsx";
import path from "path";
// ==================== 工具函数 ====================
......@@ -17,6 +19,42 @@ function getTodayDateStr(): string {
return moment().format('YYYYMMDD');
}
/**
* 从内机四段地址(如 "2-1-0-1")提取外机地址("2-1-0")
*/
function getOuterAddress(indoorUnitAddressFull: string): string | null {
if (!indoorUnitAddressFull) return null;
const parts = indoorUnitAddressFull.split('-');
if (parts.length >= 3) {
return parts.slice(0, 3).join('-');
}
return null;
}
/**
* 从 Excel 文件读取电表设备编号 → 外机地址 的映射表
*/
function loadMeterOuterAddrMap(): Map<string, string> {
const map = new Map<string, string>();
try {
const filePath = path.resolve(__dirname, '../../res/电表对应区域.xlsx');
const workbook = XLSX.readFile(filePath);
const sheet = workbook.Sheets[workbook.SheetNames[0]];
const rows: any[] = XLSX.utils.sheet_to_json(sheet);
for (const row of rows) {
const meterCode = row['电表设备编号'];
const outerAddr = row['空调外机地址'];
if (meterCode && outerAddr) {
map.set(String(meterCode), String(outerAddr));
}
}
console.log(`[工具] 电表对应区域.xlsx 读取完成,共 ${map.size} 条映射`);
} catch (err) {
console.error('[工具] 读取电表对应区域.xlsx 失败:', err);
}
return map;
}
// ==================== 分页拉取辅助函数 ====================
/**
......@@ -150,11 +188,137 @@ export async function region() {
}
console.log(`[区域集成] 完成,新增 ${insertCount} 条,跳过 ${skipCount} 条已存在`);
// 2. 一次性查询所有 region,后续两步共用内存数据,避免 DB 读写时序问题
const allRegions = await regionModel.findAll({ raw: true }) as any[];
// 3. 同步空调内机 → 反写 region.address
const updatedRegions = await syncRegionAddressByAcDevices(regionModel, allRegions);
// 4. 同步电表设备 region_key(复用上一步已更新 address 的内存数据)
await syncMeterDeviceRegionKey(updatedRegions);
} catch (err) {
console.error('[区域集成] 执行异常:', err);
}
}
// ==================== 区域-设备关联同步 ====================
/**
* 通过空调内机信息反写 region 表的 address 字段(外机地址)
* 逻辑:拉取所有空调内机 → 通过 roomId 匹配 region → 用内机地址前三位反写 region.address
*/
async function syncRegionAddressByAcDevices(regionModel: any, allRegions: any[]) {
console.log('[同步region.address] 开始...');
try {
// 拉取所有空调内机
const rows = await fetchAllPages((page, limit) => getIndoorUnitList({ page, limit }));
console.log(`[同步region.address] 共获取 ${rows.length} 条空调内机`);
// 构建 roomId → region 映射(使用外部传入的 allRegions)
const roomIdToRegion = new Map<string, any>();
for (const r of allRegions) {
if (r.room_id) {
roomIdToRegion.set(r.room_id, r);
}
}
let updateCount = 0;
for (const item of rows) {
if (!item.roomId || !item.indoorUnitAddressFull) continue;
const region = roomIdToRegion.get(item.roomId);
if (!region) continue;
// 用内机四段地址前三位作为外机地址
const outerAddr = getOuterAddress(item.indoorUnitAddressFull);
if (!outerAddr) continue;
// address 为空或值不同时才更新
if (region.address === outerAddr) continue;
await regionModel.update(
{ address: outerAddr },
{ where: { id: region.id } },
);
// 同步更新内存中的值,后续 syncMeterDeviceRegionKey 可直接使用
region.address = outerAddr;
updateCount++;
}
console.log(`[同步region.address] 完成,更新 ${updateCount} 条`);
return allRegions; // 返回已更新 address 的内存数据
} catch (err) {
console.error('[同步region.address] 执行异常:', err);
return allRegions; // 异常时仍返回原始数据,不阻塞后续流程
}
}
/**
* 通过 Excel 映射 + region.address 同步电表设备的 region_key
* 逻辑:Excel(电表设备编号→外机地址) → region.address → region.id → 更新 device.region_key
*/
async function syncMeterDeviceRegionKey(allRegions: any[]) {
console.log('[同步电表region_key] 开始...');
try {
const deviceModel = mysqlModelMap['device'];
if (!deviceModel) {
console.error('[同步电表region_key] device 表模型未初始化,跳过');
return;
}
// 1. 读取 Excel 映射:电表设备编号 → 外机地址
const meterOuterMap = loadMeterOuterAddrMap();
if (meterOuterMap.size === 0) {
console.warn('[同步电表region_key] Excel 映射为空,跳过');
return;
}
// 2. 使用外部传入的 allRegions 构建 address → regionId 映射(复用已更新 address 的内存数据)
const addrToRegionId = new Map<string, number>();
for (const r of allRegions) {
if (r.address) {
addrToRegionId.set(r.address, r.id);
}
}
console.log(`[同步电表region_key] region.address 映射共 ${addrToRegionId.size} 条`);
// 3. 查询所有电表设备
const meterDevices = await deviceModel.findAll({
where: { device_type: '电能监测' },
raw: true,
});
console.log(`[同步电表region_key] 共 ${meterDevices.length} 个电表设备`);
// 4. 逐一匹配更新
let updateCount = 0;
let missCount = 0;
for (const device of meterDevices as any[]) {
const outerAddr = meterOuterMap.get(device.device_id);
if (!outerAddr) {
missCount++;
continue;
}
const regionId = addrToRegionId.get(outerAddr);
if (!regionId) {
missCount++;
continue;
}
// region_key 已正确则跳过
if (device.region_key === regionId) continue;
await deviceModel.update(
{ region_key: regionId },
{ where: { id: device.id } },
);
updateCount++;
}
console.log(`[同步电表region_key] 完成,更新 ${updateCount} 条,未匹配 ${missCount} 条`);
} catch (err) {
console.error('[同步电表region_key] 执行异常:', err);
}
}
// ==================== 设备集成 ====================
/** 空调控制参数模板(按设备数据结构.md) */
......
......@@ -128,13 +128,17 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let airQualityDevices = await selectDataListByParam(TABLENAME.设备表, airParams, ["device_id"]);
let airDeviceIds = airQualityDevices.data.map(d => d.device_id);
let qualityIndex = 300;
// 今日环境质量
let todayEnvironmental = '优秀';
let currentCo2 = 0; // 当前二氧化氮
let co2Trend = [];
let currentCo2 = '0ppm';
let currentTemperature = '0℃';
let currentTemperature = '0℃'; // 当前温度
let temperatureTrend = [];
let currentHumidity = '0%';
let currentHumidity = '0%'; // 当前湿度
let humidityTrend = [];
let currentPm25 = '0μg/m³';
let currentPm25 = 0; // 当前PM2.5
let pm25Trend = [];
let co2TrendDetail = [];
......@@ -149,8 +153,8 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let airData = latestAir.data[0].data;
currentTemperature = `${airData.temperature ?? 0}`;
currentHumidity = `${airData.humidity ?? 0}%`;
currentPm25 = `${airData.pm25 ?? 0}μg/m³`;
currentCo2 = `${airData.co2 ?? 0}ppm`;
currentPm25 = airData.pm25;
currentCo2 = airData.co2 ?? 0;
qualityIndex = 500 - (airData.pm25 ?? 0);
}
// 过去24小时趋势
......@@ -179,12 +183,27 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
co2Trend.push({ time: `${i}时`, value: co2Val.toString() });
}
}
// 计算今日环境质量
if (currentCo2 && currentPm25) {
// 优秀:CO2< 800ppm; PM2.5≤35; 甲醛≤0.055
todayEnvironmental = currentCo2<800 && currentPm25<=35 ? '优秀':
// 优秀:CO2< 1000ppm; PM2.5≤45; TVOC≤0.4
currentCo2<1000 && currentPm25<=45 ? '优秀':
// 良好:CO2≤ 1000ppm; PM2.5≤50; 甲醛≤0.08
currentCo2<=1000 && currentPm25<=50 ? '良好':
// 一般:CO2:1000~1500; PM2.5:50~75
currentCo2>1000 && currentCo2<1500 && currentPm25>50 && currentPm25<75 ? '一般': '差'
}
// 4.2. 环境监测趋势图数据
resultAny.environmentalTrend = {
todayEnvironmental: todayEnvironmental,
currentTemperature: currentTemperature,
currentHumidity: currentHumidity,
currentPm25: currentPm25,
currentCo2: currentCo2,
currentPm25: currentPm25+'μg/m³',
currentCo2: currentCo2+'ppm',
temperatureTrend: temperatureTrend,
humidityTrend: humidityTrend,
pm25Trend: pm25Trend,
......@@ -234,10 +253,10 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let regionInfos: any = [];
if (regionKeys.length === 0) {
// 6.1.1 若没有选择区域,则查询区域表获取所有区域类型
regionInfos = await selectDataListByParam(TABLENAME.区域表, {}, ["id", "name", "type"]);
regionInfos = await selectDataListByParam(TABLENAME.区域表, {}, ["id", "room_id", "name", "type", "groups"]);
} else {
// 6.1.2 若选择了区域,则查询选择的区域
regionInfos = await selectDataListByParam(TABLENAME.区域表, { id: { "%in%": regionKeys } }, ["id", "name", "type"]);
regionInfos = await selectDataListByParam(TABLENAME.区域表, { id: { "%in%": regionKeys } }, ["id", "room_id", "name", "type", "groups"]);
}
// 6.2 循环区域查询客流传感器设备,获取过去24小时每个区域的客流量
......@@ -246,12 +265,20 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let regionCustomerMap = new Map(); // {regionName: customerNum}
let regionCustomerTable = []; // [{regionName: A馆, customerNum: 5000, saturation: '80%'}]
for (let regionInfo of regionInfos.data) {
// 区分楼栋与楼层
let regionName = regionKeys.length === 0 ? regionInfo.groups : regionInfo.type;
// 如果是按区域类型统计,则最大承载量取该类型下所有区域的总和;如果是按区域名称统计,则取该区域的最大承载量
let maxCapacity = regionKeys.length === 0 ? regionMaxCustomerMap.get(regionName) : regionInfo.max_capacity;
regionMaxCustomerMap.set(regionName, maxCapacity); // 区域最大承载量
// 初始化客流量
let totalNum = regionCustomerMap.get(regionName) ? regionCustomerMap.get(regionName) : 0;
let customerDevices = await selectDataListByParam(TABLENAME.设备表, {
device_type: "客流传感器", region_key: regionInfo.id
}, ["device_type", "device_id"]);
let customerDeviceIds = customerDevices.data.map((d: any) => d.device_id);
// 获取今日的客流量数据
if (customerDeviceIds) {
if (customerDeviceIds && customerDeviceIds.length > 0) {
let customerDeviceDatas = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": customerDeviceIds },
device_time: { "%gte%": formatLocalTime(todayStart), "%lte%": formatLocalTime(todayEnd) },
......@@ -263,19 +290,15 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let data = rec.data;
if (data.presence === '进') {
totalRegionCustomerNum += 1;
// 区分楼栋与楼层
let regionName = regionKeys.length === 0 ? regionInfo.type : regionInfo.name;
if (!regionCustomerMap.has(regionName)) {
regionCustomerMap.set(regionName, 1);
// 如果是按区域类型统计,则最大承载量取该类型下所有区域的总和;如果是按区域名称统计,则取该区域的最大承载量
let maxCapacity = regionKeys.length === 0 ? regionMaxCustomerMap.get(regionName) : regionInfo.max_capacity;
regionMaxCustomerMap.set(regionName, maxCapacity); // 区域最大承载量
} else {
let totalNum = regionCustomerMap.get(regionName);
regionCustomerMap.set(regionName, totalNum + 1);
}
}
}
} else {
regionCustomerMap.set(regionName, totalNum);
}
}
// 计算每个区域的饱和度(客流量/区域最大承载量),并转换成表格
......
......@@ -257,6 +257,10 @@ export const TablesConfig = [
defaultValue: 0,
comment: '排序序号'
},
address: {
type: DataTypes.STRING(100),
comment: '外机地址'
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
......
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