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:
@@ -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 →</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
96
api/feature-adoption.php
Normal 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",
|
||||
]);
|
||||
131
api/weval-feature-tracker.js
Normal file
131
api/weval-feature-tracker.js
Normal 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">×</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;
|
||||
})();
|
||||
@@ -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)"]
|
||||
]
|
||||
],
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user