PyP6Xer - Python Primavera P6 XER Parser
PyP6Xer is a comprehensive Python library for parsing, analyzing, and manipulating Primavera P6 XER (eXchange ERport) files. It provides an intuitive, object-oriented interface to access all project data including activities, resources, calendars, and relationships.
🚀 Features
- Complete XER Parsing: Parse all major XER file components — every model-backed table, validated against the Oracle XER schema
- Intuitive API: Object-oriented design with clean, pythonic interfaces
- Comprehensive Data Access: Activities, resources, calendars, WBS, relationships, and more
- Any-Table Access: Every XER table (including ones without a dedicated model class) is available losslessly via
xer.tables["TABLE_NAME"] - Data Export: Write modified data back to XER format — full, lossless round-trips
- Relationship Editing: Add, remove, and re-route TASKPRED relationships programmatically
- Schedule Analysis: Built-in DCMA 14-point analysis, critical path, float analysis, and schedule quality checks
- Resource Management: Resource allocation, utilization, and cost analysis
- Progress Tracking: Earned value analysis and performance metrics
- Zero Dependencies: Uses only Python standard library
- Well Tested: 280+ tests covering every table, query method, DCMA check, and edge case
📦 Installation
Install from PyPI using pip:
pip install PyP6XER
Or install from source:
git clone https://github.com/HassanEmam/PyP6Xer.git
cd PyP6Xer
pip install -e .
🏃♂️ Quick Start
Basic Usage
from xerparser.reader import Reader
# Load an XER file
xer = Reader("project.xer")
# Access projects
for project in xer.projects:
print(f"Project: {project.proj_short_name}")
print(f"Activities: {len(project.activities)}")
# Access activities in the project
for activity in project.activities:
print(f" {activity.task_code}: {activity.task_name}")
print(f" Duration: {activity.duration} days")
print(f" Status: {activity.status_code}")
Resource Analysis
# Analyze resource utilization
for resource in xer.resources:
assignments = [a for a in xer.activityresources if a.rsrc_id == resource.rsrc_id]
total_hours = sum(a.target_qty or 0 for a in assignments)
print(f"Resource: {resource.rsrc_name}")
print(f"Total Assigned Hours: {total_hours}")
print(f"Assignments: {len(assignments)}")
Critical Path Analysis
# Find critical activities
critical_activities = []
for activity in xer.activities:
if activity.total_float_hr_cnt is not None and activity.total_float_hr_cnt <= 0:
critical_activities.append(activity)
print(f"Critical Path: {len(critical_activities)} activities")
for activity in critical_activities:
print(f" {activity.task_code}: {activity.task_name}")
Schedule Quality Check
# Check for schedule quality issues
issues = []
# Activities without predecessors (except start milestones)
no_predecessors = [a for a in xer.activities
if not a.predecessors and a.task_type != "TT_Mile"]
if len(no_predecessors) > 1:
issues.append(f"Multiple start activities: {len(no_predecessors)}")
# Long duration activities
long_activities = [a for a in xer.activities
if a.duration and a.duration > 20]
if long_activities:
issues.append(f"Long duration activities (>20 days): {len(long_activities)}")
# Report issues
if issues:
print("Schedule Quality Issues:")
for issue in issues:
print(f" • {issue}")
Data Export
# Modify data and export
for activity in xer.activities:
if activity.status_code == "TK_Active":
# Update progress for active activities
activity.phys_complete_pct = 25.0
# Write to new XER file (preserves the source ERMHDR)
from xerparser.write import writeXER
writeXER(xer, "updated_project.xer") # or: xer.write("updated_project.xer")
Any-Table Access
Tables without a dedicated model class are still fully available — parse, query and write them back losslessly:
# Generic access to any XER table
steps = xer.tables["WBSSTEP"] # e.g. WBS steps
record = steps.records[0]
print(record.step_name)
# All 60+ tables from the file are present, keyed by their %T name
print(sorted(xer.tables.keys()))
🔗 Editing Activity Relationships
Relationships (the TASKPRED table) can be added, removed and re-routed in Python, then written back to a valid XER — no Excel round-trip needed (see issue #7):
from xerparser.reader import Reader
from xerparser.write import writeXER
xer = Reader("schedule.xer")
# Add a finish-to-start relationship (tasks validated, IDs auto-allocated)
rel = xer.relations.add_relationship(
task_id=1003, pred_task_id=1002,
pred_type="PR_FS", # PR_FS / PR_SS / PR_FF / PR_SF
lag_hr_cnt=8.0, # hours; negative = lead
comments="Resequence phase 2",
)
# Cross-project link: set the predecessor's project explicitly
xer.relations.add_relationship(task_id=1003, pred_task_id=5000, pred_proj_id=2)
# Relationships are mutable — edit in place
for rel in xer.activities.get_predecessors(1002):
rel.lag_hr_cnt = 4.0
# Re-route (delete + create in one step; validation failures change nothing)
xer.relations.replace_relationship(rel, pred_task_id=1001, pred_type="PR_SS")
# Remove
xer.relations.remove_relationship(rel) # by object
xer.relations.remove_relationship(12) # or by task_pred_id
writeXER(xer, "rescheduled.xer")
Convenience queries on xer.activities (Tasks):
xer.activities.get_predecessors(task_id) # relationships feeding this activity
xer.activities.get_successors(task_id) # relationships leaving this activity
Note: on the xer.relations collection itself, get_successors(act_id)
returns relationships in which act_id is the predecessor (i.e. the
successors of act_id) — the Tasks passthroughs above are the
unambiguous way to query.
📊 DCMA 14-Point Schedule Analysis
Run the full DCMA 14-point schedule quality check on a loaded programme:
from xerparser.dcma14.analysis import DCMA14
xer = Reader("schedule.xer")
results = DCMA14(xer).analysis()["analysis"]
# Every check reports a count (and, where applicable, a percentage)
print(f"Open ends (no predecessors): {results['predecessors']['cnt']}")
print(f"Open ends (no successors): {results['successors']['cnt']}")
print(f"Lags > 0: {results['lags']['cnt']}")
print(f"Leads (negative lags): {results['leads']['cnt']}")
print(f"Hard constraints: {results['constraints']['cstr_cnt']}")
print(f"Large total float: {results['totalfloat']['cnt']}")
print(f"Negative float: {results['negativefloat']['cnt']}")
print(f"Invalid dates: {results['invaliddates']['cnt']}")
print(f"Missing resource assignments:{results['resources']['cnt']}")
print(f"Critical path length test: {results['critical_test']['passed']}")
print(f"CPLI: {results['cpli']['cpli']}")
print(f"BEI: {results['bei']['bei']}")
The analysis handles multi-project programmes (per-project data dates from
last_recalc_date), calendar-aware hour/day conversions, and counts
activities rather than relations where the DCMA standard requires it.
📖 Documentation
Comprehensive documentation is available with detailed API references and examples:
- Getting Started Guide - Basic concepts and first steps
- API Reference - Complete API documentation
- Examples - Practical usage examples
- Advanced Examples - Complex analysis scenarios
- Troubleshooting - Common issues and solutions
🎯 Use Cases
Project Management
- Schedule analysis and optimization
- Critical path identification
- Resource leveling and optimization
- Progress tracking and reporting
- Earned value analysis
Data Integration
- Import P6 data into other systems
- Data migration between project management tools
- Custom reporting and dashboards
- Data validation and quality checks
Analysis and Reporting
- Portfolio analysis across multiple projects
- Resource utilization reports
- Schedule compliance analysis
- Cost variance analysis
- Performance metrics calculation
🔧 Advanced Features
Schedule Analysis Tools
from xerparser.reader import Reader
def analyze_project_performance(xer_file):
xer = Reader(xer_file)
for project in xer.projects:
activities = project.activities
# Calculate performance metrics
total_activities = len(activities)
completed = len([a for a in activities if a.status_code == "TK_Complete"])
in_progress = len([a for a in activities if a.status_code == "TK_Active"])
completion_rate = (completed / total_activities) * 100
print(f"Project: {project.proj_short_name}")
print(f"Completion: {completion_rate:.1f}%")
print(f"Active: {in_progress} activities")
Resource Optimization
def find_overallocated_resources(xer):
"""Find resources that are over-allocated."""
overallocated = []
for resource in xer.resources:
if resource.rsrc_type != "RT_Labor":
continue
assignments = [a for a in xer.activityresources if a.rsrc_id == resource.rsrc_id]
# Simple over-allocation check (assumes 40 hours/week)
total_hours = sum(a.target_qty or 0 for a in assignments)
max_available = 2080 # 40 hours * 52 weeks
if total_hours > max_available:
overallocated.append({
'resource': resource,
'allocated_hours': total_hours,
'excess_hours': total_hours - max_available
})
return overallocated
Earned Value Management
def calculate_earned_value_metrics(project, xer):
"""Calculate EVM metrics for a project."""
planned_value = 0
earned_value = 0
actual_cost = 0
for activity in project.activities:
assignments = [a for a in xer.activityresources if a.task_id == activity.task_id]
activity_planned = sum(a.target_cost or 0 for a in assignments)
activity_actual = sum((a.act_reg_cost or 0) + (a.act_ot_cost or 0) for a in assignments)
if activity.phys_complete_pct:
activity_earned = activity_planned * (activity.phys_complete_pct / 100)
else:
activity_earned = 0
planned_value += activity_planned
earned_value += activity_earned
actual_cost += activity_actual
# Calculate performance indices
cpi = earned_value / actual_cost if actual_cost > 0 else 0
spi = earned_value / planned_value if planned_value > 0 else 0
return {
'planned_value': planned_value,
'earned_value': earned_value,
'actual_cost': actual_cost,
'cpi': cpi,
'spi': spi
}
🌟 Supported XER Elements
PyP6Xer supports parsing and manipulation of all major XER file elements — 39 model-backed collections plus lossless generic access to every other table in the file (60+ tables in a typical export, including RSRCCURV and RSRCCURVDATA for resource curve modelling):
Core Project Elements
- Projects - Project information and settings
- Activities (Tasks) - Project activities with full scheduling data
- WBS - Work Breakdown Structure hierarchy
- Relationships - Activity dependencies and logic links
Resources and Assignments
- Resources - Labor, material, and equipment resources
- Resource Assignments - Activity-resource assignments with costs and quantities
- Resource Rates - Cost and pricing information
- Resource Categories - Resource classification and organization
Scheduling and Time
- Calendars - Working time definitions with exceptions
- Schedule Options - Project scheduling parameters and settings
Classification and Organization
- Activity Codes - Custom activity classification systems
- Activity Types - Activity type definitions
- OBS - Organizational Breakdown Structure
- Roles - Resource role definitions
Financial
- Cost Accounts - Project cost account structure
- Currencies - Multi-currency support
- Financial Templates - Cost management templates
🔄 Data Model
PyP6Xer uses an intuitive object model that mirrors P6's data structure:
Reader
├── projects (Projects collection)
│ └── Project
│ ├── activities (filtered by project)
│ └── wbss (filtered by project)
├── activities (Tasks collection)
│ └── Task
│ ├── resources (resource assignments)
│ ├── predecessors (predecessor relationships)
│ ├── successors (successor relationships)
│ └── activitycodes (activity code assignments)
├── resources (Resources collection)
│ └── Resource
├── relations (Predecessors collection)
│ └── Predecessor
├── calendars (Calendars collection)
│ └── Calendar
├── accounts, roles, expenses, activityresources, ... (one collection per table)
└── tables (dict of every XER table, modelled or not)
🤝 Contributing
We welcome contributions! Please see our Contributing Guide for details on:
- Setting up the development environment
- Running tests
- Submitting pull requests
- Code style guidelines
Running the Test Suite
# full suite (280+ tests: every table, query, DCMA check, edge case)
python -m pytest tests/ -q
# a single area
python -m pytest tests/test_all_model_tables.py -q # table round-trips
python -m pytest tests/test_dcma14_checks.py -q # DCMA 14-point
The suite builds its own XER fixtures from scratch (see
tests/table_fixtures.py), so it needs no sample data files.
📄 License
PyP6Xer is licensed under the GNU Lesser General Public License v2.1. See the LICENSE file for details.
🆘 Support
- Documentation: Full documentation
- Issues: GitHub Issues
- Discussions: GitHub Discussions
🎉 Acknowledgments
PyP6Xer is developed and maintained by Hassan Emam and the open-source community. Special thanks to all contributors who help improve the library.
Keywords: Primavera P6, XER Parser, Project Management, Schedule Analysis, Python, Construction, Engineering, Resource Management
Metadata
Release files for PyP6XER 2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pyp6xer-2.0.tar.gz | 130.6 kB | Without provenance | Details for pyp6xer-2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pyp6xer-2.0-py3-none-any.whl | Python 3 | none | any | Details for pyp6xer-2.0-py3-none-any.whl |
Total release size: 244.5 kB
Release files / pyp6xer-2.0.tar.gz
| Download URL | pyp6xer-2.0.tar.gz |
|---|---|
| Size | 130.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c4526f00d1b0b53b24f182dbadf5f4ef3b9d7789849541413317d82d2d9abd2b
|
|
BLAKE2b-256 checksum How to use checksums |
d81418de15e5f1f5bc0acce67c83362e1ac4b40535d17fb5c137eacc1c7383e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.25
|
Release files / pyp6xer-2.0-py3-none-any.whl
| Download URL | pyp6xer-2.0-py3-none-any.whl |
|---|---|
| Size | 113.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8f877352004e8095b4a6703f95e8348722a15d9bd344b76ce1a962f85c8a711d
|
|
BLAKE2b-256 checksum How to use checksums |
c3891cc5b1c69039cdc85eb1f9393f5f1adec95b5d1dd37e85d918ac1670e4d2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.25
|
Security for PyP6XER 2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.