feat: add staged command center scene skills

Stage registry-driven report and monitor scene skills, including the packaged snapshot collectors and scene metadata needed to align with the command center workflow inventory.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
木炎
2026-04-09 10:29:44 +08:00
parent e4283f04cc
commit 6158f720a7
50 changed files with 10314 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
{
"draft": true,
"id": "95598-repair-city-dispatch",
"name": "95598抢修-市指",
"inferred_from": [
"../../skills/95598-repair-city-dispatch/SKILL.toml",
"../../skills/95598-repair-city-dispatch/scripts/collect_repair_orders.js",
"../../skills/95598-repair-city-dispatch/references/collection-flow.md",
"../../skills/95598-repair-city-dispatch/references/data-quality.md",
"../../../大四区报告监测项/95598抢修-市指/index.html",
"../../../大四区报告监测项/95598抢修-市指_业务检测配置.txt",
"../../../大四区报告监测项/95598抢修-市指_自动处理配置.txt"
],
"confidence": {
"category": "high",
"inputs": "high",
"outputs": "high",
"dependencies": "high",
"actions": "medium"
},
"guesses": {
"category": "monitor",
"inputs": ["time"],
"dependencies": ["browser", "local-service", "repair-order-source", "history-log", "status-classification"],
"actions": ["monitor-queue", "classify-status", "compare-history", "write-local-log", "trigger-alert"]
},
"todo": [
"确认 trigger-alert 是否应拆分成 audio-alert、message-alert、callout 三类动作。",
"确认 configServices 是否需要在正式 dependencies 中单独暴露。"
]
}

View File

@@ -0,0 +1,25 @@
{
"id": "95598-repair-city-dispatch",
"name": "95598抢修-市指",
"category": "monitor",
"entry": "index.html",
"summary": "监测 95598 抢修市指工单队列状态,并结合本地日志比较与提醒侧动作返回结构化监测快照。",
"inputs": ["time"],
"outputs": ["monitor-snapshot"],
"dependencies": ["browser", "local-service", "repair-order-source", "history-log", "dispose-log", "status-classification"],
"actions": ["monitor-queue", "classify-status", "compare-history", "write-local-log", "trigger-alert"],
"tags": ["95598", "repair", "dispatch", "monitoring", "config"],
"skill": {
"package": "95598-repair-city-dispatch",
"tool": "collect_repair_orders",
"artifact_type": "monitor-snapshot"
},
"source": {
"scenario_path": "../../../大四区报告监测项/95598抢修-市指",
"skill_path": "../../skills/95598-repair-city-dispatch"
},
"status_semantics": {
"supports_partial": true,
"distinguish_blocked_from_empty": true
}
}

View File

@@ -0,0 +1,29 @@
{
"draft": true,
"id": "95598-weekly-monitor-report",
"name": "95598、12398及配网设备监控情况周统计",
"inferred_from": [
"../../skills/95598-weekly-monitor-report/SKILL.toml",
"../../skills/95598-weekly-monitor-report/scripts/collect_weekly_metrics.js",
"../../skills/95598-weekly-monitor-report/references/collection-flow.md",
"../../skills/95598-weekly-monitor-report/references/data-quality.md",
"../../../大四区报告监测项/95598、12398及配网设备监控情况周统计/index.html"
],
"confidence": {
"category": "high",
"inputs": "medium",
"outputs": "high",
"dependencies": "medium",
"actions": "medium"
},
"guesses": {
"category": "report",
"inputs": ["currentPeriod", "cumulativePeriod"],
"dependencies": ["browser", "multi-source", "period-alignment", "local-report-service"],
"actions": ["query", "collect-report", "aggregate-sections", "align-periods"]
},
"todo": [
"确认 scene.json 是否应把 period 拆成 currentPeriod 与 cumulativePeriod 两个正式输入。",
"确认 period-alignment 是否作为独立 dependency 保留,还是仅作为 action 语义。"
]
}

View File

@@ -0,0 +1,25 @@
{
"id": "95598-weekly-monitor-report",
"name": "95598、12398及配网设备监控情况周统计",
"category": "report",
"entry": "index.html",
"summary": "采集 95598、12398 及配网设备多来源周统计指标并生成分区结构化周报产物。",
"inputs": ["period"],
"outputs": ["report-artifact"],
"dependencies": ["browser", "multi-source", "period-alignment", "local-report-service"],
"actions": ["query", "collect-report", "aggregate-sections", "align-periods"],
"tags": ["95598", "12398", "weekly-report", "browser", "report"],
"skill": {
"package": "95598-weekly-monitor-report",
"tool": "collect_weekly_metrics",
"artifact_type": "report-artifact"
},
"source": {
"scenario_path": "../../../大四区报告监测项/95598、12398及配网设备监控情况周统计",
"skill_path": "../../skills/95598-weekly-monitor-report"
},
"status_semantics": {
"supports_partial": true,
"distinguish_blocked_from_empty": true
}
}

View File

@@ -0,0 +1,29 @@
{
"draft": true,
"id": "fault-details-report",
"name": "故障明细",
"inferred_from": [
"../../skills/fault-details-report/SKILL.toml",
"../../skills/fault-details-report/scripts/collect_fault_details.js",
"../../skills/fault-details-report/references/collection-flow.md",
"../../skills/fault-details-report/references/data-quality.md",
"../../../大四区报告监测项/故障明细/index.html"
],
"confidence": {
"category": "high",
"inputs": "high",
"outputs": "high",
"dependencies": "medium",
"actions": "medium"
},
"guesses": {
"category": "report",
"inputs": ["period"],
"dependencies": ["browser", "report-history", "local-report-service"],
"actions": ["query", "collect-report", "build-summary-section"]
},
"todo": [
"确认 period 在 generator 层是否保留为单字段,还是拆分为 startTime/endTime。",
"确认 local-report-service 是否需要统一归一到 local-service 词表。"
]
}

View File

@@ -0,0 +1,25 @@
{
"id": "fault-details-report",
"name": "故障明细",
"category": "report",
"entry": "index.html",
"summary": "查询故障明细行并生成包含明细与汇总分区的结构化报表产物。",
"inputs": ["period"],
"outputs": ["report-artifact"],
"dependencies": ["browser", "report-history", "local-report-service"],
"actions": ["query", "collect-report", "build-summary-section"],
"tags": ["fault", "report", "xlsx", "details", "browser"],
"skill": {
"package": "fault-details-report",
"tool": "collect_fault_details",
"artifact_type": "report-artifact"
},
"source": {
"scenario_path": "../../../大四区报告监测项/故障明细",
"skill_path": "../../skills/fault-details-report"
},
"status_semantics": {
"supports_partial": true,
"distinguish_blocked_from_empty": true
}
}

View File

@@ -0,0 +1,31 @@
{
"draft": true,
"id": "jiayuguan-meter-outage",
"name": "户表失电-嘉峪关",
"inferred_from": [
"../../skills/jiayuguan-meter-outage/SKILL.toml",
"../../skills/jiayuguan-meter-outage/scripts/collect_outage_events.js",
"../../skills/jiayuguan-meter-outage/references/collection-flow.md",
"../../skills/jiayuguan-meter-outage/references/data-quality.md",
"../../../大四区报告监测项/户表失电-嘉峪关/index.html",
"../../../大四区报告监测项/户表失电-嘉峪关_业务监测配置.txt",
"../../../大四区报告监测项/户表失电-嘉峪关_自动处理配置.txt"
],
"confidence": {
"category": "high",
"inputs": "high",
"outputs": "high",
"dependencies": "high",
"actions": "medium"
},
"guesses": {
"category": "monitor",
"inputs": ["time"],
"dependencies": ["browser", "local-service", "outage-source", "service-order-source", "history-log"],
"actions": ["collect-outage-events", "enrich-service-orders", "compare-history", "write-local-log", "trigger-alert"]
},
"todo": [
"确认 marketing token context 是否需要作为正式 dependency 单独暴露。",
"确认 trigger-alert 是否应与 auto-processing 拆成两个独立动作。"
]
}

View File

@@ -0,0 +1,25 @@
{
"id": "jiayuguan-meter-outage",
"name": "户表失电-嘉峪关",
"category": "monitor",
"entry": "index.html",
"summary": "采集嘉峪关户表失电事件与关联工单状态,结合历史比较与本地日志侧动作返回结构化监测快照。",
"inputs": ["time"],
"outputs": ["monitor-snapshot"],
"dependencies": ["browser", "local-service", "outage-source", "service-order-source", "history-log", "dispose-log", "marketing-token-context"],
"actions": ["collect-outage-events", "enrich-service-orders", "compare-history", "write-local-log", "trigger-alert"],
"tags": ["jiayuguan", "meter-outage", "monitoring", "alert", "dispatch"],
"skill": {
"package": "jiayuguan-meter-outage",
"tool": "collect_outage_events",
"artifact_type": "monitor-snapshot"
},
"source": {
"scenario_path": "../../../大四区报告监测项/户表失电-嘉峪关",
"skill_path": "../../skills/jiayuguan-meter-outage"
},
"status_semantics": {
"supports_partial": true,
"distinguish_blocked_from_empty": true
}
}

View File

@@ -0,0 +1,29 @@
{
"draft": true,
"id": "jinchang-business-environment-weekly-report",
"name": "国网金昌供电公司营商环境周例会报告",
"inferred_from": [
"../../skills/jinchang-business-environment-weekly-report/SKILL.toml",
"../../skills/jinchang-business-environment-weekly-report/scripts/collect_business_environment_metrics.js",
"../../skills/jinchang-business-environment-weekly-report/references/collection-flow.md",
"../../skills/jinchang-business-environment-weekly-report/references/data-quality.md",
"../../../大四区报告监测项/国网金昌供电公司营商环境周例会报告/index.html"
],
"confidence": {
"category": "high",
"inputs": "high",
"outputs": "high",
"dependencies": "medium",
"actions": "medium"
},
"guesses": {
"category": "report",
"inputs": ["period"],
"dependencies": ["browser", "multi-source", "local-report-service"],
"actions": ["query", "collect-report", "aggregate-sections"]
},
"todo": [
"确认 multi-source 是否要进一步细分为 source-session-cache 或 token-cache。",
"确认 dispatch-summary 等 section 名是否需要进入 scene 级 metadata。"
]
}

View File

@@ -0,0 +1,25 @@
{
"id": "jinchang-business-environment-weekly-report",
"name": "国网金昌供电公司营商环境周例会报告",
"category": "report",
"entry": "index.html",
"summary": "采集金昌营商环境周例会多来源指标并组装为分区结构化周报产物。",
"inputs": ["period"],
"outputs": ["report-artifact"],
"dependencies": ["browser", "multi-source", "local-report-service"],
"actions": ["query", "collect-report", "aggregate-sections"],
"tags": ["jinchang", "business-environment", "weekly-report", "browser", "report"],
"skill": {
"package": "jinchang-business-environment-weekly-report",
"tool": "collect_business_environment_metrics",
"artifact_type": "report-artifact"
},
"source": {
"scenario_path": "../../../大四区报告监测项/国网金昌供电公司营商环境周例会报告",
"skill_path": "../../skills/jinchang-business-environment-weekly-report"
},
"status_semantics": {
"supports_partial": true,
"distinguish_blocked_from_empty": true
}
}

View File

@@ -0,0 +1,77 @@
const fs = require('node:fs');
const path = require('node:path');
function readJson(filePath) {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}
function loadSceneRegistry(registryPath) {
const resolvedRegistryPath = path.resolve(registryPath);
const registryDir = path.dirname(resolvedRegistryPath);
const registry = readJson(resolvedRegistryPath);
const items = registry.scenes.map((item) => {
const scenePath = path.resolve(registryDir, item.scene_file);
const draftPath = path.resolve(registryDir, item.draft_file);
const sceneDir = path.dirname(scenePath);
const scene = readJson(scenePath);
const draft = readJson(draftPath);
return {
id: scene.id,
name: scene.name,
category: scene.category,
scenePath,
draftPath,
skillPath: path.resolve(sceneDir, scene.source.skill_path),
scenarioPath: path.resolve(sceneDir, scene.source.scenario_path),
inferredFrom: draft.inferred_from.map((entry) => path.resolve(sceneDir, entry)),
tool: scene.skill.tool,
artifactType: scene.skill.artifact_type,
inputs: scene.inputs,
outputs: scene.outputs,
dependencies: scene.dependencies,
actions: scene.actions,
todoCount: Array.isArray(draft.todo) ? draft.todo.length : 0
};
});
return {
registryPath: resolvedRegistryPath,
registryDir,
registry,
items
};
}
function buildCliSummary(result) {
return {
registryPath: result.registryPath,
sceneCount: result.registry.summary.scene_count,
items: result.items.map((item) => ({
id: item.id,
category: item.category,
tool: item.tool,
artifactType: item.artifactType,
scenePath: item.scenePath,
draftPath: item.draftPath,
skillPath: item.skillPath,
scenarioPath: item.scenarioPath,
todoCount: item.todoCount
}))
};
}
if (require.main === module) {
const registryPath = process.argv[2]
? path.resolve(process.argv[2])
: path.resolve(__dirname, 'scene-registry.draft.json');
const result = loadSceneRegistry(registryPath);
process.stdout.write(`${JSON.stringify(buildCliSummary(result), null, 2)}\n`);
}
module.exports = {
loadSceneRegistry,
buildCliSummary
};

View File

@@ -0,0 +1,23 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const path = require('node:path');
test('loadSceneRegistry resolves registry, scene, draft, and source paths', () => {
const { loadSceneRegistry } = require('./load-scene-registry.js');
const registryPath = path.resolve(__dirname, 'scene-registry.draft.json');
const result = loadSceneRegistry(registryPath);
assert.equal(result.registry.kind, 'scene-registry');
assert.equal(result.registry.summary.scene_count, 5);
assert.equal(result.items.length, 5);
const fault = result.items.find((item) => item.id === 'fault-details-report');
assert.ok(fault);
assert.ok(fault.scenePath.endsWith(path.join('fault-details-report', 'scene.json')));
assert.ok(fault.draftPath.endsWith(path.join('fault-details-report', 'scene.draft.json')));
assert.ok(fault.skillPath.endsWith(path.join('skill_staging', 'skills', 'fault-details-report')));
assert.ok(fault.scenarioPath.endsWith(path.join('大四区报告监测项', '故障明细')));
assert.equal(fault.tool, 'collect_fault_details');
assert.equal(fault.artifactType, 'report-artifact');
});

View File

@@ -0,0 +1,156 @@
# scene-registry.draft.json 使用约定
## 1. 文件角色
`scene-registry.draft.json``skill_staging/scenes/` 下的聚合索引草稿。
它的作用是:
- 让 generator / registry 先读取一个总入口
- 再按需定位每个 scene 的 `scene.json``scene.draft.json`
- 避免调用方先遍历所有场景目录再自己拼装索引
当前它仍是 `draft`,含义是:
- 可以作为生成器输入基线
- 但仍允许人工复核 `pending_review` 中的待确认项
## 2. 相对路径解析基准
### 2.1 registry 内部路径的基准
`scene-registry.draft.json` 中出现的相对路径,**一律相对于该 registry 文件所在目录解析**。
也就是以这个目录为 base
```text
skill_staging/scenes/
```
例如:
- `root = "."` 表示当前目录 `skill_staging/scenes/`
- `scene_file = "fault-details-report/scene.json"`
- `draft_file = "fault-details-report/scene.draft.json"`
调用方应按下面的方式理解:
```text
resolve(registry_dir, scene_file)
resolve(registry_dir, draft_file)
```
而不是相对于进程当前工作目录解析。
### 2.2 scene 文件内部路径的基准
每个 `scene.json` / `scene.draft.json` 内部出现的相对路径,**一律相对于该 scene 文件自身所在目录解析**。
也就是以:
```text
skill_staging/scenes/<scene-id>/
```
为 base。
例如在 `fault-details-report/scene.json` 中:
- `../../skills/fault-details-report`
- `../../../大四区报告监测项/故障明细`
调用方应按下面的方式理解:
```text
resolve(scene_dir, source.skill_path)
resolve(scene_dir, source.scenario_path)
```
对于 `scene.draft.json``inferred_from` 也是同一规则:
```text
resolve(scene_dir, inferred_from[i])
```
## 3. 推荐读取顺序
推荐按下面顺序消费:
1. 读取 `skill_staging/scenes/scene-registry.draft.json`
2.`scenes[]` 列表
3. 对每个条目优先读取 `scene_file`
4. 若需要查看推断依据或待确认项,再读取 `draft_file`
5.`pending_review` 非空,不要默认认为所有 scene 都已完全定稿
## 4. 调用方约束
调用方应遵守以下约定:
- 不要把这些相对路径当作绝对路径缓存
- 不要依赖当前工作目录来解析路径
- 路径解析应使用“配置文件所在目录”作为 base
- 若整体目录被搬移,只要相对目录结构不变,这些引用就应继续有效
## 5. 当前约定的边界
这份 registry 当前只约定:
- scene 聚合入口
- scene / draft 文件定位方式
- 相对路径解析基准
这份 registry **还没有**约定:
- 热重载机制
- 自动注册完成语义
- dependency/action 词表最终标准化结果
- generator 的最终输入 schema
## 6. Consumer Example
下面给一个最小读取示例,说明调用方应如何解析这些相对路径。
### 6.1 读取 registry 并定位 scene 文件
```js
import fs from "node:fs";
import path from "node:path";
const registryPath = path.resolve(
"skill_staging/scenes/scene-registry.draft.json"
);
const registryDir = path.dirname(registryPath);
const registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
for (const item of registry.scenes) {
const scenePath = path.resolve(registryDir, item.scene_file);
const draftPath = path.resolve(registryDir, item.draft_file);
const scene = JSON.parse(fs.readFileSync(scenePath, "utf8"));
console.log({
id: scene.id,
scenePath,
draftPath,
tool: scene.skill.tool,
artifactType: scene.skill.artifact_type
});
}
```
### 6.2 在 scene 内继续解析 source / inferred_from
```js
const sceneDir = path.dirname(scenePath);
const skillPath = path.resolve(sceneDir, scene.source.skill_path);
const scenarioPath = path.resolve(sceneDir, scene.source.scenario_path);
const draft = JSON.parse(fs.readFileSync(draftPath, "utf8"));
const inferredFiles = draft.inferred_from.map((p) => path.resolve(sceneDir, p));
```
### 6.3 实现要点
调用方只要遵守两条就不会解析错:
1. 先用 registry 文件所在目录解析 `scene_file` / `draft_file`
2. 再用 scene 文件所在目录解析 `source.*` / `inferred_from[]`
不要跳过这两层 base直接拿相对路径拼当前工作目录。

View File

@@ -0,0 +1,165 @@
{
"draft": true,
"kind": "scene-registry",
"version": "0.1.0",
"generated_on": "2026-04-02",
"root": ".",
"canonical_source": "scene.json",
"fallback_source": "scene.draft.json",
"generator_hints": {
"scene_id_equals_skill_package": true,
"prefer_scene_json_over_skill_package_scan": true,
"manual_review_required": true,
"reload_ready": false
},
"summary": {
"scene_count": 5,
"categories": {
"report": 3,
"monitor": 2
},
"artifact_types": {
"report-artifact": 3,
"monitor-snapshot": 2
}
},
"scenes": [
{
"id": "fault-details-report",
"name": "故障明细",
"category": "report",
"scene_file": "fault-details-report/scene.json",
"draft_file": "fault-details-report/scene.draft.json",
"skill_package": "fault-details-report",
"tool": "collect_fault_details",
"artifact_type": "report-artifact",
"entry": "index.html",
"inputs": ["period"],
"outputs": ["report-artifact"],
"dependencies": ["browser", "report-history", "local-report-service"],
"actions": ["query", "collect-report", "build-summary-section"],
"status_semantics": {
"supports_partial": true,
"distinguish_blocked_from_empty": true
},
"todo_count": 2
},
{
"id": "jinchang-business-environment-weekly-report",
"name": "国网金昌供电公司营商环境周例会报告",
"category": "report",
"scene_file": "jinchang-business-environment-weekly-report/scene.json",
"draft_file": "jinchang-business-environment-weekly-report/scene.draft.json",
"skill_package": "jinchang-business-environment-weekly-report",
"tool": "collect_business_environment_metrics",
"artifact_type": "report-artifact",
"entry": "index.html",
"inputs": ["period"],
"outputs": ["report-artifact"],
"dependencies": ["browser", "multi-source", "local-report-service"],
"actions": ["query", "collect-report", "aggregate-sections"],
"status_semantics": {
"supports_partial": true,
"distinguish_blocked_from_empty": true
},
"todo_count": 2
},
{
"id": "95598-weekly-monitor-report",
"name": "95598、12398及配网设备监控情况周统计",
"category": "report",
"scene_file": "95598-weekly-monitor-report/scene.json",
"draft_file": "95598-weekly-monitor-report/scene.draft.json",
"skill_package": "95598-weekly-monitor-report",
"tool": "collect_weekly_metrics",
"artifact_type": "report-artifact",
"entry": "index.html",
"inputs": ["period"],
"outputs": ["report-artifact"],
"dependencies": ["browser", "multi-source", "period-alignment", "local-report-service"],
"actions": ["query", "collect-report", "aggregate-sections", "align-periods"],
"status_semantics": {
"supports_partial": true,
"distinguish_blocked_from_empty": true
},
"todo_count": 2
},
{
"id": "95598-repair-city-dispatch",
"name": "95598抢修-市指",
"category": "monitor",
"scene_file": "95598-repair-city-dispatch/scene.json",
"draft_file": "95598-repair-city-dispatch/scene.draft.json",
"skill_package": "95598-repair-city-dispatch",
"tool": "collect_repair_orders",
"artifact_type": "monitor-snapshot",
"entry": "index.html",
"inputs": ["time"],
"outputs": ["monitor-snapshot"],
"dependencies": ["browser", "local-service", "repair-order-source", "history-log", "status-classification"],
"actions": ["monitor-queue", "classify-status", "compare-history", "write-local-log", "trigger-alert"],
"status_semantics": {
"supports_partial": true,
"distinguish_blocked_from_empty": true
},
"todo_count": 2
},
{
"id": "jiayuguan-meter-outage",
"name": "户表失电-嘉峪关",
"category": "monitor",
"scene_file": "jiayuguan-meter-outage/scene.json",
"draft_file": "jiayuguan-meter-outage/scene.draft.json",
"skill_package": "jiayuguan-meter-outage",
"tool": "collect_outage_events",
"artifact_type": "monitor-snapshot",
"entry": "index.html",
"inputs": ["time"],
"outputs": ["monitor-snapshot"],
"dependencies": ["browser", "local-service", "outage-source", "service-order-source", "history-log"],
"actions": ["collect-outage-events", "enrich-service-orders", "compare-history", "write-local-log", "trigger-alert"],
"status_semantics": {
"supports_partial": true,
"distinguish_blocked_from_empty": true
},
"todo_count": 2
}
],
"pending_review": [
{
"id": "fault-details-report",
"todo": [
"确认 period 在 generator 层是否保留为单字段,还是拆分为 startTime/endTime。",
"确认 local-report-service 是否需要统一归一到 local-service 词表。"
]
},
{
"id": "jinchang-business-environment-weekly-report",
"todo": [
"确认 multi-source 是否要进一步细分为 source-session-cache 或 token-cache。",
"确认 dispatch-summary 等 section 名是否需要进入 scene 级 metadata。"
]
},
{
"id": "95598-weekly-monitor-report",
"todo": [
"确认 scene.json 是否应把 period 拆成 currentPeriod 与 cumulativePeriod 两个正式输入。",
"确认 period-alignment 是否作为独立 dependency 保留,还是仅作为 action 语义。"
]
},
{
"id": "95598-repair-city-dispatch",
"todo": [
"确认 trigger-alert 是否应拆分成 audio-alert、message-alert、callout 三类动作。",
"确认 configServices 是否需要在正式 dependencies 中单独暴露。"
]
},
{
"id": "jiayuguan-meter-outage",
"todo": [
"确认 marketing token context 是否需要作为正式 dependency 单独暴露。",
"确认 trigger-alert 是否应与 auto-processing 拆成两个独立动作。"
]
}
]
}

View File

@@ -0,0 +1,82 @@
---
name: 95598-repair-city-dispatch
description: Use when the user wants to maintain city-dispatch repair-team configuration or monitor 95598 repair work orders, pending queues, and downstream alert states.
version: 0.1.0
author: sgclaw
tags:
- 95598
- repair
- dispatch
- monitoring
- config
---
# 95598 Repair City Dispatch
## When to Use
- The user asks to maintain 95598 city-dispatch team or class-list configuration.
- The user asks to monitor 95598 repair work orders and classify pending, audit, and processed states.
- The task needs a monitor snapshot derived from the current repair-order queue and local comparison logs.
- The task needs to detect new pending orders before downstream audio, message, or call-out actions.
Do not use this skill for:
- unrelated report generation
- arbitrary browser probing without the packaged collector
- claiming auto-dispatch or alert success without a fresh queue snapshot
- hiding BrowserAction, localhost service, or alert failures behind empty results
## Workflow
1. Open or attach to the city-dispatch configuration page.
2. Read scheduled workflow rule scripts from desk source-of-truth (`D:/desk/智能体资料/大四区报告监测项/95598抢修-市指_业务检测配置.txt`, `D:/desk/智能体资料/大四区报告监测项/95598抢修-市指_自动处理配置.txt`) to understand queue, class-match, and auto-dispatch semantics.
3. Collect repair orders from the upstream queue for status codes `00`, `01`, `06`, and `08`.
4. Normalize the queue into `pending`, `audit`, and `processed` counters and derive `pending_ids` / `new_pending_ids`.
5. Return the monitor snapshot before downstream audio, message, call-out, or dispose-log side effects.
6. If local persistence, comparison, or alert-side effects fail after queue collection, keep the snapshot and mark the result as `partial`.
## Runtime Contract
- Treat configuration maintenance and queue monitoring as related but distinct operations.
- Prefer the packaged collector before generic browser probing or manual queue inspection.
- The scene page `assets/scene-snapshot/index.html` is configuration-only; treat it as class-list/config context, not workflow execution proof.
- Treat `getMonitorLog` / `getDisposeLog` as comparison context, not the upstream source of truth.
- BrowserAction execution, platform session context, and localhost `MonitorServices` dependencies are part of runtime truth and must be surfaced explicitly.
- Auto-dispatch, SMS, and voice-remind outcomes are downstream effects; they do not redefine whether queue collection succeeded.
## Partial-Failure Rule
- If queue collection succeeds but local monitor-log read/write, audio-log write, message-log write, or dispose-log write fails, report `partial`.
- Local monitor/dispose comparison context unavailable for pending-id diffing (`monitor_log_unavailable` / `dispose_log_unavailable`) while queue data exists, report `partial`.
- If auto-dispatch or reminder side effects fail after snapshot construction, keep the snapshot and report `partial`.
- Never report `no orders` when login, interception, request failure, or parse failure blocked the collection request.
## Export Artifact
```json
{
"type": "monitor-snapshot",
"scene": "95598-repair-city-dispatch",
"time": "",
"pending": 0,
"audit": 0,
"processed": 0,
"pending_ids": [],
"new_pending_ids": [],
"status": "success",
"partial_reasons": []
}
```
## Output
Return:
- operation type
- scope or queue window
- pending, audit, processed counts
- pending ids and new pending ids
- complete or partial status
- missing class-match, log, audio, message, or dispose areas
- Export Artifact

View File

@@ -0,0 +1,16 @@
[skill]
name = "95598-repair-city-dispatch"
description = "Use when the user wants to maintain city-dispatch repair-team configuration or monitor 95598 repair work orders through desk rule-script workflow semantics with normalized monitor snapshots."
version = "0.1.0"
author = "sgclaw"
tags = ["95598", "repair", "dispatch", "monitoring", "config"]
prompts = [
"For 95598 repair monitoring, call 95598-repair-city-dispatch.collect_repair_orders first and treat desk rule scripts as workflow source-of-truth while `assets/scene-snapshot/index.html` remains config-only.",
]
[[tools]]
name = "collect_repair_orders"
description = "Collect repair-order queue states and prepare the monitor snapshot shell from the browser-visible business page."
kind = "browser_script"
command = "scripts/collect_repair_orders.js"

View File

@@ -0,0 +1,147 @@
let callbackName = "callBack_Repair_Taskli";
let list = []; //待处理数据列表
let shlist = []; //待审核数据列表
let ycjList = []; //已处理数据列表
let idList = [];
window[callbackName] = function (targeturl, actionurl, responseTxt) {
try {
const res = JSON.parse(
responseTxt
.replace(/%7B/g, "{")
.replace(/%27/g, "'")
.replace(/%7D/g, "}")
.replace(/'/g, '"')
.replace("}/", "}")
);
console.log(res);
if (res.msg == "success") {
if (res.page.list.length > 0) {
res.page.list.forEach((item) => {
if (item.status == "00" && item.status == "01") {
list.push(item);
list.forEach((x) => {
idList.push(x.id);
});
}
// 06为待审核数据
if (item.status == "06") {
shlist.push(item);
}
// 08为已处理数据
if (item.status == "08") {
ycjList.push(item);
}
});
}
}
} catch (e) {
let audioText = "";
audioFC(audioText);
}
let obj = {
time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), //监测时间
type: "95598抢修-市指", //监测工单类型
pending: list.length, //待处理数字
pendingList: JSON.stringify(idList), //待处理列表
audit: shlist.length, //待审核
processed: ycjList.length, //已处理
};
mac
.localHostAxjos({
url: "http://localhost:13313/MonitorServices/getMonitorLog",
method: "POST",
data: JSON.stringify({ type: "95598抢修-市指" }), //必须是字符串格式
})
.then((res2) => {
if (res2.status == 200) {
let index = 0;
if (
res2.data.length < 1 &&
list.length > 0 &&
_this.queueObj.voiceRemind == "1"
) {
let audioText = "您有95598抢修工单请及时处理";
audioFC(audioText);
}
if (res2.data.length > 0) {
let logList = JSON.parse(res2.data[0].pendingList);
if (!Array.isArray(logList)) {
logList = JSON.parse(logList);
}
idList.forEach((x) => {
index = logList.indexOf(x);
if (index == -1) {
return;
}
});
}
if (index == -1 && _this.queueObj.voiceRemind == "1") {
let audioText = "您有95598抢修工单请及时处理";
audioFC(audioText);
}
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setMonitorData",
method: "POST",
data: JSON.stringify(obj),
});
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setMonitorLog",
method: "POST",
data: JSON.stringify(obj),
});
}
});
//执行 下一队列方法
_this.processQueue();
};
let time = `${mac.moment().format("YYYY-MM-DD")}+00:00:00,${mac
.moment()
.format("YYYY-MM-DD")}+23:59:59`;
const requestParam = `page=1&type=0101&statusName=00,01,06,08&orgNo=${_this.orgID}&slsj=${time}&limit=100`;
BrowserAction("sgBrowerserJsAjax2",
"window." + callbackName,
"http://21.77.244.194:18890/mainSystem/#/login",
`http://21.77.244.194:18890/qxgl/repairOrder/list?` + requestParam,
"POST",
`Content-Type:application/json;userId:${_this.userID};Access-Control-Allow-Credentials:true;Access-Control-Allow-Methods: GET, POST, OPTIONS,PUT,DELETE,OPTION;Access-Control-Allow-Origin:*`,
""
);
function audioFC(text) {
mac
.audioPlay(text)
.then((response) => {
if (response.status == 200) {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({
type: text,
time: mac.moment().format("YYYY-MM-DD HH:mm:ss"),
status: "成功",
}),
});
} else {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({
type: text,
time: mac.moment().format("YYYY-MM-DD HH:mm:ss"),
status: "失败",
}),
});
}
})
.catch((err) => {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({
type: text,
time: mac.moment().format("YYYY-MM-DD HH:mm:ss"),
status: "异常",
}),
});
});
}

View File

@@ -0,0 +1,350 @@
var QXGDSZ = []; //95598抢修-市指工单去重
var QXGDSZList = [];
var qxszClassList = [];
var eventId = null; //派单日志参数
var eqPsrName = null; //派单日志参数
var phone = "";
var tel = [];
let objData = {
time: mac.moment().format("YYYY-MM-DD HH:mm:ss"),
type: "95598抢修-市指",
pending: 0,
pendingList: "进入自动派单",
audit: 0,
processed: 0
};
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setMonitorLog",
method: "POST",
data: JSON.stringify(objData)
});
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/getDisposeLog",
method: "POST",
data: JSON.stringify({ type: "95598抢修-市指" }), //必须是字符串格式
})
.then((res) => {
if (res.status == 200) {
// 抢修工单
let arr = res.data.reverse();
obj.pendingList
.filter((x) => arr.findIndex((y) => y.orderID == x.id) == -1)
.forEach((item) => {
QXGDSZList.push(item);
});
if (QXGDSZList.length > 0) {
objData.pendingList = '未派过单-进入自动派单'
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setMonitorLog",
method: "POST",
data: JSON.stringify(objData)
});
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/getClassList",
method: "POST",
data: JSON.stringify({ type: "95598抢修" }),
})
.then((res) => {
if (res.status == 200) {
res.data = res.data.filter(item => item.type != '95598抢修')
if (res.data.length == !0) {
res.data.forEach((item) => {
let arr = [];
if (item.scope != "") {
item.scope = item.scope.replace(/\s*/g, "");
arr.push(item.scope.split("、"));
item.scope = [].concat.apply([], arr);
} else {
item.scope = [];
}
});
}
qxszClassList = res.data;
let callbackName = "callBacks_95598qiangxiusz";
window[callbackName] = function (
targeturl,
actionurl,
responseTxt
) {
try {
const resData = JSON.parse(
decodeURI(responseTxt).replace(/'/g, '"').replace("}/", "}")
);
if (resData.code == 0) {
setTimeout(() => {
mac.audioPlay("95598抢修-市指工单自动派单成功").then(response => {
if (response.status == 200) {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: '95598抢修-市指自动派单成功', time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "成功" })
})
} else {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: '95598抢修-市指自动派单成功', time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "失败" })
})
}
}).catch(err => {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: '95598抢修-市指自动派单成功', time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "异常" })
})
})
}, 6000);
let type = "95598抢修-市指";
let orderID = eventId;
let time = mac.moment().format("YYYY-MM-DD HH:mm:ss");
let name = eqPsrName;
let state = "成功";
// i国网
const request = {
phoneList: [].concat.apply([], tel),
content: "【业数融合一平台】您有95598抢修-市指工单,请及时处理!", //短信内容
};
if (request.phoneList.length > 0) {
mac.sendMessages(request).then(res4 => {
if (res4.status == 200) {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setSendMessageLog",
method: "POST",
data: JSON.stringify({ type: '95598抢修-市指自动派单', time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "成功" })
})
} else {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setSendMessageLog",
method: "POST",
data: JSON.stringify({ type: '95598抢修-市指自动派单', time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "失败" })
})
}
}).catch(err => {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setSendMessageLog",
method: "POST",
data: JSON.stringify({ type: '95598抢修-市指自动派单', time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "异常" })
})
})
}
// 呼叫接口
setTimeout(() => {
const params = {
taskName: "95598抢修-市指",
phone: [].concat.apply([], tel),
content: "您有95598抢修-市指工单,请尽快接单处理",
name: "95598抢修-市指",
};
if (params.phone.length > 0) {
mac.callOutLogin(params);
}
}, 1000);
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setDisposeLog",
method: "POST",
data: JSON.stringify({
type,
orderID,
name,
time,
state,
}), //必须是字符串格式
}).then(re => {
if (re.status == 200) {
mac.exeTQueue();
}
})
} else {
setTimeout(() => {
mac.audioPlay("95598抢修-市指工单自动派单失败,请及时处理!").then(response => {
if (response.status == 200) {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: '95598抢修-市指自动派单失败', time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "成功" })
})
} else {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: '95598抢修-市指自动派单失败', time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "失败" })
})
}
}).catch(err => {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: '95598抢修-市指自动派单失败', time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "异常" })
})
})
}, 6000);
let type = "95598抢修-市指";
let orderID = eventId;
let time = mac.moment().format("YYYY-MM-DD HH:mm:ss");
let name = eqPsrName;
let state = "失败";
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setDisposeLog",
method: "POST",
data: JSON.stringify({
type,
orderID,
name,
time,
state,
}), //必须是字符串格式
}).then(re => {
if (re.status == 200) {
mac.exeTQueue();
}
})
}
} catch (x) {
setTimeout(() => {
mac.audioPlay("95598抢修-市指工单自动派单异常,请及时处理!").then(response => {
if (response.status == 200) {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: '95598抢修-市指自动派单异常', time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "成功" })
})
} else {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: '95598抢修-市指自动派单异常', time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "失败" })
})
}
}).catch(err => {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: '95598抢修-市指自动派单异常', time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "异常" })
})
})
}, 6000);
let type = "95598抢修-市指";
let orderID = eventId;
let time = mac.moment().format("YYYY-MM-DD HH:mm:ss");
let name = eqPsrName;
let state = "异常";
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setDisposeLog",
method: "POST",
data: JSON.stringify({ type, orderID, name, time, state }), //必须是字符串格式
}).then(re => {
if (re.status == 200) {
mac.exeTQueue();
}
})
}
};
if (QXGDSZList.length > 0) {
let ppstatus = false;
eventId = QXGDSZList[0].id;
eqPsrName = QXGDSZList[0].gzdd;
// 区县级派单参数
var requestParam = {
processBusinessKey: QXGDSZList[0].id,
starter: "",
variables: {
cldw: QXGDSZList[0].cityCode, //处理单位
cldwmc: QXGDSZList[0].cityName, //处理单位名称
qxbz: "", //班组id
qxdy: "", //抢修队员
qxfzr: "", //抢修负责人编号
qxfzrName: "", //抢修负责人名称
},
};
let a = []
qxszClassList.forEach((item) => {
item.scope.forEach((x) => {
if (QXGDSZList[0].gzdd.indexOf(x) !== -1) {
requestParam.variables.qxbz = item.orgId;
requestParam.starter = item.pCode;
requestParam.variables.qxfzr = item.pCode;
requestParam.variables.qxfzrName = item.pName;
if (item.wName !== null) {
// phone = item.wName.replace(/、/g, ",");
a.push(item.wName.split("、"));
} else {
// phone = "";
tel = [];
}
ppstatus = true;
}
});
});
tel = Array.from(new Set(a))
if (ppstatus) {
objData.pendingList = '进入自动派单-匹配成功'
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setMonitorLog",
method: "POST",
data: JSON.stringify(objData)
});
sgBrowerserJsAjax2(
"window." + callbackName,
// "http://21.77.244.194:18890/#/sczhEgis",
"http://21.77.244.194:18890/#/webview/1543953229739896",
`http://21.77.244.194:18890/qxgl/repairOrder/initProcess?lockBussinessId=${QXGDSZList[0].id}`,
"POST",
`Content-Type:application/json;userId:${_this.userID};Access-Control-Allow-Credentials:true;Access-Control-Allow-Methods: GET, POST, OPTIONS,PUT,DELETE,OPTION;Access-Control-Allow-Origin:*`,
JSON.stringify(requestParam)
);
} else {
objData.pendingList = '进入自动派单-未匹配'
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setMonitorLog",
method: "POST",
data: JSON.stringify(objData),
});
setTimeout(() => {
mac.audioPlay("95598抢修-市指工单自动派单未匹配到班组,请尽快手动处理!").then(response => {
if (response.status == 200) {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: '95598抢修-市指自动派单未匹配', time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "成功" })
})
} else {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: '95598抢修-市指自动派单未匹配', time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "失败" })
})
}
}).catch(err => {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: '95598抢修-市指自动派单未匹配', time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "异常" })
})
})
}, 6000);
let type = "95598抢修-市指";
let orderID = eventId;
let time = mac.moment().format("YYYY-MM-DD HH:mm:ss");
let name = eqPsrName;
let state = "未匹配";
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setDisposeLog",
method: "POST",
data: JSON.stringify({ type, orderID, name, time, state }), //必须是字符串格式
}).then(re => {
if (re.status == 200) {
mac.exeTQueue();
}
})
}
}
}
});
} else {
mac.exeTQueue();
}
}
});

View File

