feat(cs-automation-full): feature-adoption endpoint sovereign (21 features tracked) + JS tracker auto-inject 4 entry points (WTP + wevia-master + all-ia-hub + orchestrator) + NPS popup after 10 queries + v83 feature_adoption_rate wired live - 4 ACTIONS AUTO as requested

This commit is contained in:
opus
2026-04-21 22:58:48 +02:00
parent a705e42253
commit d98131946e
7 changed files with 232 additions and 1 deletions

View File

@@ -1324,5 +1324,6 @@ setInterval(refreshStats,60000);
<span style="margin-left:auto;color:#00d4b4;font-size:9px"><a href="/wevia-unified-hub.html" style="color:inherit;text-decoration:none" title="Truth Hub">Truth &rarr;</a></span>
</div>
<script>(function(){var p=window.location.pathname;var pub=["/","/index.html","/wevia.html","/wevia-widget.html","/enterprise-model.html","/wevia","/login","/register.html","/agents-archi.html","/wevia-meeting-rooms.html","/director-center.html","/director-chat.html","/l99-brain.html","/agents-fleet.html","/value-streaming.html","/architecture.html","/openclaw.html","/l99-saas.html","/admin-saas.html","/agents-goodjob.html","/ai-benchmark.html","/oss-discovery.html","/paperclip.html","/agents-3d.html","/agents-alive.html","/agents-enterprise.html","/agents-hd.html","/agents-iso3d.html","/agents-sim.html","/agents-valuechain.html","/avatar-picker.html"];var isPub=pub.indexOf(p)>=0||p.indexOf("/products/")===0||p.indexOf("/solutions/")===0||p.indexOf("/blog/")===0||p.indexOf("/service/")===0||p.indexOf("/marketplace")===0||p.indexOf("/contact")===0||p.indexOf("/tarifs")===0||p.indexOf("/news")===0;if(isPub||document.getElementById("weval-gl"))return;var a=document.createElement("a");a.id="weval-gl";a.href="/logout";a.textContent="Logout";a.style.cssText="position:fixed;top:10px;right:12px;z-index:99990;padding:5px 10px;background:rgba(30,30,50,0.7);color:rgba(200,210,230,0.8);border:1px solid rgba(100,100,140,0.3);border-radius:6px;font:500 11px system-ui,sans-serif;text-decoration:none;opacity:0.6;cursor:pointer;backdrop-filter:blur(6px);transition:all .15s";a.onmouseover=function(){this.style.opacity="1";this.style.background="rgba(239,68,68,0.85)";this.style.color="white"};a.onmouseout=function(){this.style.opacity="0.6";this.style.background="rgba(30,30,50,0.7)";this.style.color="rgba(200,210,230,0.8)"};document.body.appendChild(a)})()</script><script src="/opus-antioverlap-doctrine.js?v=1776776094" defer></script>
<script src="/api/weval-feature-tracker.js" defer></script>
</body>
</html>

96
api/feature-adoption.php Normal file
View File

@@ -0,0 +1,96 @@
<?php
/**
* WEVAL Feature Adoption Tracker sovereign 21avr2026
* Track which modules/widgets/features users interact with per session
* Storage: /opt/weval-l99/data/feature-adoption.jsonl
*
* GET ?action=stats -> adoption rate KPI
* POST ?action=track -> log feature use (feature, user, session_id)
* GET ?action=list -> features inventory
*/
header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *");
$STORAGE = "/opt/weval-l99/data/feature-adoption.jsonl";
$INVENTORY = "/opt/weval-l99/data/feature-inventory.json";
@mkdir(dirname($STORAGE), 0755, true);
// Default inventory if not exists
if (!file_exists($INVENTORY)) {
$default_features = [
"wtp_dashboard", "wtp_pilotage_widget", "wtp_sparklines", "wtp_l99_brain",
"wevia_master_chat", "wevia_orchestrator", "all_ia_hub", "agents_archi",
"architecture_live", "openclaw", "nonreg_dashboard", "stripe_live",
"ethica_hcp", "wevads_ia", "director_chat", "orphans_hub",
"wikidoc", "vault_manager", "nps_feedback", "csat_rating", "ticket_create"
];
@file_put_contents($INVENTORY, json_encode(["features" => $default_features, "total" => count($default_features)]));
}
$action = $_GET["action"] ?? ($_POST["action"] ?? "stats");
if ($action === "track" && $_SERVER["REQUEST_METHOD"] === "POST") {
$feature = substr(trim($_POST["feature"] ?? ""), 0, 80);
$user = substr(trim($_POST["user"] ?? "anonymous"), 0, 60);
$session = substr(trim($_POST["session_id"] ?? ""), 0, 40);
if (!$feature) {
echo json_encode(["ok"=>false,"error"=>"feature_required"]);
exit;
}
$record = ["ts"=>date("c"),"feature"=>$feature,"user"=>$user,"session_id"=>$session];
@file_put_contents($STORAGE, json_encode($record)."\n", FILE_APPEND | LOCK_EX);
echo json_encode(["ok"=>true,"tracked"=>$feature]);
exit;
}
if ($action === "list") {
$inv = @json_decode(@file_get_contents($INVENTORY), true);
echo json_encode($inv ?: ["features"=>[],"total"=>0]);
exit;
}
// stats = adoption rate calculation
$inv = @json_decode(@file_get_contents($INVENTORY), true);
$total_features = intval($inv["total"] ?? 0);
$used_features = [];
$events = [];
if (is_readable($STORAGE)) {
foreach (file($STORAGE) as $line) {
$r = @json_decode(trim($line), true);
if ($r && isset($r["feature"])) {
$used_features[$r["feature"]] = ($used_features[$r["feature"]] ?? 0) + 1;
$events[] = $r;
}
}
}
$adopted = count($used_features);
$adoption_rate = $total_features > 0 ? round(($adopted / $total_features) * 100) : 0;
// 7d & 30d activity
$now = time();
$evt_7d = 0; $evt_30d = 0;
$users_7d = []; $users_30d = [];
foreach ($events as $e) {
$t = strtotime($e["ts"]);
if ($t >= $now - 7*86400) { $evt_7d++; $users_7d[$e["user"] ?? ""] = true; }
if ($t >= $now - 30*86400) { $evt_30d++; $users_30d[$e["user"] ?? ""] = true; }
}
echo json_encode([
"ok"=>true,
"source"=>"sovereign_jsonl_tracker",
"ts"=>date("c"),
"adoption_rate_pct"=>$adoption_rate,
"features_total"=>$total_features,
"features_adopted"=>$adopted,
"features_top5"=>array_slice(array_reverse(array_keys($used_features)), 0, 5),
"events_total"=>count($events),
"events_7d"=>$evt_7d,
"events_30d"=>$evt_30d,
"unique_users_7d"=>count($users_7d),
"unique_users_30d"=>count($users_30d),
"status"=>count($events)===0 ? "wire_needed" : ($adoption_rate >= 70 ? "ok" : "warn"),
"drill"=>"Features used / Features available · Track via POST ?action=track",
]);

View File

@@ -0,0 +1,131 @@
/**
* WEVAL Feature Tracker + NPS Popup + CSAT autolink
* Zero external deps. Zero cookies. sessionStorage only.
* Auto-injects: track current page as feature + show NPS popup after 10 queries
*/
(function(){
if(window.__WEVAL_TRACKER_LOADED__) return;
window.__WEVAL_TRACKER_LOADED__ = true;
var path = window.location.pathname.replace(/^\/+|\.html$/g,'').replace(/\//g,'_') || 'index';
var feature = path.toLowerCase();
var user = (function(){
try { return localStorage.getItem('weval_user') || 'anonymous'; } catch(e) { return 'anonymous'; }
})();
var sessionId = (function(){
try {
var s = sessionStorage.getItem('weval_session_id');
if(!s) { s = 's_' + Date.now() + '_' + Math.random().toString(36).slice(2,8); sessionStorage.setItem('weval_session_id', s); }
return s;
} catch(e) { return 's_na'; }
})();
// 1. Track feature once per session per page
try {
var key = 'weval_tracked_' + feature;
if(!sessionStorage.getItem(key)) {
sessionStorage.setItem(key, '1');
var fd = new FormData();
fd.append('action','track'); fd.append('feature',feature); fd.append('user',user); fd.append('session_id',sessionId);
fetch('/api/feature-adoption.php?action=track', {method:'POST', body:fd}).catch(function(){});
}
} catch(e){}
// 2. Query counter for NPS popup trigger (only on wevia-master + all-ia-hub + wtp)
var qCounterPages = ['wevia-master','all-ia-hub','weval-technology-platform','wevia-orchestrator'];
if(qCounterPages.indexOf(feature) >= 0) {
try {
var qKey = 'weval_query_count';
var npsShown = localStorage.getItem('weval_nps_shown_30d');
var now = Date.now();
// NPS not shown in last 30d
if(!npsShown || (now - parseInt(npsShown)) > 30*86400000) {
// Count queries - increment on every significant interaction
document.addEventListener('click', function(e){
var tgt = e.target;
// Count clicks on buttons/send actions only
if(tgt.matches && (tgt.matches('button, [type="submit"], .btn, .send-button, [onclick*="send"]') || (tgt.closest && tgt.closest('button, .btn')))) {
var count = parseInt(localStorage.getItem(qKey) || '0') + 1;
localStorage.setItem(qKey, count);
if(count >= 10 && !document.getElementById('weval-nps-popup-el')) {
showNPSPopup();
}
}
}, true);
}
} catch(e){}
}
// 3. NPS Popup injection
function showNPSPopup() {
var pop = document.createElement('div');
pop.id = 'weval-nps-popup-el';
pop.innerHTML = [
'<div id="weval-nps-overlay" style="position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:99999;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(4px);font-family:system-ui,sans-serif">',
'<div style="background:linear-gradient(135deg,#0f172a,#1e293b);border:1px solid #334a7a;border-radius:12px;padding:28px;max-width:480px;width:90%;box-shadow:0 20px 60px rgba(0,0,0,.5)">',
'<div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:16px">',
'<h2 style="margin:0;color:#e2e8f0;font-size:19px">Comment notez-vous WEVAL ?</h2>',
'<button id="weval-nps-close" style="background:none;border:none;color:#64748b;font-size:22px;cursor:pointer;line-height:1" aria-label="Fermer">&times;</button>',
'</div>',
'<p style="margin:0 0 18px 0;color:#94a3b8;font-size:13.5px;line-height:1.5">Sur une échelle de 0 à 10, recommanderiez-vous WEVAL à un collègue ?</p>',
'<div id="weval-nps-scores" style="display:grid;grid-template-columns:repeat(11,1fr);gap:4px;margin-bottom:16px"></div>',
'<textarea id="weval-nps-comment" placeholder="Commentaire optionnel..." style="width:100%;padding:10px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);border-radius:6px;color:#e2e8f0;font-size:13px;font-family:inherit;resize:vertical;min-height:60px;box-sizing:border-box"></textarea>',
'<div id="weval-nps-thanks" style="display:none;margin-top:14px;padding:10px;background:rgba(16,185,129,.12);border:1px solid rgba(16,185,129,.3);border-radius:6px;color:#6ee7b7;font-size:13px;text-align:center">Merci pour votre retour !</div>',
'<div style="margin-top:14px;display:flex;gap:8px;justify-content:flex-end">',
'<button id="weval-nps-later" style="padding:8px 14px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);color:#94a3b8;border-radius:6px;cursor:pointer;font-size:12px">Plus tard</button>',
'<button id="weval-nps-submit" disabled style="padding:8px 14px;background:#3b82f6;border:none;color:#fff;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600;opacity:.5">Envoyer</button>',
'</div></div></div>'
].join('');
document.body.appendChild(pop);
// Score buttons 0-10
var scoresDiv = document.getElementById('weval-nps-scores');
var selectedScore = null;
for(var i=0;i<=10;i++){
(function(score){
var b = document.createElement('button');
b.textContent = score;
b.style.cssText = 'padding:10px 4px;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.12);color:#cbd5e1;border-radius:6px;cursor:pointer;font-size:13px;font-weight:600;transition:all .12s';
b.onmouseover = function(){ if(selectedScore!==score){ this.style.background='rgba(59,130,246,.15)'; this.style.borderColor='#3b82f6'; } };
b.onmouseout = function(){ if(selectedScore!==score){ this.style.background='rgba(255,255,255,.05)'; this.style.borderColor='rgba(255,255,255,.12)'; } };
b.onclick = function(){
selectedScore = score;
Array.from(scoresDiv.children).forEach(function(el){ el.style.background='rgba(255,255,255,.05)'; el.style.borderColor='rgba(255,255,255,.12)'; el.style.color='#cbd5e1'; });
this.style.background = score>=9?'#10b981':(score>=7?'#3b82f6':'#ef4444');
this.style.borderColor = 'transparent';
this.style.color = '#fff';
var sb = document.getElementById('weval-nps-submit');
sb.disabled = false; sb.style.opacity = '1';
};
scoresDiv.appendChild(b);
})(i);
}
function close(){
var el = document.getElementById('weval-nps-popup-el');
if(el) el.remove();
}
document.getElementById('weval-nps-close').onclick = function(){
try { localStorage.setItem('weval_nps_shown_30d', Date.now()); } catch(e){}
close();
};
document.getElementById('weval-nps-later').onclick = function(){
try { localStorage.removeItem('weval_query_count'); } catch(e){}
close();
};
document.getElementById('weval-nps-submit').onclick = function(){
if(selectedScore === null) return;
var comment = document.getElementById('weval-nps-comment').value;
var fd = new FormData();
fd.append('action','submit'); fd.append('score',selectedScore); fd.append('comment',comment); fd.append('user',user);
fetch('/api/nps-collector.php?action=submit', {method:'POST', body:fd})
.then(function(){
document.getElementById('weval-nps-thanks').style.display = 'block';
try { localStorage.setItem('weval_nps_shown_30d', Date.now()); localStorage.removeItem('weval_query_count'); } catch(e){}
setTimeout(close, 1800);
})
.catch(function(){ setTimeout(close, 400); });
};
}
// Expose globally for manual trigger + dev testing
window.wevalShowNPS = showNPSPopup;
})();

View File

@@ -138,7 +138,7 @@ $kpis = [
["id" => "support_tickets_open", "label" => "Support tickets open", "value" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/tickets-api.php"),true); return intval($r["tickets_open"]??0);})(), "unit" => "tickets", "target" => 5, "trend" => "live", "status" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/tickets-api.php"),true); $o=intval($r["tickets_open"]??0); $t=intval($r["tickets_total"]??0); if($t===0) return "wire_needed"; return $o<=5?"ok":"warn";})(), "source" => "sovereign tickets /api/tickets-api.php", "drill" => "POST subject+body · statuses: open/resolved/closed"],
["id" => "mean_time_to_resolution", "label" => "MTTR support", "value" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/tickets-api.php"),true); return floatval($r["mttr_hours"]??0);})(), "unit" => "hours", "target" => 24, "trend" => "live", "status" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/tickets-api.php"),true); $m=floatval($r["mttr_hours"]??0); $t=intval($r["tickets_total"]??0); if($t===0) return "wire_needed"; return $m<=24?"ok":"warn";})(), "source" => "sovereign tickets MTTR", "drill" => "avg(resolved_at - ts) in hours on resolved tickets"],
["id" => "customer_health_score", "label" => "Customer health score avg", "value" => 75, "unit" => "/100", "target" => 80, "trend" => "computed", "status" => "ok", "source" => "WePredict model", "drill" => "Composite: usage + tickets + payments"],
["id" => "feature_adoption_rate", "label" => "Feature adoption", "value" => $v50["feature_adoption"], "unit" => "%", "target" => 70, "trend" => "live", "status" => $v50["feature_adoption"] >= 70 ? "ok" : "warn", "source" => "Platform telemetry", "drill" => "Features used / features available"]
["id" => "feature_adoption_rate", "label" => "Feature adoption", "value" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/feature-adoption.php"),true); return intval($r["adoption_rate_pct"]??0);})(), "unit" => "%", "target" => 70, "trend" => "live", "status" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/feature-adoption.php"),true); return $r["status"]??"wire_needed";})(), "source" => "sovereign tracker /api/feature-adoption.php", "drill" => "Features adopted / Features inventory (21 features tracked)"]
]
],

View File

@@ -4802,5 +4802,6 @@ if (typeof window.navigateTo === 'function'){
<!-- ═══ AMBRE-V1-ERP-KPI-LIVE END ═══ -->
<script src="/opus-antioverlap-doctrine.js?v=1776777074" defer></script>
<script src="/api/weval-feature-tracker.js" defer></script>
</body>
</html>

View File

@@ -456,5 +456,6 @@ const _origAddA = typeof addMsg === 'function' ? addMsg : null;
<script src="/api/a11y-auto-enhancer.js" defer></script>
<script src="/opus-antioverlap-doctrine.js?v=1776776094" defer></script>
<script src="/api/weval-feature-tracker.js" defer></script>
</body>
</html>

View File

@@ -597,5 +597,6 @@ async function runTask(preset){
<script src="/api/a11y-auto-enhancer.js" defer></script>
<!-- WTP_UDOCK_V1 (Opus 21-avr) --><script src="/wtp-unified-dock.js" defer></script>
<script src="/opus-antioverlap-doctrine.js?v=1776776094" defer></script>
<script src="/api/weval-feature-tracker.js" defer></script>
</body>
</html>