alignscope 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.
@@ -0,0 +1,220 @@
1
+ /**
2
+ * AlignScope — Metrics Panel
3
+ *
4
+ * Updates the right sidebar with real-time alignment metrics:
5
+ * - Overall alignment score with progress bar
6
+ * - Per-team: role stability, coalitions, defectors, score
7
+ * - Alignment history sparkline
8
+ *
9
+ * Dynamically supports N teams from config — no hardcoded team names.
10
+ */
11
+
12
+ const MetricsPanel = {
13
+ config: null,
14
+ sparklineHistory: [],
15
+ maxSparklinePoints: 200,
16
+
17
+ init(config) {
18
+ this.config = config;
19
+ this.sparklineHistory = [];
20
+ },
21
+
22
+ update(data) {
23
+ if (!data.metrics) return;
24
+
25
+ const metrics = data.metrics;
26
+ const scores = data.team_scores || {};
27
+
28
+ // Overall alignment
29
+ const overall = metrics.overall_alignment_score;
30
+ if (overall !== undefined && overall !== null) {
31
+ document.getElementById('metric-overall-value').textContent = overall.toFixed(3);
32
+ document.getElementById('metric-overall-bar').style.width = (overall * 100) + '%';
33
+
34
+ const barEl = document.getElementById('metric-overall-bar');
35
+ if (overall > 0.8) barEl.style.background = 'var(--color-success)';
36
+ else if (overall > 0.5) barEl.style.background = 'var(--color-accent)';
37
+ else barEl.style.background = 'var(--color-defection)';
38
+ }
39
+
40
+ // Team metrics — iterate over all teams from config
41
+ const teamMetrics = metrics.team_metrics || {};
42
+ const numTeams = (this.config && this.config.teams) ? this.config.teams.length : 2;
43
+
44
+ for (let i = 0; i < numTeams; i++) {
45
+ const teamData = teamMetrics[String(i)] || teamMetrics[i];
46
+ const teamScore = scores[String(i)] ?? scores[i] ?? 0;
47
+ this.updateTeam(`team-${i}`, teamData, teamScore);
48
+ }
49
+
50
+ // Sparkline
51
+ this.sparklineHistory.push(overall);
52
+ if (this.sparklineHistory.length > this.maxSparklinePoints) {
53
+ this.sparklineHistory.shift();
54
+ }
55
+ this.drawSparkline();
56
+
57
+ // Update agent detail if one is selected
58
+ if (AlignScope.state.selectedAgent !== null) {
59
+ AlignScope.selectAgent(AlignScope.state.selectedAgent);
60
+ }
61
+ },
62
+
63
+ updateTeam(prefix, teamData, score) {
64
+ if (!teamData) return;
65
+
66
+ // Legacy mapping for cooperative built-ins
67
+ const mappedData = {
68
+ ...teamData,
69
+ role_stability: teamData.avg_role_stability,
70
+ coalitions: teamData.active_coalitions,
71
+ defectors: teamData.defector_count,
72
+ score: score
73
+ };
74
+
75
+ const metricsConfig = this.config?.metrics || [];
76
+ metricsConfig.forEach(m => {
77
+ const el = document.getElementById(`${prefix}-metric-${m.id}`);
78
+ if (!el) return;
79
+
80
+ let val = mappedData[m.id];
81
+ if (val === undefined) val = 0; // fallback if missing
82
+
83
+ // special formatting
84
+ if (typeof val === 'number') {
85
+ if (Number.isInteger(val)) {
86
+ el.textContent = val.toString();
87
+ } else {
88
+ el.textContent = val.toFixed(m.id === 'role_stability' ? 4 : 2);
89
+ }
90
+ } else {
91
+ el.textContent = String(val);
92
+ }
93
+
94
+ // Colors for defectors
95
+ if (m.id === 'defectors') {
96
+ if (val > 0) el.style.color = 'var(--color-defection)';
97
+ else el.style.color = 'var(--color-success)';
98
+ }
99
+
100
+ // role_stability mini-bar
101
+ if (m.id === 'role_stability') {
102
+ const stabBarEl = document.getElementById(`${prefix}-stability-bar`);
103
+ if (stabBarEl) {
104
+ stabBarEl.style.width = (Math.max(0, Math.min(1, val)) * 100) + '%';
105
+ }
106
+ }
107
+ });
108
+ },
109
+
110
+ drawSparkline() {
111
+ const canvas = document.getElementById('alignment-sparkline');
112
+ if (!canvas) return;
113
+
114
+ const container = canvas.parentElement;
115
+
116
+ const ctx = canvas.getContext('2d');
117
+ const dpr = window.devicePixelRatio || 1;
118
+ const rect = container.getBoundingClientRect();
119
+ const data = this.sparklineHistory;
120
+
121
+ // Dynamic width: min size is container width, otherwise 2px per data point
122
+ const desiredWidth = Math.max(rect.width, Math.max(0, data.length) * 2);
123
+ const fixedHeight = 80; // Keep height locked to 80px to prevent infinite container growth loop
124
+
125
+ canvas.width = desiredWidth * dpr;
126
+ canvas.height = fixedHeight * dpr;
127
+ canvas.style.width = desiredWidth + 'px';
128
+ canvas.style.height = fixedHeight + 'px';
129
+ ctx.scale(dpr, dpr);
130
+
131
+ const w = desiredWidth;
132
+ const h = fixedHeight;
133
+ const padding = { top: 4, bottom: 4, left: 2, right: 2 };
134
+ const plotW = w - padding.left - padding.right;
135
+ const plotH = h - padding.top - padding.bottom;
136
+
137
+ ctx.clearRect(0, 0, w, h);
138
+
139
+ if (data.length < 2) return;
140
+
141
+ const min = Math.min(...data) * 0.95;
142
+ const max = Math.max(...data) * 1.05;
143
+ const range = max - min || 1;
144
+
145
+ const xScale = (i) => padding.left + (i / (data.length - 1)) * plotW;
146
+ const yScale = (v) => padding.top + plotH - ((v - min) / range) * plotH;
147
+
148
+ // Fill area under the line
149
+ ctx.beginPath();
150
+ ctx.moveTo(xScale(0), yScale(data[0]));
151
+ for (let i = 1; i < data.length; i++) {
152
+ ctx.lineTo(xScale(i), yScale(data[i]));
153
+ }
154
+ ctx.lineTo(xScale(data.length - 1), padding.top + plotH);
155
+ ctx.lineTo(xScale(0), padding.top + plotH);
156
+ ctx.closePath();
157
+
158
+ const gradient = ctx.createLinearGradient(0, padding.top, 0, padding.top + plotH);
159
+ gradient.addColorStop(0, 'rgba(109, 158, 235, 0.15)');
160
+ gradient.addColorStop(1, 'rgba(109, 158, 235, 0.02)');
161
+ ctx.fillStyle = gradient;
162
+ ctx.fill();
163
+
164
+ // Draw line
165
+ ctx.beginPath();
166
+ ctx.moveTo(xScale(0), yScale(data[0]));
167
+ for (let i = 1; i < data.length; i++) {
168
+ ctx.lineTo(xScale(i), yScale(data[i]));
169
+ }
170
+ ctx.strokeStyle = '#6d9eeb';
171
+ ctx.lineWidth = 1.5;
172
+ ctx.stroke();
173
+
174
+ // Draw current value dot
175
+ const lastX = xScale(data.length - 1);
176
+ const lastY = yScale(data[data.length - 1]);
177
+ ctx.beginPath();
178
+ ctx.arc(lastX, lastY, 3, 0, Math.PI * 2);
179
+ ctx.fillStyle = '#6d9eeb';
180
+ ctx.fill();
181
+
182
+ // Min/max labels
183
+ ctx.font = '9px "JetBrains Mono"';
184
+ ctx.fillStyle = '#4b5563';
185
+ ctx.textAlign = 'right';
186
+ ctx.fillText(max.toFixed(2), w - 2, padding.top + 8);
187
+ ctx.fillText(min.toFixed(2), w - 2, padding.top + plotH);
188
+ },
189
+
190
+ reset() {
191
+ this.sparklineHistory = [];
192
+
193
+ document.getElementById('metric-overall-value').textContent = '—';
194
+ document.getElementById('metric-overall-bar').style.width = '0%';
195
+
196
+ const numTeams = (this.config && this.config.teams) ? this.config.teams.length : 2;
197
+ const metricsConfig = this.config?.metrics || [];
198
+
199
+ for (let i = 0; i < numTeams; i++) {
200
+ const prefix = `team-${i}`;
201
+ metricsConfig.forEach(m => {
202
+ const el = document.getElementById(`${prefix}-metric-${m.id}`);
203
+ if (el) {
204
+ el.textContent = m.id === 'defectors' || m.id === 'coalitions' ? '0' : '—';
205
+ if (m.id === 'defectors') el.style.color = '';
206
+ }
207
+ if (m.id === 'role_stability') {
208
+ const stabBarEl = document.getElementById(`${prefix}-stability-bar`);
209
+ if (stabBarEl) stabBarEl.style.width = '0%';
210
+ }
211
+ });
212
+ }
213
+
214
+ const canvas = document.getElementById('alignment-sparkline');
215
+ if (canvas) {
216
+ const ctx = canvas.getContext('2d');
217
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
218
+ }
219
+ },
220
+ };