@@ -0,0 +1,355 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="shortcut icon" href="./images/95598/logo.gif" type="image/x-icon" />
<link rel="stylesheet" href="./css/elementui.css" />
<link rel="stylesheet" href="./css/szhfn.css" />
<title>接单班组信息</title>
<script src="http://25.215.213.182:8080/a_js/axios.js"></script>
<script src="./js/jquery.js"></script>
<script src="./js/vue.js"></script>
<script src="./js/elementui.js"></script>
<script src="./js/moment.js"></script>
<script src="./js/dpage.min.js"></script>
</head>
<body>
<div id="app">
<el-table :data="allclassList" border height="945px">
<el-table-column label="接单单位名称" align="center" property="cName"></el-table-column>
<el-table-column label="当前接单单位管辖的所有地址名称" align="center" property="scope" width="240"> </el-table-column>
<el-table-column label="接单人姓名" align="center" property="pName"></el-table-column>
<el-table-column label="接单人工号" align="center" property="pCode"></el-table-column>
<el-table-column label="接单人手机号" align="center" property="wName"></el-table-column>
<el-table-column property="state" label="操作" width="160" align="center">
<template slot-scope="scope">
<el-button size="mini" type="primary" @click="viewBtnFC(scope.row)">查看</el-button>
<el-button size="mini" type="warning" @click="editBtnFC(scope.row)">修改</el-button>
</template>
</el-table-column>
</el-table>
<!-- 修改 -->
<el-dialog :visible.sync="editStatus" title="修改" :append-to-body="true" :before-close="edithandleClose">
<el-form :model="editClassForm" ref="editClassForm" label-width="110px">
<el-form-item label="接单单位名称" prop="cName">
<el-input v-model="editClassForm.cName" placeholder="请输入接单单位名称" disabled></el-input>
</el-form-item>
<el-form-item :label="editlabelText" prop="scope">
<el-input v-model="editClassForm.scope" :placeholder="'请输入当前接单单位管辖的所有'+editlabelText +'如果有多个请用、隔开xx、xxx'"
type="textarea"></el-input>
</el-form-item>
<el-form-item label="接单人姓名" prop="pName">
<el-input v-model="editClassForm.pName" placeholder="请输入接单人姓名"></el-input>
</el-form-item>
<el-form-item label="接单人账号" prop="pCode">
<el-input v-model="editClassForm.pCode" placeholder="请输入接单人账号"></el-input>
</el-form-item>
<el-form-item label="接单人手机号" prop="wName">
<el-input v-model="editClassForm.wName" placeholder="请输入接单人手机号"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editClassFC()" size="small" style="background: #018c87; color: #fff">确定</el-button>
</span>
</el-dialog>
<!-- 查看 -->
<el-dialog :visible.sync="viewStatus" title="查看" :append-to-body="true" :before-close="viewhandleClose">
<el-form :model="editClassForm" ref="editClassForm" label-width="110px">
<el-form-item label="接单单位名称" prop="cName">
<el-input v-model="editClassForm.cName" placeholder="请输入接单单位名称" disabled></el-input>
</el-form-item>
<el-form-item :label="editlabelText" prop="scope">
<el-input v-model="editClassForm.scope" :placeholder="'请输入当前接单单位管辖的所有'+editlabelText +'如果有多个请用、隔开xx、xxx'"
disabled type="textarea"></el-input>
</el-form-item>
<el-form-item label="接单人姓名" prop="pName">
<el-input v-model="editClassForm.pName" placeholder="请输入接单人姓名" disabled></el-input>
</el-form-item>
<el-form-item label="接单人账号" prop="pCode">
<el-input v-model="editClassForm.pCode" placeholder="请输入接单人账号" disabled></el-input>
</el-form-item>
<el-form-item label="接单人手机号" prop="wName">
<el-input v-model="editClassForm.wName" placeholder="请输入接单人手机号" disabled></el-input>
</el-form-item>
</el-form>
</el-dialog>
</div>
</body>
<script>
var mac = new Vue({
el: "#app",
data() {
return {
// 修改接单班组
editClassForm: {
orgId: "",
cName: "",
scope: "",
pName: "",
pCode: "",
type: "",
wName: "",
},
ids: '',
classList: [],
allclassList: [],
disabled: true,
editStatus: false,
viewStatus: false,
labelText: "",
editlabelText: "",
placeholder: "",
formInfo: {},
};
},
mounted() {
sgBrowserExcuteJsCode(
"http://25.215.213.182:8080/#/monitorQueue",
`sgBrowserExcuteJsCode("${location.href}",\`mac.setStorage('\${localStorage.tGfUser}')\`);`
);
// this.getClassList("95598抢修")
},
methods: {
setStorage(obj) {
sessionStorage.setItem("tGfUser", obj);
const tGfUser = JSON.parse(sessionStorage.getItem("tGfUser"));
mac.formInfo = {
cityCode: tGfUser.orgNo, //机构码
cityName: tGfUser.orgName, //机构名称 比如 国网嘉峪关供电公司
userNameIV: tGfUser.name, //大四区登录的人名
userCodeIV: tGfUser.id, //大四区登录账号
};
mac.zdpdFc({ name: "95598抢修" });
},
// 自动派单配置图标点击弹出配置弹出框
zdpdFc(x) {
// 区县级表头名称
if (x.name == "95598抢修") {
this.labelText = "当前接单单位管辖的所有地址名称";
this.editlabelText = "地址名称";
this.placeholder = "请输入地址名称";
} else {
this.labelText = "当前接单单位管辖的所有配变名称";
this.editlabelText = "配变名称";
this.placeholder = "请输入配变名称";
}
this.editClassForm.type = x.name;
this.getUrl(x.name);
},
// 抓取浏览器地址
getUrl() {
window.getAllUrlCallBack = (urls) => {
var urlArr = urls.split(";");
console.log(urlArr);
let hasPage = false;
let hasPage1 = false;
let home = null;
urlArr.forEach((item) => {
if (item.indexOf("http://21.77.244.194:18890/") !== -1) {
hasPage = true;
home = item;
}
});
if (hasPage) {
mac.gethngdsList("95598抢修", home);
} else {
console.log("大四区未登录");
}
};
dpage.dPageGetUrl(true, "window.getAllUrlCallBack");
},
// 获取大IV中班组信息
gethngdsList(name, home) {
window.callBackMethodYX = function (
targeturl,
actionurl,
responseTxt
) {
try {
const resData = JSON.parse(
decodeURI(responseTxt).replace(/'/g, '"').replace("}/", "}")
);
console.log(resData);
if (resData.code == 0) {
mac.classList = [];
resData.orgs.forEach((item) => {
mac.classList.push({
orgId: item.orgId,
type: "",
cName: item.orgName,
scope: "",
pName: "",
pCode: "",
wName: "",
});
});
mac.getClassList(name);
mac.$message({
message: "接单班组信息获取成功!",
type: "success",
});
} else {
mac.$message({
message: "接单班组信息获取失败!",
type: "error",
});
}
} catch (x) {
mac.$message({
message: "接单班组信息获取异常!",
type: "error",
});
}
};
const param = {
callbackfun: "window.callBackMethodYX",
urlpage: home,
ajaxurl:
"http://21.77.244.194:18890/system/organization/queryOrgBylevel/" + mac.formInfo.cityCode + "/bz",
requestType: "POST",
requestheder: `Content-Type:application/json;userId:${mac.formInfo.userCodeIV};Access-Control-Allow-Credentials:true;Access-Control-Allow-Methods: GET, POST, OPTIONS,PUT,DELETE,OPTION;Access-Control-Allow-Origin:*`,
requestParam: "",
};
dpage.dPageAjax2(param);
},
generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8); // 保证符合 UUID v4 标准
return v.toString(16);
});
},
setDate() {
const params = {
sqlStr: `insert into qxClasslist (id,cName,orgId,pCode,pName,scope,type,wName) values ("${this.generateUUID()}","应急管控班","008df5db70319f73e0508eoac3a61127","","","","95598抢修-市指","")`
}
axios.post("http://localhost:13313/configServices/exeSql", JSON.stringify(params), {
headers: {
"Content-Type": "application/json"
}
}).then(res => {
console.log(res)
}
).catch(err => { }
)
},
// 查询接单班组
getClassList(name) {
$.ajax({
url: "http://localhost:13313/MonitorServices/getClassList",
type: "POST",
dataType: "json",
crossDomain: true,
data: JSON.stringify({ type: name }),
contentType: "application/json", //指定中容格式
success: (res) => {
//括号里的data是服务器返回的数据
if (res.status == 200) {
if (res.data.length > 0) {
res.data.forEach((item) => {
item.status = false; //按钮状态
});
res.data = res.data.filter(item => item.type != '95598抢修')
if (res.data.length == 0) {
mac.setDate()
setTimeout(() => {
this.getClassList(name);
}, 1000);
} else {
this.ids = res.data[0].id
mac.allclassList = res.data;
}
} else {
let arrList = [];
mac.classList.forEach((item) => {
if (!arrList.some((e) => e.orgId == item.orgId)) {
arrList.push(item);
}
});
arrList.forEach((x) => {
x.type = name;
mac.setClassList(x);
});
setTimeout(() => {
this.getClassList(name);
}, 1000);
}
}
},
});
},
// 存储接单班组
setClassList(val) {
$.ajax({
url: "http://localhost:13313/MonitorServices/setClassList",
type: "POST",
dataType: "json",
crossDomain: true,
data: JSON.stringify(val), //必须是字符串格式
contentType: "application/json",
success: (res) => { },
});
},
// 修改
editBtnFC(e) {
this.editStatus = true;
this.editClassForm = JSON.parse(JSON.stringify(e));
},
// 查看
viewBtnFC(e) {
this.viewStatus = true;
this.editClassForm = JSON.parse(JSON.stringify(e));
},
edithandleClose() {
this.editStatus = false;
},
viewhandleClose() {
this.viewStatus = false;
},
editClassForm: {
orgId: "",
cName: "",
scope: "",
pName: "",
pCode: "",
type: "",
wName: "",
},
updateClass() {
const { orgId, cName, scope, pName, pCode, type, wName } = this.editClassForm
const params = {
sqlStr: `update qxClasslist set orgId='${orgId}',cName='${cName}',scope='${scope}',pName='${pName}',pCode='${pCode}',type='${type}',wName='${wName}' where id = '${this.ids}'`
}
axios.post("http://localhost:13313/configServices/exeSql", JSON.stringify(params), {
headers: {
"Content-Type": "application/json"
}
}).then(res => {
if (res.status == 200) {
this.$message({
type: "success",
message: "修改成功!",
});
this.editStatus = false;
mac.getClassList("95598抢修");
}
}
).catch(err => { }
)
},
// 修改后保存按钮
editClassFC() {
this.updateClass()
},
},
});
</script>
</html>

View File

@@ -0,0 +1,49 @@
# Collection Flow
## Source
- Source page: `D:\desk\智能体资料\大四区报告监测项\95598抢修-市指\index.html`
- Rule assets (scheduled workflow source-of-truth):
- `D:/desk/智能体资料/大四区报告监测项/95598抢修-市指_业务检测配置.txt`
- `D:/desk/智能体资料/大四区报告监测项/95598抢修-市指_自动处理配置.txt`
## Scope
The page itself is configuration-oriented (`assets/scene-snapshot/index.html` is configuration-only). The monitoring package combines page-visible class-list context with scheduled queue-monitor and auto-dispatch semantics from the desk rule source-of-truth scripts.
## Inputs
- Current platform session and user context
- Current city/org context copied from the platform page
- Browser-visible class-list configuration
- Local monitor-log and dispose-log context from localhost services
- The current queue window, typically the current day
## First-pass Collection Steps
1. Open or attach to the city-dispatch configuration page.
2. Verify required platform-session and org/user context is available.
3. Read the scheduled workflow rule scripts from desk source-of-truth to understand status buckets, class matching, and downstream alert/dispatch behavior.
4. Trigger the deterministic repair-order collector through BrowserAction / browser-side request execution.
5. Query the upstream repair-order queue for status codes `00`, `01`, `06`, and `08`.
6. Normalize source results into `pending`, `audit`, `processed`, `pending_ids`, and `new_pending_ids` using packaged runtime collector logic aligned to the desk rule semantics.
7. Compare with local monitor/dispose logs only as downstream context.
8. Return a structured monitor snapshot before or alongside downstream logging, audio, message, or call-out side effects.
## Dependencies
- BrowserAction or equivalent browser-side request execution
- Platform-visible session, org, and user context
- Upstream repair-order endpoints under `http://21.77.244.194:18890/qxgl/repairOrder/*`
- Localhost services under `http://localhost:13313/MonitorServices/*`
- Localhost config services under `http://localhost:13313/configServices/*`
- Optional downstream reminder services such as audio play, SMS send, and call-out hooks
## State Semantics
- Success: repair orders are collected, status buckets are derived, and the snapshot is assembled with `success` status.
- Partial result: queue collection succeeds but local comparison, logging, class matching, or downstream reminder/dispatch side effects are incomplete or fail.
- Empty result: a valid queue request returns zero repair orders for the chosen window.
- Blocked/error: login failure, missing platform context, interception failure, request failure, permission failure, or parse failure.
- Blocked/error must not be reported as empty data.
- Local monitor logs and dispose logs are downstream context, not the primary upstream business data source.

View File

@@ -0,0 +1,49 @@
# Data Quality
## Complete Result
A complete result means the upstream repair-order queue for the chosen window is available, the status buckets are normalized, and the monitor snapshot contains aligned values for:
- `pending`
- `audit`
- `processed`
- `pending_ids`
- `new_pending_ids`
## Classification Rules
The packaged first-pass snapshot standardizes queue status codes as:
- `00`, `01` -> `pending`
- `06` -> `audit`
- `08` -> `processed`
If the upstream queue returns additional states, keep them out of the first-pass counters unless the package contract is updated.
## Partial Rules
- If queue collection succeeds but status classification is incomplete or ambiguous, set `status` to `partial`.
- If queue collection succeeds but `pending_ids` cannot be compared against prior monitor/dispose logs, set `status` to `partial`.
- If local monitor-log write, monitor-data write, dispose-log write, audio-log write, or message-log write fails after snapshot assembly, keep the snapshot and set `status` to `partial`.
- If class matching or auto-dispatch derivation is incomplete after queue collection, keep the snapshot and set `status` to `partial`.
- Do not claim full completeness when only queue counts are present but downstream comparison context is missing.
## Common Weak Areas
- Missing platform session or org/user context
- Queue response parse failure
- Pending-id comparison against prior logs not available
- Local `MonitorServices` or `configServices` failure
- Audio, SMS, call-out, or dispose-log side effects failing after snapshot assembly
- Class-list configuration not aligned with queue matching scope
## Empty vs Failure
- A valid queue request with zero repair orders is empty data.
- Login failure, blocked page, missing context, request failure, interception failure, permission failure, or parse failure must be surfaced as failure or partial, not empty.
## Dependency Warnings
- Localhost service availability is part of result quality.
- Voice reminder, SMS send, call-out, or auto-dispatch side effects must never replace the underlying queue snapshot result.
- Historical monitor logs are comparison context only and must not redefine upstream queue truth.

View File

@@ -0,0 +1,302 @@
const STATUS_GROUPS = {
pending: ["00", "01"],
audit: ["06"],
processed: ["08"]
};
const LOCAL_SERVICE_ENDPOINTS = [
"http://localhost:13313/MonitorServices/getMonitorLog",
"http://localhost:13313/MonitorServices/setMonitorData",
"http://localhost:13313/MonitorServices/setMonitorLog",
"http://localhost:13313/MonitorServices/getDisposeLog",
"http://localhost:13313/MonitorServices/setDisposeLog",
"http://localhost:13313/MonitorServices/setAudioPlayLog",
"http://localhost:13313/MonitorServices/setSendMessageLog"
];
const WORKFLOW_RULE_SOURCES = [
"D:/desk/智能体资料/大四区报告监测项/95598抢修-市指_业务检测配置.txt",
"D:/desk/智能体资料/大四区报告监测项/95598抢修-市指_自动处理配置.txt"
];
const CONFIG_BASE_PAGE = "assets/scene-snapshot/index.html";
const KNOWN_ISSUES = [
"pending classification bug in rule script: status == \"00\" && status == \"01\""
];
function asArray(value) {
return Array.isArray(value) ? value : [];
}
function normalizeId(value) {
if (value === null || value === undefined || value === "") {
return null;
}
return String(value);
}
function uniq(values) {
return Array.from(new Set(values));
}
function looksLikeJsonString(value) {
if (typeof value !== "string") {
return false;
}
const trimmed = value.trim();
return trimmed.startsWith("[") || trimmed.startsWith("{") || trimmed.startsWith("\"");
}
function parseLooseJson(value, depth = 0) {
if (depth > 3 || value === null || value === undefined) {
return value;
}
if (typeof value !== "string") {
return value;
}
const trimmed = value.trim();
if (!trimmed) {
return [];
}
try {
const parsed = JSON.parse(trimmed);
return parseLooseJson(parsed, depth + 1);
} catch {
return value;
}
}
function classifyRepairOrders(repairOrders = []) {
const pendingOrders = [];
const auditOrders = [];
const processedOrders = [];
const unknownStatuses = [];
asArray(repairOrders).forEach((item) => {
const status = normalizeId(item && item.status);
if (status && STATUS_GROUPS.pending.includes(status)) {
pendingOrders.push(item);
return;
}
if (status && STATUS_GROUPS.audit.includes(status)) {
auditOrders.push(item);
return;
}
if (status && STATUS_GROUPS.processed.includes(status)) {
processedOrders.push(item);
return;
}
if (status) {
unknownStatuses.push(status);
}
});
return {
pendingOrders,
auditOrders,
processedOrders,
unknownStatuses: uniq(unknownStatuses)
};
}
function extractMonitorPendingIds(monitorLogs = []) {
const ids = [];
let malformed = false;
asArray(monitorLogs).forEach((item) => {
const pendingIds = asArray(item && item.pending_ids)
.map(normalizeId)
.filter(Boolean);
if (pendingIds.length > 0) {
ids.push(...pendingIds);
return;
}
const rawPendingList = item && item.pendingList;
const pendingList = parseLooseJson(rawPendingList);
if (Array.isArray(pendingList)) {
pendingList
.map(normalizeId)
.filter(Boolean)
.forEach((id) => ids.push(id));
return;
}
if (looksLikeJsonString(rawPendingList)) {
malformed = true;
}
});
return {
ids: uniq(ids),
malformed
};
}
function extractDisposeOrderIds(disposeLogs = []) {
const ids = [];
let malformed = false;
asArray(disposeLogs).forEach((item) => {
const rawOrderPayload = item && item.orderID;
const orderPayload = parseLooseJson(rawOrderPayload);
if (Array.isArray(orderPayload)) {
orderPayload.forEach((entry) => {
const id = normalizeId(entry && (entry.id || entry.orderID || entry.eventId));
if (id) {
ids.push(id);
return;
}
if (entry && typeof entry === "object") {
malformed = true;
return;
}
const scalarEntry = normalizeId(entry);
if (scalarEntry) {
ids.push(scalarEntry);
}
});
return;
}
const rawLooksJson = looksLikeJsonString(rawOrderPayload);
if (rawLooksJson && orderPayload === rawOrderPayload) {
malformed = true;
return;
}
const scalarId = normalizeId(orderPayload);
if (scalarId && (!rawLooksJson || orderPayload !== rawOrderPayload)) {
ids.push(scalarId);
return;
}
if (rawLooksJson) {
malformed = true;
}
});
return {
ids: uniq(ids),
malformed
};
}
function determineStatus({ blocked, hasData, partialReasons }) {
if (blocked) {
return "blocked";
}
if (partialReasons.length > 0) {
return "partial";
}
if (!hasData) {
return "empty";
}
return "success";
}
function collectRepairOrders(input = {}) {
const blockedReason = normalizeId(input.blocked_reason);
const repairOrders = asArray(input.repair_orders);
const monitorLogs = asArray(input.monitor_logs || input.monitor_log);
const disposeLogs = asArray(input.dispose_logs || input.dispose_log);
const localWriteFailures = asArray(input.local_write_failures)
.map(normalizeId)
.filter(Boolean);
const partialReasons = [];
const { pendingOrders, auditOrders, processedOrders, unknownStatuses } = classifyRepairOrders(repairOrders);
if (unknownStatuses.length > 0) {
partialReasons.push(`unclassified_statuses:${unknownStatuses.join(",")}`);
}
const pendingIds = uniq(
pendingOrders
.map((item) => normalizeId(item && item.id))
.filter(Boolean)
);
let monitorPendingIds = [];
let disposedOrderIds = [];
if (pendingIds.length > 0) {
if (monitorLogs.length === 0) {
partialReasons.push("monitor_log_unavailable");
} else {
const extractedMonitor = extractMonitorPendingIds(monitorLogs);
monitorPendingIds = extractedMonitor.ids;
if (extractedMonitor.malformed) {
partialReasons.push("monitor_log_parse_failed");
}
}
if (disposeLogs.length === 0) {
partialReasons.push("dispose_log_unavailable");
} else {
const extractedDispose = extractDisposeOrderIds(disposeLogs);
disposedOrderIds = extractedDispose.ids;
if (extractedDispose.malformed) {
partialReasons.push("dispose_log_parse_failed");
}
}
}
localWriteFailures.forEach((failure) => {
partialReasons.push(`local_write_failed:${failure}`);
});
const monitorSet = new Set(monitorPendingIds);
const disposeSet = new Set(disposedOrderIds);
const newPendingIds = pendingIds.filter((id) => !monitorSet.has(id) && !disposeSet.has(id));
const hasData = repairOrders.length > 0;
const blocked = Boolean(input.blocked) || Boolean(blockedReason);
const snapshot = {
type: "monitor-snapshot",
scene: "95598-repair-city-dispatch",
time: input.time || "",
pending: pendingOrders.length,
audit: auditOrders.length,
processed: processedOrders.length,
pending_ids: pendingIds,
new_pending_ids: newPendingIds,
status: determineStatus({
blocked,
hasData,
partialReasons
}),
partial_reasons: uniq(
blockedReason ? [...partialReasons, blockedReason] : partialReasons
),
evidence: {
workflow_rule_sources: WORKFLOW_RULE_SOURCES,
config_base_page: CONFIG_BASE_PAGE,
config_base_role: "configuration-only",
packaged_collector_role: "runtime-snapshot-collector"
},
known_issues: KNOWN_ISSUES
};
return snapshot;
}
module.exports = {
STATUS_GROUPS,
LOCAL_SERVICE_ENDPOINTS,
WORKFLOW_RULE_SOURCES,
CONFIG_BASE_PAGE,
KNOWN_ISSUES,
classifyRepairOrders,
extractMonitorPendingIds,
extractDisposeOrderIds,
determineStatus,
collectRepairOrders
};

View File

@@ -0,0 +1,117 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const {
classifyRepairOrders,
extractMonitorPendingIds,
extractDisposeOrderIds,
determineStatus,
collectRepairOrders
} = require('./collect_repair_orders.js');
test('classifyRepairOrders buckets pending/audit/processed and keeps unknown statuses', () => {
const result = classifyRepairOrders([
{ id: 'A1', status: '00' },
{ id: 'A2', status: '01' },
{ id: 'A3', status: '06' },
{ id: 'A4', status: '08' },
{ id: 'A5', status: '77' }
]);
assert.equal(result.pendingOrders.length, 2);
assert.equal(result.auditOrders.length, 1);
assert.equal(result.processedOrders.length, 1);
assert.deepEqual(result.unknownStatuses, ['77']);
});
test('extractMonitorPendingIds reads both pending_ids and pendingList payloads', () => {
const logs = [
{ pending_ids: ['A1', 'A2'] },
{ pendingList: JSON.stringify(['A3']) },
{ pendingList: JSON.stringify(JSON.stringify(['A4'])) }
];
assert.deepEqual(extractMonitorPendingIds(logs), { ids: ['A1', 'A2', 'A3', 'A4'], malformed: false });
});
test('extractDisposeOrderIds supports list payloads and scalar payloads', () => {
const logs = [
{ orderID: JSON.stringify([{ id: 'D1' }, { eventId: 'D2' }]) },
{ orderID: JSON.stringify(JSON.stringify([{ id: 'D3' }])) },
{ orderID: 'D4' }
];
assert.deepEqual(extractDisposeOrderIds(logs), { ids: ['D1', 'D2', 'D3', 'D4'], malformed: false });
});
test('extractors mark malformed payloads when JSON-like strings cannot be parsed', () => {
const monitor = extractMonitorPendingIds([{ pendingList: '{bad-json' }]);
const dispose = extractDisposeOrderIds([{ orderID: '[{"id":"A1"}, {' }]);
assert.deepEqual(monitor, { ids: [], malformed: true });
assert.deepEqual(dispose, { ids: [], malformed: true });
});
test('determineStatus follows blocked > partial > empty > success precedence', () => {
assert.equal(determineStatus({ blocked: true, hasData: true, partialReasons: [] }), 'blocked');
assert.equal(determineStatus({ blocked: false, hasData: true, partialReasons: ['x'] }), 'partial');
assert.equal(determineStatus({ blocked: false, hasData: false, partialReasons: [] }), 'empty');
assert.equal(determineStatus({ blocked: false, hasData: true, partialReasons: [] }), 'success');
});
test('collectRepairOrders builds snapshot and computes new pending ids', () => {
const snapshot = collectRepairOrders({
time: '2026-04-08 10:00:00',
repair_orders: [
{ id: 'A1', status: '00' },
{ id: 'A2', status: '01' },
{ id: 'A3', status: '06' },
{ id: 'A4', status: '08' }
],
monitor_logs: [{ pendingList: JSON.stringify(['A1']) }],
dispose_logs: [{ orderID: JSON.stringify([{ id: 'A9' }]) }]
});
assert.equal(snapshot.scene, '95598-repair-city-dispatch');
assert.equal(snapshot.pending, 2);
assert.equal(snapshot.audit, 1);
assert.equal(snapshot.processed, 1);
assert.deepEqual(snapshot.pending_ids, ['A1', 'A2']);
assert.deepEqual(snapshot.new_pending_ids, ['A2']);
assert.equal(snapshot.status, 'success');
assert.deepEqual(snapshot.partial_reasons, []);
});
test('collectRepairOrders reports partial when comparison context is missing', () => {
const snapshot = collectRepairOrders({
repair_orders: [{ id: 'A1', status: '00' }],
monitor_logs: [],
dispose_logs: []
});
assert.equal(snapshot.status, 'partial');
assert.deepEqual(snapshot.partial_reasons.sort(), ['dispose_log_unavailable', 'monitor_log_unavailable']);
});
test('collectRepairOrders reports blocked when blocked reason is provided', () => {
const snapshot = collectRepairOrders({
blocked_reason: 'missing_browser_session',
repair_orders: [{ id: 'A1', status: '00' }],
monitor_logs: [{ pendingList: '[]' }],
dispose_logs: [{ orderID: '[]' }]
});
assert.equal(snapshot.status, 'blocked');
assert.ok(snapshot.partial_reasons.includes('missing_browser_session'));
});
test('collectRepairOrders reports empty for valid empty queue', () => {
const snapshot = collectRepairOrders({
repair_orders: []
});
assert.equal(snapshot.status, 'empty');
assert.equal(snapshot.pending, 0);
assert.deepEqual(snapshot.pending_ids, []);
assert.deepEqual(snapshot.new_pending_ids, []);
});

View File

