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.
- alignscope/__init__.py +150 -0
- alignscope/_frontend/css/style.css +663 -0
- alignscope/_frontend/index.html +169 -0
- alignscope/_frontend/js/app.js +360 -0
- alignscope/_frontend/js/metrics.js +220 -0
- alignscope/_frontend/js/timeline.js +494 -0
- alignscope/_frontend/js/topology.js +368 -0
- alignscope/adapters.py +169 -0
- alignscope/cli.py +99 -0
- alignscope/detector.py +242 -0
- alignscope/integrations/__init__.py +28 -0
- alignscope/integrations/mlflow_bridge.py +70 -0
- alignscope/integrations/wandb_bridge.py +81 -0
- alignscope/metrics.py +383 -0
- alignscope/patches/__init__.py +50 -0
- alignscope/patches/pettingzoo.py +332 -0
- alignscope/patches/pymarl.py +277 -0
- alignscope/patches/rllib.py +170 -0
- alignscope/sdk.py +606 -0
- alignscope/server.py +298 -0
- alignscope/simulator.py +493 -0
- alignscope-0.1.0.dist-info/METADATA +183 -0
- alignscope-0.1.0.dist-info/RECORD +26 -0
- alignscope-0.1.0.dist-info/WHEEL +4 -0
- alignscope-0.1.0.dist-info/entry_points.txt +2 -0
- alignscope-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -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
|
+
};
|