diff --git a/api/ambre-kill25.php b/api/ambre-kill25.php
new file mode 100644
index 000000000..ac553bc74
--- /dev/null
+++ b/api/ambre-kill25.php
@@ -0,0 +1,5 @@
+&1");
+echo "killed\n";
+echo @shell_exec("pgrep -af playwright | head -5");
diff --git a/api/ambre-pw-tests/output/.playwright-artifacts-0/page@0f3e0ae59c574465f872a8b6b62f1bd0.webm b/api/ambre-pw-tests/output/.playwright-artifacts-0/page@0f3e0ae59c574465f872a8b6b62f1bd0.webm
deleted file mode 100644
index 9d6b10769..000000000
Binary files a/api/ambre-pw-tests/output/.playwright-artifacts-0/page@0f3e0ae59c574465f872a8b6b62f1bd0.webm and /dev/null differ
diff --git a/api/ambre-pw-tests/output/.playwright-artifacts-0/page@98b514225ae9d8c4a090ae10741d9292.webm b/api/ambre-pw-tests/output/.playwright-artifacts-0/page@98b514225ae9d8c4a090ae10741d9292.webm
new file mode 100644
index 000000000..e69de29bb
diff --git a/api/ambre-pw-tests/output/v25-00-landing.png b/api/ambre-pw-tests/output/v25-00-landing.png
deleted file mode 100644
index 2c7d2d014..000000000
Binary files a/api/ambre-pw-tests/output/v25-00-landing.png and /dev/null differ
diff --git a/api/ambre-pw-tests/output/v25-01-01-intro.png b/api/ambre-pw-tests/output/v25-01-01-intro.png
deleted file mode 100644
index 1df1b72c4..000000000
Binary files a/api/ambre-pw-tests/output/v25-01-01-intro.png and /dev/null differ
diff --git a/api/ambre-pw-tests/output/v25-02-02-identity.png b/api/ambre-pw-tests/output/v25-02-02-identity.png
deleted file mode 100644
index 2b424a1df..000000000
Binary files a/api/ambre-pw-tests/output/v25-02-02-identity.png and /dev/null differ
diff --git a/api/ambre-pw-tests/output/v25-03-03-calc.png b/api/ambre-pw-tests/output/v25-03-03-calc.png
deleted file mode 100644
index 3706f6785..000000000
Binary files a/api/ambre-pw-tests/output/v25-03-03-calc.png and /dev/null differ
diff --git a/api/ambre-pw-tests/output/v25-04-04-qr.png b/api/ambre-pw-tests/output/v25-04-04-qr.png
deleted file mode 100644
index bdc418804..000000000
Binary files a/api/ambre-pw-tests/output/v25-04-04-qr.png and /dev/null differ
diff --git a/api/ambre-pw-tests/tests/v25-final.spec.js b/api/ambre-pw-tests/tests/v25-final.spec.js
deleted file mode 100644
index fd1b425cd..000000000
--- a/api/ambre-pw-tests/tests/v25-final.spec.js
+++ /dev/null
@@ -1,125 +0,0 @@
-const { test } = require("@playwright/test");
-const fs = require("fs");
-
-test("V25 · FINAL · after mermaid fix · 10 turns incl PDF Premium · video longue", async ({ page }) => {
- test.setTimeout(900000);
-
- const errors = [];
- page.on("pageerror", e => errors.push("PE: " + e.message.substring(0,200)));
- page.on("console", msg => {
- if (msg.type() === "error" || msg.type() === "warning") {
- errors.push(msg.type() + ": " + msg.text().substring(0,200));
- }
- });
-
- await page.goto("/wevia.html");
- await page.evaluate(() => { try { sessionStorage.clear(); } catch(e){} });
- await page.waitForLoadState("networkidle");
- await page.waitForTimeout(3000);
-
- console.log("=== Errors on load ===");
- errors.forEach(e => console.log(" ", e));
-
- await page.screenshot({ path: "output/v25-00-landing.png" });
-
- // Check global functions
- const funcs = await page.evaluate(() => ({
- sendMsg: typeof window.sendMsg,
- send: typeof window.send,
- addMsg: typeof window.addMsg,
- }));
- console.log("\n=== Global functions ===", JSON.stringify(funcs));
-
- const turns = [
- { label: "01-intro", msg: "Bonjour", needle: /aider|ravi|bienvenue/i },
- { label: "02-identity", msg: "je m'appelle Claire, directrice innovation chez BNP Paribas", needle: /Claire/i },
- { label: "03-calc", msg: "calcule (789 * 1.15 + 250) / 4", needle: /\d{3,}/ },
- { label: "04-qr", msg: "QR code pour https://bnpparibas.com", needle: /wevia-qr-|📱/i },
- { label: "05-pdf-premium", msg: "cree un pdf premium sur: strategie IA banque 2026 avec graphiques", needle: /pdf-premium|Chart\.js|Télécharger/i },
- { label: "06-search", msg: "actualités fintech 2026", needle: /🔍|source|fintech/i },
- { label: "07-recall", msg: "comment je m'appelle et où je travaille?", needle: /Claire.*BNP|BNP.*Claire/i },
- { label: "08-emotion", msg: "je suis un peu débordée avec tous mes projets", needle: /comprends|pression|gérer|pause/i },
- { label: "09-subject", msg: "change de sujet, parle moi des dauphins", needle: /dauphin|cétacé|mammifère|intelligent|océan/i },
- { label: "10-bilan", msg: "fais le bilan de notre échange aujourd'hui", needle: /Claire|BNP|PDF|IA|dauphin|calc|QR/i },
- ];
-
- const results = [];
-
- for (let i = 0; i < turns.length; i++) {
- const t = turns[i];
- const num = String(i + 1).padStart(2, "0");
- console.log(`\n═══ [${num}/${turns.length}] ${t.label} ═══`);
- console.log(` → ${t.msg.substring(0, 100)}`);
-
- const beforeCount = await page.evaluate(() => document.querySelectorAll('.msg.assistant').length);
-
- try {
- const input = page.locator("#msgInput");
- await input.click({ force: true });
- await page.keyboard.press("Control+A");
- await page.keyboard.press("Delete");
- await input.fill(t.msg);
- await page.waitForTimeout(500);
- await input.press("Enter");
-
- const timeout = t.label.includes("pdf-premium") ? 90000 : 50000;
- const waitStart = Date.now();
- let found = false;
- let assistantReply = "";
-
- while (Date.now() - waitStart < timeout) {
- const state = await page.evaluate(() => {
- const a = Array.from(document.querySelectorAll(".msg.assistant .bubble"));
- return {
- count: a.length,
- last: a.length > 0 ? a[a.length-1].innerText : "",
- };
- });
-
- if (state.count > beforeCount) {
- assistantReply = state.last;
- // Skip if still showing error (may retry)
- if (/erreur est survenue/i.test(assistantReply) && (Date.now() - waitStart) < 15000) {
- await page.waitForTimeout(2500);
- continue;
- }
- if (t.needle.test(assistantReply)) { found = true; break; }
- if (assistantReply.length > 40 && !assistantReply.includes("erreur est")) {
- // got real reply, check needle
- if (t.needle.test(assistantReply)) { found = true; break; }
- }
- }
- await page.waitForTimeout(2000);
- }
-
- const elapsed = ((Date.now() - waitStart) / 1000).toFixed(1);
- const isError = /erreur est survenue/i.test(assistantReply);
-
- if (found) console.log(` ✅ PASS ${elapsed}s · ${assistantReply.substring(0,100)}`);
- else if (isError) console.log(` ❌ ERROR ${elapsed}s`);
- else console.log(` ⚠️ NO MATCH ${elapsed}s · reply: ${assistantReply.substring(0,150)}`);
-
- await page.evaluate(() => { const m = document.getElementById("messages"); if (m) m.scrollTop = m.scrollHeight; });
- await page.waitForTimeout(2500);
- await page.screenshot({ path: `output/v25-${num}-${t.label}.png`, fullPage: false });
-
- results.push({ turn: i+1, label: t.label, pass: found, error: isError, elapsed, reply_preview: assistantReply.substring(0,180) });
- await page.waitForTimeout(1500);
- } catch (e) {
- console.log(` ❌ EXCEPTION: ${e.message.substring(0, 120)}`);
- results.push({ turn: i+1, label: t.label, pass: false, error: true });
- }
- }
-
- await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
- await page.waitForTimeout(3000);
- await page.screenshot({ path: "output/v25-99-final.png", fullPage: true });
-
- const pass = results.filter(r=>r.pass).length;
- const errors2 = results.filter(r=>r.error).length;
- console.log(`\n═══════════════ V25 BILAN ═══════════════`);
- console.log(`${pass}/${results.length} PASS · ${errors2} errors`);
- results.forEach(r => console.log(` ${r.pass?"✅":(r.error?"❌":"⚠️")} T${r.turn} · ${r.label} · ${r.elapsed||"?"}s`));
-
- fs.writeFileSync("output/v25-results.json", JSON.stringify({ results, pass, total: results.length, load_errors: errors }, null, 2));
-});
diff --git a/api/ambre-pw-tests/tests/v28-fetch-log.spec.js b/api/ambre-pw-tests/tests/v28-fetch-log.spec.js
new file mode 100644
index 000000000..264eaeb8c
--- /dev/null
+++ b/api/ambre-pw-tests/tests/v28-fetch-log.spec.js
@@ -0,0 +1,56 @@
+const { test } = require("@playwright/test");
+
+test("V28 · wrap fetch and send HI", async ({ page }) => {
+ test.setTimeout(60000);
+
+ const netlog = [];
+ const errs = [];
+ page.on("pageerror", e => errs.push(e.message));
+ page.on("response", res => {
+ if (res.url().includes("/api/")) {
+ netlog.push({ url: res.url().split("?")[0], status: res.status() });
+ }
+ });
+
+ await page.goto("/wevia.html");
+ await page.waitForTimeout(2500);
+
+ // Monkey-patch fetch to see all calls
+ await page.evaluate(() => {
+ window._fetchLog = [];
+ const orig = window.fetch;
+ window.fetch = function(u, opts) {
+ const url = typeof u === "string" ? u : u.url;
+ window._fetchLog.push({ url: url.split("?")[0], method: (opts && opts.method) || "GET" });
+ return orig.apply(this, arguments);
+ };
+ });
+
+ // Send "HI"
+ await page.fill("#msgInput", "HI");
+ await page.waitForTimeout(300);
+ await page.press("#msgInput", "Enter");
+
+ await page.waitForTimeout(15000);
+
+ const fetchLog = await page.evaluate(() => window._fetchLog);
+ console.log("\n=== fetch calls from JS ===");
+ console.log(JSON.stringify(fetchLog, null, 2));
+
+ console.log("\n=== Network log (via Playwright) ===");
+ console.log(JSON.stringify(netlog, null, 2));
+
+ console.log("\n=== Page errors ===");
+ errs.forEach(e => console.log(" ", e.substring(0, 200)));
+
+ // DOM state
+ const domState = await page.evaluate(() => {
+ const a = document.querySelectorAll(".msg.assistant .bubble");
+ return {
+ count: a.length,
+ messages: Array.from(a).map(el => el.innerText.substring(0, 200)),
+ };
+ });
+ console.log("\n=== DOM messages ===");
+ console.log(JSON.stringify(domState, null, 2));
+});
diff --git a/api/ambre-pw-v26-deploy.php b/api/ambre-pw-v26-deploy.php
new file mode 100644
index 000000000..3ae8ea240
--- /dev/null
+++ b/api/ambre-pw-v26-deploy.php
@@ -0,0 +1,7 @@
+ $written]);
diff --git a/api/ambre-pw-v27-deploy.php b/api/ambre-pw-v27-deploy.php
new file mode 100644
index 000000000..9f8798db4
--- /dev/null
+++ b/api/ambre-pw-v27-deploy.php
@@ -0,0 +1,7 @@
+ $written]);
diff --git a/api/ambre-pw-v28-deploy.php b/api/ambre-pw-v28-deploy.php
new file mode 100644
index 000000000..12cad6c80
--- /dev/null
+++ b/api/ambre-pw-v28-deploy.php
@@ -0,0 +1,7 @@
+ $written]);
diff --git a/api/ambre-wiki-pdf-scan.php b/api/ambre-wiki-pdf-scan.php
new file mode 100644
index 000000000..4331ac70c
--- /dev/null
+++ b/api/ambre-wiki-pdf-scan.php
@@ -0,0 +1,43 @@
+[], "endpoints_pdf"=>[], "historic_pdf_mentions"=>[]];
+
+// 1. Deep search obsidian vault
+$search_terms = ["pdf premium", "pdf graphique", "pdf chart", "chart.js pdf", "pdf artefact", "reportlab chart", "matplotlib pdf", "weasyprint chart"];
+foreach (glob("/opt/obsidian-vault/**/*.md") as $f) {
+ $c = @file_get_contents($f);
+ if (!$c) continue;
+ foreach ($search_terms as $term) {
+ if (stripos($c, $term) !== false) {
+ $out["wiki_hits"][] = [
+ "file" => str_replace("/opt/obsidian-vault/", "", $f),
+ "term" => $term,
+ "size" => strlen($c),
+ "mtime" => date("Y-m-d", filemtime($f)),
+ ];
+ }
+ }
+}
+
+// 2. All PDF-related endpoints
+foreach (glob("/var/www/html/api/*pdf*.php") as $f) {
+ $out["endpoints_pdf"][] = ["name"=>basename($f), "size"=>filesize($f), "mtime"=>date("Y-m-d H:i", filemtime($f))];
+}
+foreach (glob("/var/www/html/api/*doc*.php") as $f) {
+ $out["endpoints_pdf"][] = ["name"=>basename($f), "size"=>filesize($f), "mtime"=>date("Y-m-d H:i", filemtime($f))];
+}
+
+// 3. Existing generated PDFs recent
+$gen_pdfs = [];
+foreach (glob("/var/www/html/generated/*.pdf") as $f) {
+ $gen_pdfs[] = ["name"=>basename($f), "size_kb"=>round(filesize($f)/1024, 1), "mtime"=>date("Y-m-d H:i", filemtime($f))];
+}
+usort($gen_pdfs, function($a,$b){return strcmp($b["mtime"], $a["mtime"]);});
+$out["recent_pdfs"] = array_slice($gen_pdfs, 0, 5);
+
+// 4. Look for any ambre-tool-pdf-premium.php current state
+$pdf_prem = "/var/www/html/api/ambre-tool-pdf-premium.php";
+$out["pdf_premium_exists"] = file_exists($pdf_prem);
+$out["pdf_premium_size"] = file_exists($pdf_prem) ? filesize($pdf_prem) : 0;
+
+echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
diff --git a/api/blade-actions-surfaced.json b/api/blade-actions-surfaced.json
index 56a6ecc83..ea7405df0 100644
--- a/api/blade-actions-surfaced.json
+++ b/api/blade-actions-surfaced.json
@@ -1,5 +1,5 @@
{
- "generated_at": "2026-04-22T02:05:01.403657",
+ "generated_at": "2026-04-22T02:10:01.412228",
"stats": {
"total": 48,
"pending": 31,
diff --git a/api/growth-conversion-advisor.php b/api/growth-conversion-advisor.php
new file mode 100644
index 000000000..3c9a52bc7
--- /dev/null
+++ b/api/growth-conversion-advisor.php
@@ -0,0 +1,133 @@
+'wepredict','name'=>'WePredict','url'=>'/wepredict.html','category'=>'prediction',
+ 'capability'=>'16 cockpits · 64 predictions · load/churn/ops forecast','status'=>'live','maturity'=>85,
+ 'use_for_conversion'=>'Anticipate deal close probability, churn warning, upsell timing'],
+ ['id'=>'dark_scout','name'=>'Dark Scout V83','url'=>'/v83-dark-scout-enriched.html','category'=>'intel',
+ 'capability'=>'Dark web + clearnet monitoring · threat + opportunity detection','status'=>'live','maturity'=>78,
+ 'use_for_conversion'=>'Detect client mentions, competitor pricing, early intent signals'],
+ ['id'=>'wevia_master','name'=>'WEVIA Master','url'=>'/wevia-master.html','category'=>'orchestrator',
+ 'capability'=>'269 tools Dynamic Resolver · 13 intents Wave 200 · 7 exec Wave 201','status'=>'live','maturity'=>90,
+ 'use_for_conversion'=>'Orchestrate multi-agent replies to prospects, auto-propose solutions'],
+ ['id'=>'arena','name'=>'WEVAL Arena','url'=>'/weval-arena.html','category'=>'command-center',
+ 'capability'=>'409 options · 715 enterprise agents · multi-model benchmarking','status'=>'live','maturity'=>80,
+ 'use_for_conversion'=>'A/B test LLM outputs for client docs, choose best model per use case'],
+ ['id'=>'ethica_b2b','name'=>'Ethica B2B HCP','url'=>'/consent.wevup.app','category'=>'data',
+ 'capability'=>'157K HCPs · 87% email coverage · 34 specialties · 12 sources','status'=>'live','maturity'=>82,
+ 'use_for_conversion'=>'Pharma client prospecting, targeted campaigns MENA/EU/US'],
+ ['id'=>'wevads_brain','name'=>'WEVADS Brain','url'=>'/brain-tower.html','category'=>'email-engine',
+ 'capability'=>'646 configs · 9 winners · PMTA+Kumo+Postfix triple','status'=>'live','maturity'=>95,
+ 'use_for_conversion'=>'Cold outreach industrialized · 9 sacred winners · deliverability 95%+'],
+ ['id'=>'blade_ai','name'=>'Blade AI','url'=>'/blade-ai.html','category'=>'ai-agent',
+ 'capability'=>'Web agent · desktop automation · 232 task heartbeat','status'=>'live','maturity'=>75,
+ 'use_for_conversion'=>'Auto-fill proposals, demo prep, competitor research, account creation'],
+ ['id'=>'paperclip','name'=>'Paperclip PM','url'=>'/paperclip.html','category'=>'project-mgmt',
+ 'capability'=>'848 agents · 6 projects · 9 goals · task flow','status'=>'live','maturity'=>65,
+ 'use_for_conversion'=>'Deal progression tracking, delivery SLA monitoring'],
+ ['id'=>'oss_stack','name'=>'OSS Sovereign Stack','url'=>'/api/oss-manifest.php','category'=>'toolchain',
+ 'capability'=>'10 OSS installed · 1748 MB · pandasai+Ollama, WeasyPrint, BioPython, Selenium, DocuSeal','status'=>'live','maturity'=>85,
+ 'use_for_conversion'=>'Generate proposals (WeasyPrint+DocuSeal), analyze data (pandasai+LLM), automate UI (Selenium)'],
+];
+
+// Effort/Impact matrix for top opportunities
+$opportunities = [
+ ['id'=>'vistex-cosumar','name'=>'Vistex SAP · Cosumar close','effort'=>3,'impact'=>9,'revenue_mad'=>450000,
+ 'status'=>'in_progress','time_days'=>14,'needs'=>['Lead addendum 0.8 DH/HCP counter','Portal demo'],
+ 'wevia_tools'=>['wevia_master','paperclip']],
+ ['id'=>'ethica-ma-contract','name'=>'Ethica Morocco · Kaouther Najar signing','effort'=>2,'impact'=>8,'revenue_mad'=>200000,
+ 'status'=>'in_progress','time_days'=>7,'needs'=>['Pilot consent ecm.py approval','Senders Arsenal'],
+ 'wevia_tools'=>['ethica_b2b','wevads_brain','wevia_master']],
+ ['id'=>'carrefour-retail','name'=>'Carrefour Morocco · CDC response','effort'=>5,'impact'=>8,'revenue_mad'=>350000,
+ 'status'=>'idea','time_days'=>21,'needs'=>['CDC specification','WeasyPrint proposal','Demo pharma+retail'],
+ 'wevia_tools'=>['oss_stack','wevia_master','paperclip']],
+ ['id'=>'api-hcp-package','name'=>'API HCP Maghreb · productize','effort'=>4,'impact'=>7,'revenue_mad'=>600000,
+ 'status'=>'in_progress','time_days'=>28,'needs'=>['Pricing tiers','Swagger docs','Stripe integration'],
+ 'wevia_tools'=>['ethica_b2b','arena']],
+ ['id'=>'weval-saas-freemium','name'=>'WEVAL SaaS Freemium launch','effort'=>6,'impact'=>9,'revenue_mad'=>800000,
+ 'status'=>'plan','time_days'=>45,'needs'=>['Landing','Stripe','Onboarding flow','FreePlan + Pro tier'],
+ 'wevia_tools'=>['wevia_master','blade_ai','oss_stack']],
+ ['id'=>'pharma-cloud-productize','name'=>'WEVAL Pharma Cloud productize','effort'=>5,'impact'=>8,'revenue_mad'=>500000,
+ 'status'=>'plan','time_days'=>30,'needs'=>['Package Ethica+WEVADS+Analytics','White-label','Partner channel'],
+ 'wevia_tools'=>['ethica_b2b','wevads_brain','wepredict']],
+ ['id'=>'linkedin-outbound','name'=>'LinkedIn outbound sequence','effort'=>1,'impact'=>5,'revenue_mad'=>80000,
+ 'status'=>'idea','time_days'=>3,'needs'=>['Selenium Blade sequencer','Copy 9 winners','Reply capture'],
+ 'wevia_tools'=>['blade_ai','wevads_brain']],
+ ['id'=>'stripe-consulting-pack','name'=>'Stripe Consulting pack Maghreb','effort'=>2,'impact'=>6,'revenue_mad'=>150000,
+ 'status'=>'idea','time_days'=>10,'needs'=>['Landing + calendly','Case studies','Stripe partner onboard'],
+ 'wevia_tools'=>['wevia_master']],
+ ['id'=>'seo-module-hub','name'=>'SEO Module Hub commercialization','effort'=>2,'impact'=>4,'revenue_mad'=>100000,
+ 'status'=>'plan','time_days'=>14,'needs'=>['Clients list','Pricing','Referral'],'wevia_tools'=>['paperclip']],
+ ['id'=>'huawei-refund','name'=>'Huawei Cloud refund (billing dispute)','effort'=>3,'impact'=>3,'revenue_mad'=>50000,
+ 'status'=>'in_progress','time_days'=>21,'needs'=>['Distributor switch','Docs'],'wevia_tools'=>['wevia_master']],
+];
+
+// Competitor matrix
+$competitors = [
+ ['category'=>'SAP Consulting Maghreb','competitors'=>['Vistex','Valoris','Capgemini MA'],
+ 'weval_edge'=>'SAP Ecosystem Partner · AI-augmented · sovereign stack 0€ inference','threat'=>'medium'],
+ ['category'=>'Pharma HCP Data MENA','competitors'=>['IQVIA','Veeva','Doctolib Pro'],
+ 'weval_edge'=>'157K HCPs · consent-first WevUp · 87% email coverage sovereign','threat'=>'low'],
+ ['category'=>'E-signatures MENA','competitors'=>['DocuSign','Yousign','HelloSign'],
+ 'weval_edge'=>'DocuSeal self-hosted · 0€ · data sovereignty MENA','threat'=>'high'],
+ ['category'=>'Email deliverability','competitors'=>['Mailgun','Sendgrid','Mailjet'],
+ 'weval_edge'=>'PMTA+Kumo+Postfix triple · 95%+ deliverability · own IPs','threat'=>'medium'],
+ ['category'=>'AI orchestration SMB','competitors'=>['Make.com','Zapier','n8n cloud'],
+ 'weval_edge'=>'WEVIA Master sovereign · 626 tools · 17 providers cascade 0€','threat'=>'low'],
+];
+
+// Focus & recommendations (effort/impact quadrants)
+$quick_wins = array_filter($opportunities, function($o){ return $o['effort']<=3 && $o['impact']>=7; });
+$big_bets = array_filter($opportunities, function($o){ return $o['effort']>=4 && $o['impact']>=7; });
+$fill_ins = array_filter($opportunities, function($o){ return $o['effort']<=3 && $o['impact']<7; });
+$thankless = array_filter($opportunities, function($o){ return $o['effort']>=4 && $o['impact']<7; });
+
+$total_revenue_mad_quick = array_sum(array_map(function($o){return $o['revenue_mad'];}, $quick_wins));
+$total_revenue_mad_big = array_sum(array_map(function($o){return $o['revenue_mad'];}, $big_bets));
+
+echo json_encode([
+ 'ts' => date('c'),
+ 'wave' => 228,
+ 'version' => 'deep-conversion-advisor-v1',
+ 'assets_count' => count($assets),
+ 'sovereign_ia' => $sovereign,
+ 'sovereign_ia_count' => count($sovereign),
+ 'opportunities' => array_values($opportunities),
+ 'matrix' => [
+ 'quick_wins' => array_values($quick_wins),
+ 'big_bets' => array_values($big_bets),
+ 'fill_ins' => array_values($fill_ins),
+ 'thankless' => array_values($thankless),
+ ],
+ 'matrix_revenue' => [
+ 'quick_wins_mad' => $total_revenue_mad_quick,
+ 'big_bets_mad' => $total_revenue_mad_big,
+ ],
+ 'competitors' => $competitors,
+ 'recommendations' => [
+ [ 'rank'=>1, 'action'=>'Close Vistex Cosumar (7j)', 'why'=>'Quick win maximum · 450K MAD · need Yacine call Kaouther/Olga', 'deps'=>'Lead addendum 0.8 DH final answer'],
+ [ 'rank'=>2, 'action'=>'Sign Ethica Morocco pilot (7j)', 'why'=>'Sovereign stack showcase · Kaouther Najar · 200K MAD', 'deps'=>'ecm.py pilot consent approval'],
+ [ 'rank'=>3, 'action'=>'Launch LinkedIn outbound (3j)', 'why'=>'Low effort high cadence · Blade+WEVADS automation', 'deps'=>'Selenium sequencer + 9 winners copy'],
+ [ 'rank'=>4, 'action'=>'Productize API HCP Maghreb (28j)', 'why'=>'600K MAD annual recurring · Stripe ready', 'deps'=>'Pricing tiers + Swagger public'],
+ [ 'rank'=>5, 'action'=>'Launch WEVAL SaaS Freemium (45j)', 'why'=>'Big bet 800K MAD · showcase full stack', 'deps'=>'Landing + billing + onboarding'],
+ ],
+], JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT);
diff --git a/api/v83-business-kpi-latest.json b/api/v83-business-kpi-latest.json
index 44be4ff9c..b9bacb789 100644
--- a/api/v83-business-kpi-latest.json
+++ b/api/v83-business-kpi-latest.json
@@ -1,7 +1,7 @@
{
"ok": true,
"version": "V83-business-kpi",
- "ts": "2026-04-22T00:04:42+00:00",
+ "ts": "2026-04-22T00:09:16+00:00",
"summary": {
"total_categories": 8,
"total_kpis": 64,
diff --git a/growth-engine-v2.html b/growth-engine-v2.html
index 8da5b66f4..483ecc68f 100644
--- a/growth-engine-v2.html
+++ b/growth-engine-v2.html
@@ -182,8 +182,8 @@ function buildKB(ops){
function build(){
const nav=document.getElementById('nav');
- const tabLabels={dashboard:'Dashboard',pipeline:'Pipeline CRM',plan90:'Plan 90J',social:'Réseaux & Canaux',scout:'Dark Scout',predict:'WePredict',connections:'Connexions'};
- const tabColors={dashboard:'var(--gold)',pipeline:'var(--em)',plan90:'var(--am)',social:'var(--sa)',scout:'var(--cy)',predict:'var(--ro)',connections:'var(--vi)'};/*V86*/
+ const tabLabels={dashboard:'Dashboard',advisor:'🎯 Conversion Advisor',pipeline:'Pipeline CRM',plan90:'Plan 90J',social:'Réseaux & Canaux',scout:'Dark Scout',predict:'WePredict',connections:'Connexions'};
+ const tabColors={dashboard:'var(--gold)',advisor:'#22d3ee',pipeline:'var(--em)',plan90:'var(--am)',social:'var(--sa)',scout:'var(--cy)',predict:'var(--ro)',connections:'var(--vi)'};/*V86*/
let nh='';
TABS.forEach((t,i)=>{
const on=i===0?' on':'';
@@ -411,5 +411,131 @@ document.addEventListener('DOMContentLoaded',()=>{const s=document.createElement
+
+
+
+
+