@@ -0,0 +1,112 @@
---
name: 95598-weekly-monitor-report
description: Use when the user wants to collect 95598, 12398, and distribution-monitor weekly metrics and generate a weekly report artifact.
version: 0.1.0
author: sgclaw
tags:
- 95598
- 12398
- weekly-report
- browser
- report
---
# 95598 Weekly Monitor Report
## When to Use
- The user asks to collect weekly metrics for 95598, 12398, or distribution-monitor workflows.
- The user asks to generate a weekly report artifact from browser-visible business data.
- The task needs a structured weekly summary for downstream Office export.
- The task needs one report assembled from current-period and cumulative-period inputs across multiple source groups.
Do not use this skill for:
- arbitrary business-system browsing
- direct editing of report templates without fresh collection
- claiming full completeness when some source systems failed
- treating history-report entries as the upstream source of truth
## Workflow
1. Read the current-period and cumulative-period inputs from the page.
2. Verify required system-session context is available.
3. Collect weekly metrics from the relevant source groups.
4. Normalize the metrics into section-based weekly report data.
5. Return the structured artifact before prose.
6. If one or more source groups are missing or the two periods are inconsistent, mark the result as partial.
## Runtime Contract
- Prefer deterministic packaged collection before generic probing.
- Treat login failure, permission failure, blocked page, partial source availability, and empty result separately.
- Keep the structured report artifact as the primary output.
- Historical report lists and final report downloads are downstream artifacts, not the primary business data source.
- Localhost report services must not redefine upstream collection success.
## Partial-Failure Rule
- If only part of the weekly metrics are available, report `partial`.
- If current-period and cumulative-period metrics are not aligned, report `partial`.
- If downstream export or report-log writing fails after section collection succeeds, keep the artifact and report `partial`.
- Do not report the run as fully complete when upstream sections are missing.
## Export Artifact
```json
{
"type": "report-artifact",
"report_name": "95598-weekly-monitor-report",
"period": "",
"columns": [],
"rows": [],
"sections": [
{
"name": "fault-repair",
"columns": ["metric", "current_period", "cumulative", "year_over_year", "note"],
"rows": []
},
{
"name": "frequent-outage",
"columns": ["metric", "current_period", "cumulative", "year_over_year", "note"],
"rows": []
},
{
"name": "full-aperture-work-orders",
"columns": ["metric", "current_period", "cumulative", "year_over_year", "note"],
"rows": []
},
{
"name": "key-opinion-control",
"columns": ["metric", "value", "note"],
"rows": []
},
{
"name": "device-monitoring",
"columns": ["metric", "value", "note"],
"rows": []
},
{
"name": "proactive-dispatch",
"columns": ["metric", "value", "note"],
"rows": []
}
],
"status": "ok",
"partial_reasons": []
}
```
## Output
Return:
- current period
- cumulative period
- included source groups
- section count
- complete or partial status
- missing areas
- period alignment issues
- downstream export/logging failures
- Export Artifact

View File

@@ -0,0 +1,16 @@
[skill]
name = "95598-weekly-monitor-report"
description = "Use when the user wants to collect 95598, 12398, and distribution-monitor weekly metrics and generate a weekly report artifact."
version = "0.1.0"
author = "sgclaw"
tags = ["95598", "12398", "weekly-report", "browser", "report"]
prompts = [
"For weekly monitor collection, call 95598-weekly-monitor-report.collect_weekly_metrics before generic browser probing.",
]
[[tools]]
name = "collect_weekly_metrics"
description = "Collect multi-section weekly monitor metrics and prepare the report artifact shell."
kind = "browser_script"
command = "scripts/collect_weekly_metrics.js"

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,39 @@
# Collection Flow
## Source
- Source scenario: `D:\desk\智能体资料\大四区报告监测项\95598、12398及配网设备监控情况周统计\index.html`
- Entry page provides two date-range inputs: current-period and cumulative-period.
- The page also exposes execution logs and historical reports.
- The page orchestrates multiple source groups and combines them into one weekly report.
## Inputs
- Current-period datetime range
- Cumulative-period datetime range
- Current platform session and cached system account context
## First-pass Collection Steps
1. Open or attach to the source page.
2. Read both date ranges.
3. Verify required system-session context is available.
4. Trigger the deterministic weekly collector.
5. Collect metrics grouped by source system and report section.
6. Normalize source results into section-based weekly report data.
7. Return a structured weekly artifact before final report generation.
## Dependencies
- Browser-visible page state
- Platform integration scripts such as `/a_js/YPTAPI.js`
- Multi-system account and token context
- Localhost report services under `http://localhost:13313/ReportServices/*`
## State Semantics
- Success: source groups are collected, period-aligned, and mapped into sections.
- Partial result: one or more source groups are missing, period alignment is inconsistent, or downstream reporting fails after collection.
- Empty result: a valid query returns zero rows for one source group.
- Blocked/error: login, interception, permission failure, request failure, or parsing failure.
- Blocked/error must not be reported as empty data.

View File

@@ -0,0 +1,36 @@
# Data Quality
## Complete Result
A complete result means the expected weekly source groups for the selected current-period and cumulative-period ranges are all represented, period-aligned, and assembled into output sections.
## Core Section Groups
The first-pass package should preserve separate sections for:
- fault-repair
- frequent-outage
- full-aperture-work-orders
- key-opinion-control
- device-monitoring
- proactive-dispatch
## Partial Rules
- If any required source group is missing or weak, set `status` to `partial`.
- If the current-period and cumulative-period sections are inconsistent, set `status` to `partial`.
- If downstream report generation or report-log writing fails after section data is assembled, keep the artifact and record the downstream failure in `partial_reasons`.
- Do not claim full completeness when only some upstream groups are present.
## Common Weak Areas
- Current-period and cumulative-period mismatch
- Missing source-system blocks
- Incomplete summary sections
- Missing cached account or token context
- Downstream report generation failure after successful collection
## Empty vs Failure
- A valid query with zero rows for a source can be empty data.
- Login, interception, permission failure, request failure, or parsing failure must be surfaced as failure or partial, not empty.

View File

@@ -0,0 +1,50 @@
const SECTION_TEMPLATES = [
{
name: "fault-repair",
columns: ["metric", "current_period", "cumulative", "year_over_year", "note"],
rows: []
},
{
name: "frequent-outage",
columns: ["metric", "current_period", "cumulative", "year_over_year", "note"],
rows: []
},
{
name: "full-aperture-work-orders",
columns: ["metric", "current_period", "cumulative", "year_over_year", "note"],
rows: []
},
{
name: "key-opinion-control",
columns: ["metric", "value", "note"],
rows: []
},
{
name: "device-monitoring",
columns: ["metric", "value", "note"],
rows: []
},
{
name: "proactive-dispatch",
columns: ["metric", "value", "note"],
rows: []
}
];
function collectWeeklyMetrics(input = {}) {
return {
type: "report-artifact",
report_name: "95598-weekly-monitor-report",
period: input.period || "",
columns: [],
rows: [],
sections: SECTION_TEMPLATES.map((section) => ({ ...section, rows: [] })),
status: "ok",
partial_reasons: []
};
}
module.exports = {
SECTION_TEMPLATES,
collectWeeklyMetrics
};

View File

@@ -0,0 +1,133 @@
---
name: fault-details-report
description: Use when the user wants to collect fault-detail rows and export a structured fault report artifact or spreadsheet.
version: 0.1.0
author: sgclaw
tags:
- fault
- report
- xlsx
- details
- browser
---
# Fault Details Report
## When to Use
- The user asks to collect fault-detail rows from the business system.
- The user asks to normalize fault rows into a standard export schema.
- The task needs spreadsheet or document output from structured fault data.
- The task needs the fault-detail table plus the per-station summary table derived from the same query period.
Do not use this skill for:
- unrelated outage monitoring
- editing export templates without fresh data
- claiming successful export when row collection, normalization, or export generation failed
- treating history-report entries as the primary source of truth
## Workflow
1. Read the selected start and end time from the page date-range control.
2. Collect raw fault-detail rows from the source page's repair-order query.
3. Normalize rows into the canonical detail-column order defined by the page export schema.
4. Derive the summary rows used by the secondary summary sheet.
5. Return the structured artifact before any prose summary.
6. If required columns are missing or summary derivation is incomplete, mark the result as partial.
## Runtime Contract
- Prefer a packaged deterministic collector before generic browser probing.
- Keep raw row collection, field normalization, and summary derivation as separate steps.
- Distinguish blocked, failed, partial, and empty-result states explicitly.
- Source-page history logs are report history, not the primary business data source.
- Export and report-log localhost services are downstream dependencies and must not redefine collection success.
## Partial-Failure Rule
- If rows are collected but one or more required fields cannot be normalized, report `partial`.
- If detail rows are collected but the summary sheet cannot be derived completely, report `partial`.
- If export-file generation or report-log writing fails after rows are collected, keep the artifact and report `partial`.
- Do not treat failed collection as an empty table.
## Export Artifact
```json
{
"type": "report-artifact",
"report_name": "fault-details-report",
"period": "",
"columns": [
"qxdbh",
"gssgs",
"sgs",
"gddw",
"gds",
"slsj",
"yjflMc",
"ejflMc",
"sjflMc",
"gzms",
"yhbh",
"yhmc",
"lxr",
"gzdd",
"lxdh",
"bxsj",
"gdsj",
"clzt",
"qxxcjl",
"bdz",
"line",
"pb",
"sxfl1",
"sxfl2",
"sxfl3",
"gzsb",
"gzyy",
"bz"
],
"rows": [],
"sections": [
{
"name": "summary-sheet",
"columns": [
"index",
"gsName",
"fwDept",
"className",
"allCount",
"wxCount",
"khcCount",
"sbdSbCount",
"gyGzCount",
"dyGzCount",
"tqdzCount",
"tqbxCount",
"dyxlCount",
"bqxCount",
"jllCount",
"bhxCount",
"qftdCount"
],
"rows": []
}
],
"status": "ok",
"partial_reasons": []
}
```
## Output
Return:
- operation type
- selected period
- detail row count
- summary row count
- required column coverage
- complete or partial status
- missing columns, weak mappings, or downstream export/logging failures
- Export Artifact

View File

@@ -0,0 +1,16 @@
[skill]
name = "fault-details-report"
description = "Use when the user wants to collect fault-detail rows and export a structured fault report artifact or spreadsheet."
version = "0.1.0"
author = "sgclaw"
tags = ["fault", "report", "xlsx", "details", "browser"]
prompts = [
"For fault detail collection, call fault-details-report.collect_fault_details before generic browser probing.",
]
[[tools]]
name = "collect_fault_details"
description = "Collect raw fault-detail rows and prepare the detail-plus-summary report artifact shell from the source business page."
kind = "browser_script"
command = "scripts/collect_fault_details.js"

View File

@@ -0,0 +1,914 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/elementui.css" />
<link rel="stylesheet" href="./css/page.css" />
<title>故障明细统计</title>
<link rel="shortcut icon" href="./images/favicon.svg" type="image/x-icon" />
<script src="./js/jquery.js"></script>
<script src="./js/vue.js"></script>
<script src="./js/unit.js"></script>
<script src="./js/elementui.js"></script>
<script src="./js/moment.js"></script>
<script src="./js/common.js"></script>
<script src="./js/dpage.min.js"></script>
<script src="/a_js/YPTAPI.js"></script>
</head>
<body>
<div id="app">
<div class="define_tabs">
<div class="sys_title">故障明细统计</div>
</div>
<div class="pageContainer">
<div class="sysLc">
<div class="sysLcLeft">
<div class="sizeBoxContainer">
<div class="sizeBoxTitle sizeBoxTitle1">作业信息</div>
<div class="sizeBoxContent">
<div class="workInfo">
<div class="line" v-for="(item,index) in workInfo">
<div class="title">{{ item.title }}</div>
<div class="info">{{ item.info }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="sysLcRight">
<div class="sysLcRightTop">
<div class="sizeBoxContainer">
<div class="sizeBoxTitle sizeBoxTitle2">
自动化工具执行过程
<div class="sizeBoxTitleExtre">
<el-button v-if="!runLoading" type="primary" class="showHandleBtn"
@click="toHandle(false)">开始执行</el-button>
<el-button v-else>下载中</el-button>
</div>
</div>
<div class="setPeizhi">
<el-form ref="form" label-width="80px">
<el-form-item label="选择日期">
<el-date-picker size="small" style="width: 350px" v-model="date" type="datetimerange"
ramhe-separator="至" @change="dateChange" start-placeholder="开始日期" end-placeholder="结束日期">
</el-date-picker>
</el-form-item>
</el-form>
</div>
<el-progress color="#33b5b9" :text-inside="true" :stroke-width="25" :percentage="startYoy"></el-progress>
<div class="bottomContent">
<div class="borderInfoWrap">
<div class="tittle-content">
<img src="./images/Button.png" style="margin-right: 10px" />
<div style="color: rgb(39, 168, 163); font-weight: 700; margin-top: 15px">生成日志</div>
</div>
<div class="sizeBoxContent" style="height: 405px; overflow-y: auto" id="infoWrap">
<div v-for="(item,index) in handleList" :key="item" class="infoItem">
<div :id="index==handleList.length-1?'infolast':''" :class="item.type"
style="margin-bottom: 10px">{{item.item}}</div>
</div>
</div>
</div>
<div class="borderInfoWrap">
<div class="tittle-content">
<img src="./images/Button.png" style="margin-right: 10px" />
<div style="color: rgb(39, 168, 163); font-weight: 700; margin-top: 15px">历史报告</div>
</div>
<div class="borderInfoWrapRightContent" style="height: 425px; overflow-y: auto">
<div v-for="(item,index) in reportLogList" :key="item" class="infoItem">
<div
style="display: flex; align-items: center; margin-bottom: 10px; height: 50px; margin-top: 10px; margin-left: 25px; color: blue">
<div :href="item.path">{{item.reportName}} <span
style="margin-left: 30px; color: #000">{{item.createdTime}}</span></div>
<el-button style="margin-left: 10px" @click="aSaveFile(item.path)">下载</el-button>
<el-button type="danger" style="margin-left: 10px"
@click="deleteReportLog(item.reportNo)">删除</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
const mac = new Vue({
el: "#app",
data() {
return {
userCodeIV: "", //sxbj2
passwordIV: "", //
isShow: true,
date: [moment().clone().subtract(1, "days").format("YYYY-MM-DD 16:00:00"), moment().format("YYYY-MM-DD 16:00:00")],
startDate: moment().clone().subtract(1, "days").format("YYYY-MM-DD 16:00:00"),
endDate: moment().format("YYYY-MM-DD 16:00:00"),
toDay: moment().format("MM月DD日"),
dayDate: "",
handleList: [],
dataObj: {},
fdList: [],
excleIni: [
{
sheet: `故障报修工单分析`,
cols: [
["qxdbh", "申请编号"],
["gssgs", "单位名称(省)"],
["sgs", "单位名称(城市)"],
["gddw", "供电单位"],
["gds", "供电所"],
["slsj", "受理时间"],
["yjflMc", "一级类型"],
["ejflMc", "二级类型"],
["sjflMc", "三级类型"],
["gzms", "报修内容"],
["yhbh", "客户编号"],
["yhmc", "客户名称"],
["lxr", "联系人"],
["gzdd", "联系地址"],
["lxdh", "联系电话"],
["gzdd", "现场地址"],
["bxsj", "工单下发时间"],
["gdsj", "归档时间"],
["clzt", "处理结果"],
["qxxcjl", "处理情况"],
["bdz", "变电站"],
["line", "线路"],
["pb", "配变"],
["sxfl1", "属性分类1"],
["sxfl2", "属性分类2"],
["sxfl3", "属性分类3"],
["gzsb", "故障设备"],
["gzyy", "故障原因"],
["bz", "备注"],
],
},
{
sheet: "汇总模板",
cols: [
["index", "统计周期内故障报修工单情况", "序号", "序号", "序号", "序号", "序号"],
["gsName", "统计周期内故障报修工单情况", "供电公司", "供电公司", "供电公司", "供电公司", "供电公司"],
["fwDept", "统计周期内故障报修工单情况", "县公司", "县公司", "县公司", "县公司", "县公司"],
["className", "统计周期内故障报修工单情况", "供电所", "供电所", "供电所", "供电所", "供电所"],
["allCount", "统计周期内故障报修工单情况", "报修工单数量", "总数", "总数", "总数", "总数"],
["wxCount", "统计周期内故障报修工单情况", "报修工单数量", "无效工单", "无效工单", "无效工单", "无效工单"],
["khcCount", "统计周期内故障报修工单情况", "报修工单数量", "有效工单", "客户侧", "客户侧", "客户侧"],
["sbdSbCount", "统计周期内故障报修工单情况", "报修工单数量", "有效工单", "电网侧", "输变电设备", "输变电设备"],
["gyGzCount", "统计周期内故障报修工单情况", "报修工单数量", "有效工单", "电网侧", "配电设备", "高压故障"],
["dyGzCount", "统计周期内故障报修工单情况", "报修工单数量", "有效工单", "电网侧", "配电设备", "低压故障"],
["", "", "", "", "", "", ""],
["", "", "", "", "", "", ""],
["", "", "", "", "", "", ""],
["tqdzCount", "统计周期内低压故障分类", "台区刀闸", "", "", "", ""],
["tqbxCount", "统计周期内低压故障分类", "台区保险", "", "", "", ""],
["dyxlCount", "统计周期内低压故障分类", "低压线路", "", "", "", ""],
["bqxCount", "统计周期内低压故障分类", "表前线", "", "", "", ""],
["jllCount", "统计周期内低压故障分类", "计量类", "", "", "", ""],
["bhxCount", "统计周期内低压故障分类", "表后线", "", "", "", ""],
["khcCount", "统计周期内低压故障分类", "用户内部", "", "", "", ""],
["qftdCount", "统计周期内低压故障分类", "欠费停电", "", "", "", ""],
],
},
],
infoObj: {},
mxList: [],
workInfo: [
{
title: "原创人员",
info: "朱志挺",
},
{
title: "工作痛点",
info: "每日导出故障明细信息需要人工从大IV区系统中复制粘贴各个工单的详情内容耗费大量人力和时间而且存在人工填写错误的情况。",
},
{
title: "实现思路",
info: "在大IV系统中监测95598抢修工单数据并将监测到的工单进行数据解析整合然后按照模板自动填充数据实现一键导出故障明细统计表",
},
{
title: "适用岗位",
info: "供指",
},
],
systemIndex: 0,
reportLogList: [],
runLoading: false,
startLoading: false,
reportType: "gzmxdc",
startYoy: 0,
timer: null,
};
},
watch: {
handleList: () => {
if (mac.handleList.length > 200) {
mac.handleList = mac.handleList.splice(150, mac.handleList.length);
}
setTimeout(() => {
$("#infoWrap").scrollTop(200000);
}, 1000);
},
},
mounted() {
window.mac = this;
this.getReportLog();
},
methods: {
//查询报表日志
getReportLog() {
$.ajax({
url: "http://localhost:13313/ReportServices/Api/getReportLog",
type: "POST",
dataType: "json",
crossDomain: true,
headers: { "Content-Type": "application/json;charset=UTF-8" },
contentType: "application/json;charset=utf-8",
data: JSON.stringify({ type: this.reportType }),
success: function (res) {
// console.log('报表日志', res)
if (res.status == 200) {
mac.reportBaseUrl = res.baseurl;
console.log(mac.reportBaseUrl);
mac.reportLogList = res.data;
// mac.reportLogList = mac.reportLogList.slice(-5);
mac.reportLogList = mac.reportLogList?.sort((a, b) => new Date(b?.createdTime) - new Date(a?.createdTime));
}
},
});
},
//写入报表日志
setReportLog(reportName, path) {
let userinfo = localStorage.userinfo;
let account = "";
if (userinfo) {
account = JSON.parse(userinfo).account;
}
let data = { account, type: this.reportType, reportName: reportName, reportInfo: "", path: path };
$.ajax({
url: "http://localhost:13313/ReportServices/Api/setReportLog",
type: "POST",
dataType: "json",
crossDomain: true,
headers: { "Content-Type": "application/json;charset=UTF-8" },
contentType: "application/json;charset=utf-8",
data: JSON.stringify(data),
success: function (res) {
console.log("报表日志", res);
if (res.status == 200) {
mac.getReportLog();
}
},
});
},
//删除报表日志
deleteReportLog(reportNo) {
let data = { reportNo };
$.ajax({
url: "http://localhost:13313/ReportServices/Api/deleteReportLog",
type: "POST",
dataType: "json",
crossDomain: true,
headers: { "Content-Type": "application/json;charset=UTF-8" },
contentType: "application/json;charset=utf-8",
data: JSON.stringify(data),
success: function (res) {
if (res.status == 200) {
mac.getReportLog();
}
},
});
},
handleSearch() {
console.log(this.formInline);
let list = JSON.stringify(this.formInline);
let locationUrl = JSON.stringify(location.href);
BrowserAction('sgBrowserExcuteJsCode', location.origin + "/#/monitorQueue", `window.mac.getLogint(true,${locationUrl},${list},false,'1')`);
},
async searchSystemStatusList() {
await searchSubscribeSystemList({
actionName: "searchAllSubscribeSystem",
authenticationInformation: "身份信息码",
dataSource: "mysql-operate",
data: {
userId: this.$store.state.userInfo.userId,
},
}).then((res) => {
if (res.status === 200) {
this.mutableSystemList = res.resultList.map((item) => {
console.log(item);
item.SysName = item.systemName;
item.SysID = item.systemId;
item.isLine = false;
item.loginURL = item.systemAddress;
item.successURL = item.afterLoginAddress;
item.jsCode = item.loginCommand;
return item;
});
}
});
},
loadingCloseTing(num) {
console.log(num);
if (num == "0") {
this.$message.error("登录系统失败,请检查相关配置!");
} else if (num == "1") {
this.$message.error("本地服务未启动,请启动本地服务!");
} else if (num == "2") {
this.$message.error("未匹配对应系统,请先订阅相关系统 !");
} else if (num == "3") {
this.$message.error("登陆超时,请重新登陆!");
if (this.formInline) {
mac.handleSearch();
}
} else if (num == "4") {
this.$message.error("未检测到跳转地址,请联系管理员!");
}
if (this.loading) {
this.loading = null;
}
},
// 关闭loading
loadingClose() {
this.location();
},
location() {
console.log("初始化");
if (this.loading) {
this.loading = null;
}
let locationUrl = JSON.stringify(location.href);
BrowserAction('sgBrowserExcuteJsCode', location.origin + "/#/monitorQueue", `window.mac.getListIng(${locationUrl})`);
},
loginStatusFlushed(objList) {
if (!objList || objList.length == 0) {
mac.handleList.push({
item: `${moment().format("YYYY-MM-DD HH:mm:ss")}:未匹配到系统账号信息,请添加系统账号密码!`,
type: "error",
});
return;
}
this.mutableSystemList = objList;
let locationUrl = JSON.stringify(location.href);
let dsqLoginFlag = true;
if (!this.runLoading) return;
this.formInline = null;
let dsqData = null;
console.log("this.systemIndex", this.systemIndex);
for (let i = 0; i < this.mutableSystemList.length; i++) {
const item = this.mutableSystemList[i];
if (this.systemIndex == 0) {
if (item.SysID == "9ad2e9ed2f7911ef978200155de4f645") {
//大四区
dsqLoginFlag = false;
dsqData = item;
this.systemIndex = 1;
break;
}
}
if (this.systemIndex >= 1) {
if (item.SysID == "9ad2e9ed2f7911ef978200155de4f645") {
//大四区
if (!item.isLine) {
dsqLoginFlag = false;
// dsqData = item
break;
}
}
}
}
if (!dsqLoginFlag) {
if (dsqData) {
setTimeout(() => {
this.formInline = dsqData;
mac.handleSearch();
}, 1000);
} else {
mac.handleList.push({
item: `${moment().format("YYYY-MM-DD HH:mm:ss")}:请配置大四区账号密码!`,
type: "error",
});
}
return;
}
setTimeout(() => {
if (mac.timer) {
clearInterval(mac.timer);
mac.timer = null;
}
this.getInfo();
this.getData();
}, 2000);
},
toolObj(obj) {
console.log(obj);
let newArry = Object.keys(obj);
for (let i = 0; i < newArry.length; i++) {
sessionStorage.setItem(newArry[i], obj[newArry[i]]);
}
},
getInfo() {
let tGfUser = localStorage.tGfUser;
// 大四区
if (tGfUser) {
tGfUser = JSON.parse(tGfUser);
this.userCodeIV = tGfUser.id;
this.infoObj.city = tGfUser.orgNo;
this.infoObj.cityName = tGfUser.orgName;
}
},
getTime() {
let startTime = this.dayDate
? moment(this.dayDate).clone().subtract(1, "days").format("YYYY-MM-DD HH:mm:ss")
: moment(this.date).clone().subtract(1, "days").format("YYYY-MM-DD HH:mm:ss");
let endTime = this.dayDate ? moment(this.dayDate).format("YYYY-MM-DD HH:mm:ss") : this.date;
let endEndTime = this.dayDate
? moment(this.dayDate).clone().subtract(-1, "days").format("YYYY-MM-DD HH:mm:ss")
: moment(this.date).clone().subtract(-1, "days").format("YYYY-MM-DD HH:mm:ss");
let lastStartTime = moment(startTime).clone().subtract(1, "days").format("YYYY-MM-DD HH:mm:ss");
let lastEndTime = moment(endTime).clone().subtract(1, "days").format("YYYY-MM-DD HH:mm:ss");
let lastMonthStartTime = moment(startTime).clone().subtract(1, "month").format("YYYY-MM-DD HH:mm:ss");
return {
startTime,
endTime,
endEndTime,
lastStartTime,
lastEndTime,
lastMonthStartTime,
};
},
dateChange(e) {
this.startDate = moment(e?.[0]).format("YYYY-MM-DD HH:mm:ss") || "";
this.endDate = moment(e?.[1]).format("YYYY-MM-DD HH:mm:ss") || "";
this.toDay = moment(e?.[1]).format("MM月DD日");
this.dayDate = moment(e?.[1]).format("YYYY-MM-DD HH:mm:ss") || "";
},
toHandle() {
this.mxList = [];
mac.startYoy = 0;
this.startLoading = false;
let dsqLoginFlag = false;
this.formInline = null;
let dsqData = null;
this.systemIndex = 0;
this.runLoading = true;
this.location();
mac.timer = setInterval(() => {
if (mac.startYoy < 20) {
mac.startYoy += 1;
} else {
clearInterval(mac.timer);
mac.timer = null;
}
}, 1000);
},
exportFile() {
mac.startYoy = 100;
if (mac.timer) {
clearInterval(mac.timer);
mac.timer = null;
}
let excleInI = [];
mac.excleIni.forEach((x) => {
let obj = x;
obj.sheet = x.sheet + moment(mac.QueryType == 0 ? mac.dayRange[1] : mac.proMonth).format("MM");
excleInI.push(obj);
});
mac.fdList = mac.fdList?.sort((a, b) => new Date(a?.bxsj)?.getTime() - new Date(b?.bxsj)?.getTime());
console.log(mac.fdList);
let params = {
titles: excleInI,
date: mac.infoObj.cityName + "故障报修明细表(" + this.toDay + ")",
datas: [mac.fdList, mac.mxList],
};
this.runLoading = false;
$.ajax({
url: "http://localhost:13313/SurfaceServices/personalBread/export/faultDetailsExportXLSXS",
type: "POST",
dataType: "json",
crossDomain: true,
data: JSON.stringify(params), //必须是字符串格式
contentType: "application/json", //指定内容格式
success: (res) => {
//括号里的data是服务器返回的数据
let url = res?.data?.data;
mac.aSaveFile(url);
this.setReportLog(mac.infoObj.cityName + "故障报修明细表(" + this.toDay + ")", url);
},
});
},
// 导出文件
aSaveFile(url) {
var a = document.createElement("a");
a.href = url;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
},
// 大四区登录
getData() {
window.getAllUrlCallBack = function (urls) {
const urlArr = urls.split(";");
let hasPage = false;
let hasLoginPage = false;
for (var i = 0; i < urlArr.length; i++) {
if (urlArr[i]?.indexOf(`http://21.77.244.194:18890/mainSystem/#/login`) != -1) {
hasPage = true;
}
}
if (hasPage) {
let jsCode = `function refDpage() {
const jss = document.createElement("script")
jss.src = "${location.href}/js/jquery.js"
document.head.appendChild(jss)
}
refDpage()`;
BrowserAction('sgBrowserExcuteJsCode', "http://21.77.244.194:18890/mainSystem/#/login", jsCode);
setTimeout(() => {
mac.getDataFromTargetPage();
}, 12000);
} else {
BrowserAction('sgHideBrowerserOpenPage', `http://21.77.244.194:18890/mainSystem/#/login`);
setTimeout(() => {
mac.getData();
}, 2000);
}
};
BrowserAction('sgHideBrowerserGetUrls', "window.getAllUrlCallBack");
},
// 95598抢修工单监测
getDataFromTargetPage() {
if (mac.startLoading) return;
mac.timer = setInterval(() => {
if (mac.startYoy < 90) {
mac.startYoy += 1;
} else {
clearInterval(mac.timer);
mac.timer = null;
}
}, 1000);
mac.startLoading = true;
mac.handleList.push({ item: `${moment().format("YYYY-MM-DD HH:mm:ss")}:正在查询故障明细工单!`, type: "" });
var slsj = `${this.startDate},${this.endDate}`;
let jsCode = `
$.ajax({
url: 'http://21.77.244.194:18890/qxgl/repairOrder/list?page=1&type=0101&orgNo=${mac.infoObj?.city}&gdsj=${slsj}&limit=500',
type: "GET",
dataType: 'json',
crossDomain: true,
headers:{
'Content-Type':'application/json;',
'userId':'${mac.userCodeIV}',
'Access-Control-Allow-Credentials':true,
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS,PUT,DELETE,OPTION;',
'Access-Control-Allow-Origin':'*'
},
data: '', //必须是字符串格式
contentType: "application/json", //指定内容格式
success: function (res) { //括号里的data是服务器返回的数据
let jsCode1 = \`mac.callbackfunGetZDJX(\${JSON.stringify(res)})\`;
__prototype__('sgBrowserExcuteJsCode', '${window.location.href}', jsCode1);
},
error:function(e){
let jsCode1 = \`mac.callbackfunGetZDJX(\${JSON.stringify(e)})\`;
__prototype__('sgBrowserExcuteJsCode', '${window.location.href}', jsCode1);
}
})
`;
BrowserAction('sgBrowserExcuteJsCode', "http://21.77.244.194:18890/mainSystem/#/login", jsCode);
},
callbackfunGetZDJX(resData) {
console.log(resData);
mac.fdList = [];
this.setByData(resData);
},
setByData(resData) {
if (resData.msg == "success") {
if (resData.page.list.length > 0) {
let list = [];
// this.excleIni[1].cols = this.excleIni[1].cols.slice(0,10)
let flList = [
{
name: "欠费停电",
reList: ["欠费", "余额不足"],
type: "用户侧",
dwcFl: "",
},
{
name: "客户内部故障",
reList: ["客户内部故障"],
type: "用户侧",
dwcFl: "",
},
{
name: "表后线",
reList: ["表后线"],
type: "用户侧",
dwcFl: "",
},
{
name: "输变电设备",
reList: ["输变电设备"],
type: "电网侧",
dwcFl: "输变电设备",
},
{
name: "高压故障",
reList: [
"电杆(塔)",
"杆(塔)基础",
"连接线-架空线路",
'"连接线-高压计量设备"',
"柱上隔离开关-架空线路",
"横担-架空线路",
"绝缘子-架空线路",
"导线-架空线路",
"跌落式熔断器",
"柱上断路器",
"二次及自动化装置",
"柱上负荷开关",
"金具-架空线路",
"高压电容器",
"避雷装置-架空线路",
"避雷装置-电缆线路",
"避雷装置-配电站房设备",
"接地装置-架空线路",
"接地装置-电缆线路",
"接地装置-配电站房设备",
"电缆本体-电缆线路",
"电缆终端头-电缆线路",
"电缆中间接头-电缆线路",
"油浸式变压器",
"干式变压器",
"隔离开关-配电站房设备",
"基础-配电站房设备",
"端子排-配电站房设备",
"电流互感器-配电站房设备",
"电流互感器-高压计量设备",
"电压互感器",
"穿墙套管",
"母排-配电站房设备",
"熔断器-配电站房设备",
"断路器-配电站房设备",
"负荷开关",
"计量表计-高压计量设备",
"接线端子盒",
"计量柜",
"电压互感器",
],
type: "电网侧",
dwcFl: "高压故障",
},
{
name: "台区刀闸",
reList: ["柱上隔离开关-低压架空线路", "电缆终端头", "隔离开关-低压设备"],
type: "电网侧",
dwcFl: "低压故障",
},
{
name: "台区保险",
reList: [
"绝缘子-低压架空线路",
"熔断器-低压设备",
"断路器-低压设备",
"漏电保护器",
],
type: "电网侧",
dwcFl: "低压故障",
},
{
name: "低压线路",
reList: [
"连接线-低压架空线路",
"导线-低压架空线路",
"横担-低压架空线路",
"金具-低压架空线路",
"避雷装置-低压架空线路",
"避雷装置-低压电缆线路路",
"避雷装置-低压设备",
"接地装置-低压架空线路",
"接地装置-低压设备",
"电缆本体-低压电缆线路路",
"电缆终端头-低压电缆线路路",
"电缆中间接头-低压电缆线路路",
"基础-低压架空线路",
"基础-低压设备",
"端子排-低压设备",
"电流互感器-低压设备",
"母排-低压设备",
"接户线",
"电杆",
"连接装置",
"电容器",
"交流接触器",
"低压母线槽",
"频率异常",
"谐波异常",
"电压异常",
],
type: "电网侧",
dwcFl: "低压故障",
},
{
name: "表前线",
reList: ["连接线-低压计量设备", "接线端子", "主干线", "表前线"],
type: "电网侧",
dwcFl: "低压故障",
},
{
name: "计量类",
reList: ["电流互感器-低压计量设备", "计量表计-低压计量设备", "计量箱(柜)", "表前开关(熔丝)"],
type: "电网侧",
dwcFl: "低压故障",
},
];
resData.page.list = resData.page.list.filter((item) => item.statusName != "回退营销");
resData.page.list.forEach((item) => {
item.slsj = item.bxsj;
item.gssgs = "甘肃省电力公司";
item.sgs = mac.infoObj.cityName?.indexOf("嘉峪关") != -1 ? mac.infoObj.cityName : item.cityName;
item.gddw = item.maintOrgName;
item.gds = item.maintGroupName;
item.clzt = "处理完成";
item.bdz = item.bdzMc;
item.line = item.xlmc10;
item.pb = item.byqmc;
let fl1 = "无效";
let fl2 = "用户侧";
let fl3 = "高压";
let f14 = item.sjflMc;
let dwcFl = "";
let gzyy = "";
if (item.sjflMc && item.sjflMc != "" && item.sjflMc != "无故障") {
flList.forEach((it) => {
it.reList.forEach((i) => {
let twoName = "";
let threeName = "";
let flag = false;
if (i.indexOf("-") != -1) {
threeName = i.split("-")[0];
twoName = i.split("-")[1];
flag = true;
}
if (flag) {
if (item.sjflMc?.indexOf(threeName) != -1 && item.ejflMc == twoName) {
fl1 = "有效";
fl2 = it.type;
fl3 = "低压";
f14 = it.name;
dwcFl = it.dwcFl;
let firstIndex = item.qxxcjl?.indexOf("故障现象是");
if (threeName == "计量表计" && twoName == "低压计量设备" && item.qxxcjl?.indexOf("客户原因导致表计烧损") != -1) {
fl2 = "用户侧";
fl4 = "客户内部故障";
}
if (threeName == "表前线" && twoName == "低压计量设备" && item.qxxcjl?.indexOf("客户原因导致表前线断线") != -1) {
fl2 = "用户侧";
fl4 = "客户内部故障";
}
if (firstIndex != -1) {
let content = item.qxxcjl?.substr(firstIndex + 5, item.qxxcjl.length);
let secondIndex = content?.indexOf("");
if (secondIndex > -1) {
gzyy = content.substr(0, secondIndex);
}
}
if (gzyy == "") {
firstIndex = item.qxxcjl?.indexOf("经查");
if (firstIndex != -1) {
let content = item.qxxcjl?.substr(firstIndex + 5, item.qxxcjl.length);
let secondIndex = content?.indexOf("。");
if (secondIndex > -1) {
gzyy = content.substr(0, secondIndex);
}
}
}
}
} else {
if (item.sjflMc?.indexOf(i) != -1) {
fl1 = "有效";
fl2 = it.type;
fl3 = "低压";
f14 = it.name;
dwcFl = it.dwcFl;
if (i == "表前开关(熔丝)" && item.qxxcjl?.indexOf("客户原因导致表前开关跳闸") != -1) {
fl2 = "用户侧";
fl4 = "客户内部故障";
}
let firstIndex = item.qxxcjl?.indexOf("故障现象是");
if (firstIndex != -1) {
let content = item.qxxcjl?.substr(firstIndex + 5, item.qxxcjl.length);
let secondIndex = content?.indexOf("");
if (secondIndex > -1) {
gzyy = content.substr(0, secondIndex);
}
}
if (gzyy == "") {
firstIndex = item.qxxcjl?.indexOf("经查");
if (firstIndex != -1) {
let content = item.qxxcjl?.substr(firstIndex + 5, item.qxxcjl.length);
let secondIndex = content?.indexOf("。");
if (secondIndex > -1) {
gzyy = content.substr(0, secondIndex);
}
}
}
}
}
});
});
}
if (fl1 == "无效") {
fl2 = "";
fl3 = "";
f14 = "";
dwcFl = "";
}
item.sxfl1 = fl1;
item.sxfl2 = fl2;
item.sxfl3 = dwcFl;
item.gzsb = f14;
item.dwcFl = dwcFl;
item.gzyy = gzyy;
item.fl1 = fl1;
});
console.log("抢修", resData.page.list);
let dwcList = resData.page.list.filter((item) => item.fl1?.indexOf("电网侧") != -1);
let yhcList = resData.page.list.filter((item) => item.fl1?.indexOf("用户侧") != -1);
let wxgdList = resData.page.list.filter((item) => item.fl1?.indexOf("无效工单") != -1);
list = resData.page.list;
mac.fdList = list;
let gdsObj = {};
list.forEach((item) => {
if (!gdsObj[item.gds]) {
gdsObj[item.gds] = [item];
} else {
gdsObj[item.gds].push(item);
}
});
console.log(gdsObj);
Object.keys(gdsObj).forEach((key, i) => {
let newObj = {
index: i + 1,
gsName: mac.infoObj.cityName,
fwDept: mac.infoObj.cityName == "国网嘉峪关供电公司" ? "供电服务部" : gdsObj[key][0]?.gddw,
className: mac.infoObj.cityName == "国网嘉峪关供电公司" ? "急修班" : key,
wxCount: gdsObj[key].filter((item) => item.sxfl1 == "无效").length,
khcCount: gdsObj[key].filter((item) => item.sxfl2 == "用户侧" && item.f14 != "表后线" && item.fl4 != "欠费停电").length,
gyGzCount: gdsObj[key].filter((item) => item.sxfl2 == "电网侧").filter((item) => item.dwcFl == "高压故障").length,
dyGzCount: gdsObj[key].filter((item) => item.sxfl2 == "电网侧").filter((item) => item.dwcFl == "低压故障").length,
sbdSbCount: gdsObj[key].filter((item) => item.sxfl2 == "电网侧").filter((item) => item.dwcFl == "输变电设备").length, //输变电设备
// -------------------------------------------
tqdzCount: gdsObj[key].filter((item) => item.gzsb == "台区刀闸").length, //台区刀闸
tqbxCount: gdsObj[key].filter((item) => item.gzsb == "台区保险").length, //台区保险
dyxlCount: gdsObj[key].filter((item) => item.gzsb == "低压线路").length, //低压线路
jllCount: gdsObj[key].filter((item) => item.gzsb == "计量类").length, //计量类
bhxCount: gdsObj[key].filter((item) => item.gzsb == "表后线").length, //表后线
bqxCount: gdsObj[key].filter((item) => item.gzsb == "表前线").length, //表前线
qftdCount: gdsObj[key].filter((item) => item.gzsb == "欠费停电").length, //欠费停电
};
newObj.allCount = newObj.wxCount + newObj.khcCount + newObj.gyGzCount + newObj.dyGzCount + newObj.sbdSbCount;
mac.mxList.push(newObj);
});
mac.exportFile();
mac.handleList.push({ item: `${moment().format("YYYY-MM-DD HH:mm:ss")}:故障明细导出${resData.page.list.length}条数据!`, type: "" });
} else {
mac.handleList.push({ item: `${moment().format("YYYY-MM-DD HH:mm:ss")}:查询成功,暂无故障明细数据!`, type: "" });
mac.fdList = [{ qxdbh: "今日无数据" }];
mac.exportFile();
}
const list = [];
resData.page.list.forEach((item) => {
list.push(item.id);
});
mac.dataObj.qxgd = list;
} else {
mac.handleList.push({ item: `${moment().format("YYYY-MM-DD HH:mm:ss")}:故障明细查询失败,重新查询中...`, type: "" });
mac.getData();
}
},
},
});
</script>
</body>
</html>

View File

@@ -0,0 +1,39 @@
# Collection Flow
## Source
- Source scenario: `D:\desk\智能体资料\大四区报告监测项\故障明细\index.html`
- Entry page presents a datetime range selector and a one-click execution button.
- The page also exposes generation logs and historical reports, but those are report-history views rather than the primary collection source.
## Inputs
- Start and end datetime from the page-level datetime range picker.
- Current platform session and subscribed system account context.
## First-pass Collection Steps
1. Open or attach to the source page.
2. Read the selected start and end datetime.
3. Ensure the D4 business page session is available.
4. Trigger the repair-order query used by the page logic.
5. Collect raw fault-detail rows.
6. Normalize rows using the canonical column order declared in `excleIni[0].cols`.
7. Derive the summary-sheet rows from grouped detail rows.
8. Return a structured artifact before any export-file or prose step.
## Dependencies
- Browser-visible business page state
- Page scripts and platform integration (`/a_js/YPTAPI.js`)
- D4 login/session context and BrowserAction execution
- Localhost report services under `http://localhost:13313/ReportServices/*`
- Localhost export service under `http://localhost:13313/SurfaceServices/personalBread/export/faultDetailsExportXLSXS`
## State Semantics
- Collection success: detail rows are available and mapped into the canonical schema, and summary derivation is complete.
- Partial result: detail rows are collected, but some field normalization, summary derivation, export generation, or report-log writing fails.
- Empty result: request succeeds for the chosen period but zero fault rows match.
- Blocked/error result: login failure, request failure, script failure, page interception, or parse failure.
- Blocked/error must not be reported as an empty result.

View File

@@ -0,0 +1,99 @@
# Data Quality
## Canonical Detail Columns
The first-pass canonical detail-column order follows the source page export schema:
- `qxdbh`
- `gssgs`
- `sgs`
- `gddw`
- `gds`
- `slsj`
- `yjflMc`
- `ejflMc`
- `sjflMc`
- `gzms`
- `yhbh`
- `yhmc`
- `lxr`
- `gzdd`
- `lxdh`
- `bxsj`
- `gdsj`
- `clzt`
- `qxxcjl`
- `bdz`
- `line`
- `pb`
- `sxfl1`
- `sxfl2`
- `sxfl3`
- `gzsb`
- `gzyy`
- `bz`
## Required Columns
At minimum, the first-pass artifact must preserve these keys for each collected detail row:
- `qxdbh`
- `gssgs`
- `sgs`
- `gddw`
- `gds`
- `slsj`
- `yjflMc`
- `ejflMc`
- `sjflMc`
- `gzms`
- `bxsj`
- `gdsj`
- `clzt`
- `qxxcjl`
## Nullable Columns
These may be blank in some rows but should still be preserved when present:
- `yhbh`
- `yhmc`
- `lxr`
- `gzdd`
- `lxdh`
- `bdz`
- `line`
- `pb`
- `sxfl1`
- `sxfl2`
- `sxfl3`
- `gzsb`
- `gzyy`
- `bz`
## Summary-Derivation Expectations
The first-pass package should also be able to derive summary rows keyed around:
- `index`
- `gsName`
- `fwDept`
- `className`
- `allCount`
- `wxCount`
- `khcCount`
- `sbdSbCount`
- `gyGzCount`
- `dyGzCount`
## Partial Rules
- If detail rows are collected but one or more required detail columns cannot be mapped, set `status` to `partial`.
- If the detail table is available but the summary sheet cannot be derived completely, set `status` to `partial`.
- If export generation or report-log writing fails after collection succeeds, keep the artifact and record the downstream failure in `partial_reasons`.
- Do not silently drop required columns.
## Empty vs Failure
- Zero rows for a valid date range is an empty result.
- Request, login, interception, export-service, or parsing failure is not an empty result and must be surfaced explicitly.

View File

@@ -0,0 +1,75 @@
const DETAIL_COLUMNS = [
"qxdbh",
"gssgs",
"sgs",
"gddw",
"gds",
"slsj",
"yjflMc",
"ejflMc",
"sjflMc",
"gzms",
"yhbh",
"yhmc",
"lxr",
"gzdd",
"lxdh",
"bxsj",
"gdsj",
"clzt",
"qxxcjl",
"bdz",
"line",
"pb",
"sxfl1",
"sxfl2",
"sxfl3",
"gzsb",
"gzyy",
"bz"
];
const SUMMARY_COLUMNS = [
"index",
"gsName",
"fwDept",
"className",
"allCount",
"wxCount",
"khcCount",
"sbdSbCount",
"gyGzCount",
"dyGzCount",
"tqdzCount",
"tqbxCount",
"dyxlCount",
"bqxCount",
"jllCount",
"bhxCount",
"qftdCount"
];
function collectFaultDetails(input = {}) {
return {
type: "report-artifact",
report_name: "fault-details-report",
period: input.period || "",
columns: DETAIL_COLUMNS,
rows: [],
sections: [
{
name: "summary-sheet",
columns: SUMMARY_COLUMNS,
rows: []
}
],
status: "ok",
partial_reasons: []
};
}
module.exports = {
DETAIL_COLUMNS,
SUMMARY_COLUMNS,
collectFaultDetails
};

View File

@@ -0,0 +1,84 @@
---
name: jiayuguan-meter-outage
description: Use when the user wants to monitor Jiayuguan meter-outage events, compare pending cases against historical logs, and evaluate downstream alert or auto-processing flows.
version: 0.1.0
author: sgclaw
tags:
- jiayuguan
- meter-outage
- monitoring
- alert
- dispatch
---
# Jiayuguan Meter Outage
## When to Use
- The user asks to monitor Jiayuguan meter-outage events.
- The user asks to compare current pending cases against historical monitor or dispose logs.
- The task needs one snapshot assembled from outage events plus related service-order state enrichment.
- The task needs to detect newly pending outage cases before downstream audio reminder or auto-processing.
Do not use this skill for:
- generic outage browsing without snapshot output
- arbitrary browser probing before the packaged collector
- hiding local-service or cross-system failures behind `no pending cases`
- treating incomplete outage/event/order joins as fully complete
## Workflow
1. Open or attach to the outage configuration page.
2. Read scheduled workflow rule scripts from desk source-of-truth (`D:/desk/智能体资料/大四区报告监测项/户表失电-嘉峪关_业务监测配置.txt`, `D:/desk/智能体资料/大四区报告监测项/户表失电-嘉峪关_自动处理配置.txt`) to understand outage-event, service-order, and auto-processing semantics.
3. Collect current outage events from the outage source.
4. Collect related service-order states from the work-order source.
5. Normalize the result into `pending`, `audit`, `processed`, `pending_ids`, and `new_pending_ids`.
6. Return the monitor snapshot before downstream audio, reminder, or dispose-log side effects.
7. If log comparison, enrichment, or alert-side effects fail after snapshot assembly, keep the snapshot and mark the result as `partial`.
## Runtime Contract
- Keep outage-event collection and service-order-state collection separate, then join them into one snapshot.
- Prefer the packaged collector before generic probing or manual page inspection.
- The scene page `assets/scene-snapshot/index.html` is configuration-only; treat it as class-list/config context, not workflow execution proof.
- Treat historical monitor and dispose logs as comparison context, not the upstream business source of truth.
- BrowserAction execution, platform session context, marketing token context, and localhost `MonitorServices` dependencies are part of runtime truth and must be surfaced explicitly.
- Audio remind and auto-dispatch outcomes are downstream effects; they do not redefine whether snapshot collection succeeded.
## Partial-Failure Rule
- If outage events are collected but service-order enrichment fails, report `partial`.
- If outage events and order states are collected but monitor-log or dispose-log parsing/comparison fails, report `partial`.
- If outage rows do not provide enough crosswalk information to reconcile one `consNo` cleanly against dispose-side `eventId` values, keep the snapshot and report `partial`.
- If snapshot construction succeeds but downstream audio or auto-processing side effects fail, keep the snapshot and report `partial`.
- Never collapse collection, login, interception, permission, or parse failures into empty lists.
## Export Artifact
```json
{
"type": "monitor-snapshot",
"scene": "jiayuguan-meter-outage",
"time": "",
"pending": 0,
"audit": 0,
"processed": 0,
"pending_ids": [],
"new_pending_ids": [],
"status": "success",
"partial_reasons": []
}
```
## Output
Return:
- city
- monitoring time or queue window
- pending, audit, processed counts
- pending ids and new pending ids
- complete or partial status
- missing outage, order-state, log, audio, or auto-processing areas
- Export Artifact

View File

@@ -0,0 +1,16 @@
[skill]
name = "jiayuguan-meter-outage"
description = "Use when the user wants to monitor Jiayuguan meter-outage events through desk rule-script workflow semantics, compare pending cases against logs, and evaluate downstream processing states."
version = "0.1.0"
author = "sgclaw"
tags = ["jiayuguan", "meter-outage", "monitoring", "alert", "dispatch"]
prompts = [
"For Jiayuguan outage monitoring, call jiayuguan-meter-outage.collect_outage_events first and treat desk rule scripts as workflow source-of-truth while `assets/scene-snapshot/index.html` remains config-only.",
]
[[tools]]
name = "collect_outage_events"
description = "Collect outage-event and service-order states and prepare the monitor snapshot shell from the source outage page."
kind = "browser_script"
command = "scripts/collect_outage_events.js"

View File

@@ -0,0 +1,265 @@
let callbackName = "callBack_hubiaoshidianjiayuguan";
let callbackName1 = "callBack_hubiaoshidianjiayuguan01";
let list = []
let alist = []
let alist1 = []
let idList = []
let pendingList = []
window[callbackName] = function (targeturl, actionurl, responseTxt) {
try {
const res = JSON.parse(responseTxt
.replace(/%7B/g, '{')
.replace(/%27/g, "'")
.replace(/%7D/g, "}")
.replace(/'/g, '"')
.replace("}/", "}")
);
console.log(res);
if (res.msg && res.msg == "success") {
list = res.page.list;
// list =[{
// "eventId": "5e8dcb5c-7add-4812-98e7-46ae3714ebc7",
// "outageType": "2",
// "outageTypeName": "单户停电",
// "ifOutage": "停电",
// "ifOutageName": null,
// "eventStatus": "处理中",
// "offTime": "2026-01-08 19:02:33",
// "onTime": null,
// "consNo": "6261250673482",
// "consType": "0",
// "consTypeName": "非重要用户",
// "consName": "樊微",
// "consAddr": "甘肃省嘉峪关市峪泉镇安远沟村机场路安远沟村六组",
// "phoneNum": "13014182867",
// "eqPsrId": "7036a473218a54727b73001fcd0173703307ad1ea8",
// "eqPsrName": "安远沟六组养殖区变",
// "eqPsrType": "0110",
// "eqPsrTypeName": "柱上变压器",
// "feederPsrId": "27DKX-6541",
// "feederPsrName": "110kV雄关变电站126雄开一回线",
// "startStationId": "6EFB88B2-07C8-43FB-A288-569A21BDBD93-02444",
// "startStationName": "110kV雄关变电站",
// "cityId": "8a5470c55ccb982b015d8840bfcc2a93",
// "cityName": "嘉峪关",
// "maintenanceOrgId": "8a5470c55ccb982b015d884190fb2a94",
// "maintenanceOrgName": null,
// "maintenanceGroupId": "72d5b4c09a034c60aeca73e0fbba5dd6",
// "maintenanceGroupName": "配网无人机巡检班(含运维业务)",
// "orderId": "ZDFW20260108210159684446849",
// "orderNo": "ZDFW20260108210159684446849",
// "orderStatus": "02",
// "sourceMarking": "1",
// "sourceMarkingName": "用采上送",
// "sourceReply": null,
// "sourceReplyName": null,
// "batchNumber": "5e8dcb5c-7add-4812-98e7-46ae3714ebc7",
// "createTime": "2026-01-08 19:12:33",
// "upTime": "2026-01-08 19:12:33",
// "qxdbh": null,
// "powerTypeOne": null,
// "powerTypeTwo": null,
// "showFlag": "1",
// "orgshortName": null,
// "id": null
// }]
list.forEach(item => {
// 户号
idList.push(item.consNo)
})
} else {
//执行 下一队列方法
_this.processQueue();
}
} catch (e) {
let audioText = ""
audioFC(audioText)
}
window[callbackName1] = function (targeturl, actionurl, responseTxt) {
try {
const res = JSON.parse(responseTxt
.replace(/%7B/g, '{')
.replace(/%27/g, "'")
.replace(/%7D/g, "}")
.replace(/'/g, '"')
.replace("}/", "}")
);
if (res.msg == "success") {
res.page.list.forEach((item) => {
if (item.gdztmc == "待审核") {
alist.push(item);
}
if (item.gdztmc == "已归档") {
alist1.push(item);
}
});
}
} catch (e) {
console.log(e);
}
let obj = {
time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), //监测时间
type: "户表失电-嘉峪关", //监测工单类型
pending: list.length, //待处理数字
pendingList: JSON.stringify(idList), //待处理列表
audit: alist.length, //待审核
processed: alist1.length, //已处理
};
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/getMonitorLog",
method: "POST",
data: JSON.stringify({ type: "户表失电-嘉峪关" }), //必须是字符串格式
}).then((res2) => {
if (res2.status == 200) {
let index = 0
if (res2.data.length < 1 && list.length > 0 && _this.queueObj.voiceRemind == '1') {
let audioText = "您有户表失电工单,请及时处理"
audioFC(audioText)
}
if (res2.data.length > 0) {
let logList = []
if (res2.data[0].pendingList && res2.data[0].pendingList?.indexOf('解析异常') == -1) {
logList = JSON.parse(res2.data[0].pendingList)
}
if (!Array.isArray(logList)) {
logList = JSON.parse(logList)
}
try {
idList.forEach(x => {
index = logList.indexOf(x)
if (index == -1) {
throw new Error("停止循环")
}
})
} catch (e) {
console.log("跳出循环");
}
}
if (index == -1 && _this.queueObj.voiceRemind == '1') {
let audioText = "您有户表失电工单,请及时处理"
audioFC(audioText)
}
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setMonitorData",
method: "POST",
data: JSON.stringify(obj)
})
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setMonitorLog",
method: "POST",
data: JSON.stringify(obj),
})
}
});
// 将派过的工单去重
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/getDisposeLog",
method: "POST",
data: JSON.stringify({ type: "户表失电-嘉峪关" }), //必须是字符串格式
}).then((res) => {
if (res.status == 200) {
let resList = []
try {
res.data.forEach(item => {
if (item.orderID != '') {
let logList = JSON.parse(item.orderID)
if (!Array.isArray(logList)) {
logList = JSON.parse(logList)
if (!Array.isArray(logList)) {
logList = JSON.parse(logList)
}
}
logList.forEach(i => {
resList.push(i.id)
})
}
})
resList = Array.from(new Set(resList))
list.forEach(y => {
if (resList.indexOf(y.eventId) == -1) {
pendingList.push(y)
}
})
} catch (error) {
obj.pendingList = "解析异常" + error
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setMonitorLog",
method: "POST",
data: JSON.stringify(obj)
});
}
if (pendingList.length > 0) {
//执行 下一队列方法
_this.queueObj.pendingList = pendingList;
_this.autoTask();
} else {
//执行 下一队列方法
_this.processQueue();
}
}
})
};
let slsj = `${mac.moment().format("YYYY-MM-DD")}+00:00:00,${mac.moment().format("YYYY-MM-DD")}+23:59:59`;
const requestParam = `page=1&createTime=${slsj}&orgNo=${_this.orgID}&limit=200`;
// sgBrowerserJsAjax2(
// "window." + callbackName1,
// "http://21.77.244.194:18890/mainSystem/#/login",
// `http://21.77.244.194:18890/gdgl/active/service/order/list?` + requestParam,
// "GET",
// `Content-Type:application/json;userId:${_this.userID};Access-Control-Allow-Credentials:true;Access-Control-Allow-Methods: GET, POST, OPTIONS,PUT,DELETE,OPTION;Access-Control-Allow-Origin:*`,
// ""
// );
BrowserAction(
'sgBrowerserJsAjax2',
"window." + callbackName1,
"http://21.77.244.194:18890/mainSystem/#/login",
`http://21.77.244.194:18890/gdgl/active/service/order/list?` + requestParam,
"GET",
`Content-Type:application/json;userId:${_this.userID};Access-Control-Allow-Credentials:true;Access-Control-Allow-Methods: GET, POST, OPTIONS,PUT,DELETE,OPTION;Access-Control-Allow-Origin:*`,
""
)
};
let slsj = `${mac.moment(new Date().setDate(new Date().getDate() - 2)).format("YYYY-MM-DD")}+00:00:00,${mac.moment().format("YYYY-MM-DD")}+23:59:59`;
let requestParam = `page=1&sortFiled=off_time&ifAsc=false&outageType=2&flag=0&eventStatus=1&offTime=${slsj}&orgNo=${_this.orgID}&limit=100`;
// sgBrowerserJsAjax2(
// "window." + callbackName,
// "http://21.77.244.194:18890/mainSystem/#/login",
// `http://21.77.244.194:18890/outage/dhsd/dhsdList?` + requestParam,
// "POST",
// `Content-Type:application/json;userId:${_this.userID};Access-Control-Allow-Credentials:true;Access-Control-Allow-Methods: GET, POST, OPTIONS,PUT,DELETE,OPTION;Access-Control-Allow-Origin:*`,
// ""
// );
BrowserAction(
'sgBrowerserJsAjax2',
"window." + callbackName,
"http://21.77.244.194:18890/mainSystem/#/login",
`http://21.77.244.194:18890/outage/dhsd/dhsdList?` + requestParam,
"POST",
`Content-Type:application/json;userId:${_this.userID};Access-Control-Allow-Credentials:true;Access-Control-Allow-Methods: GET, POST, OPTIONS,PUT,DELETE,OPTION;Access-Control-Allow-Origin:*`,
""
)
function audioFC(text) {
mac.audioPlay(text).then(response => {
if (response.status == 200) {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: text, time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "成功" })
})
} else {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: text, time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "失败" })
})
}
}).catch(err => {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: text, time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "异常" })
})
})
}

