> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kaireonai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Advanced fairness metrics + EU AI Act report

> W6.1 — intersectional analysis, mitigation recommendations, EU AI Act Article 14/15 conformity reports, and the publish-time fairness hard-gate.

## Fairness hard-gate (publish-time enforcement)

`POST /api/v1/decision-flows/publish` runs the fairness hard-gate as a
**pre-publish check** when the tenant has opted in. If configured
thresholds breach, the publish is blocked with HTTP 422 and a
structured violation report — the new flow version is **not** written.

### Configuration

Set on `tenant.settings.fairnessPolicy`:

```json theme={null}
{
  "fairnessPolicy": {
    "enabled": true,
    "sensitiveAttribute": "ethnicity",
    "thresholds": {
      "disparateImpactRatio": 0.8,
      "demographicParityGap": 0.2,
      "equalOpportunityGap": 0.2,
      "giniCoefficient": 0.2
    },
    "minSampleSize": 100,
    "continuousRecheck": false,
    "override": {
      "approvedBy": "compliance@example.com",
      "expiresAt": "2026-06-01T00:00:00Z"
    }
  }
}
```

| Field                             | Default | Purpose                                                                                                                                                                                           |
| --------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `enabled`                         | `false` | Master switch. When unset/false the gate is a no-op.                                                                                                                                              |
| `sensitiveAttribute`              | —       | The protected-attribute key the gate looks up — primarily in `decision_trace.requestAttributes`, falling back to `qualificationResults[*].context.attributes` for pre-migration traces.           |
| `thresholds.disparateImpactRatio` | `0.8`   | Four-fifths rule (29 CFR § 1607.4D). Below this → block.                                                                                                                                          |
| `thresholds.demographicParityGap` | `0.2`   | Max allowed `maxRate − minRate` across groups. Above this → block.                                                                                                                                |
| `thresholds.equalOpportunityGap`  | `0.2`   | Max allowed TPR gap when ground-truth labels are present.                                                                                                                                         |
| `thresholds.giniCoefficient`      | `0.2`   | Max allowed Gini coefficient of decision-rate concentration across groups (OECD band: `<0.10` excellent, `0.10–0.20` acceptable, `0.20–0.40` monitor/caution, `>0.40` alert). Above this → block. |
| `minSampleSize`                   | `100`   | Skip the gate (not block) when fewer than this many traces in the last 7 days carry the sensitive attribute.                                                                                      |
| `continuousRecheck`               | `false` | When `true`, the `/api/v1/cron/fairness-recheck` sweep re-runs this gate on live traffic and auto-pauses active flows on breach (audit-logged). The pre-publish gate runs regardless.             |
| `override`                        | —       | Four-eyes bypass: `{ approvedBy, expiresAt }`. Active overrides skip the gate (audit-logged).                                                                                                     |

### Behavior

* **Not configured / disabled** — gate is a no-op, publish proceeds.
* **Active override (not expired)** — gate is skipped, publish proceeds, audit log records the bypass.
* **Insufficient samples** — gate is skipped (`enforced: false`, reason explains `samples < minSampleSize`).
* **Thresholds breached** — publish blocked with 422:
  ```json theme={null}
  {
    "title": "Fairness gate blocked publish",
    "violations": [
      "disparate impact ratio 0.612 < threshold 0.8",
      "demographic parity gap 0.351 > threshold 0.2"
    ],
    "metrics": { "sampleSize": 1840, "disparateImpactRatio": 0.612, ... },
    "override": "To bypass, an admin can set tenant.settings.fairnessPolicy.override = { approvedBy, expiresAt } via four-eyes governance."
  }
  ```
* **Infrastructure error** — fail-open (publish proceeds, warning logged) so a transient DB blip doesn't block legitimate compliance work.

### Sample-source caveats

The gate derives the protected group from
`decision_trace.requestAttributes[sensitiveAttribute]` (the canonical
source), falling back to
`qualificationResults[*].context.attributes[sensitiveAttribute]` for
traces persisted before the request-attributes snapshot migration. A
trace counts as a positive decision when it selected at least one offer.
If fewer than `minSampleSize` of the last 7 days of traces carry the
sensitive attribute, the gate skips with an "insufficient samples" reason.

Backed by the platform's fairness hard-gate enforcement helper.

***

## Tiered fairness evaluation

`POST /api/v1/fairness/evaluate?metrics=basic|advanced` runs the full
fairness pipeline. The query string controls which metrics tier is
returned.

### Basic tier (default)

Existing demographic-parity, four-fifths-rule, equal-opportunity,
and equalized-odds gap calculations from `lib/fairness/metrics.ts`.
Unchanged behavior — every existing caller is bit-identical.

### Advanced tier

Adds intersectional analysis + mitigation recommendations from
`lib/fairness/advanced.ts`:

* **Intersectional cells** require per-sample
  `intersectionalGroups: { axisName: groupValue }`. The route
  runs the intersectional evaluator with a default minimum cell
  size of 10 samples and surfaces the cells plus the worst
  disparate-impact ratio.
* **Mitigation recommendations** are derived from the report shape
  (DI ratio, four-fifths violation, equal-opportunity gap) — no
  extra inputs needed.
* **Gini-by-group** (`giniByGroup`) — the Gini coefficient of each
  group's binary decision distribution (computed for groups with more
  than one sample).
* **KS-by-group** (`ksByGroup`) — a two-sample Kolmogorov–Smirnov test
  of each non-reference group's decision distribution against the first
  group's. Both are returned automatically in the advanced tier — no
  `modelKey` required.

When the caller asks for advanced but doesn't supply
`intersectionalGroups`, the response includes
`advancedAwaitingConfig: ["intersectional: no per-sample intersectionalGroups supplied"]`
so operators know why the analysis is empty. **No silent fallback.**

### Auto-run counterfactual fairness + LIME (needs a `modelKey`)

When `metrics=advanced` **and** the body carries a `modelKey`, the route
resolves a real scorer via `resolveScorerForFairness` (V1 supports
`gradient_boosted` only) and auto-runs two more primitives on top of the
basic tier:

* **`lime`** — runs `computeLime` against the scorer when a `limeBaseline`
  attribute set is supplied. Fairness-route LIME defaults to `200`
  samples (override via `limeOptions.samples`).
* **`counterfactualFairness`** — runs `evaluateCounterfactualFairness`
  when the request is `mode: "inline"` and supplies `modelGroupKey` +
  `counterfactualGroup`, with per-sample `attributes` to score.

Both blocks are best-effort: if the model can't be resolved, or the
required inputs are missing, the reason surfaces in
`advancedAwaitingConfig` and the basic tier still returns. Individual-
fairness (Lipschitz) and the DeLong AUC comparison also live in
`lib/fairness/advanced.ts` but are **not** auto-run from this route —
callers invoke them directly.

## EU AI Act report

`POST /api/v1/fairness/report` runs the same fairness pipeline and
returns a formatted report:

| `format` | Content type | Notes                                                                   |
| -------- | ------------ | ----------------------------------------------------------------------- |
| `csv`    | `text/csv`   | Per-group + summary metrics, suitable for compliance archive ingestion. |
| `html`   | `text/html`  | Markup-only; pipe through headless Chromium / wkhtmltopdf for PDF.      |

Body shape mirrors `/evaluate`. Optional `title` + `subtitle`
override the defaults ("Fairness Assessment Report" / "EU AI Act
Article 10 § 2(f)").

### Audit trail

Every call to `/evaluate` and `/report` writes one audit-log row
(`action: fairness_evaluate` or `fairness_report`) so DSAR exports
can cite the exact report contents.
