Contributing#

We welcome contributions to PVDeg! Whether you’re fixing bugs, adding features, improving documentation, or contributing to our material property databases, your help is valuable to the PV community.

For a quick overview, see CONTRIBUTING.md on GitHub.

This guide provides comprehensive details for contributors.

Easy Ways to Contribute#

Here are ways to contribute, even if you’re new to PVDeg, git, or Python:

  • Report bugs or request features via GitHub issues

  • Join discussions on existing issues and pull requests

  • Improve documentation - fix typos, clarify explanations, add examples

  • Enhance unit tests - increase coverage or improve test quality

  • Create or improve tutorials - demonstrate PVDeg in your area of expertise

  • Contribute to material databases - add validated degradation parameters and properties

  • Share your work - add your project to our wiki

  • Spread the word - tell colleagues about PVDeg

Getting Started#

Development Environment Setup#

  1. Fork and clone the repository:

    git clone https://github.com/YOUR-USERNAME/PVDegradationTools.git
    cd PVDegradationTools
    
  2. Create a virtual environment:

    Using venv:

    python -m venv pvdeg-dev
    source pvdeg-dev/bin/activate  # Windows: pvdeg-dev\\Scripts\\activate
    

    Or using conda:

    conda create -n pvdeg-dev python=3.11
    conda activate pvdeg-dev
    
  3. Install in editable mode with all dependencies:

    pip install -e .[all]
    
  4. Install pre-commit hooks:

    pre-commit install
    

    Pre-commit hooks automatically enforce code quality standards before each commit.

  5. (Optional but required for HPC) Register Jupyter kernel:

    python -m ipykernel install --user --name=pvdeg-dev
    

    This is especially important when working on HPC systems like NREL’s Kestrel.

Pre-commit Hooks#

We use pre-commit hooks to maintain code quality and consistency.

What the Hooks Do#

Our .pre-commit-config.yaml configuration runs:

  • black - Python code formatter (line length: 88 characters)

  • flake8 - Python linter for style guide enforcement

  • jupytext - Syncs Jupyter notebooks with Python scripts for version control

  • trailing-whitespace - Removes trailing whitespace

  • end-of-file-fixer - Ensures files end with newline

  • check-yaml - Validates YAML syntax

  • check-added-large-files - Prevents committing large files

Running Pre-commit Manually#

Run all hooks on all files:

pre-commit run --all-files

Run hooks on specific files:

pre-commit run --files path/to/file.py

Skip hooks for a specific commit (use sparingly):

git commit --no-verify -m "commit message"

Jupytext Synchronization#

Notebooks in tutorials/ are automatically synced with Python scripts in scripts/ folders.

When you edit a notebook:

  1. Pre-commit hook creates/updates a .py script version

  2. Both files are staged for commit

  3. Version control works well with notebooks (diffs show code changes, not JSON)

Manual sync:

jupytext --sync tutorials/**/*.ipynb

Code Contributions#

Code Style#

We follow standard Python conventions:

  • PEP 8 style guide with 88 character line length (black default)

  • Google-style docstrings for functions and classes (numpydoc format)

  • Type hints encouraged for function signatures

  • Descriptive variable names (avoid single letters except in loops)

  • Code must be compatible with Python 3.10 and above

  • Remove print statements and logging calls before committing (warning is acceptable)

  • Set your editor to strip trailing whitespace

Variable naming: PVDeg uses a mix of full and abbreviated names. Prefer full names for new contributions, especially in the API. Abbreviations can be used within functions to improve readability of formulae.

Example docstring:

def calculate_degradation(temp, humidity, irradiance):
    """
    Calculate degradation rate based on environmental conditions.

    Parameters
    ----------
    temp : float or array-like
        Temperature in degrees Celsius.
    humidity : float or array-like
        Relative humidity as percentage (0-100).
    irradiance : float or array-like
        Irradiance in W/m².

    Returns
    -------
    float or array-like
        Degradation rate in %/year.

    Notes
    -----
    Based on the Arrhenius equation with humidity acceleration factor.
    See [1]_ for detailed methodology.

    References
    ----------
    .. [1] Author et al. (2020). "Title." Journal. DOI.
    """
    pass

Generic type descriptors used in PVDeg documentation:

  • dict-like: dict, OrderedDict, pd.Series

  • numeric: scalar, np.array, pd.Series (typically int or float dtype)

  • array-like: np.array, pd.Series (typically int or float dtype)

