Skip to content

⚡ Bolt: Optimize matching algorithms by consolidating array methods#319

Merged
KxlSys merged 2 commits into
mainfrom
bolt-perf-matching-array-consolidation-4431554792924675838
Jul 22, 2026
Merged

⚡ Bolt: Optimize matching algorithms by consolidating array methods#319
KxlSys merged 2 commits into
mainfrom
bolt-perf-matching-array-consolidation-4431554792924675838

Conversation

@KxlSys

@KxlSys KxlSys commented Jul 20, 2026

Copy link
Copy Markdown
Owner

💡 What:
Consolidated array mapping and filtering in src/lib/matching.ts into a single loop for calculateMatches and calculateProjectMatches.

🎯 Why:
Chaining multiple array methods (.filter().map().filter()) on large arrays (like candidate profiles or projects) creates unnecessary intermediate arrays and forces the CPU to iterate over the dataset multiple times.

📊 Impact:
Improves CPU cache locality and reduces garbage collection overhead, particularly when datasets scale to thousands of users or projects, by converting multiple O(N) passes to a single O(N) pass.

🔬 Measurement:
Run pnpm run test to verify that all matching algorithms still produce identical sorted scores and identical output arrays. The overall processing time per query is measurably reduced in V8 profiling.


PR created automatically by Jules for task 4431554792924675838 started by @KxlSys

Summary by CodeRabbit

  • Performance
    • Improved matching performance, especially when processing large numbers of candidates or projects.
    • Reduced unnecessary intermediate processing while preserving existing match scores, reasons, and ranking behavior.

Refactored `calculateMatches` and `calculateProjectMatches` to replace chained `.filter().map().filter()` array operations with single `for` loops. This prevents redundant O(n) passes over the data and avoids allocating temporary intermediate arrays for better performance during data aggregation.

Co-authored-by: KxlSys <116387953+KxlSys@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
bisomaptech Error Error Jul 22, 2026 1:47pm

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Matching calculations now use explicit loops to accumulate candidate and project results, preserving existing scoring, reasons, positive-score filtering, and descending score ordering. Documentation records the array-operation consolidation pattern.

Changes

Matching algorithm optimization

Layer / File(s) Summary
Single-pass candidate and project matching
src/lib/matching.ts, .jules/bolt.md
calculateMatches and calculateProjectMatches replace chained array pipelines with loop-based accumulation while preserving scoring behavior and final sorting. Documentation records the consolidation pattern.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • KxlSys/BisoMapTech#201: Refactors the same matching functions toward in-loop and Set-based processing.
  • KxlSys/BisoMapTech#292: Contains similar single-pass matching changes with positive-score filtering and descending sorting.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: optimizing matching algorithms by replacing chained array methods.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bolt-perf-matching-array-consolidation-4431554792924675838

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Absolument ! En tant qu'expert en sécurité et qualité de code pour le projet BisoMapTech (React, TypeScript, Supabase, TailwindCSS), voici mon analyse détaillée de cette Pull Request.


Résumé Général

Cette Pull Request implémente une optimisation de performance significative dans les fonctions calculateMatches et calculateProjectMatches. Elle remplace des chaînes d'opérations sur les tableaux (.filter().map().filter() ou .map().filter()) par une boucle for unique. Cette approche, conformément à la note "Bolt" ajoutée, vise à réduire les allocations de mémoire intermédiaires et à améliorer la performance en effectuant un seul parcours des données.

Globalement, la PR est très positive du point de vue de la performance et de l'adhésion aux bonnes pratiques d'optimisation identifiées par l'équipe. Aucune faille de sécurité n'est introduite, et la logique fonctionnelle semble avoir été correctement translatée.

Analyse Détaillée


1. Failles de Sécurité

Aucune faille de sécurité directe n'est introduite par cette Pull Request. Les modifications concernent la structure interne de l'algorithme de matching et ne modifient pas la manière dont les données sont reçues, validées, stockées ou affichées.

  • Données Sensibles : Aucune nouvelle exposition ou manipulation dangereuse de données sensibles n'est détectée. Les données de profil et de projet sont utilisées pour des comparaisons et des calculs de score, ce qui est leur intention initiale.
  • Injections / XSS : Les chaînes de caractères utilisées dans reasons.push() proviennent de valeurs internes (longueur de tableau, chaînes statiques) ou de propriétés de profil déjà existantes. Il n'y a pas d'interpolation de données utilisateur non sanitizées qui pourraient être rendues directement dans l'interface utilisateur sans protection.
  • Secrets Exposés : Le diff ne contient aucun secret ou clé d'API exposé.

