Network Validation
This guide covers PyPtP's validation system for checking network data quality. The validators detect issues such as branches that refer to missing nodes, nominal voltages that do not match, branch corners that miss their nodes, and element data a network file cannot hold.
Full Example
View the complete code: 04_validation.py
Running Validation
Basic Usage
from pyptp import NetworkMV, configure_logging
from pyptp.validator import CheckRunner, Severity
configure_logging(level="INFO")
# Load network
network = NetworkMV.from_file("network.vnf")
# Run all validators
runner = CheckRunner(network)
report = runner.run()The CheckRunner executes all registered validation checks and returns a Report containing any issues found.
Quick Summary
from pyptp.ptp_log import logger
logger.info("Validation: %s", report.summary())The summary() method returns a concise string like "Found 15 issues: 3 error, 12 warning", or "No issues found".
Understanding Severity Levels
# Count issues by severity
error_count = sum(1 for issue in report.issues if issue.severity == Severity.ERROR)
warning_count = sum(1 for issue in report.issues if issue.severity == Severity.WARNING)
logger.info(" Errors: %s", error_count)
logger.info(" Warnings: %s", warning_count)Severity Definitions
| Level | Meaning | Action Required |
|---|---|---|
ERROR | Network will not function correctly | Must fix before use |
WARNING | Potential issue or suboptimal configuration | Review and fix if relevant |
Inspecting Issues
Iterate Critical Issues
for issue in report.issues:
if issue.severity == Severity.ERROR:
logger.error("ERROR: %s", issue.message)
logger.error(" Element: %s %s", issue.object_type, issue.object_id)Issue Properties
| Property | Description |
|---|---|
severity | Severity.ERROR or Severity.WARNING |
code | Machine-readable issue identifier |
message | Human-readable description |
object_type | Element type, for example "Node" or "Cable" |
object_id | GUID of the affected element |
validator | Name of the validator that raised the issue |
details | Extra data for the issue, or None |
Filter by Issue Code
# Find all coordinate mismatch issues
coord_issues = [
issue for issue in report.issues
if issue.code == "corner_coordinate_mismatch"
]Topology and Drawing Checks
These validators check how elements refer to each other and how branches are drawn:
| Validator | Network | Code | Severity | Reports |
|---|---|---|---|---|
cable_node_reference | LV, MV | missing_node_reference | ERROR | A cable whose node1 or node2 is not a node in the network |
link_node_reference | LV, MV | missing_node_reference | ERROR | A link whose node1 or node2 is not a node in the network |
transformer_node_reference | LV, MV | missing_node_reference | ERROR | A transformer whose node1 or node2 is not a node in the network |
node_unom | LV, MV | invalid_node_unom | ERROR | A node with a nominal voltage of 0 or less |
branch_unom_validator | LV, MV | unequal_unom | ERROR | A link, cable, reactance coil or line between nodes of different nominal voltage, unless every phase and auxiliary conductor switch on both sides is open |
special_transformer_sort | LV, MV | special_transformer_sort_none | ERROR | A special transformer whose sort is still SpecialTransformerSort.NONE |
branch_corner_coordinates | LV, MV | corner_coordinate_mismatch | WARNING | A branch drawing whose first_corners or second_corners does not start at the position of its node on that sheet |
branch_corner_coordinates | LV, MV | empty_corner_array | WARNING | A branch drawing with an empty first_corners or second_corners |
See Corner Validation for how corners are matched to line symbols.
Cable, Connection and Source Checks
These validators check the electrical data of single elements:
| Validator | Network | Code | Severity | Reports |
|---|---|---|---|---|
connection_load_phases | LV | load_ignored_for_phases | WARNING | A connection whose load or generation is set in fields its phase setting ignores: p1/q1 on a three-phase connection (phases 4), or the per-phase fields (pa, qa, ... qbc) on any other. The part detail says load or generation |
gm_type_reference | LV | unknown_gm_type | ERROR | A GM on a connection that refers to a GM type number the network does not contain |
source_voltage | LV | source_umin_out_of_range, source_umax_out_of_range | ERROR | A source whose umin is outside 0.5 to 1.5 times the nominal voltage of its node, or whose umax is below umin or above 1.5 times that voltage |
cable_part | LV, MV | cable_part_too_short | ERROR | A cable part shorter than 0.5 m (LV) or 1 m (MV) |
cable_part | LV, MV | cable_part_without_type | ERROR | A cable part without cable type data. An MV cable is saved without that part, an LV cable without its impedance data |
secondary_side | LV, MV | secondary_side_invalid | ERROR | A fuse, switch, measure field or fault indicator on a side its object does not have: side 0 in a node, 1 in an element, 1 or 2 in a branch, 1 to 3 in a three-winding transformer. A measure field in a transformer load may also use side 2 |
A source outside the source_voltage limits, or a secondary on a side secondary_side reports, does not load.
The validators in both tables are in ValidatorCategory.CORE, so CheckRunner(network).run(categories=ValidatorCategory.CORE) runs all of them without the native loader. cable_part_without_type fires for every cable that has no cable type data, for example an LV cable built without set_cable_type(), or an MV cable read from a file that has no type data for it.
Exporting Reports
JSON Export
from pathlib import Path
Path("validation_report.json").write_text(
str(report.to_json()),
encoding="utf-8"
)The JSON format is useful for:
- Automated CI/CD pipelines
- Integration with issue tracking systems
- Historical comparison of validation results
Programmatic Access
# Check if network passes validation
is_valid = error_count == 0
logger.info("Network valid: %s", is_valid)
# Use in automation
if not is_valid:
raise ValueError(f"Network has {error_count} validation errors")Validation in Workflows
Pre-Save Validation
def save_if_valid(network, path):
"""Only save network if it passes validation."""
report = CheckRunner(network).run()
errors = sum(1 for i in report.issues if i.severity == Severity.ERROR)
if errors > 0:
logger.error("Cannot save: %s errors found", errors)
return False
network.save(path)
logger.info("Network saved to %s", path)
return TrueCI/CD Integration
import sys
def validate_network(vnf_path):
"""Exit with error code if validation fails."""
network = NetworkMV.from_file(vnf_path)
report = CheckRunner(network).run()
errors = sum(1 for i in report.issues if i.severity == Severity.ERROR)
if errors > 0:
for issue in report.issues:
if issue.severity == Severity.ERROR:
print(f"ERROR: {issue.message}")
sys.exit(1)
print("Validation passed")
sys.exit(0)Validation by the Gaia or Vision Loader
The native_loader validator, included in CheckRunner(network).run() by default, saves the network and loads it with the Gaia or Vision loader from the bundled migrator library. save() and from_file() run the same check. See Native Validation for the issue fields, return codes and the native_check opt-out.
Complete Example
"""Validate network data quality."""
from pathlib import Path
import sys
from pyptp import NetworkMV, configure_logging
from pyptp.ptp_log import logger
from pyptp.validator import CheckRunner, Severity
configure_logging(level="INFO")
# Load network
network = NetworkMV.from_file("network.vnf")
# Run validation
runner = CheckRunner(network)
report = runner.run()
# Check results
logger.info("Validation: %s", report.summary())
# Count issues by severity
error_count = sum(1 for issue in report.issues if issue.severity == Severity.ERROR)
warning_count = sum(1 for issue in report.issues if issue.severity == Severity.WARNING)
logger.info(" Errors: %s", error_count)
logger.info(" Warnings: %s", warning_count)
# Show critical issues
for issue in report.issues:
if issue.severity == Severity.ERROR:
logger.error("ERROR: %s", issue.message)
logger.error(" Element: %s %s", issue.object_type, issue.object_id)
# Find all coordinate mismatch issues
coord_issues = [
issue for issue in report.issues
if issue.code == "corner_coordinate_mismatch"
]
# Export report
Path("validation_report.json").write_text(str(report.to_json()), encoding="utf-8")
# Check if network is valid
is_valid = error_count == 0
logger.info("Network valid: %s", is_valid)
# Use in automation
if not is_valid:
raise ValueError(f"Network has {error_count} validation errors")
# Pre-Save Validation
def save_if_valid(network, path):
"""Only save network if it passes validation."""
report = CheckRunner(network).run()
errors = sum(1 for i in report.issues if i.severity == Severity.ERROR)
if errors > 0:
logger.error("Cannot save: %s errors found", errors)
return False
network.save(path)
logger.info("Network saved to %s", path)
return True
# CI/CD Integration
def validate_network(vnf_path):
"""Exit with error code if validation fails."""
network = NetworkMV.from_file(vnf_path)
report = CheckRunner(network).run()
errors = sum(1 for i in report.issues if i.severity == Severity.ERROR)
if errors > 0:
for issue in report.issues:
if issue.severity == Severity.ERROR:
print(f"ERROR: {issue.message}")
sys.exit(1)
print("Validation passed")
sys.exit(0)