Parameters that specify a specific type require that exact input type.

Testing#

All code contributions should include unit tests.

Running the test suite:

pytest tests/

Run tests with coverage report:

pytest --cov=pvdeg --cov-report=html tests/

Test specific modules:

pytest tests/test_spectral.py

Test notebooks (requires nbval):

pytest --nbval tutorials/

Writing tests:

  • Place tests in tests/ directory

  • Name test files test_<module>.py

  • Use descriptive test function names: test_<function>_<scenario>

  • Include edge cases and error conditions

  • Use pytest fixtures for shared test data

  • Mock external dependencies (weather APIs, file downloads)

  • New test files must be included in tests/__init__.py

Debugging tests: Use pytest’s --pdb flag to drop into the debugger at test failures:

pytest tests/test_spectral.py --pdb

This is preferred over adding print or logging statements.

Example test:

import pytest
import numpy as np
from pvdeg import spectral

def test_spectral_degradation_basic():
    """Test spectral degradation with simple inputs."""
    wavelengths = np.array([300, 400, 500, 600, 700])
    spectrum = np.array([0.1, 0.3, 0.5, 0.4, 0.2])

    result = spectral.calc_degradation(wavelengths, spectrum)

    assert isinstance(result, float)
    assert result > 0
    assert result < 100  # Reasonable degradation range

def test_spectral_degradation_with_nan():
    """Test spectral degradation handles NaN values."""
    wavelengths = np.array([300, 400, np.nan, 600, 700])
    spectrum = np.array([0.1, 0.3, 0.5, 0.4, 0.2])

    with pytest.raises(ValueError, match="NaN values"):
        spectral.calc_degradation(wavelengths, spectrum)

Documentation#

Building documentation locally:

  1. Install documentation dependencies:

    pip install -e .[docs]
    
  2. Build the docs:

    cd docs
    make html
    
  3. View in browser:

    # Open docs/build/html/index.html
    

Verifying documentation: Read the Docs automatically builds documentation for each pull request. Confirm it renders correctly by following the continuous-documentation/read-the-docs link in the PR checks.

Documentation types:

  • API documentation: Automatically generated from docstrings

  • User guide: RST files in docs/source/user_guide/

  • Tutorials: Jupyter notebooks in tutorials/

  • What’s new: Release notes in docs/source/whatsnew/

Tutorial guidelines:

  • Place tutorials in appropriate tutorials/<category>/ folder

  • Use descriptive names: 01_intro_to_topic.ipynb

  • Include markdown cells explaining concepts

  • Use local data files when possible (avoid API dependencies for reproducibility)

  • Keep execution time under 5 minutes

  • Commit notebooks with their executed outputs (run them with jupyter nbconvert --to notebook --execute --inplace); do not clear outputs, as nbval validates them in CI

Notebook Validation#

When contributing notebook changes, it’s important to validate that notebooks execute correctly and produce consistent outputs across different environments.

Why validate notebooks?

  • Ensures reproducibility across different systems and environments

  • Catches environment-specific bugs before they reach users

  • Maintains consistent output formatting in version control

  • Verifies that all cells execute in the correct order

  • Confirms that notebooks work with current dependencies

Using nbconvert for validation

The jupyter nbconvert command executes notebooks in a clean kernel and validates outputs. This is the recommended approach for testing notebook changes.

Execute a single notebook:

jupyter nbconvert --to notebook --execute --inplace "tutorials/01_basics/01_basics_humidity_design.ipynb"

Execute all notebooks in a category:

jupyter nbconvert --to notebook --execute --inplace "tutorials/01_basics/*.ipynb"

Execute all tutorial notebooks (use with caution - may take a long time):

jupyter nbconvert --to notebook --execute --inplace "tutorials/**/*.ipynb"

Understanding the flags:

  • --to notebook: Converts back to notebook format (not HTML or other formats)

  • --execute: Executes all cells in order with a fresh kernel

  • --inplace: Overwrites the original notebook with execution results

Using pytest with nbval

For automated testing, use pytest with the nbval plugin to validate notebooks:

# Test all notebooks
pytest --nbval tutorials/

# Test specific notebook
pytest --nbval tutorials/01_basics/01_basics_humidity_design.ipynb

# Skip output comparison (only check execution)
pytest --nbval --nbval-lax tutorials/

The --nbval-lax flag is useful when outputs may vary slightly (e.g., timestamps, random numbers) but you still want to verify the notebook executes without errors.

Best practices for notebook contributions:

  1. Commit notebooks with their outputs: Execute the notebook top-to-bottom with a fresh kernel before committing, so stored outputs and execution counts stay current and consistent:

    jupyter nbconvert --to notebook --execute --inplace notebook.ipynb
    

    Replace notebook.ipynb with the path to your notebook (e.g., tutorials/04_scenario/01_scenario_temperature.ipynb).

  2. Test locally first: Always run notebooks locally before pushing to verify they work

  3. Use relative paths: Ensure data paths work from the repository root

  4. Document dependencies: If a notebook requires optional dependencies (e.g., PySAM), note this in a markdown cell at the top

  5. Handle API keys gracefully: Use commented-out API sections with local data alternatives, following the pattern in existing notebooks

  6. Keep execution time reasonable: Aim for under 5 minutes per notebook. For longer analyses, consider pre-computing results or creating separate demo notebooks

  7. Mark non-reproducible cells: For a cell whose output cannot be reproduced exactly (live API calls, Plotly/Matplotlib figures, timestamps, random draws), add a # NBVAL_IGNORE_OUTPUT comment on its own line inside the cell; nbval then executes the cell but skips comparing its output. Always re-run the notebook after editing such a cell — a cell with modified source but stale stored outputs (execution count null with outputs present) makes nbval fail with Unrun reference cell has outputs.

Troubleshooting notebook validation failures:

  • Import errors: Verify all dependencies are installed in your environment

  • Path errors: Check that data files exist at expected locations

  • Kernel errors: Ensure your Jupyter kernel is properly registered (see installation guide)

  • Timeout errors: Increase timeout with --ExecutePreprocessor.timeout=600 (seconds)

  • API failures: For notebooks with API calls, use local data or mock responses in tests

Database Contributions#

One of the most valuable contributions you can make is adding validated material property data and degradation parameters to our databases.

What to Contribute#

We maintain several JSON databases in pvdeg/data/:

  • DegradationDatabase.json: Degradation rates, activation energies, temperature coefficients

  • H2Opermeation.json: Water vapor transmission rates for encapsulants and barriers

  • O2permeation.json: Oxygen permeation rates

  • AApermeation.json: Acetic acid permeation data

  • albedo.json: Ground albedo values for different surfaces

How to Contribute#

  1. Locate peer-reviewed data: Published journal articles, conference proceedings, or technical reports

  2. Prepare structured data: Follow the JSON format in existing entries

  3. Document metadata: Include measurement conditions, equipment, uncertainties

  4. Submit via pull request: Add your data to the appropriate JSON file with citation

Data Quality Guidelines#

  • Peer-reviewed sources: Prefer published research over manufacturer specs

  • Complete metadata: Temperature, humidity, test conditions, sample details

  • Uncertainty quantification: Include error bars or confidence intervals when available

  • Clear units: Explicitly state all units

  • Proper citations: Full reference with DOI when possible

Example JSON Entry#

{
    "material": "EVA",
    "property": "activation_energy",
    "value": 0.87,
    "units": "eV",
    "conditions": {
        "temperature_range": "60-85 C",
        "test_method": "Arrhenius analysis",
        "sample_thickness": "0.46 mm"
    },
    "uncertainty": 0.05,
    "reference": {
        "authors": "Smith et al.",
        "title": "Degradation study of EVA encapsulants",
        "journal": "Solar Energy Materials",
        "year": 2020,
        "doi": "10.1016/j.solmat.2020.xxxxx"
    }
}

Submitting Changes#

Pull Request Process#

  1. Create a feature branch:

    git checkout -b feature/descriptive-name
    

    Use prefixes: feature/, bugfix/, docs/, refactor/

  2. Make changes and commit:

    git add .
    git commit -m "Add descriptive commit message"
    

    Write clear commit messages explaining why the change was made.

  3. Push to your fork:

    git push origin feature/descriptive-name
    
  4. Open a pull request on GitHub:

    • Provide a clear description of changes

    • Reference related issues (e.g., “Closes #123”)

    • Ensure all CI checks pass (tests, pre-commit hooks)

    • Request review from maintainers or tag @NREL/pvdeg-maintainers

Best practices:

  • Keep pull requests focused and manageable (preferably <50 lines of primary code)

  • Consider breaking large changes into smaller, incremental pull requests

  • Submit pull requests early if you want feedback (mark as draft if incomplete)

  • Be patient - reviewers bring diverse expertise and perspectives

  • GitHub will automatically “squash and merge” your commits into a single commit

Pull Request Checklist#

Before submitting your pull request, verify:

  • [ ] Code follows PEP 8 style (black formatting applied)

  • [ ] All tests pass locally (pytest tests/)

  • [ ] New functionality includes unit tests

  • [ ] Docstrings added/updated for new/modified functions

  • [ ] Type hints included for function signatures

  • [ ] Pre-commit hooks pass (pre-commit run --all-files)

  • [ ] Documentation updated if adding features

  • [ ] What’s new entry added if significant change

  • [ ] Notebooks re-run with outputs committed (not cleared) and synced with .py scripts

  • [ ] Database entries include proper citations and metadata

Code Review Process#

  • Maintainers will review your pull request within a few days

  • Discussion and iteration are normal - don’t be discouraged

  • Address feedback by pushing new commits to your branch

  • Ping one of the pvdeg maintainers if your pull request seems forgotten

  • Once approved, maintainers will merge your changes

  • Your contribution will be included in the next release

Understanding reviews: The PV modeling community is diverse. Reviewers may focus on:

  • Algorithm details and scientific accuracy

  • Integration with existing PVDeg code

  • Code maintainability and style

  • Test coverage and documentation quality

Contributor License Agreement#

First-time contributors must sign the Contributor License Agreement (CLA). This protects both you and the project.

When you submit your first pull request, a bot will comment with instructions to sign the CLA. Simply follow the provided link.

Release Process#

For maintainers and contributors interested in the release workflow:

Versioning: We follow semantic versioning (MAJOR.MINOR.PATCH)

  • MAJOR: Incompatible API changes

  • MINOR: New features (backward compatible)

  • PATCH: Bug fixes (backward compatible)

Testing releases:

  • Pre-release tags (v0.7.0-rc1, v0.7.0-alpha1) → TestPyPI only

  • Final tags (v0.7.0) → TestPyPI and PyPI

What’s new: Update docs/source/whatsnew/releases/vX.Y.Z.rst with:

  • Enhancements

  • Breaking changes

  • Deprecations

  • Bug fixes

  • Dependency updates

  • Issue references (#123)

Release checklist: the order matters, because pushing a tag immediately publishes to PyPI and a released version number can never be reused.

  1. On development, finalise docs/source/whatsnew/releases/vX.Y.Z.rst: set the real release date in the heading, and confirm every entry actually ships in this release rather than sitting on a future-work branch.

  2. Bump the citation metadata before tagging, so the tag carries correct information:

    • CITATION.cff – version and date-released.

    • .zenodo.json – only if the author list or affiliations changed.

    The update-citation workflow skips its commit when CITATION.cff already matches the tag, so doing this by hand here leaves nothing for it to do. Skipping this step is not fatal, but see step 5.

  3. Open the release pull request (development into main), get an approving review, and let CI finish.

  4. Tag development and push the tag:

    git tag -a vX.Y.Z -m "<short release description>"
    git push origin vX.Y.Z
    

    Tag development, not the merge commit on main: update-citation pushes to development explicitly, so tagging elsewhere sends a commit to the wrong branch. Pushing the tag triggers the PyPI publish, which is irreversible.

  5. If CITATION.cff was not bumped in step 2, update-citation now pushes a commit to development. Wait for it before merging, or main ships a citation file naming the previous version.

  6. Merge the release pull request into main.

  7. Publish a GitHub Release for the tag. Nothing automates this, and Zenodo mints the DOI from the Release rather than from the tag, so the DOI referenced in the docs will not appear until this is done. The vX.Y.Z.rst changelog entry works as the release notes.

Getting Help#

If you have questions or need help:

Community Guidelines#

  • Be respectful and professional

  • Assume positive intent in all interactions

  • Focus on constructive feedback

  • Welcome newcomers and help them learn

  • Give credit where it’s due

Thank you for contributing to PVDeg and supporting the PV research community! 🌞