feat(profiler/behave_shell): emit motor.error_correction
BEHAVE-EXTRACTOR.md Phase B Step B.3. Replaces the prototype's
two-line "0 vs >0 backspaces" placeholder with a backspace-timing
classifier that honours the registry's full vocabulary.
* SessionContext gains backspace_count, backspace_iats (IAT from
each backspace back to the preceding non-backspace input event),
and kill_line_count (^U / ^W). Built by _scan_correction_signals,
which retains only counts and timing aggregates — no character
data leaves the helper, in line with the BEHAVE PII discipline.
* _features/motor.py:error_correction(ctx) emits one Observation
in {immediate, deferred, absent, route_around}.
- 0 backspaces + ≥1 ^U/^W → route_around (rewrite, not correct)
- 0 backspaces + 0 kill-lines → absent
- backspaces with median IAT ≤ 500 ms → immediate
- slower → deferred
Confidence 0.65 / 0.65 / 0.55 / 0.55.
* < 3 inputs → skip emit.
* Calibration grid widened to include motor.error_correction;
green across all five shards.
Tests cover all four buckets, the < 3 inputs skip, and the PII
regression (raw command body never appears in the serialised
observation).
This commit is contained in:
@@ -18,6 +18,7 @@ from decnet.profiler.behave_shell._features.cognitive import (
|
||||
inter_command_latency_class,
|
||||
)
|
||||
from decnet.profiler.behave_shell._features.motor import (
|
||||
error_correction,
|
||||
input_modality,
|
||||
keystroke_cadence,
|
||||
motor_stability,
|
||||
@@ -31,6 +32,7 @@ FEATURES: tuple[FeatureFn, ...] = (
|
||||
paste_burst_rate,
|
||||
keystroke_cadence,
|
||||
motor_stability,
|
||||
error_correction,
|
||||
inter_command_latency_class,
|
||||
command_branch_diversity,
|
||||
feedback_loop_engagement,
|
||||
|
||||
@@ -15,6 +15,7 @@ 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 (
|
||||
BACKSPACE_IMMEDIATE_MAX_S,
|
||||
CV_BURSTY_MAX,
|
||||
CV_MACHINE_MAX,
|
||||
CV_STEADY_MAX,
|
||||
@@ -166,3 +167,41 @@ def motor_stability(ctx: SessionContext) -> Iterator[Observation]:
|
||||
value=value,
|
||||
confidence=confidence,
|
||||
)
|
||||
|
||||
|
||||
def error_correction(ctx: SessionContext) -> Iterator[Observation]:
|
||||
"""Emit ``motor.error_correction`` ∈ {immediate, deferred, absent, route_around}.
|
||||
|
||||
Backspace timing relative to the preceding non-backspace key:
|
||||
|
||||
* 0 backspaces + ≥1 ^U/^W → ``route_around`` (operator killed
|
||||
the line and rewrote rather than correcting in place).
|
||||
* 0 backspaces + 0 ^U/^W → ``absent`` (no correction observed).
|
||||
* Backspaces with median IAT ≤ ``BACKSPACE_IMMEDIATE_MAX_S``
|
||||
(500 ms) → ``immediate`` (caught the typo mid-keystroke).
|
||||
* Slower → ``deferred`` (paused, noticed, then went back).
|
||||
|
||||
< 3 input events → skip emission.
|
||||
"""
|
||||
if len(ctx.input_events) < 3:
|
||||
return
|
||||
if ctx.backspace_count == 0:
|
||||
if ctx.kill_line_count > 0:
|
||||
value, confidence = "route_around", 0.55
|
||||
else:
|
||||
value, confidence = "absent", 0.65
|
||||
else:
|
||||
if ctx.backspace_iats:
|
||||
med = statistics.median(ctx.backspace_iats)
|
||||
else:
|
||||
med = float("inf")
|
||||
if med <= BACKSPACE_IMMEDIATE_MAX_S:
|
||||
value, confidence = "immediate", 0.65
|
||||
else:
|
||||
value, confidence = "deferred", 0.55
|
||||
yield make_observation(
|
||||
ctx,
|
||||
primitive="motor.error_correction",
|
||||
value=value,
|
||||
confidence=confidence,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user