From 6158f720a7e873ff2050fe532fb87849b71c38f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E7=82=8E?= <635735027@qq.com> Date: Thu, 9 Apr 2026 10:29:44 +0800 Subject: [PATCH] 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 --- .../scene.draft.json | 31 + .../95598-repair-city-dispatch/scene.json | 25 + .../scene.draft.json | 29 + .../95598-weekly-monitor-report/scene.json | 25 + .../fault-details-report/scene.draft.json | 29 + .../scenes/fault-details-report/scene.json | 25 + .../jiayuguan-meter-outage/scene.draft.json | 31 + .../scenes/jiayuguan-meter-outage/scene.json | 25 + .../scene.draft.json | 29 + .../scene.json | 25 + .../scenes/load-scene-registry.js | 77 + .../scenes/load-scene-registry.test.js | 23 + .../scenes/scene-registry-usage.md | 156 + .../scenes/scene-registry.draft.json | 165 + .../95598-repair-city-dispatch/SKILL.md | 82 + .../95598-repair-city-dispatch/SKILL.toml | 16 + .../rules/95598抢修-市指_业务检测配置.txt | 147 + .../rules/95598抢修-市指_自动处理配置.txt | 350 +++ .../assets/scene-snapshot/index.html | 355 +++ .../references/collection-flow.md | 49 + .../references/data-quality.md | 49 + .../scripts/collect_repair_orders.js | 302 ++ .../scripts/collect_repair_orders.test.js | 117 + .../95598-weekly-monitor-report/SKILL.md | 112 + .../95598-weekly-monitor-report/SKILL.toml | 16 + .../assets/scene-snapshot/index.html | 2798 +++++++++++++++++ .../references/collection-flow.md | 39 + .../references/data-quality.md | 36 + .../scripts/collect_weekly_metrics.js | 50 + .../skills/fault-details-report/SKILL.md | 133 + .../skills/fault-details-report/SKILL.toml | 16 + .../assets/scene-snapshot/index.html | 914 ++++++ .../references/collection-flow.md | 39 + .../references/data-quality.md | 99 + .../scripts/collect_fault_details.js | 75 + .../skills/jiayuguan-meter-outage/SKILL.md | 84 + .../skills/jiayuguan-meter-outage/SKILL.toml | 16 + .../rules/户表失电-嘉峪关_业务监测配置.txt | 265 ++ .../rules/户表失电-嘉峪关_自动处理配置.txt | 470 +++ .../assets/scene-snapshot/index.html | 388 +++ .../references/collection-flow.md | 52 + .../references/data-quality.md | 49 + .../scripts/collect_outage_events.js | 344 ++ .../scripts/collect_outage_events.test.js | 145 + .../SKILL.md | 102 + .../SKILL.toml | 16 + .../assets/scene-snapshot/index.html | 1781 +++++++++++ .../references/collection-flow.md | 39 + .../references/data-quality.md | 34 + .../collect_business_environment_metrics.js | 40 + 50 files changed, 10314 insertions(+) create mode 100644 skills/skill_staging/scenes/95598-repair-city-dispatch/scene.draft.json create mode 100644 skills/skill_staging/scenes/95598-repair-city-dispatch/scene.json create mode 100644 skills/skill_staging/scenes/95598-weekly-monitor-report/scene.draft.json create mode 100644 skills/skill_staging/scenes/95598-weekly-monitor-report/scene.json create mode 100644 skills/skill_staging/scenes/fault-details-report/scene.draft.json create mode 100644 skills/skill_staging/scenes/fault-details-report/scene.json create mode 100644 skills/skill_staging/scenes/jiayuguan-meter-outage/scene.draft.json create mode 100644 skills/skill_staging/scenes/jiayuguan-meter-outage/scene.json create mode 100644 skills/skill_staging/scenes/jinchang-business-environment-weekly-report/scene.draft.json create mode 100644 skills/skill_staging/scenes/jinchang-business-environment-weekly-report/scene.json create mode 100644 skills/skill_staging/scenes/load-scene-registry.js create mode 100644 skills/skill_staging/scenes/load-scene-registry.test.js create mode 100644 skills/skill_staging/scenes/scene-registry-usage.md create mode 100644 skills/skill_staging/scenes/scene-registry.draft.json create mode 100644 skills/skill_staging/skills/95598-repair-city-dispatch/SKILL.md create mode 100644 skills/skill_staging/skills/95598-repair-city-dispatch/SKILL.toml create mode 100644 skills/skill_staging/skills/95598-repair-city-dispatch/assets/rules/95598抢修-市指_业务检测配置.txt create mode 100644 skills/skill_staging/skills/95598-repair-city-dispatch/assets/rules/95598抢修-市指_自动处理配置.txt create mode 100644 skills/skill_staging/skills/95598-repair-city-dispatch/assets/scene-snapshot/index.html create mode 100644 skills/skill_staging/skills/95598-repair-city-dispatch/references/collection-flow.md create mode 100644 skills/skill_staging/skills/95598-repair-city-dispatch/references/data-quality.md create mode 100644 skills/skill_staging/skills/95598-repair-city-dispatch/scripts/collect_repair_orders.js create mode 100644 skills/skill_staging/skills/95598-repair-city-dispatch/scripts/collect_repair_orders.test.js create mode 100644 skills/skill_staging/skills/95598-weekly-monitor-report/SKILL.md create mode 100644 skills/skill_staging/skills/95598-weekly-monitor-report/SKILL.toml create mode 100644 skills/skill_staging/skills/95598-weekly-monitor-report/assets/scene-snapshot/index.html create mode 100644 skills/skill_staging/skills/95598-weekly-monitor-report/references/collection-flow.md create mode 100644 skills/skill_staging/skills/95598-weekly-monitor-report/references/data-quality.md create mode 100644 skills/skill_staging/skills/95598-weekly-monitor-report/scripts/collect_weekly_metrics.js create mode 100644 skills/skill_staging/skills/fault-details-report/SKILL.md create mode 100644 skills/skill_staging/skills/fault-details-report/SKILL.toml create mode 100644 skills/skill_staging/skills/fault-details-report/assets/scene-snapshot/index.html create mode 100644 skills/skill_staging/skills/fault-details-report/references/collection-flow.md create mode 100644 skills/skill_staging/skills/fault-details-report/references/data-quality.md create mode 100644 skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.js create mode 100644 skills/skill_staging/skills/jiayuguan-meter-outage/SKILL.md create mode 100644 skills/skill_staging/skills/jiayuguan-meter-outage/SKILL.toml create mode 100644 skills/skill_staging/skills/jiayuguan-meter-outage/assets/rules/户表失电-嘉峪关_业务监测配置.txt create mode 100644 skills/skill_staging/skills/jiayuguan-meter-outage/assets/rules/户表失电-嘉峪关_自动处理配置.txt create mode 100644 skills/skill_staging/skills/jiayuguan-meter-outage/assets/scene-snapshot/index.html create mode 100644 skills/skill_staging/skills/jiayuguan-meter-outage/references/collection-flow.md create mode 100644 skills/skill_staging/skills/jiayuguan-meter-outage/references/data-quality.md create mode 100644 skills/skill_staging/skills/jiayuguan-meter-outage/scripts/collect_outage_events.js create mode 100644 skills/skill_staging/skills/jiayuguan-meter-outage/scripts/collect_outage_events.test.js create mode 100644 skills/skill_staging/skills/jinchang-business-environment-weekly-report/SKILL.md create mode 100644 skills/skill_staging/skills/jinchang-business-environment-weekly-report/SKILL.toml create mode 100644 skills/skill_staging/skills/jinchang-business-environment-weekly-report/assets/scene-snapshot/index.html create mode 100644 skills/skill_staging/skills/jinchang-business-environment-weekly-report/references/collection-flow.md create mode 100644 skills/skill_staging/skills/jinchang-business-environment-weekly-report/references/data-quality.md create mode 100644 skills/skill_staging/skills/jinchang-business-environment-weekly-report/scripts/collect_business_environment_metrics.js diff --git a/skills/skill_staging/scenes/95598-repair-city-dispatch/scene.draft.json b/skills/skill_staging/scenes/95598-repair-city-dispatch/scene.draft.json new file mode 100644 index 0000000..a39a9dd --- /dev/null +++ b/skills/skill_staging/scenes/95598-repair-city-dispatch/scene.draft.json @@ -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 中单独暴露。" + ] +} diff --git a/skills/skill_staging/scenes/95598-repair-city-dispatch/scene.json b/skills/skill_staging/scenes/95598-repair-city-dispatch/scene.json new file mode 100644 index 0000000..8348348 --- /dev/null +++ b/skills/skill_staging/scenes/95598-repair-city-dispatch/scene.json @@ -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 + } +} diff --git a/skills/skill_staging/scenes/95598-weekly-monitor-report/scene.draft.json b/skills/skill_staging/scenes/95598-weekly-monitor-report/scene.draft.json new file mode 100644 index 0000000..cfaddf2 --- /dev/null +++ b/skills/skill_staging/scenes/95598-weekly-monitor-report/scene.draft.json @@ -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 语义。" + ] +} diff --git a/skills/skill_staging/scenes/95598-weekly-monitor-report/scene.json b/skills/skill_staging/scenes/95598-weekly-monitor-report/scene.json new file mode 100644 index 0000000..979e465 --- /dev/null +++ b/skills/skill_staging/scenes/95598-weekly-monitor-report/scene.json @@ -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 + } +} diff --git a/skills/skill_staging/scenes/fault-details-report/scene.draft.json b/skills/skill_staging/scenes/fault-details-report/scene.draft.json new file mode 100644 index 0000000..3ecbeec --- /dev/null +++ b/skills/skill_staging/scenes/fault-details-report/scene.draft.json @@ -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 词表。" + ] +} diff --git a/skills/skill_staging/scenes/fault-details-report/scene.json b/skills/skill_staging/scenes/fault-details-report/scene.json new file mode 100644 index 0000000..aa12443 --- /dev/null +++ b/skills/skill_staging/scenes/fault-details-report/scene.json @@ -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 + } +} diff --git a/skills/skill_staging/scenes/jiayuguan-meter-outage/scene.draft.json b/skills/skill_staging/scenes/jiayuguan-meter-outage/scene.draft.json new file mode 100644 index 0000000..bd445c3 --- /dev/null +++ b/skills/skill_staging/scenes/jiayuguan-meter-outage/scene.draft.json @@ -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 拆成两个独立动作。" + ] +} diff --git a/skills/skill_staging/scenes/jiayuguan-meter-outage/scene.json b/skills/skill_staging/scenes/jiayuguan-meter-outage/scene.json new file mode 100644 index 0000000..737bddb --- /dev/null +++ b/skills/skill_staging/scenes/jiayuguan-meter-outage/scene.json @@ -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 + } +} diff --git a/skills/skill_staging/scenes/jinchang-business-environment-weekly-report/scene.draft.json b/skills/skill_staging/scenes/jinchang-business-environment-weekly-report/scene.draft.json new file mode 100644 index 0000000..8037cd7 --- /dev/null +++ b/skills/skill_staging/scenes/jinchang-business-environment-weekly-report/scene.draft.json @@ -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。" + ] +} diff --git a/skills/skill_staging/scenes/jinchang-business-environment-weekly-report/scene.json b/skills/skill_staging/scenes/jinchang-business-environment-weekly-report/scene.json new file mode 100644 index 0000000..bbedf79 --- /dev/null +++ b/skills/skill_staging/scenes/jinchang-business-environment-weekly-report/scene.json @@ -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 + } +} diff --git a/skills/skill_staging/scenes/load-scene-registry.js b/skills/skill_staging/scenes/load-scene-registry.js new file mode 100644 index 0000000..df444ea --- /dev/null +++ b/skills/skill_staging/scenes/load-scene-registry.js @@ -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 +}; diff --git a/skills/skill_staging/scenes/load-scene-registry.test.js b/skills/skill_staging/scenes/load-scene-registry.test.js new file mode 100644 index 0000000..bd2ee6b --- /dev/null +++ b/skills/skill_staging/scenes/load-scene-registry.test.js @@ -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'); +}); diff --git a/skills/skill_staging/scenes/scene-registry-usage.md b/skills/skill_staging/scenes/scene-registry-usage.md new file mode 100644 index 0000000..72f24d7 --- /dev/null +++ b/skills/skill_staging/scenes/scene-registry-usage.md @@ -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// +``` + +为 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,直接拿相对路径拼当前工作目录。 diff --git a/skills/skill_staging/scenes/scene-registry.draft.json b/skills/skill_staging/scenes/scene-registry.draft.json new file mode 100644 index 0000000..e8c4f54 --- /dev/null +++ b/skills/skill_staging/scenes/scene-registry.draft.json @@ -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 拆成两个独立动作。" + ] + } + ] +} diff --git a/skills/skill_staging/skills/95598-repair-city-dispatch/SKILL.md b/skills/skill_staging/skills/95598-repair-city-dispatch/SKILL.md new file mode 100644 index 0000000..a0c531a --- /dev/null +++ b/skills/skill_staging/skills/95598-repair-city-dispatch/SKILL.md @@ -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 diff --git a/skills/skill_staging/skills/95598-repair-city-dispatch/SKILL.toml b/skills/skill_staging/skills/95598-repair-city-dispatch/SKILL.toml new file mode 100644 index 0000000..624ba34 --- /dev/null +++ b/skills/skill_staging/skills/95598-repair-city-dispatch/SKILL.toml @@ -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" diff --git a/skills/skill_staging/skills/95598-repair-city-dispatch/assets/rules/95598抢修-市指_业务检测配置.txt b/skills/skill_staging/skills/95598-repair-city-dispatch/assets/rules/95598抢修-市指_业务检测配置.txt new file mode 100644 index 0000000..37aa560 --- /dev/null +++ b/skills/skill_staging/skills/95598-repair-city-dispatch/assets/rules/95598抢修-市指_业务检测配置.txt @@ -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: "异常", + }), + }); + }); +} diff --git a/skills/skill_staging/skills/95598-repair-city-dispatch/assets/rules/95598抢修-市指_自动处理配置.txt b/skills/skill_staging/skills/95598-repair-city-dispatch/assets/rules/95598抢修-市指_自动处理配置.txt new file mode 100644 index 0000000..b7b1cbb --- /dev/null +++ b/skills/skill_staging/skills/95598-repair-city-dispatch/assets/rules/95598抢修-市指_自动处理配置.txt @@ -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(); + } + } + }); \ No newline at end of file diff --git a/skills/skill_staging/skills/95598-repair-city-dispatch/assets/scene-snapshot/index.html b/skills/skill_staging/skills/95598-repair-city-dispatch/assets/scene-snapshot/index.html new file mode 100644 index 0000000..0078e17 --- /dev/null +++ b/skills/skill_staging/skills/95598-repair-city-dispatch/assets/scene-snapshot/index.html @@ -0,0 +1,355 @@ + + + + + + + + + + 接单班组信息 + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 确定 + + + + + + + + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/skills/skill_staging/skills/95598-repair-city-dispatch/references/collection-flow.md b/skills/skill_staging/skills/95598-repair-city-dispatch/references/collection-flow.md new file mode 100644 index 0000000..a96a290 --- /dev/null +++ b/skills/skill_staging/skills/95598-repair-city-dispatch/references/collection-flow.md @@ -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. diff --git a/skills/skill_staging/skills/95598-repair-city-dispatch/references/data-quality.md b/skills/skill_staging/skills/95598-repair-city-dispatch/references/data-quality.md new file mode 100644 index 0000000..345971e --- /dev/null +++ b/skills/skill_staging/skills/95598-repair-city-dispatch/references/data-quality.md @@ -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. diff --git a/skills/skill_staging/skills/95598-repair-city-dispatch/scripts/collect_repair_orders.js b/skills/skill_staging/skills/95598-repair-city-dispatch/scripts/collect_repair_orders.js new file mode 100644 index 0000000..11fb87b --- /dev/null +++ b/skills/skill_staging/skills/95598-repair-city-dispatch/scripts/collect_repair_orders.js @@ -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 +}; diff --git a/skills/skill_staging/skills/95598-repair-city-dispatch/scripts/collect_repair_orders.test.js b/skills/skill_staging/skills/95598-repair-city-dispatch/scripts/collect_repair_orders.test.js new file mode 100644 index 0000000..3a31daa --- /dev/null +++ b/skills/skill_staging/skills/95598-repair-city-dispatch/scripts/collect_repair_orders.test.js @@ -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, []); +}); diff --git a/skills/skill_staging/skills/95598-weekly-monitor-report/SKILL.md b/skills/skill_staging/skills/95598-weekly-monitor-report/SKILL.md new file mode 100644 index 0000000..4824659 --- /dev/null +++ b/skills/skill_staging/skills/95598-weekly-monitor-report/SKILL.md @@ -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 diff --git a/skills/skill_staging/skills/95598-weekly-monitor-report/SKILL.toml b/skills/skill_staging/skills/95598-weekly-monitor-report/SKILL.toml new file mode 100644 index 0000000..97e4a7c --- /dev/null +++ b/skills/skill_staging/skills/95598-weekly-monitor-report/SKILL.toml @@ -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" diff --git a/skills/skill_staging/skills/95598-weekly-monitor-report/assets/scene-snapshot/index.html b/skills/skill_staging/skills/95598-weekly-monitor-report/assets/scene-snapshot/index.html new file mode 100644 index 0000000..d37c1ef --- /dev/null +++ b/skills/skill_staging/skills/95598-weekly-monitor-report/assets/scene-snapshot/index.html @@ -0,0 +1,2798 @@ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + \ No newline at end of file diff --git a/skills/skill_staging/skills/95598-weekly-monitor-report/references/collection-flow.md b/skills/skill_staging/skills/95598-weekly-monitor-report/references/collection-flow.md new file mode 100644 index 0000000..f5c5a59 --- /dev/null +++ b/skills/skill_staging/skills/95598-weekly-monitor-report/references/collection-flow.md @@ -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. diff --git a/skills/skill_staging/skills/95598-weekly-monitor-report/references/data-quality.md b/skills/skill_staging/skills/95598-weekly-monitor-report/references/data-quality.md new file mode 100644 index 0000000..462efc4 --- /dev/null +++ b/skills/skill_staging/skills/95598-weekly-monitor-report/references/data-quality.md @@ -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. diff --git a/skills/skill_staging/skills/95598-weekly-monitor-report/scripts/collect_weekly_metrics.js b/skills/skill_staging/skills/95598-weekly-monitor-report/scripts/collect_weekly_metrics.js new file mode 100644 index 0000000..e6a14ee --- /dev/null +++ b/skills/skill_staging/skills/95598-weekly-monitor-report/scripts/collect_weekly_metrics.js @@ -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 +}; diff --git a/skills/skill_staging/skills/fault-details-report/SKILL.md b/skills/skill_staging/skills/fault-details-report/SKILL.md new file mode 100644 index 0000000..8602cfa --- /dev/null +++ b/skills/skill_staging/skills/fault-details-report/SKILL.md @@ -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 diff --git a/skills/skill_staging/skills/fault-details-report/SKILL.toml b/skills/skill_staging/skills/fault-details-report/SKILL.toml new file mode 100644 index 0000000..a64cc79 --- /dev/null +++ b/skills/skill_staging/skills/fault-details-report/SKILL.toml @@ -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" diff --git a/skills/skill_staging/skills/fault-details-report/assets/scene-snapshot/index.html b/skills/skill_staging/skills/fault-details-report/assets/scene-snapshot/index.html new file mode 100644 index 0000000..6d80428 --- /dev/null +++ b/skills/skill_staging/skills/fault-details-report/assets/scene-snapshot/index.html @@ -0,0 +1,914 @@ + + + + + + + + + + 故障明细统计 + + + + + + + + + + + + +
+
+
故障明细统计
+
+
+
+
+
+
作业信息
+
+
+
+
{{ item.title }}:
+
{{ item.info }}
+
+
+
+
+
+
+
+
+
+ 自动化工具执行过程 +
+ 开始执行 + 下载中 +
+
+
+ + + + + + +
+ +
+
+
+ +
生成日志
+
+
+
+
{{item.item}}
+
+
+
+
+
+ +
历史报告
+
+
+
+
+
{{item.reportName}} {{item.createdTime}}
+ 下载 + 删除 +
+
+
+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/skills/skill_staging/skills/fault-details-report/references/collection-flow.md b/skills/skill_staging/skills/fault-details-report/references/collection-flow.md new file mode 100644 index 0000000..32c0c59 --- /dev/null +++ b/skills/skill_staging/skills/fault-details-report/references/collection-flow.md @@ -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. diff --git a/skills/skill_staging/skills/fault-details-report/references/data-quality.md b/skills/skill_staging/skills/fault-details-report/references/data-quality.md new file mode 100644 index 0000000..1d65b60 --- /dev/null +++ b/skills/skill_staging/skills/fault-details-report/references/data-quality.md @@ -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. diff --git a/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.js b/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.js new file mode 100644 index 0000000..4f5ffb9 --- /dev/null +++ b/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.js @@ -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 +}; diff --git a/skills/skill_staging/skills/jiayuguan-meter-outage/SKILL.md b/skills/skill_staging/skills/jiayuguan-meter-outage/SKILL.md new file mode 100644 index 0000000..54ce74f --- /dev/null +++ b/skills/skill_staging/skills/jiayuguan-meter-outage/SKILL.md @@ -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 diff --git a/skills/skill_staging/skills/jiayuguan-meter-outage/SKILL.toml b/skills/skill_staging/skills/jiayuguan-meter-outage/SKILL.toml new file mode 100644 index 0000000..3330e36 --- /dev/null +++ b/skills/skill_staging/skills/jiayuguan-meter-outage/SKILL.toml @@ -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" diff --git a/skills/skill_staging/skills/jiayuguan-meter-outage/assets/rules/户表失电-嘉峪关_业务监测配置.txt b/skills/skill_staging/skills/jiayuguan-meter-outage/assets/rules/户表失电-嘉峪关_业务监测配置.txt new file mode 100644 index 0000000..25f9c23 --- /dev/null +++ b/skills/skill_staging/skills/jiayuguan-meter-outage/assets/rules/户表失电-嘉峪关_业务监测配置.txt @@ -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: "异常" }) + }) + }) +} diff --git a/skills/skill_staging/skills/jiayuguan-meter-outage/assets/rules/户表失电-嘉峪关_自动处理配置.txt b/skills/skill_staging/skills/jiayuguan-meter-outage/assets/rules/户表失电-嘉峪关_自动处理配置.txt new file mode 100644 index 0000000..4a51fb3 --- /dev/null +++ b/skills/skill_staging/skills/jiayuguan-meter-outage/assets/rules/户表失电-嘉峪关_自动处理配置.txt @@ -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: "异常" }) + }) + }) + } +} \ No newline at end of file diff --git a/skills/skill_staging/skills/jiayuguan-meter-outage/assets/scene-snapshot/index.html b/skills/skill_staging/skills/jiayuguan-meter-outage/assets/scene-snapshot/index.html new file mode 100644 index 0000000..a9c17f4 --- /dev/null +++ b/skills/skill_staging/skills/jiayuguan-meter-outage/assets/scene-snapshot/index.html @@ -0,0 +1,388 @@ + + + + + + + + + + 接单班组信息 + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + 全选 + + + {{ day.label }} + + + + + + + + + + + + + + + 确定 + + + + + + + + + + + + + + 全选 + + + {{ day.label }} + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/skills/skill_staging/skills/jiayuguan-meter-outage/references/collection-flow.md b/skills/skill_staging/skills/jiayuguan-meter-outage/references/collection-flow.md new file mode 100644 index 0000000..c50c46a --- /dev/null +++ b/skills/skill_staging/skills/jiayuguan-meter-outage/references/collection-flow.md @@ -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. diff --git a/skills/skill_staging/skills/jiayuguan-meter-outage/references/data-quality.md b/skills/skill_staging/skills/jiayuguan-meter-outage/references/data-quality.md new file mode 100644 index 0000000..9dd8407 --- /dev/null +++ b/skills/skill_staging/skills/jiayuguan-meter-outage/references/data-quality.md @@ -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. diff --git a/skills/skill_staging/skills/jiayuguan-meter-outage/scripts/collect_outage_events.js b/skills/skill_staging/skills/jiayuguan-meter-outage/scripts/collect_outage_events.js new file mode 100644 index 0000000..4b25dd7 --- /dev/null +++ b/skills/skill_staging/skills/jiayuguan-meter-outage/scripts/collect_outage_events.js @@ -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 +}; diff --git a/skills/skill_staging/skills/jiayuguan-meter-outage/scripts/collect_outage_events.test.js b/skills/skill_staging/skills/jiayuguan-meter-outage/scripts/collect_outage_events.test.js new file mode 100644 index 0000000..a1def5c --- /dev/null +++ b/skills/skill_staging/skills/jiayuguan-meter-outage/scripts/collect_outage_events.test.js @@ -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, []); +}); diff --git a/skills/skill_staging/skills/jinchang-business-environment-weekly-report/SKILL.md b/skills/skill_staging/skills/jinchang-business-environment-weekly-report/SKILL.md new file mode 100644 index 0000000..89cc112 --- /dev/null +++ b/skills/skill_staging/skills/jinchang-business-environment-weekly-report/SKILL.md @@ -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 diff --git a/skills/skill_staging/skills/jinchang-business-environment-weekly-report/SKILL.toml b/skills/skill_staging/skills/jinchang-business-environment-weekly-report/SKILL.toml new file mode 100644 index 0000000..983ec03 --- /dev/null +++ b/skills/skill_staging/skills/jinchang-business-environment-weekly-report/SKILL.toml @@ -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" diff --git a/skills/skill_staging/skills/jinchang-business-environment-weekly-report/assets/scene-snapshot/index.html b/skills/skill_staging/skills/jinchang-business-environment-weekly-report/assets/scene-snapshot/index.html new file mode 100644 index 0000000..60fd8a8 --- /dev/null +++ b/skills/skill_staging/skills/jinchang-business-environment-weekly-report/assets/scene-snapshot/index.html @@ -0,0 +1,1781 @@ + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + \ No newline at end of file diff --git a/skills/skill_staging/skills/jinchang-business-environment-weekly-report/references/collection-flow.md b/skills/skill_staging/skills/jinchang-business-environment-weekly-report/references/collection-flow.md new file mode 100644 index 0000000..c818b19 --- /dev/null +++ b/skills/skill_staging/skills/jinchang-business-environment-weekly-report/references/collection-flow.md @@ -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. diff --git a/skills/skill_staging/skills/jinchang-business-environment-weekly-report/references/data-quality.md b/skills/skill_staging/skills/jinchang-business-environment-weekly-report/references/data-quality.md new file mode 100644 index 0000000..987e56c --- /dev/null +++ b/skills/skill_staging/skills/jinchang-business-environment-weekly-report/references/data-quality.md @@ -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. diff --git a/skills/skill_staging/skills/jinchang-business-environment-weekly-report/scripts/collect_business_environment_metrics.js b/skills/skill_staging/skills/jinchang-business-environment-weekly-report/scripts/collect_business_environment_metrics.js new file mode 100644 index 0000000..949cf33 --- /dev/null +++ b/skills/skill_staging/skills/jinchang-business-environment-weekly-report/scripts/collect_business_environment_metrics.js @@ -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 +};