Python library for reading, editing, validating, and comparing PSS/E power system models (RAW v33/34/35 and RAWX formats).
Project description
psse_model_util
Python library for reading, editing, validating, and comparing PSS/E power system models (RAW v33/34/35 and RAWX formats).
Status: Active development — proprietary, internal use only.
Overview
psse_model_util parses PSS/E RAW and RAWX files into structured Python objects backed by pandas DataFrames and a NetworkX graph. Its primary use case is comparing seasonal Bulk Electric System (BES) model variants (e.g. summer vs. winter).
Key capabilities:
- Load RAW (v33/34/35) or RAWX (JSON) files into a
Modelobject - Filter networks by area, voltage level, or arbitrary query
- Compare two models: DataFrame-level deltas + graph topology diffs (added/removed edges, path sectionalizations, bypasses)
- Export to CSV or pickle for downstream analysis
- Build a NetworkX graph for topological analysis (shortest paths, connectivity, etc.)
Installation
Prerequisites
Install from PyPI
# Latest stable release
pip install psse-model-util
# Latest pre-release (beta / rc) — pip skips pre-releases without --pre
pip install --pre psse-model-util
Install (editable / dev)
# Clone or copy the project directory
cd psse_model_util
# Install with PDM
pdm install
# Or install directly with pip (editable)
pip install -e .
Install dev extras
pdm install -G dev
# Includes: pytest, pytest-cov, ruff
Quick Start
Load a model
from psse_model_util.model import Model
# From a RAW file (v33/34/35)
model = Model("path/to/model.raw", name="Summer_Peak")
# From a RAWX file
model = Model("path/to/model.rawx", name="Summer_Peak")
# Access network data
buses = model.network.bus # pd.DataFrame
lines = model.network.acline # pd.DataFrame
generators = model.network.generator # pd.DataFrame
Filter a model
# Filter by area (Model-level — delegates to network)
filtered = model.filter_by_area(areas={101: "AREA1", 102: "AREA2"})
# Filter by voltage level or section query (Network-level)
ehv = model.network.filter_by_kv(low_value=345)
model.network.filter_section("bus", "baskv >= 230", inplace=True)
Build and query the network graph
import networkx as nx
graph = model.network.graph(regenerate=True)
# Shortest path between two buses
path = nx.shortest_path(graph, ("bus", 101), ("bus", 205))
# Access bus properties
props = graph.nodes[("bus", 101)]
Compare two models
from psse_model_util.model import Model
from psse_model_util.compare import ModelComparison
model1 = Model("summer.raw", name="Summer")
model2 = Model("winter.raw", name="Winter")
# Filter to your area of interest first (recommended)
m1 = model1.filter_by_area(areas=[101, 102, 103])
m2 = model2.filter_by_area(areas=[101, 102, 103])
comp = ModelComparison(m1, m2)
comp.compare_network_dfs() # DataFrame-level column deltas
comp.compare_graph() # Topology: added/removed edges, path changes
comp.to_csv(df_comparison_to_csv=True, graph_comparison_to_csv=True)
Export and serialise a model
# Export all network sections to CSV
model.to_csv()
# Cache as pickle (fast reload)
model.to_pickle()
# Reload from cache
model2 = Model("path/to/model.raw") # auto-loads from cache if available
# Serialise to a JSON string or file
json_str = model.to_json()
model.to_json(file_path="model_export.json")
# Load from a rawx dict (avoids the json_data mutation issue in to_json round-trips)
from psse_model_util.raw_to_rawx import raw_file_to_rawx_dict
rawx_dict = raw_file_to_rawx_dict("path/to/model.raw")
model2 = Model(file_path_or_json=rawx_dict)
Note:
Model(file_path_or_json=json_str)works for loading but a fullto_json()→Model(json_str)round-trip is lossy due to a known internal mutation (see Known Issues). Useraw_file_to_rawx_dictas shown above when you need a lossless in-memory copy.
Project Structure
psse_model_util/
├── model.py # Model, Network, General, Harmonics, TimeSeries classes
├── raw_to_rawx.py # RAW file parser → rawx-compatible dict
├── compare.py # ModelComparison class
├── version.py # Legacy version shim (deprecated — use __about__.py)
├── inch.py # WIP: IDEV/INCH export (Phase 3.2)
├── __about__.py # Version: 2026.4.3 (calver YYYY.M.micro)
├── __init__.py
├── CLAUDE.md # Architecture guide for Claude Code
├── common/
│ ├── constants.py # INCLUDE_AREAS, filter constants, query defaults
│ ├── dataframe_util.py # convert_df_column_dtypes()
│ ├── dirs.py # Canonical app dirs (platformdirs)
│ ├── file_util.py # to_pickle(), read_pickle(), wait_for_file()
│ ├── json_util.py # load_and_clean_json()
│ └── logging_config.py # Logger setup
├── dataformat/
│ ├── classes.py # Data model classes
│ ├── inch_templates.py # INCH format templates
│ ├── rawx_json_template.py # RAWX section schema (fields, data_types, id_cols, bus_cols)
│ └── rawx_raw_map.csv # RAW ↔ RAWX field mapping (v34 + v35 columns)
└── tests/
├── conftest.py # Shared fixtures and sys.path setup
├── test_classes.py # dataformat/classes.py domain types
├── test_compare.py # ModelComparison
├── test_dataframe_util.py # common/dataframe_util helpers
├── test_dirs.py # common/dirs platform paths
├── test_file_util.py # common/file_util pickle helpers
├── test_model.py # Model + Network (primary integration tests)
├── test_model2.py # Supplemental Model method tests
├── test_raw_to_rawx.py # RAW parser + substation section
├── test_rawx.py # RAWX-format model loading
├── test_phase_*.py # Feature-phase regression tests
├── legacy_tests/ # Pre-refactor scripts (not collected by pytest)
└── data/ # Test fixtures (do not delete)
├── Model_1.raw # Synthetic v34 — baseline for ModelComparison tests
├── Model_2.raw # Synthetic v34 — intentionally modified from Model_1
├── sample_34.raw # Minimal v34 RAW file
├── sample2_34.raw # Second minimal v34 RAW file
├── sample_v35.raw # Minimal v35 RAW file
├── sample_v35.rawx # Minimal v35 RAWX file
├── sample2_v35.rawx # Second minimal v35 RAWX file
├── transformer.raw # Transformer-focused test case
├── minimal.raw # Smallest valid RAW file
└── Model_1 and 2 differences.txt # Documented delta between Model_1/Model_2
Dev Setup
# Lint
pdm run ruff check .
# Run all tests
pdm run pytest
# Run a single test file
pdm run pytest tests/test_model.py
# Run a specific test by name
pdm run pytest tests/test_model.py -k "test_filter_by_area"
# Test with coverage report
pdm run pytest --cov=psse_model_util --cov-report=term-missing
# Current: 434 tests, 78% coverage (CI gate: 40%)
# Version (hatch-managed calver)
hatch version
Claude Code users:
CLAUDE.mdat the repo root contains architecture notes, data-flow diagrams, and the rationale behind key design decisions.
Versioning & Releases
Uses CalVer: YYYY.M.micro — managed by Hatch via src/psse_model_util/__about__.py (the single source of truth for the version).
2026.4.5 → year=2026, month=4, micro=5 (stable)
2026.4.5b1 → beta 1 of 2026.4.5 (pre-release)
2026.5.0rc1 → release candidate 1 of 2026.5.0 (pre-release)
Pre-release suffixes follow PEP 440: bN (beta), rcN (release candidate). pip ignores pre-releases unless you pass --pre, so betas reach only opt-in testers while stable installs stay on full releases.
Release process
Publishing is automated via Trusted Publishing (OIDC — no API tokens) in .github/workflows/publish.yml:
- Bump
__version__insrc/psse_model_util/__about__.py(use abN/rcNsuffix for a pre-release). - Cut a GitHub Release, or run the Publish to PyPI workflow manually (Actions → Publish to PyPI → Run workflow). A release created by
cd.ymlusesGITHUB_TOKEN, which cannot triggerpublish.yml, so the manual run is the reliable trigger. - The workflow builds, publishes to TestPyPI, then to PyPI. The
pypistep is gated by a required-reviewer environment, so the final publish needs a manual approval click.
Known Issues / Roadmap
| Issue | Phase | Status |
|---|---|---|
RAWX export bug — exported .rawx doesn't reload in PSS/E |
2.2 | Open (low priority) |
to_json() → Model(json_str) round-trip is lossy (json_data mutation in _create_dataframe) |
— | Open |
| INCH/IDEV export scaffolded but not functional | 3.2 | Open |
License
Proprietary — internal use only.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file psse_model_util-2026.4.5b1.tar.gz.
File metadata
- Download URL: psse_model_util-2026.4.5b1.tar.gz
- Upload date:
- Size: 354.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eaa44077bb44faf5b8134bacd38f7ceef3c62cd1a38e66f7a044fabcc2212e97
|
|
| MD5 |
85ccc6e20db64c58c54be5429ce33931
|
|
| BLAKE2b-256 |
2ff141520c395c5a37033156b4519e9d56c3c72e4280ab57efac6b5e90a9aaed
|
Provenance
The following attestation bundles were made for psse_model_util-2026.4.5b1.tar.gz:
Publisher:
publish.yml on ppsyOps/psse_model_util
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
psse_model_util-2026.4.5b1.tar.gz -
Subject digest:
eaa44077bb44faf5b8134bacd38f7ceef3c62cd1a38e66f7a044fabcc2212e97 - Sigstore transparency entry: 1862867467
- Sigstore integration time:
-
Permalink:
ppsyOps/psse_model_util@3437ab8fa34d0572396a0de7a2af5e83b370d366 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ppsyOps
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3437ab8fa34d0572396a0de7a2af5e83b370d366 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file psse_model_util-2026.4.5b1-py3-none-any.whl.
File metadata
- Download URL: psse_model_util-2026.4.5b1-py3-none-any.whl
- Upload date:
- Size: 123.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9b457dc13acf40067b6886adbfa03f3a830bc5cff2a0f5a4d4d10d84d051b94
|
|
| MD5 |
7a6af48f3d2a51c0f2d08e0f07489b71
|
|
| BLAKE2b-256 |
34b83fc8d08a4472a7af40c36a004e62540348c3fbb0e2de7f40cf0a19a5443a
|
Provenance
The following attestation bundles were made for psse_model_util-2026.4.5b1-py3-none-any.whl:
Publisher:
publish.yml on ppsyOps/psse_model_util
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
psse_model_util-2026.4.5b1-py3-none-any.whl -
Subject digest:
a9b457dc13acf40067b6886adbfa03f3a830bc5cff2a0f5a4d4d10d84d051b94 - Sigstore transparency entry: 1862867899
- Sigstore integration time:
-
Permalink:
ppsyOps/psse_model_util@3437ab8fa34d0572396a0de7a2af5e83b370d366 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ppsyOps
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3437ab8fa34d0572396a0de7a2af5e83b370d366 -
Trigger Event:
workflow_dispatch
-
Statement type: