docs/architecture.md

# ExFairness Architecture

## Overview

ExFairness is designed as a modular, composable library for fairness analysis in ML systems. The architecture follows functional programming principles and leverages Elixir's strengths in concurrent processing and data transformation.

## System Architecture

```mermaid
graph TB
    A[User API] --> B[Metrics Layer]
    A --> C[Detection Layer]
    A --> D[Mitigation Layer]

    B --> E[Group Fairness]
    B --> F[Individual Fairness]
    B --> G[Causal Fairness]

    C --> H[Disparate Impact]
    C --> I[Intersectional Analysis]
    C --> J[Temporal Monitoring]

    D --> K[Pre-processing]
    D --> L[In-processing]
    D --> M[Post-processing]

    E --> N[Nx Computation Engine]
    F --> N
    G --> N
    H --> N
    I --> N
    J --> N
```

## Core Components

### 1. Metrics Layer

The metrics layer implements mathematical definitions of fairness:

- **Group Fairness Metrics**: Demographic parity, equalized odds, equal opportunity
- **Individual Fairness Metrics**: Lipschitz continuity, similarity-based fairness
- **Causal Fairness Metrics**: Counterfactual fairness, path-specific effects

**Design Pattern**: Pure functions that take predictions, labels, and sensitive attributes as Nx tensors and return fairness measurements.

### 2. Detection Layer

The detection layer identifies bias in data and models:

- **Statistical Testing**: Hypothesis tests for fairness violations
- **Intersectional Analysis**: Multi-dimensional fairness across attribute combinations
- **Temporal Monitoring**: Time-series analysis of fairness drift

**Design Pattern**: Higher-order functions that compose metric calculations with statistical analysis.

### 3. Mitigation Layer

The mitigation layer provides bias correction techniques:

- **Pre-processing**: Data transformation before model training
- **In-processing**: Fairness constraints during model training
- **Post-processing**: Prediction adjustment after model training

**Design Pattern**: Transformation pipelines that can be composed and configured.

### 4. Reporting Layer

The reporting layer generates comprehensive fairness assessments:

- **Metric Aggregation**: Combine multiple fairness metrics
- **Interpretation**: Provide actionable recommendations
- **Export**: Generate reports in multiple formats

## Data Flow

```mermaid
sequenceDiagram
    participant User
    participant API
    participant Metrics
    participant Nx
    participant Report

    User->>API: fairness_report(predictions, labels, sensitive_attr)
    API->>Metrics: compute_demographic_parity()
    Metrics->>Nx: tensor operations
    Nx-->>Metrics: results
    API->>Metrics: compute_equalized_odds()
    Metrics->>Nx: tensor operations
    Nx-->>Metrics: results
    API->>Report: aggregate_metrics()
    Report-->>User: comprehensive report
```

## Module Organization

### Primary Modules

```
ExFairness/
├── ExFairness                        # Main API module
├── ExFairness.Metrics/
│   ├── DemographicParity
│   ├── EqualizedOdds
│   ├── EqualOpportunity
│   ├── PredictiveParity
│   ├── Calibration
│   ├── IndividualFairness
│   └── Counterfactual
├── ExFairness.Detection/
│   ├── DisparateImpact
│   ├── StatisticalParity
│   ├── Intersectional
│   ├── TemporalDrift
│   ├── LabelBias
│   └── Representation
├── ExFairness.Mitigation/
│   ├── Reweighting
│   ├── Resampling
│   ├── ThresholdOptimization
│   ├── AdversarialDebiasing
│   ├── FairRepresentation
│   └── Calibration
├── ExFairness.Report
└── ExFairness.Utils
```

## Design Principles

### 1. Tensor-First Design

All computations use Nx tensors for:
- Performance optimization
- GPU acceleration support
- Functional composition
- Type safety

```elixir
# Example: Demographic parity calculation
def demographic_parity(predictions, sensitive_attr) do
  group_a_mask = Nx.equal(sensitive_attr, 0)
  group_b_mask = Nx.equal(sensitive_attr, 1)

  rate_a = compute_positive_rate(predictions, group_a_mask)
  rate_b = compute_positive_rate(predictions, group_b_mask)

  %{disparity: abs(rate_a - rate_b), ...}
end
```

### 2. Composability

Functions are designed to be composed:

```elixir
predictions
|> ExFairness.Metrics.DemographicParity.compute(sensitive_attr)
|> ExFairness.Detection.StatisticalParity.test(threshold: 0.1)
|> ExFairness.Report.interpret()
```

### 3. Configuration Over Convention

All metrics accept configuration options:

```elixir
ExFairness.equalized_odds(
  predictions,
  labels,
  sensitive_attr,
  threshold: 0.05,
  confidence_level: 0.95,
  bootstrap_samples: 1000
)
```

### 4. Immutability

All operations are pure and return new data structures:

```elixir
{fair_data, weights} = ExFairness.Mitigation.reweight(data, sensitive_attr)
# Original data is unchanged
```

## Performance Considerations

### Tensor Operations

- All metrics use vectorized Nx operations
- Avoid loops; use tensor operations
- Support for GPU backends (EXLA, Torchx)

### Parallel Processing

- Metrics can be computed in parallel using Task.async_stream
- Intersectional analysis parallelized across subgroups

```elixir
metrics = [:demographic_parity, :equalized_odds, :equal_opportunity]

results = Task.async_stream(metrics, fn metric ->
  apply(ExFairness.Metrics, metric, [predictions, labels, sensitive_attr])
end, max_concurrency: System.schedulers_online())
```

### Caching

- Reuse intermediate computations (confusion matrices, rates)
- Cache expensive operations (bootstrap sampling)

## Extension Points

### Custom Metrics

Users can define custom fairness metrics:

```elixir
defmodule MyCustomMetric do
  @behaviour ExFairness.Metric

  def compute(predictions, labels, sensitive_attr, opts) do
    # Custom metric implementation
  end

  def interpret(result) do
    # Custom interpretation
  end
end

ExFairness.fairness_report(predictions, labels, sensitive_attr,
  custom_metrics: [MyCustomMetric]
)
```

### Custom Mitigation

Users can implement custom mitigation strategies:

```elixir
defmodule MyMitigationStrategy do
  @behaviour ExFairness.Mitigation

  def apply(data, sensitive_attr, opts) do
    # Custom mitigation implementation
  end
end
```

## Integration Points

### With Nx/Axon

```elixir
# During model training
model = Axon.input("features")
|> Axon.dense(64, activation: :relu)
|> Axon.dense(1, activation: :sigmoid)

# Add fairness constraint
model = ExFairness.Mitigation.add_fairness_constraint(
  model,
  sensitive_attr_index: 3,
  constraint: :demographic_parity
)
```

### With Scholar

```elixir
# Evaluate fairness of Scholar models
model = Scholar.linear_regression(x_train, y_train)
predictions = Scholar.predict(model, x_test)

ExFairness.fairness_report(predictions, y_test, sensitive_attr)
```

### With Explorer

```elixir
# Work with DataFrames
df = Explorer.DataFrame.from_csv("data.csv")

predictions = df["predictions"] |> Explorer.Series.to_tensor()
labels = df["labels"] |> Explorer.Series.to_tensor()
sensitive = df["race"] |> Explorer.Series.to_tensor()

ExFairness.fairness_report(predictions, labels, sensitive)
```

## Testing Strategy

### Unit Tests

- Each metric has comprehensive unit tests
- Test edge cases (empty tensors, single-group data)
- Test numerical stability

### Property-Based Tests

```elixir
property "demographic_parity is symmetric" do
  check all predictions <- tensor(:int, shape: {100}),
            sensitive <- tensor(:int, shape: {100}) do

    result1 = ExFairness.demographic_parity(predictions, sensitive)
    result2 = ExFairness.demographic_parity(predictions, 1 - sensitive)

    assert_in_delta(result1.disparity, result2.disparity, 0.001)
  end
end
```

### Integration Tests

- Test full pipelines (detection -> mitigation -> validation)
- Test with real-world datasets
- Benchmark performance

## Error Handling

### Input Validation

```elixir
def demographic_parity(predictions, sensitive_attr) do
  validate_tensors!(predictions, sensitive_attr)
  validate_binary_predictions!(predictions)
  validate_binary_groups!(sensitive_attr)

  # Computation
end
```

### Informative Errors

```elixir
raise ExFairness.Error, """
Insufficient samples in group B (n=5).
Fairness metrics require at least 30 samples per group for reliable estimates.
Consider using bootstrap methods or collecting more data.
"""
```

## Future Architecture Enhancements

1. **Distributed Computation**: Support for analyzing large datasets across multiple nodes
2. **Streaming Metrics**: Online fairness monitoring for production systems
3. **AutoML Integration**: Automated fairness-aware hyperparameter tuning
4. **Causal Discovery**: Automated causal graph learning for counterfactual fairness
5. **Explainability Integration**: Combine with SHAP/LIME for bias explanation