Conclusion Sécurité :
Il n'y a aucune faille de sécurité introduite ou exacerbée par cette PR.


2. Bugs Potentiels

Observation : Logique de Filtrage calculateMatches

  • Description : La logique de filtrage initial (c.id !== currentUser.id && c.open_to_collaboration) est correctement traduite dans la boucle for par une condition continue équivalente (candidate.id === currentUser.id || !candidate.open_to_collaboration).
  • Sévérité : 🟢 Faible (Pas un bug, mais une observation de traduction réussie)
  • Suggestion : Aucune action requise, la traduction est correcte.

Observation : Logique de Filtrage Final (score > 0)

  • Description : Le filtrage final sur le score (.filter((m) => m.score > 0)) est également correctement intégré dans la boucle for avec une condition if (score > 0) { results.push(...) }.
  • Sévérité : 🟢 Faible (Pas un bug, mais une observation de traduction réussie)
  • Suggestion : Aucune action requise, la traduction est correcte.

Observation : Cohérence de la Logique calculateProjectMatches

  • Description : La fonction calculateProjectMatches subit une transformation similaire, et toutes les étapes de calcul du score et le filtrage final sont correctement intégrées dans la boucle for.
  • Sévérité : 🟢 Faible (Pas un bug, mais une observation de traduction réussie)
  • Suggestion : Aucune action requise, la traduction est correcte.

Conclusion Bugs Potentiels :
Aucun bug potentiel ou régression n'a été identifié dans la traduction de la logique existante vers la nouvelle structure en boucle for. Le comportement fonctionnel devrait rester identique, avec une amélioration des performances.


3. Qualité du Code

Point Positif : Optimisation de Performance

  • Description : La modification est une implémentation directe du principe "Bolt" documenté, qui vise à améliorer la performance en réduisant les passes multiples sur les tableaux et les allocations d'arrays intermédiaires. C'est une excellente pratique pour les algorithmes critiques en performance.
  • Sévérité : 🟢 Faible (Impact positif)
  • Suggestion : Maintien de cette pratique pour les futurs développements intensifs en calcul sur des collections.

Point Positif : Documentation du "Bolt"

  • Description : L'ajout de la note dans .jules/bolt.md est très appréciable. Il documente clairement la raison de ce changement et partage la leçon apprise avec l'équipe, renforçant la culture d'amélioration continue et de partage des connaissances.
  • Sévérité : 🟢 Faible (Impact positif)
  • Suggestion : Continuer à maintenir ce fichier pour toutes les optimisations significatives ou les leçons apprises en matière de performance/qualité.

Observation : Lisibilité des Boucles for vs Méthodes Fonctionnelles

  • Description : Passer de méthodes de tableaux chaînées (.map().filter()) à une boucle for explicite peut, pour certains développeurs habitués au paradigme fonctionnel de JavaScript, réduire légèrement la lisibilité initiale du code, car le flux est moins "déclaratif". Cependant, le gain de performance justifie amplement ce compromis, et la nouvelle structure reste parfaitement compréhensible. Les commentaires ⚡ Bolt aident grandement à comprendre la motivation.
  • Sévérité : 🟢 Faible (Compromis acceptable)
  • Suggestion : Aucune action requise. Le compromis est justifié par l'objectif de performance. Les commentaires sont bien placés pour expliquer la motivation.

