aboutsummaryrefslogtreecommitdiff
path: root/api/debug/clusters.php
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-07-29 20:46:12 +0200
committerAgando <roduccomm@gmail.com>2026-07-29 20:46:12 +0200
commit19b8da0faccfc4438ed19fbc99d992a6a07af003 (patch)
tree8bcc4112a20b57a8b03a360097b20101b75e7b4a /api/debug/clusters.php
parent2538c91876455fe96febf441fc8dde20f2606023 (diff)
auto: 2026-07-29_20:50 changes — gunes sponsorluk page, bunq payment modal, AI platforms, news W30
Diffstat (limited to 'api/debug/clusters.php')
-rw-r--r--api/debug/clusters.php98
1 files changed, 98 insertions, 0 deletions
diff --git a/api/debug/clusters.php b/api/debug/clusters.php
new file mode 100644
index 0000000..5174317
--- /dev/null
+++ b/api/debug/clusters.php
@@ -0,0 +1,98 @@
+<?php
+require_once __DIR__ . '/config.php';
+
+$errors = load_json(ERRORS_FILE);
+$rebuildFlag = DEBUG_DATA_DIR . '/.rebuild_clusters';
+
+// Check if cached clusters exist and are fresh
+if (file_exists(CLUSTERS_FILE) && !file_exists($rebuildFlag)) {
+ $clusters = load_json(CLUSTERS_FILE);
+ json_response([
+ 'success' => true,
+ 'cached' => true,
+ 'clusters' => $clusters,
+ 'total_errors' => count($errors),
+ ]);
+}
+
+// Rebuild clusters
+$normalized = [];
+foreach ($errors as $e) {
+ $msg = strtolower($e['message'] ?? '');
+ $msg = preg_replace('/\b0x[a-f0-9]+\b/', '0xHEX', $msg);
+ $msg = preg_replace('/\b\d+\b/', 'N', $msg);
+ $msg = preg_replace('/["\'].*?["\']/', '"STR"', $msg);
+ $msg = preg_replace('/[^\w\s]/', ' ', $msg);
+ $msg = preg_replace('/\s+/', ' ', $msg);
+ $normalized[] = trim($msg);
+}
+
+$clusters = [];
+$assigned = [];
+$threshold = CLUSTER_SIMILARITY_THRESHOLD;
+
+for ($i = 0; $i < count($errors); $i++) {
+ if (isset($assigned[$i])) continue;
+
+ $cluster = [
+ 'errors' => [$i],
+ 'messages' => [$errors[$i]['message']],
+ 'count' => $errors[$i]['count'] ?? 1,
+ 'types' => [$errors[$i]['type'] ?? 'unknown'],
+ 'categories' => [$errors[$i]['category'] ?? 'runtime'],
+ 'label' => $errors[$i]['message'] ?? '',
+ ];
+ $assigned[$i] = true;
+
+ for ($j = $i + 1; $j < count($errors); $j++) {
+ if (isset($assigned[$j])) continue;
+
+ // Jaccard similarity
+ $tokensA = array_unique(array_filter(explode(' ', $normalized[$i])));
+ $tokensB = array_unique(array_filter(explode(' ', $normalized[$j])));
+ if (empty($tokensA) && empty($tokensB)) {
+ $similarity = 1;
+ } else {
+ $intersection = count(array_intersect($tokensA, $tokensB));
+ $union = count($tokensA) + count($tokensB) - $intersection;
+ $similarity = $union > 0 ? $intersection / $union : 0;
+ }
+
+ if ($similarity >= $threshold) {
+ $cluster['errors'][] = $j;
+ $cluster['messages'][] = $errors[$j]['message'];
+ $cluster['count'] += $errors[$j]['count'] ?? 1;
+ if (!in_array($errors[$j]['type'] ?? 'unknown', $cluster['types'])) {
+ $cluster['types'][] = $errors[$j]['type'] ?? 'unknown';
+ }
+ if (!in_array($errors[$j]['category'] ?? 'runtime', $cluster['categories'])) {
+ $cluster['categories'][] = $errors[$j]['category'] ?? 'runtime';
+ }
+ $assigned[$j] = true;
+ }
+ }
+
+ // Most frequent message as label
+ $freq = array_count_values($cluster['messages']);
+ arsort($freq);
+ $cluster['label'] = substr(key($freq) ?: $cluster['label'], 0, 200);
+ $cluster['error_count'] = count($cluster['errors']);
+ unset($cluster['errors']);
+ unset($cluster['messages']);
+
+ $clusters[] = $cluster;
+}
+
+usort($clusters, fn($a, $b) => $b['count'] - $a['count']);
+
+// Cache
+save_json(CLUSTERS_FILE, $clusters);
+if (file_exists($rebuildFlag)) unlink($rebuildFlag);
+
+json_response([
+ 'success' => true,
+ 'cached' => false,
+ 'clusters' => array_slice($clusters, 0, 50),
+ 'total_errors' => count($errors),
+ 'total_clusters' => count($clusters),
+]);