dsh-plugin-pscad-expert-vyma 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # pscad-dsh-plugin — PSCAD 自动化建模与仿真控制专家插件
2
2
 
3
- > **版本**:1.0.8 | **注册名**:`pscad-automation-expert`
3
+ > **版本**:1.0.10 | **注册名**:`pscad-automation-expert`
4
4
  > **来源**:PSCAD 知识资产固化项目(四阶段:技能文档 → 模板索引 → 工具库 → 插件打包)
5
5
  > 所有知识、工具、模板均来自真实仿真验证(L13~L19 认证轮 + R4 科研轮),**无伪造数据**。
6
6
 
@@ -25,8 +25,8 @@ pscad-dsh-plugin/
25
25
  └── templates/ # 已验证成功运行的基准 .pscx 拓扑(6 个)
26
26
  ├── grid_gfm_support.pscx # 并网 GFM 支撑基准(容量/位置扫描)
27
27
  ├── fault_study.pscx # 故障研究基准(3LG/1LG/2LG 可控故障支路)
28
- ├── island_droop_controller_DROOPC.pscx # 孤岛下垂 + Fortran 自定义控制器基准
29
- ├── line_model_pi_100km_BergeronPI.pscx # source3 单线图 + newpi 线路(Ferranti 对照)
28
+ ├── island_droop_controller.pscx # 孤岛下垂 + Fortran 自定义控制器基准
29
+ ├── line_model_pi_100km.pscx # source3 单线图 + newpi 线路(Ferranti 对照)
30
30
  ├── gfm_island_droop_nose.pscx # 双 GFM 孤岛下垂 + 鼻点扫描(历史经典)
31
31
  └── three_phase_cable_fault.pscx # 三相电缆故障(Cable_Coax 专项)
