Source: backend/workers/workerFoldersuggestion.js

/**
 * @fileoverview Diese Datei enthält den Worker zur Ordner-Vorschlagserstellung.
 * Sie ermöglicht die Berechnung von Ähnlichkeiten und Clustering von Dokument- und Ordner-Embeddings.
 * 
 * @author Lennart
 */


const { parentPort } = require('worker_threads');
const { runClustering } = require('../models/modelClustering');

/**
 * Lauscht auf eingehende Nachrichten vom Hauptthread und führt entsprechende Aktionen aus.
 * Unterstützt Gesundheitschecks und führt Clustering-Operationen durch.
 * 
 * @param {Object} task - Das empfangene Task-Objekt.
 * @param {string} task.type - Der Typ der Aufgabe (`HEALTH_CHECK` oder Clustering-Aufgabe).
 * @param {Array<number>} [task.docEmbedding] - Das Embedding des zu analysierenden Dokuments.
 * @param {Array<Object>} [task.folderVectors] - Eine Liste von Ordnern mit ihren Embeddings.
 * @param {Object} [task.config] - Konfiguration für das Clustering.
 */
parentPort.on('message', async (task) => {
    if (task.type === 'HEALTH_CHECK') {
        parentPort.postMessage({ type: 'HEALTH_CHECK', status: 'healthy' });
        return;
    }

    try {
        const { docEmbedding, folderVectors, config } = task;

        // Prepare clustering input
        const clusteringInput = {
            embeddings: [docEmbedding, ...folderVectors.map(f => f.embedding)],
            documentIndex: 0
        };

        // Run clustering in worker
        const clusteringResults = await runClustering(
            clusteringInput.embeddings,
            {
                minClusterSize: 2,
                semanticThreshold: config.similarityThreshold,
                anchorInfluence: config.clusterInfluence
            }
        );

        // Process results
        const suggestions = processSuggestions(
            clusteringResults,
            docEmbedding,
            folderVectors,
            config
        );

        parentPort.postMessage({
            success: true,
            suggestions
        });

    } catch (error) {
        parentPort.postMessage({
            success: false,
            error: error.message
        });
    }
});

/**
 * Verarbeitet die Clustering-Ergebnisse und berechnet die besten Ordner-Vorschläge.
 * 
 * @function processSuggestions
 * @param {Object} clusteringResults - Das Ergebnis des Clustering-Prozesses.
 * @param {Array<number>} docEmbedding - Das Embedding des Dokuments.
 * @param {Array<Object>} folderVectors - Eine Liste von Ordnern mit ihren Embeddings.
 * @param {Object} config - Konfigurationsoptionen für die Berechnung.
 * @returns {Array<Object>} Eine sortierte Liste von Ordner-Vorschlägen mit Metadaten.
 */
function processSuggestions(clusteringResults, docEmbedding, folderVectors, config) {
    const documentCluster = clusteringResults.labels[0];

    return folderVectors
        .map((folder, index) => {
            const folderCluster = clusteringResults.labels[index + 1];
            const similarity = calculateCosineSimilarity(docEmbedding, folder.embedding);

            const clusterBoost =
                documentCluster === folderCluster && documentCluster !== -1
                    ? config.clusterInfluence
                    : 0;

            return {
                folderId: folder.folder_id,
                folderName: folder.folder_name,
                similarity: similarity * (1 + clusterBoost),
                fileCount: folder.file_count,
                recentFiles: folder.recent_files,
                parentId: folder.parent_folder_id,
                hasChildren: folder.has_children,
                avgFileSimilarity: folder.avg_file_similarity,
                clusterContext: {
                    inSameCluster: documentCluster === folderCluster,
                    clusterLabel: folderCluster
                }
            };
        })
        .sort((a, b) => b.similarity - a.similarity)
        .slice(0, config.maxSuggestions);
}

/**
 * Berechnet die Kosinus-Ähnlichkeit zwischen zwei Vektoren.
 * 
 * @function calculateCosineSimilarity
 * @param {Array<number>} vec1 - Der erste Vektor.
 * @param {Array<number>} vec2 - Der zweite Vektor.
 * @returns {number} Ein Wert zwischen -1 und 1, der die Ähnlichkeit der beiden Vektoren beschreibt.
 */
function calculateCosineSimilarity(vec1, vec2) {
    const dotProduct = vec1.reduce((sum, val, i) => sum + val * vec2[i], 0);
    const norm1 = Math.sqrt(vec1.reduce((sum, val) => sum + val * val, 0));
    const norm2 = Math.sqrt(vec2.reduce((sum, val) => sum + val * val, 0));
    return dotProduct / (norm1 * norm2);
}