05ed037fe8
- DNS: pilot.landvex.com -> 16.170.83.169 - TLS: Let's Encrypt certificate (expires 2026-09-30) - Nginx: reverse proxy with SSL termination - API: https://pilot.landvex.com/api/v1/missions - UI: https://pilot.landvex.com/ - Upload: POST /api/v1/missions/import (multipart/form-data) Verified: ✅ https://pilot.landvex.com/health ✅ https://pilot.landvex.com/version ✅ https://pilot.landvex.com/api/v1/missions (list) ✅ https://pilot.landvex.com/api/v1/missions/:id (get) ✅ POST /api/v1/missions/import (video upload) ✅ UI loads with title 'LandveX Intelligence Lab' Next: Pilot 001 — Break the system!
342 lines
12 KiB
JavaScript
342 lines
12 KiB
JavaScript
#!/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');
|
|
}
|