diff --git a/api/app/lib/controllers/control/data.js b/api/app/lib/controllers/control/data.js index 26f1fcf..1cf6684 100644 --- a/api/app/lib/controllers/control/data.js +++ b/api/app/lib/controllers/control/data.js @@ -209,6 +209,26 @@ async function getVideoAlarmsAggDay(ctx) { } } +//BI分析-问题处置效率分析 +async function getAlarmsHandleStatistics(ctx) { + try { + const { projectCorrelationId } = ctx.query + const models = ctx.fs.dc.models; + const data = await models.AlarmHandleStatistics.findAll({ + order: [['time', 'DESC']], + where: projectCorrelationId ? { projectCorrelationId: projectCorrelationId } : {}, + limit: 1 + }) + ctx.status = 200; + ctx.body = data; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} //最新动态 async function getLatestDynamic(ctx) { try { @@ -308,5 +328,7 @@ module.exports = { getDataAlarmsAggDay, getAppAlarmsAggDay, getVideoAlarmsAggDay, + getAlarmsHandleStatistics, + getLatestDynamic }; \ No newline at end of file diff --git a/api/app/lib/models/alarm_handle_statistics.js b/api/app/lib/models/alarm_handle_statistics.js new file mode 100644 index 0000000..ad5ef7d --- /dev/null +++ b/api/app/lib/models/alarm_handle_statistics.js @@ -0,0 +1,98 @@ +/* eslint-disable*/ + +'use strict'; + +module.exports = dc => { + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const AlarmHandleStatistics = sequelize.define("alarmHandleStatistics", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: true, + field: "id", + autoIncrement: true, + unique: "alarm_handle_statistics_id_uindex" + }, + time: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "time", + autoIncrement: false + }, + projectCorrelationId: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "project_correlation_id", + autoIncrement: false + }, + day1: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "day1", + autoIncrement: false + }, + day3: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "day3", + autoIncrement: false + }, + day7: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "day7", + autoIncrement: false + }, + day15: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "day15", + autoIncrement: false + }, + day30: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "day30", + autoIncrement: false + }, + day30m: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "day30m", + autoIncrement: false + } + }, { + tableName: "alarm_handle_statistics", + comment: "", + indexes: [] + }); + dc.models.AlarmHandleStatistics = AlarmHandleStatistics; + return AlarmHandleStatistics; +}; \ No newline at end of file diff --git a/api/app/lib/routes/control/index.js b/api/app/lib/routes/control/index.js index 95aa8e5..bda94bc 100644 --- a/api/app/lib/routes/control/index.js +++ b/api/app/lib/routes/control/index.js @@ -36,6 +36,9 @@ module.exports = function (app, router, opts) { app.fs.api.logAttr['GET/video/alarms/agg/day'] = { content: '查询BI分析数据-视频异常', visible: false }; router.get('/video/alarms/agg/day', csData.getVideoAlarmsAggDay); + app.fs.api.logAttr['GET/alarms/handle/statistics'] = { content: '查询BI分析数据-问题处理效率分析', visible: false }; + router.get('/alarms/handle/statistics', csData.getAlarmsHandleStatistics); + //最新动态 app.fs.api.logAttr['GET/latest/dynamic'] = { content: '查询最新动态', visible: false }; router.get('/latest/dynamic', csData.getLatestDynamic); diff --git a/api/app/lib/schedule/alarms_handle_statistics.js b/api/app/lib/schedule/alarms_handle_statistics.js new file mode 100644 index 0000000..f4e945f --- /dev/null +++ b/api/app/lib/schedule/alarms_handle_statistics.js @@ -0,0 +1,550 @@ +'use strict'; +const moment = require('moment'); +const request = require('superagent'); + +module.exports = function (app, opts) { + const { models } = app.fs.dc + const { clickHouse, utils: { anxinStrucIdRange } } = app.fs + const { database: anxinyun } = clickHouse.anxinyun.opts.config + const alarmHandleStatistics = app.fs.scheduleInit( + { + interval: '40 8 15 * * *', + // immediate: true, + //proRun: true, + }, + async () => { + try { + let time = moment().format() + let anxinStruc = await getAxyStructs() + let pomsProject = await pomsProjectRange() + if (anxinStruc.length) { + let dataAlarms = await getDataAlarms(anxinStruc);//数据告警 + let appAlarms = await getAppAlarms(pomsProject);//应用告警 + let videoAlarms = await getVideoAlarms(anxinStruc);//视频告警 + + //算全局 + let dataArrToSave = [] + let dataMap = calculate(dataAlarms, appAlarms, videoAlarms) + let sum = dataAlarms.length + appAlarms.length + videoAlarms.length; + if (sum) { + dataArrToSave.push({ + time: time, + projectCorrelationId: null,//全局 + day1: parseFloat((100 * dataMap.day1 / sum).toFixed(2)), + day3: parseFloat((100 * dataMap.day3 / sum).toFixed(2)), + day7: parseFloat((100 * dataMap.day7 / sum).toFixed(2)), + day15: parseFloat((100 * dataMap.day15 / sum).toFixed(2)), + day30: parseFloat((100 * dataMap.day30 / sum).toFixed(2)), + day30m: parseFloat((100 * dataMap.day30m / sum).toFixed(2)), + }) + } + + //算单个项目 + //pomsProject.map(p => { + //let pid = p.id; + let pid = 26 + let pDataAlarms = dataAlarms.filter(da => da.pomsProject.map(dap => dap.id).indexOf(pid) != -1) + let pAppAlarms = appAlarms.filter(aa => aa.app.projectCorrelations.map(ap => ap.id).indexOf(pid) != -1) + let pVideoAlarms = videoAlarms.filter(va => va.pomsProject.map(dap => dap.id).indexOf(pid) != -1) + + let pDataMap = calculate(pDataAlarms, pAppAlarms, pVideoAlarms) + let sm = pDataAlarms.length + pAppAlarms.length + pVideoAlarms.length; + if (sm) { + dataArrToSave.push({ + time: time, + projectCorrelationId: pid,//单个项目 + day1: parseFloat((100 * pDataMap.day1 / sum).toFixed(2)), + day3: parseFloat((100 * pDataMap.day3 / sum).toFixed(2)), + day7: parseFloat((100 * pDataMap.day7 / sum).toFixed(2)), + day15: parseFloat((100 * pDataMap.day15 / sum).toFixed(2)), + day30: parseFloat((100 * pDataMap.day30 / sum).toFixed(2)), + day30m: parseFloat((100 * pDataMap.day30m / sum).toFixed(2)), + }) + } + //}) + await models.AlarmHandleStatistics.bulkCreate(dataArrToSave) + } + } catch (error) { + console.error(error); + } + } + ) + + function calculate(dataAlarms, appAlarms, videoAlarms) { + try { + //算全局 + let dataMap = { + day1: 0,//当日处理 + day3: 0,//3日内 + day7: 0,//7日内 + day15: 0,//15日内 + day30: 0,//30日内 + day30m: 0//超过30日 + } + dataAlarms.filter(d => d.State > 3).map(da => { + let range = moment(da.confirmTime).diff(moment(da.StartTime), 'day') + if (range <= 1) { + dataMap.day1++ + } else if (range > 1 && range <= 3) { + dataMap.day3++ + } else if (range > 3 && range <= 7) { + dataMap.day7++ + } else if (range > 7 && range <= 15) { + dataMap.day15++ + } else if (range > 15 && range <= 30) { + dataMap.day30++ + } else if (range > 30) { + dataMap.day30m++ + } + }) + appAlarms.filter(d => d.confirmTime).map(da => { + let range = moment(da.confirmTime).diff(moment(da.createTime), 'day') + if (range < 1) { + dataMap.day1++ + } else if (range >= 1 && range < 3) { + dataMap.day3++ + } else if (range >= 3 && range < 7) { + dataMap.day7++ + } else if (range >= 7 && range < 15) { + dataMap.day15++ + } else if (range >= 15 && range < 30) { + dataMap.day30++ + } else if (range >= 30) { + dataMap.day30m++ + } + }) + videoAlarms.filter(d => d.confirmTime || d.autoRestore).map(da => { + let range = moment(da.confirmTime).diff(moment(da.createTime), 'day') + if (range < 1) { + dataMap.day1++ + } else if (range >= 1 && range < 3) { + dataMap.day3++ + } else if (range >= 3 && range < 7) { + dataMap.day7++ + } else if (range >= 7 && range < 15) { + dataMap.day15++ + } else if (range >= 15 && range < 30) { + dataMap.day30++ + } else if (range >= 30) { + dataMap.day30m++ + } + }) + return dataMap; + } catch (error) { + console.error(error); + } + } + + async function getDataAlarms(anxinStruc) { + try { + const anxinStrucIds = anxinStruc.map(a => a.strucId) + let whereOption = [] + whereOption.push(`alarms.StructureId IN (${anxinStrucIds.join(",")})`) + + let start = moment().add(-1, 'year').format('YYYY-MM-DD HH:mm:ss');//最近一年 + whereOption.push(`alarms.StartTime >= '${start}'`) + + let alarmQueryOptionStr = `FROM alarms + LEFT JOIN ${anxinyun}.t_structure + ON ${anxinyun}.t_structure.id = alarms.StructureId + ${whereOption.length ? 'WHERE ' + whereOption.join(' AND ') : ''}` + + const alarmRes = await clickHouse.dataAlarm.query(` + SELECT + alarms.AlarmId AS AlarmId, + alarms.State AS State, + alarms.StructureId AS StructureId, + SourceName, StartTime, EndTime + ${alarmQueryOptionStr}`).toPromise(); + + const confirmedAlarm = alarmRes.filter(ar => ar.State && ar.State > 2).map(ar => "'" + ar.AlarmId + "'"); + const confirmedAlarmDetailMax = confirmedAlarm.length ? + await clickHouse.dataAlarm.query(` + SELECT + max(Time) AS Time, AlarmId , max(Content) AS Content + FROM + alarm_details + WHERE + AlarmId IN (${confirmedAlarm.join(',')}) + GROUP BY AlarmId + `).toPromise() : []; + + alarmRes.forEach(ar => { + ar.pomsProject = ( + anxinStruc.find(as => as.strucId == ar.StructureId) || + { + pomsProject: [ + // TODO: 开发临时添加 + ] + } + ).pomsProject + + // 最新告警详情 - 确认信息 + let corConfirmedData = (confirmedAlarmDetailMax.find(cdm => cdm.AlarmId == ar.AlarmId) || {}); + ar.confirmTime = corConfirmedData.Time || ar.EndTime + }) + return alarmRes; + } catch (error) { + console.error(error); + } + } + + async function getAppAlarms(pomsProject) { + try { + const pomsProjectIds = pomsProject.map(p => p.id) + let findOption = { + where: { + createTime: { $gte: moment().add(-1, 'year').format() },//最近一年 + }, + attributes: ['id', 'createTime', 'confirmTime'], + include: [{ + model: models.App, + attributes: { + exclude: ['projectId'] + }, + include: [{ + model: models.ProjectCorrelation, + attributes: ['id'] + }] + }] + } + findOption.where['$app->projectCorrelations.id$'] = { + $in: pomsProjectIds + } + const listRes = await models.AppAlarm.findAll(findOption) + return listRes + } catch (error) { + console.error(error); + } + } + async function getVideoAlarms(anxinStruc) { + try { + const anxinStrucIds = anxinStruc.map(a => a.strucId) + let statusAlarmWhereOption = [] + let start = moment().add(-1, 'year').format('YYYY-MM-DD HH:mm:ss');//最近一年 + statusAlarmWhereOption.push(`camera_status_alarm.create_time >= '${start}'`) + + const alarmRes = anxinStrucIds.length ? await clickHouse.vcmp.query( + ` + SELECT + cameraAlarm.cameraId AS cameraId, + cameraAlarm.cameraName AS cameraName, + cameraAlarm.cameraKindId AS cameraKindId, + cameraAlarm.venderId AS venderId, + cameraAlarm.venderName AS venderName, + cameraAlarm.cameraSerialNo AS cameraSerialNo, + cameraAlarm.cameraChannelNo AS cameraChannelNo, + cameraAlarm.alarmId AS alarmId, + cameraAlarm.createTime AS createTime, + cameraAlarm.updateTime AS updateTime, + cameraAlarm.platform AS platform, + cameraAlarm.confirmContent AS confirmContent, + cameraAlarm.confirmTime AS confirmTime, + ${'cameraAlarm.autoRestore AS autoRestore,'} + camera_status_resolve.id AS resolveId, + camera_status.describe AS statusDescribe, + camera_status_resolve.resolve AS resolve, + "gbCamera".online AS cameraOnline, + secret_yingshi.token AS yingshiToken, + anxinIpc.t_video_ipc.name AS anxinIpcPosition, + anxinStation.id AS anxinStationId, + anxinStation.name AS anxinStationName, + anxinStruc.name AS strucName, + anxinStruc.id AS strucId + FROM + ( + SELECT + camera.id AS cameraId, + camera.gb_id AS gbId, + camera.name AS cameraName, + camera.kind_id AS cameraKindId, + camera.vender_id AS venderId, + camera.yingshi_secret_id AS yingshiSecretId, + vender.name AS venderName, + camera_status_alarm.id AS alarmId, + camera_status_alarm.create_time AS createTime, + camera_status_alarm.update_time AS updateTime, + camera_status_alarm.platform AS platform, + camera_status_alarm.status_id AS statusId, + camera_status_alarm.serial_no AS cameraSerialNo, + camera_status_alarm.channel_no AS cameraChannelNo, + camera_status_alarm.confirm AS confirmContent, + ${'camera_status_alarm.auto_restore AS autoRestore,'} + camera_status_alarm.confirm_time AS confirmTime + FROM camera_status_alarm + INNER JOIN camera + ON camera.serial_no = camera_status_alarm.serial_no + AND camera.channel_no = camera_status_alarm.channel_no + LEFT JOIN vender + ON vender.id = camera.vender_id + WHERE + camera.delete = false + AND camera.recycle_time is null + ${statusAlarmWhereOption.length ? 'AND ' + statusAlarmWhereOption.join(' AND ') : ''} + AND alarmId IN ( + SELECT camera_status_alarm.id AS alarmId + FROM camera_status_alarm + RIGHT JOIN ${anxinyun}.t_video_ipc + ON toString(${anxinyun}.t_video_ipc.channel_no) = camera_status_alarm.channel_no + AND ${anxinyun}.t_video_ipc.serial_no = camera_status_alarm.serial_no + ${`WHERE ${anxinyun}.t_video_ipc.structure IN (${anxinStrucIds.join(',')})`} + ) + ) AS cameraAlarm + LEFT JOIN camera_status + ON cameraAlarm.platform = camera_status.platform + AND cameraAlarm.statusId = camera_status.id + LEFT JOIN camera_status_resolve + ON camera_status_resolve.status_id = camera_status.id + LEFT JOIN "gbCamera" + ON "gbCamera".id = cameraAlarm.gbId + LEFT JOIN "secret_yingshi" + ON "secret_yingshi".id = cameraAlarm.yingshiSecretId + + LEFT JOIN ${anxinyun}.t_video_ipc AS anxinIpc + ON toString(anxinIpc.channel_no) = cameraAlarm.cameraChannelNo + AND anxinIpc.serial_no = cameraAlarm.cameraSerialNo + LEFT JOIN ${anxinyun}.t_structure AS anxinStruc + ON anxinStruc.id = anxinIpc.structure + AND anxinStruc.id IN (${anxinStrucIds.join(',')}) + LEFT JOIN ${anxinyun}.t_video_ipc_station AS anxinIpcStation + ON anxinIpcStation.ipc = anxinIpc.id + LEFT JOIN ${anxinyun}.t_sensor AS anxinStation + ON anxinStation.id = anxinIpcStation.station + `).toPromise() : [] + + let returnD = [] + let positionD = {} + // 每个设备一个告警 + for (let a of alarmRes) { + if (positionD[a.cameraId]) { + let curD = returnD[positionD[a.cameraId].positionReturnD] + if (a.resolveId && !curD.resolve.some(r => r.id == a.resolveId)) { + curD.resolve.push({ + id: a.resolveId, + resolve: a.resolve + }) + } + if (a.strucId && !curD.struc.some(s => s.id == a.strucId)) { + curD.struc.push({ + id: a.strucId, + projectId: a.projectId, + name: a.strucName + }) + } + if (a.anxinStationId && !curD.station.some(s => s.id == a.anxinStationId)) { + curD.station.push({ + id: a.anxinStationId, + name: a.anxinStationName, + position: a.anxinIpcPosition + }) + } + } else { + let d = { + cameraId: a.cameraId, + cameraName: a.cameraName, + camerOnline: a.cameraOnline, + cameraSerialNo: a.cameraSerialNo, + cameraChannelNo: a.cameraChannelNo, + autoRestore: a.autoRestore, + createTime: a.createTime, + updateTime: a.updateTime, + platform: a.platform, + statusDescribe: a.statusDescribe, + alarmId: a.alarmId, + confirmContent: a.confirmContent, + confirmTime: a.confirmTime, + + venderId: a.venderId, + venderName: a.venderName, + cameraKindId: a.cameraKindId, + yingshiToken: a.yingshiToken, + + resolve: [], + struc: [], + station: [] + } + + // pep 项目 + d.pomsProject = ( + anxinStruc.find(as => as.strucId == a.strucId) || + { + pomsProject: [ + + ] + } + ).pomsProject + + if (a.resolveId) { + d.resolve.push({ + id: a.resolveId, + resolve: a.resolve + }) + } + if (a.strucId) { + d.struc.push({ + id: a.strucId, + projectId: a.projectId, + name: a.strucName + }) + } + if (a.anxinStationId) { + d.station.push({ + id: a.anxinStationId, + name: a.anxinStationName, + position: a.anxinIpcPosition + }) + } + returnD.push(d) + positionD[a.cameraId] = { + positionReturnD: returnD.length - 1 + } + } + } + return returnD; + } catch (error) { + console.error(error); + } + } + async function pomsProjectRange() { + try { + const { pepProjectRes, bindRes } = await pomsWithPepRangeParams() + let pomsProject = [] + for (let b of bindRes) { + if (b.pepProjectId) { + let corPepProject = pepProjectRes.find(pp => pp.id == b.pepProjectId) || {} + pomsProject.push({ + ...b.dataValues, + pepProject: corPepProject + }) + } else { + pomsProject.push({ + ...b.dataValues + }) + } + } + return pomsProject + } catch (error) { + console.error(error); + } + } + + async function pomsWithPepRangeParams() { + try { + const bindRes = await models.ProjectCorrelation.findAll({ where: { del: false } }); + // 获取不重复的 项企项目id + let pepProjectIds = [] + for (let b of bindRes) { + if (b.pepProjectId) { + pepProjectIds.push(b.pepProjectId) + } + } + // 查询项企项目的信息 + const pepProjectRes = pepProjectIds.length ? + await clickHouse.projectManage.query( + `SELECT + t_pim_project.id AS id, + t_pim_project.project_name AS projectName, + t_pim_project.isdelete AS isdelete, + t_pim_project_construction.construction_status_id AS constructionStatusId, + t_pim_project_state.construction_status AS constructionStatus + FROM + t_pim_project + LEFT JOIN t_pim_project_construction + ON t_pim_project.id = t_pim_project_construction.project_id + LEFT JOIN t_pim_project_state + ON t_pim_project_construction.construction_status_id = t_pim_project_state.id + WHERE + id IN (${pepProjectIds.join(',')})` + ).toPromise() : []; + + return { + pepProjectRes, bindRes + } + + } catch (error) { + console.error(error); + } + } + + async function getAxyStructs() { + try { + const { pepProjectRes, bindRes } = await pomsWithPepRangeParams() + // 获取不重复的 安心云项目 id + const anxinProjectIds = [ + ...(bindRes).reduce( + (arr, b) => { + for (let sid of b.anxinProjectId) { + arr.add(sid); + } + return arr; + }, + new Set() + ) + ] + // 查询安心云项目及结构物信息 + const undelStrucRes = anxinProjectIds.length ? + await clickHouse.anxinyun.query( + `SELECT + t_project.id AS projectId, + t_structure.id AS strucId, + t_structure.name AS strucName, + project_state + FROM + t_project + LEFT JOIN + t_project_structure + ON t_project_structure.project = t_project.id + LEFT JOIN + t_project_structuregroup + ON t_project_structuregroup.project = t_project.id + LEFT JOIN + t_structuregroup_structure + ON t_structuregroup_structure.structuregroup = t_project_structuregroup.structuregroup + LEFT JOIN + t_project_construction + ON t_project_construction.project = t_project.id + LEFT JOIN + t_structure_site + ON t_structure_site.siteid = t_project_construction.construction + RIGHT JOIN + t_structure + ON t_structure.id = t_project_structure.structure + OR t_structure.id = t_structuregroup_structure.structure + OR t_structure.id = t_structure_site.structid + WHERE + project_state != -1 + AND + t_project.id IN (${anxinProjectIds.join(',')})`).toPromise() : [] + + // 构建安心云结构物和项企项目的关系 + // 并保存信息至数据 + let undelStruc = [] + for (let s of undelStrucRes) { + if (!undelStruc.some(us => us.strucId == s.strucId)) { + let pomsProject = [] + for (let { dataValues: br } of bindRes) { + if (br.anxinProjectId.some(braId => braId == s.projectId)) { + let corPepProject = pepProjectRes.find(pp => pp.id == br.pepProjectId) + pomsProject.push({ + ...br, + pepProject: corPepProject + }) + } + } + undelStruc.push({ + strucId: s.strucId, + strucName: s.strucName, + projectId: s.projectId, + pomsProject: pomsProject + }) + } + } + return undelStruc + } catch (error) { + console.error(error); + } + } + return { + alarmHandleStatistics + } +} \ No newline at end of file