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" ] ]); }