Compare commits

...

3 Commits

Author SHA1 Message Date
opus
b1a2a6490d wave(214): drill-down 7sigma fails + resolver catalog WTP
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
2026-04-21 16:31:01 +02:00
Opus Wire
0925c771a0 feat(token-health-dashboard): monitoring UI + runbook rotation
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
NEW: /token-health-dashboard.html (7.8KB)
- 4 KPI hero cards (health pct, actifs, expirés, total)
- 17 providers grid avec status visual
- Runbook inline pour rotation manuelle par provider
- Fetch live /api/wevia-autonomy-status.json
- Color coding: OK green, EXPIRED red border-left

NEW: vault/doctrines/token-rotation-runbook.md (2KB)
- Procédure par provider (Groq, SambaNova, Alibaba, GitHub)
- Blueprint Selenium Docker pour automatisation
- Integration WEVIA Master intent token_rotate
- Post-rotation checklist

Response to token health warning: 63pct (4 expired)
- Priorité HAUTE: Groq (primary fallback cascade)
- Blueprint Selenium prêt dans tips-6-mois-cracked.md

Zero régression · dock WTP_UDOCK included pour navigation uniforme
2026-04-21 16:30:49 +02:00
opus
d46f607976 AUTO-BACKUP 20260421-1630 2026-04-21 16:30:04 +02:00
10 changed files with 327 additions and 2244 deletions

View File

