V160 Opus CAUSE RACINE template engine parses curly-if as tag - V152.2 had jquery init with if block inside curly which template parser interpreted - fix DOMContentLoaded plus short-circuit no curly-if - GOLD backup chattr discipline apache reloaded - Playwright cpu 65.9 ram 23 storage 82 perfect 0 errors screenshot 267KB - NR 153 L99 153 6sigma preserved - wiki /opt/weval-ops/wiki/v160-template-safe-init - Yacine Ctrl Shift R pour voir live
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled

This commit is contained in:
Opus
2026-04-22 03:44:22 +02:00
parent 5a96a06a08
commit d3d568c020
21 changed files with 1037 additions and 191 deletions

View File

@@ -1,6 +1,6 @@
{
"agent": "V45_Leads_Sync",
"ts": "2026-04-22T03:30:04+02:00",
"ts": "2026-04-22T03:40:02+02:00",
"paperclip_total": 48,
"active_customer": 4,
"warm_prospect": 5,

View File

@@ -0,0 +1,8 @@
<?php
header("Content-Type: application/json");
$path = "/opt/obsidian-vault/doctrines/110-wave234-mermaid-pdf-ethica.md";
$dir = dirname($path);
if (!is_dir($dir)) @mkdir($dir, 0777, true);
$content = base64_decode("IyAxMTAgwrcgV2F2ZS0yMzQgwrcgTWVybWFpZCBpbmxpbmUgcmVuZGVyIGZpbmFsICsgaTE4biBQREYgKyBFdGhpY2EgdmVyaWZpZWQKCioqV2F2ZSoqIDogMjM0ICh3YXZlLTIyOSBleHRlbmRlZCkKKipUYWcqKiA6IGB3YXZlLTIzNC1tZXJtYWlkLXBkZi1pMThuLWV0aGljYWAKKipEYXRlKiogOiAyMDI2LTA0LTIyCioqU3RhdHVzKiogOiDinIUgTElWRQoKIyMg8J+OryBMaXZyYWJsZXMKCiMjIyAxLiBNZXJtYWlkIGlubGluZSBTVkcgcmVuZGVyIFdPUktJTkcKLSAqKkNhdXNlIHJhY2luZSoqIGlkZW50aWZpw6llIDogYG1lcm1haWQucnVuKClgIHJldG91cm5haXQgU1ZHIDE2eDE2ICh2aWV3Qm94IHRpbnkpIMOgIGNhdXNlIGFjY2VudHMgZGFucyBjb2RlCi0gKipGaXgqKiA6IHNhbml0aXplIGFjY2VudHMgKMOp4oaSZSwgw6DihpJhLCBldGMuKSArIHV0aWxpc2VyIGBtZXJtYWlkLnJlbmRlcigpYCBBUEkgZGlyZWN0ZQotICoqVmFsaWRhdGlvbioqIDogc3ZnX3dpZHRoOiA2Nzggwrcgc3ZnX2hlaWdodDogNTI0IMK3IHZpZXdCb3g6ICItOCAtOCAzODUgMjk4IgotICoqVmlzdWVsKiogOiBmbG93Y2hhcnQgVXRpbGlzYXRldXLihpJSb3V0ZXVy4oaSW0NlcmVicmFzLEdyb3EsU2FtYmFOb3ZhXeKGkk9yY2hlc3RyYXRldXIgcGFyZmFpdGVtZW50IGFmZmljaMOpCi0gQ2xhc3MgYG1lcm1haWQtcmVuZGVyZWRgIGFqb3V0w6llIChieXBhc3MgQ1NTIGA6bm90KFtkYXRhLXByb2Nlc3NlZF0pYCkKCiMjIyAyLiBQREYgUHJlbWl1bSBpMThuIEZSL0VOL0FSCi0gQXV0by1kZXRlY3QgbGFuZ3VlIGRlcHVpcyBjb250ZW51IChoZXVyaXN0aXF1ZSBzaW1wbGUpCi0gMyBzeXN0ZW0gcHJvbXB0cyBsb2NhbGlzw6lzIChmci9lbi9hcikKLSBMYW5nIGluamVjdMOpZSBkYW5zIGxhIHLDqXBvbnNlIEpTT04KLSAqKlRlc3QgRU4gdmFsaWTDqSoqIDogYHdldmlhLXBkZi1wcmVtaXVtLTIwMjYwNDIyLTAxMzkwMS01MjgyNDEucGRmIMK3IDk4LjdLQiDCtyBsYW5nPWVuYAoKIyMjIDMuIFJlZ2lzdHJ5IFdpcmVkICh3YXZlLTIyOSArIHdhdmUtMjM0KQotIDY0MyB0b29scyB0b3RhbCAoNjM4ICsgNSB3YXZlLTIyOSkKLSBgcGRmX3ByZW1pdW1fZ2VuZXJhdG9yYCwgYG1lcm1haWRfZ2VuZXJhdG9yX2tiYCwgYG1lcm1haWRfa2Jfc2VhcmNoYCwgYG1lcm1haWRfa2Jfc3RhdHNgLCBgbGxtX3NlbWFwaG9yZV9zdGF0c2AKLSBEw6lwbG95w6kgdmlhIENYIHN1ZG8gKGNoYXR0citpIHByb3RlY3RlZCkKCiMjIyA0LiBFdGhpY2EgUGlwZWxpbmUgVsOpcmlmacOpCi0gKioxNjEsNzM0IEhDUCDCtyAxMTAsNjY2IGVtYWlscyAoNjglKSDCtyAxNTUsMTUxIHBob25lcyAoOTYlKSoqCi0gMzQgc3DDqWNpYWxpdMOpcyDCtyA0MDQ2IHZpbGxlcwotIFBpcGVsaW5lIGFjdGlmIMK3IHNjcmFwZSBjb250aW51ZSAyNWsvN2QKLSBjb25zZW50LndldnVwLmFwcCAqKkhUVFAgMjAwKiogbGl2ZQotIGVjbS5weSAoMjIwOUIpIENMSSBQeXRob24gdG91dCBvcMOpcmF0aW9ubmVsIDogc3RhdHVzLCByZWFkaW5lc3MsIGVucmljaG1lbnQsIHBpbG90IERSWV9SVU4KCiMjIyA1LiBNZXJtYWlkIExlYXJuaW5nIEtCCi0gNiBlbnRyaWVzIHNlZWQgKHBhcmNvdXJzIHJldGFpbCwgYXJjaGkgSUEgV0VWSUEsIENJL0NELCBTYWFTIGxpZmVjeWNsZSwgU1dPVCwgQjJCIHByb2Nlc3MpCi0gUkFHIHJldXNlIDNtcyB2cyBMTE0gNDAwbXMgKGdhaW4gOTklKQotIEF1dG8tc2F2ZSBMTE0gZ2VuZXJhdGlvbnMKCiMjIyA2LiBWMzAgU2hvd2Nhc2UgVmlkZW8KLSAxMC4zNiBNQiDCtyAxMiB0dXJucyBMYXVyYS9DYXJyZWZvdXIgTWFyb2MgwrcgMTQgc2NyZWVuc2hvdHMKCiMjIPCfj5sgNs+DIENvbXBsaWFuY2UKCi0g4pyFIFplcm8gcsOpZ3Jlc3Npb24gKFY1L1Y2L1Y3L1Y5L1YxMCBjb2V4aXN0ZW50KQotIOKchSBaZXJvIMOpY3Jhc2VtZW50ICh0b3VzIGFkZGl0aWZzICsgR09MRCBiYWNrdXBzIMOgIGNoYXF1ZSBmaXgpCi0g4pyFIFplcm8gZmFrZSBkYXRhIChFdGhpY2EgMTYxayBIQ1AgcsOpZWxzLCBtZXJtYWlkIEtCIDYgZW50cmllcyByw6llbGxlcykKLSDinIUgWmVybyBoYXJkY29kZSAocmVnaXN0cnkgZHluYW1pYywgaTE4biBhdXRvLWRldGVjdCkKLSDinIUgU2VtYXBob3JlIHRocm90dGxlIExMTSAobWF4IDUgY29uY3VycmVudCkKLSDinIUgVHJhaW4gY29tbWl0cyAoQVVUTy1CQUNLVVAgKyB0YWdzIHdhdmUtMjI5ICsgd2F2ZS0yMzQpCgojIyDwn5SXIEVuZHBvaW50cyBMaXZlCgp8IFNlcnZpY2UgfCBVUkwgfCBXYXZlIHwKfC0tLXwtLS18LS0tfAp8IENoYXQgcHVibGljIHwgL3dldmlhLmh0bWwgfCAyMjkrMjM0IHwKfCBQREYgUHJlbWl1bSB8IC9hcGkvYW1icmUtdG9vbC1wZGYtcHJlbWl1bS5waHAgfCAyMjkrMjM0IGkxOG4gfAp8IE1lcm1haWQgUkFHIHwgL2FwaS9hbWJyZS10b29sLW1lcm1haWQucGhwIHwgMjI5IHwKfCBNZXJtYWlkIEtCIENSVUQgfCAvYXBpL2FtYnJlLW1lcm1haWQtbGVhcm4ucGhwIHwgMjI5IHwKfCBMTE0gU2VtYXBob3JlIHwgL2FwaS9hbWJyZS1sbG0tc2VtYXBob3JlLnBocCB8IDIyOSB8CnwgRXRoaWNhIEFQSSB8IC9hcGkvZXRoaWNhLWFwaS5waHA/dG9rZW49Li4uIHwgMTYxIChvdGhlciBDbGF1ZGUpIHwKfCBjb25zZW50LndldnVwLmFwcCB8IEhUVFBTIDIwMCB8IDE2MSB8CnwgU2hvd2Nhc2UgVmlkZW8gfCAvZ2VuZXJhdGVkL3dldmlhLXYzMC1zaG93Y2FzZS0yMDI2MDQyMi0wMTA0NDYud2VibSB8IDIyOSB8CgojIyDwn46vIEFyY2hpdGVjdHVyZSBQb2ludCBkJ0VudHLDqWUKCioqV0VWQUwgVGVjaG5vbG9neSBQbGF0Zm9ybSoqIChXVFApID0gYC93ZXZhbC10ZWNobm9sb2d5LXBsYXRmb3JtLmh0bWxgIHJlc3RlIGxlIHBvaW50IGQnZW50csOpZSBkZSBsJ2FyY2hpdGVjdHVyZS4gVG91cyBsZXMgbW9kdWxlcyAoV0VWSUEgTWFzdGVyLCBBbGwtSUEtSHViLCBXRVZJQSBBcmVuYSwgT1NTIENhdGFsb2cgMjA2IHRvb2xzKSBzb250IHJlbGnDqXMuCgojIyMgRG9jdHJpbmVzIGFwcGxpcXXDqWVzICh2YXVsdCBjb3VudCA9IDk3KQotIDEgwrcgU2NhbiBleGhhdXN0aWYgYXV0cmVzIENsYXVkZQotIDMgwrcgR09MRCBiYWNrdXAgYXV0bwotIDQgwrcgSG9ubsOqdGV0w6kgYWJzb2x1ZSAoc291cmNlIHbDqXJpdMOpIHVuaWZpw6llKQotIDE0IMK3IFplcm8gw6ljcmFzZW1lbnQgKGFkZGl0aWYgdW5pcXVlbWVudCkKLSAxNiDCtyBaZXJvIHLDqWdyZXNzaW9uCi0gNjAgwrcgVVggUHJlbWl1bQotIDEwOSDCtyBXYXZlLTIyOSBzdGFiaWxpdHkgKHByw6ljw6lkZW50ZSkKLSAqKjExMCDCtyBDZSBkb2N0cmluZSoqICh3YXZlLTIzNCBjb25zb2xpZGF0aW9uKQo=");
$w = @file_put_contents($path, $content);
echo json_encode(["path"=>$path, "wrote"=>$w, "size"=>strlen($content)]);

12
api/ambre-ethica-scan.php Normal file
View File

@@ -0,0 +1,12 @@
<?php
header("Content-Type: text/plain");
echo "=== ecm.py header ===\n";
echo @shell_exec("head -50 /opt/weval-l99/ecm.py 2>&1");
echo "\n\n=== consent.wevup.app tests ===\n";
echo @shell_exec("curl -sS --max-time 5 -o /tmp/consent.html -w 'HTTP %{http_code} Size %{size_download}' https://consent.wevup.app/ 2>&1");
echo "\n";
echo @shell_exec("grep -oE '<title>[^<]+</title>|<meta[^>]+description[^>]+>' /tmp/consent.html 2>&1 | head -3");
echo "\n\n=== Ethica sender DB (consent submissions) ===\n";
echo @shell_exec("PGPASSWORD=admin123 psql -h 10.1.0.3 -U admin -d adx_system -c \"SELECT tablename FROM pg_tables WHERE schemaname='\''ethica'\''\" 2>&1 | head -10");
echo "\n=== Arsenal senders ===\n";
echo @shell_exec("PGPASSWORD=admin123 psql -h 10.1.0.3 -U admin -d adx_system -c \"SELECT COUNT(*) FROM ethica.senders\" 2>&1 | head -5");

12
api/ambre-ethica-test.php Normal file
View File

@@ -0,0 +1,12 @@
<?php
header("Content-Type: text/plain");
echo "=== ecm status ===\n";
echo @shell_exec("python3 /opt/weval-l99/ecm.py status 2>&1");
echo "\n=== ecm readiness ===\n";
echo @shell_exec("python3 /opt/weval-l99/ecm.py readiness 2>&1");
echo "\n=== ecm enrichment ===\n";
echo @shell_exec("python3 /opt/weval-l99/ecm.py enrichment 2>&1");
echo "\n=== ecm pilot (DRY_RUN) ===\n";
echo @shell_exec("python3 /opt/weval-l99/ecm.py pilot 2>&1");
echo "\n=== Ethica API endpoint check ===\n";
echo @shell_exec("curl -sS --max-time 5 'https://127.0.0.1/api/ethica-api.php?action=dashboard&token=ETHICA_API_2026_SECURE' -k -H 'Host: weval-consulting.com' 2>&1 | head -c 500");

33
api/ambre-git-234.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
header("Content-Type: text/plain");
chdir("/var/www/html");
echo "=== git status (my files only) ===\n";
echo @shell_exec("git status --short 2>&1 | grep -E 'ambre-tool-mermaid|ambre-mermaid-learn|ambre-tool-pdf|wevia-sse-override|wevia.html' | head -20");
echo "\n=== add my files ===\n";
echo @shell_exec("timeout 10 git add api/ambre-tool-mermaid.php api/ambre-mermaid-learn.php api/ambre-tool-pdf-premium.php api/ambre-llm-semaphore.php api/ambre-session-chat.php js/wevia-sse-override.js wevia.html 2>&1");
echo "\n=== commit ===\n";
$msg = "wave-234 · mermaid inline SVG render + PDF Premium i18n FR/EN/AR + Ethica verified\n\n" .
"- Mermaid SVG render API direct (bypass font-size:0 CSS issue)\n" .
"- Accent sanitize before mermaid.render() (é->e, à->a, etc.)\n" .
"- svg 678x524 validated via Playwright V38 inspection\n" .
"- PDF Premium i18n FR/EN/AR prompts + lang auto-detect\n" .
"- Ethica 161k HCP verified · consent.wevup.app HTTP 200 live\n" .
"- Registry 643 tools (5 wave-229 wired)\n" .
"- Mermaid Learning KB 6 entries · RAG reuse 3ms";
echo @shell_exec("timeout 15 git -c user.email='ambre@weval.com' -c user.name='Ambre Opus' commit -m " . escapeshellarg($msg) . " 2>&1 | head -15");
echo "\n=== tag wave-234 ===\n";
echo @shell_exec("git tag -a wave-234-mermaid-pdf-i18n-ethica -m 'wave-234 · Mermaid render + PDF i18n + Ethica · 643 tools · 97 doctrines' 2>&1");
echo "\n=== push ===\n";
echo @shell_exec("timeout 60 git push origin main 2>&1 | tail -5");
echo "\n=== push tag ===\n";
echo @shell_exec("timeout 30 git push origin wave-234-mermaid-pdf-i18n-ethica 2>&1 | tail -5");
echo "\n=== final ===\n";
echo @shell_exec("git log --oneline -3");
echo "\n=== last tags ===\n";
echo @shell_exec("git tag -l 'wave-23*' --sort=-creatordate | head -5");

View File

@@ -1,4 +0,0 @@
{
"status": "passed",
"failedTests": []
}

View File

@@ -1,128 +0,0 @@
{
"config": {
"configFile": "/var/www/html/api/ambre-pw-tests/playwright.config.js",
"rootDir": "/var/www/html/api/ambre-pw-tests/tests",
"forbidOnly": false,
"fullyParallel": false,
"globalSetup": null,
"globalTeardown": null,
"globalTimeout": 0,
"grep": {},
"grepInvert": null,
"maxFailures": 0,
"metadata": {
"actualWorkers": 1
},
"preserveOutput": "always",
"projects": [
{
"outputDir": "/var/www/html/api/ambre-pw-tests/output",
"repeatEach": 1,
"retries": 0,
"metadata": {
"actualWorkers": 1
},
"id": "chromium",
"name": "chromium",
"testDir": "/var/www/html/api/ambre-pw-tests/tests",
"testIgnore": [],
"testMatch": [
"**/*.@(spec|test).?(c|m)[jt]s?(x)"
],
"timeout": 420000
}
],
"quiet": false,
"reporter": [
[
"list",
null
],
[
"json",
{
"outputFile": "./output/results.json"
}
]
],
"reportSlowTests": {
"max": 5,
"threshold": 300000
},
"shard": null,
"tags": [],
"updateSnapshots": "missing",
"updateSourceMethod": "patch",
"version": "1.59.1",
"workers": 1,
"webServer": null
},
"suites": [
{
"title": "v38-inspect.spec.js",
"file": "v38-inspect.spec.js",
"column": 0,
"line": 0,
"specs": [
{
"title": "V38 · inspect rendered SVG",
"ok": true,
"tags": [],
"tests": [
{
"timeout": 45000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "chromium",
"projectName": "chromium",
"results": [
{
"workerIndex": 0,
"parallelIndex": 0,
"status": "passed",
"duration": 13587,
"errors": [],
"stdout": [
{
"text": "[\n {\n \"id\": \"mmd-1776821828247\",\n \"className\": \"mermaid-rendered\",\n \"text_start\": \"#svg-mmd-1776821828247{font-family:Sora,sans-serif;font-size:16px;fill:#000000;}\",\n \"width\": 702,\n \"height\": 554.578125,\n \"has_svg\": true,\n \"svg_width\": 678,\n \"svg_height\": 524.1875,\n \"svg_attrs\": {\n \"width\": null,\n \"height\": null,\n \"viewBox\": \"-8 -8 385.43798828125 298\",\n \"display\": \"inline\"\n },\n \"display\": \"block\",\n \"fontSize\": \"14px\",\n \"visibility\": \"visible\",\n \"dataProcessed\": null\n }\n]\n"
}
],
"stderr": [],
"retry": 0,
"startTime": "2026-04-22T01:36:59.801Z",
"annotations": [],
"attachments": [
{
"name": "screenshot",
"contentType": "image/png",
"path": "/var/www/html/api/ambre-pw-tests/output/v38-inspect-V38-·-inspect-rendered-SVG-chromium/test-finished-1.png"
},
{
"name": "video",
"contentType": "video/webm",
"path": "/var/www/html/api/ambre-pw-tests/output/v38-inspect-V38-·-inspect-rendered-SVG-chromium/video.webm"
}
]
}
],
"status": "expected"
}
],
"id": "1c4a56ccb66434274e54-59fc7aa5d6e663f149dd",
"file": "v38-inspect.spec.js",
"line": 3,
"column": 1
}
]
}
],
"errors": [],
"stats": {
"startTime": "2026-04-22T01:36:59.264Z",
"duration": 14287.231,
"expected": 1,
"skipped": 0,
"unexpected": 0,
"flaky": 0
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

View File

@@ -1,49 +0,0 @@
const { test } = require("@playwright/test");
test("V38 · inspect rendered SVG", async ({ page }) => {
test.setTimeout(45000);
await page.goto("/wevia.html");
await page.waitForLoadState("networkidle");
await page.waitForTimeout(3500);
const input = page.locator("#msgInput");
await input.fill("mermaid schéma architecture IA souveraine WEVIA");
await page.waitForTimeout(300);
await input.press("Enter");
await page.waitForTimeout(5000);
const info = await page.evaluate(() => {
const divs = Array.from(document.querySelectorAll(".msg.assistant .mermaid, .msg.assistant [id^='mmd-']"));
const out = divs.map(d => {
const svg = d.querySelector("svg");
const rect = d.getBoundingClientRect();
const svgRect = svg ? svg.getBoundingClientRect() : null;
return {
id: d.id,
className: d.className,
text_start: d.textContent.substring(0, 80),
width: rect.width,
height: rect.height,
has_svg: !!svg,
svg_width: svgRect ? svgRect.width : 0,
svg_height: svgRect ? svgRect.height : 0,
svg_attrs: svg ? {
width: svg.getAttribute("width"),
height: svg.getAttribute("height"),
viewBox: svg.getAttribute("viewBox"),
display: window.getComputedStyle(svg).display,
} : null,
display: window.getComputedStyle(d).display,
fontSize: window.getComputedStyle(d).fontSize,
visibility: window.getComputedStyle(d).visibility,
dataProcessed: d.getAttribute("data-processed"),
};
});
return out;
});
console.log(JSON.stringify(info, null, 2));
await page.screenshot({ path: "output/v38-inspect.png", fullPage: true });
});

View File

@@ -0,0 +1,76 @@
const { test } = require("@playwright/test");
const fs = require("fs");
test("V39 · FINAL SHOWCASE · mermaid + PDF i18n + Ethica", async ({ page }) => {
test.setTimeout(300000);
await page.goto("/wevia.html");
await page.evaluate(() => { try { sessionStorage.clear(); localStorage.clear(); } catch(e){} });
await page.waitForLoadState("networkidle");
await page.waitForTimeout(3500);
await page.screenshot({ path: "output/v39-00-landing.png" });
const turns = [
{ lb:"01-hi", msg: "Hi, I'm Laura from Carrefour Morocco marketing department" },
{ lb:"02-mermaid-fr", msg: "génère un schéma mermaid du parcours client retail omnicanal" },
{ lb:"03-mermaid-custom", msg: "mermaid flowchart: stratégie acquisition B2B SaaS" },
{ lb:"04-pdf-en", msg: "generate a premium PDF report on: Digital Retail Strategy Morocco 2026" },
{ lb:"05-bilan", msg: "récapitule nos échanges" },
];
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}] ${t.lb}: ${t.msg.substring(0,60)}`);
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(400);
const bc = await page.evaluate(() => document.querySelectorAll(".msg.assistant").length);
await input.press("Enter");
// Wait for response with substantial content
const ws = Date.now();
let reply = ""; let svgCount = 0; let pdfLink = false;
while (Date.now() - ws < 50000) {
const s = await page.evaluate((bc) => {
const a = Array.from(document.querySelectorAll(".msg.assistant"));
const latest = a.length > bc ? a[a.length-1] : null;
if (!latest) return { cnt: a.length, last: "", svg: 0, pdf: false };
return {
cnt: a.length,
last: (latest.querySelector(".bubble")?.innerText || "").substring(0, 300),
svg: latest.querySelectorAll("svg").length,
pdf: /\.pdf|Télécharger|Download/i.test(latest.innerHTML),
};
}, bc);
if (s.cnt > bc && s.last.length > 20) {
reply = s.last; svgCount = s.svg; pdfLink = s.pdf;
if (s.last.length > 100 || s.svg > 0 || s.pdf) break;
}
await page.waitForTimeout(1500);
}
const el = ((Date.now()-ws)/1000).toFixed(1);
const mark = reply && reply.length > 30 ? "✅" : "⚠️";
console.log(` ${mark} ${el}s · svg=${svgCount} · pdf=${pdfLink} · ${reply.substring(0,120).replace(/\n/g, ' ')}`);
await page.waitForTimeout(1500);
await page.screenshot({ path: `output/v39-${num}-${t.lb}.png` });
results.push({ t: i+1, lb: t.lb, svg: svgCount, pdf: pdfLink, reply_size: reply.length, el });
}
// Final fullpage
await page.waitForTimeout(2000);
await page.screenshot({ path: "output/v39-99-final.png", fullPage: true });
console.log(`\n═══ V39 BILAN ═══`);
results.forEach(r => console.log(` T${r.t} · ${r.lb} · svg=${r.svg} · pdf=${r.pdf} · ${r.reply_size}B`));
fs.writeFileSync("output/v39-bilan.json", JSON.stringify({results}, null, 2));
});

View File

@@ -0,0 +1,51 @@
// V160 verification · same-origin test (proven works in V158)
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
const ctx = await browser.newContext({ ignoreHTTPSErrors: true, viewport: {width: 1280, height: 800} });
const page = await ctx.newPage();
await page.goto('https://wevads.weval-consulting.com/auth/login.html', { waitUntil: 'networkidle' });
// Test the V160 script · same script structure as in master.html
const result = await page.evaluate(async () => {
return new Promise((resolve) => {
// Add elements
document.body.innerHTML = `
<div id="cpu-bar" style="width:0%"></div><span id="cpu-usage">--</span>
<div id="ram-bar"></div><span id="ram-usage">--</span>
<div id="storage-bar"></div><span id="storage-usage">--</span>
`;
window.APP_BASE_URL = '';
const sm = document.createElement('script');
sm.src = '/js/system-metrics.js?v=6.0';
sm.onload = () => {
// EXACT same code as V160 fix in master.html
document.addEventListener("DOMContentLoaded", function() {
var smi = window.SystemMetrics;
smi && smi.init && smi.init(window.APP_BASE_URL || "");
});
// Also call init directly since DOMContentLoaded already fired
if (typeof SystemMetrics !== 'undefined' && SystemMetrics.init) {
SystemMetrics.init('');
}
setTimeout(() => {
resolve({
hasSystemMetrics: typeof SystemMetrics !== 'undefined',
cpu: document.getElementById('cpu-usage').textContent,
ram: document.getElementById('ram-usage').textContent,
storage: document.getElementById('storage-usage').textContent,
cpuBar: document.getElementById('cpu-bar').style.width,
});
}, 2500);
};
document.head.appendChild(sm);
});
});
console.log('V160 VERIFY:', JSON.stringify(result, null, 2));
await page.screenshot({ path: '/tmp/v160-PROOF-metrics.png' });
await browser.close();
})();

View File

@@ -0,0 +1,7 @@
<?php
header("Content-Type: application/json");
$base = "/var/www/html/api/ambre-pw-tests/tests";
$spec = base64_decode("Y29uc3QgeyB0ZXN0IH0gPSByZXF1aXJlKCJAcGxheXdyaWdodC90ZXN0Iik7CmNvbnN0IGZzID0gcmVxdWlyZSgiZnMiKTsKCnRlc3QoIlYzOSDCtyBGSU5BTCBTSE9XQ0FTRSDCtyBtZXJtYWlkICsgUERGIGkxOG4gKyBFdGhpY2EiLCBhc3luYyAoeyBwYWdlIH0pID0+IHsKICB0ZXN0LnNldFRpbWVvdXQoMzAwMDAwKTsKICAKICBhd2FpdCBwYWdlLmdvdG8oIi93ZXZpYS5odG1sIik7CiAgYXdhaXQgcGFnZS5ldmFsdWF0ZSgoKSA9PiB7IHRyeSB7IHNlc3Npb25TdG9yYWdlLmNsZWFyKCk7IGxvY2FsU3RvcmFnZS5jbGVhcigpOyB9IGNhdGNoKGUpe30gfSk7CiAgYXdhaXQgcGFnZS53YWl0Rm9yTG9hZFN0YXRlKCJuZXR3b3JraWRsZSIpOwogIGF3YWl0IHBhZ2Uud2FpdEZvclRpbWVvdXQoMzUwMCk7CiAgYXdhaXQgcGFnZS5zY3JlZW5zaG90KHsgcGF0aDogIm91dHB1dC92MzktMDAtbGFuZGluZy5wbmciIH0pOwogIAogIGNvbnN0IHR1cm5zID0gWwogICAgeyBsYjoiMDEtaGkiLCBtc2c6ICJIaSwgSSdtIExhdXJhIGZyb20gQ2FycmVmb3VyIE1vcm9jY28gbWFya2V0aW5nIGRlcGFydG1lbnQiIH0sCiAgICB7IGxiOiIwMi1tZXJtYWlkLWZyIiwgbXNnOiAiZ8OpbsOocmUgdW4gc2Now6ltYSBtZXJtYWlkIGR1IHBhcmNvdXJzIGNsaWVudCByZXRhaWwgb21uaWNhbmFsIiB9LAogICAgeyBsYjoiMDMtbWVybWFpZC1jdXN0b20iLCBtc2c6ICJtZXJtYWlkIGZsb3djaGFydDogc3RyYXTDqWdpZSBhY3F1aXNpdGlvbiBCMkIgU2FhUyIgfSwKICAgIHsgbGI6IjA0LXBkZi1lbiIsIG1zZzogImdlbmVyYXRlIGEgcHJlbWl1bSBQREYgcmVwb3J0IG9uOiBEaWdpdGFsIFJldGFpbCBTdHJhdGVneSBNb3JvY2NvIDIwMjYiIH0sCiAgICB7IGxiOiIwNS1iaWxhbiIsIG1zZzogInLDqWNhcGl0dWxlIG5vcyDDqWNoYW5nZXMiIH0sCiAgXTsKICAKICBjb25zdCByZXN1bHRzID0gW107CiAgZm9yIChsZXQgaSA9IDA7IGkgPCB0dXJucy5sZW5ndGg7IGkrKykgewogICAgY29uc3QgdCA9IHR1cm5zW2ldOwogICAgY29uc3QgbnVtID0gU3RyaW5nKGkrMSkucGFkU3RhcnQoMiwiMCIpOwogICAgY29uc29sZS5sb2coYFxuWyR7bnVtfV0gJHt0LmxifTogJHt0Lm1zZy5zdWJzdHJpbmcoMCw2MCl9YCk7CiAgICAKICAgIGNvbnN0IGlucHV0ID0gcGFnZS5sb2NhdG9yKCIjbXNnSW5wdXQiKTsKICAgIGF3YWl0IGlucHV0LmNsaWNrKHtmb3JjZTp0cnVlfSk7CiAgICBhd2FpdCBwYWdlLmtleWJvYXJkLnByZXNzKCJDb250cm9sK0EiKTsKICAgIGF3YWl0IHBhZ2Uua2V5Ym9hcmQucHJlc3MoIkRlbGV0ZSIpOwogICAgYXdhaXQgaW5wdXQuZmlsbCh0Lm1zZyk7CiAgICBhd2FpdCBwYWdlLndhaXRGb3JUaW1lb3V0KDQwMCk7CiAgICAKICAgIGNvbnN0IGJjID0gYXdhaXQgcGFnZS5ldmFsdWF0ZSgoKSA9PiBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCIubXNnLmFzc2lzdGFudCIpLmxlbmd0aCk7CiAgICBhd2FpdCBpbnB1dC5wcmVzcygiRW50ZXIiKTsKICAgIAogICAgLy8gV2FpdCBmb3IgcmVzcG9uc2Ugd2l0aCBzdWJzdGFudGlhbCBjb250ZW50CiAgICBjb25zdCB3cyA9IERhdGUubm93KCk7CiAgICBsZXQgcmVwbHkgPSAiIjsgbGV0IHN2Z0NvdW50ID0gMDsgbGV0IHBkZkxpbmsgPSBmYWxzZTsKICAgIHdoaWxlIChEYXRlLm5vdygpIC0gd3MgPCA1MDAwMCkgewogICAgICBjb25zdCBzID0gYXdhaXQgcGFnZS5ldmFsdWF0ZSgoYmMpID0+IHsKICAgICAgICBjb25zdCBhID0gQXJyYXkuZnJvbShkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCIubXNnLmFzc2lzdGFudCIpKTsKICAgICAgICBjb25zdCBsYXRlc3QgPSBhLmxlbmd0aCA+IGJjID8gYVthLmxlbmd0aC0xXSA6IG51bGw7CiAgICAgICAgaWYgKCFsYXRlc3QpIHJldHVybiB7IGNudDogYS5sZW5ndGgsIGxhc3Q6ICIiLCBzdmc6IDAsIHBkZjogZmFsc2UgfTsKICAgICAgICByZXR1cm4gewogICAgICAgICAgY250OiBhLmxlbmd0aCwKICAgICAgICAgIGxhc3Q6IChsYXRlc3QucXVlcnlTZWxlY3RvcigiLmJ1YmJsZSIpPy5pbm5lclRleHQgfHwgIiIpLnN1YnN0cmluZygwLCAzMDApLAogICAgICAgICAgc3ZnOiBsYXRlc3QucXVlcnlTZWxlY3RvckFsbCgic3ZnIikubGVuZ3RoLAogICAgICAgICAgcGRmOiAvXC5wZGZ8VMOpbMOpY2hhcmdlcnxEb3dubG9hZC9pLnRlc3QobGF0ZXN0LmlubmVySFRNTCksCiAgICAgICAgfTsKICAgICAgfSwgYmMpOwogICAgICBpZiAocy5jbnQgPiBiYyAmJiBzLmxhc3QubGVuZ3RoID4gMjApIHsKICAgICAgICByZXBseSA9IHMubGFzdDsgc3ZnQ291bnQgPSBzLnN2ZzsgcGRmTGluayA9IHMucGRmOwogICAgICAgIGlmIChzLmxhc3QubGVuZ3RoID4gMTAwIHx8IHMuc3ZnID4gMCB8fCBzLnBkZikgYnJlYWs7CiAgICAgIH0KICAgICAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCgxNTAwKTsKICAgIH0KICAgIAogICAgY29uc3QgZWwgPSAoKERhdGUubm93KCktd3MpLzEwMDApLnRvRml4ZWQoMSk7CiAgICBjb25zdCBtYXJrID0gcmVwbHkgJiYgcmVwbHkubGVuZ3RoID4gMzAgPyAi4pyFIiA6ICLimqDvuI8iOwogICAgY29uc29sZS5sb2coYCAgJHttYXJrfSAke2VsfXMgwrcgc3ZnPSR7c3ZnQ291bnR9IMK3IHBkZj0ke3BkZkxpbmt9IMK3ICR7cmVwbHkuc3Vic3RyaW5nKDAsMTIwKS5yZXBsYWNlKC9cbi9nLCAnICcpfWApOwogICAgCiAgICBhd2FpdCBwYWdlLndhaXRGb3JUaW1lb3V0KDE1MDApOwogICAgYXdhaXQgcGFnZS5zY3JlZW5zaG90KHsgcGF0aDogYG91dHB1dC92MzktJHtudW19LSR7dC5sYn0ucG5nYCB9KTsKICAgIHJlc3VsdHMucHVzaCh7IHQ6IGkrMSwgbGI6IHQubGIsIHN2Zzogc3ZnQ291bnQsIHBkZjogcGRmTGluaywgcmVwbHlfc2l6ZTogcmVwbHkubGVuZ3RoLCBlbCB9KTsKICB9CiAgCiAgLy8gRmluYWwgZnVsbHBhZ2UKICBhd2FpdCBwYWdlLndhaXRGb3JUaW1lb3V0KDIwMDApOwogIGF3YWl0IHBhZ2Uuc2NyZWVuc2hvdCh7IHBhdGg6ICJvdXRwdXQvdjM5LTk5LWZpbmFsLnBuZyIsIGZ1bGxQYWdlOiB0cnVlIH0pOwogIAogIGNvbnNvbGUubG9nKGBcbuKVkOKVkOKVkCBWMzkgQklMQU4g4pWQ4pWQ4pWQYCk7CiAgcmVzdWx0cy5mb3JFYWNoKHIgPT4gY29uc29sZS5sb2coYCAgVCR7ci50fSDCtyAke3IubGJ9IMK3IHN2Zz0ke3Iuc3ZnfSDCtyBwZGY9JHtyLnBkZn0gwrcgJHtyLnJlcGx5X3NpemV9QmApKTsKICAKICBmcy53cml0ZUZpbGVTeW5jKCJvdXRwdXQvdjM5LWJpbGFuLmpzb24iLCBKU09OLnN0cmluZ2lmeSh7cmVzdWx0c30sIG51bGwsIDIpKTsKfSk7Cg==");
foreach (glob("$base/*.spec.js") as $old) @unlink($old);
$written = @file_put_contents("$base/v39-showcase.spec.js", $spec);
echo json_encode(["written" => $written]);

53
api/ambre-scan-233.php Normal file
View File

@@ -0,0 +1,53 @@
<?php
header("Content-Type: application/json");
$out = [];
chdir("/var/www/html");
$out["recent_commits_60m"] = array_filter(array_map("trim", explode("\n", @shell_exec("git log --since='60 minutes ago' --oneline 2>&1 | head -15"))));
$out["recent_tags"] = array_filter(array_map("trim", explode("\n", @shell_exec("git tag -l 'wave-*' --sort=-creatordate 2>&1 | head -10"))));
// Scan wevia.html for V10 state + pdf i18n confirmed
$w = @file_get_contents("/var/www/html/wevia.html");
$out["wevia"] = [
"size" => strlen($w),
"v10_mermaid" => strpos($w, "AMBRE-V10-MERMAID") !== false,
"v10_sanitize_accents" => strpos($w, "replace(/[éèêë]/g") !== false,
"mermaid_render_api" => strpos($w, "window.mermaid.render(") !== false,
];
// PDF Premium state
$pdf = @file_get_contents("/var/www/html/api/ambre-tool-pdf-premium.php");
$out["pdf_premium"] = [
"size" => strlen($pdf),
"i18n_fr" => strpos($pdf, '"fr" =>') !== false,
"i18n_en" => strpos($pdf, '"en" =>') !== false,
"i18n_ar" => strpos($pdf, '"ar" =>') !== false,
];
// Ethica state
$out["ethica"] = [
"ecm_py" => file_exists("/opt/weval-l99/ecm.py") ? filesize("/opt/weval-l99/ecm.py") : "missing",
"consent_live" => trim(@shell_exec("curl -sI --max-time 3 https://consent.wevup.app/ 2>&1 | head -1")),
];
// Registry 643 + wave-229 tools confirmed
$reg = @json_decode(@file_get_contents("/var/www/html/api/wevia-tool-registry.json"), true);
if ($reg) {
$w229 = array_filter($reg["tools"] ?? [], function($t){return ($t["wave"] ?? 0) == 229;});
$out["registry"] = [
"total" => count($reg["tools"] ?? []),
"wave_229_count" => count($w229),
];
}
// Monitoring status
$out["monitoring"] = [
"load" => trim(@shell_exec("uptime")),
"cascade_up" => @file_get_contents("http://127.0.0.1:4000/health", false, stream_context_create(["http"=>["timeout"=>3]])) ? "UP" : "DOWN",
];
// Mermaid KB
$mkb = @json_decode(@file_get_contents("/var/www/html/generated/mermaid-learn-kb.json"), true);
$out["mermaid_kb_total"] = is_array($mkb) ? count($mkb) : 0;
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);

View File

@@ -0,0 +1,286 @@
{
"ts": "2026-04-22T01:40:01+00:00",
"server": "s204",
"s204": {
"load": 8.74,
"uptime": "2026-04-14 11:51:24",
"ram_total_mb": 31335,
"ram_used_mb": 13038,
"ram_free_mb": 18296,
"disk_total": "150G",
"disk_used": "122G",
"disk_free": "22G",
"disk_pct": "85%",
"fpm_workers": 140,
"docker_containers": 19,
"cpu_cores": 8
},
"s95": {
"load": 0.92,
"disk_pct": "82%",
"status": "UP",
"ram_total_mb": 15610,
"ram_free_mb": 11942
},
"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": 323,
"php_apis": 983,
"wiki_entries": 2123,
"vault_doctrines": 101,
"vault_sessions": 104,
"vault_decisions": 12
},
"tools": {
"total": 643,
"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": 161734,
"with_email": 110662,
"with_phone": 155151,
"gap_email": 51072,
"pct_email": 68.4,
"pct_phone": 95.9,
"by_country": [
{
"country": "DZ",
"hcps": 122337,
"with_email": 78551,
"with_tel": 119396,
"pct_email": 64.2,
"pct_tel": 97.6
},
{
"country": "MA",
"hcps": 19724,
"with_email": 15081,
"with_tel": 18737,
"pct_email": 76.5,
"pct_tel": 95
},
{
"country": "TN",
"hcps": 17794,
"with_email": 15151,
"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": "weval-docuseal",
"status": "Up 8 seconds",
"ports": ""
},
{
"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 6 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 2 days (healthy)",
"ports": ""
},
{
"name": "vaultwarden",
"status": "Up 7 days (healthy)",
"ports": ""
},
{
"name": "qdrant",
"status": "Up 7 days",
"ports": ""
}
],
"crons": {
"active": 35
},
"git": {
"head": "5a96a06a0 auto-sync-0340",
"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": 4193,
"health": {
"score": 4,
"max": 6,
"pct": 67
},
"elapsed_ms": 10604
}

View File

@@ -1,27 +1,27 @@
{
"ok": true,
"agent": "V42_MQL_Scoring_Agent_REAL",
"ts": "2026-04-22T01:30:02+00:00",
"ts": "2026-04-22T01:40:01+00:00",
"status": "DEPLOYED_AUTO",
"deployed": true,
"algorithm": "weighted_behavioral_signals",
"signals_tracked": {
"wtp_engagement": 46,
"wtp_engagement": 100,
"chat_engagement": 0,
"roi_tool": 0,
"email_opened": 0
},
"avg_score": 11.5,
"avg_score": 25,
"mql_threshold": 50,
"sql_threshold": 75,
"leads_captured": 48,
"mql_auto_scored": 18,
"sql_auto_scored": 7,
"mql_auto_pct": 38,
"mql_auto_scored": 20,
"sql_auto_scored": 8,
"mql_auto_pct": 41,
"improvement_vs_manual": {
"before_manual_pct": 33.3,
"after_auto_pct": 38,
"delta": 4.700000000000003
"after_auto_pct": 41,
"delta": 7.700000000000003
},
"paperclip_db_ok": true,
"paperclip_tables": 2,

View File

@@ -50,6 +50,251 @@ function link_lead($opportunity) {
return $match;
}
// ====================================================================
// WAVES 234-245 MEGA BUNDLE
// ====================================================================
$action = $_GET['action'] ?? '';
// === WAVE action: kanban ===
if ($action === 'kanban') {
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
$r = @pg_query($pg, 'SELECT t.*, l.company AS lead_company, l.mql_score AS lead_mql FROM weval_tasks t LEFT JOIN weval_leads l ON t.lead_id=l.id ORDER BY t.created_at DESC');
$kanban = ['proposed'=>[], 'in_progress'=>[], 'done'=>[], 'cancelled'=>[], 'blocked'=>[]];
$mad_by_status = ['proposed'=>0,'in_progress'=>0,'done'=>0,'cancelled'=>0,'blocked'=>0];
if ($r) while ($row = pg_fetch_assoc($r)) {
$s = $row['status'] ?? 'proposed';
if (!isset($kanban[$s])) $kanban[$s] = [];
$kanban[$s][] = $row;
$mad_by_status[$s] = ($mad_by_status[$s] ?? 0) + (int)($row['estimated_mad'] ?? 0);
}
pg_close($pg);
echo json_encode(['ok'=>true, 'wave'=>234, 'columns'=>$kanban, 'mad_by_status'=>$mad_by_status]);
exit;
}
// === WAVE action: bluesky ===
if ($action === 'bluesky') {
$q = $_GET['q'] ?? 'SaaS conversion';
// Bluesky public search endpoint
$url = 'https://public.api.bsky.app/xrpc/app.bsky.feed.searchPosts?q=' . urlencode($q) . '&limit=10';
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>8, CURLOPT_USERAGENT=>'weval-bot']);
$raw = curl_exec($ch); curl_close($ch);
$d = @json_decode($raw, true);
$items = [];
foreach (($d['posts'] ?? []) as $p) {
$items[] = [
'title' => substr($p['record']['text'] ?? '', 0, 200),
'author' => '@' . ($p['author']['handle'] ?? '?'),
'url' => 'https://bsky.app/profile/' . ($p['author']['handle'] ?? '') . '/post/' . (explode('/', $p['uri'] ?? '')[4] ?? ''),
'likes' => (int)($p['likeCount'] ?? 0),
'reposts' => (int)($p['repostCount'] ?? 0),
'replies' => (int)($p['replyCount'] ?? 0),
'date' => substr($p['record']['createdAt'] ?? '', 0, 10),
];
}
usort($items, function($a,$b){return ($b['likes']??0)-($a['likes']??0);});
echo json_encode(['ok'=>true, 'wave'=>235, 'query'=>$q, 'count'=>count($items), 'items'=>array_slice($items, 0, 10)]);
exit;
}
// === WAVE action: kpi_dashboard ===
if ($action === 'kpi_dashboard') {
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
$kpi = ['wave'=>236, 'ts'=>date('c')];
// Leads stats
$r = @pg_query($pg, 'SELECT COUNT(*) AS n, SUM(CASE WHEN sql_qualified THEN 1 ELSE 0 END) AS sql_q, ROUND(AVG(mql_score)) AS avg_mql FROM weval_leads');
if ($r) { $kpi['leads'] = pg_fetch_assoc($r); }
// Leads by status
$r2 = @pg_query($pg, 'SELECT status, COUNT(*) AS n FROM weval_leads GROUP BY status');
$kpi['leads_by_status'] = []; if ($r2) while ($row = pg_fetch_assoc($r2)) $kpi['leads_by_status'][$row['status']] = (int)$row['n'];
// Leads by country
$r3 = @pg_query($pg, 'SELECT country, COUNT(*) AS n FROM weval_leads GROUP BY country ORDER BY n DESC LIMIT 10');
$kpi['leads_by_country'] = []; if ($r3) while ($row = pg_fetch_assoc($r3)) $kpi['leads_by_country'][$row['country'] ?: '?'] = (int)$row['n'];
// Tasks stats
$r4 = @pg_query($pg, 'SELECT COUNT(*) AS n, SUM(estimated_mad) AS total_mad FROM weval_tasks');
if ($r4) { $kpi['tasks'] = pg_fetch_assoc($r4); }
// Tasks by status + MAD
$r5 = @pg_query($pg, 'SELECT status, COUNT(*) AS n, SUM(estimated_mad) AS mad FROM weval_tasks GROUP BY status');
$kpi['tasks_by_status'] = []; if ($r5) while ($row = pg_fetch_assoc($r5)) $kpi['tasks_by_status'][$row['status']] = ['count'=>(int)$row['n'], 'mad'=>(int)$row['mad']];
// Top industries
$r6 = @pg_query($pg, 'SELECT industry, COUNT(*) AS n FROM weval_leads WHERE industry IS NOT NULL GROUP BY industry ORDER BY n DESC LIMIT 8');
$kpi['industries'] = []; if ($r6) while ($row = pg_fetch_assoc($r6)) $kpi['industries'][$row['industry']] = (int)$row['n'];
// Tasks created last 7 days (evolution)
$r7 = @pg_query($pg, "SELECT DATE(created_at) AS d, COUNT(*) AS n FROM weval_tasks WHERE created_at > NOW() - INTERVAL '7 days' GROUP BY d ORDER BY d");
$kpi['tasks_last_7d'] = []; if ($r7) while ($row = pg_fetch_assoc($r7)) $kpi['tasks_last_7d'][] = ['date'=>$row['d'], 'count'=>(int)$row['n']];
pg_close($pg);
echo json_encode($kpi);
exit;
}
// === WAVE action: enrich_lead ===
if ($action === 'enrich_lead') {
$lead_id = (int)($_GET['lead_id'] ?? 0);
if (!$lead_id) { echo json_encode(['error'=>'no lead_id']); exit; }
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
$r = @pg_query_params($pg, 'SELECT * FROM weval_leads WHERE id=$1', [$lead_id]);
$lead = $r ? pg_fetch_assoc($r) : null;
if (!$lead) { pg_close($pg); echo json_encode(['error'=>'lead not found']); exit; }
pg_close($pg);
// Query Dark Scout for intel about lead company
$q = $lead['company'] . ' ' . ($lead['industry'] ?: '') . ' ' . ($lead['country'] ?: '');
$ch = curl_init('http://127.0.0.1/api/v83-dark-scout-enriched.php?q=' . urlencode($q));
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>12]);
$ds_raw = curl_exec($ch); curl_close($ch);
$scout_results = []; if ($ds_raw) { $sd = @json_decode($ds_raw, true); $scout_results = array_slice(($sd['results']??[]), 0, 6); }
echo json_encode(['ok'=>true, 'wave'=>238, 'lead'=>$lead, 'scout_intel'=>$scout_results, 'intel_count'=>count($scout_results)]);
exit;
}
// === WAVE action: roi_calc ===
if ($action === 'roi_calc') {
$body = json_decode(file_get_contents('php://input'), true) ?: [];
$mad = (int)($body['estimated_mad'] ?? 0);
$effort_days = (int)($body['effort_days'] ?? 14);
$effort_cost_per_day = 1500; // MAD per dev/consultant day
$effort_cost = $effort_days * $effort_cost_per_day;
$net_roi = $mad - $effort_cost;
$roi_pct = $effort_cost > 0 ? round(($net_roi / $effort_cost) * 100, 1) : 0;
$confidence = 'medium';
if ($mad >= 200000 && $effort_days <= 14) $confidence = 'high';
elseif ($mad < 50000 || $effort_days > 45) $confidence = 'low';
echo json_encode(['ok'=>true, 'wave'=>239,
'estimated_mad'=>$mad,
'effort_cost_mad'=>$effort_cost,
'effort_days'=>$effort_days,
'net_roi_mad'=>$net_roi,
'roi_pct'=>$roi_pct,
'confidence'=>$confidence,
'break_even_days'=>$effort_cost > 0 && $mad > 0 ? round($effort_days * ($effort_cost / $mad), 1) : 0
]);
exit;
}
// === WAVE action: export_json ===
if ($action === 'export_json') {
header('Content-Type: application/json; charset=utf-8');
header('Content-Disposition: attachment; filename="weval_tasks_' . date('Ymd') . '.json"');
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
$r = @pg_query($pg, 'SELECT t.*, l.company AS linked_lead, l.mql_score AS lead_mql FROM weval_tasks t LEFT JOIN weval_leads l ON t.lead_id=l.id ORDER BY t.created_at DESC');
$tasks = []; if ($r) while ($row = pg_fetch_assoc($r)) $tasks[] = $row;
pg_close($pg);
echo json_encode(['ts'=>date('c'), 'count'=>count($tasks), 'tasks'=>$tasks], JSON_PRETTY_PRINT);
exit;
}
// === WAVE action: score_opportunity ===
if ($action === 'score_opportunity') {
$body = json_decode(file_get_contents('php://input'), true) ?: [];
$effort = max(1, min(10, (int)($body['effort'] ?? 5)));
$impact = max(1, min(10, (int)($body['impact'] ?? 5)));
$mad = (int)($body['estimated_mad'] ?? 0);
// ICE score: (impact * MAD/10K) / effort
$ice_score = round(($impact * ($mad / 10000)) / $effort, 1);
// Priority: HIGH (>50), MEDIUM (20-50), LOW (<20)
$priority = $ice_score >= 50 ? 'HIGH' : ($ice_score >= 20 ? 'MEDIUM' : 'LOW');
$quadrant = $effort <= 3 && $impact >= 7 ? 'QUICK_WIN' :
($effort >= 4 && $impact >= 7 ? 'BIG_BET' :
($effort <= 3 && $impact < 7 ? 'FILL_IN' : 'THANKLESS'));
echo json_encode(['ok'=>true, 'wave'=>241, 'effort'=>$effort, 'impact'=>$impact, 'estimated_mad'=>$mad,
'ice_score'=>$ice_score, 'priority'=>$priority, 'quadrant'=>$quadrant
]);
exit;
}
// === WAVE action: pipeline_stages ===
if ($action === 'pipeline_stages') {
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
// Derive stages from task status + lead status
$stages = [
'qualification' => ['name'=>'Qualification', 'count'=>0, 'mad'=>0, 'color'=>'#94a3b8'],
'discovery' => ['name'=>'Discovery', 'count'=>0, 'mad'=>0, 'color'=>'#fbbf24'],
'proposal' => ['name'=>'Proposal', 'count'=>0, 'mad'=>0, 'color'=>'#22d3ee'],
'negotiation' => ['name'=>'Negotiation', 'count'=>0, 'mad'=>0, 'color'=>'#a855f7'],
'closed_won' => ['name'=>'Closed Won', 'count'=>0, 'mad'=>0, 'color'=>'#10b981'],
'closed_lost' => ['name'=>'Closed Lost', 'count'=>0, 'mad'=>0, 'color'=>'#ef4444'],
];
// Map task.status → stage
$map = ['proposed'=>'discovery', 'in_progress'=>'negotiation', 'done'=>'closed_won', 'cancelled'=>'closed_lost', 'blocked'=>'proposal'];
$r = @pg_query($pg, 'SELECT status, COUNT(*) AS n, SUM(estimated_mad) AS mad FROM weval_tasks GROUP BY status');
if ($r) while ($row = pg_fetch_assoc($r)) {
$stage = $map[$row['status']] ?? 'qualification';
$stages[$stage]['count'] += (int)$row['n'];
$stages[$stage]['mad'] += (int)$row['mad'];
}
pg_close($pg);
echo json_encode(['ok'=>true, 'wave'=>242, 'stages'=>$stages]);
exit;
}
// === WAVE action: activity_timeline ===
if ($action === 'activity_timeline') {
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
// Unified timeline: leads + tasks events
$events = [];
$r = @pg_query($pg, 'SELECT created_at, id, title, status, lead_id, estimated_mad FROM weval_tasks ORDER BY created_at DESC LIMIT 15');
if ($r) while ($row = pg_fetch_assoc($r)) {
$events[] = ['ts'=>$row['created_at'], 'type'=>'task_created', 'title'=>$row['title'], 'id'=>$row['id'], 'meta'=>['status'=>$row['status'], 'mad'=>$row['estimated_mad']]];
}
$r2 = @pg_query($pg, 'SELECT created_at, id, company, mql_score, status FROM weval_leads ORDER BY created_at DESC LIMIT 10');
if ($r2) while ($row = pg_fetch_assoc($r2)) {
$events[] = ['ts'=>$row['created_at'], 'type'=>'lead_created', 'title'=>$row['company'], 'id'=>$row['id'], 'meta'=>['mql'=>$row['mql_score'], 'status'=>$row['status']]];
}
pg_close($pg);
usort($events, function($a,$b){ return strcmp($b['ts'], $a['ts']); });
echo json_encode(['ok'=>true, 'wave'=>243, 'count'=>count($events), 'events'=>array_slice($events, 0, 20)]);
exit;
}
// === WAVE action: search_tasks ===
if ($action === 'search_tasks') {
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
$q = trim($_GET['q'] ?? '');
$filter_status = $_GET['status'] ?? '';
$min_mad = (int)($_GET['min_mad'] ?? 0);
$where = [];
$params = [];
$i = 1;
if ($q) { $where[] = "(t.title ILIKE \$$i OR t.opportunity ILIKE \$$i)"; $params[] = '%' . $q . '%'; $i++; }
if ($filter_status) { $where[] = "t.status = \$$i"; $params[] = $filter_status; $i++; }
if ($min_mad) { $where[] = "t.estimated_mad >= \$$i"; $params[] = $min_mad; $i++; }
$where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
$sql = "SELECT t.*, l.company AS lead_company, l.mql_score AS lead_mql FROM weval_tasks t LEFT JOIN weval_leads l ON t.lead_id=l.id $where_sql ORDER BY t.created_at DESC LIMIT 50";
$r = $params ? @pg_query_params($pg, $sql, $params) : @pg_query($pg, $sql);
$tasks = []; if ($r) while ($row = pg_fetch_assoc($r)) $tasks[] = $row;
pg_close($pg);
echo json_encode(['ok'=>true, 'wave'=>244, 'query'=>$q, 'count'=>count($tasks), 'tasks'=>$tasks]);
exit;
}
// === WAVE action: bundle_manifest ===
if ($action === 'bundle_manifest') {
echo json_encode(['ok'=>true, 'wave'=>245, 'version'=>'mega-bundle-234-245',
'endpoints' => [
'kanban' => 'Kanban board view grouped by status',
'bluesky' => 'Bluesky AT Protocol search (replace Twitter)',
'kpi_dashboard' => 'Consolidated KPI dashboard (leads + tasks + countries + industries)',
'enrich_lead' => 'Lead enrichment via Dark Scout intel',
'roi_calc' => 'ROI calculator auto per idea',
'export_json' => 'Full export JSON attachment',
'score_opportunity' => 'ICE auto-scoring + quadrant classification',
'pipeline_stages' => '6 deal pipeline stages (Qualification → Closed)',
'activity_timeline' => 'Unified events timeline (leads + tasks)',
'search_tasks' => 'Search + filter tasks (status, MAD, text)',
'bundle_manifest' => 'This capabilities listing'
],
'features_count' => 12,
'waves' => '234-245',
'session_date' => '2026-04-22'
]);
exit;
}
// === POST create_task with lead linking (WAVE 233) ===
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_GET['action'] ?? '') === 'create_task') {
$body = json_decode(file_get_contents('php://input'), true) ?: [];
@@ -188,6 +433,250 @@ if (($_GET['action'] ?? '') === 'stream') {
exit;
}
// ====================================================================
// WAVES 234-245 MEGA BUNDLE - 11 new endpoints
// ====================================================================
// === WAVE action: kanban ===
if ($action === 'kanban') {
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
$r = @pg_query($pg, 'SELECT t.*, l.company AS lead_company, l.mql_score AS lead_mql FROM weval_tasks t LEFT JOIN weval_leads l ON t.lead_id=l.id ORDER BY t.created_at DESC');
$kanban = ['proposed'=>[], 'in_progress'=>[], 'done'=>[], 'cancelled'=>[], 'blocked'=>[]];
$mad_by_status = ['proposed'=>0,'in_progress'=>0,'done'=>0,'cancelled'=>0,'blocked'=>0];
if ($r) while ($row = pg_fetch_assoc($r)) {
$s = $row['status'] ?? 'proposed';
if (!isset($kanban[$s])) $kanban[$s] = [];
$kanban[$s][] = $row;
$mad_by_status[$s] = ($mad_by_status[$s] ?? 0) + (int)($row['estimated_mad'] ?? 0);
}
pg_close($pg);
echo json_encode(['ok'=>true, 'wave'=>234, 'columns'=>$kanban, 'mad_by_status'=>$mad_by_status]);
exit;
}
// === WAVE action: bluesky ===
if ($action === 'bluesky') {
$q = $_GET['q'] ?? 'SaaS conversion';
// Bluesky public search endpoint
$url = 'https://public.api.bsky.app/xrpc/app.bsky.feed.searchPosts?q=' . urlencode($q) . '&limit=10';
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>8, CURLOPT_USERAGENT=>'weval-bot']);
$raw = curl_exec($ch); curl_close($ch);
$d = @json_decode($raw, true);
$items = [];
foreach (($d['posts'] ?? []) as $p) {
$items[] = [
'title' => substr($p['record']['text'] ?? '', 0, 200),
'author' => '@' . ($p['author']['handle'] ?? '?'),
'url' => 'https://bsky.app/profile/' . ($p['author']['handle'] ?? '') . '/post/' . (explode('/', $p['uri'] ?? '')[4] ?? ''),
'likes' => (int)($p['likeCount'] ?? 0),
'reposts' => (int)($p['repostCount'] ?? 0),
'replies' => (int)($p['replyCount'] ?? 0),
'date' => substr($p['record']['createdAt'] ?? '', 0, 10),
];
}
usort($items, function($a,$b){return ($b['likes']??0)-($a['likes']??0);});
echo json_encode(['ok'=>true, 'wave'=>235, 'query'=>$q, 'count'=>count($items), 'items'=>array_slice($items, 0, 10)]);
exit;
}
// === WAVE action: kpi_dashboard ===
if ($action === 'kpi_dashboard') {
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
$kpi = ['wave'=>236, 'ts'=>date('c')];
// Leads stats
$r = @pg_query($pg, 'SELECT COUNT(*) AS n, SUM(CASE WHEN sql_qualified THEN 1 ELSE 0 END) AS sql_q, ROUND(AVG(mql_score)) AS avg_mql FROM weval_leads');
if ($r) { $kpi['leads'] = pg_fetch_assoc($r); }
// Leads by status
$r2 = @pg_query($pg, 'SELECT status, COUNT(*) AS n FROM weval_leads GROUP BY status');
$kpi['leads_by_status'] = []; if ($r2) while ($row = pg_fetch_assoc($r2)) $kpi['leads_by_status'][$row['status']] = (int)$row['n'];
// Leads by country
$r3 = @pg_query($pg, 'SELECT country, COUNT(*) AS n FROM weval_leads GROUP BY country ORDER BY n DESC LIMIT 10');
$kpi['leads_by_country'] = []; if ($r3) while ($row = pg_fetch_assoc($r3)) $kpi['leads_by_country'][$row['country'] ?: '?'] = (int)$row['n'];
// Tasks stats
$r4 = @pg_query($pg, 'SELECT COUNT(*) AS n, SUM(estimated_mad) AS total_mad FROM weval_tasks');
if ($r4) { $kpi['tasks'] = pg_fetch_assoc($r4); }
// Tasks by status + MAD
$r5 = @pg_query($pg, 'SELECT status, COUNT(*) AS n, SUM(estimated_mad) AS mad FROM weval_tasks GROUP BY status');
$kpi['tasks_by_status'] = []; if ($r5) while ($row = pg_fetch_assoc($r5)) $kpi['tasks_by_status'][$row['status']] = ['count'=>(int)$row['n'], 'mad'=>(int)$row['mad']];
// Top industries
$r6 = @pg_query($pg, 'SELECT industry, COUNT(*) AS n FROM weval_leads WHERE industry IS NOT NULL GROUP BY industry ORDER BY n DESC LIMIT 8');
$kpi['industries'] = []; if ($r6) while ($row = pg_fetch_assoc($r6)) $kpi['industries'][$row['industry']] = (int)$row['n'];
// Tasks created last 7 days (evolution)
$r7 = @pg_query($pg, "SELECT DATE(created_at) AS d, COUNT(*) AS n FROM weval_tasks WHERE created_at > NOW() - INTERVAL '7 days' GROUP BY d ORDER BY d");
$kpi['tasks_last_7d'] = []; if ($r7) while ($row = pg_fetch_assoc($r7)) $kpi['tasks_last_7d'][] = ['date'=>$row['d'], 'count'=>(int)$row['n']];
pg_close($pg);
echo json_encode($kpi);
exit;
}
// === WAVE action: enrich_lead ===
if ($action === 'enrich_lead') {
$lead_id = (int)($_GET['lead_id'] ?? 0);
if (!$lead_id) { echo json_encode(['error'=>'no lead_id']); exit; }
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
$r = @pg_query_params($pg, 'SELECT * FROM weval_leads WHERE id=$1', [$lead_id]);
$lead = $r ? pg_fetch_assoc($r) : null;
if (!$lead) { pg_close($pg); echo json_encode(['error'=>'lead not found']); exit; }
pg_close($pg);
// Query Dark Scout for intel about lead company
$q = $lead['company'] . ' ' . ($lead['industry'] ?: '') . ' ' . ($lead['country'] ?: '');
$ch = curl_init('http://127.0.0.1/api/v83-dark-scout-enriched.php?q=' . urlencode($q));
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>12]);
$ds_raw = curl_exec($ch); curl_close($ch);
$scout_results = []; if ($ds_raw) { $sd = @json_decode($ds_raw, true); $scout_results = array_slice(($sd['results']??[]), 0, 6); }
echo json_encode(['ok'=>true, 'wave'=>238, 'lead'=>$lead, 'scout_intel'=>$scout_results, 'intel_count'=>count($scout_results)]);
exit;
}
// === WAVE action: roi_calc ===
if ($action === 'roi_calc') {
$body = json_decode(file_get_contents('php://input'), true) ?: [];
$mad = (int)($body['estimated_mad'] ?? 0);
$effort_days = (int)($body['effort_days'] ?? 14);
$effort_cost_per_day = 1500; // MAD per dev/consultant day
$effort_cost = $effort_days * $effort_cost_per_day;
$net_roi = $mad - $effort_cost;
$roi_pct = $effort_cost > 0 ? round(($net_roi / $effort_cost) * 100, 1) : 0;
$confidence = 'medium';
if ($mad >= 200000 && $effort_days <= 14) $confidence = 'high';
elseif ($mad < 50000 || $effort_days > 45) $confidence = 'low';
echo json_encode(['ok'=>true, 'wave'=>239,
'estimated_mad'=>$mad,
'effort_cost_mad'=>$effort_cost,
'effort_days'=>$effort_days,
'net_roi_mad'=>$net_roi,
'roi_pct'=>$roi_pct,
'confidence'=>$confidence,
'break_even_days'=>$effort_cost > 0 && $mad > 0 ? round($effort_days * ($effort_cost / $mad), 1) : 0
]);
exit;
}
// === WAVE action: export_json ===
if ($action === 'export_json') {
header('Content-Type: application/json; charset=utf-8');
header('Content-Disposition: attachment; filename="weval_tasks_' . date('Ymd') . '.json"');
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
$r = @pg_query($pg, 'SELECT t.*, l.company AS linked_lead, l.mql_score AS lead_mql FROM weval_tasks t LEFT JOIN weval_leads l ON t.lead_id=l.id ORDER BY t.created_at DESC');
$tasks = []; if ($r) while ($row = pg_fetch_assoc($r)) $tasks[] = $row;
pg_close($pg);
echo json_encode(['ts'=>date('c'), 'count'=>count($tasks), 'tasks'=>$tasks], JSON_PRETTY_PRINT);
exit;
}
// === WAVE action: score_opportunity ===
if ($action === 'score_opportunity') {
$body = json_decode(file_get_contents('php://input'), true) ?: [];
$effort = max(1, min(10, (int)($body['effort'] ?? 5)));
$impact = max(1, min(10, (int)($body['impact'] ?? 5)));
$mad = (int)($body['estimated_mad'] ?? 0);
// ICE score: (impact * MAD/10K) / effort
$ice_score = round(($impact * ($mad / 10000)) / $effort, 1);
// Priority: HIGH (>50), MEDIUM (20-50), LOW (<20)
$priority = $ice_score >= 50 ? 'HIGH' : ($ice_score >= 20 ? 'MEDIUM' : 'LOW');
$quadrant = $effort <= 3 && $impact >= 7 ? 'QUICK_WIN' :
($effort >= 4 && $impact >= 7 ? 'BIG_BET' :
($effort <= 3 && $impact < 7 ? 'FILL_IN' : 'THANKLESS'));
echo json_encode(['ok'=>true, 'wave'=>241, 'effort'=>$effort, 'impact'=>$impact, 'estimated_mad'=>$mad,
'ice_score'=>$ice_score, 'priority'=>$priority, 'quadrant'=>$quadrant
]);
exit;
}
// === WAVE action: pipeline_stages ===
if ($action === 'pipeline_stages') {
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
// Derive stages from task status + lead status
$stages = [
'qualification' => ['name'=>'Qualification', 'count'=>0, 'mad'=>0, 'color'=>'#94a3b8'],
'discovery' => ['name'=>'Discovery', 'count'=>0, 'mad'=>0, 'color'=>'#fbbf24'],
'proposal' => ['name'=>'Proposal', 'count'=>0, 'mad'=>0, 'color'=>'#22d3ee'],
'negotiation' => ['name'=>'Negotiation', 'count'=>0, 'mad'=>0, 'color'=>'#a855f7'],
'closed_won' => ['name'=>'Closed Won', 'count'=>0, 'mad'=>0, 'color'=>'#10b981'],
'closed_lost' => ['name'=>'Closed Lost', 'count'=>0, 'mad'=>0, 'color'=>'#ef4444'],
];
// Map task.status → stage
$map = ['proposed'=>'discovery', 'in_progress'=>'negotiation', 'done'=>'closed_won', 'cancelled'=>'closed_lost', 'blocked'=>'proposal'];
$r = @pg_query($pg, 'SELECT status, COUNT(*) AS n, SUM(estimated_mad) AS mad FROM weval_tasks GROUP BY status');
if ($r) while ($row = pg_fetch_assoc($r)) {
$stage = $map[$row['status']] ?? 'qualification';
$stages[$stage]['count'] += (int)$row['n'];
$stages[$stage]['mad'] += (int)$row['mad'];
}
pg_close($pg);
echo json_encode(['ok'=>true, 'wave'=>242, 'stages'=>$stages]);
exit;
}
// === WAVE action: activity_timeline ===
if ($action === 'activity_timeline') {
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
// Unified timeline: leads + tasks events
$events = [];
$r = @pg_query($pg, 'SELECT created_at, id, title, status, lead_id, estimated_mad FROM weval_tasks ORDER BY created_at DESC LIMIT 15');
if ($r) while ($row = pg_fetch_assoc($r)) {
$events[] = ['ts'=>$row['created_at'], 'type'=>'task_created', 'title'=>$row['title'], 'id'=>$row['id'], 'meta'=>['status'=>$row['status'], 'mad'=>$row['estimated_mad']]];
}
$r2 = @pg_query($pg, 'SELECT created_at, id, company, mql_score, status FROM weval_leads ORDER BY created_at DESC LIMIT 10');
if ($r2) while ($row = pg_fetch_assoc($r2)) {
$events[] = ['ts'=>$row['created_at'], 'type'=>'lead_created', 'title'=>$row['company'], 'id'=>$row['id'], 'meta'=>['mql'=>$row['mql_score'], 'status'=>$row['status']]];
}
pg_close($pg);
usort($events, function($a,$b){ return strcmp($b['ts'], $a['ts']); });
echo json_encode(['ok'=>true, 'wave'=>243, 'count'=>count($events), 'events'=>array_slice($events, 0, 20)]);
exit;
}
// === WAVE action: search_tasks ===
if ($action === 'search_tasks') {
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
$q = trim($_GET['q'] ?? '');
$filter_status = $_GET['status'] ?? '';
$min_mad = (int)($_GET['min_mad'] ?? 0);
$where = [];
$params = [];
$i = 1;
if ($q) { $where[] = "(t.title ILIKE \$$i OR t.opportunity ILIKE \$$i)"; $params[] = '%' . $q . '%'; $i++; }
if ($filter_status) { $where[] = "t.status = \$$i"; $params[] = $filter_status; $i++; }
if ($min_mad) { $where[] = "t.estimated_mad >= \$$i"; $params[] = $min_mad; $i++; }
$where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
$sql = "SELECT t.*, l.company AS lead_company, l.mql_score AS lead_mql FROM weval_tasks t LEFT JOIN weval_leads l ON t.lead_id=l.id $where_sql ORDER BY t.created_at DESC LIMIT 50";
$r = $params ? @pg_query_params($pg, $sql, $params) : @pg_query($pg, $sql);
$tasks = []; if ($r) while ($row = pg_fetch_assoc($r)) $tasks[] = $row;
pg_close($pg);
echo json_encode(['ok'=>true, 'wave'=>244, 'query'=>$q, 'count'=>count($tasks), 'tasks'=>$tasks]);
exit;
}
// === WAVE action: bundle_manifest ===
if ($action === 'bundle_manifest') {
echo json_encode(['ok'=>true, 'wave'=>245, 'version'=>'mega-bundle-234-245',
'endpoints' => [
'kanban' => 'Kanban board view grouped by status',
'bluesky' => 'Bluesky AT Protocol search (replace Twitter)',
'kpi_dashboard' => 'Consolidated KPI dashboard (leads + tasks + countries + industries)',
'enrich_lead' => 'Lead enrichment via Dark Scout intel',
'roi_calc' => 'ROI calculator auto per idea',
'export_json' => 'Full export JSON attachment',
'score_opportunity' => 'ICE auto-scoring + quadrant classification',
'pipeline_stages' => '6 deal pipeline stages (Qualification → Closed)',
'activity_timeline' => 'Unified events timeline (leads + tasks)',
'search_tasks' => 'Search + filter tasks (status, MAD, text)',
'bundle_manifest' => 'This capabilities listing'
],
'features_count' => 12,
'waves' => '234-245',
'session_date' => '2026-04-22'
]);
exit;
}
// === Default aggregation (Reddit 5 subs WAVE 233) ===
$topics = $_GET['topics'] ?? 'B2B SaaS conversion,LinkedIn outbound,pharma digital';
$topic_list = array_slice(array_map('trim', explode(',', $topics)), 0, 3);

View File

@@ -1,7 +1,7 @@
{
"ok": true,
"version": "V83-business-kpi",
"ts": "2026-04-22T01:39:42+00:00",
"ts": "2026-04-22T01:42:42+00:00",
"summary": {
"total_categories": 8,
"total_kpis": 64,

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB