Skip to content

feat: live decision rules (DecisionEngine + UI)#69

Merged
roiguri merged 10 commits into
devfrom
decisions
Jul 16, 2026
Merged

feat: live decision rules (DecisionEngine + UI)#69
roiguri merged 10 commits into
devfrom
decisions

Conversation

@roiguri

@roiguri roiguri commented Jul 14, 2026

Copy link
Copy Markdown
Owner

@

Summary

Adds the live decision-rules stack on top of the raw probability stream: a DecisionEngine that turns per-decoder probabilities into sustained on/off decisions, session logging of dense decisions + config timeline, wiring into the live stream, and the Phase 2 UI (live decision panel + settings controls with a threshold line on the chart).

Commits (10)

  • debfc5c docs(plan): decision rules for live decoding
  • 9feeb95 feat(online): decision primitives — config, threshold, sustain gate
  • f66b6d0 feat(online): DecisionEngine batched per-decoder decisions
  • 65e33df feat(online): log dense decisions + config timeline in LiveSessionLogger
  • 0360d6c refactor(online): single global threshold (drop per-decoder override map)
  • 4ded0ac refactor(online): measure sustain/release in timepoints, drop frequency
  • c301632 chore(online): default decision settings to threshold 0.80, sustain 3 timepoints
  • 34449b1 feat(online): wire DecisionEngine as a sibling consumer of prediction_ready
  • 14f9840 feat(frontend): live decision panel (Phase C1)
  • 59368e5 feat(frontend): decision settings controls + live threshold line (Phase C4)

Notes

  • Fast-forward merge relative to dev (no divergence, no conflicts).
  • Backend: decision_engine.py, decision_binding.py, extended session_logger.py + session.py.
  • Frontend: decision_panel.py, extended settings_panel.py, phase2_screen.py, live_probability_chart.py.
  • Tests added for engine, session logger, live stream, decision panel, and settings.
    @

roiguri added 10 commits July 9, 2026 11:43
Per-decoder independent activation decisions (threshold + sustain latch,
multi-active) over the Phase 2 probability stream. Dense decisions.csv +
decision_config.jsonl timeline; engine as a sibling consumer of
prediction_ready; apply-gated, thread-safe live config changes. Hardcoded
defaults first, optional YAML override last.
Pure-Python building blocks for the per-decoder decision layer (plan A1):
DecisionConfig (hardcoded defaults + per-decoder threshold fallback),
ThresholdCriterion (instantaneous gate), and SustainGate (per-decoder,
independent both-edge debounce latch). No Qt, no I/O.
process_batch consumes a prediction_ready batch and threads SustainGate state
across boundaries, returning a DecisionResult of per-sample latches (plan A2).
set_pending_config stages a config that the next batch applies at its boundary
— bumping the version, resetting counters, keeping latches, and stamping a
ConfigChange with the batch's first timestamp for the logger.
Opt-in decision logging (plan A3): on_decisions appends a dense decisions.csv
(one bool column per decoder + config_version) and, on a config change carried
in the result, a full snapshot to decision_config.jsonl — version 0 written at
construction, manifest gains decision_schema_version/initial_config/n_samples.
Adds episodes_from_decisions to recover on/off intervals by diffing the columns.
…map)

Decisions use one global threshold shared by every decoder — decoders still
latch independently via the sustain gate, they just share the threshold value.
Removes DecisionConfig.thresholds / threshold_for and the per-decoder expansion
in the logged config (now {"threshold": ...}). Simpler config, one chart line,
one settings control; a per-decoder threshold was unused clutter.
Predictions and decisions are already per-timepoint, so seconds + target_sfreq
was an unnecessary round-trip. DecisionConfig now carries integer
sustain_timepoints/release_timepoints (counts of predictions); the engine drops
target_sfreq and the seconds_to_samples conversion, counting timepoints
directly. Removes the rounding/step-size ambiguity of a seconds field.
…_ready

Phase B: DecisionBinding, a thin QObject wrapping the pure engine, joins the
pipeline like the logger does — connected to worker.prediction_ready
(DirectConnection), re-emitting each batch's DecisionResult on decision_ready.
StreamWorker is untouched. AppSession builds the engine from hardcoded defaults
(target_sfreq from the preprocessor) and, when logging, seeds the logger's
version-0 config and connects decision_ready -> on_decisions. LiveStreamSession
owns the binding, resets it on start(), forwards decision_ready, and exposes
update_decision_config(). Decisions are computed on every run, logged or not.
DecisionPanel: a single row of per-decoder status tiles (name · latest
probability · ACTIVE/IDLE). An active tile is highlighted in the decoder's own
colour; idle tiles are muted. Multiple tiles light at once — the multi-active
model made visible. Phase2Screen wires live.decision_ready -> _on_decision
(QueuedConnection) for on/off and feeds probabilities from the prediction
stream; resets on Start. DecisionResult is read duck-typed (no backend import).
…se C4)

Replaces the sidebar placeholder with apply-gated decision settings: a threshold
slider + editable spinbox (synced), an integer sustain-timepoints stepper, and
Apply/Reset (enabled only while the draft differs from the applied config, with
distinct enabled/disabled styling and horizontal -/+ steppers). Apply moves both
chart threshold lines (live + event-locked) and, when a run is live, stages the
new config on the engine; the values also seed the next run built from Start.

Contract kept: the panel emits plain {threshold, sustain_timepoints}; AppSession
builds the DecisionConfig (decision_config_defaults / update_decision_config /
build decision_params). Chart/FrozenEventChart gain set_threshold. Folds in C2
(live threshold line). C3 history strip stays deferred.
@roiguri roiguri changed the title Decisions feat: live decision rules (DecisionEngine + UI) Jul 16, 2026
@roiguri roiguri merged commit 2fedbe6 into dev Jul 16, 2026
@roiguri roiguri deleted the decisions branch July 16, 2026 08:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant