traceforge-toolkit 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- traceforge/__init__.py +72 -0
- traceforge/__main__.py +5 -0
- traceforge/_generated.py +254 -0
- traceforge/adapters/__init__.py +12 -0
- traceforge/adapters/base.py +82 -0
- traceforge/adapters/genai_otel.py +164 -0
- traceforge/adapters/mapped_json.py +297 -0
- traceforge/adapters/otel.py +220 -0
- traceforge/boundary/__init__.py +46 -0
- traceforge/boundary/data/boundary-model.joblib +0 -0
- traceforge/boundary/decode.py +87 -0
- traceforge/boundary/features.py +137 -0
- traceforge/boundary/inference.py +267 -0
- traceforge/boundary/inferencer.py +146 -0
- traceforge/classify/__init__.py +95 -0
- traceforge/classify/cmd.py +109 -0
- traceforge/classify/coding.py +213 -0
- traceforge/classify/config.py +554 -0
- traceforge/classify/core.py +266 -0
- traceforge/classify/data/binary_info.yaml +358 -0
- traceforge/classify/data/canonical_tools.yaml +251 -0
- traceforge/classify/data/effect_overrides.yaml +480 -0
- traceforge/classify/data/mcp_profiles.yaml +711 -0
- traceforge/classify/data/recommendation_rules.yaml +111 -0
- traceforge/classify/data/risk.yaml +534 -0
- traceforge/classify/data/shell_defaults.yaml +95 -0
- traceforge/classify/data/shell_rules.yaml +1192 -0
- traceforge/classify/data/tool_classifications.yaml +215 -0
- traceforge/classify/data/verb_inference.yaml +218 -0
- traceforge/classify/mcp.py +181 -0
- traceforge/classify/phases.py +75 -0
- traceforge/classify/powershell.py +93 -0
- traceforge/classify/registry.py +138 -0
- traceforge/classify/risk.py +553 -0
- traceforge/classify/rules.py +215 -0
- traceforge/classify/schema.yaml +282 -0
- traceforge/classify/shell.py +503 -0
- traceforge/classify/tools.py +91 -0
- traceforge/classify/workflow.py +32 -0
- traceforge/cli/__init__.py +42 -0
- traceforge/cli/config_cmd.py +130 -0
- traceforge/cli/detect.py +46 -0
- traceforge/cli/download_cmd.py +74 -0
- traceforge/cli/factory.py +60 -0
- traceforge/cli/gate_cmd.py +30 -0
- traceforge/cli/init_cmd.py +86 -0
- traceforge/cli/replay.py +130 -0
- traceforge/cli/runner.py +181 -0
- traceforge/cli/score.py +217 -0
- traceforge/cli/status.py +65 -0
- traceforge/cli/watch.py +297 -0
- traceforge/config/__init__.py +80 -0
- traceforge/config/defaults.py +149 -0
- traceforge/config/loader.py +239 -0
- traceforge/config/mappings.py +104 -0
- traceforge/config/models.py +579 -0
- traceforge/enricher.py +576 -0
- traceforge/formatting/__init__.py +12 -0
- traceforge/formatting/budget.py +92 -0
- traceforge/formatting/density.py +154 -0
- traceforge/gate/__init__.py +17 -0
- traceforge/gate/client.py +145 -0
- traceforge/gate/external.py +305 -0
- traceforge/gate/registry.py +108 -0
- traceforge/gate/server.py +174 -0
- traceforge/gates/__init__.py +8 -0
- traceforge/gates/pii.py +352 -0
- traceforge/gates/pii_patterns.yaml +228 -0
- traceforge/governance/__init__.py +121 -0
- traceforge/governance/assessor.py +441 -0
- traceforge/governance/budget.py +59 -0
- traceforge/governance/canonical.py +56 -0
- traceforge/governance/codec.py +414 -0
- traceforge/governance/context.py +249 -0
- traceforge/governance/drift.py +196 -0
- traceforge/governance/emitter.py +234 -0
- traceforge/governance/envelope.py +138 -0
- traceforge/governance/ifc.py +364 -0
- traceforge/governance/integrity.py +162 -0
- traceforge/governance/labeler.py +250 -0
- traceforge/governance/mcp_drift.py +328 -0
- traceforge/governance/monitor.py +539 -0
- traceforge/governance/observer.py +276 -0
- traceforge/governance/persistence.py +401 -0
- traceforge/governance/phase1.py +77 -0
- traceforge/governance/pii.py +276 -0
- traceforge/governance/pipeline.py +840 -0
- traceforge/governance/registry.py +110 -0
- traceforge/governance/results.py +183 -0
- traceforge/governance/risk_wrapper.py +113 -0
- traceforge/governance/rules.py +368 -0
- traceforge/governance/scorer.py +262 -0
- traceforge/governance/shield.py +318 -0
- traceforge/governance/state.py +466 -0
- traceforge/governance/types.py +176 -0
- traceforge/mappings/__init__.py +1 -0
- traceforge/mappings/aider.yaml +216 -0
- traceforge/mappings/aider_markdown.yaml +113 -0
- traceforge/mappings/amazonq.yaml +56 -0
- traceforge/mappings/antigravity.yaml +88 -0
- traceforge/mappings/claude.yaml +93 -0
- traceforge/mappings/cline.yaml +286 -0
- traceforge/mappings/codex.yaml +158 -0
- traceforge/mappings/continue_dev.yaml +57 -0
- traceforge/mappings/copilot.yaml +266 -0
- traceforge/mappings/copilot_markdown.yaml +72 -0
- traceforge/mappings/copilot_vscode.yaml +181 -0
- traceforge/mappings/crewai.yaml +592 -0
- traceforge/mappings/goose.yaml +128 -0
- traceforge/mappings/langgraph.yaml +165 -0
- traceforge/mappings/maf.yaml +90 -0
- traceforge/mappings/maf_transcript.yaml +99 -0
- traceforge/mappings/openai_agents.yaml +144 -0
- traceforge/mappings/opencode.yaml +473 -0
- traceforge/mappings/openhands.yaml +320 -0
- traceforge/mappings/pydantic_ai.yaml +183 -0
- traceforge/mappings/smolagents.yaml +89 -0
- traceforge/mappings/sweagent.yaml +82 -0
- traceforge/migrations/__init__.py +1 -0
- traceforge/migrations/env.py +60 -0
- traceforge/migrations/models.py +145 -0
- traceforge/migrations/runner.py +44 -0
- traceforge/migrations/script.py.mako +25 -0
- traceforge/migrations/versions/0001_initial.py +176 -0
- traceforge/migrations/versions/__init__.py +4 -0
- traceforge/models.py +29 -0
- traceforge/parsers/__init__.py +21 -0
- traceforge/parsers/aider.py +416 -0
- traceforge/parsers/base.py +226 -0
- traceforge/parsers/copilot.py +314 -0
- traceforge/phase/__init__.py +50 -0
- traceforge/phase/data/phase-model.joblib +0 -0
- traceforge/phase/data/potion-base-8M/README.md +99 -0
- traceforge/phase/data/potion-base-8M/config.json +13 -0
- traceforge/phase/data/potion-base-8M/model.safetensors +0 -0
- traceforge/phase/data/potion-base-8M/modules.json +14 -0
- traceforge/phase/data/potion-base-8M/tokenizer.json +1 -0
- traceforge/phase/event_rows.py +107 -0
- traceforge/phase/features.py +468 -0
- traceforge/phase/inference.py +279 -0
- traceforge/phase/inferencer.py +171 -0
- traceforge/phase/segmentation.py +258 -0
- traceforge/pipeline.py +891 -0
- traceforge/preprocessors/__init__.py +34 -0
- traceforge/preprocessors/amazonq.py +224 -0
- traceforge/preprocessors/antigravity.py +116 -0
- traceforge/preprocessors/claude.py +95 -0
- traceforge/preprocessors/cline.py +36 -0
- traceforge/preprocessors/codex.py +311 -0
- traceforge/preprocessors/continue_dev.py +119 -0
- traceforge/preprocessors/copilot_vscode.py +171 -0
- traceforge/preprocessors/goose.py +156 -0
- traceforge/preprocessors/maf_transcript.py +84 -0
- traceforge/preprocessors/openai_agents.py +86 -0
- traceforge/preprocessors/opencode.py +85 -0
- traceforge/preprocessors/openhands.py +36 -0
- traceforge/preprocessors/pydantic_ai.py +62 -0
- traceforge/preprocessors/registry.py +24 -0
- traceforge/preprocessors/smolagents.py +90 -0
- traceforge/py.typed +0 -0
- traceforge/sdk/__init__.py +59 -0
- traceforge/sdk/gate_policy.py +63 -0
- traceforge/sdk/gate_types.py +140 -0
- traceforge/sdk/pipeline.py +265 -0
- traceforge/sdk/verdict.py +81 -0
- traceforge/sinks/__init__.py +23 -0
- traceforge/sinks/base.py +95 -0
- traceforge/sinks/callback.py +132 -0
- traceforge/sinks/console.py +112 -0
- traceforge/sinks/factory.py +94 -0
- traceforge/sinks/jsonl.py +125 -0
- traceforge/sinks/otel_exporter.py +212 -0
- traceforge/sinks/parquet.py +260 -0
- traceforge/sinks/s3.py +206 -0
- traceforge/sinks/sqlite_output.py +234 -0
- traceforge/sinks/webhook.py +136 -0
- traceforge/sources/__init__.py +18 -0
- traceforge/sources/auto_detect.py +173 -0
- traceforge/sources/base.py +45 -0
- traceforge/sources/file_poll.py +136 -0
- traceforge/sources/file_watch.py +221 -0
- traceforge/sources/http_poll.py +141 -0
- traceforge/sources/replay.py +63 -0
- traceforge/sources/sqlite.py +187 -0
- traceforge/sources/sse.py +198 -0
- traceforge/telemetry/__init__.py +192 -0
- traceforge/title/__init__.py +23 -0
- traceforge/title/_resolve.py +79 -0
- traceforge/title/context.py +295 -0
- traceforge/title/data/boilerplate_files.json +14 -0
- traceforge/title/heuristics.py +429 -0
- traceforge/title/hygiene.py +90 -0
- traceforge/title/inference.py +314 -0
- traceforge/title/inferencer.py +477 -0
- traceforge/title/naming.py +398 -0
- traceforge/trace.py +291 -0
- traceforge/tracking/__init__.py +30 -0
- traceforge/tracking/models.py +115 -0
- traceforge/tracking/phase_tracker.py +288 -0
- traceforge/types.py +315 -0
- traceforge_toolkit-0.1.0.dist-info/METADATA +188 -0
- traceforge_toolkit-0.1.0.dist-info/RECORD +205 -0
- traceforge_toolkit-0.1.0.dist-info/WHEEL +4 -0
- traceforge_toolkit-0.1.0.dist-info/entry_points.txt +2 -0
- traceforge_toolkit-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Classical segmentation detectors as features (research/docs/03 §6).
|
|
2
|
+
|
|
3
|
+
We don't commit to one segmentation algorithm. We run several cheap detectors
|
|
4
|
+
over the per-event *phase stream* of a session and feed their outputs as
|
|
5
|
+
features; the classifier learns when to trust each. Every input here is a
|
|
6
|
+
canonical phase enum, so the features are portable by construction.
|
|
7
|
+
|
|
8
|
+
This is the per-event ``phase_signals`` stream — i.e. the enricher's own
|
|
9
|
+
``metadata.phases`` estimate. Feeding its segmentation outputs back in as
|
|
10
|
+
features is exactly how the deterministic majority-vote "voting algo" survives
|
|
11
|
+
inside the learned classifier rather than being the final decision.
|
|
12
|
+
|
|
13
|
+
Detectors:
|
|
14
|
+
|
|
15
|
+
* ``bocpd`` — a categorical Bayesian Online Changepoint Detection
|
|
16
|
+
(Adams & MacKay 2007) with a Dirichlet-multinomial predictive and a constant
|
|
17
|
+
hazard. Emits the posterior changepoint probability and the (normalised)
|
|
18
|
+
expected run length at each step. Online / causal by construction.
|
|
19
|
+
* ``majority_vote`` — for each window ``w``, did the windowed (trailing)
|
|
20
|
+
majority phase change at this step, and how many steps since the last change.
|
|
21
|
+
* ``phase_entropy`` — Shannon entropy of the phase distribution in a trailing
|
|
22
|
+
window (high entropy ⇒ unstable region ⇒ boundary-likely).
|
|
23
|
+
|
|
24
|
+
All numeric knobs (window sizes, hazard's expected run length, Dirichlet
|
|
25
|
+
concentration) are passed in via :class:`SegmentationParams`, never baked in.
|
|
26
|
+
Every detector reads only the trailing prefix of the stream, so the features
|
|
27
|
+
are causal and computable online.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import math
|
|
33
|
+
from collections import deque
|
|
34
|
+
from dataclasses import dataclass
|
|
35
|
+
|
|
36
|
+
import numpy as np
|
|
37
|
+
|
|
38
|
+
#: Canonical phase vocabulary for the stream, plus a sentinel for "no signal".
|
|
39
|
+
PHASE_VOCAB: tuple[str, ...] = (
|
|
40
|
+
"planning",
|
|
41
|
+
"implementation",
|
|
42
|
+
"verification",
|
|
43
|
+
"exploration",
|
|
44
|
+
"review",
|
|
45
|
+
"none",
|
|
46
|
+
)
|
|
47
|
+
_VOCAB_INDEX = {p: i for i, p in enumerate(PHASE_VOCAB)}
|
|
48
|
+
_K = len(PHASE_VOCAB)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class SegmentationParams:
|
|
53
|
+
"""Hyperparameters for the segmentation detectors (sourced from YAML)."""
|
|
54
|
+
|
|
55
|
+
windows: tuple[int, ...]
|
|
56
|
+
entropy_window: int
|
|
57
|
+
bocpd_expected_run_length: float
|
|
58
|
+
bocpd_alpha: float
|
|
59
|
+
bocpd_r_max: int
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def hazard(self) -> float:
|
|
63
|
+
return 1.0 / float(self.bocpd_expected_run_length)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def phase_of(phase_signals) -> str:
|
|
67
|
+
"""Primary phase of an event from its phase_signals list."""
|
|
68
|
+
|
|
69
|
+
for s in phase_signals or ():
|
|
70
|
+
if s in _VOCAB_INDEX:
|
|
71
|
+
return s
|
|
72
|
+
return "none"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _bocpd(
|
|
76
|
+
cats: list[int], hazard: float, alpha: float, r_max: int
|
|
77
|
+
) -> tuple[list[float], list[float]]:
|
|
78
|
+
"""Categorical BOCPD. Returns (changepoint_prob[t], expected_runlength[t])."""
|
|
79
|
+
|
|
80
|
+
cp_score: list[float] = []
|
|
81
|
+
exp_run: list[float] = []
|
|
82
|
+
run_post = np.zeros(r_max + 2)
|
|
83
|
+
run_post[0] = 1.0
|
|
84
|
+
recent: list[int] = []
|
|
85
|
+
|
|
86
|
+
for x in cats:
|
|
87
|
+
maxr = min(len(recent), r_max)
|
|
88
|
+
# Dirichlet-multinomial predictive of x under each run-length hypothesis.
|
|
89
|
+
pred = np.empty(maxr + 1)
|
|
90
|
+
pred[0] = (alpha) / (_K * alpha) # empty run → prior
|
|
91
|
+
cnt = np.zeros(_K)
|
|
92
|
+
n = 0
|
|
93
|
+
for r in range(1, maxr + 1):
|
|
94
|
+
cnt[recent[-r]] += 1
|
|
95
|
+
n += 1
|
|
96
|
+
pred[r] = (cnt[x] + alpha) / (n + _K * alpha)
|
|
97
|
+
|
|
98
|
+
cur = run_post[: maxr + 1].copy()
|
|
99
|
+
s = cur.sum()
|
|
100
|
+
if s == 0:
|
|
101
|
+
cur[0] = 1.0
|
|
102
|
+
else:
|
|
103
|
+
cur /= s
|
|
104
|
+
evidence = cur * pred
|
|
105
|
+
new = np.zeros(r_max + 2)
|
|
106
|
+
new[1 : maxr + 2] = evidence * (1.0 - hazard)
|
|
107
|
+
new[0] = evidence.sum() * hazard
|
|
108
|
+
total = new.sum()
|
|
109
|
+
if total > 0:
|
|
110
|
+
new /= total
|
|
111
|
+
cp_score.append(float(new[0]))
|
|
112
|
+
exp_run.append(float(np.dot(np.arange(r_max + 2), new) / r_max))
|
|
113
|
+
run_post = new
|
|
114
|
+
recent.append(x)
|
|
115
|
+
|
|
116
|
+
return cp_score, exp_run
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _windowed(
|
|
120
|
+
cats: list[str], windows: tuple[int, ...], entropy_window: int
|
|
121
|
+
) -> list[dict[str, float]]:
|
|
122
|
+
"""Majority-vote-change, steps-since-change, and entropy features per step."""
|
|
123
|
+
|
|
124
|
+
feats: list[dict[str, float]] = [dict() for _ in cats]
|
|
125
|
+
for w in windows:
|
|
126
|
+
last_majority: str | None = None
|
|
127
|
+
last_change = 0
|
|
128
|
+
for t in range(len(cats)):
|
|
129
|
+
window = cats[max(0, t - w + 1) : t + 1]
|
|
130
|
+
maj = max(set(window), key=window.count)
|
|
131
|
+
changed = last_majority is not None and maj != last_majority
|
|
132
|
+
feats[t][f"seg_majchange_w{w}"] = 1.0 if changed else 0.0
|
|
133
|
+
if changed:
|
|
134
|
+
last_change = t
|
|
135
|
+
feats[t][f"seg_since_majchange_w{w}"] = float(t - last_change)
|
|
136
|
+
last_majority = maj
|
|
137
|
+
|
|
138
|
+
for t in range(len(cats)):
|
|
139
|
+
window = cats[max(0, t - entropy_window + 1) : t + 1]
|
|
140
|
+
counts: dict[str, int] = {}
|
|
141
|
+
for c in window:
|
|
142
|
+
counts[c] = counts.get(c, 0) + 1
|
|
143
|
+
total = len(window)
|
|
144
|
+
ent = -sum((v / total) * math.log2(v / total) for v in counts.values())
|
|
145
|
+
feats[t][f"seg_phase_entropy_w{entropy_window}"] = float(ent)
|
|
146
|
+
return feats
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def session_segmentation_features(
|
|
150
|
+
ordered_phase_signals: list,
|
|
151
|
+
ordered_event_ids: list[str],
|
|
152
|
+
params: SegmentationParams,
|
|
153
|
+
) -> dict[str, dict[str, float]]:
|
|
154
|
+
"""Compute per-event segmentation features for one session.
|
|
155
|
+
|
|
156
|
+
``ordered_phase_signals`` and ``ordered_event_ids`` must be aligned and in
|
|
157
|
+
session sequence order. Returns ``event_id -> feature dict``.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
cats = [phase_of(ps) for ps in ordered_phase_signals]
|
|
161
|
+
cat_ids = [_VOCAB_INDEX[c] for c in cats]
|
|
162
|
+
cp_score, exp_run = _bocpd(cat_ids, params.hazard, params.bocpd_alpha, params.bocpd_r_max)
|
|
163
|
+
windowed = _windowed(cats, params.windows, params.entropy_window)
|
|
164
|
+
|
|
165
|
+
out: dict[str, dict[str, float]] = {}
|
|
166
|
+
for i, eid in enumerate(ordered_event_ids):
|
|
167
|
+
d = dict(windowed[i])
|
|
168
|
+
d["seg_bocpd_changepoint"] = cp_score[i]
|
|
169
|
+
d["seg_bocpd_runlength"] = exp_run[i]
|
|
170
|
+
out[eid] = d
|
|
171
|
+
return out
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class IncrementalSegmentation:
|
|
175
|
+
"""Online, exactly-equivalent streaming form of the segmentation detectors.
|
|
176
|
+
|
|
177
|
+
Feeding events one at a time through :meth:`push` yields, for each event,
|
|
178
|
+
the *identical* feature dict :func:`session_segmentation_features` produces
|
|
179
|
+
for that event over the full prefix — because every detector here is causal
|
|
180
|
+
and carries only bounded state forward (BOCPD run-length posterior + the
|
|
181
|
+
last ``r_max`` categories; per-window majority + trailing category buffers).
|
|
182
|
+
This is what lets the pipeline stamp phases live without re-scanning the
|
|
183
|
+
session or resetting BOCPD on a moving window.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
def __init__(self, params: SegmentationParams) -> None:
|
|
187
|
+
self.p = params
|
|
188
|
+
r_max = params.bocpd_r_max
|
|
189
|
+
self._run_post = np.zeros(r_max + 2)
|
|
190
|
+
self._run_post[0] = 1.0
|
|
191
|
+
self._recent: deque[int] = deque(maxlen=r_max)
|
|
192
|
+
self._t = 0
|
|
193
|
+
cat_span = max(max(params.windows, default=1), params.entropy_window)
|
|
194
|
+
self._cats: deque[str] = deque(maxlen=cat_span)
|
|
195
|
+
self._last_majority: dict[int, str | None] = {w: None for w in params.windows}
|
|
196
|
+
self._last_change: dict[int, int] = {w: 0 for w in params.windows}
|
|
197
|
+
|
|
198
|
+
def push(self, phase_signals) -> dict[str, float]:
|
|
199
|
+
c = phase_of(phase_signals)
|
|
200
|
+
x = _VOCAB_INDEX[c]
|
|
201
|
+
d: dict[str, float] = {}
|
|
202
|
+
|
|
203
|
+
# --- BOCPD step (mirror of _bocpd's per-observation update) ---
|
|
204
|
+
p = self.p
|
|
205
|
+
r_max = p.bocpd_r_max
|
|
206
|
+
hazard = p.hazard
|
|
207
|
+
alpha = p.bocpd_alpha
|
|
208
|
+
maxr = min(len(self._recent), r_max)
|
|
209
|
+
pred = np.empty(maxr + 1)
|
|
210
|
+
pred[0] = alpha / (_K * alpha)
|
|
211
|
+
cnt = np.zeros(_K)
|
|
212
|
+
n = 0
|
|
213
|
+
for r in range(1, maxr + 1):
|
|
214
|
+
cnt[self._recent[-r]] += 1
|
|
215
|
+
n += 1
|
|
216
|
+
pred[r] = (cnt[x] + alpha) / (n + _K * alpha)
|
|
217
|
+
cur = self._run_post[: maxr + 1].copy()
|
|
218
|
+
s = cur.sum()
|
|
219
|
+
if s == 0:
|
|
220
|
+
cur[0] = 1.0
|
|
221
|
+
else:
|
|
222
|
+
cur /= s
|
|
223
|
+
evidence = cur * pred
|
|
224
|
+
new = np.zeros(r_max + 2)
|
|
225
|
+
new[1 : maxr + 2] = evidence * (1.0 - hazard)
|
|
226
|
+
new[0] = evidence.sum() * hazard
|
|
227
|
+
total = new.sum()
|
|
228
|
+
if total > 0:
|
|
229
|
+
new /= total
|
|
230
|
+
d["seg_bocpd_changepoint"] = float(new[0])
|
|
231
|
+
d["seg_bocpd_runlength"] = float(np.dot(np.arange(r_max + 2), new) / r_max)
|
|
232
|
+
self._run_post = new
|
|
233
|
+
self._recent.append(x)
|
|
234
|
+
|
|
235
|
+
# --- windowed majority-change / steps-since-change / entropy ---
|
|
236
|
+
self._cats.append(c)
|
|
237
|
+
buf = list(self._cats)
|
|
238
|
+
t = self._t
|
|
239
|
+
for w in p.windows:
|
|
240
|
+
window = buf[-w:]
|
|
241
|
+
maj = max(set(window), key=window.count)
|
|
242
|
+
changed = self._last_majority[w] is not None and maj != self._last_majority[w]
|
|
243
|
+
d[f"seg_majchange_w{w}"] = 1.0 if changed else 0.0
|
|
244
|
+
if changed:
|
|
245
|
+
self._last_change[w] = t
|
|
246
|
+
d[f"seg_since_majchange_w{w}"] = float(t - self._last_change[w])
|
|
247
|
+
self._last_majority[w] = maj
|
|
248
|
+
ew = p.entropy_window
|
|
249
|
+
window = buf[-ew:]
|
|
250
|
+
counts: dict[str, int] = {}
|
|
251
|
+
for cc in window:
|
|
252
|
+
counts[cc] = counts.get(cc, 0) + 1
|
|
253
|
+
tot = len(window)
|
|
254
|
+
d[f"seg_phase_entropy_w{ew}"] = float(
|
|
255
|
+
-sum((v / tot) * math.log2(v / tot) for v in counts.values())
|
|
256
|
+
)
|
|
257
|
+
self._t += 1
|
|
258
|
+
return d
|