Skip to content

Commit da264aa

Browse files
authored
Merge branch 'main' into bolt-optimization-matching-page-11450142854350254193
2 parents 6a7ce37 + b6fe074 commit da264aa

4 files changed

Lines changed: 190 additions & 165 deletions

File tree

src/hooks/use-platform-stats.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,36 @@ export interface PlatformStats {
99
techCount: number;
1010
}
1111

12+
// ⚡ Bolt: Consolidated multiple array passes (.map.filter, .filter, .flatMap) into a single O(n) loop
1213
function computeStats(
1314
profiles: Array<{ city?: string | null; open_to_collaboration?: boolean | null; tech_stack?: string[] | null }>
1415
): PlatformStats {
1516
const total = profiles.length;
1617
if (total === 0) return { totalUsers: 0, totalCities: 0, collaborationRate: 0, techCount: 0 };
1718

18-
// ⚡ Bolt: Consolidate data aggregation into a single O(n) pass
19-
// Avoids multiple redundant iterations and intermediate array allocations
20-
const citySet = new Set<string>();
21-
const techSet = new Set<string>();
2219
let collab = 0;
20+
const citiesSet = new Set<string>();
21+
const techsSet = new Set<string>();
2322

24-
for (let i = 0; i < total; i++) {
25-
const p = profiles[i];
26-
if (p.city) citySet.add(p.city);
27-
if (p.open_to_collaboration) collab++;
23+
for (const p of profiles) {
24+
if (p.open_to_collaboration) {
25+
collab++;
26+
}
27+
if (p.city) {
28+
citiesSet.add(p.city);
29+
}
2830
if (p.tech_stack) {
29-
for (let j = 0; j < p.tech_stack.length; j++) {
30-
techSet.add(p.tech_stack[j]);
31+
for (const tech of p.tech_stack) {
32+
techsSet.add(tech);
3133
}
3234
}
3335
}
3436

3537
return {
3638
totalUsers: total,
37-
totalCities: citySet.size,
39+
totalCities: citiesSet.size,
3840
collaborationRate: Math.round((collab / total) * 100),
39-
techCount: techSet.size,
41+
techCount: techsSet.size,
4042
};
4143
}
4244

src/lib/matching.ts

Lines changed: 113 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -46,55 +46,62 @@ export function calculateMatches(
4646
): MatchResult[] {
4747
// ⚡ Bolt: Pre-calculate the current user's tech stack as a Set to avoid O(N*M) lookups
4848
const currentUserTechsSet = new Set(currentUser.tech_stack);
49-
50-
return candidates
51-
.filter((c) => c.id !== currentUser.id && c.open_to_collaboration)
52-
.map((candidate) => {
53-
let score = 0;
54-
const reasons: string[] = [];
55-
56-
const complementary = COMPLEMENTARY_ROLES[currentUser.role_type] || [];
57-
if (complementary.includes(candidate.role_type)) {
58-
score += 30;
59-
reasons.push("Competences complementaires");
60-
}
61-
62-
// ⚡ Bolt: Fast Set lookup instead of array.includes() for nested tech stack matching
63-
const commonTechs = candidate.tech_stack.filter((t) =>
64-
currentUserTechsSet.has(t)
65-
);
66-
if (commonTechs.length > 0) {
67-
score += Math.min(commonTechs.length * 10, 25);
68-
reasons.push(`${commonTechs.length} technologie(s) en commun`);
69-
}
70-
71-
if (
72-
currentUser.city &&
73-
candidate.city &&
74-
currentUser.city.toLowerCase() === candidate.city.toLowerCase()
75-
) {
76-
score += 20;
77-
reasons.push("Meme ville");
78-
}
79-
80-
const levelMap = { junior: 1, mid: 2, senior: 3 };
81-
const diff = Math.abs(
82-
levelMap[currentUser.experience_level] -
83-
levelMap[candidate.experience_level]
84-
);
85-
if (diff <= 1) {
86-
score += 15;
87-
reasons.push("Niveau d'experience compatible");
88-
}
89-
90-
if (candidate.open_to_collaboration) {
91-
score += 10;
92-
}
93-
94-
return { profile: candidate, score: Math.min(score, 100), reasons };
95-
})
96-
.filter((m) => m.score > 0)
97-
.sort((a, b) => b.score - a.score);
49+
const complementary = COMPLEMENTARY_ROLES[currentUser.role_type] || [];
50+
const levelMap = { junior: 1, mid: 2, senior: 3 };
51+
52+
// ⚡ Bolt: Consolidate map and filter into a single reduce pass
53+
const results = candidates.reduce<MatchResult[]>((acc, candidate) => {
54+
if (candidate.id === currentUser.id || !candidate.open_to_collaboration) {
55+
return acc;
56+
}
57+
58+
let score = 0;
59+
const reasons: string[] = [];
60+
61+
if (complementary.includes(candidate.role_type)) {
62+
score += 30;
63+
reasons.push("Competences complementaires");
64+
}
65+
66+
// ⚡ Bolt: Fast Set lookup instead of array.includes() for nested tech stack matching
67+
const commonTechs = candidate.tech_stack.filter((t) =>
68+
currentUserTechsSet.has(t)
69+
);
70+
if (commonTechs.length > 0) {
71+
score += Math.min(commonTechs.length * 10, 25);
72+
reasons.push(`${commonTechs.length} technologie(s) en commun`);
73+
}
74+
75+
if (
76+
currentUser.city &&
77+
candidate.city &&
78+
currentUser.city.toLowerCase() === candidate.city.toLowerCase()
79+
) {
80+
score += 20;
81+
reasons.push("Meme ville");
82+
}
83+
84+
const diff = Math.abs(
85+
levelMap[currentUser.experience_level] -
86+
levelMap[candidate.experience_level]
87+
);
88+
if (diff <= 1) {
89+
score += 15;
90+
reasons.push("Niveau d'experience compatible");
91+
}
92+
93+
if (candidate.open_to_collaboration) {
94+
score += 10;
95+
}
96+
97+
if (score > 0) {
98+
acc.push({ profile: candidate, score: Math.min(score, 100), reasons });
99+
}
100+
101+
return acc;
102+
}, []);
103+
104+
return results.sort((a, b) => b.score - a.score);
98105
}
99106

100107
export function calculateProjectMatches(
@@ -105,58 +112,62 @@ export function calculateProjectMatches(
105112
// it for every single tech in every single project.
106113
const profileTechsLower = profile.tech_stack.map((t) => t.toLowerCase());
107114

108-
return projects
109-
.map((project) => {
110-
let score = 0;
111-
const reasons: string[] = [];
112-
113-
// ⚡ Bolt: Instead of iterating and lowercasing the project's entire tech stack
114-
// for *every* item in the profile's tech stack (O(M*K)), we pre-calculate a Set
115-
// of the lowercased project techs (O(M)) and do O(1) lookups.
116-
const projectTechsSet = new Set(project.tech_stack.map((pt) => pt.toLowerCase()));
117-
118-
const commonTechs = profileTechsLower.filter((tLower) =>
119-
projectTechsSet.has(tLower)
115+
// ⚡ Bolt: Consolidate map and filter into a single reduce pass
116+
const results = projects.reduce<ProjectMatch[]>((acc, project) => {
117+
let score = 0;
118+
const reasons: string[] = [];
119+
120+
// ⚡ Bolt: Instead of iterating and lowercasing the project's entire tech stack
121+
// for *every* item in the profile's tech stack (O(M*K)), we pre-calculate a Set
122+
// of the lowercased project techs (O(M)) and do O(1) lookups.
123+
const projectTechsSet = new Set(project.tech_stack.map((pt) => pt.toLowerCase()));
124+
125+
const commonTechs = profileTechsLower.filter((tLower) =>
126+
projectTechsSet.has(tLower)
127+
);
128+
129+
if (commonTechs.length > 0) {
130+
score += Math.min(commonTechs.length * 15, 40);
131+
reasons.push(`${commonTechs.length} techno(s) en commun`);
132+
}
133+
134+
const roleInStack = project.tech_stack.some((t) => {
135+
const tl = t.toLowerCase();
136+
const r = profile.role_type;
137+
return (
138+
(r === "frontend" && (tl.includes("react") || tl.includes("vue") || tl.includes("angular") || tl.includes("next") || tl.includes("svelte"))) ||
139+
(r === "mobile" && (tl.includes("flutter") || tl.includes("react native") || tl.includes("dart") || tl.includes("mobile"))) ||
140+
(r === "backend" && (tl.includes("node") || tl.includes("python") || tl.includes("django") || tl.includes("php") || tl.includes("java") || tl.includes("go") || tl.includes("rust"))) ||
141+
(r === "fullstack" && (tl.includes("react") || tl.includes("node") || tl.includes("next") || tl.includes("supabase") || tl.includes("typescript"))) ||
142+
(r === "data" && (tl.includes("python") || tl.includes("sql") || tl.includes("spark") || tl.includes("data"))) ||
143+
(r === "devops" && (tl.includes("docker") || tl.includes("kubernetes") || tl.includes("aws") || tl.includes("linux"))) ||
144+
(r === "cybersecurite" && (tl.includes("cyber") || tl.includes("linux") || tl.includes("securite") || tl.includes("reseaux"))) ||
145+
(r === "design" && (tl.includes("figma") || tl.includes("design") || tl.includes("ui")))
120146
);
121-
122-
if (commonTechs.length > 0) {
123-
score += Math.min(commonTechs.length * 15, 40);
124-
reasons.push(`${commonTechs.length} techno(s) en commun`);
125-
}
126-
127-
const roleInStack = project.tech_stack.some((t) => {
128-
const tl = t.toLowerCase();
129-
const r = profile.role_type;
130-
return (
131-
(r === "frontend" && (tl.includes("react") || tl.includes("vue") || tl.includes("angular") || tl.includes("next") || tl.includes("svelte"))) ||
132-
(r === "mobile" && (tl.includes("flutter") || tl.includes("react native") || tl.includes("dart") || tl.includes("mobile"))) ||
133-
(r === "backend" && (tl.includes("node") || tl.includes("python") || tl.includes("django") || tl.includes("php") || tl.includes("java") || tl.includes("go") || tl.includes("rust"))) ||
134-
(r === "fullstack" && (tl.includes("react") || tl.includes("node") || tl.includes("next") || tl.includes("supabase") || tl.includes("typescript"))) ||
135-
(r === "data" && (tl.includes("python") || tl.includes("sql") || tl.includes("spark") || tl.includes("data"))) ||
136-
(r === "devops" && (tl.includes("docker") || tl.includes("kubernetes") || tl.includes("aws") || tl.includes("linux"))) ||
137-
(r === "cybersecurite" && (tl.includes("cyber") || tl.includes("linux") || tl.includes("securite") || tl.includes("reseaux"))) ||
138-
(r === "design" && (tl.includes("figma") || tl.includes("design") || tl.includes("ui")))
139-
);
140-
});
141-
if (roleInStack) {
142-
score += 25;
143-
reasons.push("Votre rôle correspond au projet");
144-
}
145-
146-
if (project.open_to_collaboration && profile.open_to_collaboration) {
147-
score += 20;
148-
reasons.push("Les deux parties disponibles");
149-
}
150-
151-
if (profile.experience_level === "senior") {
152-
score += 10;
153-
reasons.push("Profil senior valorisé");
154-
} else if (profile.experience_level === "mid") {
155-
score += 5;
156-
}
157-
158-
return { project, score: Math.min(score, 100), reasons };
159-
})
160-
.filter((m) => m.score > 0)
161-
.sort((a, b) => b.score - a.score);
147+
});
148+
if (roleInStack) {
149+
score += 25;
150+
reasons.push("Votre rôle correspond au projet");
151+
}
152+
153+
if (project.open_to_collaboration && profile.open_to_collaboration) {
154+
score += 20;
155+
reasons.push("Les deux parties disponibles");
156+
}
157+
158+
if (profile.experience_level === "senior") {
159+
score += 10;
160+
reasons.push("Profil senior valorisé");
161+
} else if (profile.experience_level === "mid") {
162+
score += 5;
163+
}
164+
165+
if (score > 0) {
166+
acc.push({ project, score: Math.min(score, 100), reasons });
167+
}
168+
169+
return acc;
170+
}, []);
171+
172+
return results.sort((a, b) => b.score - a.score);
162173
}

0 commit comments

Comments
 (0)