&min_conf=0.5&limit=20&source=llm|rss|playwright] // Returns aggregated scan data for pain-points-atlas integration header('Content-Type: application/json'); header('Cache-Control: public, max-age=60'); header('Access-Control-Allow-Origin: *'); $erp = $_GET['erp'] ?? null; $min_conf = isset($_GET['min_conf']) ? (float)$_GET['min_conf'] : 0.3; $limit = isset($_GET['limit']) ? min(100, (int)$_GET['limit']) : 50; $source = $_GET['source'] ?? null; // llm|rss|playwright try { $pdo = new PDO('pgsql:host=10.1.0.3;port=5432;dbname=adx_system', 'admin', 'admin123', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT => 3, ]); // Source filter $source_clause = ""; $params_stats = []; if ($source) { $source_clause = " WHERE query LIKE :source_pattern"; $params_stats[':source_pattern'] = $source . '_%'; } // Global stats $stmt = $pdo->prepare("SELECT COUNT(*) as total_gaps, COUNT(DISTINCT erp_id) as erps_covered, ROUND(AVG(confidence_score)::NUMERIC, 3) as avg_confidence, MAX(scanned_at) as last_scan_at, COUNT(*) FILTER (WHERE query LIKE 'llm_%') as gaps_llm, COUNT(*) FILTER (WHERE query LIKE 'rss_%') as gaps_rss, COUNT(*) FILTER (WHERE query LIKE 'playwright%') as gaps_playwright FROM erp_gap_scans" . $source_clause); $stmt->execute($params_stats); $stats = $stmt->fetch(PDO::FETCH_ASSOC); // Per-ERP breakdown $stmt = $pdo->prepare("-- V96.4: GROUP BY erp_id only (not erp_name) to avoid duplicates (Oracle Fusion vs Oracle Fusion Cloud ERP) SELECT erp_id, MAX(erp_name) as erp_name, -- canonical display name COUNT(*) as gaps_count, ROUND(AVG(confidence_score)::NUMERIC, 3) as avg_conf, MAX(scanned_at) as last_scan, array_agg(DISTINCT CASE WHEN query LIKE 'llm_%' THEN 'LLM' WHEN query LIKE 'rss_%' THEN 'RSS' WHEN query LIKE 'playwright%' THEN 'Playwright' ELSE 'Other' END) as sources FROM erp_gap_scans" . $source_clause . " GROUP BY erp_id ORDER BY gaps_count DESC"); $stmt->execute($params_stats); $per_erp = $stmt->fetchAll(PDO::FETCH_ASSOC); // Detail gaps (filtered) $detail_sql = "SELECT id, erp_id, erp_name, title, snippet, source_url, confidence_score, keywords, CASE WHEN query LIKE 'llm_%' THEN 'LLM' WHEN query LIKE 'rss_%' THEN 'RSS' WHEN query LIKE 'playwright%' THEN 'Playwright' ELSE 'Other' END as source, scanned_at FROM erp_gap_scans WHERE confidence_score >= :min_conf"; $params = [':min_conf' => $min_conf]; if ($erp) { $detail_sql .= " AND erp_id = :erp"; $params[':erp'] = $erp; } if ($source) { $detail_sql .= " AND query LIKE :src"; $params[':src'] = $source . '_%'; } $detail_sql .= " ORDER BY confidence_score DESC, scanned_at DESC LIMIT " . (int)$limit; $stmt = $pdo->prepare($detail_sql); $stmt->execute($params); $details = $stmt->fetchAll(PDO::FETCH_ASSOC); // Latest scans per source $stmt = $pdo->query("SELECT CASE WHEN query LIKE 'llm_%' THEN 'LLM' WHEN query LIKE 'rss_%' THEN 'RSS' WHEN query LIKE 'playwright%' THEN 'Playwright' ELSE 'Other' END as source, MAX(scanned_at) as last_scan, COUNT(*) as gaps, COUNT(DISTINCT erp_id) as erps FROM erp_gap_scans GROUP BY source ORDER BY last_scan DESC"); $sources = $stmt->fetchAll(PDO::FETCH_ASSOC); echo json_encode([ 'generated_at' => date('c'), 'version' => 'V96', 'module' => 'ERP Gap Scans — consolidated D+C+B', 'stats' => $stats, 'sources' => $sources, 'per_erp' => $per_erp, 'gaps' => $details, 'filters_applied' => [ 'erp' => $erp, 'min_confidence' => $min_conf, 'limit' => $limit, 'source' => $source, ], ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => 'db_error', 'message' => $e->getMessage()]); }