View File

@@ -0,0 +1,470 @@
console.log("户表失电嘉峪关进入自动派单==")
let a = JSON.parse(localStorage.getItem("markYXObj"))
let loginUserInfo = JSON.parse(a.sessionStorage.loginUserInfo)
let tokens = JSON.parse(localStorage["markYXObj"])?.sessionStorage?.token;
var HBSD = [];
var hbsdList = [];
var hbsdClassList = [];
var consName = null;
let tel = []
let ecssMgtOrgCode = ''
let objData = {
time: mac.moment().format("YYYY-MM-DD HH:mm:ss"),
type: "户表失电-嘉峪关",
pending: 0,
pendingList: "",
audit: 0,
processed: 0
};
let successList = [];
let flag = false;
let callbackName1 = "callBack_hubiaoshidianjiayuguanpaidan01";
//将同一个配变eqPsrName下的工单合并一块
obj.pendingList.filter((x) => HBSD.findIndex((y) => y.id == x.eventId) == -1).forEach((x) => {
let index = hbsdList.findIndex((y) => y.eqPsrName == x.eqPsrName);
if (index == -1) {
hbsdList.push({
eqPsrName: x.eqPsrName,
eventId: x.eventId,
obj: x,
gdbh: "",
});
} else {
hbsdList[index].eventId += "," + x.eventId;
// hbsdList 是最后整合出来需要派发的工单
}
HBSD.push({ id: x.eventId });
});
// 查询大四区班组
if (hbsdList.length > 0) {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/getClassList",
method: "POST",
data: JSON.stringify({ type: objData.type }),
}).then((res) => {
//括号里的res是服务器返回的数据
if (res.status == 200) {
res.data.forEach((item) => {
let arr = [];
if (item.scope != "") {
item.scope = item.scope.replace(/\s*/g, "");
arr.push(item.scope.split("、"));
item.scope = [].concat.apply([], arr);
} else {
item.scope = [];
}
});
hbsdClassList = res.data;
console.log(hbsdClassList);
getTeamData(hbsdList)
}
});
}
// 拿着大四区工单中的用户编号去营销系统查询,查到哪个班组就给哪个班组派发
function getTeamData(hbsdList) {
console.log(hbsdList)
const requestParam1 = {
mgtOrgCode: loginUserInfo.orgNo,
mgtOrgCodeList: ['62411', '624110105', '624110106', '6241102', '624110101', '6241101', '624110102', '624110201'],
mgtOrgName: "国网嘉峪关供电公司",
orgFlag: true,
pageNo: 1,
pageSize: 10,
searchMode: "all",
someLink: hbsdList[0].obj.consNo
}
BrowserAction(
'sgBrowerserJsAjax2',
"window.callbackName_yx360_hbsd",
"http://yx.gs.sgcc.com.cn",
`http://yxgateway.gs.sgcc.com.cn/emss-custmgtf-custview-front//member/custElecEP/queryEleCust`,
"POST",
`Content-Type:application/json;auth_token:${tokens}`,
window.encrypt_old(requestParam1)
)
window['callbackName_yx360_hbsd'] = (targeturl, actionurl, responseTxt) => {
try {
const resData = decodeURI(responseTxt).replace(/'/g, '"').replace("}/", "}");
let jsonData = resData.slice(0, resData.lastIndexOf("]") + 1);
jsonData += "}";
let res = eval("(" + jsonData + ")");
if (res.code == "00000") {
let data = res.data;
if (data.length > 0) {
// 624110102一班 624110106二班 624110105城郊
ecssMgtOrgCode = data[0].eccs[0].mgtOrgCode;
getgdbh();
} else {
setTimeout(() => {
let audioText = `户号${hbsdList[0].obj.consNo}在营销系统中未查到相关信息,请手动派发相关工单!`;
audioFC(audioText);
}, 6000);
}
}
} catch (err) {
}
};
}
function getgdbh() {
let callbackName = "callBack_hubiaoshidianjiayuguanpaidan";
// 派单前获取工单编号,用来调接口时作为参数传递
window[callbackName] = function (targeturl, actionurl, responseTxt) {
try {
const resData = JSON.parse(responseTxt
.replace(/%7B/g, '{')
.replace(/%27/g, "'")
.replace(/%7D/g, "}")
.replace(/'/g, '"')
.replace("}/", "}")
);
if (resData.code == 0) {
if (hbsdList.length > 0) {
hbsdList[0].gdbh = resData.gdbh;
}
// 自动派单接口
zdpdFc();
}
} catch (x) {
setTimeout(() => {
let audioText = "户表失电工单编号获取异常,请手动派单";
audioFC(audioText)
}, 6000);
let type = "户表失电-嘉峪关";
let orderID = "";
let time = mac.moment().format("YYYY-MM-DD HH:mm:ss");
let name = "";
let state = "工单编号获取异常" + x;
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setDisposeLog",
method: "POST",
data: JSON.stringify({ type, orderID, name, time, state }), //必须是字符串格式
}).then(re => {
if (re.status == 200) {
mac.exeTQueue();
}
})
}
};
BrowserAction(
'sgBrowerserJsAjax2',
"window." + callbackName,
`http://21.77.244.194:18890/#/webview/1543953229739896`,
`http://21.77.244.194:18890/gdgl/zdfw/tgforderzdfw/gdbh`,
"GET",
`Content-Type:application/json;userId:${_this.userID};Access-Control-Allow-Credentials:true;Access-Control-Allow-Methods: GET, POST, OPTIONS,PUT,DELETE,OPTION;Access-Control-Allow-Origin:*`,
""
)
}
// 派单接口
function zdpdFc() {
window[callbackName1] = function (targeturl, actionurl, responseTxt) {
try {
const resData = JSON.parse(responseTxt.replace(/%7B/g, '{').replace(/%27/g, "'").replace(/%7D/g, "}").replace(/'/g, '"').replace("}/", "}"));
if (resData.code == 0) {
setTimeout(() => {
let audioText = "户表失电工单自动派单成功";
audioFC(audioText)
}, 6000);
// let type = "户表失电";
// let orderID = JSON.stringify(HBSD);
// let time = mac.moment().format("YYYY-MM-DD HH:mm:ss");
// let name = consName;
// let state = "成功";
// // i国网
// const request = {
// phoneList: [].concat.apply([], tel),
// content: "【业数融合一平台】您有户表失电工单,请及时处理!", //短信内容
// };
// if (request.phoneList.length > 0) {
// msgFC(request)
// }
// // 呼叫接口
// setTimeout(() => {
// const params = {
// taskName: "户表失电",
// phone: [].concat.apply([], tel),
// content: "您有户表失电工单,请尽快接单处理",
// name: "户表失电",
// };
// if (params.phone.length > 0) {
// mac.callOutLogin(params);
// }
// }, 1000);
let type = "户表失电-嘉峪关";
let orderID = JSON.stringify(successList);
let time = mac.moment().format("YYYY-MM-DD HH:mm:ss");
let name = consName;
let state = '成功';
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setDisposeLog",
method: "POST",
data: JSON.stringify({ type, orderID, name, time, state, }), //必须是字符串格式
}).then(re => {
if (re.status == 200) {
mac.exeTQueue();
}
})
} else {
let type = "户表失电-嘉峪关";
setTimeout(() => {
let audioText = "户表失电工单自动派单失败,请及时处理"
audioFC(audioText)
}, 6000);
let orderID = JSON.stringify(successList);
let time = mac.moment().format("YYYY-MM-DD HH:mm:ss");
let name = consName;
let state = "失败";
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setDisposeLog",
method: "POST",
data: JSON.stringify({ type, orderID, name, time, state, }), //必须是字符串格式
}).then(re => {
if (re.status == 200) {
mac.exeTQueue();
}
})
}
} catch (x) {
setTimeout(() => {
let audioText = "户表失电工单自动派单异常,请及时处理";
audioFC(audioText)
}, 6000);
let type = "户表失电-嘉峪关";
let orderID = JSON.stringify(successList);
let time = mac.moment().format("YYYY-MM-DD HH:mm:ss");
let name = consName;
let state = "异常" + x;
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setDisposeLog",
method: "POST",
data: JSON.stringify({ type, orderID, name, time, state, }), //必须是字符串格式
}).then(re => {
if (re.status == 200) {
mac.exeTQueue();
}
})
}
};
if (hbsdList.length > 0) {
consName = hbsdList[0].obj.consName;
let t = mac.moment().format("YYYY-MM-DD") + " " + "23:00:00";
// 区县级参数
var requestParam = {
comment: "",
processBusinessKey: hbsdList[0].gdbh, //工单编号id
processDefKey: "gdfw_zdfwgd_202305", //固定
starter: _this.userID, //登录账号
variables: {
data: {
address: hbsdList[0].obj.consAddr, //联系地址
content: "【自动派单】单户失电", //受理内容
eventId: hbsdList[0].eventId, //每一项的eventId如有多项用隔开
eventType: "01",
fdly: "05", //复电来源固定
flag: "01",
gdbh: hbsdList[0].gdbh, //工单编号
hfbz: "01", // 回复标志固定
mobile: hbsdList[0].obj.phoneNum, //联系电话
slyj: "请及时处理", //受理意见
yhbh: hbsdList[0].obj.consNo, //用户编号
yhmc: hbsdList[0].obj.consName, //用户名称
yqfksj: t, //要求反馈时间
ywlb: "07", //业务类别 大概率固定
},
maintGroup: "", //班组code
maintGroupName: "", //班组名称
maintOrg: "", //接单单位Code
maintOrgName: "", //接单单位
personType: "", //班组
receivePerson: "", //接单人工号
receivePersonName: "", //接单人
receiveType: true, //是否班组接单
sendType: "city",
},
};
// var week = "日一二三四五六".charAt(new Date().getDay());
function getTodayChineseWeekday() {
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
const today = new Date();
return weekdays[today.getDay()]
}
function isTodayIn(weekdayStr) {
const today = getTodayChineseWeekday();
const allowerDays = weekdayStr.split(',').map(day => day.trim());
return allowerDays.includes(today)
}
// 时间比对范围08:30 ~ 17:50 范围判断方法
function isInTimeRange() {
// 获取当前时间的HHmm格式0830、1750
const currentTime = mac.moment().format("HHmm");
// 定义边界范围
const startTime = 830;
const endTime = 1750;
// 核心逻辑判断当前时间在08:30 ~ 17:50 范围
const currentNum = Number(currentTime);
return currentNum >= startTime && currentNum <= endTime;
}
let assigned = false; // 标记是否已成功分配到班组
for (const item of hbsdClassList) {
// 判断是否满足派单条件:设置接单 + 今天在周期 + 时间在工作时段
if (item.isSetcTime === '1' && isTodayIn(item.cTime) && isInTimeRange()) {
if (ecssMgtOrgCode === '624110102' && item.orgId == "5c3512baaafd4d86b2683f49001b755d") {
// 一班
requestParam.variables.maintGroup = item.orgId;
requestParam.variables.maintGroupName = item.cName;
requestParam.variables.maintOrg = "77e0c17eac9a405492c393e70ffd8479";
requestParam.variables.maintOrgName = "市场部";
requestParam.variables.personType = "member";
requestParam.variables.receivePerson = item.pCode;
requestParam.variables.receivePersonName = item.pName;
console.log('进入一班===');
} else if (ecssMgtOrgCode === '624110106' && item.orgId == "e69661b8bc7945df838d9690ea126e6f") {
// 二班
requestParam.variables.maintGroup = item.orgId;
requestParam.variables.maintGroupName = item.cName;
requestParam.variables.maintOrg = "77e0c17eac9a405492c393e70ffd8479";
requestParam.variables.maintOrgName = "市场部";
requestParam.variables.personType = "member";
requestParam.variables.receivePerson = item.pCode;
requestParam.variables.receivePersonName = item.pName;
console.log('进入二班===');
} else if (ecssMgtOrgCode === '624110105' && item.orgId == "b9af53b58f594f4f9e8fc70bd4135833") {
// 城郊
requestParam.variables.maintGroup = item.orgId;
requestParam.variables.maintGroupName = item.cName;
requestParam.variables.maintOrg = "b57f5cb612864627807e7a603aa190c8";
requestParam.variables.maintOrgName = "配网管理部";
requestParam.variables.personType = "member";
requestParam.variables.receivePerson = item.pCode;
requestParam.variables.receivePersonName = item.pName;
console.log('进入城郊班===');
} else {
continue;
}
successList.push({ id: hbsdList[0].eventId });
autoTask_fun(requestParam);
assigned = true;
break;
}
// else {
// if (item.cName == '配网抢修班') {
// requestParam.variables.maintGroup = item.orgId;
// requestParam.variables.maintGroupName = item.cName;
// requestParam.variables.maintOrg = "b57f5cb612864627807e7a603aa190c8";
// requestParam.variables.maintOrgName = "配网管理部";
// requestParam.variables.personType = "bz";
// requestParam.variables.receivePerson = item.pCode || '';
// requestParam.variables.receivePersonName = item.pName || '';
// successList.push({ id: hbsdList[0].eventId });
// console.log('进入抢修班===');
// autoTask_fun(requestParam);
// }
//}
}
// 派单配网抢修班
if (!assigned) {
for (const item of hbsdClassList) {
if (item.cName == '配网抢修班') {
requestParam.variables.maintGroup = item.orgId;
requestParam.variables.maintGroupName = item.cName;
requestParam.variables.maintOrg = "b57f5cb612864627807e7a603aa190c8";
requestParam.variables.maintOrgName = "配网管理部";
requestParam.variables.personType = "bz";
requestParam.variables.receivePerson = item.pCode || '';
requestParam.variables.receivePersonName = item.pName || '';
successList.push({ id: hbsdList[0].eventId });
console.log('进入抢修班===');
autoTask_fun(requestParam);
}
}
// let flag = true;
// if (flag) {
// console.log('【日志】无班组满足派单条件,已派往抢修班');
// let type = "户表失电-嘉峪关";
// let orderID = JSON.stringify(successList);
// let time = mac.moment().format("YYYY-MM-DD HH:mm:ss");
// let name = "";
// let state = "被派到抢修班";
// mac.localHostAxjos({
// url: "http://localhost:13313/MonitorServices/setDisposeLog",
// method: "POST",
// data: JSON.stringify({
// type,
// orderID,
// name,
// time,
// state,
// }), //必须是字符串格式
// }).then(re => {
// if (re.status == 200) {
// mac.exeTQueue();
// }
// })
// }
}
}
// 自动播报
function audioFC(text) {
mac.audioPlay(text).then(response => {
if (response.status == 200) {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: text, time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "成功" })
})
} else {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: text, time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "失败" })
})
}
}).catch(err => {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setAudioPlayLog",
method: "POST",
data: JSON.stringify({ type: text, time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "异常" })
})
})
}
function autoTask_fun(requestParam) {
console.log('自动派单===========', requestParam)
BrowserAction(
'sgBrowerserJsAjax2',
"window." + callbackName1,
`http://21.77.244.194:18890/#/webview/1543953229739896`,
`http://21.77.244.194:18890/gdgl/active/service/order/saveAndSend`,
"POST",
`Content-Type:application/json;userId:${_this.userID};Access-Control-Allow-Credentials:true;Access-Control-Allow-Methods: GET, POST, OPTIONS,PUT,DELETE,OPTION;Access-Control-Allow-Origin:*`,
JSON.stringify(requestParam)
)
}
function msgFC(request) {
mac.sendMessages(request).then(res4 => {
if (res4.status == 200) {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setSendMessageLog",
method: "POST",
data: JSON.stringify({ type: request.content, time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "成功" })
})
} else {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setSendMessageLog",
method: "POST",
data: JSON.stringify({ type: request.content, time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "失败" })
})
}
}).catch(err => {
mac.localHostAxjos({
url: "http://localhost:13313/MonitorServices/setSendMessageLog",
method: "POST",
data: JSON.stringify({ type: request.content, time: mac.moment().format("YYYY-MM-DD HH:mm:ss"), status: "异常" })
})
})
}
}

