Files
boc/SIL/historical-analysis.mjs
T

342 lines
12 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
// ═══════════════════════════════════════════════════════════════════════════
// SIL Historical Analysis — Analysera trender över tid
// Erik-krav: "Vilka moduler orsakar flest regressionsfel? Vilka komponenter
// förändras oftast? Vilka delar av systemet har lägst confidence?"
// ═══════════════════════════════════════════════════════════════════════════
import { readFileSync, existsSync, writeFileSync } from 'fs';
const LOGS = {
shadow: '/home/bernt/.openclaw/workspace/SIL/shadow-log.jsonl',
gold: '/home/bernt/.openclaw/workspace/SIL/gold-set.jsonl',
stability: '/home/bernt/.openclaw/workspace/SIL/stability-log.jsonl',
versions: '/home/bernt/.openclaw/workspace/SIL/graph-version-log.jsonl',
policy: '/home/bernt/.openclaw/workspace/SIL/policy-violations.jsonl',
reasoning: '/home/bernt/.openclaw/workspace/SIL/reasoning-results.jsonl'
};
class HistoricalAnalysis {
constructor() {
this.data = this.loadAllLogs();
}
loadAllLogs() {
const data = {};
for (const [key, path] of Object.entries(LOGS)) {
data[key] = this.loadLog(path);
}
return data;
}
loadLog(path) {
if (!existsSync(path)) return [];
return readFileSync(path, 'utf8')
.split('\n')
.filter(line => line.trim())
.map(line => {
try { return JSON.parse(line); } catch { return null; }
})
.filter(Boolean);
}
// ── Trendanalyser ───────────────────────────────────────────────────────
/**
* Vilka moduler orsakar flest regressionsfel?
*/
analyzeRegressionFailures() {
const goldEntries = this.data.gold;
const failures = {};
for (const entry of goldEntries) {
const predictions = entry.predictions || [];
for (const pred of predictions) {
if (pred.verified === false) {
failures[pred.component] = (failures[pred.component] || 0) + 1;
}
}
}
return Object.entries(failures)
.sort((a, b) => b[1] - a[1])
.map(([component, count]) => ({
component,
failures: count,
percentage: Math.round(count / goldEntries.length * 100) + '%'
}));
}
/**
* Vilka komponenter förändras oftast?
*/
analyzeChangeFrequency() {
const shadowEntries = this.data.shadow;
const changes = {};
for (const entry of shadowEntries) {
const components = entry.components || entry.changedComponents || [];
if (!Array.isArray(components)) continue;
for (const comp of components) {
const compId = typeof comp === 'string' ? comp : comp.id;
if (compId) {
changes[compId] = (changes[compId] || 0) + 1;
}
}
}
return Object.entries(changes)
.sort((a, b) => b[1] - a[1])
.map(([component, count]) => ({
component,
changes: count,
trend: this.calculateTrend(component)
}));
}
/**
* Vilka delar har lägst confidence?
*/
analyzeLowConfidence() {
const shadowEntries = this.data.shadow;
const confidenceByComponent = {};
for (const entry of shadowEntries) {
const components = entry.components || entry.changedComponents || [];
if (!Array.isArray(components)) continue;
for (const comp of components) {
const compId = typeof comp === 'string' ? comp : comp.id;
if (!compId) continue;
if (!confidenceByComponent[compId]) {
confidenceByComponent[compId] = [];
}
confidenceByComponent[compId].push(entry.confidence || 0);
}
}
return Object.entries(confidenceByComponent)
.map(([component, confidences]) => ({
component,
avgConfidence: Math.round(
confidences.reduce((a, b) => a + b, 0) / confidences.length * 100
) + '%',
minConfidence: Math.round(Math.min(...confidences) * 100) + '%',
samples: confidences.length
}))
.sort((a, b) => parseFloat(a.avgConfidence) - parseFloat(b.avgConfidence));
}
/**
* Vilka beroenden är mest instabila?
*/
analyzeUnstableDependencies() {
const goldEntries = this.data.gold;
const dependencyChanges = {};
for (const entry of goldEntries) {
const predicted = entry.predicted || [];
const actual = entry.actual || [];
// Räkna felaktiga förutsägelser per komponent
for (const pred of predicted) {
if (!actual.includes(pred)) {
dependencyChanges[pred] = (dependencyChanges[pred] || 0) + 1;
}
}
}
return Object.entries(dependencyChanges)
.sort((a, b) => b[1] - a[1])
.map(([component, count]) => ({
component,
falsePredictions: count,
instability: count / goldEntries.length
}));
}
/**
* Var ökar den tekniska skulden snabbast?
*/
analyzeTechnicalDebt() {
const policyEntries = this.data.policy;
const violationsByTime = {};
for (const entry of policyEntries) {
const date = entry.timestamp?.split('T')[0];
if (!date) continue;
if (!violationsByTime[date]) {
violationsByTime[date] = { count: 0, critical: 0 };
}
violationsByTime[date].count++;
if (entry.severity === 'CRITICAL') {
violationsByTime[date].critical++;
}
}
const sorted = Object.entries(violationsByTime)
.sort((a, b) => new Date(a[0]) - new Date(b[0]));
// Beräkna trend
const trends = [];
for (let i = 1; i < sorted.length; i++) {
const prev = sorted[i - 1][1];
const curr = sorted[i][1];
trends.push({
date: sorted[i][0],
change: curr.count - prev.count,
acceleration: (curr.count - prev.count) - (prev.count - (sorted[i - 2]?.[1]?.count || 0))
});
}
return {
daily: sorted.map(([date, data]) => ({ date, ...data })),
trends: trends.slice(-7), // senaste 7 dagarna
increasing: trends.filter(t => t.change > 0).length,
decreasing: trends.filter(t => t.change < 0).length
};
}
/**
* Beräkna trend för en komponent
*/
calculateTrend(component) {
const shadowEntries = this.data.shadow;
const byWeek = {};
for (const entry of shadowEntries) {
const components = entry.components || [];
if (!components.includes(component)) continue;
const date = entry.timestamp?.split('T')[0];
if (!date) continue;
const week = this.getWeekKey(new Date(date));
byWeek[week] = (byWeek[week] || 0) + 1;
}
const weeks = Object.entries(byWeek).sort();
if (weeks.length < 2) return 'stable';
const first = weeks[0][1];
const last = weeks[weeks.length - 1][1];
if (last > first * 1.5) return 'increasing';
if (last < first * 0.5) return 'decreasing';
return 'stable';
}
getWeekKey(date) {
const d = new Date(date);
d.setHours(0, 0, 0, 0);
d.setDate(d.getDate() - d.getDay() + 1);
return d.toISOString().split('T')[0];
}
// ── Sammanfattning ──────────────────────────────────────────────────────
generateReport() {
const report = {
timestamp: new Date().toISOString(),
regressions: this.analyzeRegressionFailures(),
changeFrequency: this.analyzeChangeFrequency(),
lowConfidence: this.analyzeLowConfidence(),
unstableDependencies: this.analyzeUnstableDependencies(),
technicalDebt: this.analyzeTechnicalDebt()
};
this.printReport(report);
this.saveReport(report);
return report;
}
printReport(report) {
console.log('╔═══════════════════════════════════════════════════════════════╗');
console.log('║ SIL HISTORICAL ANALYSIS ║');
console.log('║ Trender och långsiktiga mönster ║');
console.log('╚═══════════════════════════════════════════════════════════════╝');
console.log();
// Regressionsfel
console.log('🔴 MODULER MED FLEST REGRESSIONSFEL\n');
for (const r of report.regressions.slice(0, 10)) {
console.log(` ${r.component}: ${r.failures} fel (${r.percentage})`);
}
console.log();
// Förändringsfrekvens
console.log('📈 MEST FÖRÄNDRADE KOMPONENTER\n');
for (const c of report.changeFrequency.slice(0, 10)) {
const icon = c.trend === 'increasing' ? '📈' :
c.trend === 'decreasing' ? '📉' : '➡️';
console.log(` ${icon} ${c.component}: ${c.changes} ändringar (${c.trend})`);
}
console.log();
// Låg confidence
console.log('⚠️ LÄGST CONFIDENCE\n');
for (const c of report.lowConfidence.slice(0, 10)) {
console.log(` ${c.component}: ${c.avgConfidence} (min: ${c.minConfidence}, ${c.samples} samples)`);
}
console.log();
// Instabila beroenden
console.log('🌊 INSTABILA BEROENDEN\n');
for (const d of report.unstableDependencies.slice(0, 10)) {
console.log(` ${d.component}: ${d.falsePredictions} felaktiga förutsägelser`);
}
console.log();
// Teknisk skuld
if (report.technicalDebt) {
console.log('💰 TEKNISK SKULD\n');
console.log(` Trend: ${report.technicalDebt.increasing > report.technicalDebt.decreasing ? 'ÖKANDE' : 'MINSKANDE'}`);
console.log(` Dagar med ökning: ${report.technicalDebt.increasing}`);
console.log(` Dagar med minskning: ${report.technicalDebt.decreasing}`);
if (report.technicalDebt.trends.length > 0) {
console.log('\n Senaste 7 dagarna:');
for (const t of report.technicalDebt.trends) {
const icon = t.change > 0 ? '📈' : t.change < 0 ? '📉' : '➡️';
console.log(` ${icon} ${t.date}: ${t.change > 0 ? '+' : ''}${t.change}`);
}
}
console.log();
}
console.log('═══════════════════════════════════════════════════════════════\n');
}
saveReport(report) {
const filename = `/home/bernt/.openclaw/workspace/SIL/historical-report-${new Date().toISOString().split('T')[0]}.json`;
writeFileSync(filename, JSON.stringify(report, null, 2));
console.log(`💾 Rapport sparad: ${filename}\n`);
}
}
// ── Main ──────────────────────────────────────────────────────────────────
const analysis = new HistoricalAnalysis();
const command = process.argv[2] || '--report';
if (command === '--report') {
analysis.generateReport();
} else if (command === '--regressions') {
console.log(JSON.stringify(analysis.analyzeRegressionFailures(), null, 2));
} else if (command === '--changes') {
console.log(JSON.stringify(analysis.analyzeChangeFrequency(), null, 2));
} else if (command === '--confidence') {
console.log(JSON.stringify(analysis.analyzeLowConfidence(), null, 2));
} else if (command === '--debt') {
console.log(JSON.stringify(analysis.analyzeTechnicalDebt(), null, 2));
} else {
console.log('Användning:');
console.log(' node historical-analysis.mjs --report # Full rapport');
console.log(' node historical-analysis.mjs --regressions # Regressionsfel');
console.log(' node historical-analysis.mjs --changes # Förändringsfrekvens');
console.log(' node historical-analysis.mjs --confidence # Låg confidence');
console.log(' node historical-analysis.mjs --debt # Teknisk skuld');
}