feat(profiler/behave_shell): emit motor.keystroke_cadence
BEHAVE-EXTRACTOR.md Phase B Step B.1.
* SessionContext gains typing_bursts: tuple[tuple[float, ...], ...]
built by _split_typing_bursts(iats) — splits at gaps > IKI_THINK_MAX_S
(1.5s) and drops bursts of fewer than 3 IATs. Mirrors prototype's
_split_into_bursts at BEHAVE/prototype_extractors/shell/extract.py:275.
* _features/motor.py:keystroke_cadence(ctx) emits one Observation
in {steady, bursty, hunt_and_peck, machine}. Median CV across
typing bursts; mean IKI < IKI_MACHINE_MAX_S paired with CV <
CV_MACHINE_MAX → machine. Confidence 0.85/0.70/0.65/0.60 per the
prototype's calibration history.
* < MIN_INPUTS_FOR_CADENCE inputs or zero typing bursts → skip
emission. v0.1 emits only the burst-CV variant; the prototype's
NAIVE session-CV variant is parked for v0.2.
* Calibration grid widened (PHASE_A_PRIMITIVES → PHASE_AB_PRIMITIVES)
to include motor.keystroke_cadence. Grid green across all five
shards.
Tests: too-few-inputs → no emit, all-think-pauses → no burst → no
emit, uniform IATs → steady, sub-5ms → machine, mixed-pace → bursty,
extreme bimodal → hunt_and_peck.
This commit is contained in:
@@ -19,6 +19,7 @@ from decnet.profiler.behave_shell._features.cognitive import (
|
||||
)
|
||||
from decnet.profiler.behave_shell._features.motor import (
|
||||
input_modality,
|
||||
keystroke_cadence,
|
||||
paste_burst_rate,
|
||||
)
|
||||
|
||||
@@ -27,6 +28,7 @@ FeatureFn = Callable[[SessionContext], Iterable[Observation]]
|
||||
FEATURES: tuple[FeatureFn, ...] = (
|
||||
input_modality,
|
||||
paste_burst_rate,
|
||||
keystroke_cadence,
|
||||
inter_command_latency_class,
|
||||
command_branch_diversity,
|
||||
feedback_loop_engagement,
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
Step 2: ``motor.input_modality`` — typed / pasted / mixed.
|
||||
Step 3: ``motor.paste_burst_rate`` — none / occasional / habitual.
|
||||
Step B.1: ``motor.keystroke_cadence`` — steady / bursty / hunt_and_peck / machine.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import statistics
|
||||
from itertools import chain
|
||||
from typing import Iterator
|
||||
|
||||
from decnet_behave_core.spec.envelope import Observation
|
||||
@@ -12,6 +15,11 @@ from decnet_behave_core.spec.envelope import Observation
|
||||
from decnet.profiler.behave_shell._ctx import SessionContext
|
||||
from decnet.profiler.behave_shell._features._emit import make_observation
|
||||
from decnet.profiler.behave_shell._thresholds import (
|
||||
CV_BURSTY_MAX,
|
||||
CV_MACHINE_MAX,
|
||||
CV_STEADY_MAX,
|
||||
IKI_MACHINE_MAX_S,
|
||||
MIN_INPUTS_FOR_CADENCE,
|
||||
MODALITY_PASTED_MIN,
|
||||
MODALITY_TYPED_MAX,
|
||||
PASTE_RATE_HABITUAL_MIN,
|
||||
@@ -76,3 +84,45 @@ def paste_burst_rate(ctx: SessionContext) -> Iterator[Observation]:
|
||||
value=level,
|
||||
confidence=confidence,
|
||||
)
|
||||
|
||||
|
||||
def keystroke_cadence(ctx: SessionContext) -> Iterator[Observation]:
|
||||
"""Emit ``motor.keystroke_cadence`` ∈ {steady, bursty, hunt_and_peck, machine}.
|
||||
|
||||
Median CV of within-typing-burst IATs (bursts split at gaps >
|
||||
``IKI_THINK_MAX_S`` so think-pauses between commands don't
|
||||
inflate the variance). Pasted-only sessions and sessions below
|
||||
``MIN_INPUTS_FOR_CADENCE`` skip emission — no honest cadence
|
||||
available.
|
||||
|
||||
v0.1 emits only the burst-CV variant. The prototype's NAIVE
|
||||
session-CV variant (lower confidence, second emission per
|
||||
primitive) is parked for v0.2.
|
||||
"""
|
||||
if len(ctx.input_events) < MIN_INPUTS_FOR_CADENCE:
|
||||
return
|
||||
if not ctx.typing_bursts:
|
||||
return
|
||||
burst_cvs: list[float] = []
|
||||
for b in ctx.typing_bursts:
|
||||
m = statistics.fmean(b)
|
||||
if m > 0:
|
||||
burst_cvs.append(statistics.pstdev(b) / m)
|
||||
if not burst_cvs:
|
||||
return
|
||||
cv = statistics.median(burst_cvs)
|
||||
mean_iki = statistics.fmean(chain.from_iterable(ctx.typing_bursts))
|
||||
if mean_iki < IKI_MACHINE_MAX_S and cv < CV_MACHINE_MAX:
|
||||
value, confidence = "machine", 0.85
|
||||
elif cv < CV_STEADY_MAX:
|
||||
value, confidence = "steady", 0.70
|
||||
elif cv < CV_BURSTY_MAX:
|
||||
value, confidence = "bursty", 0.65
|
||||
else:
|
||||
value, confidence = "hunt_and_peck", 0.60
|
||||
yield make_observation(
|
||||
ctx,
|
||||
primitive="motor.keystroke_cadence",
|
||||
value=value,
|
||||
confidence=confidence,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user