From 5993fc861ae1e462567517210a97537064e628ae Mon Sep 17 00:00:00 2001 From: aswinkrishnapoyil Date: Thu, 15 Oct 2026 11:03:34 +0200 Subject: [PATCH 1/5] Preserve PDF export results and bind bootstrap destination --- PowerFactory/Agent_DIgSILENT.py | 67 ++++++++++++++-------- PowerFactory/MCP_PowerFactory.py | 3 +- PowerFactory/README.md | 9 ++- PowerFactory/tests/test_grid_bootstrap.py | 42 ++++++++++++++ PowerFactory/tests/test_grid_pdf_export.py | 27 ++++++--- 5 files changed, 112 insertions(+), 36 deletions(-) diff --git a/PowerFactory/Agent_DIgSILENT.py b/PowerFactory/Agent_DIgSILENT.py index a1f9876..f87a5bd 100644 --- a/PowerFactory/Agent_DIgSILENT.py +++ b/PowerFactory/Agent_DIgSILENT.py @@ -931,6 +931,7 @@ def export_active_grid_pdf( open_digsilent: bool = True, ) -> tuple[bool, str]: """Export one single-line diagram from the active project as PDF.""" + restore_warning = "" try: file_path = checked_path( file_path, @@ -1025,19 +1026,23 @@ def export_active_grid_pdf( ) finally: if previous_diagram is not None: - restore_code = graphics_board.Show(previous_diagram) - if restore_code not in (None, 0): - raise RuntimeError( - f"Could not restore previous diagram: {restore_code}" + try: + restore_code = graphics_board.Show(previous_diagram) + if restore_code not in (None, 0): + raise RuntimeError(f"error code {restore_code}") + except Exception as restore_exc: + restore_warning = ( + f" Warning: could not restore previous diagram: {restore_exc}" ) log.ok(f"Grid diagram exported to PDF → {file_path}") return True, ( f"Grid diagram '{selected_name}' exported to {file_path}" + + restore_warning ) except Exception as exc: log.error(f"Grid PDF export failed: {exc}") - return False, str(exc) + return False, str(exc) + restore_warning @classmethod def import_project(cls, file_path: str, open_digsilent: bool = True) -> tuple[bool, str]: @@ -1371,6 +1376,36 @@ def failure(message, **extra): ) reused_buses.append(existing.GetFullName()) + data_folder = None + model_folder = None + destination_plan = None + if grid is None: + data_folders = cls._find_named_contents( + project, "Network Data", "IntPrjfolder", + ) + if len(data_folders) > 1: + raise RuntimeError("Multiple Network Data folders were found") + data_folder = data_folders[0] if data_folders else None + if data_folder is None: + model_folders = cls._find_named_contents( + project, "Network Model", "IntPrjfolder", + ) + if len(model_folders) > 1: + raise RuntimeError("Multiple Network Model folders were found") + model_folder = model_folders[0] if model_folders else None + destination_plan = { + "data_folder": ( + data_folder.GetFullName() if data_folder is not None else None + ), + "parent": ( + data_folder.GetFullName() if data_folder is not None + else model_folder.GetFullName() if model_folder is not None + else project.GetFullName() + ), + "create_network_model": data_folder is None and model_folder is None, + "create_network_data": data_folder is None, + } + required_confirmation = cls._creation_confirmation_token( confirmation_prefix, { @@ -1384,6 +1419,7 @@ def failure(message, **extra): "grid_will_be_activated": not grid_is_active, "buses_to_create": pending_buses, "reused_buses": reused_buses, + "destination_plan": destination_plan, }, ) @@ -1403,6 +1439,7 @@ def failure(message, **extra): definition["name"] for definition in pending_buses ], "reused_buses": reused_buses, + "destination_plan": destination_plan, "confirmation_required": required_confirmation, "message": ( "Preview only; no grid, folder, or bus was created." @@ -1422,27 +1459,7 @@ def failure(message, **extra): return result if grid is None: - data_folders = cls._find_named_contents( - project, - "Network Data", - "IntPrjfolder", - ) - if len(data_folders) > 1: - raise RuntimeError("Multiple Network Data folders were found") - data_folder = data_folders[0] if data_folders else None if data_folder is None: - model_folders = cls._find_named_contents( - project, - "Network Model", - "IntPrjfolder", - ) - if len(model_folders) > 1: - raise RuntimeError( - "Multiple Network Model folders were found" - ) - model_folder = ( - model_folders[0] if model_folders else None - ) if model_folder is None: model_folder = project.CreateObject( "IntPrjfolder", diff --git a/PowerFactory/MCP_PowerFactory.py b/PowerFactory/MCP_PowerFactory.py index b98ce97..1e2ccad 100644 --- a/PowerFactory/MCP_PowerFactory.py +++ b/PowerFactory/MCP_PowerFactory.py @@ -1156,7 +1156,8 @@ def export_active_grid_pdf( The active project and study case are required. If the Graphics Board has more than one single-line diagram, provide its exact name. Existing files - are never overwritten. + are never overwritten. Failure to restore the previous diagram is appended + as a warning in the message without changing the export's success flag. Parameters ---------- diff --git a/PowerFactory/README.md b/PowerFactory/README.md index 3b74300..57aa9e9 100644 --- a/PowerFactory/README.md +++ b/PowerFactory/README.md @@ -768,13 +768,16 @@ inherited by `DIgSILENTAgent`; the public MCP tool names and arguments are uncha Equipment-type rollback and confirmation hashing share helpers in that module. Grid-bootstrap confirmation is bound to the active project and study case, -the resolved grid, and the create/reuse/activation decisions. If that state +the resolved grid, the destination folder for a new grid, and the +create/reuse/activation decisions. The preview returns the destination folder +and required folder-creation decisions in `destination_plan`. If that state changes, request a fresh preview before confirming, including for repeated calls. PDF export restores a previously active single-line diagram in a `finally` block after success or failure. If no current diagram is reported, there is -no diagram to restore. Restoration failure is reported as a failed export; -the PDF may already have been written. Successful single-line restoration was +no diagram to restore. Restoration failure adds a warning to the result message: +a completed export still reports success, while a failed export retains its +original error. Successful single-line restoration was verified live on SP1 in [FT-26](evidence/FT-26_pdf_diagram_restoration_sp1.txt). Failure-path restoration has automated coverage; active plot pages are not covered. Bootstrap stale-token rejection after creation was verified live in diff --git a/PowerFactory/tests/test_grid_bootstrap.py b/PowerFactory/tests/test_grid_bootstrap.py index 12d0ebb..3cc0863 100644 --- a/PowerFactory/tests/test_grid_bootstrap.py +++ b/PowerFactory/tests/test_grid_bootstrap.py @@ -149,6 +149,48 @@ def test_confirmation_rejects_changed_create_reuse_plan(self): self.assertIn("confirmation must exactly match", result["message"]) self.assertEqual(len(grid["ElmTerm"]), 1) + def test_confirmation_rejects_changed_destination_folder_plan(self): + for existing_model, add_data_folder in ((False, False), (False, True), (True, True)): + with self.subTest(existing_model=existing_model, add_data_folder=add_data_folder): + self.setUp() + model = None + if existing_model: + model = self.project.CreateObject("IntPrjfolder", "Network Model") + preview = self.call() + self.assertTrue(preview["success"], preview.get("message")) + if model is None: + model = self.project.CreateObject("IntPrjfolder", "Network Model") + if add_data_folder: + model.CreateObject("IntPrjfolder", "Network Data") + + rejected = self.call(confirmation=preview["confirmation_required"]) + self.assertFalse(rejected["success"]) + self.assertIn("confirmation must exactly match", rejected["message"]) + self.assertEqual(self.project.GetContents("*.ElmNet", 1), []) + fresh_preview = self.call() + applied = self.call(confirmation=fresh_preview["confirmation_required"]) + self.assertTrue(applied["success"], applied.get("message")) + self.assertEqual(len(model["IntPrjfolder"][0]["ElmNet"]), 1) + + def test_confirmation_rejects_relocated_destination_folder(self): + model = self.project.CreateObject("IntPrjfolder", "Network Model") + model.CreateObject("IntPrjfolder", "Network Data") + preview = self.call() + model.SetAttribute("loc_name", "Renamed Model") + rejected = self.call(confirmation=preview["confirmation_required"]) + self.assertFalse(rejected["success"]) + self.assertIn("confirmation must exactly match", rejected["message"]) + self.assertEqual(self.project.GetContents("*.ElmNet", 1), []) + + def test_ambiguous_destination_fails_during_preview_without_creating_objects(self): + for name in ("First", "Second"): + parent = self.project.CreateObject("IntPrjfolder", name) + parent.CreateObject("IntPrjfolder", "Network Data") + preview = self.call() + self.assertFalse(preview["success"]) + self.assertIn("Multiple Network Data folders", preview["message"]) + self.assertEqual(self.project.GetContents("*.ElmNet", 1), []) + def test_confirmation_creates_folder_grid_and_buses(self): preview = self.call() diff --git a/PowerFactory/tests/test_grid_pdf_export.py b/PowerFactory/tests/test_grid_pdf_export.py index 7858484..415f333 100644 --- a/PowerFactory/tests/test_grid_pdf_export.py +++ b/PowerFactory/tests/test_grid_pdf_export.py @@ -146,13 +146,26 @@ def test_restores_previous_diagram_after_success_and_failure(self): def test_reports_restoration_failure(self): selected = FakePage("Export Grid") - app = FakeApplication([selected]) - app.current_diagram = FakePage("Previously open diagram") - with tempfile.TemporaryDirectory(dir=os.path.dirname(__file__)) as directory: - with patch.object(app.board, "Show", side_effect=[0, 1]): - success, message = self.run_export(app, os.path.join(directory, "grid.pdf")) - self.assertFalse(success) - self.assertIn("restore previous diagram", message) + for export_fails in (False, True): + for restore_failure in (1, RuntimeError("restore raised")): + with self.subTest(export_fails=export_fails, restore=restore_failure): + app = FakeApplication([selected]) + app.current_diagram = FakePage("Previously open diagram") + with tempfile.TemporaryDirectory(dir=os.path.dirname(__file__)) as directory: + output = os.path.join(directory, "grid.pdf") + with patch.object(app.board, "Show", side_effect=[0, restore_failure]): + if export_fails: + with patch.object(app.command, "Execute", side_effect=RuntimeError("original export failure")): + success, message = self.run_export(app, output) + else: + success, message = self.run_export(app, output) + self.assertEqual(os.path.isfile(output), not export_fails) + self.assertEqual(success, not export_fails, message) + self.assertIn("Warning: could not restore previous diagram", message) + if export_fails: + self.assertTrue(message.startswith("original export failure"), message) + else: + self.assertTrue(message.startswith("Grid diagram 'Export Grid' exported"), message) def test_requires_name_when_multiple_diagrams_are_available(self): app = FakeApplication([FakePage("Overview"), FakePage("Summary Grid")]) From 6b14337841f8082efda478a071cb62e16b37ebe0 Mon Sep 17 00:00:00 2001 From: aswinkrishnapoyil Date: Thu, 15 Oct 2026 12:59:56 +0200 Subject: [PATCH 2/5] Unify EvEMTBench catalog discovery and exposed MCP tools --- PowerFactory/MCP_PowerFactory.py | 374 +----------------- PowerFactory/README.md | 232 ++++++----- PowerFactory/evemtbench.py | 82 +++- .../FT-27_unified_evemtbench_catalog_mcp.txt | 84 ++++ PowerFactory/evidence/README.md | 1 + PowerFactory/functions_overview.txt | 16 +- PowerFactory/tests/check_evemtbench_wheel.py | 18 +- .../test_evemtbench_component_factory.py | 67 ++++ PowerFactory/tests/test_state_inspection.py | 22 +- .../tests/test_transformer_type_library.py | 4 +- 10 files changed, 410 insertions(+), 490 deletions(-) create mode 100644 PowerFactory/evidence/FT-27_unified_evemtbench_catalog_mcp.txt diff --git a/PowerFactory/MCP_PowerFactory.py b/PowerFactory/MCP_PowerFactory.py index 1e2ccad..7b069d6 100644 --- a/PowerFactory/MCP_PowerFactory.py +++ b/PowerFactory/MCP_PowerFactory.py @@ -27,17 +27,6 @@ export_active_grid_pdf Export an active single-line diagram to PDF. create_evemtbench_component Create any supported EvEMTBench component. list_evemtbench_components Inspect the versioned equipment database. - create_evemtbench_transformer Create a new Table 11 TypTr2 and ElmTr2. - list_evemtbench_line_profiles Inspect the overhead-line profile database. - create_evemtbench_line Create a new Table 7 TypLne and ElmLne. - list_evemtbench_cable_profiles Inspect the underground-cable database. - create_evemtbench_cable Create a new Table 6 TypLne and ElmLne. - list_evemtbench_external_grid_profiles Inspect the Table 4 external-grid database. - create_evemtbench_external_grid Create a new Table 4 ElmXnet. - list_evemtbench_load_profiles Inspect the Table 5 load-power database. - create_evemtbench_load Create a new Table 5 ElmLod. - list_evemtbench_synchronous_machine_profiles - Inspect the Table 13 machine database. create_grid_with_buses Bootstrap an ElmNet and initial ElmTerm objects. import_project Import a .pfd file and activate it in PowerFactory. create_study_case Create/activate a study case by name (no simulation run). @@ -1192,21 +1181,24 @@ def export_active_grid_pdf( @mcp.tool() def list_evemtbench_components( - voltage_class: str = "", - rated_power_mva: float | None = None, + component_type: str, + filters: dict[str, Any] | None = None, ) -> str: - """List executable EvEMTBench transformer data without opening PowerFactory. - - Leave both filters empty to list every supported fixed-voltage transformer - class. Set ``voltage_class`` for one class, and optionally select an exact - published ``rated_power_mva``. The response includes Table 10 zero-sequence - rules and Table 12 saturation data even though those fields are not yet - mapped to PowerFactory attributes. + """Inspect one EvEMTBench catalog and discover its creation parameters. + + component_type: transformer, overhead_line, underground_cable, + external_grid, load, or synchronous_machine. Omit filters to see the full + selected catalog. Allowed filters: transformer={voltage_class,rated_power_mva}, + overhead_line={voltage_kv,profile}, underground_cable={profile}, + external_grid={voltage_kv}, load={voltage_kv}, + synchronous_machine={machine_type}. Profile choices: p05, p50, p95. + Returns source data, mapping limits, available_filters and creation_parameters + (names, types, required flags and defaults). No PowerFactory connection is opened. """ _, DIgSILENTAgent = _load_modules() return _to_json(DIgSILENTAgent.list_evemtbench_components( - voltage_class, - rated_power_mva, + component_type, + filters, )) @@ -1219,8 +1211,9 @@ def create_evemtbench_component( ``component_type`` must be ``transformer``, ``overhead_line``, ``underground_cable``, ``external_grid``, ``load``, or - ``synchronous_machine``. Put the corresponding specialized creator's - arguments in ``parameters``. Use an empty ``confirmation`` for the + ``synchronous_machine``. First call list_evemtbench_components for the + selected type to discover its creation_parameters and catalog choices. + Put those inputs in ``parameters``. Use an empty ``confirmation`` for the read-only preview, then repeat the same request with the exact returned ``confirmation_required`` value. Validation, creation, verification, and rollback are delegated unchanged to the existing specialized creator. @@ -1233,339 +1226,6 @@ def create_evemtbench_component( ) -@mcp.tool() -def create_evemtbench_transformer( - voltage_class: str, - rated_power_mva: float, - nominal_frequency_hz: float, - vector_group: str, - clock_number: int, - transformer_name: str, - type_name: str, - high_voltage_bus_name: str, - low_voltage_bus_name: str, - grid_name: str = "", - seed: int = 0, - out_of_service: bool = False, - confirmation: str = "", - open_digsilent: bool = True, -) -> str: - """Preview or create a new transformer type and connected transformer. - - No existing transformer or type is used as a template. Table 11 supplies - the sampled electrical values and winding-connection family. The caller - explicitly supplies the clock number because the paper does not select one - for every transformer. Nominal frequency must explicitly be 50 or 60 Hz; - it is not inferred from the grid. Existing high- and low-voltage buses are required. - An empty ``confirmation`` performs a read-only preview; a confirmed call - creates the ``TypTr2``, connected ``ElmTr2``, cubicles, and closed switches. - The new type is rolled back if element creation fails. Tables 10 and 12 are - stored in the database but are not applied until their PowerFactory - attribute mappings have been validated. - """ - return _agent_result( - "create_evemtbench_transformer", - voltage_class, - rated_power_mva, - nominal_frequency_hz, - vector_group, - clock_number, - transformer_name, - type_name, - high_voltage_bus_name, - low_voltage_bus_name, - grid_name, - seed, - out_of_service, - confirmation, - open_digsilent, - structured=True, - ) - - -@mcp.tool() -def list_evemtbench_line_profiles( - voltage_kv: float | None = None, - profile: str = "", -) -> str: - """List executable EvEMTBench overhead-line sequence profiles. - - The database exposes the paper's 5th, 50th and 95th percentile R/X/C - values per kilometre for 20, 110 and 345 kV overhead lines, plus the - corresponding Table 3 length ranges. This is a read-only catalog tool and - does not open PowerFactory. - """ - _, DIgSILENTAgent = _load_modules() - return _to_json(DIgSILENTAgent.list_evemtbench_line_profiles( - voltage_kv, - profile, - )) - - -@mcp.tool() -def create_evemtbench_line( - voltage_kv: float, - nominal_frequency_hz: float, - rated_current_ka: float, - profile: str, - length_km: float, - line_name: str, - type_name: str, - bus1_name: str, - bus2_name: str, - grid_name: str = "", - out_of_service: bool = False, - confirmation: str = "", - open_digsilent: bool = True, -) -> str: - """Preview or create a new paper-backed TypLne and connected ElmLne. - - The caller selects a published ``p05``, ``p50`` or ``p95`` per-kilometre - electrical profile and supplies the engineering current rating. Both buses - must already exist at the selected 20, 110 or 345 kV voltage level, and the - requested length must remain inside the paper's range for that voltage. - Capacitances are converted from nF/km to PowerFactory's uF/km fields. - - An empty confirmation performs a read-only preview. A confirmed call - creates a new ``TypLne``, connected ``ElmLne``, cubicles and closed circuit - breakers, rolling the new objects back if creation fails. This is a sequence - equivalent; it does not reproduce the paper's geometric ULM model. - """ - return _agent_result( - "create_evemtbench_line", - voltage_kv, - nominal_frequency_hz, - rated_current_ka, - profile, - length_km, - line_name, - type_name, - bus1_name, - bus2_name, - grid_name, - out_of_service, - confirmation, - open_digsilent, - structured=True, - ) - - -@mcp.tool() -def list_evemtbench_cable_profiles( - profile: str = "", -) -> str: - """List executable 20 kV EvEMTBench cable sequence profiles. - - The database exposes Table 6's 5th, 50th and 95th percentile R/X/C values - per kilometre and the Table 3 cable-length range. Construction and - installation data are returned for inspection but are not written. This - read-only catalog tool does not open PowerFactory. - """ - _, DIgSILENTAgent = _load_modules() - return _to_json(DIgSILENTAgent.list_evemtbench_cable_profiles(profile)) - - -@mcp.tool() -def create_evemtbench_cable( - nominal_frequency_hz: float, - rated_current_ka: float, - profile: str, - length_km: float, - cable_name: str, - type_name: str, - bus1_name: str, - bus2_name: str, - grid_name: str = "", - out_of_service: bool = False, - confirmation: str = "", - open_digsilent: bool = True, -) -> str: - """Preview or create a 20 kV Table 6 cable sequence equivalent. - - The caller selects a published ``p05``, ``p50`` or ``p95`` profile, - supplies the engineering current rating, and selects 50 or 60 Hz. Both - existing buses must be 20 kV, and length must be 0.5 to 3.75 km. An empty - confirmation is read-only; a confirmed call creates a new ``TypLne``, - connected ``ElmLne``, cubicles, and closed circuit breakers. Failed - creation is rolled back. The tool does not reproduce the paper's geometric - ULM cable model. - """ - return _agent_result( - "create_evemtbench_cable", - nominal_frequency_hz, - rated_current_ka, - profile, - length_km, - cable_name, - type_name, - bus1_name, - bus2_name, - grid_name, - out_of_service, - confirmation, - open_digsilent, - structured=True, - ) - - -@mcp.tool() -def list_evemtbench_external_grid_profiles( - voltage_kv: float | None = None, -) -> str: - """List executable EvEMTBench Table 4 external-grid profiles. - - Leave ``voltage_kv`` empty to return the 110 kV and combined 380/345 kV - classes. An exact 110, 345, or 380 kV filter returns the applicable source - ranges. This read-only catalog tool does not open PowerFactory. - """ - _, DIgSILENTAgent = _load_modules() - return _to_json( - DIgSILENTAgent.list_evemtbench_external_grid_profiles(voltage_kv) - ) - - -@mcp.tool() -def create_evemtbench_external_grid( - voltage_kv: float, - external_grid_name: str, - bus_name: str, - grid_name: str = "", - seed: int = 0, - out_of_service: bool = False, - confirmation: str = "", - open_digsilent: bool = True, -) -> str: - """Preview or create a seeded Table 4 external-grid equivalent. - - The selected existing bus must be exactly 110, 345, or 380 kV. Table 4 - supplies uniformly sampled short-circuit power, positive- and zero-sequence - ratios, voltage setpoint, and initial phase angle. An empty confirmation is - read-only; a confirmed call creates one connected slack ``ElmXnet`` plus a - cubicle and closed circuit breaker. Failed creation is rolled back. - """ - return _agent_result( - "create_evemtbench_external_grid", - voltage_kv, - external_grid_name, - bus_name, - grid_name, - seed, - out_of_service, - confirmation, - open_digsilent, - structured=True, - ) - - -@mcp.tool() -def list_evemtbench_load_profiles( - voltage_kv: float | None = None, -) -> str: - """List executable EvEMTBench Table 5 load-power profiles. - - Leave ``voltage_kv`` empty to return the 20, 110 and 345 kV profiles. - The catalog also returns the paper's ZIP coefficients, which remain - read-only until their PowerFactory attributes are live-validated. - """ - _, DIgSILENTAgent = _load_modules() - return _to_json( - DIgSILENTAgent.list_evemtbench_load_profiles(voltage_kv) - ) - - -@mcp.tool() -def create_evemtbench_load( - voltage_kv: float, - load_name: str, - bus_name: str, - grid_name: str = "", - seed: int = 0, - out_of_service: bool = False, - confirmation: str = "", - open_digsilent: bool = True, -) -> str: - """Preview or create a seeded Table 5 balanced load. - - At 110 and 345 kV, active and reactive powers are sampled directly. At - 20 kV, apparent power and a reactive-power angle spanning the paper's - 0.95 capacitive to 0.8 inductive limits are sampled, then converted to P - and Q. An empty confirmation is read-only; a confirmed call creates one - connected ``ElmLod`` plus a cubicle and closed circuit breaker. Failed - creation is rolled back. - """ - return _agent_result( - "create_evemtbench_load", - voltage_kv, - load_name, - bus_name, - grid_name, - seed, - out_of_service, - confirmation, - open_digsilent, - structured=True, - ) - - -@mcp.tool() -def list_evemtbench_synchronous_machine_profiles( - machine_type: str = "", -) -> str: - """List EvEMTBench Table 13 synchronous-machine profiles. - - Use ``salient_pole`` or ``round_rotor`` to filter the catalog. Leave the - value empty to return both. This tool is read-only and does not open - PowerFactory. Round-rotor creation is available separately; salient-pole - creation remains disabled because Table 13 omits q-axis transient data. - """ - _, DIgSILENTAgent = _load_modules() - return _to_json( - DIgSILENTAgent.list_evemtbench_synchronous_machine_profiles( - machine_type - ) - ) - - -@mcp.tool() -def create_evemtbench_synchronous_machine( - machine_type: str, - rated_power_mva: float, - machine_name: str, - type_name: str, - bus_name: str, - grid_name: str = "", - seed: int = 0, - active_power_mw: float = 0.0, - reactive_power_mvar: float = 0.0, - out_of_service: bool = False, - confirmation: str = "", - open_digsilent: bool = True, -) -> str: - """Preview or create a seeded Table 13 round-rotor machine. - - The MVA rating must be 25 to 1200. Its rated voltage is derived from the - paper's rating rule, so the selected existing bus must be at 6.3, 10.5, - 21, or 27 kV as applicable. An empty confirmation is read-only; a confirmed - call creates a new ``TypSym``, connected ``ElmSym``, cubicle, and closed - circuit breaker. New objects are rolled back on failure. ``salient_pole`` - remains catalog-only because Table 13 does not provide xq transient data. - """ - return _agent_result( - "create_evemtbench_synchronous_machine", - machine_type, - rated_power_mva, - machine_name, - type_name, - bus_name, - grid_name, - seed, - active_power_mw, - reactive_power_mvar, - out_of_service, - confirmation, - open_digsilent, - structured=True, - ) @mcp.tool() diff --git a/PowerFactory/README.md b/PowerFactory/README.md index 57aa9e9..3437422 100644 --- a/PowerFactory/README.md +++ b/PowerFactory/README.md @@ -64,18 +64,7 @@ Output folder: CSV results, PNG plots, optional .pfd export | `remove_contingency_result_variables` | Removes variables from matching AC or DC result recording selections without running a calculation. | | `export_active_grid_pdf` | Exports one active-project single-line diagram to a new PDF file. | | `create_evemtbench_component` | Routes one nested parameter object to any validated EvEMTBench component creator. | -| `list_evemtbench_components` | Reads the versioned EvEMTBench transformer database without opening PowerFactory. | -| `create_evemtbench_transformer` | Previews or creates a new database-backed `TypTr2` and connected `ElmTr2` without a template. | -| `list_evemtbench_line_profiles` | Reads the versioned 20/110/345 kV overhead-line sequence profiles without opening PowerFactory. | -| `create_evemtbench_line` | Previews or creates a new profile-backed `TypLne` and connected `ElmLne` without a template. | -| `list_evemtbench_cable_profiles` | Reads the versioned 20 kV underground-cable sequence profiles without opening PowerFactory. | -| `create_evemtbench_cable` | Previews or creates a new Table 6 cable-equivalent `TypLne` and connected `ElmLne`. | -| `list_evemtbench_external_grid_profiles` | Reads the versioned EvEMTBench Table 4 external-grid ranges without opening PowerFactory. | -| `create_evemtbench_external_grid` | Previews or creates a seeded Table 4 `ElmXnet` on an existing bus. | -| `list_evemtbench_load_profiles` | Reads the versioned EvEMTBench Table 5 load-power ranges without opening PowerFactory. | -| `create_evemtbench_load` | Previews or creates a seeded Table 5 `ElmLod` on an existing bus. | -| `list_evemtbench_synchronous_machine_profiles` | Reads the versioned EvEMTBench Table 13 synchronous-machine ranges without opening PowerFactory. | -| `create_evemtbench_synchronous_machine` | Previews or creates a seeded round-rotor `TypSym` and connected `ElmSym`. | +| `list_evemtbench_components` | Lists any of six EvEMTBench catalogs, filters and required creation parameters without opening PowerFactory. | | `create_grid_with_buses` | Previews or bootstraps an `ElmNet` and initial `ElmTerm` objects in the active project. | | `import_project` | Imports a `.pfd` project file and activates it. | | `create_study_case` | Creates (or activates) a study case by name, copying from a base case when needed. Supports `request_id` for idempotency. | @@ -153,7 +142,8 @@ Its Python Reference, section 5.6.26, documents `IntMon.RemoveVar` return codes: | `DIgSILENTAgent.export_results_to_csv` | Exports the RMS result object to CSV via ComRes. | | `DIgSILENTAgent.export_active_grid_pdf` | Exports a selected Graphics Board single-line diagram to PDF via ComWr. | | `DIgSILENTAgent.create_evemtbench_component` | Validates and routes a unified request to an existing specialized component creator. | -| `DIgSILENTAgent.list_evemtbench_components` | Validates and filters the versioned transformer database. | +| `DIgSILENTAgent.list_evemtbench_components` | Routes catalog requests for all six types and returns parameter metadata. | +| `DIgSILENTAgent.list_evemtbench_transformer_profiles` | Internal transformer catalog implementation. | | `DIgSILENTAgent.create_evemtbench_transformer` | Samples, previews, creates, connects, verifies, and rolls back a new transformer type and element. | | `DIgSILENTAgent.list_evemtbench_line_profiles` | Validates and filters the versioned overhead-line sequence profiles. | | `DIgSILENTAgent.create_evemtbench_line` | Previews, creates, connects, verifies, and rolls back a new overhead-line type and element. | @@ -299,7 +289,8 @@ some graphical or cubicle cleanup failed; do not retry the component deletion. `create_evemtbench_component` is the common write entry point for the six validated component families: `transformer`, `overhead_line`, `underground_cable`, `external_grid`, `load`, and `synchronous_machine`. -Supply the selected specialized creator's arguments inside `parameters`: +Use the creation inputs returned by `list_evemtbench_components` inside +`parameters`: ```python create_evemtbench_component( @@ -321,8 +312,39 @@ An empty `confirmation` keeps the request in preview mode. Repeat the same request with the exact returned `confirmation_required` value to apply it. The unified tool delegates validation, seeded sampling, creation, retained-value checks, and rollback to the existing specialized creator without changing its -behavior. The specialized tools remain available for backward compatibility -and for clients that benefit from their explicit per-component schemas. +behavior. Only the unified create and list tools are exposed through MCP. +Specialized methods remain internal Python implementation details. + +### Catalog discovery and migration + +Call `list_evemtbench_components(component_type, filters)` before creating equipment. +Omit `filters` to inspect the complete selected catalog. The response includes +`available_filters` and `creation_parameters`, with names, types, required flags, +and defaults. Catalog-only data and mapping limits remain in the response. + +| component_type | Optional filters | +|---|---| +| `transformer` | `voltage_class`, `rated_power_mva` (requires voltage_class) | +| `overhead_line` | `voltage_kv`, `profile` | +| `underground_cable` | `profile` | +| `external_grid` | `voltage_kv` | +| `load` | `voltage_kv` | +| `synchronous_machine` | `machine_type` | + +```python +list_evemtbench_components( + component_type="overhead_line", + filters={"voltage_kv": 110, "profile": "p50"}, +) +``` + +This intentionally changes the MCP interface. Replace previous specialized +create calls with `create_evemtbench_component` and place their arguments inside +`parameters`. Replace specialized list calls with `list_evemtbench_components` +and place their filters inside `filters`. The old transformer-only listing now +requires `component_type="transformer"`; move voltage_class/rated_power_mva into +`filters`. Restart the server and refresh the client's tool list after updating. +The examples below use the new public interface. --- @@ -334,7 +356,7 @@ stores the fixed-voltage transformer classes from EvEMTBench Tables 10-12. Use `list_evemtbench_components` to inspect classes, ratings, ranges, vector groups, zero-sequence rules, and saturation data without opening PowerFactory. -`create_evemtbench_transformer` creates a new `TypTr2` directly; it does not +`create_evemtbench_component(component_type="transformer", parameters=...)` creates a new `TypTr2` directly; it does not copy or require an existing transformer. It also creates an `ElmTr2`, two cubicles, and closed circuit breakers between existing buses. The type is stored in the project library's `Transformer Types` folder; a confirmed call @@ -342,19 +364,22 @@ creates that folder when it is absent from a greenfield project. The first call is a read-only preview when `confirmation` is empty: ```text -create_evemtbench_transformer( - voltage_class="110/20", - rated_power_mva=20, - nominal_frequency_hz=60, - vector_group="YNd", - clock_number=11, - transformer_name="Research Transformer", - type_name="Research Transformer Type", - high_voltage_bus_name="HV Bus", - low_voltage_bus_name="LV Bus", - seed=42, - confirmation="", - open_digsilent=true +create_evemtbench_component( + component_type="transformer", + parameters={ + "voltage_class": "110/20", + "rated_power_mva": 20, + "nominal_frequency_hz": 60, + "vector_group": "YNd", + "clock_number": 11, + "transformer_name": "Research Transformer", + "type_name": "Research Transformer Type", + "high_voltage_bus_name": "HV Bus", + "low_voltage_bus_name": "LV Bus", + "seed": 42, + "confirmation": "", + "open_digsilent": True, + }, ) ``` @@ -390,27 +415,30 @@ and limitations. [`data/evemtbench_overhead_lines.json`](data/evemtbench_overhead_lines.json) stores the published 5th, 50th and 95th percentile sequence parameters from Table 7 for 20, 110 and 345 kV overhead lines, together with the corresponding -Table 3 length ranges. `list_evemtbench_line_profiles` inspects this catalog +Table 3 length ranges. `list_evemtbench_components(component_type="overhead_line")` inspects this catalog without opening PowerFactory. -`create_evemtbench_line` creates a new `TypLne` directly and connects a new +`create_evemtbench_component(component_type="overhead_line", parameters=...)` creates a new `TypLne` directly and connects a new `ElmLne` between two existing equal-voltage buses. The caller selects `p05`, `p50` or `p95`, supplies the actual current rating, and chooses 50 or 60 Hz. The first call is a read-only preview: ```text -create_evemtbench_line( - voltage_kv=110, - nominal_frequency_hz=50, - rated_current_ka=1, - profile="p50", - length_km=10, - line_name="Research Line", - type_name="Research Line Type", - bus1_name="Bus A", - bus2_name="Bus B", - confirmation="", - open_digsilent=true +create_evemtbench_component( + component_type="overhead_line", + parameters={ + "voltage_kv": 110, + "nominal_frequency_hz": 50, + "rated_current_ka": 1, + "profile": "p50", + "length_km": 10, + "line_name": "Research Line", + "type_name": "Research Line Type", + "bus1_name": "Bus A", + "bus2_name": "Bus B", + "confirmation": "", + "open_digsilent": True, + }, ) ``` @@ -434,28 +462,31 @@ sampling distribution. Tower and conductor geometry remains catalog-only. [`data/evemtbench_underground_cables.json`](data/evemtbench_underground_cables.json) stores Table 6's published 5th, 50th and 95th percentile sequence parameters for 20 kV underground cables and Table 3's 0.5 to 3.75 km length range. -`list_evemtbench_cable_profiles` returns those profiles plus the paper's +`list_evemtbench_components(component_type="underground_cable")` returns those profiles plus the paper's construction and installation data without opening PowerFactory. -`create_evemtbench_cable` creates a new `TypLne` sequence equivalent and +`create_evemtbench_component(component_type="underground_cable", parameters=...)` creates a new `TypLne` sequence equivalent and connects a new `ElmLne` between two existing 20 kV buses. The caller chooses a published percentile, 50 or 60 Hz, and an engineering current rating because Table 6 does not publish cable ampacity. The first call is a read-only preview: ```text -create_evemtbench_cable( - nominal_frequency_hz=50, - rated_current_ka=0.4, - profile="p50", - length_km=2, - cable_name="Research Cable", - type_name="Research Cable Type", - bus1_name="MV Bus A", - bus2_name="MV Bus B", - grid_name="Research Grid", - out_of_service=true, - confirmation="", - open_digsilent=true +create_evemtbench_component( + component_type="underground_cable", + parameters={ + "nominal_frequency_hz": 50, + "rated_current_ka": 0.4, + "profile": "p50", + "length_km": 2, + "cable_name": "Research Cable", + "type_name": "Research Cable Type", + "bus1_name": "MV Bus A", + "bus2_name": "MV Bus B", + "grid_name": "Research Grid", + "out_of_service": True, + "confirmation": "", + "open_digsilent": True, + }, ) ``` @@ -476,23 +507,26 @@ spacing, and earth-resistivity data for a future geometric implementation. [`data/evemtbench_external_grids.json`](data/evemtbench_external_grids.json) stores the Table 4 ranges for 110 kV and combined 380/345 kV external-grid -equivalents. `list_evemtbench_external_grid_profiles` inspects the ranges +equivalents. `list_evemtbench_components(component_type="external_grid")` inspects the ranges without opening PowerFactory. -`create_evemtbench_external_grid` uses a supplied seed to sample every Table 4 +`create_evemtbench_component(component_type="external_grid", parameters=...)` uses a supplied seed to sample every Table 4 interval uniformly and connects a new slack `ElmXnet` to an existing bus. The bus must be exactly 110, 345, or 380 kV. The first call is a read-only preview: ```text -create_evemtbench_external_grid( - voltage_kv=110, - external_grid_name="Research External Grid", - bus_name="HV Bus", - grid_name="Research Grid", - seed=42, - out_of_service=true, - confirmation="", - open_digsilent=true +create_evemtbench_component( + component_type="external_grid", + parameters={ + "voltage_kv": 110, + "external_grid_name": "Research External Grid", + "bus_name": "HV Bus", + "grid_name": "Research Grid", + "seed": 42, + "out_of_service": True, + "confirmation": "", + "open_digsilent": True, + }, ) ``` @@ -510,24 +544,27 @@ PowerFactory 2026 SP1; SP4 validation remains pending. [`data/evemtbench_loads.json`](data/evemtbench_loads.json) stores the Table 5 load-power ranges for 20, 110, and 345 kV systems. The read-only -`list_evemtbench_load_profiles` tool returns these ranges without opening +`list_evemtbench_components(component_type="load")` tool returns these ranges without opening PowerFactory. -`create_evemtbench_load` samples active and reactive power directly at 110 and +`create_evemtbench_component(component_type="load", parameters=...)` samples active and reactive power directly at 110 and 345 kV. At 20 kV it samples apparent power and a reactive-power angle between the published 0.95 capacitive and 0.8 inductive limits, then calculates P and Q. The first call is a read-only preview: ```text -create_evemtbench_load( - voltage_kv=20, - load_name="Research Load", - bus_name="MV Bus", - grid_name="Research Grid", - seed=42, - out_of_service=true, - confirmation="", - open_digsilent=true +create_evemtbench_component( + component_type="load", + parameters={ + "voltage_kv": 20, + "load_name": "Research Load", + "bus_name": "MV Bus", + "grid_name": "Research Grid", + "seed": 42, + "out_of_service": True, + "confirmation": "", + "open_digsilent": True, + }, ) ``` @@ -544,10 +581,10 @@ until their PowerFactory attribute mapping is live-validated. [`data/evemtbench_synchronous_machines.json`](data/evemtbench_synchronous_machines.json) stores the salient-pole and round-rotor ranges from Table 13, including the rated-voltage selection rule and parameter dependencies. Use -`list_evemtbench_synchronous_machine_profiles` to inspect both profiles or to +`list_evemtbench_components(component_type="synchronous_machine")` to inspect both profiles or to filter by `salient_pole` or `round_rotor`. -`create_evemtbench_synchronous_machine` supports the round-rotor profile. The +`create_evemtbench_component(component_type="synchronous_machine", parameters=...)` supports the round-rotor profile. The caller selects a rating from 25 to 1200 MVA and an existing bus. The tool derives the rated voltage using Table 13 (6.3, 10.5, 21, or 27 kV), samples the remaining ranges reproducibly from `seed`, and validates all published @@ -555,19 +592,22 @@ reactance and saturation dependencies. The requested P/Q operating point must fit the MVA rating and the paper's reactive-power limits. ```python -create_evemtbench_synchronous_machine( - machine_type="round_rotor", - rated_power_mva=100, - machine_name="Research Generator", - type_name="Research Generator Type", - bus_name="Generator Bus 10.5", - grid_name="Research Grid", - seed=42, - active_power_mw=60, - reactive_power_mvar=5, - out_of_service=true, - confirmation="", - open_digsilent=true +create_evemtbench_component( + component_type="synchronous_machine", + parameters={ + "machine_type": "round_rotor", + "rated_power_mva": 100, + "machine_name": "Research Generator", + "type_name": "Research Generator Type", + "bus_name": "Generator Bus 10.5", + "grid_name": "Research Grid", + "seed": 42, + "active_power_mw": 60, + "reactive_power_mvar": 5, + "out_of_service": True, + "confirmation": "", + "open_digsilent": True, + }, ) ``` diff --git a/PowerFactory/evemtbench.py b/PowerFactory/evemtbench.py index 03e03ec..f00af5a 100644 --- a/PowerFactory/evemtbench.py +++ b/PowerFactory/evemtbench.py @@ -5,10 +5,79 @@ import json import math import random -from typing import Any, Optional +from typing import Any, Optional, get_args class EvEMTBenchMixin: + _evemtbench_creators = { + "transformer": "create_evemtbench_transformer", + "overhead_line": "create_evemtbench_line", + "underground_cable": "create_evemtbench_cable", + "external_grid": "create_evemtbench_external_grid", + "load": "create_evemtbench_load", + "synchronous_machine": "create_evemtbench_synchronous_machine", + } + + @staticmethod + def _evemtbench_parameter_schema(method): + schema = {} + types = {str: "string", float: "number", int: "integer", bool: "boolean"} + for name, parameter in inspect.signature(method).parameters.items(): + if name.startswith("_"): + continue + annotation = parameter.annotation + arguments = get_args(annotation) + nullable = type(None) in arguments + if nullable: + annotation = next(arg for arg in arguments if arg is not type(None)) + entry = { + "type": types.get(annotation, str(annotation)), + "required": parameter.default is inspect.Parameter.empty, + } + if nullable: + entry["nullable"] = True + if parameter.default is not inspect.Parameter.empty: + entry["default"] = parameter.default + schema[name] = entry + return schema + + @classmethod + def list_evemtbench_components(cls, component_type: str, filters=None): + """List one component catalog and its creation inputs without PowerFactory.""" + listings = { + "transformer": "list_evemtbench_transformer_profiles", + "overhead_line": "list_evemtbench_line_profiles", + "underground_cable": "list_evemtbench_cable_profiles", + "external_grid": "list_evemtbench_external_grid_profiles", + "load": "list_evemtbench_load_profiles", + "synchronous_machine": "list_evemtbench_synchronous_machine_profiles", + } + kind = str(component_type or "").strip().lower().replace("-", "_") + if kind not in listings: + return { + "success": False, + "component_type": kind, + "supported_component_types": list(listings), + "message": "component_type must be one of: " + ", ".join(listings), + } + listing = getattr(cls, listings[kind]) + metadata = { + "component_type": kind, + "available_filters": cls._evemtbench_parameter_schema(listing), + "creation_parameters": cls._evemtbench_parameter_schema( + getattr(cls, cls._evemtbench_creators[kind]) + ), + } + if filters is None: + filters = {} + if not isinstance(filters, dict): + return {"success": False, "message": "filters must be an object", **metadata} + try: + inspect.signature(listing).bind(**filters) + except TypeError as exc: + return {"success": False, "message": f"Invalid {kind} filters: {exc}", **metadata} + return {**listing(**filters), **metadata} + @classmethod def _rollback_equipment_type(cls, equipment_folder, created_type, created_folder, type_name, type_class, folder_name): @@ -1191,7 +1260,7 @@ def sampled(interval): } @classmethod - def list_evemtbench_components( + def list_evemtbench_transformer_profiles( cls, voltage_class: str = "", rated_power_mva: Optional[float] = None, @@ -1382,14 +1451,7 @@ def create_evemtbench_component( parameters: dict[str, Any], ) -> dict[str, Any]: """Route one component request to its validated EvEMTBench creator.""" - creators = { - "transformer": "create_evemtbench_transformer", - "overhead_line": "create_evemtbench_line", - "underground_cable": "create_evemtbench_cable", - "external_grid": "create_evemtbench_external_grid", - "load": "create_evemtbench_load", - "synchronous_machine": "create_evemtbench_synchronous_machine", - } + creators = cls._evemtbench_creators kind = str(component_type or "").strip().lower().replace("-", "_") method_name = creators.get(kind) supported = ", ".join(creators) diff --git a/PowerFactory/evidence/FT-27_unified_evemtbench_catalog_mcp.txt b/PowerFactory/evidence/FT-27_unified_evemtbench_catalog_mcp.txt new file mode 100644 index 0000000..16983de --- /dev/null +++ b/PowerFactory/evidence/FT-27_unified_evemtbench_catalog_mcp.txt @@ -0,0 +1,84 @@ +Evidence ID: FT-27 +Title: Unified EvEMTBench MCP exposure and filtered catalog listing +Date: 15 October 2026 +Classification: Compiled evidence record +Source: Contributor-supplied VS Code MCP client output from the development PC +Revision: PR #122 commit 5993fc8 plus patch 0107, before commit + +Scope and provenance: +The contributor restarted the powerFactoryUnified MCP server using the +PowerMCP-tests-layout checkout and supplied the client's tool-exposure report +and catalog response. No independent tool-registry capture or byte-for-byte +running-source verification was supplied. +This read-only catalog call does not open PowerFactory or require a project. + +The client reported only these exposed EvEMTBench tools: +- list_evemtbench_components +- create_evemtbench_component +No specialized EvEMTBench create/list tools were reported visible. + +Call: +list_evemtbench_components( + component_type="overhead_line", + filters={"voltage_kv": 110, "profile": "p50"} +) + +Selected response fields (extract from supplied JSON): +success: true +component_type: overhead_line +catalog_id: evemtbench-overhead-lines-tables-3-7-9-v1 +source.tables: [3, 7, 9] +affiliation: Institute of Electrical Energy Systems, Friedrich-Alexander-Universität Erlangen-Nürnberg (FAU) +returned_count: 1 +results[0].voltage_class: "110" +results[0].nominal_voltage_kv: 110.0 +results[0].length_km: {"min": 1.0, "max": 32.0} +results[0].electrical_profiles: +{ + "p50": { + "r1_ohm_per_km": 0.06, + "x1_ohm_per_km": 0.29, + "c1_nf_per_km": 12.58, + "r0_ohm_per_km": 0.31, + "x0_ohm_per_km": 1.07, + "c0_nf_per_km": 5.59 + } +} +powerfactory.source_capacitance_unit: nF/km +powerfactory.powerfactory_capacitance_unit: uF/km +powerfactory.capacitance_conversion_factor: 0.001 +catalog_only.geometry.reason: +Tower and conductor geometry is retained in the human-readable catalog but +is not written by the TypLne sequence-equivalent creator. + +Filter discovery: +voltage_kv: number, optional, nullable, default null +profile: string, optional, default "" + +Creation-parameter discovery: +voltage_kv, nominal_frequency_hz, rated_current_ka, length_km: + number, required +profile, line_name, type_name, bus1_name, bus2_name: + string, required +grid_name: string, optional, default "" +out_of_service: boolean, optional, default false +confirmation: string, optional, default "" +open_digsilent: boolean, optional, default true + +Related automated validation: +Development-PC output: 166 tests and 85 subtests passed in 6.90 seconds. +The focused tests cover all six unified catalog routes, filter routing, +parameter discovery, invalid filters, and the two-tool MCP registration surface. +Local wheel build and six installed unified catalog listings also passed. + +Incidental client behavior: +The client first attempted PowerMCP-PSCAD-list_documentation, which failed. +This is outside the requested PowerFactory test and is not validation evidence +for the unified tools. + +Conclusion and limits: +The supplied response verifies filtered overhead-line listing and creation-input +discovery through the refreshed MCP interface. No PowerFactory objects were +created or modified. No confirmed creation was retested in this run. The other +five catalog routes were tested automatically and from the installed wheel, +not through this captured MCP client call. No new CI result is claimed. diff --git a/PowerFactory/evidence/README.md b/PowerFactory/evidence/README.md index cb326d9..35e46a0 100644 --- a/PowerFactory/evidence/README.md +++ b/PowerFactory/evidence/README.md @@ -2,6 +2,7 @@ Latest follow-up records: +- [FT-27](FT-27_unified_evemtbench_catalog_mcp.txt): unified MCP tool exposure and filtered overhead-line listing with creation-parameter discovery. - [FT-25](FT-25_bootstrap_confirmation_binding_sp1.txt): bootstrap stale-token rejection after the create/reuse plan changed. - [FT-26](FT-26_pdf_diagram_restoration_sp1.txt): restoration of the previously active single-line diagram after successful PDF export. diff --git a/PowerFactory/functions_overview.txt b/PowerFactory/functions_overview.txt index 58ba687..f68dc74 100644 --- a/PowerFactory/functions_overview.txt +++ b/PowerFactory/functions_overview.txt @@ -17,18 +17,7 @@ Implemented MCP tools - get_config: returns the active configuration file. - export_active_grid_pdf: exports one active-project single-line diagram to a new PDF file. - create_evemtbench_component: routes a component type and parameter object to any validated EvEMTBench creator while preserving preview, confirmation, verification, and rollback behavior. -- list_evemtbench_components: validates and filters the versioned EvEMTBench transformer database without opening PowerFactory. -- create_evemtbench_transformer: previews or creates a new database-backed TypTr2 and connected ElmTr2 without an existing template. -- list_evemtbench_line_profiles: validates and filters the versioned 20/110/345 kV overhead-line sequence profiles without opening PowerFactory. -- create_evemtbench_line: previews or creates a new profile-backed TypLne and connected ElmLne without an existing template. -- list_evemtbench_cable_profiles: validates and filters the versioned 20 kV underground-cable sequence profiles without opening PowerFactory. -- create_evemtbench_cable: previews or creates a Table 6 cable-equivalent TypLne and connected ElmLne without an existing template. -- list_evemtbench_external_grid_profiles: validates and filters the versioned Table 4 external-grid ranges without opening PowerFactory. -- create_evemtbench_external_grid: previews or creates a seeded Table 4 ElmXnet on an existing 110, 345, or 380 kV bus. -- list_evemtbench_load_profiles: validates and filters the versioned Table 5 load-power ranges without opening PowerFactory. -- create_evemtbench_load: previews or creates a seeded Table 5 ElmLod on an existing 20, 110, or 345 kV bus. -- list_evemtbench_synchronous_machine_profiles: validates and filters the versioned Table 13 salient-pole and round-rotor ranges without opening PowerFactory. -- create_evemtbench_synchronous_machine: previews or creates a seeded round-rotor TypSym and connected ElmSym without an existing template. +- list_evemtbench_components: lists any of the six equipment catalogs with component-specific filters and creation-parameter discovery, without opening PowerFactory. - create_grid_with_buses: previews or bootstraps and activates an ElmNet with one to 100 initial ElmTerm objects in the active project and study case. - import_project: imports a .pfd project and activates it. - create_study_case: creates or activates a study case with optional idempotency support. @@ -70,7 +59,8 @@ Implemented simulation engine behavior - DIgSILENTAgent.export_results_to_csv: exports the RMS results to CSV via ComRes. - DIgSILENTAgent.export_active_grid_pdf: exports a selected Graphics Board single-line diagram to PDF via ComWr. - DIgSILENTAgent.create_evemtbench_component: validates and routes unified component requests to the existing specialized creator. -- DIgSILENTAgent.list_evemtbench_components: validates and filters the transformer catalog. +- DIgSILENTAgent.list_evemtbench_components: routes unified catalog requests and returns filter/creation parameter metadata. +- DIgSILENTAgent.list_evemtbench_transformer_profiles: validates and filters the transformer catalog internally. - DIgSILENTAgent.create_evemtbench_transformer: samples, previews, creates, connects, verifies, and rolls back a transformer type and element. - DIgSILENTAgent.list_evemtbench_line_profiles: validates and filters the overhead-line profile catalog. - DIgSILENTAgent.create_evemtbench_line: previews, creates, connects, verifies, and rolls back an overhead-line type and element. diff --git a/PowerFactory/tests/check_evemtbench_wheel.py b/PowerFactory/tests/check_evemtbench_wheel.py index 77bb0cc..ef5cae9 100644 --- a/PowerFactory/tests/check_evemtbench_wheel.py +++ b/PowerFactory/tests/check_evemtbench_wheel.py @@ -58,22 +58,20 @@ agent_module = importlib.util.module_from_spec(agent_spec) agent_spec.loader.exec_module(agent_module) agent = agent_module.DIgSILENTAgent -listing_calls = ( - ("transformers", agent.list_evemtbench_components), - ("overhead lines", agent.list_evemtbench_line_profiles), - ("underground cables", agent.list_evemtbench_cable_profiles), - ("external grids", agent.list_evemtbench_external_grid_profiles), - ("loads", agent.list_evemtbench_load_profiles), - ("synchronous machines", agent.list_evemtbench_synchronous_machine_profiles), +component_types = ( + "transformer", "overhead_line", "underground_cable", + "external_grid", "load", "synchronous_machine", ) -for label, listing in listing_calls: - result = listing() +for label in component_types: + result = agent.list_evemtbench_components(label) if not result.get("success"): raise SystemExit(f"installed-wheel {label} listing failed: {result.get('message')}") if result.get("returned_count", 0) < 1: raise SystemExit(f"installed-wheel {label} listing returned no catalog entries") + if not result.get("creation_parameters") or "available_filters" not in result: + raise SystemExit(f"installed-wheel {label} listing omitted parameter discovery") print( f"Wheel catalog smoke test passed: {len(CATALOGS)} JSON catalogs loaded " - f"and {len(listing_calls)} read-only listing APIs succeeded from {catalog_root}" + f"and {len(component_types)} unified catalog listings succeeded from {catalog_root}" ) diff --git a/PowerFactory/tests/test_evemtbench_component_factory.py b/PowerFactory/tests/test_evemtbench_component_factory.py index 7e9ac9b..366cf5b 100644 --- a/PowerFactory/tests/test_evemtbench_component_factory.py +++ b/PowerFactory/tests/test_evemtbench_component_factory.py @@ -1,8 +1,75 @@ import unittest +from unittest.mock import patch import Agent_DIgSILENT as agent_module +class UnifiedEvEMTBenchListingTest(unittest.TestCase): + def test_lists_all_six_catalogs_without_powerfactory(self): + agent = agent_module.DIgSILENTAgent + with patch.object(agent, "_get_application", side_effect=AssertionError("must stay offline")): + for kind in agent._evemtbench_creators: + with self.subTest(component_type=kind): + result = agent.list_evemtbench_components(kind) + self.assertTrue(result["success"], result.get("message")) + self.assertEqual(result["component_type"], kind) + self.assertGreater(result["returned_count"], 0) + self.assertIn("source", result) + self.assertIn("catalog_id", result) + inputs = result["creation_parameters"] + self.assertEqual(inputs["confirmation"], + {"type": "string", "required": False, "default": ""}) + self.assertTrue(inputs["open_digsilent"]["default"]) + self.assertFalse(any(name.startswith("_") for name in inputs)) + transformer = agent.list_evemtbench_components("transformer") + self.assertEqual(transformer["creation_parameters"]["clock_number"], + {"type": "integer", "required": True}) + line = agent.list_evemtbench_components("overhead_line") + self.assertTrue(line["available_filters"]["voltage_kv"]["nullable"]) + self.assertFalse(line["available_filters"]["voltage_kv"]["required"]) + + def test_routes_filters_and_preserves_catalog_limits(self): + agent = agent_module.DIgSILENTAgent + cases = ( + ("transformer", {"voltage_class": "345/25", "rated_power_mva": 800}), + ("overhead_line", {"voltage_kv": 110, "profile": "p50"}), + ("underground_cable", {"profile": "p95"}), + ("external_grid", {"voltage_kv": 110}), + ("load", {"voltage_kv": 20}), + ("synchronous_machine", {"machine_type": "salient_pole"}), + ) + for kind, filters in cases: + with self.subTest(component_type=kind): + original = dict(filters) + result = agent.list_evemtbench_components(kind, filters) + self.assertTrue(result["success"], result.get("message")) + self.assertEqual(result["returned_count"], 1) + self.assertEqual(filters, original) + self.assertEqual(result["component_type"], kind) + transformer = agent.list_evemtbench_components(*cases[0]) + self.assertEqual(transformer["unmapped_source_tables"], [10, 12]) + self.assertEqual(transformer["results"][0]["ratings"][0]["rated_power_mva"], 800) + line = agent.list_evemtbench_components(*cases[1]) + self.assertEqual(set(line["results"][0]["electrical_profiles"]), {"p50"}) + machine = agent.list_evemtbench_components(*cases[-1]) + self.assertIsNone(machine["results"][0]["parameters"]["xq_transient_pu"]) + self.assertIn("salient-pole creation remains disabled", machine["powerfactory"]["status"]) + + def test_rejects_bad_types_filters_and_private_options(self): + agent = agent_module.DIgSILENTAgent + unknown = agent.list_evemtbench_components("unsupported") + self.assertFalse(unknown["success"]) + self.assertEqual(len(unknown["supported_component_types"]), 6) + for filters in ([], {"_database_loader": "override"}, {"voltage_class": "20"}, {"open_digsilent": True}): + with self.subTest(filters=filters): + result = agent.list_evemtbench_components("load", filters) + self.assertFalse(result["success"]) + self.assertIn("available_filters", result) + invalid = agent.list_evemtbench_components("overhead_line", {"profile": "made-up"}) + self.assertFalse(invalid["success"]) + self.assertIn("p50", invalid["message"]) + + class UnifiedEvEMTBenchCreatorTest(unittest.TestCase): class FakeAgent(agent_module.DIgSILENTAgent): calls = [] diff --git a/PowerFactory/tests/test_state_inspection.py b/PowerFactory/tests/test_state_inspection.py index 7766b48..ca39abc 100644 --- a/PowerFactory/tests/test_state_inspection.py +++ b/PowerFactory/tests/test_state_inspection.py @@ -9,10 +9,13 @@ class FakeFastMCP: def __init__(self, *args, **kwargs): - pass + self.registered_tools = [] def tool(self): - return lambda function: function + def register(function): + self.registered_tools.append(function.__name__) + return function + return register class FakeAgent: @@ -1246,6 +1249,21 @@ def test_unified_evemtbench_tool_uses_structured_agent_result(self): structured=True, ) + def test_only_unified_evemtbench_tools_are_registered(self): + self.assertEqual( + {name for name in mcp_module.mcp.registered_tools if "evemtbench" in name}, + {"list_evemtbench_components", "create_evemtbench_component"}, + ) + + def test_unified_listing_passes_filters_without_opening_powerfactory(self): + filters = {"voltage_kv": 110} + with patch.object(FakeAgent, "list_evemtbench_components", + return_value={"success": True, "component_type": "load"}, + create=True) as listing: + result = json.loads(mcp_module.list_evemtbench_components("load", filters)) + self.assertTrue(result["success"]) + listing.assert_called_once_with("load", filters) + def test_get_parameters_serializes_object_lists(self): reference = FakeObject( "Bus 02", diff --git a/PowerFactory/tests/test_transformer_type_library.py b/PowerFactory/tests/test_transformer_type_library.py index 5710824..f8b5bc8 100644 --- a/PowerFactory/tests/test_transformer_type_library.py +++ b/PowerFactory/tests/test_transformer_type_library.py @@ -315,7 +315,7 @@ def call(self, app, **kwargs): def test_database_validates_and_lists_supported_classes(self): database = agent_module.DIgSILENTAgent._load_evemtbench_database() - result = agent_module.DIgSILENTAgent.list_evemtbench_components() + result = agent_module.DIgSILENTAgent.list_evemtbench_transformer_profiles() self.assertEqual(database["schema_version"], 1) self.assertTrue(result["success"], result.get("message")) @@ -328,7 +328,7 @@ def test_database_validates_and_lists_supported_classes(self): self.assertIn("Institute of Electrical Energy Systems", result["affiliation"]) def test_catalog_filters_an_exact_rating(self): - result = agent_module.DIgSILENTAgent.list_evemtbench_components( + result = agent_module.DIgSILENTAgent.list_evemtbench_transformer_profiles( "345/25", 800.0, ) From 7bc3e8c157c26d17f56ed34a38028a9f8484cdb9 Mon Sep 17 00:00:00 2001 From: aswinkrishnapoyil Date: Thu, 15 Oct 2026 13:43:28 +0200 Subject: [PATCH 3/5] Record MCP catalog checks for all six component types --- .../FT-27_unified_evemtbench_catalog_mcp.txt | 84 +++++++++++++++++-- 1 file changed, 79 insertions(+), 5 deletions(-) diff --git a/PowerFactory/evidence/FT-27_unified_evemtbench_catalog_mcp.txt b/PowerFactory/evidence/FT-27_unified_evemtbench_catalog_mcp.txt index 16983de..032433f 100644 --- a/PowerFactory/evidence/FT-27_unified_evemtbench_catalog_mcp.txt +++ b/PowerFactory/evidence/FT-27_unified_evemtbench_catalog_mcp.txt @@ -77,8 +77,82 @@ This is outside the requested PowerFactory test and is not validation evidence for the unified tools. Conclusion and limits: -The supplied response verifies filtered overhead-line listing and creation-input -discovery through the refreshed MCP interface. No PowerFactory objects were -created or modified. No confirmed creation was retested in this run. The other -five catalog routes were tested automatically and from the installed wheel, -not through this captured MCP client call. No new CI result is claimed. +The first supplied response verifies filtered overhead-line listing and +creation-input discovery through the refreshed MCP interface. The additional +MCP-client checks below extend coverage to all six catalog types. No +PowerFactory objects were created or modified. Confirmed creation was not +retested during this catalog validation. + +Additional MCP-client checks, 15 October 2026: +Revision reported by contributor: 6b14337, published in PR #122. +The following results are compiled from contributor-supplied client summaries. +The original large responses were read through the client's file-reading tool; +their temporary files were not supplied to the repository or independently +parsed during preparation of this record. These are not verbatim JSON captures. + +For each additional call the client reported success=true, matching +component_type, returned_count=1, and presence of catalog_id, source, +available_filters and creation_parameters. + +1. transformer + filters: {"voltage_class": "345/25", "rated_power_mva": 800} + catalog_id: evemtbench-transformers-tables-10-12-v1 + available_filters: voltage_class, rated_power_mva + required creation inputs: voltage_class, rated_power_mva, + nominal_frequency_hz, vector_group, clock_number, transformer_name, + type_name, high_voltage_bus_name, low_voltage_bus_name + Returned limitations: unmapped_source_tables=[10,12]; Table 12 saturation + mapping pending; Block catalog-only pending an explicit voltage rule; + other component families catalog-only pending validated mappings. + The client initially stopped on display truncation. A later read of the + saved response recovered the missing fields and verified the checks. + +2. underground_cable + filters: {"profile": "p50"} + catalog_id: evemtbench-underground-cables-tables-3-6-v1 + available_filters: profile + required creation inputs: nominal_frequency_hz, rated_current_ka, profile, + length_km, cable_name, type_name, bus1_name, bus2_name + Returned limitations: geometric ULM model remains catalog-only; cable + ampacity is not provided and requires a validated engineering rating. + +3. external_grid + filters: {"voltage_kv": 110} + catalog_id: evemtbench-external-grids-table-4-v1 + available_filters: voltage_kv + required creation inputs: voltage_kv, external_grid_name, bus_name + No catalog-only or unsupported mapping limitation was reported returned. + This does not establish unrestricted engineering applicability. + +4. load + filters: {"voltage_kv": 20} + catalog_id: evemtbench-loads-table-5-v1 + available_filters: voltage_kv + required creation inputs: voltage_kv, load_name, bus_name + Returned limitation: load-model coefficients are stored but not written + pending live validation of PowerFactory ZIP attributes. + +5. synchronous_machine + filters: {"machine_type": "round_rotor"} + catalog_id: evemtbench-synchronous-machines-table-13-v1 + available_filters: machine_type + required creation inputs: machine_type, rated_power_mva, machine_name, + type_name, bus_name + Returned status text: corrected open-circuit time-input mapping awaits a + creation retest; salient-pole creation disabled because xq transient data + is omitted by the source. + The creation-retest wording predates FT-23, which already records live + round-rotor creation and time-constant validation. This record preserves + the client's reported catalog text without treating it as current evidence + that the earlier creation validation is missing. + +The client reported reading saved transformer, cable and machine responses +in sections. Other response fields and unviewed portions are not asserted here. +The initial transformer sequence also attempted an unrelated PSCAD documentation +call, which failed; it is excluded from PowerFactory validation. + +Final scope: +All six types now have contributor-reported MCP-client catalog coverage in +addition to automated and installed-wheel coverage. This validates catalog +routing and parameter discovery, not physical model suitability or a fresh +creation/calculation run. No new CI result is claimed for this evidence update. From 4506361c7a37acada41fa349c2b5b8de5f4dcd49 Mon Sep 17 00:00:00 2001 From: aswinkrishnapoyil Date: Fri, 16 Oct 2026 18:07:14 +0200 Subject: [PATCH 4/5] Update PowerFactory architecture diagram --- PowerFactory/README.md | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/PowerFactory/README.md b/PowerFactory/README.md index 3437422..c203104 100644 --- a/PowerFactory/README.md +++ b/PowerFactory/README.md @@ -6,22 +6,32 @@ A Model Context Protocol (MCP) server that exposes DIgSILENT PowerFactory simula ## Architecture -``` +```text Claude / AI assistant - │ MCP protocol + │ MCP protocol ▼ -MCP_PowerFactory.py ← MCP Python SDK 2 server - │ Python calls +MCP_PowerFactory.py MCP Python SDK 2 server + │ Python calls ▼ -Agent_DIgSILENT.py ← Simulation engine - │ +Agent_DIgSILENT.py Simulation and shared PowerFactory helpers + │ + ├── Inherits EvEMTBenchMixin from evemtbench.py + │ ├── Reads data/evemtbench_*.json + │ ├── Lists catalogs, filters and creation parameters + │ └── Plans, creates, verifies and rolls back equipment + │ + │ Simulation, inspection, export and equipment-creation calls ▼ -DIgSILENT PowerFactory +DIgSILENT PowerFactory │ ▼ -Output folder: CSV results, PNG plots, optional .pfd export +Outputs: CSV results, PNG plots, diagram PDF, optional .pfd export ``` +`evemtbench.py` is part of the agent layer, not a separate MCP server. +Catalog listing returns JSON data without opening PowerFactory. Equipment +creation and simulation require a PowerFactory connection. + ## Features - **Full simulation pipeline** — connect, activate study case, load flow, RMS transient simulation, CSV export, plot generation, optional PFD export in one call. From a7d60e6da33bd29232cf50ad382dd869b56048a4 Mon Sep 17 00:00:00 2001 From: aswinkrishnapoyil Date: Mon, 19 Oct 2026 14:24:53 +0200 Subject: [PATCH 5/5] Simplify PowerFactory README and separate detailed tool guide --- PowerFactory/README.md | 836 +++----------------------- PowerFactory/references/tool_guide.md | 752 +++++++++++++++++++++++ 2 files changed, 846 insertions(+), 742 deletions(-) create mode 100644 PowerFactory/references/tool_guide.md diff --git a/PowerFactory/README.md b/PowerFactory/README.md index c203104..cfa9f1a 100644 --- a/PowerFactory/README.md +++ b/PowerFactory/README.md @@ -1,8 +1,7 @@ # DIgSILENT PowerFactory MCP Agent -A Model Context Protocol (MCP) server that exposes DIgSILENT PowerFactory simulation capabilities to AI assistants such as Claude. The agent automates RMS transient stability simulations, load flow and short-circuit calculations, parameter modifications, and result exports — all through a clean, tool-callable interface. - ---- +Connect an AI assistant to DIgSILENT PowerFactory to inspect models, run +calculations and fault simulations, build equipment, and export results. ## Architecture @@ -34,312 +33,41 @@ creation and simulation require a PowerFactory connection. ## Features -- **Full simulation pipeline** — connect, activate study case, load flow, RMS transient simulation, CSV export, plot generation, optional PFD export in one call. -- **Custom fault scenarios** — define bus faults, line faults, and generator switching events at call time without editing config files. -- **Parameter modification** — update any PowerFactory attribute on any object via the MCP interface. -- **Model inspection and discovery** — inspect the active project and study case, read selected attributes, and discover objects using raw queries or friendly component categories. -- **Guarded component management** — create or safely preview and delete buses, loads, generators, lines, and two-winding transformers. -- **Diagram and switchgear synchronization** — optionally update the active single-line diagram and create one closed circuit breaker in each generated connection cubicle. -- **Load flow and short-circuit** — run Load flow and Short-Circuit calculation. -- **Automatic plot generation** — bus voltage magnitudes and generator speeds plotted from exported CSV results. -- **Study case management** — create, copy, and activate study cases with idempotency support (replay-safe via `request_id`). -- **Project import/export** — import `.pfd` files and export the active project back to `.pfd`. -- **Grid diagram export** — export a selected active-project single-line diagram to PDF. -- **Greenfield equipment types** — create paper-backed transformer, overhead-line, and underground-cable types without requiring project templates. -- **CSV result reading** — auto-discovers the latest result file or reads a specified path. -- **JSON configuration** — all simulation parameters controlled through `simulation_config.json`. - ---- +- Model inspection, parameter changes, and study-case management. +- Load flow, short-circuit, contingency analysis, and RMS fault simulations. +- Grid bootstrap, component management, and six EvEMTBench equipment catalogs. +- CSV results, PNG plots, single-line PDF export, and optional project export. ## Implemented Functions -### MCP Tools (`MCP_PowerFactory.py`) - -| Tool | Description | -|------|-------------| -| `ping` | Health check — returns `"pong"`. | -| `close_digsilent` | Closes the PowerFactory session. | -| `get_config` | Returns the active `simulation_config.json` as a JSON string. | -| `get_active_project` | Returns the active PowerFactory project without modifying it. | -| `get_active_study_case` | Returns the active PowerFactory study case without modifying it. | -| `get_parameters` | Reads selected attributes from matching calculation-relevant objects, with opt-in metadata and active-project library inspection. | -| `list_objects` | Lists calculation-relevant objects using a raw PowerFactory query. | -| `list_components` | Lists objects using friendly equipment categories. | -| `list_study_cases` | Lists study cases and identifies the active case. | -| `list_contingencies` | Lists configured fault-library cases and their `EvtOutage`, `EvtSwitch`, and `EvtShc` events without modifying the project. | -| `create_contingency` | Creates or reuses an idempotent switch-based contingency definition. | -| `get_contingency_configuration` | Reads the active Contingency Analysis command settings without running a calculation. | -| `configure_contingency_screening` | Persists selected DC/AC-linearised screening criteria without running a calculation. | -| `add_contingency_result_variables` | Adds variables to the configured AC or DC contingency result recording selection without running a calculation. | -| `remove_contingency_result_variables` | Removes variables from matching AC or DC result recording selections without running a calculation. | -| `export_active_grid_pdf` | Exports one active-project single-line diagram to a new PDF file. | -| `create_evemtbench_component` | Routes one nested parameter object to any validated EvEMTBench component creator. | -| `list_evemtbench_components` | Lists any of six EvEMTBench catalogs, filters and required creation parameters without opening PowerFactory. | -| `create_grid_with_buses` | Previews or bootstraps an `ElmNet` and initial `ElmTerm` objects in the active project. | -| `import_project` | Imports a `.pfd` project file and activates it. | -| `create_study_case` | Creates (or activates) a study case by name, copying from a base case when needed. Supports `request_id` for idempotency. | -| `modify_parameter` | Sets one attribute on all PowerFactory objects matching a query string. Auto-casts string values to the correct type. | -| `add_component` | Creates a bus, load, generator, line, or two-winding transformer with validation, rollback, circuit breakers, and optional diagram insertion. | -| `delete_component` | Previews or confirms exact-name component deletion, including generated cubicles, circuit breakers, and optional diagram cleanup. | -| `run_loadflow` | Runs a standalone load flow calculation (ComLdf). | -| `run_short_circuit` | Runs a standalone short-circuit calculation (ComShc). | -| `run_contingency_analysis` | Runs ComSimoutage using its configured method or an explicit AC, DC, AC-linearised, or linearised-screening mode. | -| `get_contingency_results` | Reads a bounded, optionally variable-filtered slice of the configured AC or DC contingency result file using bulk or sparse access. | -| `get_contingency_summary` | Interprets existing contingency results as affected elements, convergence, overloads, and voltage violations. | -| `run_simulation` | Executes the full pipeline defined in `simulation_config.json`. | -| `run_custom_case` | Runs one fault simulation with parameters supplied at call time (no config file edit required). | -| `read_results_csv` | Reads an RMS result CSV; auto-discovers the latest file if no path is given. | - -The contingency tools can create switch-based definitions and inspect or run -the active `ComSimoutage` command. An explicit calculation mode applies only to -that run; the study case's previous mode is restored afterward. - -Read-only contingency tools search the active study case for an existing -`ComSimoutage`; they do not call `GetFromStudyCase`, which can create a missing -command. `get_contingency_results` accepts exact variable identifiers such as -`["c:loading"]`, returns each column's original index and object name/class when -PowerFactory provides one, and automatically includes `b:i_obj` for row-to-case -mapping. It uses `ElmRes.GetColumnValues` with a temporary, deleted `IntVec`, -falling back to PowerFactory's sparse iterator. Filtered reads resolve columns -from `IntMon` selections and `FindColumn`, avoiding a full `ElmRes` metadata -scan. `max_read_seconds` supplies a cooperative read budget; narrow `variables`, -`max_rows`, or `max_columns` for large files. - -`configure_contingency_screening` intentionally persists selected screening -settings. It can choose DC or AC-linearised screening; enable the simple and -combined loading criteria; set their loading and relative-change thresholds; -ignore components already overloaded in the base case; and restrict screening -to recorded elements. It returns the previous and resulting settings without -executing the analysis. - -Result recording is read from `IntMon` selections, never from stale result -columns. Both recording tools report each unreadable monitor once and keep -the record of successful changes to other objects. Class selections identified -by `IntMon.className` count as recorded for objects of that exact class. -Object-specific removal of a class-recorded variable is reported as failed, -without changing either its class or object selections. Other variables can -still be removed. Unidentified targetless selections remain errors; additions -and absence claims are skipped when their state is unknown. - -Removal reports `failed_variables` separately from `already_absent_variables`. -A variable can appear in both `removed` and `failed` when different monitors -produce a partial change; rerun the analysis whenever `removed_variables` is -non-zero. `configured_objects` counts objects with readable matching selections, -and `objects_without_selection` counts objects with no known selection. -The tool schemas require strings; blank or whitespace-only variables are also -rejected. Missing screening flags are returned as `null`, rather than disabled. - -PowerFactory 2026's User Manual, section 19.3.1.1, explicitly supports class-name -variable selections for contingency analysis. These tools report them rather -than attempting to remove a class-wide variable for an individual object. -Its Python Reference, section 5.6.26, documents `IntMon.RemoveVar` return codes: -`0` means removed, `1` means not found; other codes or exceptions are failures. - -### Simulation Engine (`Agent_DIgSILENT.py`) - -| Class / Method | Description | -|----------------|-------------| -| `SimulationConfig` | Dataclass holding all simulation parameters. `from_json()` loads from a JSON file. | -| `Logger` | Timestamped console logging with `info`, `ok`, `warn`, `error`, `section`. | -| `DIgSILENTAgent.__init__` | Initialises the agent from a `SimulationConfig`. | -| `DIgSILENTAgent.connect` | Connects to PowerFactory and activates the configured project. | -| `DIgSILENTAgent.activate_study_case` | Activates or creates the target study case. | -| `DIgSILENTAgent.run_loadflow` | Executes ComLdf. | -| `DIgSILENTAgent.run_contingency_analysis` | Executes ComSimoutage without changing its contingency filters and optionally selects a validated calculation mode. | -| `DIgSILENTAgent.run_rms_simulation` | Applies fault events, then runs ComInc + ComSim. | -| `DIgSILENTAgent._apply_fault_event` | Builds the event sequence for bus faults, line faults, or generator switches. | -| `DIgSILENTAgent.addSwitchEvent` | Creates a PowerFactory `EvtSwitch` event. | -| `DIgSILENTAgent.export_results_to_csv` | Exports the RMS result object to CSV via ComRes. | -| `DIgSILENTAgent.export_active_grid_pdf` | Exports a selected Graphics Board single-line diagram to PDF via ComWr. | -| `DIgSILENTAgent.create_evemtbench_component` | Validates and routes a unified request to an existing specialized component creator. | -| `DIgSILENTAgent.list_evemtbench_components` | Routes catalog requests for all six types and returns parameter metadata. | -| `DIgSILENTAgent.list_evemtbench_transformer_profiles` | Internal transformer catalog implementation. | -| `DIgSILENTAgent.create_evemtbench_transformer` | Samples, previews, creates, connects, verifies, and rolls back a new transformer type and element. | -| `DIgSILENTAgent.list_evemtbench_line_profiles` | Validates and filters the versioned overhead-line sequence profiles. | -| `DIgSILENTAgent.create_evemtbench_line` | Previews, creates, connects, verifies, and rolls back a new overhead-line type and element. | -| `DIgSILENTAgent.list_evemtbench_cable_profiles` | Validates and filters the versioned underground-cable sequence profiles. | -| `DIgSILENTAgent.create_evemtbench_cable` | Previews, creates, connects, verifies, and rolls back a new cable type and element. | -| `DIgSILENTAgent.list_evemtbench_external_grid_profiles` | Validates and filters the external-grid catalog. | -| `DIgSILENTAgent.create_evemtbench_external_grid` | Samples, previews, creates, connects, verifies, and rolls back a new external grid. | -| `DIgSILENTAgent.create_grid_with_buses` | Previews, creates or reuses, verifies, and rolls back a grid and its initial buses. | -| `DIgSILENTAgent.export_project_to_pfd` | Exports the active project to `.pfd` via ComPfdexport. | -| `DIgSILENTAgent.generate_standard_plots` | Reads the exported CSV and produces voltage and generator speed PNG plots. | -| `DIgSILENTAgent.import_project` | Imports a `.pfd` file into the running PowerFactory session. | -| `DIgSILENTAgent.create_study_case` | Creates or reuses a study case by exact name. | -| `DIgSILENTAgent.modify_parameter` | Sets an attribute on all objects returned by `GetCalcRelevantObjects`. | -| `DIgSILENTAgent.add_component` | Creates and verifies supported network components and their connections. | -| `DIgSILENTAgent.delete_component` | Performs guarded exact-name component deletion and cleanup. | -| `DIgSILENTAgent.short_circuit` | Standalone ComShc execution. | -| `DIgSILENTAgent.run_pipeline` | Orchestrates the full workflow and returns a structured status report. | -| `DIgSILENTAgent.close` | Shuts down PowerFactory and clears shared handles. | - -## Greenfield Grid Bootstrap - -`create_grid_with_buses` removes the requirement for a pre-existing -calculation grid and buses. It requires an active PowerFactory project and -study case. It can create the standard `Network Model` / `Network Data` folder -hierarchy, a new `ElmNet`, and one to 100 `ElmTerm` objects without using a -template. A confirmed call activates the grid and verifies that PowerFactory -reports it as calculation-relevant. - -```text -create_grid_with_buses( - grid_name="Research Grid", - nominal_frequency_hz=50, - buses=[ - {"name": "HV Bus", "nominal_voltage_kv": 110}, - { - "name": "MV Bus", - "nominal_voltage_kv": 20, - "out_of_service": true - } - ], - confirmation="" -) -``` - -The first call is a read-only preview. Repeat it with the exact returned -`confirmation_required` value to apply the plan. An existing grid or bus is -reused only when its frequency, voltage, and service state match exactly; -conflicting objects are reported without modification. If creation fails, all -objects created by that call are removed. The tool does not create a project, -study case, single-line diagram, equipment type, or calculation command. - -The bootstrap currently accepts 50 or 60 Hz so its grid is compatible with the -EvEMTBench transformer and overhead-line creators. Once the buses exist, pass -their names and the grid name directly to those tools. - -## Component Management - -### Create a component - -```text -add_component( - component_type, - component_name, - parameters, - grid_name="", - out_of_service=false, - open_digsilent=true, - update_graphics=false -) -``` - -Supported component parameters: - -| Type | Required parameters | Optional parameters | +| Task | Main tools | Guide | |---|---|---| -| `bus` | `nominal_voltage_kv` | — | -| `load` | `bus_name`, `active_power_mw` | `reactive_power_mvar` | -| `generator` | `bus_name`, `template_generator`, `active_power_mw` | `reactive_power_mvar` | -| `line` | `bus1_name`, `bus2_name`, `template_line`, `length_km` | — | -| `transformer` | `high_voltage_bus_name`, `low_voltage_bus_name`, `template_transformer` | — | - -Component names are limited to 40 characters, matching PowerFactory's -`loc_name` limit. - -An explicit `null` for optional reactive power is treated as an omitted value -and defaults to zero. Boolean values are not accepted for numeric parameters. - -Each generated connection cubicle contains one closed circuit breaker -(`StaSwitch`, `aUsage="cbk"`, `on_off=1`). Set `update_graphics=true` to -request insertion into the active single-line diagram. - -Graphical updates use the Diagram Layout Tool's automatic insertion mode so -existing diagram objects are not re-laid out as a K-neighbourhood. - -If graphical insertion fails, the network component remains created and the -tool returns `success=false` with the graphical error. Check the returned -message before retrying to avoid creating a duplicate. - -PowerFactory may place an isolated bus far from the existing network when it -is inserted directly into the diagram. For a better anchored result, create -the bus with `update_graphics=false`, then create its first connecting line -with `update_graphics=true`. - -### Delete a component +| Inspect models | `list_components`, `list_objects`, `get_parameters` | [Tool reference](references/tool_guide.md#implemented-functions) | +| Manage study cases and imports | `list_study_cases`, `create_study_case`, `import_project` | [Examples](references/tool_guide.md#prompt-examples) | +| Run calculations | `run_loadflow`, `run_short_circuit` | [Tool reference](references/tool_guide.md#implemented-functions) | +| Simulate faults and read results | `run_custom_case`, `run_simulation`, `read_results_csv` | [Configuration and outputs](references/tool_guide.md#configuration) | +| Run contingency analysis | `list_contingencies`, `run_contingency_analysis`, `get_contingency_summary` | [Contingency tools](references/tool_guide.md#implemented-functions) | +| Bootstrap grids and buses | `create_grid_with_buses` | [Bootstrap](references/tool_guide.md#greenfield-grid-bootstrap) | +| Add or delete equipment | `add_component`, `delete_component` | [Component management](references/tool_guide.md#component-management) | +| Build paper-backed equipment | `list_evemtbench_components`, `create_evemtbench_component` | [EvEMTBench](#evemtbench-equipment) | +| Export a diagram | `export_active_grid_pdf` | [Export behavior](references/tool_guide.md#automated-tests-and-validation-reports) | -```text -delete_component( - component_type, - component_name, - grid_name="", - confirmation="", - open_digsilent=true, - update_graphics=false -) -``` +The [detailed guide](references/tool_guide.md) contains full tool and agent-method +inventories, parameter examples, and prompt examples. -Calling `delete_component` without confirmation returns a preview and the -required confirmation phrase: - -```text -DELETE -``` +## EvEMTBench Equipment -Name lookup is case-insensitive. The confirmation phrase contains the full -PowerFactory object path, binds the confirmation to the previewed grid, and -must be copied exactly from the preview. +Two public MCP tools cover six component families: -Confirmed deletion removes the exact component. A connection cubicle is removed -only when its generated name and sole generated circuit breaker both match; -cubicles containing relays, instrument transformers, or other objects are -preserved. A bus cannot be deleted while it still has connected cubicles. Set -`update_graphics=true` to remove matching diagram objects from every single-line -diagram in the active project and rebuild the active view. +- `list_evemtbench_components(component_type, filters)` reads catalog data and + returns source information, available filters, and creation parameters. +- `create_evemtbench_component(component_type, parameters)` previews or creates + equipment using the selected catalog. -The response reports component deletion separately from cleanup. In particular, -`success=false` with `deleted=true` means the network component was removed but -some graphical or cubicle cleanup failed; do not retry the component deletion. - ---- - -## Unified EvEMTBench Component Creator - -`create_evemtbench_component` is the common write entry point for the six -validated component families: `transformer`, `overhead_line`, -`underground_cable`, `external_grid`, `load`, and `synchronous_machine`. -Use the creation inputs returned by `list_evemtbench_components` inside -`parameters`: - -```python -create_evemtbench_component( - component_type="load", - parameters={ - "voltage_kv": 20, - "load_name": "Research Load", - "bus_name": "MV Bus", - "grid_name": "Research Grid", - "seed": 42, - "out_of_service": True, - "confirmation": "", - "open_digsilent": True, - }, -) -``` +Supported types: `transformer`, `overhead_line`, `underground_cable`, +`external_grid`, `load`, and `synchronous_machine`. -An empty `confirmation` keeps the request in preview mode. Repeat the same -request with the exact returned `confirmation_required` value to apply it. -The unified tool delegates validation, seeded sampling, creation, retained-value -checks, and rollback to the existing specialized creator without changing its -behavior. Only the unified create and list tools are exposed through MCP. -Specialized methods remain internal Python implementation details. - -### Catalog discovery and migration - -Call `list_evemtbench_components(component_type, filters)` before creating equipment. -Omit `filters` to inspect the complete selected catalog. The response includes -`available_filters` and `creation_parameters`, with names, types, required flags, -and defaults. Catalog-only data and mapping limits remain in the response. - -| component_type | Optional filters | -|---|---| -| `transformer` | `voltage_class`, `rated_power_mva` (requires voltage_class) | -| `overhead_line` | `voltage_kv`, `profile` | -| `underground_cable` | `profile` | -| `external_grid` | `voltage_kv` | -| `load` | `voltage_kv` | -| `synchronous_machine` | `machine_type` | +Discover a line profile without opening PowerFactory: ```python list_evemtbench_components( @@ -348,221 +76,9 @@ list_evemtbench_components( ) ``` -This intentionally changes the MCP interface. Replace previous specialized -create calls with `create_evemtbench_component` and place their arguments inside -`parameters`. Replace specialized list calls with `list_evemtbench_components` -and place their filters inside `filters`. The old transformer-only listing now -requires `component_type="transformer"`; move voltage_class/rated_power_mva into -`filters`. Restart the server and refresh the client's tool list after updating. -The examples below use the new public interface. - ---- - -## EvEMTBench Transformer Database and Creator - -The versioned database at -[`data/evemtbench_transformers.json`](data/evemtbench_transformers.json) -stores the fixed-voltage transformer classes from EvEMTBench Tables 10-12. -Use `list_evemtbench_components` to inspect classes, ratings, ranges, vector -groups, zero-sequence rules, and saturation data without opening PowerFactory. - -`create_evemtbench_component(component_type="transformer", parameters=...)` creates a new `TypTr2` directly; it does not -copy or require an existing transformer. It also creates an `ElmTr2`, two -cubicles, and closed circuit breakers between existing buses. The type is -stored in the project library's `Transformer Types` folder; a confirmed call -creates that folder when it is absent from a greenfield project. The first -call is a read-only preview when `confirmation` is empty: - -```text -create_evemtbench_component( - component_type="transformer", - parameters={ - "voltage_class": "110/20", - "rated_power_mva": 20, - "nominal_frequency_hz": 60, - "vector_group": "YNd", - "clock_number": 11, - "transformer_name": "Research Transformer", - "type_name": "Research Transformer Type", - "high_voltage_bus_name": "HV Bus", - "low_voltage_bus_name": "LV Bus", - "seed": 42, - "confirmation": "", - "open_digsilent": True, - }, -) -``` - -The preview validates the grid, buses, nominal voltages, names, rating, vector -group, clock number, and an explicit nominal frequency of 50 or 60 Hz, then -returns the exact confirmation phrase. Frequency is not inferred from the grid -because existing projects can contain types with a different `frnom`. A -confirmed call writes three-phase technology (`nt2ph`), frequency (`frnom`), -the impedance modes, `strn`, `utrn_h`, `utrn_l`, `uktr`, `pcutr`, `pfe`, -`curmg`, `tr2cn_h`, `tr2cn_l`, and `nt2ag`. Structural settings and nameplate -ratings are committed before the performance values so PowerFactory can -refresh dependent type fields. Copper loss and short-circuit voltage are -converted to the equivalent `r1pu` and `x1pu` values before assignment, then -verified again through PowerFactory's derived `pcutr` and `uktr` attributes. -The performance values use PowerFactory's native Python property assignment; -each value is checked immediately and again after the database write. If element -creation fails, the new type, element, cubicles, and switches are rolled back. - -Table 10 zero-sequence and Table 12 saturation data are stored and returned by -the catalog tool, but are not written until their PowerFactory attribute -mappings are validated. The `Block` class and other component families remain -catalog-only for the same reason. The caller must explicitly choose a clock -number because Table 11 specifies vector-group families rather than one clock -number for every generated transformer. - -See [references/evemtbench.md](references/evemtbench.md) for the source mapping -and limitations. - ---- - -## EvEMTBench Overhead-Line Database and Creator - -[`data/evemtbench_overhead_lines.json`](data/evemtbench_overhead_lines.json) -stores the published 5th, 50th and 95th percentile sequence parameters from -Table 7 for 20, 110 and 345 kV overhead lines, together with the corresponding -Table 3 length ranges. `list_evemtbench_components(component_type="overhead_line")` inspects this catalog -without opening PowerFactory. - -`create_evemtbench_component(component_type="overhead_line", parameters=...)` creates a new `TypLne` directly and connects a new -`ElmLne` between two existing equal-voltage buses. The caller selects `p05`, -`p50` or `p95`, supplies the actual current rating, and chooses 50 or 60 Hz. -The first call is a read-only preview: - -```text -create_evemtbench_component( - component_type="overhead_line", - parameters={ - "voltage_kv": 110, - "nominal_frequency_hz": 50, - "rated_current_ka": 1, - "profile": "p50", - "length_km": 10, - "line_name": "Research Line", - "type_name": "Research Line Type", - "bus1_name": "Bus A", - "bus2_name": "Bus B", - "confirmation": "", - "open_digsilent": True, - }, -) -``` - -The preview checks the voltage class, frequency, paper length range, existing -buses, names, and exact confirmation phrase. A confirmed call creates the -project library's `Line Types` folder when needed, writes `uline`, `sline`, -`frnom`, `rline`, `xline`, `cline`, `rline0`, `xline0` and `cline0`, and then -creates the line, two cubicles and closed circuit breakers. Published -capacitances are converted from nF/km to PowerFactory's uF/km attributes. New -objects are rolled back when creation fails. - -This tool deliberately implements a three-phase `TypLne` sequence equivalent, -not the paper's frequency-dependent geometric ULM model. The three published -percentiles are discrete profiles rather than endpoints of an invented -sampling distribution. Tower and conductor geometry remains catalog-only. - ---- - -## EvEMTBench Underground-Cable Database and Creator - -[`data/evemtbench_underground_cables.json`](data/evemtbench_underground_cables.json) -stores Table 6's published 5th, 50th and 95th percentile sequence parameters -for 20 kV underground cables and Table 3's 0.5 to 3.75 km length range. -`list_evemtbench_components(component_type="underground_cable")` returns those profiles plus the paper's -construction and installation data without opening PowerFactory. - -`create_evemtbench_component(component_type="underground_cable", parameters=...)` creates a new `TypLne` sequence equivalent and -connects a new `ElmLne` between two existing 20 kV buses. The caller chooses a -published percentile, 50 or 60 Hz, and an engineering current rating because -Table 6 does not publish cable ampacity. The first call is a read-only preview: - -```text -create_evemtbench_component( - component_type="underground_cable", - parameters={ - "nominal_frequency_hz": 50, - "rated_current_ka": 0.4, - "profile": "p50", - "length_km": 2, - "cable_name": "Research Cable", - "type_name": "Research Cable Type", - "bus1_name": "MV Bus A", - "bus2_name": "MV Bus B", - "grid_name": "Research Grid", - "out_of_service": True, - "confirmation": "", - "open_digsilent": True, - }, -) -``` - -Repeat the call with the exact returned `confirmation_required` value to -create the type, cable element, two cubicles, and closed circuit breakers. -Published capacitances are converted from nF/km to PowerFactory's uF/km -attributes, retained values are verified, and new objects are rolled back if -creation fails. - -The created `TypLne` is a three-phase sequence equivalent suitable for initial -network construction; it is not the paper's geometric ULM cable model. The -catalog preserves insulation, conductor, sheath, arrangement, burial-depth, -spacing, and earth-resistivity data for a future geometric implementation. - ---- - -## EvEMTBench External-Grid Database and Creator - -[`data/evemtbench_external_grids.json`](data/evemtbench_external_grids.json) -stores the Table 4 ranges for 110 kV and combined 380/345 kV external-grid -equivalents. `list_evemtbench_components(component_type="external_grid")` inspects the ranges -without opening PowerFactory. - -`create_evemtbench_component(component_type="external_grid", parameters=...)` uses a supplied seed to sample every Table 4 -interval uniformly and connects a new slack `ElmXnet` to an existing bus. The -bus must be exactly 110, 345, or 380 kV. The first call is a read-only preview: - -```text -create_evemtbench_component( - component_type="external_grid", - parameters={ - "voltage_kv": 110, - "external_grid_name": "Research External Grid", - "bus_name": "HV Bus", - "grid_name": "Research Grid", - "seed": 42, - "out_of_service": True, - "confirmation": "", - "open_digsilent": True, - }, -) -``` - -Repeat the call with the exact returned `confirmation_required` value to create -the external grid, cubicle, and closed circuit breaker. The tool writes `snss`, -`rntxn`, `x0tx1`, `r0tx0`, `usetp`, and `phiini`, sets `bustp="SL"`, verifies -the retained values, and rolls back new objects if creation fails. The sampled -values are research inputs and must still be checked for suitability in the -target network. The attribute mapping and retained values were validated on -PowerFactory 2026 SP1; SP4 validation remains pending. - ---- - -## EvEMTBench Load Database and Creator +Preview a load in an existing grid with a compatible bus: -[`data/evemtbench_loads.json`](data/evemtbench_loads.json) stores the Table 5 -load-power ranges for 20, 110, and 345 kV systems. The read-only -`list_evemtbench_components(component_type="load")` tool returns these ranges without opening -PowerFactory. - -`create_evemtbench_component(component_type="load", parameters=...)` samples active and reactive power directly at 110 and -345 kV. At 20 kV it samples apparent power and a reactive-power angle between -the published 0.95 capacitive and 0.8 inductive limits, then calculates P and -Q. The first call is a read-only preview: - -```text +```python create_evemtbench_component( component_type="load", parameters={ @@ -573,273 +89,109 @@ create_evemtbench_component( "seed": 42, "out_of_service": True, "confirmation": "", - "open_digsilent": True, }, ) ``` -Repeat the call with the exact returned `confirmation_required` value to create -the balanced `ElmLod`, cubicle, and closed circuit breaker. The tool verifies -`plini`, `qlini`, the bus connection, and the service state and rolls back new -objects if creation fails. The Table 5 ZIP coefficients remain catalog-only -until their PowerFactory attribute mapping is live-validated. - ---- +These are MCP tool-call examples. An empty confirmation creates nothing. +Inspect the preview, then repeat the same request with the exact returned +`confirmation_required` string to apply it. If inputs or resolved project +state change, obtain a fresh preview. -## EvEMTBench Synchronous-Machine Database and Creator +Choose the catalog class or profile explicitly; a paper table alone does not +select equipment. See the [equipment examples](references/tool_guide.md#evemtbench-transformer-database-and-creator), +[source mappings](references/evemtbench.md), and +[component catalog](references/evemtbench_component_catalog.md). -[`data/evemtbench_synchronous_machines.json`](data/evemtbench_synchronous_machines.json) -stores the salient-pole and round-rotor ranges from Table 13, including the -rated-voltage selection rule and parameter dependencies. Use -`list_evemtbench_components(component_type="synchronous_machine")` to inspect both profiles or to -filter by `salient_pole` or `round_rotor`. +### Scope and safety -`create_evemtbench_component(component_type="synchronous_machine", parameters=...)` supports the round-rotor profile. The -caller selects a rating from 25 to 1200 MVA and an existing bus. The tool -derives the rated voltage using Table 13 (6.3, 10.5, 21, or 27 kV), samples the -remaining ranges reproducibly from `seed`, and validates all published -reactance and saturation dependencies. The requested P/Q operating point must -fit the MVA rating and the paper's reactive-power limits. +- Bootstrap requires an active project and study case. It does not create a + project or a single-line diagram. +- Lines and cables use sequence equivalents, not geometric ULM models. + Supply a suitable engineering current rating. +- Transformer zero-sequence and saturation mappings, and load ZIP coefficients, + remain catalog-only. Synchronous-machine creation supports round rotors only. +- Sampled research inputs need engineering review. Retained-value checks do not + establish a complete dynamic model or suitability for an energized network. +- Preview/confirmation applies to EvEMTBench creation, bootstrap, and deletion. + Other tools, including `add_component` and `modify_parameter`, can change + the model immediately. Inspect returned status and partial-change warnings. -```python -create_evemtbench_component( - component_type="synchronous_machine", - parameters={ - "machine_type": "round_rotor", - "rated_power_mva": 100, - "machine_name": "Research Generator", - "type_name": "Research Generator Type", - "bus_name": "Generator Bus 10.5", - "grid_name": "Research Grid", - "seed": 42, - "active_power_mw": 60, - "reactive_power_mvar": 5, - "out_of_service": True, - "confirmation": "", - "open_digsilent": True, - }, -) -``` - -Repeat the call with the exact returned `confirmation_required` string to -create the `TypSym`, `ElmSym`, cubicle, and closed circuit breaker. The type is -created without a template, and all new objects are rolled back if creation -fails. Table 13's short-circuit time constants are converted to PowerFactory's -writable open-circuit inputs and checked again through the derived `tds`, -`tqs`, `tdss`, and `tqss` values. The attribute relationships, `iturbo=1` -round-rotor mode, confirmed creation, and structured response were validated -on PowerFactory 2026 SP1. Salient-pole creation remains disabled because Table -13 does not provide `x'q` or `T'q`; the profile continues to be available -through the read-only catalog. - ---- +When upgrading from specialized MCP tools, migrate to the two unified tools +and refresh the client's tool list. Internal Python helpers remain available. +See the [detailed guide](references/tool_guide.md) for per-family constraints. ## Configuration -Copy `simulation_config.example.json` to `simulation_config.json` and fill in your paths and parameters: - -```json -{ - "project_path": "\\\\\\.IntPrj", - "study_case": "Case 1", - "base_study_case": "0. Base", - "output_dir": "C:\\path\\to\\output", - "run_label": "Test_1", - "result_name": "All calculations.ElmRes", - "cases": [ - { - "case_name": "Fault_1", - "fault_type": "bus", - "fault_element": "Bus 25.ElmTerm", - "t_start": 0.0, "t_fault": 1.0, "t_clear": 1.08, "t_end": 10.0, "dt_rms": 0.01 - } - ] -} -``` - -Supported `fault_type` values: `"bus"`, `"line"`, `"gen_switch"`. - - ---- +Copy [simulation_config.example.json](simulation_config.example.json) to +`simulation_config.json`. Set the project, study case, output paths, and fault +cases. Do not commit this machine-specific file. Configure +`POWERFACTORY_PYTHON_PATH` or `PYTHONPATH` for the PowerFactory Python module. +See the [configuration guide](references/tool_guide.md#configuration) for details. ## Output Structure -Each simulation run creates a dedicated subfolder: - -``` -output_dir/ -└── run_label/ - ├── run_label_RMS.csv - ├── run_label_voltages.png - ├── run_label_gen_speeds.png - └── run_label.pfd (optional) -``` - ---- +Each run writes CSV results and PNG plots beneath `output_dir/run_label/`, +with an optional `.pfd` project export. See the +[output example](references/tool_guide.md#output-structure). ## Requirements -See [requirements.txt](requirements.txt) and [INSTALL.txt](INSTALL.txt) for full details. - -- Python 3.10+ -- DIgSILENT PowerFactory 2023 or later (with Python interface enabled) -- PowerFactory Python module path configured through `POWERFACTORY_PYTHON_PATH` or `PYTHONPATH` -- MCP Python SDK 2.x -- numpy >= 1.26 -- matplotlib >= 3.8 -- pandas >= 1.5 - ---- +A licensed Windows installation of PowerFactory with its Python interface is +required for simulations and model changes. Catalog listing works offline. +Use a Python version supported by your PowerFactory installation. +See [INSTALL.txt](INSTALL.txt) and [requirements.txt](requirements.txt) for setup +and dependencies. ## Quick Start -1. Install Python dependencies: `pip install -r requirements.txt` -2. Add the PowerFactory Python path to your environment (see [INSTALL.txt](INSTALL.txt)). -3. Copy and edit the config: `cp simulation_config.example.json simulation_config.json` -4. Start the MCP server: `python MCP_PowerFactory.py` -5. Connect your AI assistant to the server using the MCP protocol. +From the `PowerFactory` directory, with your virtual environment active: ---- - -## Prompt Examples - -Below are example prompts you can send to an AI assistant connected to this MCP server. - -**Health check** -``` -Ping the PowerFactory server and confirm it's reachable. -``` - -**Run a load flow** -``` -Run a load flow on the active study case and tell me if it converged. -``` - -**Run a transient fault simulation** -``` -Simulate a three-phase bus fault on Bus 25 starting at t=1.0 s, cleared at t=1.08 s, -with a total simulation window of 10 s and a time step of 0.01 s. Export the results -and generate the voltage and generator speed plots. -``` - -**Modify a parameter before running** -``` -Set the generator G 01.ElmSym out of service (e:outserv = 1), -then run a load flow and report the result. -``` - -**Inspect the active context** -``` -Return the active PowerFactory project and study case, then list the first ten -buses without modifying anything. -``` - -**Create and connect a bus** -``` -Create an in-service 110 kV bus named "MCP Example Bus" in Grid without a -graphical update. Connect Bus 01 to it with a 1 km line based on -"Line 01 - 02.ElmLne", request a graphical update for the line, and run a load -flow. Stop if any operation fails. -``` - -**Preview and delete a component** -``` -Preview deletion of the load "MCP Example Load" in Grid. Show me the required -confirmation phrase and do not perform the confirmed deletion yet. -``` - -**Create a new study case and run a custom fault** -``` -Create a study case called "Scenario_A" based on "0. Base", -then run a line fault on "Line 12-34.ElmLne" from t=0.5 s to t=0.58 s -in a 15 s window. Save the CSV and plots to C:\Results\Scenario_A. -``` - -**Short-circuit calculation** -``` -Run a short-circuit calculation on the active study case and return the results. -``` - -**Read the latest results** -``` -Read the most recent RMS results CSV and summarise the peak voltage deviations -and minimum generator speed recorded during the simulation. +```powershell +python -m pip install -r requirements.txt +Copy-Item simulation_config.example.json simulation_config.json +python MCP_PowerFactory.py --transport stdio ``` -**Import a project and run the full pipeline** -``` -Import the project at C:\Projects\IEEE39.pfd, activate it, -then run the full simulation pipeline defined in the config file. -``` +Before starting the server, configure your project and output paths as described +above. See [INSTALL.txt](INSTALL.txt) for MCP client configuration. -**Export an active grid diagram to PDF** -``` -Export the single-line diagram named "Summary Grid" from the active -PowerFactory project to C:\Projects\exports\summary-grid.pdf. -Do not overwrite an existing file. -``` +Once connected, start with `ping`, then inspect the context using +`get_active_project` and `get_active_study_case` before requesting changes. -**Inspect and preview an EvEMTBench transformer** -``` -List the available 110/20 kV EvEMTBench transformer ratings. Then preview a -20 MVA YNd11 transformer type named "Research Transformer Type" and an element -named "Research Transformer" between "HV Bus" and "LV Bus", using seed 42. -Return the sampled values and confirmation phrase without creating anything. -``` +## Prompt Examples ---- +> Inspect the active project and study case, then list ten buses without changes. -## Research provenance and affiliation +> Preview a 20 kV EvEMTBench load on MV Bus in Research Grid using seed 42. +> Return the sampled values and confirmation phrase without creating anything. -PowerFactory development and validation contributions by Aswin Krishna Poyil -are carried out at the **Institute of Electrical Energy Systems, -Friedrich-Alexander-Universität Erlangen-Nürnberg (FAU)** under the -supervision of Tobias Lorz and Timon Conrad, whose support provided the -opportunity to pursue this work independently. +See [more prompts](references/tool_guide.md#prompt-examples) for calculations, +fault simulations, component management, and exports. -The transformer-type library generator is informed by Georg Kordowich, -Jonathan Loebel, Julian Oelhaf, Andreas Maier, Siming Bayer, Christian Bergler, -and Johann Jaeger, *A simulation based dataset of faults and events for machine -learning in power systems*, arXiv:2608.19777 (2026). The paper documents the -EvEMTBench element models and parameter ranges used to construct realistic -synthetic grids. See [references/evemtbench.md](references/evemtbench.md) for -the implementation notes and source links. +## EvEMTBench Data Source ---- +Equipment catalogs use [EvEMTBench (Kordowich et al., 2026)](https://arxiv.org/abs/2608.19777). +See [source mappings and limitations](references/evemtbench.md). ## License MIT -## Automated tests and validation reports - -The EvEMTBench catalogs and builders are implemented in `evemtbench.py` and -inherited by `DIgSILENTAgent`; the public MCP tool names and arguments are unchanged. -Equipment-type rollback and confirmation hashing share helpers in that module. - -Grid-bootstrap confirmation is bound to the active project and study case, -the resolved grid, the destination folder for a new grid, and the -create/reuse/activation decisions. The preview returns the destination folder -and required folder-creation decisions in `destination_plan`. If that state -changes, request a fresh preview before confirming, including for repeated calls. +## Automated Tests and Validation Reports -PDF export restores a previously active single-line diagram in a `finally` -block after success or failure. If no current diagram is reported, there is -no diagram to restore. Restoration failure adds a warning to the result message: -a completed export still reports success, while a failed export retains its -original error. Successful single-line restoration was -verified live on SP1 in [FT-26](evidence/FT-26_pdf_diagram_restoration_sp1.txt). -Failure-path restoration has automated coverage; active plot pages are not covered. -Bootstrap stale-token rejection after creation was verified live in -[FT-25](evidence/FT-25_bootstrap_confirmation_binding_sp1.txt). - -Run the PowerFactory unit and catalog tests from the repository root: +From the repository root: ```powershell python -m pytest PowerFactory/tests -q ``` -The installed-wheel smoke test, `tests/check_evemtbench_wheel.py`, runs separately -in CI against an installed package outside the checkout. -Validation records and the contribution and function-test reports are in -[`evidence/`](evidence/README.md). +The installed-wheel smoke check, +[`check_evemtbench_wheel.py`](tests/check_evemtbench_wheel.py), runs separately +in CI against an installed package outside the checkout. Automated tests do +not replace licensed-runtime validation. + +Live records and contribution and function-test reports are indexed in +[`evidence/`](evidence/README.md). See the +[validation notes](references/tool_guide.md#automated-tests-and-validation-reports) +for confirmation binding and PDF diagram restoration limitations. diff --git a/PowerFactory/references/tool_guide.md b/PowerFactory/references/tool_guide.md new file mode 100644 index 0000000..d2596d9 --- /dev/null +++ b/PowerFactory/references/tool_guide.md @@ -0,0 +1,752 @@ +# PowerFactory tool and workflow guide + +[Back to the README](../README.md) + +Detailed tool inventories, equipment examples, configuration, and validation notes. + +## Implemented Functions + +### MCP Tools (`MCP_PowerFactory.py`) + +| Tool | Description | +|------|-------------| +| `ping` | Health check — returns `"pong"`. | +| `close_digsilent` | Closes the PowerFactory session. | +| `get_config` | Returns the active `simulation_config.json` as a JSON string. | +| `get_active_project` | Returns the active PowerFactory project without modifying it. | +| `get_active_study_case` | Returns the active PowerFactory study case without modifying it. | +| `get_parameters` | Reads selected attributes from matching calculation-relevant objects, with opt-in metadata and active-project library inspection. | +| `list_objects` | Lists calculation-relevant objects using a raw PowerFactory query. | +| `list_components` | Lists objects using friendly equipment categories. | +| `list_study_cases` | Lists study cases and identifies the active case. | +| `list_contingencies` | Lists configured fault-library cases and their `EvtOutage`, `EvtSwitch`, and `EvtShc` events without modifying the project. | +| `create_contingency` | Creates or reuses an idempotent switch-based contingency definition. | +| `get_contingency_configuration` | Reads the active Contingency Analysis command settings without running a calculation. | +| `configure_contingency_screening` | Persists selected DC/AC-linearised screening criteria without running a calculation. | +| `add_contingency_result_variables` | Adds variables to the configured AC or DC contingency result recording selection without running a calculation. | +| `remove_contingency_result_variables` | Removes variables from matching AC or DC result recording selections without running a calculation. | +| `export_active_grid_pdf` | Exports one active-project single-line diagram to a new PDF file. | +| `create_evemtbench_component` | Routes one nested parameter object to any validated EvEMTBench component creator. | +| `list_evemtbench_components` | Lists any of six EvEMTBench catalogs, filters and required creation parameters without opening PowerFactory. | +| `create_grid_with_buses` | Previews or bootstraps an `ElmNet` and initial `ElmTerm` objects in the active project. | +| `import_project` | Imports a `.pfd` project file and activates it. | +| `create_study_case` | Creates (or activates) a study case by name, copying from a base case when needed. Supports `request_id` for idempotency. | +| `modify_parameter` | Sets one attribute on all PowerFactory objects matching a query string. Auto-casts string values to the correct type. | +| `add_component` | Creates a bus, load, generator, line, or two-winding transformer with validation, rollback, circuit breakers, and optional diagram insertion. | +| `delete_component` | Previews or confirms exact-name component deletion, including generated cubicles, circuit breakers, and optional diagram cleanup. | +| `run_loadflow` | Runs a standalone load flow calculation (ComLdf). | +| `run_short_circuit` | Runs a standalone short-circuit calculation (ComShc). | +| `run_contingency_analysis` | Runs ComSimoutage using its configured method or an explicit AC, DC, AC-linearised, or linearised-screening mode. | +| `get_contingency_results` | Reads a bounded, optionally variable-filtered slice of the configured AC or DC contingency result file using bulk or sparse access. | +| `get_contingency_summary` | Interprets existing contingency results as affected elements, convergence, overloads, and voltage violations. | +| `run_simulation` | Executes the full pipeline defined in `simulation_config.json`. | +| `run_custom_case` | Runs one fault simulation with parameters supplied at call time (no config file edit required). | +| `read_results_csv` | Reads an RMS result CSV; auto-discovers the latest file if no path is given. | + +The contingency tools can create switch-based definitions and inspect or run +the active `ComSimoutage` command. An explicit calculation mode applies only to +that run; the study case's previous mode is restored afterward. + +Read-only contingency tools search the active study case for an existing +`ComSimoutage`; they do not call `GetFromStudyCase`, which can create a missing +command. `get_contingency_results` accepts exact variable identifiers such as +`["c:loading"]`, returns each column's original index and object name/class when +PowerFactory provides one, and automatically includes `b:i_obj` for row-to-case +mapping. It uses `ElmRes.GetColumnValues` with a temporary, deleted `IntVec`, +falling back to PowerFactory's sparse iterator. Filtered reads resolve columns +from `IntMon` selections and `FindColumn`, avoiding a full `ElmRes` metadata +scan. `max_read_seconds` supplies a cooperative read budget; narrow `variables`, +`max_rows`, or `max_columns` for large files. + +`configure_contingency_screening` intentionally persists selected screening +settings. It can choose DC or AC-linearised screening; enable the simple and +combined loading criteria; set their loading and relative-change thresholds; +ignore components already overloaded in the base case; and restrict screening +to recorded elements. It returns the previous and resulting settings without +executing the analysis. + +Result recording is read from `IntMon` selections, never from stale result +columns. Both recording tools report each unreadable monitor once and keep +the record of successful changes to other objects. Class selections identified +by `IntMon.className` count as recorded for objects of that exact class. +Object-specific removal of a class-recorded variable is reported as failed, +without changing either its class or object selections. Other variables can +still be removed. Unidentified targetless selections remain errors; additions +and absence claims are skipped when their state is unknown. + +Removal reports `failed_variables` separately from `already_absent_variables`. +A variable can appear in both `removed` and `failed` when different monitors +produce a partial change; rerun the analysis whenever `removed_variables` is +non-zero. `configured_objects` counts objects with readable matching selections, +and `objects_without_selection` counts objects with no known selection. +The tool schemas require strings; blank or whitespace-only variables are also +rejected. Missing screening flags are returned as `null`, rather than disabled. + +PowerFactory 2026's User Manual, section 19.3.1.1, explicitly supports class-name +variable selections for contingency analysis. These tools report them rather +than attempting to remove a class-wide variable for an individual object. +Its Python Reference, section 5.6.26, documents `IntMon.RemoveVar` return codes: +`0` means removed, `1` means not found; other codes or exceptions are failures. + +### Simulation Engine (`Agent_DIgSILENT.py`) + +| Class / Method | Description | +|----------------|-------------| +| `SimulationConfig` | Dataclass holding all simulation parameters. `from_json()` loads from a JSON file. | +| `Logger` | Timestamped console logging with `info`, `ok`, `warn`, `error`, `section`. | +| `DIgSILENTAgent.__init__` | Initialises the agent from a `SimulationConfig`. | +| `DIgSILENTAgent.connect` | Connects to PowerFactory and activates the configured project. | +| `DIgSILENTAgent.activate_study_case` | Activates or creates the target study case. | +| `DIgSILENTAgent.run_loadflow` | Executes ComLdf. | +| `DIgSILENTAgent.run_contingency_analysis` | Executes ComSimoutage without changing its contingency filters and optionally selects a validated calculation mode. | +| `DIgSILENTAgent.run_rms_simulation` | Applies fault events, then runs ComInc + ComSim. | +| `DIgSILENTAgent._apply_fault_event` | Builds the event sequence for bus faults, line faults, or generator switches. | +| `DIgSILENTAgent.addSwitchEvent` | Creates a PowerFactory `EvtSwitch` event. | +| `DIgSILENTAgent.export_results_to_csv` | Exports the RMS result object to CSV via ComRes. | +| `DIgSILENTAgent.export_active_grid_pdf` | Exports a selected Graphics Board single-line diagram to PDF via ComWr. | +| `DIgSILENTAgent.create_evemtbench_component` | Validates and routes a unified request to an existing specialized component creator. | +| `DIgSILENTAgent.list_evemtbench_components` | Routes catalog requests for all six types and returns parameter metadata. | +| `DIgSILENTAgent.list_evemtbench_transformer_profiles` | Internal transformer catalog implementation. | +| `DIgSILENTAgent.create_evemtbench_transformer` | Samples, previews, creates, connects, verifies, and rolls back a new transformer type and element. | +| `DIgSILENTAgent.list_evemtbench_line_profiles` | Validates and filters the versioned overhead-line sequence profiles. | +| `DIgSILENTAgent.create_evemtbench_line` | Previews, creates, connects, verifies, and rolls back a new overhead-line type and element. | +| `DIgSILENTAgent.list_evemtbench_cable_profiles` | Validates and filters the versioned underground-cable sequence profiles. | +| `DIgSILENTAgent.create_evemtbench_cable` | Previews, creates, connects, verifies, and rolls back a new cable type and element. | +| `DIgSILENTAgent.list_evemtbench_external_grid_profiles` | Validates and filters the external-grid catalog. | +| `DIgSILENTAgent.create_evemtbench_external_grid` | Samples, previews, creates, connects, verifies, and rolls back a new external grid. | +| `DIgSILENTAgent.create_grid_with_buses` | Previews, creates or reuses, verifies, and rolls back a grid and its initial buses. | +| `DIgSILENTAgent.export_project_to_pfd` | Exports the active project to `.pfd` via ComPfdexport. | +| `DIgSILENTAgent.generate_standard_plots` | Reads the exported CSV and produces voltage and generator speed PNG plots. | +| `DIgSILENTAgent.import_project` | Imports a `.pfd` file into the running PowerFactory session. | +| `DIgSILENTAgent.create_study_case` | Creates or reuses a study case by exact name. | +| `DIgSILENTAgent.modify_parameter` | Sets an attribute on all objects returned by `GetCalcRelevantObjects`. | +| `DIgSILENTAgent.add_component` | Creates and verifies supported network components and their connections. | +| `DIgSILENTAgent.delete_component` | Performs guarded exact-name component deletion and cleanup. | +| `DIgSILENTAgent.short_circuit` | Standalone ComShc execution. | +| `DIgSILENTAgent.run_pipeline` | Orchestrates the full workflow and returns a structured status report. | +| `DIgSILENTAgent.close` | Shuts down PowerFactory and clears shared handles. | + +## Greenfield Grid Bootstrap + +`create_grid_with_buses` removes the requirement for a pre-existing +calculation grid and buses. It requires an active PowerFactory project and +study case. It can create the standard `Network Model` / `Network Data` folder +hierarchy, a new `ElmNet`, and one to 100 `ElmTerm` objects without using a +template. A confirmed call activates the grid and verifies that PowerFactory +reports it as calculation-relevant. + +```text +create_grid_with_buses( + grid_name="Research Grid", + nominal_frequency_hz=50, + buses=[ + {"name": "HV Bus", "nominal_voltage_kv": 110}, + { + "name": "MV Bus", + "nominal_voltage_kv": 20, + "out_of_service": true + } + ], + confirmation="" +) +``` + +The first call is a read-only preview. Repeat it with the exact returned +`confirmation_required` value to apply the plan. An existing grid or bus is +reused only when its frequency, voltage, and service state match exactly; +conflicting objects are reported without modification. If creation fails, all +objects created by that call are removed. The tool does not create a project, +study case, single-line diagram, equipment type, or calculation command. + +The bootstrap currently accepts 50 or 60 Hz so its grid is compatible with the +EvEMTBench transformer and overhead-line creators. Once the buses exist, pass +their names and the grid name directly to those tools. + +## Component Management + +### Create a component + +```text +add_component( + component_type, + component_name, + parameters, + grid_name="", + out_of_service=false, + open_digsilent=true, + update_graphics=false +) +``` + +Supported component parameters: + +| Type | Required parameters | Optional parameters | +|---|---|---| +| `bus` | `nominal_voltage_kv` | — | +| `load` | `bus_name`, `active_power_mw` | `reactive_power_mvar` | +| `generator` | `bus_name`, `template_generator`, `active_power_mw` | `reactive_power_mvar` | +| `line` | `bus1_name`, `bus2_name`, `template_line`, `length_km` | — | +| `transformer` | `high_voltage_bus_name`, `low_voltage_bus_name`, `template_transformer` | — | + +Component names are limited to 40 characters, matching PowerFactory's +`loc_name` limit. + +An explicit `null` for optional reactive power is treated as an omitted value +and defaults to zero. Boolean values are not accepted for numeric parameters. + +Each generated connection cubicle contains one closed circuit breaker +(`StaSwitch`, `aUsage="cbk"`, `on_off=1`). Set `update_graphics=true` to +request insertion into the active single-line diagram. + +Graphical updates use the Diagram Layout Tool's automatic insertion mode so +existing diagram objects are not re-laid out as a K-neighbourhood. + +If graphical insertion fails, the network component remains created and the +tool returns `success=false` with the graphical error. Check the returned +message before retrying to avoid creating a duplicate. + +PowerFactory may place an isolated bus far from the existing network when it +is inserted directly into the diagram. For a better anchored result, create +the bus with `update_graphics=false`, then create its first connecting line +with `update_graphics=true`. + +### Delete a component + +```text +delete_component( + component_type, + component_name, + grid_name="", + confirmation="", + open_digsilent=true, + update_graphics=false +) +``` + +Calling `delete_component` without confirmation returns a preview and the +required confirmation phrase: + +```text +DELETE +``` + +Name lookup is case-insensitive. The confirmation phrase contains the full +PowerFactory object path, binds the confirmation to the previewed grid, and +must be copied exactly from the preview. + +Confirmed deletion removes the exact component. A connection cubicle is removed +only when its generated name and sole generated circuit breaker both match; +cubicles containing relays, instrument transformers, or other objects are +preserved. A bus cannot be deleted while it still has connected cubicles. Set +`update_graphics=true` to remove matching diagram objects from every single-line +diagram in the active project and rebuild the active view. + +The response reports component deletion separately from cleanup. In particular, +`success=false` with `deleted=true` means the network component was removed but +some graphical or cubicle cleanup failed; do not retry the component deletion. + +--- + +## Unified EvEMTBench Component Creator + +`create_evemtbench_component` is the common write entry point for the six +validated component families: `transformer`, `overhead_line`, +`underground_cable`, `external_grid`, `load`, and `synchronous_machine`. +Use the creation inputs returned by `list_evemtbench_components` inside +`parameters`: + +```python +create_evemtbench_component( + component_type="load", + parameters={ + "voltage_kv": 20, + "load_name": "Research Load", + "bus_name": "MV Bus", + "grid_name": "Research Grid", + "seed": 42, + "out_of_service": True, + "confirmation": "", + "open_digsilent": True, + }, +) +``` + +An empty `confirmation` keeps the request in preview mode. Repeat the same +request with the exact returned `confirmation_required` value to apply it. +The unified tool delegates validation, seeded sampling, creation, retained-value +checks, and rollback to the existing specialized creator without changing its +behavior. Only the unified create and list tools are exposed through MCP. +Specialized methods remain internal Python implementation details. + +### Catalog discovery and migration + +Call `list_evemtbench_components(component_type, filters)` before creating equipment. +Omit `filters` to inspect the complete selected catalog. The response includes +`available_filters` and `creation_parameters`, with names, types, required flags, +and defaults. Catalog-only data and mapping limits remain in the response. + +| component_type | Optional filters | +|---|---| +| `transformer` | `voltage_class`, `rated_power_mva` (requires voltage_class) | +| `overhead_line` | `voltage_kv`, `profile` | +| `underground_cable` | `profile` | +| `external_grid` | `voltage_kv` | +| `load` | `voltage_kv` | +| `synchronous_machine` | `machine_type` | + +```python +list_evemtbench_components( + component_type="overhead_line", + filters={"voltage_kv": 110, "profile": "p50"}, +) +``` + +This intentionally changes the MCP interface. Replace previous specialized +create calls with `create_evemtbench_component` and place their arguments inside +`parameters`. Replace specialized list calls with `list_evemtbench_components` +and place their filters inside `filters`. The old transformer-only listing now +requires `component_type="transformer"`; move voltage_class/rated_power_mva into +`filters`. Restart the server and refresh the client's tool list after updating. +The examples below use the new public interface. + +--- + +## EvEMTBench Transformer Database and Creator + +The versioned database at +[`data/evemtbench_transformers.json`](../data/evemtbench_transformers.json) +stores the fixed-voltage transformer classes from EvEMTBench Tables 10-12. +Use `list_evemtbench_components` to inspect classes, ratings, ranges, vector +groups, zero-sequence rules, and saturation data without opening PowerFactory. + +`create_evemtbench_component(component_type="transformer", parameters=...)` creates a new `TypTr2` directly; it does not +copy or require an existing transformer. It also creates an `ElmTr2`, two +cubicles, and closed circuit breakers between existing buses. The type is +stored in the project library's `Transformer Types` folder; a confirmed call +creates that folder when it is absent from a greenfield project. The first +call is a read-only preview when `confirmation` is empty: + +```text +create_evemtbench_component( + component_type="transformer", + parameters={ + "voltage_class": "110/20", + "rated_power_mva": 20, + "nominal_frequency_hz": 60, + "vector_group": "YNd", + "clock_number": 11, + "transformer_name": "Research Transformer", + "type_name": "Research Transformer Type", + "high_voltage_bus_name": "HV Bus", + "low_voltage_bus_name": "LV Bus", + "seed": 42, + "confirmation": "", + "open_digsilent": True, + }, +) +``` + +The preview validates the grid, buses, nominal voltages, names, rating, vector +group, clock number, and an explicit nominal frequency of 50 or 60 Hz, then +returns the exact confirmation phrase. Frequency is not inferred from the grid +because existing projects can contain types with a different `frnom`. A +confirmed call writes three-phase technology (`nt2ph`), frequency (`frnom`), +the impedance modes, `strn`, `utrn_h`, `utrn_l`, `uktr`, `pcutr`, `pfe`, +`curmg`, `tr2cn_h`, `tr2cn_l`, and `nt2ag`. Structural settings and nameplate +ratings are committed before the performance values so PowerFactory can +refresh dependent type fields. Copper loss and short-circuit voltage are +converted to the equivalent `r1pu` and `x1pu` values before assignment, then +verified again through PowerFactory's derived `pcutr` and `uktr` attributes. +The performance values use PowerFactory's native Python property assignment; +each value is checked immediately and again after the database write. If element +creation fails, the new type, element, cubicles, and switches are rolled back. + +Table 10 zero-sequence and Table 12 saturation data are stored and returned by +the catalog tool, but are not written until their PowerFactory attribute +mappings are validated. The `Block` class and other component families remain +catalog-only for the same reason. The caller must explicitly choose a clock +number because Table 11 specifies vector-group families rather than one clock +number for every generated transformer. + +See [references/evemtbench.md](evemtbench.md) for the source mapping +and limitations. + +--- + +## EvEMTBench Overhead-Line Database and Creator + +[`data/evemtbench_overhead_lines.json`](../data/evemtbench_overhead_lines.json) +stores the published 5th, 50th and 95th percentile sequence parameters from +Table 7 for 20, 110 and 345 kV overhead lines, together with the corresponding +Table 3 length ranges. `list_evemtbench_components(component_type="overhead_line")` inspects this catalog +without opening PowerFactory. + +`create_evemtbench_component(component_type="overhead_line", parameters=...)` creates a new `TypLne` directly and connects a new +`ElmLne` between two existing equal-voltage buses. The caller selects `p05`, +`p50` or `p95`, supplies the actual current rating, and chooses 50 or 60 Hz. +The first call is a read-only preview: + +```text +create_evemtbench_component( + component_type="overhead_line", + parameters={ + "voltage_kv": 110, + "nominal_frequency_hz": 50, + "rated_current_ka": 1, + "profile": "p50", + "length_km": 10, + "line_name": "Research Line", + "type_name": "Research Line Type", + "bus1_name": "Bus A", + "bus2_name": "Bus B", + "confirmation": "", + "open_digsilent": True, + }, +) +``` + +The preview checks the voltage class, frequency, paper length range, existing +buses, names, and exact confirmation phrase. A confirmed call creates the +project library's `Line Types` folder when needed, writes `uline`, `sline`, +`frnom`, `rline`, `xline`, `cline`, `rline0`, `xline0` and `cline0`, and then +creates the line, two cubicles and closed circuit breakers. Published +capacitances are converted from nF/km to PowerFactory's uF/km attributes. New +objects are rolled back when creation fails. + +This tool deliberately implements a three-phase `TypLne` sequence equivalent, +not the paper's frequency-dependent geometric ULM model. The three published +percentiles are discrete profiles rather than endpoints of an invented +sampling distribution. Tower and conductor geometry remains catalog-only. + +--- + +## EvEMTBench Underground-Cable Database and Creator + +[`data/evemtbench_underground_cables.json`](../data/evemtbench_underground_cables.json) +stores Table 6's published 5th, 50th and 95th percentile sequence parameters +for 20 kV underground cables and Table 3's 0.5 to 3.75 km length range. +`list_evemtbench_components(component_type="underground_cable")` returns those profiles plus the paper's +construction and installation data without opening PowerFactory. + +`create_evemtbench_component(component_type="underground_cable", parameters=...)` creates a new `TypLne` sequence equivalent and +connects a new `ElmLne` between two existing 20 kV buses. The caller chooses a +published percentile, 50 or 60 Hz, and an engineering current rating because +Table 6 does not publish cable ampacity. The first call is a read-only preview: + +```text +create_evemtbench_component( + component_type="underground_cable", + parameters={ + "nominal_frequency_hz": 50, + "rated_current_ka": 0.4, + "profile": "p50", + "length_km": 2, + "cable_name": "Research Cable", + "type_name": "Research Cable Type", + "bus1_name": "MV Bus A", + "bus2_name": "MV Bus B", + "grid_name": "Research Grid", + "out_of_service": True, + "confirmation": "", + "open_digsilent": True, + }, +) +``` + +Repeat the call with the exact returned `confirmation_required` value to +create the type, cable element, two cubicles, and closed circuit breakers. +Published capacitances are converted from nF/km to PowerFactory's uF/km +attributes, retained values are verified, and new objects are rolled back if +creation fails. + +The created `TypLne` is a three-phase sequence equivalent suitable for initial +network construction; it is not the paper's geometric ULM cable model. The +catalog preserves insulation, conductor, sheath, arrangement, burial-depth, +spacing, and earth-resistivity data for a future geometric implementation. + +--- + +## EvEMTBench External-Grid Database and Creator + +[`data/evemtbench_external_grids.json`](../data/evemtbench_external_grids.json) +stores the Table 4 ranges for 110 kV and combined 380/345 kV external-grid +equivalents. `list_evemtbench_components(component_type="external_grid")` inspects the ranges +without opening PowerFactory. + +`create_evemtbench_component(component_type="external_grid", parameters=...)` uses a supplied seed to sample every Table 4 +interval uniformly and connects a new slack `ElmXnet` to an existing bus. The +bus must be exactly 110, 345, or 380 kV. The first call is a read-only preview: + +```text +create_evemtbench_component( + component_type="external_grid", + parameters={ + "voltage_kv": 110, + "external_grid_name": "Research External Grid", + "bus_name": "HV Bus", + "grid_name": "Research Grid", + "seed": 42, + "out_of_service": True, + "confirmation": "", + "open_digsilent": True, + }, +) +``` + +Repeat the call with the exact returned `confirmation_required` value to create +the external grid, cubicle, and closed circuit breaker. The tool writes `snss`, +`rntxn`, `x0tx1`, `r0tx0`, `usetp`, and `phiini`, sets `bustp="SL"`, verifies +the retained values, and rolls back new objects if creation fails. The sampled +values are research inputs and must still be checked for suitability in the +target network. The attribute mapping and retained values were validated on +PowerFactory 2026 SP1; SP4 validation remains pending. + +--- + +## EvEMTBench Load Database and Creator + +[`data/evemtbench_loads.json`](../data/evemtbench_loads.json) stores the Table 5 +load-power ranges for 20, 110, and 345 kV systems. The read-only +`list_evemtbench_components(component_type="load")` tool returns these ranges without opening +PowerFactory. + +`create_evemtbench_component(component_type="load", parameters=...)` samples active and reactive power directly at 110 and +345 kV. At 20 kV it samples apparent power and a reactive-power angle between +the published 0.95 capacitive and 0.8 inductive limits, then calculates P and +Q. The first call is a read-only preview: + +```text +create_evemtbench_component( + component_type="load", + parameters={ + "voltage_kv": 20, + "load_name": "Research Load", + "bus_name": "MV Bus", + "grid_name": "Research Grid", + "seed": 42, + "out_of_service": True, + "confirmation": "", + "open_digsilent": True, + }, +) +``` + +Repeat the call with the exact returned `confirmation_required` value to create +the balanced `ElmLod`, cubicle, and closed circuit breaker. The tool verifies +`plini`, `qlini`, the bus connection, and the service state and rolls back new +objects if creation fails. The Table 5 ZIP coefficients remain catalog-only +until their PowerFactory attribute mapping is live-validated. + +--- + +## EvEMTBench Synchronous-Machine Database and Creator + +[`data/evemtbench_synchronous_machines.json`](../data/evemtbench_synchronous_machines.json) +stores the salient-pole and round-rotor ranges from Table 13, including the +rated-voltage selection rule and parameter dependencies. Use +`list_evemtbench_components(component_type="synchronous_machine")` to inspect both profiles or to +filter by `salient_pole` or `round_rotor`. + +`create_evemtbench_component(component_type="synchronous_machine", parameters=...)` supports the round-rotor profile. The +caller selects a rating from 25 to 1200 MVA and an existing bus. The tool +derives the rated voltage using Table 13 (6.3, 10.5, 21, or 27 kV), samples the +remaining ranges reproducibly from `seed`, and validates all published +reactance and saturation dependencies. The requested P/Q operating point must +fit the MVA rating and the paper's reactive-power limits. + +```python +create_evemtbench_component( + component_type="synchronous_machine", + parameters={ + "machine_type": "round_rotor", + "rated_power_mva": 100, + "machine_name": "Research Generator", + "type_name": "Research Generator Type", + "bus_name": "Generator Bus 10.5", + "grid_name": "Research Grid", + "seed": 42, + "active_power_mw": 60, + "reactive_power_mvar": 5, + "out_of_service": True, + "confirmation": "", + "open_digsilent": True, + }, +) +``` + +Repeat the call with the exact returned `confirmation_required` string to +create the `TypSym`, `ElmSym`, cubicle, and closed circuit breaker. The type is +created without a template, and all new objects are rolled back if creation +fails. Table 13's short-circuit time constants are converted to PowerFactory's +writable open-circuit inputs and checked again through the derived `tds`, +`tqs`, `tdss`, and `tqss` values. The attribute relationships, `iturbo=1` +round-rotor mode, confirmed creation, and structured response were validated +on PowerFactory 2026 SP1. Salient-pole creation remains disabled because Table +13 does not provide `x'q` or `T'q`; the profile continues to be available +through the read-only catalog. + +--- + +## Configuration + +Copy `simulation_config.example.json` to `simulation_config.json` and fill in your paths and parameters: + +```json +{ + "project_path": "\\\\\\.IntPrj", + "study_case": "Case 1", + "base_study_case": "0. Base", + "output_dir": "C:\\path\\to\\output", + "run_label": "Test_1", + "result_name": "All calculations.ElmRes", + "cases": [ + { + "case_name": "Fault_1", + "fault_type": "bus", + "fault_element": "Bus 25.ElmTerm", + "t_start": 0.0, "t_fault": 1.0, "t_clear": 1.08, "t_end": 10.0, "dt_rms": 0.01 + } + ] +} +``` + +Supported `fault_type` values: `"bus"`, `"line"`, `"gen_switch"`. + + +--- + +## Output Structure + +Each simulation run creates a dedicated subfolder: + +``` +output_dir/ +└── run_label/ + ├── run_label_RMS.csv + ├── run_label_voltages.png + ├── run_label_gen_speeds.png + └── run_label.pfd (optional) +``` + +--- + +## Prompt Examples + +Below are example prompts you can send to an AI assistant connected to this MCP server. + +**Health check** +``` +Ping the PowerFactory server and confirm it's reachable. +``` + +**Run a load flow** +``` +Run a load flow on the active study case and tell me if it converged. +``` + +**Run a transient fault simulation** +``` +Simulate a three-phase bus fault on Bus 25 starting at t=1.0 s, cleared at t=1.08 s, +with a total simulation window of 10 s and a time step of 0.01 s. Export the results +and generate the voltage and generator speed plots. +``` + +**Modify a parameter before running** +``` +Set the generator G 01.ElmSym out of service (e:outserv = 1), +then run a load flow and report the result. +``` + +**Inspect the active context** +``` +Return the active PowerFactory project and study case, then list the first ten +buses without modifying anything. +``` + +**Create and connect a bus** +``` +Create an in-service 110 kV bus named "MCP Example Bus" in Grid without a +graphical update. Connect Bus 01 to it with a 1 km line based on +"Line 01 - 02.ElmLne", request a graphical update for the line, and run a load +flow. Stop if any operation fails. +``` + +**Preview and delete a component** +``` +Preview deletion of the load "MCP Example Load" in Grid. Show me the required +confirmation phrase and do not perform the confirmed deletion yet. +``` + +**Create a new study case and run a custom fault** +``` +Create a study case called "Scenario_A" based on "0. Base", +then run a line fault on "Line 12-34.ElmLne" from t=0.5 s to t=0.58 s +in a 15 s window. Save the CSV and plots to C:\Results\Scenario_A. +``` + +**Short-circuit calculation** +``` +Run a short-circuit calculation on the active study case and return the results. +``` + +**Read the latest results** +``` +Read the most recent RMS results CSV and summarise the peak voltage deviations +and minimum generator speed recorded during the simulation. +``` + +**Import a project and run the full pipeline** +``` +Import the project at C:\Projects\IEEE39.pfd, activate it, +then run the full simulation pipeline defined in the config file. +``` + +**Export an active grid diagram to PDF** +``` +Export the single-line diagram named "Summary Grid" from the active +PowerFactory project to C:\Projects\exports\summary-grid.pdf. +Do not overwrite an existing file. +``` + +**Inspect and preview an EvEMTBench transformer** +``` +List the available 110/20 kV EvEMTBench transformer ratings. Then preview a +20 MVA YNd11 transformer type named "Research Transformer Type" and an element +named "Research Transformer" between "HV Bus" and "LV Bus", using seed 42. +Return the sampled values and confirmation phrase without creating anything. +``` + +--- + +## Automated tests and validation reports + +The EvEMTBench catalogs and builders are implemented in `evemtbench.py` and +inherited by `DIgSILENTAgent`; the public MCP tool names and arguments are unchanged. +Equipment-type rollback and confirmation hashing share helpers in that module. + +Grid-bootstrap confirmation is bound to the active project and study case, +the resolved grid, the destination folder for a new grid, and the +create/reuse/activation decisions. The preview returns the destination folder +and required folder-creation decisions in `destination_plan`. If that state +changes, request a fresh preview before confirming, including for repeated calls. + +PDF export restores a previously active single-line diagram in a `finally` +block after success or failure. If no current diagram is reported, there is +no diagram to restore. Restoration failure adds a warning to the result message: +a completed export still reports success, while a failed export retains its +original error. Successful single-line restoration was +verified live on SP1 in [FT-26](../evidence/FT-26_pdf_diagram_restoration_sp1.txt). +Failure-path restoration has automated coverage; active plot pages are not covered. +Bootstrap stale-token rejection after creation was verified live in +[FT-25](../evidence/FT-25_bootstrap_confirmation_binding_sp1.txt). + +Run the PowerFactory unit and catalog tests from the repository root: + +```powershell +python -m pytest PowerFactory/tests -q +``` + +The installed-wheel smoke test, `tests/check_evemtbench_wheel.py`, runs separately +in CI against an installed package outside the checkout. +Validation records and the contribution and function-test reports are in +[`evidence/`](../evidence/README.md).