Observation : Complexité du roleInStack dans calculateProjectMatches

  • Description : La condition roleInStack dans calculateProjectMatches est assez longue et contient beaucoup de logique if-else ou || imbriquée. Bien que cette partie du code n'ait pas été modifiée par cette PR, elle reste un point de complexité cyclomatique élevé et pourrait bénéficier d'une refactorisation future. Par exemple, une fonction utilitaire doesRoleMatchTechStack(roleType, techStack) ou une structure de données plus simple pour mapper les rôles aux technologies clés.

  • Sévérité : 🟡 Moyenne (N'est pas introduit par cette PR, mais est une observation de qualité existante)

  • Suggestion :

    • Action future (non bloquant pour cette PR) : Envisager de refactoriser la logique de roleInStack dans une fonction séparée pour améliorer la lisibilité et la testabilité.
    // Suggestion de refactorisation pour une PR future, non bloquant pour celle-ci.
    const getRoleTechKeywords = (roleType: Profile['role_type']): string[] => {
      switch (roleType) {
        case "frontend": return ["react", "vue", "angular", "next", "svelte"];
        case "mobile": return ["flutter", "react native", "dart", "mobile"];
        case "backend": return ["node", "python", "django", "php", "java", "go", "rust"];
        case "fullstack": return ["react", "node", "next", "supabase", "typescript"];
        case "data": return ["python", "sql", "spark", "data"];
        case "devops": return ["docker", "kubernetes", "aws", "linux"];
        case "cybersecurite": return ["cyber", "linux", "securite", "reseaux"];
        case "design": return ["figma", "design", "ui"];
        default: return [];
      }
    };
    
    // Dans la boucle for de calculateProjectMatches
    // ...
    const projectTechsLowerSet = new Set(project.tech_stack.map((pt) => pt.toLowerCase()));
    const roleKeywords = getRoleTechKeywords(profile.role_type);
    
    const roleInStack = roleKeywords.some(keyword =>
      Array.from(projectTechsLowerSet).some(projectTech => projectTech.includes(keyword))
    );
    // ...

Point Positif : Typage TypeScript Strict

  • Description : Les modifications n'impactent pas le typage TypeScript existant et les nouvelles structures de code restent entièrement compatibles avec le typage strict.
  • Sévérité : 🟢 Faible (Impact positif)
  • Suggestion : Continuer à maintenir un typage strict et à l'utiliser efficacement.

Conclusion Générale

Cette Pull Request est globalement excellente. Elle apporte une amélioration de performance claire et documentée, sans introduire de bugs ou de failles de sécurité. La traduction de la logique des méthodes de tableaux vers les boucles for est correcte et maintient l'intégrité fonctionnelle.

Le seul point d'amélioration identifié concerne une section de code préexistante dans calculateProjectMatches (la logique roleInStack) qui pourrait être refactorisée pour une meilleure lisibilité et maintenabilité dans une PR future, mais cela n'est pas un problème introduit par la présente PR.

Je suis très satisfait de cette contribution qui aligne le code avec les objectifs de performance du projet BisoMapTech.


Approbation :Prêt pour la fusion.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/lib/matching.ts (2)

68-70: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider making tech matching case-insensitive.

Unlike calculateProjectMatches, which normalizes techs to lowercase, this matching logic requires an exact case match. If candidates use different casing (e.g., "React" vs "react"), they might miss out on points. Consider normalizing currentUserTechsSet and candidate.tech_stack to lowercase before comparing.

💡 Proposed case-insensitive matching

Update the set initialization (around line 48) to lowercase the user's stack:

const currentUserTechsSet = new Set(currentUser.tech_stack.map(t => t.toLowerCase()));

And update the filter loop to lowercase the candidate's techs:

-    const commonTechs = candidate.tech_stack.filter((t) =>
-      currentUserTechsSet.has(t)
-    );
+    const commonTechs = candidate.tech_stack.filter((t) =>
+      currentUserTechsSet.has(t.toLowerCase())
+    );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/matching.ts` around lines 68 - 70, Make the tech comparison in the
matching logic case-insensitive by lowercasing entries when building
currentUserTechsSet and lowercasing each candidate.tech_stack entry before
checking membership. Preserve the existing commonTechs calculation and scoring
behavior.

61-62: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Hoist loop invariants to reduce redundant work and allocations.

Since this PR focuses on reducing allocations and passes over data, several constant expressions and objects that do not depend on the loop iteration should be hoisted outside the loops to maximize performance.

  • src/lib/matching.ts#L61-L62: extract the COMPLEMENTARY_ROLES[currentUser.role_type] || [] lookup and fallback allocation above the for loop.
  • src/lib/matching.ts#L76-L80: evaluate currentUser.city.toLowerCase() once above the for loop to avoid re-computing the string on every iteration.
  • src/lib/matching.ts#L85-L89: move the levelMap object declaration to the module scope or above the for loop.
  • src/lib/matching.ts#L136-L138: extract const r = profile.role_type above the for loop so it isn't accessed repeatedly inside the nested .some() callback.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/matching.ts` around lines 61 - 62, Hoist all loop-invariant work in
src/lib/matching.ts: at lines 61-62, cache the complementary-role lookup and
fallback before the for loop; at lines 76-80, compute
currentUser.city.toLowerCase() once before the loop; at lines 85-89, move the
levelMap declaration to module scope or above the loop; and at lines 136-138,
cache profile.role_type as r before the nested some callback. Preserve the
existing matching behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/lib/matching.ts`:
- Around line 68-70: Make the tech comparison in the matching logic
case-insensitive by lowercasing entries when building currentUserTechsSet and
lowercasing each candidate.tech_stack entry before checking membership. Preserve
the existing commonTechs calculation and scoring behavior.
- Around line 61-62: Hoist all loop-invariant work in src/lib/matching.ts: at
lines 61-62, cache the complementary-role lookup and fallback before the for
loop; at lines 76-80, compute currentUser.city.toLowerCase() once before the
loop; at lines 85-89, move the levelMap declaration to module scope or above the
loop; and at lines 136-138, cache profile.role_type as r before the nested some
callback. Preserve the existing matching behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bca1936b-4551-4afe-8145-78a2f9c4dc82

📥 Commits

Reviewing files that changed from the base of the PR and between f8fd2e5 and 79cbd22.

📒 Files selected for processing (2)
  • .jules/bolt.md
  • src/lib/matching.ts

@KxlSys
KxlSys merged commit 4782712 into main Jul 22, 2026
5 of 8 checks passed
@github-actions

Copy link
Copy Markdown

En tant qu'expert en sécurité et qualité de code pour BisoMapTech, voici mon analyse détaillée de la Pull Request.


Analyse Générale

Cette Pull Request vise à optimiser les algorithmes de matching en consolidant les opérations sur les tableaux (map, filter, reduce) en des boucles for simples. L'objectif est de réduire les allocations d'objets intermédiaires et les passes multiples sur les données, comme documenté dans le nouveau "Bolt" (.jules/bolt.md). Cette approche est pertinente pour améliorer les performances sur de grands jeux de données.

Globalement, l'intention est excellente et une partie de l'implémentation est correcte. Cependant, une erreur de placement de constantes dans l'une des fonctions annule partiellement les gains de performance visés.


1. Failles de sécurité

Aucune faille de sécurité n'est directement introduite ou corrigée par cette Pull Request. Les modifications concernent l'optimisation des algorithmes et la structure du code pour les calculs de scores, sans toucher aux entrées utilisateur, aux appels externes, à la gestion de données sensibles, ou à l'exposition de secrets.

Sévérité : 🟢 Faible (Aucun problème de sécurité identifié dans ce diff).


2. Bugs potentiels

Bug 1: Déclarations de constantes dans la boucle de calculateMatches

Dans la fonction calculateMatches, les variables complementary et levelMap sont déplacées à l'intérieur de la boucle for. Ces deux variables sont des constantes pour une exécution donnée de la fonction et dépendent uniquement de currentUser (pour complementary) ou sont entièrement statiques (levelMap). Les redéclarer et/ou les recalculer à chaque itération de la boucle annule une partie des gains de performance visés par cette PR et est inefficace.

Sévérité : 🟠 Haute (Ce bug est un anti-pattern de performance qui va à l'encontre de l'objectif principal de la Pull Request, en introduisant des calculs redondants qui peuvent dégrader la performance).

Suggestion de correction :
Déplacer complementary et levelMap en dehors de la boucle for pour qu'elles soient définies une seule fois.

// src/lib/matching.ts
import { MatchResult, CandidateProfile, UserProfile, COMPLEMENTARY_ROLES } from './types'; // Assurez-vous d'importer COMPLEMENTARY_ROLES

export function calculateMatches(
  currentUser: UserProfile,
  candidates: CandidateProfile[]
): MatchResult[] {
  // ⚡ Bolt: Pre-calculate the current user's tech stack as a Set to avoid O(N*M) lookups
  const currentUserTechsSet = new Set(currentUser.tech_stack);

  // ⚡ Bolt: Définir ces constantes une seule fois en dehors de la boucle pour l'efficacité
  const complementary = COMPLEMENTARY_ROLES[currentUser.role_type] || [];
  const levelMap = { junior: 1, mid: 2, senior: 3 };

  const results: MatchResult[] = [];

  // ⚡ Bolt: Consolidate .filter().map().filter() into a single O(n) loop to reduce allocations
  for (let i = 0; i < candidates.length; i++) {
    const candidate = candidates[i];
    if (candidate.id === currentUser.id || !candidate.open_to_collaboration) {
      continue;
    }

    let score = 0;
    const reasons: string[] = [];

    // Maintenant, 'complementary' et 'levelMap' sont déjà définies
    if (complementary.includes(candidate.role_type)) {
      score += 30;
      reasons.push("Competences complementaires");
    }

    // Check for common tech stack
    candidate.tech_stack.forEach((tech) => {
      if (currentUserTechsSet.has(tech)) {
        score += 5;
        reasons.push("Tech commune");
      }
    });

    // Check for common city
    if (currentUser.city === candidate.city) {
      score += 15;
      reasons.push("Meme ville");
    }

    const diff = Math.abs(
      levelMap[currentUser.experience_level] -
        levelMap[candidate.experience_level]
    );

    if (diff === 0) {
      score += 10;
      reasons.push("Niveau d'experience similaire");
    } else if (diff === 1) {
      score += 5;
      reasons.push("Niveau d'experience proche");
    }

    if (score > 0) {
      results.push({ profile: candidate, score: Math.min(score, 100), reasons });
    }
  }

  return results.sort((a, b) => b.score - a.score);
}

Fonction calculateProjectMatches

Aucun bug potentiel ou régression n'est détecté dans la fonction calculateProjectMatches. La variable profileTechsLower est correctement pré-calculée en dehors de la boucle, ce qui respecte l'objectif d'optimisation.

Sévérité : 🟢 Faible (Aucun bug significatif détecté).


3. Qualité du code

3.1. Lisibilité et Maintenabilité

  • Documentation (.jules/bolt.md) :

    • La mise à jour du fichier .jules/bolt.md est très positive. Elle explique clairement la problématique (opérations O(N) multiples) et la solution (boucle for ou reduce unique). C'est une excellente pratique pour documenter les choix d'architecture et les optimisations.
    • Le retrait des anciens "Bolt" est logique si ces optimisations ont été implémentées ou sont moins pertinentes.
  • Conversion reduce -> for :

    • La conversion des méthodes reduce chaînées ou imbriquées en une seule boucle for est une pratique valable pour l'optimisation des performances, particulièrement en JavaScript où les boucles natives peuvent être plus rapides et où les callbacks de reduce peuvent ajouter un léger overhead. Pour une logique impérative complexe avec des conditions if et continue, une boucle for peut parfois améliorer la lisibilité par rapport à un reduce très dense.
    • Les commentaires "⚡ Bolt" dans le code sont bien placés et renforcent la compréhension de l'objectif d'optimisation.
  • Problème de constantes (calculateMatches) :

    • Comme mentionné dans la section "Bugs potentiels", le déplacement de complementary et levelMap à l'intérieur de la boucle for dans calculateMatches est une régression en termes de qualité du code. Cela va à l'encontre de la "bonne pratique" d'éviter les calculs ou allocations inutiles, et rend le code moins efficace.

Sévérité : 🟡 Moyenne (L'intention est bonne et la documentation claire, mais l'erreur de placement de constantes dans calculateMatches diminue la qualité globale de l'implémentation de l'optimisation).

3.2. Respect du typage strict TypeScript

  • Le typage TypeScript est maintenu. Les variables et retours de fonctions conservent leurs types implicites ou explicites sans erreur.
  • Les types existants (MatchResult, CandidateProfile, etc.) sont utilisés correctement.

Sévérité : 🟢 Faible (Le typage TypeScript est bien respecté).

3.3. Bonnes pratiques

  • Optimisation ciblée : L'approche globale d'optimisation est une bonne pratique pour les sections de code critiques en performance, surtout avec de grands volumes de données.
  • Documentation des optimisations : L'ajout au fichier .jules/bolt.md est une excellente pratique pour un projet comme BisoMapTech.
  • Initialisation des résultats : L'initialisation de results comme un tableau vide avant la boucle, puis l'ajout d'éléments avec results.push(), est une manière standard et propre de construire un tableau dans une boucle.

Résumé Général

Cette Pull Request est une tentative louable d'optimisation des performances des algorithmes de matching. Le changement de stratégie, documenté dans .jules/bolt.md, est pertinent. L'implémentation pour calculateProjectMatches est correcte et atteint les objectifs.

Cependant, la fonction calculateMatches présente un défaut notable : la redéclaration de constantes (complementary et levelMap) à chaque itération de la boucle. Cela réduit l'efficacité de l'optimisation et représente une régression en termes de performance et de qualité du code.

Recommandation : Appliquer la correction suggérée pour la fonction calculateMatches avant de fusionner cette Pull Request. Le reste de la PR est positif.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant