Files
weval-l99/v73_fix_doughnut.py
2026-04-20 04:10:40 +02:00

100 lines
4.4 KiB
Python

#!/usr/bin/env python3
path = "/var/www/html/business-kpi-dashboard.php"
with open(path, "r", encoding="utf-8") as f:
c = f.read()
if "V73 FIX" in c:
print("ALREADY_FIXED")
exit(0)
# Replace the summary parsing logic with correct counting from catalog
old = """ const catalog = full.catalog || {};
const summary = (full.summary || {});
// ============= Chart 1: Status doughnut =============
const counts = {ok: summary.ok||0, warn: summary.warn||0, fail: summary.fail||0, wire_needed: summary.wire_needed||0};"""
new = """ const catalog = full.catalog || {};
// V73 FIX - action=full does NOT return summary, compute from catalog
let summary = full.summary || {};
if (!summary.ok) {
// Fetch summary separately as fallback
try {
const rS = await fetch("/api/wevia-v83-business-kpi.php?action=summary",{cache:"no-store"});
const sJ = await rS.json();
summary = sJ.summary || {};
} catch(e) { console.warn("[V71] summary fetch fail", e); }
}
// Always recompute from catalog (source of truth) if still missing
if (!summary.ok) {
summary = {ok:0, warn:0, fail:0, wire_needed:0, total_kpis:0, data_completeness_pct:0};
for (const c of Object.values(catalog)) {
for (const k of (c.kpis||[])) {
summary.total_kpis++;
if (k.status && summary[k.status] !== undefined) summary[k.status]++;
}
}
summary.data_completeness_pct = summary.total_kpis ? Math.round((summary.ok/summary.total_kpis)*1000)/10 : 0;
}
// ============= Chart 1: Status doughnut =============
const counts = {ok: summary.ok||0, warn: summary.warn||0, fail: summary.fail||0, wire_needed: summary.wire_needed||0};"""
if old in c:
c = c.replace(old, new, 1)
print("SUMMARY_FIX_APPLIED")
else:
print("OLD_PATTERN_NOT_FOUND - maybe already patched")
# Fix 2: Top 8 KPIs should show MIXED percentages, not all 100%
old_top = """ if (typeof k.value === \"number\" && typeof k.target === \"number\" && k.target > 0 && k.value > 0) {
allKpis.push({label: k.label, value: k.value, target: k.target, unit: k.unit, pct: Math.min(100, (k.value/k.target)*100)});
}"""
new_top = """ if (typeof k.value === \"number\" && typeof k.target === \"number\" && k.target > 0) {
// V73: show actual pct (can be <100 for below-target, cap at 150 visually for over-performers)
const rawPct = (k.value / k.target) * 100;
allKpis.push({label: k.label, value: k.value, target: k.target, unit: k.unit, pct: Math.min(150, rawPct), rawPct: Math.round(rawPct), status: k.status});
}"""
if old_top in c:
c = c.replace(old_top, new_top, 1)
print("TOP8_FIX_APPLIED")
# Update top8 sort - show mix (below-target first, then near-target)
old_sort = """allKpis.sort((a,b) => b.pct - a.pct);
const top8 = allKpis.slice(0, 8);
document.getElementById(\"v71-top-bars\").innerHTML = top8.map(k => `
<div class=\"v71-bar-row\">
<div class=\"l\" title=\"${k.label}\">${k.label.substring(0,22)}</div>
<div class=\"bar-wrap\"><div class=\"bar\" style=\"width:${k.pct}%\"></div></div>
<div class=\"v\">${Math.round(k.pct)}%</div>
</div>`).join(\"\");"""
new_sort = """// V73: show mix of below/on/above target for insight
allKpis.sort((a,b) => {
// Prioritize variance from 100 (show extremes)
return Math.abs(b.pct - 100) - Math.abs(a.pct - 100);
});
const top8 = allKpis.slice(0, 8);
const barColor = (pct, status) => {
if (status === \"fail\" || pct < 50) return \"linear-gradient(90deg,#fc8181,#f6ad55)\";
if (status === \"warn\" || pct < 90) return \"linear-gradient(90deg,#f6ad55,#ecc94b)\";
if (pct > 120) return \"linear-gradient(90deg,#48bb78,#6c9ef8)\";
return \"linear-gradient(90deg,#48bb78,#38b2ac)\";
};
document.getElementById(\"v71-top-bars\").innerHTML = top8.map(k => `
<div class=\"v71-bar-row\" title=\"${k.label}: ${k.value.toLocaleString(\"fr-FR\")} / target ${k.target.toLocaleString(\"fr-FR\")} ${k.unit}\">
<div class=\"l\">${k.label.substring(0,22)}</div>
<div class=\"bar-wrap\"><div class=\"bar\" style=\"width:${Math.min(100, k.pct)}%;background:${barColor(k.pct, k.status)}\"></div></div>
<div class=\"v\">${k.rawPct}%</div>
</div>`).join(\"\");"""
if old_sort in c:
c = c.replace(old_sort, new_sort, 1)
print("SORT_FIX_APPLIED")
with open(path, "w", encoding="utf-8") as f:
f.write(c)
print(f"DONE - size {len(c)}")