@@ -1,6 +1,6 @@
{
"agent": "V41_Disk_Monitor",
"ts": "2026-04-21T16:00:01+02:00",
"ts": "2026-04-21T16:30:01+02:00",
"disk_pct": 82,
"disk_free_gb": 27,
"growth_per_day_gb": 1.5,

33
api/ambre-pw-deep.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
header("Content-Type: application/json");
$out = [];
// Read playwright-check.sh
$f = "/var/www/html/api/v76-scripts/playwright-check.sh";
if (file_exists($f)) $out["pw_check_sh"] = @file_get_contents($f);
// Targeted config locations
$cfgs = ["/var/www/html/playwright.config.js", "/var/www/html/playwright.config.ts",
"/var/www/html/api/playwright.config.js", "/opt/weval-l99/playwright.config.js"];
foreach ($cfgs as $c) if (file_exists($c)) $out["config_$c"] = substr(@file_get_contents($c), 0, 600);
// Find recent spec files in common dirs (no find/ recursive)
foreach (["/var/www/html", "/var/www/html/tests", "/var/www/html/api", "/opt/weval-l99/tests"] as $d) {
foreach (glob("$d/*.spec.{js,ts}", GLOB_BRACE) as $s) $out["specs"][] = $s;
foreach (glob("$d/tests/*.spec.{js,ts}", GLOB_BRACE) as $s) $out["specs"][] = $s;
}
// which playwright
$out["which_npx"] = trim(@shell_exec("which npx 2>&1") ?: "");
$out["which_playwright"] = trim(@shell_exec("which playwright 2>&1") ?: "");
// Look for package.json with playwright
foreach (["/var/www/html/package.json", "/var/www/html/api/package.json", "/opt/weval-l99/package.json"] as $p) {
if (file_exists($p)) {
$pkg = @json_decode(@file_get_contents($p), true);
$has_pw = isset($pkg["devDependencies"]["@playwright/test"]) || isset($pkg["dependencies"]["@playwright/test"]);
if ($has_pw || $pkg) $out["pkg_$p"] = ["has_playwright"=>$has_pw, "scripts"=>$pkg["scripts"]??[]];
}
}
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{
"generated_at": "2026-04-21T16:25:01.526924",
"generated_at": "2026-04-21T16:30:01.984827",
"stats": {
"total": 48,
"pending": 31,

View File

@@ -1,8 +1,8 @@
{
"status": "ALIVE",
"ts": "2026-04-21T16:15:02.035623",
"last_heartbeat": "2026-04-21T16:15:02.035623",
"last_heartbeat_ts_epoch": 1776780902,
"ts": "2026-04-21T16:30:02.091494",
"last_heartbeat": "2026-04-21T16:30:02.091494",
"last_heartbeat_ts_epoch": 1776781802,
"tasks_today": 232,
"tasks_week": 574,
"agent_id": "blade-ops",

View File

@@ -1,281 +0,0 @@
{
"ts": "2026-04-21T14:25:01+00:00",
"server": "s204",
"s204": {
"load": 1.48,
"uptime": "2026-04-14 11:51:24",
"ram_total_mb": 31335,
"ram_used_mb": 11831,
"ram_free_mb": 19503,
"disk_total": "150G",
"disk_used": "118G",
"disk_free": "27G",
"disk_pct": "82%",
"fpm_workers": 140,
"docker_containers": 19,
"cpu_cores": 8
},
"s95": {
"load": 0.7,
"disk_pct": "81%",
"status": "UP",
"ram_total_mb": 15610,
"ram_free_mb": 12027
},
"pmta": [
{
"name": "SER6",
"ip": "110.239.84.121",
"status": "DOWN"
},
{
"name": "SER7",
"ip": "110.239.65.64",
"status": "DOWN"
},
{
"name": "SER8",
"ip": "182.160.55.107",
"status": "DOWN"
},
{
"name": "SER9",
"ip": "110.239.86.68",
"status": "DOWN"
}
],
"assets": {
"html_pages": 315,
"php_apis": 811,
"wiki_entries": 2046,
"vault_doctrines": 75,
"vault_sessions": 104,
"vault_decisions": 12
},
"tools": {
"total": 627,
"registry_version": "?"
},
"sovereign": {
"status": "UP",
"providers": [
"Cerebras-fast",
"Cerebras-think",
"Groq",
"Cloudflare-AI",
"Gemini",
"SambaNova",
"NVIDIA-NIM",
"Mistral",
"Groq-OSS",
"HF-Space",
"HF-Router",
"OpenRouter",
"GitHub-Models"
],
"active": 13,
"total": 13,
"primary": "Cerebras-fast",
"cost": "0€"
},
"ethica": {
"total_hcps": 161733,
"with_email": 110611,
"with_phone": 155151,
"gap_email": 51122,
"pct_email": 68.4,
"pct_phone": 95.9,
"by_country": [
{
"country": "DZ",
"hcps": 122337,
"with_email": 78508,
"with_tel": 119396,
"pct_email": 64.2,
"pct_tel": 97.6
},
{
"country": "MA",
"hcps": 19723,
"with_email": 15077,
"with_tel": 18737,
"pct_email": 76.4,
"pct_tel": 95
},
{
"country": "TN",
"hcps": 17794,
"with_email": 15147,
"with_tel": 17018,
"pct_email": 85.1,
"pct_tel": 95.6
},
{
"country": "INTL",
"hcps": 1879,
"with_email": 1879,
"with_tel": 0,
"pct_email": 100,
"pct_tel": 0
}
]
},
"docker": [
{
"name": "loki",
"status": "Up 5 days",
"ports": ""
},
{
"name": "listmonk",
"status": "Up 5 days",
"ports": ""
},
{
"name": "plausible-plausible-1",
"status": "Up 4 days",
"ports": ""
},
{
"name": "plausible-plausible-db-1",
"status": "Up 4 days",
"ports": ""
},
{
"name": "plausible-plausible-events-db-1",
"status": "Up 4 days",
"ports": ""
},
{
"name": "n8n-docker-n8n-1",
"status": "Up 5 days",
"ports": ""
},
{
"name": "mattermost-docker-mm-db-1",
"status": "Up 5 days",
"ports": ""
},
{
"name": "mattermost-docker-mattermost-1",
"status": "Up 5 days (healthy)",
"ports": ""
},
{
"name": "twenty",
"status": "Up 5 days",
"ports": ""
},
{
"name": "twenty-redis",
"status": "Up 5 days",
"ports": ""
},
{
"name": "langfuse",
"status": "Up 5 days",
"ports": ""
},
{
"name": "redis-weval",
"status": "Up 7 days",
"ports": ""
},
{
"name": "gitea",
"status": "Up 7 days",
"ports": ""
},
{
"name": "node-exporter",
"status": "Up 7 days",
"ports": ""
},
{
"name": "prometheus",
"status": "Up 7 days",
"ports": ""
},
{
"name": "searxng",
"status": "Up 7 days",
"ports": ""
},
{
"name": "uptime-kuma",
"status": "Up 38 hours (healthy)",
"ports": ""
},
{
"name": "vaultwarden",
"status": "Up 7 days (healthy)",
"ports": ""
},
{
"name": "qdrant",
"status": "Up 7 days",
"ports": ""
}
],
"crons": {
"active": 35
},
"git": {
"head": "69dcf0a39 auto-sync-1625",
"dirty": 2,
"status": "DIRTY"
},
"nonreg": {
"total": 153,
"passed": 153,
"score": "100%"
},
"services": [
{
"name": "DeerFlow",
"port": 3002,
"status": "UP"
},
{
"name": "DeerFlow API",
"port": 8001,
"status": "UP"
},
{
"name": "Qdrant",
"port": 6333,
"status": "UP"
},
{
"name": "Ollama",
"port": 11434,
"status": "UP"
},
{
"name": "Redis",
"port": 6379,
"status": "UP"
},
{
"name": "Sovereign",
"port": 4000,
"status": "UP"
},
{
"name": "SearXNG",
"port": 8080,
"status": "UP"
}
],
"whisper": {
"binary": "COMPILED",
"model": "142MB"
},
"grand_total": 3893,
"health": {
"score": 5,
"max": 6,
"pct": 83
},
"elapsed_ms": 10544
}

View File

@@ -1,7 +1,7 @@
{
"ok": true,
"agent": "V42_MQL_Scoring_Agent_REAL",
"ts": "2026-04-21T14:20:01+00:00",
"ts": "2026-04-21T14:30:02+00:00",
"status": "DEPLOYED_AUTO",
"deployed": true,
"algorithm": "weighted_behavioral_signals",

View File

@@ -1,7 +1,7 @@
{
"ok": true,
"version": "V83-business-kpi",
"ts": "2026-04-21T14:26:43+00:00",
"ts": "2026-04-21T14:29:43+00:00",
"summary": {
"total_categories": 8,
"total_kpis": 64,

149
token-health-dashboard.html Normal file
View File

@@ -0,0 +1,149 @@
<!DOCTYPE html>
<html lang="fr"><head><meta charset="UTF-8"><title>Token Health Dashboard · WEVAL</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
*{margin:0;padding:0;box-sizing:border-box;font-family:-apple-system,'Segoe UI',sans-serif}
body{background:#060d1a;color:#e2e8f0;min-height:100vh;padding:20px}
.container{max-width:1200px;margin:0 auto}
h1{font-family:'Orbitron',sans-serif;background:linear-gradient(135deg,#06b6d4,#a855f7);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
font-size:2rem;margin-bottom:8px;letter-spacing:1px}
.subtitle{color:#94a3b8;margin-bottom:24px;font-size:0.9rem}
.hero{background:linear-gradient(135deg,rgba(6,182,212,0.08),rgba(168,85,247,0.08));
border:1px solid rgba(6,182,212,0.25);border-radius:16px;padding:24px;margin-bottom:24px;
backdrop-filter:blur(12px)}
.hero-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:16px}
.stat{text-align:center}
.stat-val{font-family:'Orbitron',sans-serif;font-size:2.5rem;font-weight:900;line-height:1}
.stat-val.danger{color:#ef4444}
.stat-val.warn{color:#f59e0b}
.stat-val.success{color:#22c55e}
.stat-lbl{font-size:0.7rem;color:#64748b;text-transform:uppercase;letter-spacing:1.5px;margin-top:6px}
.providers{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px;margin-bottom:24px}
.provider{background:rgba(15,23,42,0.8);border:1px solid rgba(100,116,139,0.15);
border-radius:12px;padding:14px 16px;transition:all 0.2s}
.provider.ok{border-left:3px solid #22c55e}
.provider.expired{border-left:3px solid #ef4444;background:rgba(239,68,68,0.05)}
.provider-name{font-weight:700;font-size:0.95rem;display:flex;justify-content:space-between;align-items:center}
.badge{padding:2px 8px;border-radius:10px;font-size:0.65rem;font-weight:700;text-transform:uppercase}
.badge.ok{background:rgba(34,197,94,0.15);color:#22c55e}
.badge.expired{background:rgba(239,68,68,0.15);color:#ef4444}
.provider-meta{font-size:0.75rem;color:#94a3b8;margin-top:6px}
.provider-action{margin-top:10px;display:flex;gap:8px;flex-wrap:wrap}
.btn{padding:4px 10px;border-radius:6px;font-size:0.7rem;font-weight:700;cursor:pointer;
text-decoration:none;border:none;transition:all 0.15s}
.btn-primary{background:linear-gradient(135deg,#06b6d4,#8b5cf6);color:#fff}
.btn-secondary{background:rgba(100,116,139,0.15);color:#94a3b8;border:1px solid rgba(100,116,139,0.25)}
.btn:hover{transform:translateY(-1px);opacity:0.9}
.runbook{background:rgba(15,23,42,0.8);border:1px solid rgba(100,116,139,0.15);
border-radius:12px;padding:20px;margin-top:24px}
.runbook h2{color:#06b6d4;font-size:1.2rem;margin-bottom:14px;
border-left:3px solid #06b6d4;padding-left:10px}
.runbook-step{padding:12px;margin:10px 0;background:rgba(11,13,21,0.6);border-radius:8px;border-left:3px solid rgba(168,85,247,0.4)}
.runbook-step strong{color:#c084fc}
code{background:#0f0f15;color:#22c55e;padding:2px 6px;border-radius:4px;font-size:0.85em}
</style></head><body>
<div class="container">
<h1>🔑 Token Health Dashboard</h1>
<p class="subtitle">Monitoring centralisé des credentials API providers · cascade souveraine WEVIA</p>
<section class="hero">
<div class="hero-stats" id="hero-stats">
<div class="stat"><div class="stat-val warn" id="stat-health"></div><div class="stat-lbl">Health</div></div>
<div class="stat"><div class="stat-val success" id="stat-ok"></div><div class="stat-lbl">Actifs</div></div>
<div class="stat"><div class="stat-val danger" id="stat-exp"></div><div class="stat-lbl">Expirés</div></div>
<div class="stat"><div class="stat-val" id="stat-total"></div><div class="stat-lbl">Total</div></div>
</div>
</section>
<h2 style="color:#06b6d4;margin-bottom:14px;border-left:3px solid #06b6d4;padding-left:10px">Providers Status</h2>
<div class="providers" id="providers-grid"><div style="color:#64748b">Chargement live...</div></div>
<section class="runbook">
<h2>📘 Runbook Rotation Tokens (manuel ou via Selenium blueprint)</h2>
<div class="runbook-step">
<strong>Groq</strong> · dashboard: <code>https://console.groq.com/keys</code><br>
1. Se connecter avec yacineutt@gmail.com<br>
2. Créer nouvelle API key · label: <code>weval-prod-2026</code><br>
3. Copier dans <code>/etc/weval/secrets.env</code><code>GROQ_API_KEY=gsk_...</code><br>
4. Reload: <code>sudo systemctl reload php8.5-fpm</code>
</div>
<div class="runbook-step">
<strong>SambaNova</strong> · dashboard: <code>https://cloud.sambanova.ai/</code><br>
1. Se connecter avec yacineutt@gmail.com<br>
2. Settings → API Keys → Generate new<br>
3. Update <code>SAMBANOVA_API_KEY=...</code> dans secrets.env
</div>
<div class="runbook-step">
<strong>Alibaba (DashScope)</strong> · dashboard: <code>https://dashscope.console.aliyun.com/</code><br>
1. Login Chinese account<br>
2. API Key Management → Create new<br>
3. Update <code>ALIBABA_API_KEY=sk-...</code> dans secrets.env
</div>
<div class="runbook-step">
<strong>GitHub PAT</strong> · dashboard: <code>https://github.com/settings/tokens?type=beta</code><br>
1. Regenerate → copy new <code>ghp_...</code><br>
2. Update <code>GITHUB_PAT=ghp_...</code> dans secrets.env<br>
3. Test: <code>sudo git push origin main</code>
</div>
<div class="runbook-step">
<strong>Blueprint Selenium automatisé</strong> · voir <code>/opt/obsidian-vault/doctrines/tips-6-mois-cracked.md</code><br>
Script Python + Selenium Grid Docker + Chrome + YacineUTT session sync
</div>
</section>
<section style="margin-top:20px;text-align:center;color:#64748b;font-size:0.85rem">
<button class="btn btn-primary" onclick="location.reload()">🔄 Refresh</button>
<a href="/wiki.html" class="btn btn-secondary">📘 Wiki</a>
<a href="/wtp-udock-coverage.html" class="btn btn-secondary">📊 WTP Dashboard</a>
</section>
</div>
<script>
fetch('/api/wevia-autonomy-status.json', {cache:'no-store'})
.then(r => r.json())
.then(d => {
const alerts = d.alerts || [];
const expiredList = alerts.filter(a => a.msg && /expir/i.test(a.msg)).map(a => {
const m = a.msg.match(/Token\s+(\w+)/i);
return m ? m[1].toLowerCase() : null;
}).filter(Boolean);
const KNOWN = ['cerebras','groq','deepseek','mistral','together','openrouter','sambanova',
'alibaba','gemini','nvidia-nim','cohere','huggingface','replicate','zhipu',
'anthropic','github','whatsapp'];
const health = Math.round((KNOWN.length - expiredList.length) / KNOWN.length * 100);
document.getElementById('stat-health').textContent = health + '%';
document.getElementById('stat-ok').textContent = KNOWN.length - expiredList.length;
document.getElementById('stat-exp').textContent = expiredList.length;
document.getElementById('stat-total').textContent = KNOWN.length;
const grid = document.getElementById('providers-grid');
grid.innerHTML = '';
KNOWN.forEach(p => {
const isExpired = expiredList.includes(p);
const card = document.createElement('div');
card.className = 'provider ' + (isExpired ? 'expired' : 'ok');
card.innerHTML = `
<div class="provider-name">
<span>${p}</span>
<span class="badge ${isExpired?'expired':'ok'}">${isExpired?'EXPIRED':'OK'}</span>
</div>
<div class="provider-meta">${isExpired ? '⚠️ Rotation requise' : '✅ Active'}</div>
${isExpired ? '<div class="provider-action"><button class="btn btn-primary" onclick="alert(\'Voir runbook dans cette page pour '+p+'\')">Rotate</button></div>' : ''}
`;
grid.appendChild(card);
});
})
.catch(e => {
document.getElementById('providers-grid').innerHTML = '<div style="color:#ef4444">Erreur: ' + e.message + '</div>';
});
</script>
<!-- WTP_UDOCK_V1 (Opus 21-avr t39) --><script src=\/wtp-unified-dock.js\ defer></script>
</body></html>

View File

@@ -3350,6 +3350,144 @@ if (typeof window.navigateTo === 'function'){
</script>
</section>
<section id="wtp-drill-resolver-wave214" data-added-by="opus-wave-214" style="margin:24px 16px;padding:24px;background:linear-gradient(135deg,#3b0764 0%,#083344 100%);border:1px solid #a855f7;border-radius:12px;font-family:system-ui,sans-serif;box-shadow:0 10px 40px rgba(168,85,247,.2)">
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;margin-bottom:18px">
<div>
<div style="display:flex;align-items:center;gap:10px">
<span style="font-size:22px">🧩</span>
<h2 style="margin:0;color:#e9d5ff;font-size:20px;font-weight:700">Drill-down 7σ Fails · Resolver Catalog</h2>
<span style="padding:3px 10px;border-radius:12px;background:linear-gradient(135deg,#a855f7,#22d3ee);color:#fff;font-size:10px;font-weight:700;letter-spacing:.6px">WAVE 214</span>
</div>
<p style="margin:4px 0 0 0;color:#c4b5fd;font-size:12.5px">49 dimensions à fixer vers 7σ · 310 intents Resolver · 224 wired · gap 86</p>
</div>
<div id="wtp-dr-status" style="padding:6px 14px;border-radius:16px;background:rgba(168,85,247,.15);color:#ddd6fe;font-size:11px;font-weight:600;border:1px solid rgba(168,85,247,.3)">LOADING</div>
</div>
<!-- Two columns: 7σ fails + Resolver catalog -->
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(340px,1fr));gap:16px">
<!-- 7σ Fails drill-down -->
<div style="padding:18px;background:rgba(255,255,255,.03);border:1px solid rgba(251,146,60,.25);border-radius:10px">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px">
<span style="font-size:18px">🎯</span>
<h3 style="margin:0;color:#fed7aa;font-size:14px;font-weight:700">7σ Fails par page</h3>
<span id="wtp-dr-7s-count" style="margin-left:auto;padding:2px 8px;border-radius:10px;background:rgba(251,146,60,.15);color:#fdba74;font-size:10px;font-weight:700">...</span>
</div>
<div id="wtp-dr-7s-grid" style="max-height:340px;overflow-y:auto"></div>
</div>
<!-- Resolver Catalog -->
<div style="padding:18px;background:rgba(255,255,255,.03);border:1px solid rgba(139,92,246,.25);border-radius:10px">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px">
<span style="font-size:18px">🧠</span>
<h3 style="margin:0;color:#ddd6fe;font-size:14px;font-weight:700">Resolver Catalog</h3>
<span id="wtp-dr-res-count" style="margin-left:auto;padding:2px 8px;border-radius:10px;background:rgba(139,92,246,.15);color:#c4b5fd;font-size:10px;font-weight:700">...</span>
</div>
<div id="wtp-dr-res-kpi" style="display:grid;grid-template-columns:repeat(4,1fr);gap:6px;margin-bottom:12px"></div>
<div id="wtp-dr-res-domains" style="max-height:270px;overflow-y:auto"></div>
</div>
</div>
<script>
(function(){
function dimChip(name, ok){
var col = ok ? '#10b981' : '#ef4444';
return '<span style="padding:1px 6px;border-radius:8px;background:'+col+'22;color:'+col+';font-size:9.5px;font-weight:700;font-family:monospace;margin:1px">'+name+'</span>';
}
function pageRow(pg){
var fails = pg.fails || [];
var total_dims = pg.total_dims || 0;
var name = pg.id || pg.url || '?';
var col = fails.length === 0 ? '#10b981' : (fails.length <= 2 ? '#fbbf24' : '#ef4444');
var chips = fails.map(function(f){ return dimChip(f,false); }).join('');
return '<div style="padding:8px 10px;margin-bottom:6px;background:rgba(255,255,255,.02);border-left:3px solid '+col+';border-radius:4px">'
+'<div style="display:flex;align-items:center;justify-content:space-between;gap:8px"><div style="font-size:11.5px;color:#fed7aa;font-family:monospace;font-weight:600">'+name+'</div><div style="font-size:10px;color:'+col+';font-weight:700">'+fails.length+' fails</div></div>'
+'<div style="margin-top:4px">'+chips+'</div>'
+'</div>';
}
function kpiCard(label, value, color){
return '<div style="padding:8px 10px;background:rgba(139,92,246,.08);border:1px solid rgba(139,92,246,.18);border-radius:6px;text-align:center">'
+'<div style="font-size:9.5px;color:#c4b5fd;text-transform:uppercase;letter-spacing:.4px">'+label+'</div>'
+'<div style="font-size:18px;font-weight:700;color:'+color+';margin-top:2px">'+value+'</div>'
+'</div>';
}
function domainChip(name, info){
var cnt = (typeof info === 'number') ? info : (info.count || info.intents || 0);
return '<div style="display:flex;align-items:center;justify-content:space-between;padding:6px 10px;margin-bottom:4px;background:rgba(139,92,246,.05);border:1px solid rgba(139,92,246,.12);border-radius:6px;font-size:11px;color:#ddd6fe"><span style="font-family:monospace">'+name+'</span><span style="font-weight:700;color:#a78bfa">'+cnt+'</span></div>';
}
function render(){
// 7σ fails
fetch('/api/seven-sigma-latest.json?cb='+Date.now())
.then(function(r){return r.json();})
.then(function(d){
var pages = d.pages || [];
var grid = document.getElementById('wtp-dr-7s-grid');
var badge = document.getElementById('wtp-dr-7s-count');
if (!grid) return;
var total_fails = 0;
var items = pages.map(function(p){
var dims = p.dimensions || {};
var fails = [];
Object.keys(dims).forEach(function(k){
var v = dims[k];
if (v && typeof v === 'object' && (v.status === 'FAIL' || v.status === 'fail')) fails.push(k);
});
total_fails += fails.length;
return {id: p.id || p.url, fails: fails, total_dims: Object.keys(dims).length};
}).sort(function(a,b){return b.fails.length - a.fails.length;});
if (badge) badge.textContent = total_fails+' fails / '+pages.length+' pages';
grid.innerHTML = items.map(pageRow).join('');
})
.catch(function(){
var grid = document.getElementById('wtp-dr-7s-grid');
if (grid) grid.innerHTML = '<div style="color:#f87171;font-size:11px;padding:10px">seven-sigma unavailable</div>';
});
// Resolver catalog
fetch('/api/arena-intent-registry.json?cb='+Date.now())
.then(function(r){return r.json();})
.then(function(d){
var kpi = document.getElementById('wtp-dr-res-kpi');
var doms = document.getElementById('wtp-dr-res-domains');
var cnt = document.getElementById('wtp-dr-res-count');
var wired = d.wired || 0;
var gap = d.gap || 0;
var total = d.total_intents || (wired + gap);
var pct = total ? Math.round(wired*100/total) : 0;
if (cnt) cnt.textContent = wired+'/'+total+' ('+pct+'%)';
if (kpi) {
kpi.innerHTML = kpiCard('INTENTS', total, '#ddd6fe')
+ kpiCard('SKILLS', d.total_skills||0, '#c4b5fd')
+ kpiCard('WIRED', wired, '#10b981')
+ kpiCard('GAP', gap, gap===0?'#10b981':'#fbbf24');
}
if (doms) {
var domains = d.domains || {};
var html = '<div style="font-size:10px;color:#c4b5fd;text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px">Domains · '+Object.keys(domains).length+'</div>';
if (Array.isArray(domains)) {
html += domains.map(function(n){return domainChip(n, 0);}).join('');
} else {
Object.keys(domains).forEach(function(k){ html += domainChip(k, domains[k]); });
}
doms.innerHTML = html;
}
})
.catch(function(){
var doms = document.getElementById('wtp-dr-res-domains');
if (doms) doms.innerHTML = '<div style="color:#f87171;font-size:11px;padding:10px">registry unavailable</div>';
});
var status = document.getElementById('wtp-dr-status');
if (status) { status.textContent = 'LIVE'; status.style.background='rgba(16,185,129,.2)'; status.style.color='#6ee7b7'; }
}
render();
setInterval(render, 180000);
})();
</script>
</section>
<section id="wtp-visual-mgmt-wave212" data-added-by="opus-wave-212" style="margin:32px 16px 20px;padding:28px;background:linear-gradient(135deg,#164e63 0%,#1e3a8a 100%);border:1px solid #0ea5e9;border-radius:14px;font-family:system-ui,sans-serif;box-shadow:0 10px 40px rgba(14,165,233,.2)">
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;margin-bottom:18px">
<div>