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

# Metric Definitions

> Formal definitions of the BehavioralMetrics fields and the formulas used to compute them

The behavior SDK does not compute these metrics in Dart/Kotlin/Swift directly. The canonical implementation runs inside the runtime's behavior feature deriver — the SDK ships events and the runtime returns metric bundles. Apps see the results as fields on `BehavioralMetrics` inside `BehaviorSessionSummary`.

## Window

A metric is computed either:

* Over the **entire session** (default, returned in `BehaviorSessionSummary.behavioralMetrics`).
* Over an **arbitrary time range** when you call `calculateMetricsForTimeRange({startTimestampSeconds, endTimestampSeconds, sessionId?})`.

When the window contains no events, every metric returns `0.0` and lists return empty.

## The canonical 12

### `interactionIntensity`

```text theme={null}
non_typing_interaction = total_events − interruption_events − typing_events
typing_equivalent      = total_typing_duration_sec / 10        // 10s typing = 1 "event"
total_interaction      = non_typing_interaction + typing_equivalent
events_per_minute      = total_interaction / session_duration_sec * 60
interaction_intensity  = clamp01(events_per_minute / 10)
```

`interruption_events = notification_events + call_events + app_switch_events`. Range `[0, 1]`. Anchored so \~10 events/min reads as full intensity.

### `taskSwitchRate`

Exponential saturation:

```text theme={null}
task_switch_rate = clamp01( 1 − exp(−app_switches_per_min / 2.0) )
```

2 switches/min ≈ 0.63; high rate approaches 1.

### `taskSwitchCost`

```text theme={null}
task_switch_cost_ms = clamp(0, 10_000, duration_ms / app_switch_count)   // 0 if no switches
task_switch_cost    = task_switch_cost_ms / 10_000                       // normalised 0..1
```

In the SDK, `BehavioralMetrics.taskSwitchCost` is the **millisecond** form (raw `int`); flux internally also exposes a normalised `[0, 1]` version.

### `idleTimeRatio`

```text theme={null}
idle_time_ratio = clamp01( total_idle_time_sec / session_duration_sec )
```

Idle gaps are counted from the SDK's idle-segment detector (gaps above the configured threshold).

### `activeTimeRatio`

```text theme={null}
active_interaction_time_ms = duration_ms − total_idle_time_ms − task_switch_cost_ms
active_time_ratio          = clamp01( active_interaction_time_ms / duration_ms )
```

Note: this is **not** simply `1 − idle_time_ratio` — task-switch cost is also subtracted.

### `notificationLoad`

Exponential saturation:

```text theme={null}
notification_load = clamp01( 1 − exp(−notifications_per_min / 1.0) )
```

1 notification/min ≈ 0.63.

### `burstiness`

Barabási burstiness over inter-event gaps:

```text theme={null}
B   = (σ − μ) / (σ + μ)         // σ = stddev, μ = mean of inter-event gaps
burstiness = clamp01( (B + 1) / 2 )
```

`0.0` = perfectly regular (Poisson-like), `0.5` = random / no data, `1.0` = very bursty. Flux returns `0.5` when the session has no inter-event gaps yet.

### `behavioralDistractionScore`

Weighted composite, fixed weights:

```text theme={null}
distraction_score = clamp01(
    0.35 * task_switch_rate
  + 0.30 * notification_load
  + 0.20 * fragmented_idle_ratio
  + 0.15 * scroll_jitter_rate
)
```

The four weights are constants in flux; they are not configurable per session.

### `focusHint`

```text theme={null}
focus_hint = 1.0 − distraction_score
```

By construction, `focusHint + distractionScore = 1.0`. (Flux's test `test_focus_hint_is_inverse_of_distraction` enforces this.)

### `fragmentedIdleRatio`

```text theme={null}
fragmented_idle_ratio = max(0, idle_segment_count / session_duration_sec)
```

Note: the raw ratio can exceed `1.0` on very short sessions; flux clamps at the encoding step before HSI export.

### `scrollJitterRate`

```text theme={null}
scroll_jitter_rate = clamp01( direction_reversals / (scroll_events − 1) )
                    if scroll_events > 1, else 0
```

A direction reversal counts when scrolling flips between up and down.

### `deepFocusBlocks`

Engagement segments lasting at least **`DEEP_FOCUS_MIN_DURATION_SEC = 120` seconds (2 minutes)**, fixed in flux. Each block in the SDK shape:

```dart theme={null}
class DeepFocusBlock {
  final String startAt;     // ISO 8601 timestamp
  final String endAt;       // ISO 8601 timestamp
  final int durationMs;     // duration in milliseconds
}
```

Flux internally returns just the count; the SDK enriches it with timing detail when constructing `BehaviorSessionSummary`.

## Typing metrics (extension)

When a session contains typing events, the SDK adds `TypingMetrics` derived from typing-specific aggregations (formulas live in flux's typing module):

| Field                        | JSON key                       | Range / units |
| ---------------------------- | ------------------------------ | ------------- |
| `typingSpeed`                | `typing_speed`                 | taps/sec      |
| `typingCadenceStability`     | `typing_cadence_stability`     | 0–1           |
| `typingCadenceVariability`   | `typing_cadence_variability`   | unitless (CV) |
| `typingActivityRatio`        | `typing_activity_ratio`        | 0–1           |
| `typingGapRatio`             | `typing_gap_ratio`             | 0–1           |
| `typingBurstiness`           | `typing_burstiness`            | 0–1           |
| `typingInteractionIntensity` | `typing_interaction_intensity` | 0–1           |
| `clipboardActivityRate`      | `clipboard_activity_rate`      | 0–1           |
| `correctionRate`             | `correction_rate`              | 0–1           |

A few are defined as composite ratios (commented in `behavior_gesture_detector.dart`):

* `typing_interaction_intensity = w₁ · typing_speed_norm + w₂ · (1 − typing_gap_ratio) + w₃ · typing_cadence_stability` (weights set in the runtime feature deriver).
* `clipboard_activity_rate = (copy + paste + cut) / (typing_taps + clipboard_actions)`.
* `correction_rate = (backspace + delete) / (typing_taps + backspace + delete)`.

## NotificationSummary

| Field                         | Definition                                                                       |
| ----------------------------- | -------------------------------------------------------------------------------- |
| `notificationCount`           | Total notifications received during session.                                     |
| `notificationIgnored`         | Notifications with `InterruptionAction.ignored`.                                 |
| `notificationIgnoreRate`      | `notificationIgnored / notificationCount` (0 when no notifications).             |
| `notificationClusteringIndex` | Variance-based clustering measure (Fano-factor flavor) over inter-arrival times. |
| `callCount`, `callIgnored`    | Same shape for incoming calls.                                                   |

## Numerical stability

Flux clamps:

* All bounded outputs are wrapped in `.clamp(0.0, 1.0)` (or per-field bounds for raw mileage values like `task_switch_cost_ms` capped at 10000).
* Empty input (no events, zero duration, no scrolls, etc.) returns the safe fallback (`0.0` for ratios, `0.5` for burstiness).

If a metric ever surfaces outside its declared range, it's a bug in flux — not a value to interpret.

## Related

* [Behavior Overview](/synheart-behavior/overview) — full SDK surface.
* [Model Card](/synheart-behavior/model-card) — motion state, separate from these metrics.
* [Threat Model](/synheart-behavior/threat-model) — what's collected and what's not.