32
32
  ```
@@ -59,7 +59,7 @@ Agent 维持运行 Loop。全函数 Type Hints + 中文 Docstring。
59
59
  含中段母线 Vmid 与受端母线 Vr 双接入点。
60
60
  - **fault_study**:在上述架构上加可控故障支路(breaker1+Rf+tbreak,TC=0.3/TO=0.4),
61
61
  3LG/1LG/2LG 只需改 tbreak 参数(每类独立案例);短路电流已与解析公式 <1% 对照。
62
- - **island_droop_controller_DROOPC**:孤岛 GFM + 工程内嵌 Fortran 自定义组件 DROOPC(饱和下垂+虚拟惯量),
62
+ - **island_droop_controller**:孤岛 GFM + 工程内嵌 Fortran 自定义组件 DROOPC(饱和下垂+虚拟惯量),
63
63
  研究变量集中在 DROOPC 参数(M/F0/T/FMAX/FMIN)。
64
64
  - **line_model_pi_100km**:source3 单线图 + newpi 线路空载(Ferranti 效应/线路充电对照)。
65
65
  - **gfm_island_droop_nose**:双 GFM 孤岛下垂 + 鼻点族判稳(经典同步稳定性验证)。
package/index.js CHANGED
@@ -15,27 +15,104 @@
15
15
 
16
16
  import { defineTool } from '@deepseek-ai/dsh-tools'
17
17
  import Schema from '@deepseek-ai/schemastery'
18
- import { spawn } from 'node:child_process'
18
+ import { spawn, spawnSync } from 'node:child_process'
19
19
  import { fileURLToPath } from 'node:url'
20
+ import fs from 'node:fs'
21
+ import path from 'node:path'
20
22
 
21
23
  export const name = 'pscad-expert-bridge'
22
24
  export const inject = ['tools']
23
25
 
24
26
  // ---------------- 插件配置(Schemastery,无硬编码) ----------------
25
27
  export const Config = Schema.object({
26
- pythonExecutable: Schema.string().default('python'),
28
+ pythonExecutable: Schema.string().default('python')
29
+ .description('Python 解释器路径;默认自动探测(PATH→py 启动器→常见安装位置),无需手动配置'),
27
30
  toolsCli: Schema.string().default('./tools/cli.py'),
28
31
  defaultFolder: Schema.string().default(''),
29
32
  timeoutMs: Schema.number().default(120000),
30
33
  })
31
34
 
35
+ // ---------------- Python 解释器自动探测(免 profile YAML 配置) ----------------
36
+ let _resolvedPython = null
37
+
38
+ function pyVersionOk(exe) {
39
+ try {
40
+ const r = spawnSync(exe, ['-c', 'import sys; print(sys.version_info[0], sys.version_info[1])'], {
41
+ timeout: 10000, windowsHide: true, encoding: 'utf8',
42
+ })
43
+ if (r.status !== 0 || r.error) return false
44
+ const m = /^(\d+)\s+(\d+)/.exec((r.stdout || '').trim())
45
+ return !!m && Number(m[1]) >= 3 && Number(m[2]) >= 7
46
+ } catch { return false }
47
+ }
48
+
49
+ function findPythonInDir(parent, prefix) {
50
+ // 从目录名解析次版本号(Python3xx),升序优先旧版:
51
+ // 越接近 3.7 越稳妥(mhi.zip 2.2.1 面向 Python 3.7,且依赖 3.12 已移除的 distutils)
52
+ try {
53
+ const dirs = fs.readdirSync(parent, { withFileTypes: true })
54
+ .filter(e => e.isDirectory() && e.name.startsWith(prefix))
55
+ .map(e => ({ name: e.name, minor: parseInt(e.name.slice(prefix.length), 10) || 999 }))
56
+ .sort((a, b) => a.minor - b.minor)
57
+ for (const d of dirs) {
58
+ const exe = path.join(parent, d.name, 'python.exe')
59
+ if (fs.existsSync(exe)) return exe
60
+ }
61
+ } catch {}
62
+ return null
63
+ }
64
+
65
+ function resolvePython(config) {
66
+ if (_resolvedPython) return _resolvedPython
67
+ // 用户显式配置(非默认 'python')优先,尊重覆盖
68
+ if (config.pythonExecutable && config.pythonExecutable !== 'python') {
69
+ _resolvedPython = config.pythonExecutable
70
+ return _resolvedPython
71
+ }
72
+ // 1) PATH 上的 python:验证是真实 3.7+(排除微软商店占位 stub,其退出码 9009/2)
73
+ if (pyVersionOk('python')) { _resolvedPython = 'python'; return _resolvedPython }
74
+ // 2) py 启动器枚举已安装版本:取最接近 3.7 的可用版本
75
+ try {
76
+ const r = spawnSync('py', ['-0p'], { timeout: 10000, windowsHide: true, encoding: 'utf8' })
77
+ if (r.status === 0) {
78
+ const picks = []
79
+ for (const line of (r.stdout || '').split(/\r?\n/)) {
80
+ // 兼容含空格路径(如 C:\Program Files\Python37\python.exe)
81
+ const m = /-V:(\d+)\.(\d+)\s+\*?\s*(.+\\python\.exe)\s*$/.exec(line)
82
+ if (m && fs.existsSync(m[3])) {
83
+ const v = Number(m[1]) * 100 + Number(m[2])
84
+ if (Number(m[1]) >= 3 && v >= 307) picks.push({ v, exe: m[3] })
85
+ }
86
+ }
87
+ if (picks.length) {
88
+ // 优先 Python 3.7–3.11(mhi.zip 2.2.1 依赖 distutils,3.12+ 不可导入);
89
+ // 若机器只有 3.12+,退而用之(配合 pip 安装的 mhi-pscad 新版仍可用)
90
+ const le311 = picks.filter(p => p.v <= 311).sort((a, b) => a.v - b.v)
91
+ const gt311 = picks.filter(p => p.v > 311).sort((a, b) => a.v - b.v)
92
+ _resolvedPython = (le311.length ? le311[0] : gt311[0]).exe
93
+ return _resolvedPython
94
+ }
95
+ }
96
+ } catch {}
97
+ // 3) 常见安装位置(Program Files / 用户级 / 盘符根)
98
+ const home = process.env.LOCALAPPDATA || path.join(process.env.USERPROFILE || '', 'AppData', 'Local')
99
+ const found = findPythonInDir('C:/Program Files', 'Python3') ||
100
+ findPythonInDir(path.join(home, 'Programs', 'Python'), 'Python3') ||
101
+ findPythonInDir('C:/', 'Python3')
102
+ if (found) { _resolvedPython = found; return _resolvedPython }
103
+ // 4) 兜底:保持默认(若 PATH python 为 stub,错误会自然暴露并有明确提示)
104
+ _resolvedPython = 'python'
105
+ return _resolvedPython
106
+ }
107
+
32
108
  // ---------------- Python 桥接(子进程 JSON-RPC,支持取消) ----------------
33
109
  function runPy(tool, args, config, signal) {
34
110
  const cliPath = fileURLToPath(new URL(config.toolsCli, import.meta.url))
111
+ const python = resolvePython(config)
35
112
  return new Promise((resolve, reject) => {
36
113
  let settled = false
37
114
  const done = (fn, v) => { if (!settled) { settled = true; fn(v) } }
38
- const proc = spawn(config.pythonExecutable, [cliPath, tool, JSON.stringify(args)], {
115
+ const proc = spawn(python, [cliPath, tool, JSON.stringify(args)], {
39
116
  windowsHide: true,
40
117
  stdio: ['ignore', 'pipe', 'pipe'],
41
118
  })
@@ -171,6 +248,46 @@ const toolDefs = [
171
248
  description: 'PSCAD 插件环境自检:配置状态、路径存在性、工具清单。诊断用。',
172
249
  parameters: {},
173
250
  },
251
+ {
252
+ name: 'pscad_register_pgb',
253
+ description: '注册 PGB 输出通道(放置 pgb + 同名 datalabel;通道名与目标信号同名即记录该信号)。',
254
+ parameters: {
255
+ channel_name: { type: 'string', required: true, description: '输出通道名(与目标信号同名)' },
256
+ x: { type: 'integer', description: 'pgb 锚点 X;缺省自动布点' },
257
+ y: { type: 'integer', description: 'pgb 锚点 Y;缺省自动布点' },
258
+ },
259
+ },
260
+ {
261
+ name: 'pscad_configure_simulation',
262
+ description: '配置当前工程仿真参数(时长/EMTDC 步长/采样步长/输出文件名)。短线路 time_step 须 < 传播时间/10。',
263
+ parameters: {
264
+ time_duration: { type: 'number', description: '仿真时长(秒,默认 0.5)' },
265
+ time_step: { type: 'number', description: 'EMTDC 步长(微秒,默认 10)' },
266
+ sample_step: { type: 'number', description: '输出采样步长(微秒,默认 50)' },
267
+ output_filename: { type: 'string', description: '输出文件名(不含路径,写入 .gf46 目录)' },
268
+ },
269
+ },
270
+ {
271
+ name: 'pscad_compute_sequence',
272
+ description: 'Fortescue 对称分量换算:由三相电流幅值/相角计算正负零序分量。',
273
+ parameters: {
274
+ ia: { type: 'number', required: true, description: 'A 相电流(相量)' },
275
+ ib: { type: 'number', required: true, description: 'B 相电流(相量)' },
276
+ ic: { type: 'number', required: true, description: 'C 相电流(相量)' },
277
+ },
278
+ },
279
+ {
280
+ name: 'pscad_restart_pscad',
281
+ description: '强制重启 PSCAD 自动化服务(卡死/10054 崩溃恢复):终止进程、重新启动并重连、重载当前工程。',
282
+ parameters: {},
283
+ },
284
+ {
285
+ name: 'pscad_check_component_overlaps',
286
+ description: '布局自检:遍历画布全部组件检查锚点/端口重叠(build 前诊断)。',
287
+ parameters: {
288
+ min_gap: { type: 'integer', description: '最小间距(默认 2)' },
289
+ },
290
+ },
174
291
  ]
175
292
 
176
293
  // ---------------- 插件主体 ----------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-pscad-expert-vyma",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "PSCAD 自动化建模与仿真控制专家插件(DSH Agent 工具库 + 基准模板 + 领域知识)",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/plugin.yaml CHANGED
@@ -3,7 +3,7 @@
3
3
  # 由知识资产固化项目自动生成(2026-08-25)· 部署至 Git 仓库
4
4
  # ============================================================
5
5
  name: pscad-automation-expert
6
- version: 1.0.8
6
+ version: 1.0.10
7
7
  description: PSCAD 自动化建模与仿真控制专家插件
8
8
 
9
9
  # ---- 系统提示词(加载到 Agent 上下文最前置位置)----
package/tools/cli.py CHANGED
@@ -83,6 +83,11 @@ TOOL_WHITELIST: Dict[str, Any] = {
83
83
  'pscad_run_simulation': _run_simulation_configure,
84
84
  'pscad_read_output': pat.read_output,
85
85
  'pscad_self_check': pat.self_check,
86
+ 'pscad_register_pgb': pat.register_pgb,
87
+ 'pscad_configure_simulation': pat.configure_simulation,
88
+ 'pscad_compute_sequence': pat.compute_sequence,
89
+ 'pscad_restart_pscad': pat.restart_pscad,
90
+ 'pscad_check_component_overlaps': pat.check_component_overlaps,
86
91
  }
87
92
 
88
93
 
package/tools/main.py CHANGED
@@ -352,26 +352,49 @@ def _require_config() -> Optional[Dict[str, Any]]:
352
352
  return CONFIG_MISSING_RESPONSE
353
353
 
354
354
 
355
+ def _mhi_import_error(exc: Exception, pylib: str) -> str:
356
+ """构造 mhi 导入失败消息;针对 Python ≥3.12 + distutils 给出明确降级指引。"""
357
+ ver = '%d.%d' % sys.version_info[:2]
358
+ hint = ''
359
+ if sys.version_info >= (3, 12) and 'distutils' in str(exc):
360
+ hint = ('。PSCAD 自带 mhi.zip 为 mhi.pscad 2.2.1,依赖 Python 3.12 已移除的 '
361
+ 'distutils,仅支持 Python ≤3.11:请改用 ≤3.11 的 Python 作为桥接解释器,'
362
+ '或执行 pip install mhi-pscad 安装新版(无此依赖)')
363
+ return ('import mhi.pscad 失败(Python %s,pylib=%s): %s%s'
364
+ % (ver, pylib, exc, hint))
365
+
366
+
355
367
  def _import_mhi() -> Tuple[bool, str]:
356
- """懒加载 mhi.pscad(仅在实际需要连接 PSCAD 时调用)。
368
+ """懒加载 mhi.pscad:多候选导入,**绝不抛异常**。
357
369
 
358
- 流程:``load_config()`` 取 pylib 路径 → 动态加入 ``sys.path`` → import。
359
- **配置缺失时不抛异常**,返回引导消息:先调用 ``init_pscad_environment``。
370
+ 顺序:① 已加载则直接绑定返回;② site-packages 直接导入(pip 安装的
371
+ mhi-pscad 新版天然可导入);③ 将配置/自动发现的 pylib(目录或 mhi.zip)
372
+ 加入 sys.path 后导入。全部失败返回带指引的消息(Python ≥3.12 时提示
373
+ distutils 兼容问题与降级方案)。
360
374
  """
361
375
  global mhi # 必须绑定到模块级:连接代码(_ensure_connected/_launch_pscad)
362
376
  # 依赖 mhi.pscad.connect(...),函数内 import 只会绑定局部名导致 NameError
363
- pylib = _cfg_pylib()
364
- if not pylib:
365
- return False, MISSING_CONFIG_MSG
366
377
  try:
367
- if 'mhi.pscad' not in sys.modules:
368
- if pylib not in sys.path:
369
- sys.path.insert(0, pylib)
370
- import mhi.pscad # noqa: F401 幂等:已加载时仅查 sys.modules 并绑定名字
378
+ if 'mhi.pscad' in sys.modules:
379
+ import mhi.pscad # noqa: F401 幂等绑定模块级名字
380
+ return True, ''
381
+ # ② 先试 site-packages / 已有 sys.path(pip 版 mhi-pscad)
382
+ try:
383
+ import mhi.pscad # noqa: F401
384
+ return True, ''
385
+ except Exception:
386
+ pass
387
+ # ③ 候选 pylib(配置优先,_cfg_pylib 内部已含自动发现回退)
388
+ pylib = _cfg_pylib()
389
+ if not pylib:
390
+ return False, MISSING_CONFIG_MSG
391
+ if pylib not in sys.path:
392
+ sys.path.insert(0, pylib)
393
+ import mhi.pscad # noqa: F401
371
394
  return True, ''
372
395
  except Exception as exc:
373
- return False, ('import mhi.pscad 失败(mhi_pylib_path=%s): %s'
374
- % (pylib, exc))
396
+ pylib = _cfg_pylib() or ''
397
+ return False, _mhi_import_error(exc, pylib)
375
398
 
376
399
 
377
400
  # ---------------------------------------------------------------------------