View File

@@ -0,0 +1,388 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="shortcut icon" href="./images/95598/logo.gif" type="image/x-icon" />
<link rel="stylesheet" href="./css/elementui.css" />
<link rel="stylesheet" href="./css/szhfn.css" />
<title>接单班组信息</title>
<script src="./js/jquery.js"></script>
<script src="./js/vue.js"></script>
<script src="./js/elementui.js"></script>
<script src="./js/moment.js"></script>
<script src="./js/dpage.min.js"></script>
<script src="./js/axios.js"></script>
<script src="/a_js/YPTAPI.js"></script>
</head>
<body>
<div id="app">
<el-table :data="allclassList" border height="945px">
<el-table-column label="接单单位名称" align="center" property="cName"></el-table-column>
<el-table-column label="接单时间" align="center" property="cTime"></el-table-column>
<el-table-column label="接单人姓名" align="center" property="pName"></el-table-column>
<el-table-column label="接单人工号" align="center" property="pCode"></el-table-column>
<el-table-column label="接单人手机号" align="center" property="wName"></el-table-column>
<el-table-column property="state" label="操作" width="160" align="center">
<template slot-scope="scope">
<el-button size="mini" type="primary" @click="viewBtnFC(scope.row)">查看</el-button>
<el-button size="mini" type="warning" @click="editBtnFC(scope.row)">修改</el-button>
</template>
</el-table-column>
</el-table>
<!-- 修改 -->
<el-dialog :visible.sync="editStatus" title="修改" :append-to-body="true" :before-close="edithandleClose">
<el-form :model="editClassForm" ref="editClassForm" label-width="110px">
<el-form-item label="接单单位名称" prop="cName">
<el-input v-model="editClassForm.cName" placeholder="请输入接单单位名称" disabled></el-input>
</el-form-item>
<el-form-item label="是否接单" prop="isSetcTime">
<el-switch is-range v-model="editClassForm.isSetcTime" activate-color="#13ce66" inactive-color="#ff4949"
@change="isSetcTimeOnChange"></el-switch>
</el-form-item>
<!-- <el-form-item label="接单时间" prop="cTime" v-show="editClassForm.isSetcTime">
<el-time-picker
is-range
v-model="editClassForm.cTime"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="HH:ss:mm"
placeholder="选择时间范围"
@change="cTimeOnChange"
></el-time-picker>
</el-form-item> -->
<el-form-item label="接单时间" prop="cTime" v-show="editClassForm.isSetcTime">
<!-- <el-input v-model="editClassForm.cTime" placeholder="请输入当前班组接单时间,如果有多个请用、隔开(例:周一、周六、周日)"></el-input>
-->
<el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange">全选
</el-checkbox>
<el-checkbox-group v-model="selectedDays">
<el-checkbox v-for="day in weekOptions" :key="day.value" :label="day.value">{{ day.label }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="接单人姓名" prop="pName">
<el-input v-model="editClassForm.pName" placeholder="请输入接单人姓名"></el-input>
</el-form-item>
<el-form-item label="接单人账号" prop="pCode">
<el-input v-model="editClassForm.pCode" placeholder="请输入接单人账号"></el-input>
</el-form-item>
<el-form-item label="接单人手机号" prop="wName">
<el-input v-model="editClassForm.wName" placeholder="请输入接单人手机号"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editClassFC()" size="small" style="background: #018c87; color: #fff">确定</el-button>
</span>
</el-dialog>
<!-- 查看 -->
<el-dialog :visible.sync="viewStatus" title="查看" :append-to-body="true" :before-close="viewhandleClose">
<el-form :model="editClassForm" ref="editClassForm" label-width="110px">
<el-form-item label="接单单位名称" prop="cName">
<el-input v-model="editClassForm.cName" placeholder="请输入接单单位名称" disabled></el-input>
</el-form-item>
<el-form-item label="设置接单时间" prop="isSetcTime">
<el-switch is-range v-model="editClassForm.isSetcTime" activate-color="#13ce66" inactive-color="#ff4949"
disabled></el-switch>
</el-form-item>
<el-form-item label="接单时间" prop="cTime" v-show="editClassForm.isSetcTime">
<!-- <el-input v-model="editClassForm.cTime" placeholder="请输入当前班组接单时间,如果有多个请用、隔开(例:周一、周六、周日)"></el-input>
-->
<el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange" disabled>全选
</el-checkbox>
<el-checkbox-group v-model="selectedDays" disabled>
<el-checkbox v-for="day in weekOptions" :key="day.value" :label="day.value">{{ day.label }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="接单人姓名" prop="pName">
<el-input v-model="editClassForm.pName" placeholder="请输入接单人姓名" disabled></el-input>
</el-form-item>
<el-form-item label="接单人账号" prop="pCode">
<el-input v-model="editClassForm.pCode" placeholder="请输入接单人账号" disabled></el-input>
</el-form-item>
<el-form-item label="接单人手机号" prop="wName">
<el-input v-model="editClassForm.wName" placeholder="请输入接单人手机号" disabled></el-input>
</el-form-item>
</el-form>
</el-dialog>
</div>
</body>
<script>
var mac = new Vue({
el: "#app",
data() {
return {
// 修改接单班组
editClassForm: {
orgId: "",
cName: "",
isSetcTime: false,
cTime: "",
scope: "",
pName: "",
pCode: "",
type: "",
wName: "",
},
classList: [],
allclassList: [],
disabled: true,
editStatus: false,
viewStatus: false,
labelText: "",
editlabelText: "",
placeholder: "",
formInfo: {},
checkAll: false,
isIndeterminate: false,
selectedDays: [],
weekOptions: [
{ label: '周一', value: '周一' },
{ label: '周二', value: '周二' },
{ label: '周三', value: '周三' },
{ label: '周四', value: '周四' },
{ label: '周五', value: '周五' },
{ label: '周六', value: '周六' },
{ label: '周日', value: '周日' }
]
};
},
watch: {
selectedDays(newVal) {
const len = this.weekOptions.length;
if (newVal.length === 0) {
this.checkAll = false;
this.isIndeterminate = false;
} else if (newVal.lengtht === len) {
this.checkAll = true;
this.isIndeterminate = false;
} else {
this.checkAll = false;
this.isIndeterminate = true;
}
this.editClassForm.cTime = newVal.join(',')
}
},
mounted() {
this.setStorage(localStorage.tGfUser)
},
methods: {
setStorage(obj) {
// console.log(obj);
const mac = this;
sessionStorage.setItem("tGfUser", obj);
const tGfUser = JSON.parse(sessionStorage.getItem("tGfUser"));
mac.formInfo = {
cityCode: tGfUser.orgNo, //机构码
cityName: tGfUser.orgName, //机构名称 比如 国网嘉峪关供电公司
userNameIV: tGfUser.name, //大四区登录的人名
userCodeIV: tGfUser.id, //大四区登录账号
};
mac.zdpdFc({ name: "户表失电-嘉峪关" });
},
// 自动派单配置图标点击弹出配置弹出框
zdpdFc(x) {
// 区县级表头名称
if (x.name == "95598抢修") {
this.labelText = "当前接单单位管辖的所有地址名称";
this.editlabelText = "地址名称";
this.placeholder = "请输入地址名称";
} else {
this.labelText = "当前接单单位管辖的所有配变名称";
this.editlabelText = "配变名称";
this.placeholder = "请输入配变名称";
}
this.editClassForm.type = x.name;
// this.getUrl(x.name);
this.gethngdsList(x.name)
},
// 抓取浏览器地址
// getUrl() {
// window.getAllUrlCallBack = (urls) => {
// var urlArr = urls.split(";");
// let hasPage = false;
// let hasPage1 = false;
// let home = null;
// urlArr.forEach((item) => {
// if (item.indexOf("http://21.77.244.194:18890/") !== -1) {
// hasPage = true;
// home = item;
// }
// });
// if (hasPage) {
// mac.gethngdsList("户表失电", home);
// }
// };
// dpage.dPageGetUrl(true, "window.getAllUrlCallBack");
// },
// 获取大IV中班组信息
gethngdsList(name) {
const mac = this;
axios.post(`http://21.77.244.194:18890/system/organization/queryOrgBylevel/${mac.formInfo.cityCode}/bz`, '', {
baseurl: 'http://21.77.244.194:18890/#/webview/1543953229739896',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json',
'Userid': `${mac.formInfo.userCodeIV}`
}
}).then(res => {
if (res.status == '200' && res.data.msg == 'success' && res.data.orgs.length) {
let list = res.data.orgs.forEach((item) => {
mac.classList.push({
orgId: item.orgId,
type: "",
cName: item.orgName,
scope: "",
pName: "",
pCode: "",
wName: "",
});
});
console.log(mac.classList)
mac.getClassList(name);
mac.$message({
message: "接单班组信息获取成功!",
type: "success",
});
}
}
).catch(error => {
mac.$message({
message: "接单班组信息获取失败!",
type: "error",
});
console.error(error);
}
)
},
// 查询接单班组
getClassList(name) {
$.ajax({
url: "http://localhost:13313/MonitorServices/getClassList",
type: "POST",
dataType: "json",
crossDomain: true,
data: JSON.stringify({ type: name }),
contentType: "application/json", //指定中容格式
success: (res) => {
//括号里的data是服务器返回的数据
if (res.status == 200) {
if (res.data.length > 0) {
res.data.forEach((item) => {
item.status = false; //按钮状态
});
mac.allclassList = res.data;
} else {
let arrList = [];
mac.classList.forEach((item) => {
if (!arrList.some((e) => e.orgId == item.orgId)) {
arrList.push(item);
}
});
arrList.forEach((x) => {
x.type = name;
mac.setClassList(x);
});
setTimeout(() => {
this.getClassList(name);
}, 1000);
}
}
},
});
},
// 存储接单班组
setClassList(val) {
$.ajax({
url: "http://localhost:13313/MonitorServices/setClassList",
type: "POST",
dataType: "json",
crossDomain: true,
data: JSON.stringify(val), //必须是字符串格式
contentType: "application/json",
success: (res) => { },
});
},
handleCheckAllChange(val) {
this.selectedDays = val ? this.weekOptions.map(item => item.value) : [];
this.isIndeterminate = false;
},
// 修改
editBtnFC(e) {
this.editStatus = true;
e.isSetcTime = e.isSetcTime == "1" ? true : false;
this.selectedDays = e.cTime == '' || e.cTime == null ? [] : e.cTime.split(',');
this.editClassForm = JSON.parse(JSON.stringify(e));
// 时间格式化
if (e.cTime && typeof e.cTime === "string" && e.cTime.includes("~")) {
const timeParts = e.cTime.split("~");
this.editClassForm.cTime = [moment().format("YYYY-MM-DD") + " " + timeParts[0], moment().format("YYYY-MM-DD") + " " + timeParts[1]];
}
},
// 查看
viewBtnFC(e) {
this.viewStatus = true;
e.isSetcTime = e.isSetcTime == "1" ? true : false;
this.selectedDays = e.cTime == '' || e.cTime == null ? [] : e.cTime.split(',');
this.editClassForm = JSON.parse(JSON.stringify(e));
// 时间格式化
if (e.cTime && typeof e.cTime === "string" && e.cTime.includes("~")) {
const timeParts = e.cTime.split("~");
this.editClassForm.cTime = [moment().format("YYYY-MM-DD") + " " + timeParts[0], moment().format("YYYY-MM-DD") + " " + timeParts[1]];
}
},
edithandleClose() {
this.editStatus = false;
},
viewhandleClose() {
this.viewStatus = false;
},
isSetcTimeOnChange(e) {
this.editClassForm.isSetcTime = e;
if (e == false) {
this.editClassForm.cTime = "";
}
},
// 修改后保存按钮
editClassFC() {
const formData = { ...this.editClassForm };
// if (Array.isArray(formData.cTime)) {
// formData.cTime = moment(formData.cTime[0]).format("HH:mm:ss") + "~" + moment(formData.cTime[1]).format("HH:mm:ss");
// }
console.log(this.selectedDays)
$.ajax({
// url: "http://localhost:13313/MonitorServices/editClassList",
url: "http://localhost:13313/MonitorServices/editClassList",
type: "POST",
dataType: "json",
crossDomain: true,
data: JSON.stringify(formData), //必须是字符串格式
contentType: "application/json",
success: (res) => {
if (res.status == 200) {
this.$message({
type: "success",
message: "修改成功!",
});
this.editStatus = false;
mac.getClassList(this.editClassForm.type);
}
},
});
},
},
});
</script>
</html>

View File

@@ -0,0 +1,52 @@
# Collection Flow
## Source
- Source page: `D:\desk\智能体资料\大四区报告监测项\户表失电-嘉峪关\index.html`
- Rule assets (scheduled workflow source-of-truth):
- `D:/desk/智能体资料/大四区报告监测项/户表失电-嘉峪关_业务监测配置.txt`
- `D:/desk/智能体资料/大四区报告监测项/户表失电-嘉峪关_自动处理配置.txt`
## Scope
The page focuses on class-list and auto-processing configuration (`assets/scene-snapshot/index.html` is configuration-only). The monitoring package combines outage-event collection, service-order enrichment, historical pending comparison, and downstream auto-processing context from the desk rule source-of-truth scripts.
## Inputs
- Current platform session and user context
- Current org context from the outage platform
- Marketing-system token context used by downstream enrichment flows
- Browser-visible class-list configuration
- Local monitor-log and dispose-log context from localhost services
- The current outage and service-order query windows
## First-pass Collection Steps
1. Open or attach to the outage configuration page.
2. Verify required platform-session, org, and token context is available.
3. Read the scheduled workflow rule scripts from desk source-of-truth to understand outage-event, service-order, historical comparison, and auto-processing semantics.
4. Trigger the deterministic outage collector through BrowserAction / browser-side request execution.
5. Query current outage events from the outage source.
6. Query related service-order states from the active-service order source.
7. Normalize source results into `pending`, `audit`, `processed`, `pending_ids`, and `new_pending_ids` using packaged runtime collector logic while preserving the `consNo` vs `eventId` identity mismatch note.
8. Compare with local monitor/dispose logs only as downstream context.
9. Return a structured monitor snapshot before downstream audio reminder or auto-processing side effects.
## Dependencies
- BrowserAction or equivalent browser-side request execution
- Platform-visible session, org, and user context
- Optional marketing token context for downstream enrichment / dispatch flows
- Upstream outage endpoints under `http://21.77.244.194:18890/outage/dhsd/*`
- Upstream service-order endpoints under `http://21.77.244.194:18890/gdgl/active/service/order/*`
- Localhost services under `http://localhost:13313/MonitorServices/*`
- Localhost config services used by the source configuration page
## State Semantics
- Success: outage events are collected, service-order states are collected, and the snapshot is assembled with `success` status.
- Partial result: outage events are collected but order-state enrichment, historical comparison, local logging, downstream reminder / auto-processing side effects, or `consNo``eventId` identity crosswalk quality are incomplete.
- Empty result: a valid outage query returns zero relevant outage events for the chosen window.
- Blocked/error: login failure, missing context, token failure, interception failure, request failure, permission failure, or parse failure.
- Blocked/error must not be reported as empty data.
- Historical monitor logs and dispose logs are downstream comparison context, not the primary upstream business data source.

View File

@@ -0,0 +1,49 @@
# Data Quality
## Complete Result
A complete result means the outage-event source and the related service-order source are both available for the chosen windows, historical comparison is usable, and the monitor snapshot contains aligned values for:
- `pending`
- `audit`
- `processed`
- `pending_ids`
- `new_pending_ids`
## Snapshot Rules
The packaged first-pass snapshot uses:
- outage-event rows as the primary source for `pending` and `pending_ids`
- service-order rows to derive `audit` and `processed`
- historical monitor/dispose logs only to derive `new_pending_ids`
If the package later expands to more status buckets, update the contract before surfacing them as first-pass counters.
## Partial Rules
- If outage events are collected but service-order enrichment fails, set `status` to `partial`.
- If outage events and service-order states are collected but monitor-log or dispose-log parsing/comparison fails, set `status` to `partial`.
- If snapshot assembly succeeds but monitor-log write, monitor-data write, dispose-log write, or audio-log write fails, keep the snapshot and set `status` to `partial`.
- If pending-id comparison is unavailable, do not claim full completeness.
- Do not report the run as fully complete when only outage rows are present without usable comparison context.
## Common Weak Areas
- Missing platform session, org, or marketing token context
- Outage response parse failure
- Service-order response parse failure
- Historical dispose-log parse failure
- Local `MonitorServices` failure after snapshot assembly
- Audio reminder or auto-processing side effects failing after collection
## Empty vs Failure
- A valid outage request with zero relevant outage events is empty data.
- Login failure, blocked page, missing context, token failure, request failure, interception failure, permission failure, or parse failure must be surfaced as failure or partial, not empty.
## Dependency Warnings
- Localhost service availability affects snapshot completeness.
- Historical comparison quality depends on prior monitor/dispose log integrity.
- Audio reminder or auto-processing side effects must never replace the underlying snapshot result.

View File

@@ -0,0 +1,344 @@
const SOURCE_GROUPS = {
outage_events: "http://21.77.244.194:18890/outage/dhsd/dhsdList",
service_orders: "http://21.77.244.194:18890/gdgl/active/service/order/list"
};
const LOCAL_SERVICE_ENDPOINTS = [
"http://localhost:13313/MonitorServices/getMonitorLog",
"http://localhost:13313/MonitorServices/setMonitorData",
"http://localhost:13313/MonitorServices/setMonitorLog",
"http://localhost:13313/MonitorServices/getDisposeLog",
"http://localhost:13313/MonitorServices/setDisposeLog",
"http://localhost:13313/MonitorServices/setAudioPlayLog"
];
const WORKFLOW_RULE_SOURCES = [
"D:/desk/智能体资料/大四区报告监测项/户表失电-嘉峪关_业务监测配置.txt",
"D:/desk/智能体资料/大四区报告监测项/户表失电-嘉峪关_自动处理配置.txt"
];
const CONFIG_BASE_PAGE = "assets/scene-snapshot/index.html";
const IDENTITY_MODEL = {
pending_identity: "consNo",
dispose_dedupe_identity: "eventId",
status: "implementation intent exists but not rigorous / buggy"
};
function asArray(value) {
return Array.isArray(value) ? value : [];
}
function normalizeId(value) {
if (value === null || value === undefined || value === "") {
return null;
}
return String(value);
}
function uniq(values) {
return Array.from(new Set(values));
}
function looksLikeJsonString(value) {
if (typeof value !== "string") {
return false;
}
const trimmed = value.trim();
return trimmed.startsWith("[") || trimmed.startsWith("{") || trimmed.startsWith("\"");
}
function parseLooseJson(value, depth = 0) {
if (depth > 4 || value === null || value === undefined) {
return value;
}
if (typeof value !== "string") {
return value;
}
const trimmed = value.trim();
if (!trimmed) {
return [];
}
try {
const parsed = JSON.parse(trimmed);
return parseLooseJson(parsed, depth + 1);
} catch {
return value;
}
}
function classifyServiceOrders(serviceOrders = []) {
const auditOrders = [];
const processedOrders = [];
const unknownStatusLabels = [];
asArray(serviceOrders).forEach((item) => {
const statusLabel = normalizeId(item && item.gdztmc);
if (statusLabel === "待审核") {
auditOrders.push(item);
return;
}
if (statusLabel === "已归档") {
processedOrders.push(item);
return;
}
if (statusLabel) {
unknownStatusLabels.push(statusLabel);
}
});
return {
auditOrders,
processedOrders,
unknownStatusLabels: uniq(unknownStatusLabels)
};
}
function buildOutageContext(outageEvents = []) {
const pendingIds = [];
const eventIds = [];
const eventIdsByConsNo = {};
asArray(outageEvents).forEach((item) => {
const consNo = normalizeId(item && item.consNo);
const eventId = normalizeId(item && item.eventId);
if (consNo) {
pendingIds.push(consNo);
if (!eventIdsByConsNo[consNo]) {
eventIdsByConsNo[consNo] = [];
}
if (eventId) {
eventIdsByConsNo[consNo].push(eventId);
}
}
if (eventId) {
eventIds.push(eventId);
}
});
Object.keys(eventIdsByConsNo).forEach((consNo) => {
eventIdsByConsNo[consNo] = uniq(eventIdsByConsNo[consNo]);
});
return {
pendingIds: uniq(pendingIds),
eventIds: uniq(eventIds),
eventIdsByConsNo
};
}
function extractMonitorPendingIds(monitorLogs = []) {
const ids = [];
let malformed = false;
asArray(monitorLogs).forEach((item) => {
const pendingIds = asArray(item && item.pending_ids)
.map(normalizeId)
.filter(Boolean);
if (pendingIds.length > 0) {
ids.push(...pendingIds);
return;
}
const rawPendingList = item && item.pendingList;
const pendingList = parseLooseJson(rawPendingList);
if (Array.isArray(pendingList)) {
pendingList
.map(normalizeId)
.filter(Boolean)
.forEach((id) => ids.push(id));
return;
}
if (looksLikeJsonString(rawPendingList)) {
malformed = true;
}
});
return {
ids: uniq(ids),
malformed
};
}
function extractDisposeEventIds(disposeLogs = []) {
const ids = [];
let malformed = false;
asArray(disposeLogs).forEach((item) => {
const rawOrderPayload = item && item.orderID;
const orderPayload = parseLooseJson(rawOrderPayload);
if (Array.isArray(orderPayload)) {
orderPayload.forEach((entry) => {
const eventId = normalizeId(entry && (entry.eventId || entry.id || entry.orderID));
if (eventId) {
ids.push(eventId);
return;
}
if (entry && typeof entry === "object") {
malformed = true;
return;
}
const scalarEntry = normalizeId(entry);
if (scalarEntry) {
ids.push(scalarEntry);
}
});
return;
}
const rawLooksJson = looksLikeJsonString(rawOrderPayload);
if (rawLooksJson && orderPayload === rawOrderPayload) {
malformed = true;
return;
}
const scalarId = normalizeId(orderPayload);
if (scalarId && (!rawLooksJson || orderPayload !== rawOrderPayload)) {
ids.push(scalarId);
return;
}
if (rawLooksJson) {
malformed = true;
}
});
return {
ids: uniq(ids),
malformed
};
}
function determineStatus({ blocked, hasData, partialReasons }) {
if (blocked) {
return "blocked";
}
if (partialReasons.length > 0) {
return "partial";
}
if (!hasData) {
return "empty";
}
return "success";
}
function collectOutageEvents(input = {}) {
const blockedReason = normalizeId(input.blocked_reason);
const outageEvents = asArray(input.outage_events);
const serviceOrders = asArray(input.service_orders);
const monitorLogs = asArray(input.monitor_logs || input.monitor_log);
const disposeLogs = asArray(input.dispose_logs || input.dispose_log);
const localWriteFailures = asArray(input.local_write_failures)
.map(normalizeId)
.filter(Boolean);
const partialReasons = [];
const outageContext = buildOutageContext(outageEvents);
const { auditOrders, processedOrders, unknownStatusLabels } = classifyServiceOrders(serviceOrders);
if (unknownStatusLabels.length > 0) {
partialReasons.push(`service_order_status_unclassified:${unknownStatusLabels.join(",")}`);
}
let monitorPendingIds = [];
let disposedEventIds = [];
if (outageContext.pendingIds.length > 0) {
if (monitorLogs.length === 0) {
partialReasons.push("monitor_log_unavailable");
} else {
const extractedMonitor = extractMonitorPendingIds(monitorLogs);
monitorPendingIds = extractedMonitor.ids;
if (extractedMonitor.malformed) {
partialReasons.push("monitor_log_parse_failed");
}
}
if (disposeLogs.length === 0) {
partialReasons.push("dispose_log_unavailable");
} else {
const extractedDispose = extractDisposeEventIds(disposeLogs);
disposedEventIds = extractedDispose.ids;
if (extractedDispose.malformed) {
partialReasons.push("dispose_log_parse_failed");
}
}
}
if (outageContext.pendingIds.length > 0 && outageContext.eventIds.length === 0) {
partialReasons.push("event_identity_missing_for_pending");
}
const ambiguousIdentityConsNos = outageContext.pendingIds.filter((consNo) => {
const relatedEventIds = asArray(outageContext.eventIdsByConsNo[consNo]);
return relatedEventIds.length > 1;
});
if (ambiguousIdentityConsNos.length > 0) {
partialReasons.push(`identity_crosswalk_ambiguous:${ambiguousIdentityConsNos.join(",")}`);
}
localWriteFailures.forEach((failure) => {
partialReasons.push(`local_write_failed:${failure}`);
});
const monitorSet = new Set(monitorPendingIds);
const disposedEventSet = new Set(disposedEventIds);
const newPendingIds = outageContext.pendingIds.filter((consNo) => {
const relatedEventIds = asArray(outageContext.eventIdsByConsNo[consNo]);
const unseenByMonitor = !monitorSet.has(consNo);
const unseenByDispose = relatedEventIds.length === 0
? true
: relatedEventIds.every((eventId) => !disposedEventSet.has(eventId));
return unseenByMonitor && unseenByDispose;
});
const hasData = outageEvents.length > 0;
const blocked = Boolean(input.blocked) || Boolean(blockedReason);
return {
type: "monitor-snapshot",
scene: "jiayuguan-meter-outage",
time: input.time || "",
pending: outageContext.pendingIds.length,
audit: auditOrders.length,
processed: processedOrders.length,
pending_ids: outageContext.pendingIds,
new_pending_ids: uniq(newPendingIds),
status: determineStatus({
blocked,
hasData,
partialReasons
}),
partial_reasons: uniq(
blockedReason ? [...partialReasons, blockedReason] : partialReasons
),
evidence: {
workflow_rule_sources: WORKFLOW_RULE_SOURCES,
config_base_page: CONFIG_BASE_PAGE,
config_base_role: "configuration-only",
packaged_collector_role: "runtime-snapshot-collector"
},
identity_model: IDENTITY_MODEL
};
}
module.exports = {
SOURCE_GROUPS,
LOCAL_SERVICE_ENDPOINTS,
WORKFLOW_RULE_SOURCES,
CONFIG_BASE_PAGE,
IDENTITY_MODEL,
classifyServiceOrders,
buildOutageContext,
extractMonitorPendingIds,
extractDisposeEventIds,
determineStatus,
collectOutageEvents
};

View File

@@ -0,0 +1,145 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const {
classifyServiceOrders,
buildOutageContext,
extractMonitorPendingIds,
extractDisposeEventIds,
determineStatus,
collectOutageEvents
} = require('./collect_outage_events.js');
test('classifyServiceOrders buckets audit/processed and keeps unknown labels', () => {
const result = classifyServiceOrders([
{ gdztmc: '待审核' },
{ gdztmc: '已归档' },
{ gdztmc: '处理中' }
]);
assert.equal(result.auditOrders.length, 1);
assert.equal(result.processedOrders.length, 1);
assert.deepEqual(result.unknownStatusLabels, ['处理中']);
});
test('buildOutageContext extracts consNo pending ids and event ids', () => {
const context = buildOutageContext([
{ consNo: 'C1', eventId: 'E1' },
{ consNo: 'C2', eventId: 'E2' },
{ consNo: 'C1', eventId: 'E1' }
]);
assert.deepEqual(context.pendingIds, ['C1', 'C2']);
assert.deepEqual(context.eventIds, ['E1', 'E2']);
assert.deepEqual(context.eventIdsByConsNo, { C1: ['E1'], C2: ['E2'] });
});
test('extractMonitorPendingIds parses pending_ids and nested pendingList', () => {
const logs = [
{ pending_ids: ['C1'] },
{ pendingList: JSON.stringify(['C2']) },
{ pendingList: JSON.stringify(JSON.stringify(['C3'])) }
];
assert.deepEqual(extractMonitorPendingIds(logs), { ids: ['C1', 'C2', 'C3'], malformed: false });
});
test('extractDisposeEventIds parses nested orderID payloads', () => {
const logs = [
{ orderID: JSON.stringify([{ id: 'E1' }, { eventId: 'E2' }]) },
{ orderID: JSON.stringify(JSON.stringify([{ id: 'E3' }])) },
{ orderID: 'E4' }
];
assert.deepEqual(extractDisposeEventIds(logs), { ids: ['E1', 'E2', 'E3', 'E4'], malformed: false });
});
test('extractors mark malformed payloads when JSON-like strings cannot be parsed', () => {
const monitor = extractMonitorPendingIds([{ pendingList: '{bad-json' }]);
const dispose = extractDisposeEventIds([{ orderID: '[{"id":"E1"}, {' }]);
assert.deepEqual(monitor, { ids: [], malformed: true });
assert.deepEqual(dispose, { ids: [], malformed: true });
});
test('determineStatus follows blocked > partial > empty > success precedence', () => {
assert.equal(determineStatus({ blocked: true, hasData: true, partialReasons: [] }), 'blocked');
assert.equal(determineStatus({ blocked: false, hasData: true, partialReasons: ['x'] }), 'partial');
assert.equal(determineStatus({ blocked: false, hasData: false, partialReasons: [] }), 'empty');
assert.equal(determineStatus({ blocked: false, hasData: true, partialReasons: [] }), 'success');
});
test('collectOutageEvents builds snapshot using outage and service-order inputs', () => {
const snapshot = collectOutageEvents({
time: '2026-04-08 10:00:00',
outage_events: [
{ consNo: 'C1', eventId: 'E1' },
{ consNo: 'C2', eventId: 'E2' }
],
service_orders: [
{ gdztmc: '待审核' },
{ gdztmc: '已归档' }
],
monitor_logs: [{ pendingList: JSON.stringify(['C1']) }],
dispose_logs: [{ orderID: JSON.stringify([{ id: 'E2' }]) }]
});
assert.equal(snapshot.scene, 'jiayuguan-meter-outage');
assert.equal(snapshot.pending, 2);
assert.equal(snapshot.audit, 1);
assert.equal(snapshot.processed, 1);
assert.deepEqual(snapshot.pending_ids, ['C1', 'C2']);
assert.deepEqual(snapshot.new_pending_ids, []);
assert.equal(snapshot.status, 'success');
assert.equal(snapshot.partial_reasons.length, 0);
});
test('collectOutageEvents reports partial when comparison logs are missing', () => {
const snapshot = collectOutageEvents({
outage_events: [{ consNo: 'C1', eventId: 'E1' }],
service_orders: []
});
assert.equal(snapshot.status, 'partial');
assert.deepEqual(snapshot.partial_reasons.sort(), [
'dispose_log_unavailable',
'monitor_log_unavailable'
]);
});
test('collectOutageEvents reports partial when one consNo maps to multiple event ids', () => {
const snapshot = collectOutageEvents({
outage_events: [
{ consNo: 'C1', eventId: 'E1' },
{ consNo: 'C1', eventId: 'E2' }
],
monitor_logs: [{ pendingList: JSON.stringify([]) }],
dispose_logs: [{ orderID: JSON.stringify([]) }]
});
assert.equal(snapshot.status, 'partial');
assert.ok(snapshot.partial_reasons.includes('identity_crosswalk_ambiguous:C1'));
});
test('collectOutageEvents reports blocked when blocked reason exists', () => {
const snapshot = collectOutageEvents({
blocked_reason: 'missing_marketing_token',
outage_events: [{ consNo: 'C1', eventId: 'E1' }],
monitor_logs: [{ pendingList: '[]' }],
dispose_logs: [{ orderID: '[]' }]
});
assert.equal(snapshot.status, 'blocked');
assert.ok(snapshot.partial_reasons.includes('missing_marketing_token'));
});
test('collectOutageEvents reports empty for valid empty outage result', () => {
const snapshot = collectOutageEvents({
outage_events: []
});
assert.equal(snapshot.status, 'empty');
assert.equal(snapshot.pending, 0);
assert.deepEqual(snapshot.pending_ids, []);
assert.deepEqual(snapshot.new_pending_ids, []);
});

View File

@@ -0,0 +1,102 @@
---
name: jinchang-business-environment-weekly-report
description: Use when the user wants to collect Jinchang business-environment meeting metrics and generate a weekly report artifact.
version: 0.1.0
author: sgclaw
tags:
- jinchang
- business-environment
- weekly-report
- browser
- report
---
# Jinchang Business Environment Weekly Report
## When to Use
- The user asks to collect weekly business-environment metrics for Jinchang.
- The user asks to generate a weekly meeting report artifact.
- The task needs a structured summary for downstream Word or Excel export.
- The task needs one report artifact assembled from multiple metric groups and multiple system queries.
Do not use this skill for:
- unrelated city reporting
- pure template editing without data collection
- claiming complete output when only some metric groups are available
- treating historical report entries as the primary source data
## Workflow
1. Read the selected weekly range from the page.
2. Verify required system-session context is available.
3. Collect the required business-environment metric groups from the source systems.
4. Map the metrics into standard report sections.
5. Return the structured artifact before prose.
6. If some sections are unavailable or period alignment is inconsistent, mark the result as partial.
## Runtime Contract
- Prefer deterministic extraction before generic browser probing.
- Treat login failure, blocked pages, request failure, partial source availability, and empty data as separate outcomes.
- Structured sections are the primary output contract.
- Historical report lists and final document download are downstream artifacts, not the primary business data source.
- Localhost report-log and export services must not redefine upstream collection success.
## Partial-Failure Rule
- If some sections are collected and others fail, report `partial`.
- If weekly period alignment across metric groups is inconsistent, report `partial`.
- If final export or report-log writing fails after section collection succeeds, keep the artifact and report `partial`.
- Do not flatten section-level failures into a full success.
## Export Artifact
```json
{
"type": "report-artifact",
"report_name": "jinchang-business-environment-weekly-report",
"period": "",
"columns": [],
"rows": [],
"sections": [
{
"name": "abnormal-transformer-monitoring",
"columns": ["unit", "transformer_name", "abnormal_type", "note", "handling"],
"rows": []
},
{
"name": "power-outage-monitoring",
"columns": ["unit", "station", "event_type", "count", "note"],
"rows": []
},
{
"name": "work-order-acceptance",
"columns": ["category", "current_period", "previous_period", "trend", "note"],
"rows": []
},
{
"name": "dispatch-summary",
"columns": ["metric", "value", "note"],
"rows": []
}
],
"status": "ok",
"partial_reasons": []
}
```
## Output
Return:
- operation type
- region
- period
- section count
- complete or partial status
- missing sections
- period alignment issues
- downstream export/logging failures
- Export Artifact

View File

@@ -0,0 +1,16 @@
[skill]
name = "jinchang-business-environment-weekly-report"
description = "Use when the user wants to collect Jinchang business-environment meeting metrics and generate a weekly report artifact."
version = "0.1.0"
author = "sgclaw"
tags = ["jinchang", "business-environment", "weekly-report", "browser", "report"]
prompts = [
"For Jinchang weekly report collection, call jinchang-business-environment-weekly-report.collect_business_environment_metrics before generic browser probing.",
]
[[tools]]
name = "collect_business_environment_metrics"
description = "Collect multi-section Jinchang weekly business-environment metrics and prepare the report artifact shell."
kind = "browser_script"
command = "scripts/collect_business_environment_metrics.js"

View File

@@ -0,0 +1,39 @@
# Collection Flow
## Source
- Source scenario: `D:\desk\智能体资料\大四区报告监测项\国网金昌供电公司营商环境周例会报告\index.html`
- Entry page includes a weekly date-range selector, execution log area, and historical report list.
- The page orchestrates multiple source-system queries and then assembles one meeting report.
## Inputs
- Weekly date range selected from the page-level daterange control.
- Current platform session and cached tokens for the required upstream systems.
## First-pass Collection Steps
1. Open or attach to the source page.
2. Read the selected weekly range.
3. Verify required source-system session context is available.
4. Collect abnormal-transformer monitoring metrics.
5. Collect outage-monitoring metrics.
6. Collect work-order and dispatch metrics.
7. Map collected values into section-based report rows.
8. Return the section-based artifact before final document generation.
## Dependencies
- Browser-visible page state
- Platform script integration such as `/a_js/YPTAPI.js`
- Multi-system session and token cache availability
- Localhost report services under `http://localhost:13313/ReportServices/*`
- Localhost export or surface services used by the final generated report
## State Semantics
- Success: required sections are available, mapped, and period-aligned.
- Partial result: some section groups are collected while others fail, or period alignment is inconsistent.
- Empty result: a valid query returns zero rows for a specific metric group.
- Blocked/error: login problems, missing cached sessions, page interception, request failure, or script failure.
- Blocked/error must not be misreported as empty data.

View File

@@ -0,0 +1,34 @@
# Data Quality
## Complete Result
A complete result means the required metric groups for the selected period are all present, period-aligned, and can be placed into report sections.
## Core Section Groups
The first-pass package should preserve separate sections for:
- abnormal-transformer monitoring
- outage monitoring
- work-order acceptance and comparison
- dispatch or meeting summary indicators
## Partial Rules
- If one or more metric groups fail while others succeed, set `status` to `partial`.
- If one or more sections are built from a different weekly period than the selected range, set `status` to `partial`.
- If final export generation or report-log writing fails after section data is assembled, keep the artifact and record the downstream failure in `partial_reasons`.
- Do not claim a full weekly report when only part of the sections are available.
## Common Weak Areas
- Weekly period mismatch
- Section-level missing data
- Inconsistent metric grouping across sources
- Missing upstream token or session context
- Downstream document generation failure after successful collection
## Empty vs Failure
- A source returning no rows for the requested period can be empty data.
- Login failure, missing cached session, interception, export failure, or request failure must be reported explicitly and not treated as empty.

View File

@@ -0,0 +1,40 @@
const SECTION_TEMPLATES = [
{
name: "abnormal-transformer-monitoring",
columns: ["unit", "transformer_name", "abnormal_type", "note", "handling"],
rows: []
},
{
name: "power-outage-monitoring",
columns: ["unit", "station", "event_type", "count", "note"],
rows: []
},
{
name: "work-order-acceptance",
columns: ["category", "current_period", "previous_period", "trend", "note"],
rows: []
},
{
name: "dispatch-summary",
columns: ["metric", "value", "note"],
rows: []
}
];
function collectBusinessEnvironmentMetrics(input = {}) {
return {
type: "report-artifact",
report_name: "jinchang-business-environment-weekly-report",
period: input.period || "",
columns: [],
rows: [],
sections: SECTION_TEMPLATES.map((section) => ({ ...section, rows: [] })),
status: "ok",
partial_reasons: []
};
}
module.exports = {
SECTION_TEMPLATES,
collectBusinessEnvironmentMetrics
};