Contributing to GlassAlpha¶
Thank you for your interest in contributing to GlassAlpha! This guide will help you understand how to effectively contribute to our professional ML auditing toolkit.
Project philosophy¶
GlassAlpha follows an audit-first approach, prioritizing regulatory compliance and professional quality:
- Quality over features - Better to have fewer capabilities that work perfectly
- Determinism over performance - Reproducible results matter more than speed
- User value focus - Every change should improve audit quality or usability
- Professional standards - Code quality suitable for regulated industries
Development setup¶
Prerequisites¶
- Python 3.11+ (required for type hints and modern features)
- Git for version control
- Virtual environment tool (venv, conda, or poetry)
Quick setup¶
- Fork and clone the repository:
- Create and activate virtual environment:
python -m venv glassalpha-dev
source glassalpha-dev/bin/activate # Windows: glassalpha-dev\Scripts\activate
- Install in development mode:
- Set up development tools:
# Install pre-commit hooks
pre-commit install
# Verify installation
glassalpha --version
glassalpha list
- Run tests to verify setup:
Development dependencies¶
The [dev] installation includes:
- Testing: pytest, pytest-cov
- Code Quality: ruff (linter), black (formatter), mypy (type checker)
- Pre-commit: Automated quality checks
- Documentation: Tools for docs development
Project structure¶
Understanding the codebase structure helps target contributions effectively:
glassalpha/
├── src/glassalpha/ # Main package
│ ├── models/ # Model wrappers (XGBoost, LightGBM, etc.)
│ ├── explain/ # Explainers (TreeSHAP, KernelSHAP)
│ ├── metrics/ # Performance, fairness, drift metrics
│ ├── data/ # Data loading and processing
│ ├── config/ # Configuration management
│ ├── cli/ # Command-line interface
│ ├── report/ # PDF generation and templates
│ ├── preprocessing/ # Artifact verification (compliance-critical)
│ ├── utils/ # Utilities (seeds, hashing, determinism)
│ └── exceptions.py # Custom exceptions
├── tests/ # Test suite
├── configs/ # Example configurations
├── dev/ # Development resources (internal)
└── pyproject.toml # Package configuration
Key Extension Points:
- Add new models by implementing explicit dispatch in
models/__init__.py - Add new explainers by implementing explicit dispatch in
explain/__init__.py - Add new metrics by implementing explicit dispatch in
metrics/__init__.py
Code quality standards¶
Type safety¶
All code must include type hints and pass mypy --strict:
from typing import Dict, List, Optional
import pandas as pd
import numpy as np
def compute_fairness_metrics(
y_true: np.ndarray,
y_pred: np.ndarray,
sensitive_features: pd.DataFrame,
metrics: List[str]
) -> Dict[str, float]:
"""Compute fairness metrics across demographic groups."""
...
Code formatting and linting¶
We use automated tools to ensure consistent code quality:
# Check code formatting
black --check src/
# Check linting
ruff check src/
# Check type hints
mypy src/
# Or use the convenience script
./scripts/lint.sh
Testing requirements¶
Coverage Target: 50%+ for core modules
Test Categories:
- Unit tests - Individual component testing
- Integration tests - Component interaction testing
- Determinism tests - Reproducibility verification
- End-to-end tests - Full audit pipeline
Example Test:
def test_xgboost_wrapper_deterministic():
"""Test that XGBoost produces identical results with same seed."""
# Arrange
X, y = make_classification(n_samples=100, random_state=42)
model1 = XGBoostWrapper(random_state=42)
model2 = XGBoostWrapper(random_state=42)
# Act
model1.fit(X, y)
model2.fit(X, y)
pred1 = model1.predict(X)
pred2 = model2.predict(X)
# Assert
np.testing.assert_array_equal(pred1, pred2)
Performance testing¶
CLI performance is critical for user experience. We maintain automated performance regression tests.
Performance Expectations:
| Command | Target | Current Baseline |
|---|---|---|
glassalpha --help |
<300ms | ~108ms |
glassalpha --version |
<100ms | ~46ms |
glassalpha datasets list |
<500ms | ~248ms |
| Full audit (german_credit) | <60s | ~5s |
Run Performance Tests:
# Run all performance tests
pytest tests/test_cli_performance.py -v
# Skip slow tests during rapid iteration
pytest -m "not slow" -v
# Test specific performance aspects
pytest tests/test_cli_performance.py::TestCLIPerformance::test_help_command_fast -v
Performance Test Categories:
- CLI Speed Tests - Ensure commands stay responsive
- Import Cleanness Tests - Verify no heavy imports at module level
- Audit Smoke Tests - Validate end-to-end audit completion
Maintaining Performance:
When adding new features, follow lazy loading patterns:
# ❌ BAD: Eager import at module level
from sklearn.model_selection import train_test_split
# ✅ GOOD: Lazy import within function
def split_data(data):
from sklearn.model_selection import train_test_split
return train_test_split(data)
Check for Regressions:
If your changes affect CLI startup or command performance:
# Measure CLI help time
time glassalpha --help
# Profile import time
python -X importtime -c "import glassalpha.__main__" 2> importtime.txt
cat importtime.txt
# Run full performance suite
pytest tests/test_cli_performance.py -v -s
Performance tests run automatically in CI and will fail if:
--helpexceeds 300ms (currently 108ms with 3x safety margin)- Heavy ML libraries (pandas, sklearn, xgboost) load during
--help - Full audits take longer than 60s
Contribution workflow¶
1. Choose contribution type¶
High-Value Contributions:
- Bug fixes - Especially for determinism or audit quality issues
- Performance improvements - Faster audit generation
- New model support - Additional ML library wrappers
- Enhanced explanations - Better SHAP integration or visualizations
- Improved error handling - Clearer error messages and recovery
- Documentation - Examples, guides, API documentation
Lower Priority:
- Complex features without clear audit benefit
- Breaking API changes
- Features requiring significant maintenance overhead
2. Create feature branch¶
3. Development process¶
Before coding:
- Check existing issues and discussions
- Create an issue for significant changes
- Discuss architecture for major features
While coding:
- Write tests first (TDD approach recommended)
- Follow existing patterns and interfaces
- Maintain deterministic behavior
- Add comprehensive docstrings
Code example pattern:
def load_new_model(model_type: str, **kwargs):
"""Add new model to explicit dispatch in models/__init__.py."""
if model_type == "new_model":
from .new_model import NewModelWrapper
return NewModelWrapper(**kwargs)
# Keep existing models...
raise ValueError(f"Unknown model_type: {model_type}")
4. Testing your changes¶
# Run specific test categories
pytest tests/test_models/ # Model tests
pytest tests/test_explainers/ # Explainer tests
pytest tests/test_integration/ # Integration tests
# Run all tests with coverage
pytest --cov=src/glassalpha --cov-report=html
# Test determinism (crucial for audit reproducibility)
pytest -k deterministic
# Test end-to-end workflow
glassalpha audit --config german_credit_simple.yaml --output test.pdf
5. Quality checks¶
# Comprehensive quality check
./scripts/lint.sh
# Manual checks if needed
black src/
ruff check --fix src/
mypy src/
6. Commit your changes¶
Use conventional commit format:
git add .
git commit -m "feat(models): add RandomForest wrapper with TreeSHAP support
- Implement RandomForestWrapper following ModelInterface
- Add comprehensive test suite with determinism checks
- Include capability declarations for SHAP compatibility
- Update model registry and documentation
Closes #123"
Commit Types:
feat:New featuresfix:Bug fixesdocs:Documentation updatestest:Test improvementsrefactor:Code restructuringperf:Performance improvements
Pull request process¶
Before submitting¶
- Rebase on main to ensure clean history
- Run full test suite and ensure all pass
- Check coverage hasn't decreased significantly
- Update documentation for any user-visible changes
- Add changelog entry if user-facing
PR description template¶
## Description
Brief description of changes and motivation.
## Type of Change
- [ ] Bug fix (non-breaking change fixing an issue)
- [ ] New feature (non-breaking change adding functionality)
- [ ] Breaking change (would cause existing functionality to not work as expected)
- [ ] Documentation update
## Testing
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Manual testing completed
- [ ] Determinism verified (if applicable)
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] Tests added/updated
- [ ] No breaking changes (or clearly documented)
Review process¶
- Automated checks must pass (CI/CD pipeline)
- Code review by maintainer
- Testing verification in review environment
- Documentation review for user-facing changes
- Merge after approval
Architecture guidelines¶
Explicit dispatch pattern¶
GlassAlpha uses explicit dispatch for AI maintainability. All extension points use clear if/elif chains with error messages.
Adding New Models¶
Add to src/glassalpha/models/__init__.py:
def load_model(model_type: str, **kwargs):
"""Load model with clear error messages."""
if model_type == "xgboost":
from .tree_models import XGBoostWrapper
return XGBoostWrapper(**kwargs)
elif model_type == "lightgbm":
from .tree_models import LightGBMWrapper
return LightGBMWrapper(**kwargs)
elif model_type == "new_model": # Add your model here
from .new_model import NewModelWrapper
return NewModelWrapper(**kwargs)
else:
raise ValueError(f"Unknown model_type: {model_type}")
# Also update model detection
def detect_model_type(model_path: str) -> str:
# Implementation
...
Adding New Explainers¶
Add to src/glassalpha/explain/__init__.py:
def select_explainer(model_type: str, config):
"""Select explainer with fallback chain."""
if model_type == "xgboost":
try:
from .shap import TreeSHAPExplainer
return TreeSHAPExplainer()
except ImportError:
pass
# Fallback to KernelSHAP for any model
try:
from .shap import KernelSHAPExplainer
return KernelSHAPExplainer()
except ImportError:
raise ImportError("No explainers available")
raise ValueError(f"No compatible explainer for {model_type}")
Adding New Metrics¶
Add to src/glassalpha/metrics/__init__.py:
def compute_metrics(model_type: str, y_true, y_pred, **kwargs):
"""Compute metrics with deterministic output."""
if model_type in ["xgboost", "lightgbm"]:
# Tree model metrics
return compute_tree_metrics(y_true, y_pred, **kwargs)
elif model_type == "sklearn":
# Linear model metrics
return compute_linear_metrics(y_true, y_pred, **kwargs)
else:
raise ValueError(f"Unsupported model_type: {model_type}")
AI-maintainable principles¶
GlassAlpha follows AI-maintainable code principles from .cursor/rules/architecture.mdc:
- Explicit over dynamic - Clear if/elif chains instead of registries
- Flat over nested - Max 3 directory levels
- Consolidated over scattered - Similar code together in one file
- Simple data flow - Direct function calls, minimal indirection
- Clear boundaries - Compliance-critical code isolated
Deterministic design¶
All operations must be reproducible:
def deterministic_operation(data, random_state=None):
"""Ensure operation can be reproduced exactly."""
if random_state is not None:
np.random.seed(random_state)
# Deterministic processing
result = process_data(data)
# Include randomness source in output for audit trail
return {
"result": result,
"random_state": random_state,
"timestamp": datetime.utcnow(),
"version": __version__
}
Testing guidelines¶
Test organization¶
tests/
├── test_core/ # Core architecture tests
├── test_models/ # Model wrapper tests
├── test_explainers/ # Explainer tests
├── test_metrics/ # Metrics tests
├── test_integration/ # Component integration
├── test_cli/ # CLI interface tests
├── test_deterministic/ # Reproducibility tests
└── test_end_to_end/ # Full pipeline tests
Writing effective tests¶
Test Structure (Arrange-Act-Assert):
def test_specific_behavior():
"""Test description explaining what behavior is verified."""
# Arrange - Set up test data and conditions
data = create_test_data()
config = create_test_config()
# Act - Execute the operation being tested
result = perform_operation(data, config)
# Assert - Verify expected outcomes
assert result.success
assert len(result.explanations) > 0
assert result.manifest["random_seed"] == config.random_seed
Testing Patterns:
- Determinism Tests:
def test_audit_determinism():
"""Verify identical configs produce identical results."""
config = load_config("test_config.yaml")
result1 = run_audit(config)
result2 = run_audit(config)
# Critical for regulatory compliance
assert result1.manifest == result2.manifest
np.testing.assert_array_equal(result1.shap_values, result2.shap_values)
- Error Handling Tests:
def test_graceful_error_handling():
"""Verify clear error messages for common failures."""
invalid_config = {"model": {"type": "nonexistent"}}
with pytest.raises(ComponentNotFoundError) as exc_info:
run_audit(invalid_config)
assert "nonexistent" in str(exc_info.value)
assert "available models" in str(exc_info.value)
Documentation standards¶
Docstring format¶
Use Google-style docstrings:
def explain_model_decision(
model: ModelInterface,
instance: pd.Series,
background_data: Optional[pd.DataFrame] = None,
explainer_type: str = "auto"
) -> Dict[str, Any]:
"""Generate explanation for a single model decision.
This function provides detailed explanations for individual predictions,
helping users understand which features contributed to the model's decision
and by how much.
Args:
model: Trained model implementing ModelInterface protocol
instance: Single data instance to explain (must match model input schema)
background_data: Reference data for SHAP baseline. If None, uses
model training data when available
explainer_type: Type of explainer to use ("auto", "shap", "lime").
"auto" selects best explainer based on model capabilities
Returns:
Dictionary containing:
- "explanations": Feature importance scores
- "baseline": Reference prediction value
- "prediction": Model prediction for this instance
- "confidence": Prediction confidence if available
- "metadata": Explainer type, version, parameters used
Raises:
ValueError: If instance shape doesn't match model input requirements
ExplainerNotSupportedError: If no compatible explainer found for model
DataValidationError: If background_data format is incompatible
Example:
>>> model = XGBoostWrapper()
>>> model.fit(X_train, y_train)
>>> explanation = explain_model_decision(model, X_test.iloc[0])
>>> print(explanation["explanations"])
{'feature1': 0.23, 'feature2': -0.15, ...}
Generate explanation plot:
>>> plot_explanation_waterfall(explanation)
Note:
Explanations are computed using the model's most compatible explainer.
For tree-based models (XGBoost, LightGBM), TreeSHAP provides exact
Shapley values. For other models, KernelSHAP provides approximations.
"""
Example updates¶
When adding new features, update relevant examples:
- Configuration examples - Show how to use new features
- Tutorial updates - Integrate new capabilities into user journey
- API documentation - Document new interfaces and parameters
Security and privacy guidelines¶
Data handling¶
- No PII in logs - Never log personally identifiable information
- Sanitize inputs - Validate and clean all user inputs
- Hash sensitive data - Use SHA-256 for any identifier hashing
- Local processing - Core library must work completely offline
Example safe logging¶
import logging
from glassalpha.utils import hash_dataframe
logger = logging.getLogger(__name__)
def process_audit_data(data: pd.DataFrame):
"""Process audit data safely."""
data_hash = hash_dataframe(data)
# Safe: Log hash and metadata, never actual data
logger.info(f"Processing dataset: hash={data_hash}, shape={data.shape}")
# NEVER do this:
# logger.info(f"Processing data: {data.to_dict()}") # Could contain PII
return process_data(data)
Getting help¶
Communication channels¶
- GitHub Issues - Bug reports, feature requests
- GitHub Discussions - General questions, usage help
- Code Review - Feedback on pull requests
Before asking questions¶
- Search existing issues and discussions
- Check documentation including FAQ and troubleshooting
- Try the quickstart tutorial to understand basic usage
- Look at existing code examples for implementation patterns
How to ask effective questions¶
Good Question Format:
**What I'm trying to do:** Add support for CatBoost models
**What I've tried:**
- Implemented CatBoostWrapper following ModelInterface
- Added basic tests following XGBoost example
- Getting error: "TreeSHAP not compatible with CatBoost"
**Expected behavior:** TreeSHAP should work with CatBoost like other tree models
**Environment:**
- GlassAlpha version: 0.2.0
- Python version: 3.11.5
- CatBoost version: 1.2.0
**Code snippet:** [minimal reproducing example]
Recognition and credits¶
Contributors are recognized in several ways:
- Changelog entries for significant contributions
- GitHub contributor list automatically maintained
- Documentation credits for major documentation contributions
- Release notes highlighting key contributions
Development roadmap¶
Understanding our direction helps target valuable contributions:
Current Focus Areas:
- Enhanced model support (additional ML libraries)
- Improved explanation quality and performance
- Better error handling and user experience
- Comprehensive documentation and examples
Potential Future Considerations (community-driven):
- Additional data modalities based on demand
- Extended compliance framework support
- Enhanced integration capabilities
- Advanced visualization options
Quick reference¶
Essential Commands:
# Setup
pip install -e ".[dev]" && pre-commit install
# Development
./scripts/lint.sh # Code quality checks
pytest # Run tests
glassalpha audit --config german_credit_simple.yaml --output test.pdf
# Quality gates
black --check src/ # Formatting
ruff check src/ # Linting
mypy src/ # Type checking
pytest --cov=src/glassalpha # Coverage
Getting Unstuck:
- Check Trust & Deployment Guide for system design
- Look at existing implementations for patterns
- Search GitHub Issues
- Ask in GitHub Discussions
Thank you for contributing to GlassAlpha! Your contributions help make ML auditing more transparent, reliable, and accessible for regulated industries.