Documentation Guide#
OpenSTEF welcomes improvements to documentation! Whether you’re fixing typos, clarifying explanations, or adding tutorials, your contributions help make forecasting more accessible to everyone.
Getting started#
Documentation structure#
OpenSTEF documentation is organized following the Diátaxis framework:
Tutorials (
examples/tutorials/): Learning-oriented guides for beginnersHow-to guides (
user_guide/): Problem-oriented practical guidesReference (
api/): Information-oriented technical referenceExplanation (
user_guide/intro/): Understanding-oriented background material
The documentation lives in several places:
docs/source/ # Main documentation source
├── api/ # API reference (auto-generated)
├── user_guide/ # User guides and tutorials
├── project/ # Project information
├── contribute/ # Contributing guides
└── examples/ # Example gallery
examples/ # Example scripts and tutorials
├── examples/ # Gallery examples
└── tutorials/ # Tutorials
packages/*/src/ # Inline code documentation (docstrings)
Building the documentation#
To build the documentation locally:
# Build the documentation
poe docs
# Build and serve with live reload (recommended for editing)
poe docs --serve
# Clean previous builds
poe docs-clean
The built documentation will be available at docs/build/html/index.html.
Note
Building documentation requires additional dependencies that are included in the
development environment. Make sure you’ve run uv sync --group dev first.
How the documentation pipeline works#
A poe docs invocation chains several stages. Knowing where each one lives
helps when something goes wrong or when you want to extend the build.
Tutorial and benchmark sync#
The examples/tutorials/ and examples/benchmarks/ directories are the
canonical home for runnable notebooks. Before Sphinx starts,
docs.sync_sources copies them into docs/source/tutorials/ and
docs/source/benchmarks/ so Sphinx can pick them up. The script also embeds
a few tutorials into the user-guide nav (for example,
examples/tutorials/feature_engineering.py is also synced to
docs/source/user_guide/guides/feature_engineering_tutorial.py). The sync
runs automatically during every build via conf.py’s setup() hook.
Notebook rendering with myst-nb#
Tutorial .py files are paired with .ipynb companions via
jupytext. During the build,
myst-nb executes the notebooks and
embeds the outputs into the rendered pages. Execution results are cached
under docs/build/.jupyter_cache, so unchanged notebooks are not re-run
on subsequent builds. Benchmarks under benchmarks/*/* are explicitly
excluded from execution (they are too expensive to run on every build) and
appear with the outputs that were saved when the notebook was last run by
hand.
Autosummary stub caching#
API reference pages are generated from package source code by
sphinx.ext.autosummary. Running it across hundreds of modules is slow,
so the custom docs.autosummary_cache extension tracks .py source
mtimes against a stamp file (docs/build/.autosummary_stamp) and skips
stub generation when nothing has changed. Delete the stamp file or any
packages/*/src source file to force a refresh.
Concept and guide figures#
Static figures used by concept and guide pages live in
docs/source/_figures/. These are stand-alone scripts
(generate_concept_figures.py, generate_guide_figures.py) that train
real models on the Liander dataset and write SVG/GIF outputs into
docs/source/images/. They are not run by the normal docs build;
the _figures/ directory is excluded via exclude_patterns in
conf.py. Regenerate by hand when you change the figures:
uv run python docs/source/_figures/generate_concept_figures.py
uv run python docs/source/_figures/generate_guide_figures.py
Each generated SVG/GIF needs a sibling .license sidecar for REUSE
compliance; copy from an existing file when adding a new figure.
CI deployment#
The Deploy Documentation workflow in
.github/workflows/docs.yaml builds the docs on every push to
main and publishes them to the root of the gh-pages branch.
CI uses the same poe docs invocation as local development, so a
build that is clean locally should also be clean in CI.
Writing docstrings#
OpenSTEF uses Google-style docstrings for all code documentation. This style is clear, readable, and well-supported by Sphinx.
Basic docstring structure#
def forecast_energy(data: pd.DataFrame, horizon: int = 24) -> pd.DataFrame:
"""Generate energy forecasts for the specified horizon.
This function creates forecasts using the configured model and feature
engineering pipeline. It handles missing data and provides uncertainty
estimates for each prediction.
Args:
data: Historical energy consumption data with datetime index.
Must include columns: ['load', 'temperature', 'wind_speed'].
horizon: Number of hours to forecast ahead. Must be positive.
Returns:
DataFrame with forecasted values and uncertainty bounds:
- 'forecast': Point predictions
- 'forecast_lower': Lower confidence bound (5th percentile)
- 'forecast_upper': Upper confidence bound (95th percentile)
Raises:
ValueError: If data is empty or missing required columns.
TypeError: If horizon is not a positive integer.
Example:
Basic usage with sample data:
>>> import pandas as pd
>>> data = pd.DataFrame({
... 'load': [100, 120, 110],
... 'temperature': [20, 22, 21],
... 'wind_speed': [5, 7, 6]
... }, index=pd.date_range('2025-01-01', periods=3, freq='h'))
>>> forecast = forecast_energy(data, horizon=6)
>>> forecast.shape
(6, 3)
Note:
The model automatically handles daylight saving time transitions and
public holidays for improved accuracy.
See Also:
evaluate_forecast: Evaluate forecast accuracy against ground truth.
prepare_features: Prepare input data for forecasting.
"""
Docstring sections#
Use these sections in your docstrings (order matters):
Summary line: One-line description of what the function does
Extended description: More detailed explanation (optional)
Args: Function parameters and their types/descriptions
Returns: Description of return value(s)
Raises: Exceptions that may be raised
Example: Code examples showing how to use the function
Note: Additional important information
Invariants: Contract guarantees and requirements (for classes and interfaces)
See Also: References to related functions/classes
Type hints and docstrings#
Always use type hints in function signatures. The docstring should complement, not repeat, the type information:
# Good: Type hints in signature, description in docstring
def train_model(data: TimeSeriesDataset, config: ModelConfig) -> ForecastModel:
"""Train a forecasting model on the provided dataset.
Args:
data: Training dataset with features and targets.
config: Model configuration including hyperparameters.
Returns:
Trained model ready for forecasting.
"""
# Avoid: Repeating type information in docstring
def train_model(data: TimeSeriesDataset, config: ModelConfig) -> ForecastModel:
"""Train a forecasting model on the provided dataset.
Args:
data (TimeSeriesDataset): Training dataset with features and targets.
config (ModelConfig): Model configuration including hyperparameters.
Returns:
ForecastModel: Trained model ready for forecasting.
"""
Invariants section#
For classes and interfaces, use an Invariants section to document the contract
guarantees and requirements that implementers and users must follow:
class ForecastModel(ABC):
"""Base class for all forecasting models.
Provides a standardized interface for training and prediction across
different forecasting algorithms and approaches.
Invariants:
- fit() must be called before predict() for stateful models
- predict() should handle all horizons specified in configuration
- Output format must be consistent with ForecastDataset structure
- Model state must remain unchanged during prediction calls
Example:
Basic model implementation:
>>> class SimpleModel(ForecastModel):
... def fit(self, data):
... self._fitted = True
... def predict(self, data):
... return generate_forecasts(data)
"""
The Invariants section should document:
Pre-conditions: What must be true before calling methods
Post-conditions: What the implementation guarantees after execution
State requirements: How object state should be managed
Interface contracts: Consistent behavior expectations across implementations
This helps both implementers understand what they must provide and users understand what they can rely on.
Note
The Invariants section is an OpenSTEF-specific extension to Google-style
docstrings. Use it for classes and interfaces where contract guarantees are
important for correct implementation and usage.
Examples in docstrings#
Include practical examples in your docstrings using the Example section.
These examples are automatically tested with poe doctests.
Writing good examples#
def calculate_mae(y_true: np.ndarray, y_pred: np.ndarray) -> float:
"""Calculate Mean Absolute Error between predictions and ground truth.
Example:
Basic usage:
>>> import numpy as np
>>> y_true = np.array([1, 2, 3, 4, 5])
>>> y_pred = np.array([1.1, 2.2, 2.9, 3.8, 5.2])
>>> mae = calculate_mae(y_true, y_pred)
>>> round(mae, 2)
0.16
With perfect predictions:
>>> perfect_pred = np.array([1, 2, 3, 4, 5])
>>> calculate_mae(y_true, perfect_pred)
0.0
"""
Example guidelines#
Keep examples simple but realistic
Use ``>>>`` prompts for interactive examples
Show expected output when it’s not obvious
Test edge cases (empty inputs, perfect predictions, etc.)
Use ``round()`` for floating-point outputs to avoid precision issues
Import required modules within the example if needed
Writing narrative documentation#
For user guides, tutorials, and explanatory content, use reStructuredText (.rst) files.
reStructuredText basics#
Section headers
===============
Subsection headers
------------------
*Italic text* and **bold text**
``Code snippets`` and :func:`function references`
.. code-block:: python
# Code blocks with syntax highlighting
import openstef
model = openstef.create_model()
.. note::
Informational notes for readers.
.. warning::
Important warnings about potential issues.
Cross-references#
Link to other parts of the documentation:
# Link to other documents
See the :doc:`user_guide/installation` guide.
# Link to specific functions/classes
Use :func:`openstef.models.forecast` for predictions.
# Link to sections within documents
Refer to :ref:`development_setup` for setup instructions.
Contributing to examples and tutorials#
Examples and tutorials are crucial for user onboarding. When adding new examples:
Choose the right location:
examples/examples/- Short, focused examplesexamples/tutorials/- Multi-step tutorials
Follow naming conventions:
Use descriptive filenames:
basic_forecasting.py,advanced_transforms.pyStart with a docstring explaining the example’s purpose
Structure your example:
""" Basic Energy Forecasting ======================== This example demonstrates how to create simple energy forecasts using OpenSTEF. We'll load sample data, train a model, and generate predictions. """ import pandas as pd import openstef # Load sample data data = openstef.load_sample_data() # ... rest of example
Include explanations: Use comments and markdown cells to explain each step
Test your examples: Run
poe docteststo ensure all examples work
Documentation style guide#
Writing style#
Be clear and concise - Avoid jargon, explain technical terms
Use active voice - “The model predicts” rather than “Predictions are made”
Write for your audience - Tutorials for beginners, reference for experts
Include context - Explain why something is useful, not just how to do it
Code style in documentation#
Use realistic examples - Avoid
foo,bar; use domain-relevant namesShow complete examples - Include imports and setup code
Highlight important parts - Use comments to draw attention to key concepts
Test all code - Ensure examples actually work
All code examples in documentation should follow our Style Guide.
Building and testing documentation#
Before submitting documentation changes:
# Check that documentation builds without errors
poe docs
# Test all code examples in docstrings
poe doctests
# Run full quality checks (includes documentation)
poe all --check
Common issues#
Import errors: Make sure all imports in examples are available
Outdated examples: Keep examples current with API changes
Broken links: Verify that all cross-references work
Missing docstrings: All public functions need documentation
Getting help#
If you need assistance:
💬 Slack: Join the LF Energy Slack workspace (#openstef channel)
🐛 Issues: Check GitHub Issues or create a new one
📧 Email: Contact us at
openstef@lfenergy.org🤝 Community meetings: Join our four-weekly co-coding sessions
For more information, see our Support page.
Working with tutorial notebooks#
Tutorials live in examples/tutorials/ as paired Jupytext
files: a .py (percent format) source of truth and a .ipynb companion kept in sync.
Key rules#
Edit the
.pyfile, not the.ipynb; the script is the single source of truth.Never commit notebook outputs. The
.ipynbonmainmust be output-free.Notebooks are rendered into the docs via myst-nb with cached execution (
nb_execution_mode = "cache").
Workflow#
# After editing a .py tutorial:
poe notebooks # Sync .py → .ipynb
# Before committing:
poe notebooks-clear # Strip any outputs from .ipynb
poe notebooks-check # Verify sync + no outputs (runs in CI)
Creating a new tutorial#
# Create the .py file in percent format, then pair it:
jupytext --set-formats "ipynb,py:percent" examples/tutorials/my_tutorial.py
# Add a toctree entry in docs/source/examples.rst
# Optionally tag the first SPDX cell with "remove-cell" (see existing tutorials)
Additional documentation resources#
If you need help with documentation specifically:
Check the Sphinx documentation
Look at existing documentation for examples
Reference the Diátaxis framework for guidance on documentation types