Files
html/api/em-api.php
2026-04-20 03:00:04 +02:00

644 lines
36 KiB
PHP

<?php
/**
* /api/em/ router — EM endpoints (GODMODE 17avr)
* Handles: agents-registry, vsm, bpmn-routines, dmaic, kpi/live, poc/start, tenant/bootstrap, plans
*/
header("Content-Type: application/json; charset=utf-8");
header("Access-Control-Allow-Origin: *");
$DB_HOST = "127.0.0.1";
$DB_PORT = 5432;
$DB_NAME = "adx_system";
$DB_USER = "admin";
$DB_PASS = "admin123";
try {
$pdo = new PDO("pgsql:host=$DB_HOST;port=$DB_PORT;dbname=$DB_NAME", $DB_USER, $DB_PASS, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
} catch (Exception $e) {
// Fallback to local if S95 unreachable
try {
$pdo = new PDO("pgsql:host=127.0.0.1;port=5432;dbname=adx_system", $DB_USER, $DB_PASS, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
} catch (Exception $e2) {
http_response_code(500);
echo json_encode(["error" => "db-unreachable"]);
exit;
}
}
$uri = parse_url($_SERVER["REQUEST_URI"] ?? "", PHP_URL_PATH);
$path = preg_replace("#^/api/em/?#", "", $uri);
$parts = explode("/", trim($path, "/"));
$endpoint = $parts[0] ?? "";
function audit($pdo, $action, $target = null, $payload = []) {
$tenant = $_GET["tenant"] ?? $_POST["tenant"] ?? "weval";
try {
$stmt = $pdo->prepare("INSERT INTO weval.audit_log (tenant_id, actor, action, target, payload, ip) VALUES (?,?,?,?,?,?)");
$stmt->execute([$tenant, "em-api", $action, $target, json_encode($payload), $_SERVER["REMOTE_ADDR"] ?? ""]);
} catch (Exception $e) {}
}
switch ($endpoint) {
case "agents-registry":
$tenant = $_GET["tenant"] ?? "weval";
$dept = $_GET["dept"] ?? null;
$sql = "SELECT id,name,dept,tier,layer_soa,skills,routines,status,source FROM weval.agent_registry WHERE tenant_id=?";
$params = [$tenant];
if ($dept) { $sql .= " AND dept=?"; $params[] = $dept; }
$sql .= " ORDER BY tier, name";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as &$r) {
$r["skills"] = json_decode($r["skills"], true);
$r["routines"] = json_decode($r["routines"], true);
}
echo json_encode(["tenant" => $tenant, "count" => count($rows), "agents" => $rows]);
break;
case "vsm":
$tenant = $_GET["tenant"] ?? "weval";
if (!empty($parts[1])) {
$stmt = $pdo->prepare("SELECT * FROM weval.vsm_dept WHERE tenant_id=? AND dept_code=?");
$stmt->execute([$tenant, $parts[1]]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) { http_response_code(404); echo json_encode(["error"=>"not-found"]); break; }
$row["kpis"] = json_decode($row["kpis"], true);
$row["agents"] = json_decode($row["agents"], true);
echo json_encode($row);
} else {
$stmt = $pdo->prepare("SELECT dept_code, dept_name, icon, supplier, input, process, output, customer, kpis, agents FROM weval.vsm_dept WHERE tenant_id=? ORDER BY id");
$stmt->execute([$tenant]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as &$r) {
$r["kpis"] = json_decode($r["kpis"], true);
$r["agents"] = json_decode($r["agents"], true);
}
echo json_encode(["tenant"=>$tenant, "count"=>count($rows), "depts"=>$rows]);
}
break;
case "bpmn-routines":
$tenant = $_GET["tenant"] ?? "weval";
$stmt = $pdo->prepare("SELECT id, name, dept, steps, sla_hours, status, mapped_intents FROM weval.bpmn_routines WHERE tenant_id=? ORDER BY id");
$stmt->execute([$tenant]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as &$r) {
$r["steps"] = json_decode($r["steps"], true);
$r["mapped_intents"] = json_decode($r["mapped_intents"], true);
}
echo json_encode(["tenant"=>$tenant, "count"=>count($rows), "routines"=>$rows]);
break;
case "dmaic":
$tenant = $parts[1] ?? $_GET["tenant"] ?? "weval";
$vs = $parts[2] ?? $_GET["vs"] ?? null;
if ($vs) {
$stmt = $pdo->prepare("SELECT * FROM weval.dmaic_cycles WHERE tenant_id=? AND vs_id=?");
$stmt->execute([$tenant, $vs]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
echo json_encode($row ?: ["error"=>"not-found"]);
} else {
$stmt = $pdo->prepare("SELECT id, vs_id, name, phase, progress, updated_at FROM weval.dmaic_cycles WHERE tenant_id=? ORDER BY id");
$stmt->execute([$tenant]);
echo json_encode(["tenant"=>$tenant, "cycles"=>$stmt->fetchAll(PDO::FETCH_ASSOC)]);
}
break;
case "kpi":
if (($parts[1] ?? "") === "live") {
$tenant = $_GET["tenant"] ?? "weval";
$dept = $_GET["dept"] ?? null;
$sql = "SELECT DISTINCT ON (dept, kpi_name) dept, kpi_name, value, unit, ts, source FROM weval.kpi_timeseries WHERE tenant_id=?";
$params = [$tenant];
if ($dept) { $sql .= " AND dept=?"; $params[] = $dept; }
$sql .= " ORDER BY dept, kpi_name, ts DESC";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
echo json_encode(["tenant"=>$tenant, "kpis"=>$stmt->fetchAll(PDO::FETCH_ASSOC)]);
} else {
http_response_code(404);
echo json_encode(["error"=>"unknown-kpi-endpoint"]);
}
break;
case "poc":
if (($parts[1] ?? "") === "start") {
$raw = json_decode(file_get_contents("php://input"), true) ?? $_POST;
$tenant_id = "poc_" . substr(md5(($raw["name"] ?? "demo") . time()), 0, 8);
$name = $raw["name"] ?? "POC Demo";
$email = $raw["email"] ?? "demo@example.com";
$dept = $raw["dept"] ?? "commerce";
try {
$pdo->prepare("INSERT INTO weval.tenants (tenant_id, name, plan_code, phase, contact_email) VALUES (?,?,?,?,?)")->execute([$tenant_id, $name, "poc", "poc", $email]);
// Clone the selected dept VSM
$pdo->prepare("INSERT INTO weval.vsm_dept (tenant_id, dept_code, dept_name, icon, supplier, input, process, output, customer, kpis, agents) SELECT ?, dept_code, dept_name, icon, supplier, input, process, output, customer, kpis, agents FROM weval.vsm_dept WHERE tenant_id='weval' AND dept_code=?")->execute([$tenant_id, $dept]);
// Create DMAIC cycle
$pdo->prepare("INSERT INTO weval.dmaic_cycles (tenant_id, vs_id, name, phase) VALUES (?, ?, ?, 'define')")->execute([$tenant_id, $dept . "-poc", "POC $name - $dept VSM"]);
audit($pdo, "poc_start", $tenant_id, ["email"=>$email, "dept"=>$dept]);
echo json_encode(["ok"=>true, "tenant_id"=>$tenant_id, "plan"=>"poc", "dept"=>$dept, "brain_center_url"=>"/brain-center-tenant.html?t=$tenant_id", "dmaic_url"=>"/dmaic-workbench.html?t=$tenant_id&vs=$dept-poc", "next_steps"=>["Interview DG","Scan process $dept","DMAIC Define","Baseline KPIs","Rapport"]]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(["error"=>$e->getMessage()]);
}
} else {
echo json_encode(["endpoints"=>["/api/em/poc/start"]]);
}
break;
case "plans":
$stmt = $pdo->query("SELECT plan_code, plan_name, tier, setup_fee, monthly_fee, currency, vs_count, max_users, features FROM weval.em_plans WHERE active=true ORDER BY setup_fee");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as &$r) $r["features"] = json_decode($r["features"], true);
echo json_encode(["plans"=>$rows]);
break;
case "tenant":
if (($parts[1] ?? "") === "bootstrap") {
$raw = json_decode(file_get_contents("php://input"), true) ?? $_POST;
$tenant_id = $raw["tenant_id"] ?? "demo_" . time();
$plan = $raw["plan"] ?? "mvp";
$name = $raw["name"] ?? "Demo Tenant";
$email = $raw["email"] ?? "demo@example.com";
try {
$pdo->prepare("INSERT INTO weval.tenants (tenant_id, name, plan_code, phase, contact_email) VALUES (?,?,?,?,?) ON CONFLICT (tenant_id) DO UPDATE SET plan_code=EXCLUDED.plan_code, phase=EXCLUDED.phase")->execute([$tenant_id, $name, $plan, $plan, $email]);
// Clone all VSM depts for MVP/Enterprise
if (in_array($plan, ["mvp","enterprise"])) {
$n = $plan === "enterprise" ? 15 : 5;
$pdo->prepare("INSERT INTO weval.vsm_dept (tenant_id, dept_code, dept_name, icon, supplier, input, process, output, customer, kpis, agents) SELECT ?, dept_code, dept_name, icon, supplier, input, process, output, customer, kpis, agents FROM weval.vsm_dept WHERE tenant_id='weval' LIMIT ? ON CONFLICT DO NOTHING")->execute([$tenant_id, $n]);
}
audit($pdo, "tenant_bootstrap", $tenant_id, ["plan"=>$plan]);
echo json_encode(["ok"=>true, "tenant_id"=>$tenant_id, "plan"=>$plan, "brain_center_url"=>"/brain-center-tenant.html?t=$tenant_id"]);
} catch (Exception $e) { http_response_code(500); echo json_encode(["error"=>$e->getMessage()]); }
} else {
$stmt = $pdo->query("SELECT tenant_id, name, plan_code, phase, status FROM weval.tenants ORDER BY created_at DESC LIMIT 50");
echo json_encode(["tenants"=>$stmt->fetchAll(PDO::FETCH_ASSOC)]);
}
break;
case "enterprise-kpis":
// V96.19 Opus alias to standalone enterprise-kpis.php
$qs = http_build_query($_REQUEST);
$url = 'http://127.0.0.1:5890/api/enterprise-kpis.php?' . $qs;
$ctx = stream_context_create(['http' => ['timeout' => 15]]);
$data = @file_get_contents($url, false, $ctx);
echo $data !== false ? $data : json_encode(["error"=>"backend_unreachable"]);
break;
case "audit":
$tenant = $_GET["tenant"] ?? "weval";
$limit = min(intval($_GET["limit"] ?? 50), 500);
$stmt = $pdo->prepare("SELECT actor, action, target, ts FROM weval.audit_log WHERE tenant_id=? ORDER BY ts DESC LIMIT ?");
$stmt->execute([$tenant, $limit]);
echo json_encode(["tenant"=>$tenant, "events"=>$stmt->fetchAll(PDO::FETCH_ASSOC)]);
break;
case "erp-connectors":
$stmt = $pdo->query("SELECT code, name, vendor, protocols, modules, auth_type, status, config_schema FROM weval.erp_connectors WHERE status='available' ORDER BY vendor, name");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as &$r) {
$r["protocols"] = json_decode($r["protocols"], true);
$r["modules"] = json_decode($r["modules"], true);
$r["config_schema"] = json_decode($r["config_schema"], true);
}
echo json_encode(["count" => count($rows), "connectors" => $rows]);
break;
case "ai-providers":
$stmt = $pdo->query("SELECT code, name, vendor, models, capabilities, endpoint, auth_type, status FROM weval.ai_providers WHERE status='available' ORDER BY vendor, name");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as &$r) {
$r["models"] = json_decode($r["models"], true);
$r["capabilities"] = json_decode($r["capabilities"], true);
}
echo json_encode(["count" => count($rows), "providers" => $rows]);
break;
case "industry-templates":
$sector = $_GET["sector"] ?? null;
$sql = "SELECT code, name, sector, vsm_depts, kpis, routines, compliance, description FROM weval.industry_templates";
$params = [];
if ($sector) { $sql .= " WHERE sector=?"; $params[] = $sector; }
$sql .= " ORDER BY name";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as &$r) {
$r["vsm_depts"] = json_decode($r["vsm_depts"], true);
$r["kpis"] = json_decode($r["kpis"], true);
$r["routines"] = json_decode($r["routines"], true);
$r["compliance"] = json_decode($r["compliance"], true);
}
echo json_encode(["count" => count($rows), "templates" => $rows]);
break;
case "tenant-integrations":
$tenant = $_GET["tenant"] ?? "weval";
if (!empty($parts[1]) && $parts[1] === "connect" && $_SERVER["REQUEST_METHOD"] === "POST") {
$raw = json_decode(file_get_contents("php://input"), true) ?? $_POST;
$type = $raw["type"] ?? "";
$code = $raw["code"] ?? "";
$config = $raw["config"] ?? [];
$tenant_id = $raw["tenant_id"] ?? $tenant;
if (!in_array($type, ["erp","ai","industry"])) { http_response_code(400); echo json_encode(["error"=>"invalid-type"]); break; }
try {
$pdo->prepare("INSERT INTO weval.tenant_integrations (tenant_id, integration_type, integration_code, config, status) VALUES (?,?,?,?,'active') ON CONFLICT (tenant_id, integration_type, integration_code) DO UPDATE SET config=EXCLUDED.config, status='active'")
->execute([$tenant_id, $type, $code, json_encode($config)]);
audit($pdo, "integration_connect", "$tenant_id:$type:$code", ["masked"=>count($config)." keys"]);
// If industry → apply template (clone VSM depts from template)
if ($type === "industry") {
$ts = $pdo->prepare("SELECT vsm_depts FROM weval.industry_templates WHERE code=?");
$ts->execute([$code]);
$tpl = $ts->fetch(PDO::FETCH_ASSOC);
if ($tpl) {
$depts = json_decode($tpl["vsm_depts"], true) ?? [];
foreach ($depts as $d) {
$pdo->prepare("INSERT INTO weval.vsm_dept (tenant_id, dept_code, dept_name, icon, supplier, input, process, output, customer, kpis, agents) SELECT ?, dept_code, dept_name, icon, supplier, input, process, output, customer, kpis, agents FROM weval.vsm_dept WHERE tenant_id='weval' AND dept_code=? ON CONFLICT DO NOTHING")->execute([$tenant_id, $d]);
}
}
}
echo json_encode(["ok"=>true,"tenant_id"=>$tenant_id,"type"=>$type,"code"=>$code]);
} catch (Exception $e) { http_response_code(500); echo json_encode(["error"=>$e->getMessage()]); }
} else {
$stmt = $pdo->prepare("SELECT ti.tenant_id, ti.integration_type, ti.integration_code, ti.status, ti.created_at FROM weval.tenant_integrations ti WHERE ti.tenant_id=? ORDER BY created_at DESC");
$stmt->execute([$tenant]);
echo json_encode(["tenant"=>$tenant, "integrations"=>$stmt->fetchAll(PDO::FETCH_ASSOC)]);
}
break;
case "scalability":
// Return overall scalability matrix
$erp = $pdo->query("SELECT COUNT(*) FROM weval.erp_connectors")->fetchColumn();
$ai = $pdo->query("SELECT COUNT(*) FROM weval.ai_providers")->fetchColumn();
$ind = $pdo->query("SELECT COUNT(*) FROM weval.industry_templates")->fetchColumn();
$ti = $pdo->query("SELECT COUNT(*) FROM weval.tenant_integrations")->fetchColumn();
echo json_encode([
"erp_connectors_available" => intval($erp),
"ai_providers_available" => intval($ai),
"industry_templates_available" => intval($ind),
"tenant_integrations_active" => intval($ti),
"matrix" => ["ERP" => $erp, "AI" => $ai, "Industries" => $ind, "Total_combinations" => $erp * $ai * $ind]
]);
break;
case "muda":
$tenant = $_GET["tenant"] ?? "weval";
$type = $_GET["type"] ?? null;
$sql = "SELECT id, vs_id, muda_type, description, severity, impact_hours, impact_euro, status, detected_by, created_at FROM weval.muda_entries WHERE tenant_id=?";
$params = [$tenant];
if ($type) { $sql .= " AND muda_type=?"; $params[] = $type; }
$sql .= " ORDER BY severity DESC, created_at DESC";
$stmt = $pdo->prepare($sql); $stmt->execute($params);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$total_hours = 0; $total_euro = 0;
foreach ($rows as $r) { $total_hours += floatval($r["impact_hours"]); $total_euro += floatval($r["impact_euro"]); }
echo json_encode(["tenant"=>$tenant,"count"=>count($rows),"total_impact_hours"=>$total_hours,"total_impact_euro"=>$total_euro,"entries"=>$rows]);
break;
case "poka-yoke":
$tenant = $_GET["tenant"] ?? "weval";
$stmt = $pdo->prepare("SELECT id, process, failure_mode, device_type, mechanism, validation, status, efficiency_pct FROM weval.poka_yoke WHERE tenant_id=? ORDER BY efficiency_pct DESC");
$stmt->execute([$tenant]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$avg_eff = count($rows) ? array_sum(array_column($rows, "efficiency_pct")) / count($rows) : 0;
echo json_encode(["tenant"=>$tenant,"count"=>count($rows),"avg_efficiency_pct"=>round($avg_eff,1),"devices"=>$rows]);
break;
case "kaizen":
$tenant = $_GET["tenant"] ?? "weval";
$stmt = $pdo->prepare("SELECT id, title, dept, problem, baseline, target, actual, savings_hours, savings_euro, team, duration_days, status FROM weval.kaizen_events WHERE tenant_id=? ORDER BY created_at DESC");
$stmt->execute([$tenant]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as &$r) {
$r["baseline"] = json_decode($r["baseline"], true);
$r["target"] = json_decode($r["target"], true);
$r["actual"] = json_decode($r["actual"], true);
}
$total_savings_euro = array_sum(array_column($rows, "savings_euro"));
echo json_encode(["tenant"=>$tenant,"count"=>count($rows),"total_savings_euro"=>$total_savings_euro,"events"=>$rows]);
break;
case "gemba":
$tenant = $_GET["tenant"] ?? "weval";
$stmt = $pdo->prepare("SELECT id, location, walker, observations, actions, muda_spotted, walk_duration_min, created_at FROM weval.gemba_walks WHERE tenant_id=? ORDER BY created_at DESC");
$stmt->execute([$tenant]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as &$r) {
$r["observations"] = json_decode($r["observations"], true);
$r["actions"] = json_decode($r["actions"], true);
}
echo json_encode(["tenant"=>$tenant,"count"=>count($rows),"walks"=>$rows]);
break;
case "pdca":
$tenant = $_GET["tenant"] ?? "weval";
$stmt = $pdo->prepare("SELECT id, title, phase, kpi_name, baseline, target, actual, created_at FROM weval.pdca_cycles WHERE tenant_id=? ORDER BY created_at DESC");
$stmt->execute([$tenant]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(["tenant"=>$tenant,"count"=>count($rows),"cycles"=>$rows]);
break;
case "andon":
$tenant = $_GET["tenant"] ?? "weval";
$status = $_GET["status"] ?? null;
$sql = "SELECT id, station, severity, message, status, triggered_by, resolved_by, resolution_time_min, created_at, resolved_at FROM weval.andon_alerts WHERE tenant_id=?";
$params = [$tenant];
if ($status) { $sql .= " AND status=?"; $params[] = $status; }
$sql .= " ORDER BY created_at DESC LIMIT 100";
$stmt = $pdo->prepare($sql); $stmt->execute($params);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$open = count(array_filter($rows, fn($r)=>$r["status"]==="open"));
echo json_encode(["tenant"=>$tenant,"count"=>count($rows),"open"=>$open,"alerts"=>$rows]);
break;
case "five-s":
$tenant = $_GET["tenant"] ?? "weval";
$stmt = $pdo->prepare("SELECT id, area, seiri, seiton, seiso, seiketsu, shitsuke, total_score, auditor, notes, created_at FROM weval.five_s_audits WHERE tenant_id=? ORDER BY created_at DESC");
$stmt->execute([$tenant]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$avg = count($rows) ? array_sum(array_column($rows, "total_score")) / count($rows) : 0;
echo json_encode(["tenant"=>$tenant,"count"=>count($rows),"avg_score"=>round($avg,1),"max_score"=>25,"audits"=>$rows]);
break;
case "a3":
$tenant = $_GET["tenant"] ?? "weval";
$stmt = $pdo->prepare("SELECT id, title, background, current_state, target_state, gap_analysis, root_causes, countermeasures, owner, status, created_at FROM weval.a3_reports WHERE tenant_id=? ORDER BY created_at DESC");
$stmt->execute([$tenant]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as &$r) {
$r["root_causes"] = json_decode($r["root_causes"], true);
$r["countermeasures"] = json_decode($r["countermeasures"], true);
}
echo json_encode(["tenant"=>$tenant,"count"=>count($rows),"reports"=>$rows]);
break;
case "lean6sigma-dashboard":
$tenant = $_GET["tenant"] ?? "weval";
$muda = $pdo->prepare("SELECT COUNT(*) as c, SUM(impact_hours) as h, SUM(impact_euro) as e FROM weval.muda_entries WHERE tenant_id=?");
$muda->execute([$tenant]);
$m = $muda->fetch(PDO::FETCH_ASSOC);
$poka = $pdo->prepare("SELECT COUNT(*) as c, AVG(efficiency_pct) as avg FROM weval.poka_yoke WHERE tenant_id=? AND status='active'");
$poka->execute([$tenant]);
$p = $poka->fetch(PDO::FETCH_ASSOC);
$kaizen = $pdo->prepare("SELECT COUNT(*) as c, SUM(savings_euro) as saved FROM weval.kaizen_events WHERE tenant_id=?");
$kaizen->execute([$tenant]);
$k = $kaizen->fetch(PDO::FETCH_ASSOC);
$andon_open = $pdo->prepare("SELECT COUNT(*) FROM weval.andon_alerts WHERE tenant_id=? AND status='open'");
$andon_open->execute([$tenant]);
$five_s_avg = $pdo->prepare("SELECT AVG(total_score) FROM weval.five_s_audits WHERE tenant_id=?");
$five_s_avg->execute([$tenant]);
$pdca_active = $pdo->prepare("SELECT COUNT(*) FROM weval.pdca_cycles WHERE tenant_id=? AND phase != 'act'");
$pdca_active->execute([$tenant]);
$a3_open = $pdo->prepare("SELECT COUNT(*) FROM weval.a3_reports WHERE tenant_id=? AND status IN ('draft','in-progress')");
$a3_open->execute([$tenant]);
$gemba = $pdo->prepare("SELECT COUNT(*), SUM(muda_spotted) FROM weval.gemba_walks WHERE tenant_id=?");
$gemba->execute([$tenant]);
$g = $gemba->fetch(PDO::FETCH_NUM);
$five_s_val = floatval($five_s_avg->fetchColumn() ?? 0);
$andon_o = intval($andon_open->fetchColumn());
$pdca_a = intval($pdca_active->fetchColumn());
$a3_o = intval($a3_open->fetchColumn());
echo json_encode([
"tenant" => $tenant,
"muda" => ["count"=>intval($m["c"]),"impact_hours"=>floatval($m["h"]),"impact_euro"=>floatval($m["e"])],
"poka_yoke" => ["count"=>intval($p["c"]),"avg_efficiency_pct"=>round(floatval($p["avg"]),1)],
"kaizen" => ["count"=>intval($k["c"]),"total_savings_euro"=>floatval($k["saved"])],
"andon_open" => $andon_o,
"five_s_avg_score" => round(floatval($five_s_val),1),
"pdca_active" => $pdca_a,
"a3_open" => $a3_o,
"gemba" => ["walks"=>intval($g[0]),"muda_spotted"=>intval($g[1])],
"maturity_score" => round((min(25, floatval($five_s_val ?? 0))/25 * 30) + (min(100, floatval($p["avg"] ?? 0))/100 * 30) + (min(10, intval($k["c"]))/10 * 20) + (min(5, intval($g[0]))/5 * 20), 1)
]);
break;
case "universal-connectors":
$cat = $_GET["category"] ?? null;
$search = $_GET["q"] ?? null;
$sql = "SELECT connector_code, connector_name, category, auth_type, base_url, docs_url, webhook_support, realtime_support, status, use_cases FROM weval.universal_connectors WHERE 1=1";
$params = [];
if ($cat) { $sql .= " AND category=?"; $params[] = $cat; }
if ($search) { $sql .= " AND (connector_name ILIKE ? OR connector_code ILIKE ?)"; $params[] = "%$search%"; $params[] = "%$search%"; }
$sql .= " ORDER BY category, connector_name";
$stmt = $pdo->prepare($sql); $stmt->execute($params);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as &$r) $r["use_cases"] = json_decode($r["use_cases"] ?? "[]", true);
$categories = $pdo->query("SELECT category, COUNT(*) as c FROM weval.universal_connectors GROUP BY category ORDER BY c DESC")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(["total"=>count($rows), "by_category"=>$categories, "connectors"=>$rows]);
break;
case "universal-stats":
$stats = [];
$stats["total"] = (int)$pdo->query("SELECT COUNT(*) FROM weval.universal_connectors")->fetchColumn();
$stats["by_category"] = $pdo->query("SELECT category, COUNT(*) as c FROM weval.universal_connectors GROUP BY category ORDER BY c DESC")->fetchAll(PDO::FETCH_ASSOC);
$stats["by_auth"] = $pdo->query("SELECT auth_type, COUNT(*) as c FROM weval.universal_connectors GROUP BY auth_type ORDER BY c DESC")->fetchAll(PDO::FETCH_ASSOC);
$stats["webhook_enabled"] = (int)$pdo->query("SELECT COUNT(*) FROM weval.universal_connectors WHERE webhook_support=true")->fetchColumn();
$stats["realtime_enabled"] = (int)$pdo->query("SELECT COUNT(*) FROM weval.universal_connectors WHERE realtime_support=true")->fetchColumn();
echo json_encode($stats);
break;
case "candidates":
// GET /api/em/candidates?tenant=weval&client=OCP_SAP_SUPPLY&status=validated&min_score=0.5
$tenant = $_GET["tenant"] ?? "weval";
$client = $_GET["client"] ?? null;
$status = $_GET["status"] ?? null;
$min_score = isset($_GET["min_score"]) ? floatval($_GET["min_score"]) : null;
$id = $parts[1] ?? null;
if ($_SERVER["REQUEST_METHOD"] === "POST" && !$id) {
// Create candidate
$body = json_decode(file_get_contents("php://input"), true) ?: $_POST;
if (empty($body["full_name"])) { http_response_code(400); echo json_encode(["error"=>"full_name required"]); break; }
$stmt = $pdo->prepare("INSERT INTO weval.candidates (tenant_id, client_code, full_name, phone, email, age, nationality, location, diploma, school, experience_years, status, notes) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) RETURNING id");
$stmt->execute([
$tenant, $body["client_code"] ?? null, $body["full_name"],
$body["phone"] ?? null, $body["email"] ?? null,
$body["age"] ?? null, $body["nationality"] ?? null, $body["location"] ?? null,
$body["diploma"] ?? null, $body["school"] ?? null,
$body["experience_years"] ?? null,
$body["status"] ?? "sourced",
$body["notes"] ?? null
]);
$new_id = $stmt->fetchColumn();
audit($pdo, "candidate_add", (string)$new_id, $body);
echo json_encode(["ok"=>true, "id"=>$new_id, "full_name"=>$body["full_name"]]);
break;
}
if ($id && ($parts[2] ?? "") === "score" && $_SERVER["REQUEST_METHOD"] === "POST") {
// Score a candidate
$body = json_decode(file_get_contents("php://input"), true) ?: $_POST;
$hard = floatval($body["hard_score"] ?? 0);
$soft = floatval($body["soft_score"] ?? 0);
$total = ($hard + $soft) / 2;
$pdo->prepare("UPDATE weval.candidates SET hard_score=?, soft_score=?, total_score=?, updated_at=NOW() WHERE id=? AND tenant_id=?")->execute([$hard,$soft,$total,$id,$tenant]);
$pdo->prepare("INSERT INTO weval.candidate_scoring (candidate_id, tenant_id, scorer, hard_score, soft_score, total_score, breakdown, comment) VALUES (?,?,?,?,?,?,?,?)")->execute([$id,$tenant,$body["scorer"]??"system",$hard,$soft,$total,json_encode($body["breakdown"]??[]),$body["comment"]??null]);
audit($pdo, "candidate_score", (string)$id, ["hard"=>$hard,"soft"=>$soft,"total"=>$total]);
echo json_encode(["ok"=>true, "id"=>$id, "hard_score"=>$hard, "soft_score"=>$soft, "total_score"=>$total]);
break;
}
if ($id && ($parts[2] ?? "") === "validate" && $_SERVER["REQUEST_METHOD"] === "POST") {
// Candidate -> Consultant
$body = json_decode(file_get_contents("php://input"), true) ?: $_POST;
$cand = $pdo->prepare("SELECT * FROM weval.candidates WHERE id=? AND tenant_id=?");
$cand->execute([$id, $tenant]);
$c = $cand->fetch(PDO::FETCH_ASSOC);
if (!$c) { http_response_code(404); echo json_encode(["error"=>"candidate_not_found"]); break; }
// Generate code
$code = "CST_" . date("Y") . "_" . str_pad($id, 3, "0", STR_PAD_LEFT);
$seniority = ($c["experience_years"] ?? 0) >= 10 ? "senior" : (($c["experience_years"] ?? 0) >= 5 ? "confirmed" : "junior");
$stmt = $pdo->prepare("INSERT INTO weval.consultants (tenant_id, candidate_id, consultant_code, full_name, email, phone, role, seniority, tjm_default, commission_rate, entity, status, hire_date) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) RETURNING id");
$stmt->execute([$tenant, $id, $code, $c["full_name"], $c["email"], $c["phone"],
$body["role"] ?? "Consultant", $seniority,
$body["tjm"] ?? 2470, $body["commission"] ?? 0.05,
$body["entity"] ?? "BA Solution", "active", date("Y-m-d")]);
$cst_id = $stmt->fetchColumn();
$pdo->prepare("UPDATE weval.candidates SET status='placed', updated_at=NOW() WHERE id=?")->execute([$id]);
audit($pdo, "candidate_validate", (string)$id, ["consultant_id"=>$cst_id,"code"=>$code]);
echo json_encode(["ok"=>true, "candidate_id"=>$id, "consultant_id"=>$cst_id, "consultant_code"=>$code]);
break;
}
if ($id) {
// GET single
$stmt = $pdo->prepare("SELECT * FROM weval.candidates WHERE id=? AND tenant_id=?");
$stmt->execute([$id, $tenant]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) { http_response_code(404); echo json_encode(["error"=>"not_found"]); break; }
// Attach skills history
$sk = $pdo->prepare("SELECT * FROM weval.candidate_skills WHERE candidate_id=?");
$sk->execute([$id]);
$row["skills"] = $sk->fetchAll(PDO::FETCH_ASSOC);
$hist = $pdo->prepare("SELECT * FROM weval.candidate_scoring WHERE candidate_id=? ORDER BY scored_at DESC LIMIT 10");
$hist->execute([$id]);
$row["scoring_history"] = $hist->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($row);
break;
}
// List
$sql = "SELECT id, full_name, status, short_list, experience_years, hard_score, soft_score, total_score, target_roles, client_code, new_flag, internal FROM weval.candidates WHERE tenant_id=?";
$params = [$tenant];
if ($client) { $sql .= " AND client_code=?"; $params[] = $client; }
if ($status) { $sql .= " AND status=?"; $params[] = $status; }
if ($min_score !== null) { $sql .= " AND total_score >= ?"; $params[] = $min_score; }
$sql .= " ORDER BY total_score DESC, experience_years DESC NULLS LAST";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as &$r) { $r["target_roles"] = json_decode($r["target_roles"], true); }
echo json_encode(["tenant"=>$tenant, "count"=>count($rows), "candidates"=>$rows]);
break;
case "missions":
$tenant = $_GET["tenant"] ?? "weval";
$id = $parts[1] ?? null;
if ($_SERVER["REQUEST_METHOD"] === "POST" && !$id) {
$body = json_decode(file_get_contents("php://input"), true) ?: $_POST;
if (empty($body["consultant_id"])) { http_response_code(400); echo json_encode(["error"=>"consultant_id required"]); break; }
$mcode = $body["mission_code"] ?? ("MIS_" . date("Y") . "_" . substr(md5(uniqid()), 0, 6));
$stmt = $pdo->prepare("INSERT INTO weval.missions (tenant_id, mission_code, consultant_id, client_code, client_name, deal_id, role, tjm, commission_rate, start_date, end_date, status) VALUES (?,?,?,?,?,?,?,?,?,?,?,?) RETURNING id");
$stmt->execute([$tenant, $mcode, $body["consultant_id"],
$body["client_code"] ?? null, $body["client_name"] ?? null,
$body["deal_id"] ?? null, $body["role"] ?? "Consultant",
$body["tjm"] ?? 2470, $body["commission_rate"] ?? 0.05,
$body["start_date"] ?? date("Y-m-d"), $body["end_date"] ?? null,
$body["status"] ?? "planned"]);
$new_id = $stmt->fetchColumn();
audit($pdo, "mission_create", (string)$new_id, $body);
echo json_encode(["ok"=>true, "id"=>$new_id, "mission_code"=>$mcode]);
break;
}
if ($id && ($parts[2] ?? "") === "billing" && $_SERVER["REQUEST_METHOD"] === "POST") {
$body = json_decode(file_get_contents("php://input"), true) ?: $_POST;
$period = $body["period_month"] ?? date("Y-m-01");
$days = floatval($body["days_worked"] ?? 0);
$tjm = floatval($body["tjm_applied"] ?? 2470);
$c_chafik = floatval($body["commission_chafik"] ?? 0.05);
$c_youssef = floatval($body["commission_youssef"] ?? 0.25);
$gross = $days * $tjm;
$com_c = $gross * $c_chafik;
$com_y = $gross * $c_youssef;
$cash = $gross - $com_c - $com_y;
$stmt = $pdo->prepare("INSERT INTO weval.mission_billing (tenant_id, mission_id, period_month, days_worked, tjm_applied, commission_chafik, commission_youssef, cash_consultant) VALUES (?,?,?,?,?,?,?,?) ON CONFLICT (mission_id, period_month) DO UPDATE SET days_worked=EXCLUDED.days_worked, tjm_applied=EXCLUDED.tjm_applied, cash_consultant=EXCLUDED.cash_consultant RETURNING id");
$stmt->execute([$tenant, $id, $period, $days, $tjm, $c_chafik, $c_youssef, $cash]);
$bid = $stmt->fetchColumn();
audit($pdo, "mission_bill", (string)$id, ["period"=>$period,"days"=>$days,"gross"=>$gross,"cash"=>$cash]);
echo json_encode(["ok"=>true, "billing_id"=>$bid, "mission_id"=>$id, "period"=>$period, "days"=>$days, "gross"=>$gross, "commission_chafik"=>$com_c, "commission_youssef"=>$com_y, "cash_consultant"=>$cash]);
break;
}
if ($id) {
$stmt = $pdo->prepare("SELECT m.*, c.full_name as consultant_name, c.role as consultant_role, d.title as deal_title, d.stage as deal_stage, d.value as deal_value, d.currency as deal_currency, d.expected_close as deal_close FROM weval.missions m LEFT JOIN weval.consultants c ON c.id = m.consultant_id LEFT JOIN crm.deals d ON d.id = m.deal_id WHERE m.id=? AND m.tenant_id=?");
$stmt->execute([$id, $tenant]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) { http_response_code(404); echo json_encode(["error"=>"not_found"]); break; }
$b = $pdo->prepare("SELECT id, period_month, days_worked, tjm_applied, gross_amount, commission_chafik, commission_youssef, cash_consultant, invoiced, paid FROM weval.mission_billing WHERE mission_id=? ORDER BY period_month");
$b->execute([$id]);
$row["billing"] = $b->fetchAll(PDO::FETCH_ASSOC);
$row["total_gross"] = array_sum(array_column($row["billing"], "gross_amount"));
$row["total_days"] = array_sum(array_column($row["billing"], "days_worked"));
$row["total_cash"] = array_sum(array_column($row["billing"], "cash_consultant"));
echo json_encode($row);
break;
}
// List missions + LEFT JOIN crm.deals (consolidation 17avr 15h05, lecture seule)
$stmt = $pdo->prepare("SELECT m.id, m.mission_code, m.client_code, m.client_name, m.role, m.tjm, m.start_date, m.end_date, m.status, m.deal_id, c.full_name as consultant_name, d.title as deal_title, d.stage as deal_stage, d.value as deal_value, d.currency as deal_currency FROM weval.missions m LEFT JOIN weval.consultants c ON c.id = m.consultant_id LEFT JOIN crm.deals d ON d.id = m.deal_id WHERE m.tenant_id=? ORDER BY m.start_date DESC");
$stmt->execute([$tenant]);
echo json_encode(["tenant"=>$tenant, "missions"=>$stmt->fetchAll(PDO::FETCH_ASSOC)]);
break;
case "consultants":
$tenant = $_GET["tenant"] ?? "weval";
$stmt = $pdo->prepare("SELECT id, consultant_code, full_name, role, seniority, tjm_default, commission_rate, entity, status, hire_date FROM weval.consultants WHERE tenant_id=? ORDER BY hire_date DESC NULLS LAST, id");
$stmt->execute([$tenant]);
echo json_encode(["tenant"=>$tenant, "consultants"=>$stmt->fetchAll(PDO::FETCH_ASSOC)]);
break;
default:
echo json_encode([
"service" => "WEVIA EM API",
"version" => "1.0-godmode-17avr",
"endpoints" => [
"/api/em/agents-registry?tenant=weval&dept=?",
"/api/em/vsm?tenant=weval (list) or /api/em/vsm/{dept}",
"/api/em/bpmn-routines?tenant=weval",
"/api/em/dmaic/{tenant}/{vs_id}",
"/api/em/kpi/live?tenant=weval&dept=?",
"/api/em/poc/start (POST)",
"/api/em/plans",
"/api/em/tenant/bootstrap (POST)",
"/api/em/audit?tenant=weval",
"/api/em/erp-connectors",
"/api/em/ai-providers",
"/api/em/industry-templates?sector=?",
"/api/em/tenant-integrations?tenant=X",
"POST /api/em/tenant-integrations/connect",
"/api/em/scalability",
"/api/em/muda?tenant=&type=",
"/api/em/poka-yoke?tenant=",
"/api/em/kaizen?tenant=",
"/api/em/gemba?tenant=",
"/api/em/pdca?tenant=",
"/api/em/andon?tenant=&status=",
"/api/em/five-s?tenant=",
"/api/em/a3?tenant=",
"/api/em/lean6sigma-dashboard?tenant=",
"/api/em/universal-connectors?category=&q=",
"/api/em/universal-stats"
]
]);
}