CONTRIBUTING.md

# Contributing to ExFairness

Thank you for your interest in contributing to ExFairness! This document provides guidelines for contributing to the project.

## Table of Contents

- [Code of Conduct](#code-of-conduct)
- [Getting Started](#getting-started)
- [Development Workflow](#development-workflow)
- [Contribution Guidelines](#contribution-guidelines)
- [Testing Requirements](#testing-requirements)
- [Documentation Standards](#documentation-standards)
- [Submitting Changes](#submitting-changes)

---

## Code of Conduct

### Our Pledge

We are committed to providing a welcoming and inclusive environment for all contributors, regardless of background or identity.

### Expected Behavior

- Be respectful and considerate in all interactions
- Provide constructive feedback
- Focus on what's best for the project and community
- Show empathy towards other contributors

### Unacceptable Behavior

- Harassment or discriminatory language
- Personal attacks or trolling
- Publishing others' private information
- Other conduct inappropriate in a professional setting

---

## Getting Started

### Prerequisites

- Elixir 1.14 or higher
- Erlang/OTP 25 or higher
- Git
- Basic understanding of fairness in machine learning (optional but helpful)

### Setting Up Development Environment

```bash
# 1. Fork the repository on GitHub
# 2. Clone your fork
git clone https://github.com/YOUR_USERNAME/ExFairness.git
cd ExFairness

# 3. Add upstream remote
git remote add upstream https://github.com/North-Shore-AI/ExFairness.git

# 4. Install dependencies
mix deps.get

# 5. Verify tests pass
mix test

# 6. Verify quality checks pass
mix format --check-formatted
mix compile --warnings-as-errors
mix credo --strict
```

---

## Development Workflow

### Strict Test-Driven Development (TDD)

ExFairness follows **strict TDD**. All contributions must follow the Red-Green-Refactor cycle:

#### 1. RED Phase - Write Failing Tests

```elixir
# test/ex_fairness/metrics/new_metric_test.exs
defmodule ExFairness.Metrics.NewMetricTest do
  use ExUnit.Case, async: true
  doctest ExFairness.Metrics.NewMetric

  describe "compute/3" do
    test "computes metric correctly" do
      predictions = Nx.tensor([1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0])
      sensitive = Nx.tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])

      result = NewMetric.compute(predictions, sensitive)

      assert result.metric_value == expected_value
      assert result.passes == expected_pass_fail
    end

    # Add more tests...
  end
end
```

Run tests to verify they fail:
```bash
mix test test/ex_fairness/metrics/new_metric_test.exs
# Should show compilation error or test failures
```

#### 2. GREEN Phase - Implement to Pass

```elixir
# lib/ex_fairness/metrics/new_metric.ex
defmodule ExFairness.Metrics.NewMetric do
  @moduledoc """
  Documentation for new metric.

  ## Mathematical Definition

  [Include formal definition]

  ## When to Use

  [Explain appropriate use cases]

  ## Limitations

  [Discuss limitations]

  ## References

  [Include research citations]
  """

  alias ExFairness.Validation

  @spec compute(Nx.Tensor.t(), Nx.Tensor.t(), keyword()) :: map()
  def compute(predictions, sensitive_attr, opts \\ []) do
    # Validate inputs
    Validation.validate_predictions!(predictions)
    # ... implement logic
  end
end
```

Run tests to verify they pass:
```bash
mix test test/ex_fairness/metrics/new_metric_test.exs
# Should show all tests passing
```

#### 3. REFACTOR Phase - Optimize and Document

- Add comprehensive documentation
- Add type specifications
- Optimize performance
- Add doctests
- Ensure code formatting

```bash
mix format
mix compile --warnings-as-errors
mix credo --strict
```

---

## Contribution Guidelines

### Types of Contributions

We welcome:

1. **Bug Fixes** - Fix issues in existing code
2. **New Metrics** - Implement additional fairness metrics
3. **New Detection Algorithms** - Add bias detection methods
4. **New Mitigation Techniques** - Add fairness mitigation approaches
5. **Documentation Improvements** - Enhance docs, examples, guides
6. **Performance Optimizations** - Improve speed/memory usage
7. **Test Additions** - Add edge cases, property tests, integration tests

### Before Starting

1. **Check existing issues** - Avoid duplicate work
2. **Open an issue** - Discuss your proposal first
3. **Get approval** - Especially for large changes
4. **Follow the roadmap** - See `docs/20251020/future_directions.md`

### Coding Standards

#### Code Style

- Follow the [Elixir Style Guide](https://github.com/christopheradams/elixir_style_guide)
- Use `mix format` (configured for 100-char lines)
- Pass `mix credo --strict`
- No compiler warnings

#### Naming Conventions

```elixir
# Modules: CamelCase
defmodule ExFairness.Metrics.DemographicParity

# Functions: snake_case
def compute_disparity(predictions, sensitive_attr)

# Variables: snake_case
group_a_rate = 0.5

# Constants: @uppercase
@default_threshold 0.1

# Private functions: prefix with defp
defp generate_interpretation(...)
```

#### Type Specifications

**Required for all public functions:**

```elixir
@type result :: %{
  disparity: float(),
  passes: boolean(),
  threshold: float()
}

@spec compute(Nx.Tensor.t(), Nx.Tensor.t(), keyword()) :: result()
def compute(predictions, sensitive_attr, opts \\ []) do
  # ...
end
```

---

## Testing Requirements

### Minimum Test Coverage

Every new feature must include:

1. **At least 5 unit tests:**
   - Happy path (normal case)
   - Edge case #1
   - Edge case #2
   - Error case (validation)
   - Configuration test (custom options)

2. **At least 1 doctest:**
   - Working example in @doc
   - Verified to execute correctly

3. **Property tests (if applicable):**
   - For metrics: symmetry, boundedness, monotonicity

### Test Data Requirements

- **Minimum 10 samples per group** (statistical reliability)
- **Use 20-element patterns** for consistency
- **Explicit calculations** in comments
- **Realistic scenarios** (not trivial 1-2 samples)

Example:
```elixir
test "computes metric correctly" do
  # Group A: 5/10 = 0.5, Group B: 3/10 = 0.3
  # Expected disparity: 0.2
  predictions = Nx.tensor([1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0])
  sensitive = Nx.tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])

  result = YourMetric.compute(predictions, sensitive)

  assert_in_delta(result.disparity, 0.2, 0.01)
end
```

### Running Tests

```bash
# Run all tests
mix test

# Run specific test file
mix test test/ex_fairness/metrics/your_metric_test.exs

# Run with coverage
mix coveralls

# Run specific test
mix test test/ex_fairness/metrics/your_metric_test.exs:42
```

---

## Documentation Standards

### Module Documentation (@moduledoc)

Every module must include:

```elixir
defmodule ExFairness.Metrics.YourMetric do
  @moduledoc """
  Brief description of the metric.

  ## Mathematical Definition

  [Include formal probability notation]

  ## When to Use

  - Use case 1
  - Use case 2

  ## Limitations

  - Limitation 1
  - Limitation 2

  ## References

  - Author (Year). "Paper title." *Venue*.

  ## Examples

      iex> # Working example
      iex> result = ExFairness.Metrics.YourMetric.compute(...)
      iex> result.passes
      true

  """
end
```

### Function Documentation (@doc)

Every public function must include:

```elixir
@doc """
Brief description.

## Parameters

  * `param1` - Description
  * `param2` - Description
  * `opts` - Options:
    * `:option1` - Description (default: value)

## Returns

A map containing:
  * `:field1` - Description
  * `:field2` - Description

## Examples

    iex> result = function(arg1, arg2)
    iex> result.field1
    expected_value

"""
@spec function(type1(), type2(), keyword()) :: return_type()
def function(param1, param2, opts \\ []) do
  # Implementation
end
```

### Citation Format

Follow academic citation standards:

```
Author, A., Author, B., & Author, C. (Year). "Title of paper."
*Journal/Conference Name*, volume(issue), pages.
DOI: xx.xxxx/xxxxx
```

Example:
```
Hardt, M., Price, E., & Srebro, N. (2016). "Equality of Opportunity
in Supervised Learning." In *Advances in Neural Information Processing
Systems* (NeurIPS '16), pp. 3315-3323.
```

---

## Submitting Changes

### Pull Request Process

1. **Create a feature branch**
   ```bash
   git checkout -b feature/your-feature-name
   ```

2. **Make your changes**
   - Follow TDD (tests first)
   - Follow coding standards
   - Update documentation

3. **Verify quality**
   ```bash
   mix format
   mix test
   mix compile --warnings-as-errors
   mix credo --strict
   mix dialyzer  # If PLT already built
   ```

4. **Commit with clear messages**
   ```bash
   git commit -m "Add calibration fairness metric

   Implements calibration metric as specified in Pleiss et al. (2017).
   Includes binning, ECE computation, and calibration curves.

   - 15 unit tests
   - 2 doctests
   - Complete documentation with mathematical definition
   - Citations included
   "
   ```

5. **Push to your fork**
   ```bash
   git push origin feature/your-feature-name
   ```

6. **Open Pull Request**
   - Use clear PR title
   - Reference any related issues
   - Describe what you changed and why
   - Include test results

### Pull Request Template

```markdown
## Description
[Describe your changes]

## Motivation
[Why is this change needed?]

## Related Issues
Fixes #123

## Changes
- [ ] New feature / bug fix / documentation
- [ ] Tests added/updated
- [ ] Documentation added/updated
- [ ] CHANGELOG.md updated

## Testing
- [ ] All tests pass (`mix test`)
- [ ] No warnings (`mix compile --warnings-as-errors`)
- [ ] Credo passes (`mix credo --strict`)
- [ ] Code formatted (`mix format --check-formatted`)

## Checklist
- [ ] Followed TDD (tests written first)
- [ ] Added type specs (@spec)
- [ ] Added documentation (@doc)
- [ ] Included research citations (if applicable)
- [ ] Updated CHANGELOG.md
```

### Commit Message Guidelines

**Format:**
```
<type>: <subject>

<body>

<footer>
```

**Types:**
- `feat:` New feature
- `fix:` Bug fix
- `docs:` Documentation only
- `test:` Test additions/changes
- `refactor:` Code refactoring
- `perf:` Performance improvements
- `chore:` Maintenance tasks

**Example:**
```
feat: Add calibration fairness metric

Implements calibration metric with binning and ECE computation.
Based on Pleiss et al. (2017) "On fairness and calibration."

- 15 unit tests for binning strategies and edge cases
- 2 doctests with working examples
- Complete mathematical documentation
- Citations: Pleiss et al. (2017)

Closes #42
```

---

## Adding New Fairness Metrics

### Step-by-Step Guide

#### 1. Research Phase

- [ ] Find peer-reviewed paper defining the metric
- [ ] Understand mathematical definition
- [ ] Identify when to use and limitations
- [ ] Check if similar metric exists

#### 2. Design Phase

- [ ] Write specification document (in `docs/`)
- [ ] Define function signature and return type
- [ ] Plan test cases (minimum 10)
- [ ] Get approval via GitHub issue

#### 3. Implementation Phase (TDD)

**RED - Write tests first:**

```bash
# Create test file
touch test/ex_fairness/metrics/your_metric_test.exs

# Write comprehensive tests
# Run and verify they fail
mix test test/ex_fairness/metrics/your_metric_test.exs
```

**GREEN - Implement:**

```bash
# Create implementation file
touch lib/ex_fairness/metrics/your_metric.ex

# Implement minimum code to pass tests
# Run and verify tests pass
mix test test/ex_fairness/metrics/your_metric_test.exs
```

**REFACTOR - Polish:**

```bash
# Add documentation
# Add type specs
# Optimize if needed
# Add to main API (lib/ex_fairness.ex)

# Verify everything passes
mix test
mix format
mix compile --warnings-as-errors
mix credo --strict
```

#### 4. Documentation Phase

- [ ] Add to README.md examples section
- [ ] Add to mathematical foundations section
- [ ] Include in metrics reference table
- [ ] Add research citations with DOI
- [ ] Update CHANGELOG.md

#### 5. Validation Phase

- [ ] Test against reference implementation (if available)
- [ ] Verify on real dataset (if applicable)
- [ ] Performance benchmark
- [ ] Code review

### Metric Template

Use this template for new metrics:

```elixir
defmodule ExFairness.Metrics.YourMetric do
  @moduledoc """
  Brief description.

  ## Mathematical Definition

  [Formal definition with notation]

  ## When to Use

  - Use case 1
  - Use case 2

  ## Limitations

  - Limitation 1
  - Limitation 2

  ## References

  - Citation 1
  - Citation 2

  ## Examples

      iex> # Working example
  """

  alias ExFairness.{Utils, Validation}

  @default_threshold 0.1
  @default_min_per_group 10

  @type result :: %{
    # Define return type fields
  }

  @spec compute(Nx.Tensor.t(), Nx.Tensor.t(), keyword()) :: result()
  def compute(predictions, sensitive_attr, opts \\ []) do
    # 1. Extract options
    # 2. Validate inputs
    # 3. Compute metric
    # 4. Generate interpretation
    # 5. Return result map
  end

  defp generate_interpretation(...) do
    # Plain language explanation
  end
end
```

---

## Testing Requirements

### Test File Structure

```elixir
defmodule ExFairness.Metrics.YourMetricTest do
  use ExUnit.Case, async: true
  doctest ExFairness.Metrics.YourMetric

  alias ExFairness.Metrics.YourMetric

  describe "compute/3" do
    test "computes perfect fairness" do
      # Test with zero disparity
    end

    test "detects disparity" do
      # Test with known disparity
    end

    test "accepts custom threshold" do
      # Test configuration options
    end

    test "validates inputs" do
      # Test input validation
    end

    test "handles edge case: all zeros" do
      # Edge case testing
    end

    test "handles edge case: all ones" do
      # Edge case testing
    end

    test "returns interpretation" do
      # Test interpretation generation
    end
  end
end
```

### Mandatory Test Coverage

- [ ] Happy path (normal operation)
- [ ] Perfect fairness (disparity = 0)
- [ ] Maximum disparity
- [ ] Custom threshold
- [ ] Input validation (invalid inputs raise errors)
- [ ] Edge case: all zeros
- [ ] Edge case: all ones
- [ ] Edge case: single value
- [ ] Unbalanced groups
- [ ] Interpretation generation

### Assertion Guidelines

**For floating point values:**
```elixir
# Use assert_in_delta with 0.01 tolerance
assert_in_delta(result.disparity, 0.5, 0.01)
```

**For exact values:**
```elixir
# Use exact equality
assert result.passes == true
assert Nx.to_number(count) == 10
```

**For errors:**
```elixir
# Use assert_raise with regex
assert_raise ExFairness.Error, ~r/must be binary/, fn ->
  YourMetric.compute(invalid_input, sensitive)
end
```

---

## Documentation Standards

### Required Documentation Elements

Every new module must include:

1. **@moduledoc with:**
   - Brief description
   - Mathematical definition (formal notation)
   - When to use (3+ bullet points)
   - Limitations (2+ bullet points)
   - Research citations (full bibliographic info)
   - Working example (doctest)

2. **@doc for every public function with:**
   - Description
   - Parameters section (with types and defaults)
   - Returns section (with structure)
   - Examples section (with doctest)

3. **@spec for every public function**

4. **Inline comments for complex logic**

### Documentation Verification

```bash
# Generate docs locally
mix docs

# Open in browser
open doc/index.html

# Check for warnings
mix docs 2>&1 | grep warning

# Verify doctests pass
mix test --only doctest
```

---

## Code Review Checklist

Before submitting PR, verify:

### Code Quality
- [ ] No compiler warnings (`mix compile --warnings-as-errors`)
- [ ] No Credo issues (`mix credo --strict`)
- [ ] Code formatted (`mix format --check-formatted`)
- [ ] No Dialyzer errors (`mix dialyzer`)

### Testing
- [ ] All new code has tests
- [ ] All tests pass (`mix test`)
- [ ] Test coverage is comprehensive
- [ ] Edge cases covered
- [ ] Doctests work

### Documentation
- [ ] @moduledoc added to new modules
- [ ] @doc added to new public functions
- [ ] @spec added to all public functions
- [ ] Examples work (verified by doctests)
- [ ] Research citations included
- [ ] README.md updated (if user-facing change)
- [ ] CHANGELOG.md updated

### Quality
- [ ] Follows existing code patterns
- [ ] No code duplication
- [ ] Appropriate use of Nx.Defn (GPU acceleration)
- [ ] Error messages are helpful
- [ ] Comments explain "why" not "what"

---

## Development Commands

### Essential Commands

```bash
# Install dependencies
mix deps.get

# Run tests
mix test

# Run specific test
mix test test/path/to/test.exs:line_number

# Run with coverage
mix coveralls
mix coveralls.html  # HTML report in cover/

# Format code
mix format

# Check formatting
mix format --check-formatted

# Compile with warnings as errors
mix compile --warnings-as-errors

# Run linter
mix credo --strict

# Type checking (requires PLT build)
mix dialyzer

# Generate documentation
mix docs

# Full quality check (run before PR)
mix format --check-formatted && \
mix compile --warnings-as-errors && \
mix test && \
mix credo --strict
```

### Building PLT for Dialyzer (One-time)

```bash
# This takes a few minutes the first time
mix dialyzer --plt

# Then run analysis
mix dialyzer
```

---

## Performance Considerations

### When to Use Nx.Defn

**Use for:**
- Numerical computations
- Operations on tensors
- Code that benefits from GPU acceleration

**Don't use for:**
- String manipulation
- Control flow with dynamic decisions
- I/O operations

### Example

```elixir
# Good: Numerical computation with defn
import Nx.Defn

defn compute_disparity(rate_a, rate_b) do
  Nx.abs(Nx.subtract(rate_a, rate_b))
end

# Good: Validation in regular Elixir
def compute(predictions, sensitive_attr, opts \\ []) do
  Validation.validate_predictions!(predictions)  # Regular Elixir
  disparity = compute_disparity(rate_a, rate_b)  # Nx.Defn
end
```

---

## Adding Research Citations

### Citation Requirements

For new metrics or algorithms:

1. **Find the original paper** that proposed the technique
2. **Include full citation** with:
   - Authors (all, or first 3 + "et al.")
   - Year
   - Title (in quotes)
   - Venue (journal or conference)
   - Volume/issue/pages (for journals)
   - DOI (if available)

3. **Add to module @moduledoc**
4. **Add to README.md** Research Foundations section

### Citation Format Example

```elixir
@moduledoc """
Your metric description.

## References

- Hardt, M., Price, E., & Srebro, N. (2016). "Equality of Opportunity
  in Supervised Learning." In *Advances in Neural Information Processing
  Systems* (NeurIPS '16), pp. 3315-3323.
"""
```

---

## Common Pitfalls to Avoid

### Don't

❌ Write implementation before tests
❌ Change tests to make them pass (fix code instead)
❌ Skip edge case testing
❌ Use floating point equality (use `assert_in_delta`)
❌ Forget to update CHANGELOG.md
❌ Add compiler warnings
❌ Skip documentation
❌ Use trivial test data (2-3 samples)
❌ Forget type specifications
❌ Copy-paste without attribution

### Do

✅ Write tests first (TDD)
✅ Use `assert_in_delta` for floats
✅ Test edge cases explicitly
✅ Update CHANGELOG.md
✅ Add comprehensive documentation
✅ Include research citations
✅ Use realistic test data (10+ per group)
✅ Add type specifications
✅ Format code before committing
✅ Run full quality check before PR

---

## Getting Help

### Resources

- **Documentation:** https://hexdocs.pm/ex_fairness
- **Issues:** https://github.com/North-Shore-AI/ExFairness/issues
- **Discussions:** https://github.com/North-Shore-AI/ExFairness/discussions
- **Technical Docs:** `docs/20251020/` directory

### Asking Questions

**Good question:**
> "I want to add the calibration metric from Pleiss et al. (2017). I've read the paper and understand the math. Should I use uniform binning or quantile binning for the default? The paper uses uniform but some implementations use quantile."

**Contains:**
- Specific feature
- Research reference
- Shows you've done homework
- Asks specific question

**Not helpful:**
> "How do I add a new metric?"

**Too vague:**
- No specific metric mentioned
- No research reference
- No specific question

### Response Time

- Simple questions: 24-48 hours
- Feature proposals: 3-7 days for review
- Pull requests: 1-2 weeks for review

---

## Release Process (Maintainers Only)

### Version Numbering

Follows [Semantic Versioning](https://semver.org/):

- **MAJOR** (1.0.0): Breaking changes
- **MINOR** (0.2.0): New features, backward compatible
- **PATCH** (0.1.1): Bug fixes only

### Release Checklist

- [ ] All tests pass
- [ ] CHANGELOG.md updated
- [ ] Version bumped in mix.exs
- [ ] Documentation generated successfully
- [ ] Git tag created (`git tag -a v0.2.0 -m "Release v0.2.0"`)
- [ ] Pushed to GitHub (`git push --tags`)
- [ ] Published to Hex.pm (`mix hex.publish`)
- [ ] HexDocs generated
- [ ] GitHub release created with notes

---

## Recognition

Contributors will be:

- Listed in release notes
- Mentioned in CHANGELOG.md
- Credited in git commit history
- Thanked in project documentation

Significant contributions may lead to:

- Co-authorship on academic papers
- Maintainer status
- Conference presentation opportunities

---

## Questions?

If you have questions about contributing, please:

1. Check this document first
2. Search existing issues
3. Open a new issue with the `question` label
4. Be patient - we're a small team!

---

## Thank You!

Your contributions help make ML fairer for everyone. We appreciate your effort to improve ExFairness!

**Happy Contributing!** 🚀

---

**Last Updated:** October 20, 2025
**Version:** 1.0
**Maintainers:** North Shore AI Research Team