From 5e7dde6218049d964160d48576130f66f815c749 Mon Sep 17 00:00:00 2001 From: Alec Lichtenberger Date: Thu, 5 Feb 2026 23:57:38 -0800 Subject: [PATCH 1/6] Merge protection commit --- backend/src/controllers/userController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/controllers/userController.ts b/backend/src/controllers/userController.ts index 9450cca..3e382ae 100644 --- a/backend/src/controllers/userController.ts +++ b/backend/src/controllers/userController.ts @@ -367,7 +367,6 @@ export const getAlumniSimilarities = asyncHandler(async (req, res, next) => { } - //Prepare data student and alumni for groq const StudentData = { name: studentUser.name, school: studentUser.school, From 76427ae50a73f1fb94ca878382208e168bcd1f6c Mon Sep 17 00:00:00 2001 From: Alec Lichtenberger Date: Mon, 9 Feb 2026 21:06:35 -0800 Subject: [PATCH 2/6] Added groq request for similarity scores --- backend/src/controllers/SimilarityController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/controllers/SimilarityController.ts b/backend/src/controllers/SimilarityController.ts index 16af77e..e7d9524 100644 --- a/backend/src/controllers/SimilarityController.ts +++ b/backend/src/controllers/SimilarityController.ts @@ -233,3 +233,4 @@ export async function generateSimilarityScore( } } + From fe0e12731491ebe64ef14813b241f4b89a7bb413 Mon Sep 17 00:00:00 2001 From: Alec Lichtenberger Date: Wed, 11 Feb 2026 14:30:25 -0800 Subject: [PATCH 3/6] Finished the backend controllers for now --- .../src/controllers/SimilarityController.ts | 2 - backend/src/controllers/userController.ts | 101 +++++++++++++++++- 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/backend/src/controllers/SimilarityController.ts b/backend/src/controllers/SimilarityController.ts index e7d9524..95c670c 100644 --- a/backend/src/controllers/SimilarityController.ts +++ b/backend/src/controllers/SimilarityController.ts @@ -232,5 +232,3 @@ export async function generateSimilarityScore( ); } } - - diff --git a/backend/src/controllers/userController.ts b/backend/src/controllers/userController.ts index 7564a0a..799b47a 100644 --- a/backend/src/controllers/userController.ts +++ b/backend/src/controllers/userController.ts @@ -4,7 +4,10 @@ import asyncHandler from "express-async-handler"; import createHttpError from "http-errors"; import validationErrorParser from "../util/validationErrorParser"; import Company from "../models/Company"; -import { analyzeSimilarities } from "../controllers/SimilarityController"; +import { + analyzeSimilarities, + generateSimilarityScore, +} from "../controllers/SimilarityController"; interface BaseUserResponse { _id?: string; @@ -402,3 +405,99 @@ export const getAlumniSimilarities = asyncHandler(async (req, res, next) => { summary: similarities.summary, }); }); + +export const getSimilarityScores = asyncHandler(async (req, res, next) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return next(createHttpError(400, validationErrorParser(errors))); + } + + const { studentId } = matchedData(req, { locations: ["params"] }); + const { id: alumniId } = matchedData(req, { locations: ["params"] }); + if (!studentId) { + return next(createHttpError(400, "StudentId is required")); + } + + const [alumniUser, studentUser] = await Promise.all([ + User.findById(alumniId) + .populate({ + path: "company", + model: Company, + }) + .exec(), + User.findById(studentId).exec(), + ]); + + if (!alumniUser) { + return next(createHttpError(404, "Alumni user not found.")); + } + + if (!studentUser) { + return next(createHttpError(404, "Student user not found.")); + } + + if (alumniUser.type !== UserType.Alumni) { + return next(createHttpError(400, "User is not an alumni.")); + } + + if (studentUser.type !== UserType.Student) { + return next(createHttpError(400, "User is not a student.")); + } + + const StudentData = { + name: studentUser.name, + school: studentUser.school, + fieldOfInterest: studentUser.fieldOfInterest, + projects: studentUser.projects, + hobbies: studentUser.hobbies, + skills: studentUser.skills, + companiesOfInterest: studentUser.companiesOfInterest, + major: studentUser.major, + classLevel: studentUser.classLevel, + }; + + const AlumniData = { + name: alumniUser.name, + position: alumniUser.position, + company: alumniUser.company, + organizations: alumniUser.organizations, + specializations: alumniUser.specializations, + hobbies: alumniUser.hobbies, + skills: alumniUser.skills, + }; + + const similarityScore = await generateSimilarityScore( + StudentData, + AlumniData, + ); + const career = similarityScore.careerScore; + const skill = similarityScore.skillScore; + const project = similarityScore.projectScore; + const organization = similarityScore.organizationScore; + const personal = similarityScore.personalScore; + const school = similarityScore.schoolScore; + + const finalScore = + 0.3 * career + + 0.25 * skill + + 0.15 * project + + 0.1 * organization + + 0.1 * personal + + 0.1 * school; + + res.status(200).json({ + student: { + _id: studentUser._id, + name: studentUser.name, + email: studentUser.email, + }, + alumni: { + _id: alumniUser._id, + name: alumniUser.name, + email: alumniUser.email, + position: alumniUser.position, + company: alumniUser.company, + }, + similarityScore: finalScore, + }); +}); From 9a8e83ce87638f312bb1d28b509e6460801fb248 Mon Sep 17 00:00:00 2001 From: Alec Lichtenberger Date: Fri, 27 Feb 2026 13:43:53 -0800 Subject: [PATCH 4/6] Connect page shows the same people every time, next check to make sure that scores are being properly calculated. --- backend/src/controllers/userController.ts | 126 ++++++++++++---------- backend/src/routes/userRoutes.ts | 6 ++ frontend/src/api/users.ts | 22 ++++ frontend/src/pages/Connect.tsx | 108 +++++++++++++++++-- 4 files changed, 196 insertions(+), 66 deletions(-) diff --git a/backend/src/controllers/userController.ts b/backend/src/controllers/userController.ts index 799b47a..5b3030b 100644 --- a/backend/src/controllers/userController.ts +++ b/backend/src/controllers/userController.ts @@ -406,40 +406,31 @@ export const getAlumniSimilarities = asyncHandler(async (req, res, next) => { }); }); -export const getSimilarityScores = asyncHandler(async (req, res, next) => { + + +export const getBatchSimilarityScores = asyncHandler(async (req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) { return next(createHttpError(400, validationErrorParser(errors))); } const { studentId } = matchedData(req, { locations: ["params"] }); - const { id: alumniId } = matchedData(req, { locations: ["params"] }); + const { alumniIds } = matchedData(req, { locations: ["body"] }); + if (!studentId) { return next(createHttpError(400, "StudentId is required")); } - const [alumniUser, studentUser] = await Promise.all([ - User.findById(alumniId) - .populate({ - path: "company", - model: Company, - }) - .exec(), - User.findById(studentId).exec(), - ]); - - if (!alumniUser) { - return next(createHttpError(404, "Alumni user not found.")); + if (!Array.isArray(alumniIds) || alumniIds.length === 0) { + return next(createHttpError(400, "alumniIds array is required and must not be empty")); } + const studentUser = await User.findById(studentId).exec(); + if (!studentUser) { return next(createHttpError(404, "Student user not found.")); } - if (alumniUser.type !== UserType.Alumni) { - return next(createHttpError(400, "User is not an alumni.")); - } - if (studentUser.type !== UserType.Student) { return next(createHttpError(400, "User is not a student.")); } @@ -455,49 +446,68 @@ export const getSimilarityScores = asyncHandler(async (req, res, next) => { major: studentUser.major, classLevel: studentUser.classLevel, }; + const alumniUsers = await User.find({ _id: { $in: alumniIds } }) + .populate({ + path: "company", + model: Company, + }) + .exec(); - const AlumniData = { - name: alumniUser.name, - position: alumniUser.position, - company: alumniUser.company, - organizations: alumniUser.organizations, - specializations: alumniUser.specializations, - hobbies: alumniUser.hobbies, - skills: alumniUser.skills, - }; - - const similarityScore = await generateSimilarityScore( - StudentData, - AlumniData, + const scores = await Promise.all( + alumniUsers.map(async (alumniUser) => { + if (alumniUser.type !== UserType.Alumni) { + return { + alumniId: alumniUser._id, + similarityScore: 0, + }; + } + + const AlumniData = { + name: alumniUser.name, + position: alumniUser.position, + company: alumniUser.company, + organizations: alumniUser.organizations, + specializations: alumniUser.specializations, + hobbies: alumniUser.hobbies, + skills: alumniUser.skills, + }; + + try { + const similarityScore = await generateSimilarityScore( + StudentData, + AlumniData, + ); + const career = similarityScore.careerScore; + const skill = similarityScore.skillScore; + const project = similarityScore.projectScore; + const organization = similarityScore.organizationScore; + const personal = similarityScore.personalScore; + const school = similarityScore.schoolScore; + + const finalScore = + 0.3 * career + + 0.25 * skill + + 0.15 * project + + 0.1 * organization + + 0.1 * personal + + 0.1 * school; + + return { + alumniId: alumniUser._id, + similarityScore: finalScore, + }; + } catch (error) { + console.error(`Error computing similarity score for alumni ${alumniUser._id}:`, error); + return { + alumniId: alumniUser._id, + similarityScore: 0, + }; + } + }) ); - const career = similarityScore.careerScore; - const skill = similarityScore.skillScore; - const project = similarityScore.projectScore; - const organization = similarityScore.organizationScore; - const personal = similarityScore.personalScore; - const school = similarityScore.schoolScore; - - const finalScore = - 0.3 * career + - 0.25 * skill + - 0.15 * project + - 0.1 * organization + - 0.1 * personal + - 0.1 * school; res.status(200).json({ - student: { - _id: studentUser._id, - name: studentUser.name, - email: studentUser.email, - }, - alumni: { - _id: alumniUser._id, - name: alumniUser.name, - email: alumniUser.email, - position: alumniUser.position, - company: alumniUser.company, - }, - similarityScore: finalScore, + studentId: studentUser._id, + scores, }); }); diff --git a/backend/src/routes/userRoutes.ts b/backend/src/routes/userRoutes.ts index 96e61aa..d9ad811 100644 --- a/backend/src/routes/userRoutes.ts +++ b/backend/src/routes/userRoutes.ts @@ -25,6 +25,12 @@ userRouter.get( userController.getAlumniSimilarities, ); +userRouter.post( + "/batch-similarity-scores/:studentId", + userValidator.getSimilaritiesValidator, + userController.getBatchSimilarityScores, +); + userRouter.patch( "/:id", preprocessCompany, diff --git a/frontend/src/api/users.ts b/frontend/src/api/users.ts index cd3aaaf..240a7fc 100644 --- a/frontend/src/api/users.ts +++ b/frontend/src/api/users.ts @@ -172,3 +172,25 @@ export async function getSimilarities( return handleAPIError(error); } } + +/** + * Fetch similarity scores for multiple alumni in a single batch call + * + * @param studentId The ID of the student + * @param alumniIds Array of alumni IDs to compare with + * @returns Object containing an array of similarity scores + */ +export async function getBatchSimilarityScores( + studentId: string, + alumniIds: string[], +): Promise }>> { + try { + const response = await post(`/api/users/batch-similarity-scores/${studentId}`, { + alumniIds, + }); + const json = (await response.json()) as { studentId: string; scores: Array<{ alumniId: string; similarityScore: number }> }; + return { success: true, data: json }; + } catch (error) { + return handleAPIError(error); + } +} diff --git a/frontend/src/pages/Connect.tsx b/frontend/src/pages/Connect.tsx index ee4cd68..49a041a 100644 --- a/frontend/src/pages/Connect.tsx +++ b/frontend/src/pages/Connect.tsx @@ -1,23 +1,61 @@ -import { useState, useCallback } from "react"; +import { useState, useCallback, useEffect } from "react"; import SearchBar from "../components/public/SearchBar"; import DataList from "../components/public/DataList"; import AlumniTile from "../components/connect/AlumniTile"; -import { getAlumni } from "../api/users"; +import { getAlumni, getBatchSimilarityScores } from "../api/users"; import { Alumni } from "../types/User"; import { IndustryType } from "../types/Company"; import { LuUsers, LuSearch } from "react-icons/lu"; +import { useAuth } from "../contexts/useAuth"; import "../styles/Animations.css"; +interface AlumniWithScore extends Alumni { + similarityScore?: number; +} + interface SearchBarData extends Record { query: string; industry: string[]; } const Connect = () => { + const { user } = useAuth(); const [search, setSearch] = useState({ query: "", industry: [], }); + const [similarityScores, setSimilarityScores] = useState>({}); + const [loadingScores, setLoadingScores] = useState(false); + const [hasFetchedScores, setHasFetchedScores] = useState(false); + + const fetchSimilarityScores = useCallback( + async (alumni: Alumni[]) => { + if (!user?.type || user.type !== "STUDENT") return; + if (!user._id) return; + + setLoadingScores(true); //Load the state for the scores allow for skeleton page at a later date + const scores: Record = {}; + + try { + const alumniToScore = alumni.slice(0, 20); + const alumniIds = alumniToScore.map(a => a._id); + + const res = await getBatchSimilarityScores(user._id!, alumniIds); + if (res.success) { + res.data.scores.forEach(score => { + scores[score.alumniId] = score.similarityScore; + }); + } + setSimilarityScores(scores); + setHasFetchedScores(true); + } catch (error) { + console.error("Error fetching similarity scores:", error); + } finally { + setLoadingScores(false); + } + }, + [user] + ); const getPaginatedOpenAlumni = useCallback( async (page: number, perPage: number) => { @@ -28,13 +66,62 @@ const Connect = () => { industry: search.industry, }); - return res.success - ? res.data - : { page: 0, perPage: 0, total: 0, data: [] }; + return res.success ? res.data : { page: 0, perPage: 0, total: 0, data: [] }; }, [search] ); + + useEffect(() => { + const loadFirstPageWithScores = async () => { + if (hasFetchedScores || !user?.type || user.type !== "STUDENT" || !user._id) { + return; + } + + try { + const res = await getAlumni({ + page: 0, + perPage: 20, + query: search.query || undefined, + industry: search.industry, + }); + + if (res.success && res.data.data.length > 0) { + await fetchSimilarityScores(res.data.data); + } + } catch (error) { + console.error("Error loading first page with scores:", error); + } + }; + + loadFirstPageWithScores(); + }, [user, hasFetchedScores, search, fetchSimilarityScores]); + + const fetchAndSortAlumni = useCallback( + async (page: number, perPage: number) => { + const res = await getPaginatedOpenAlumni(page, perPage); + + if (res.data && res.data.length > 0 && similarityScores) { + const sortedAlumni = [...res.data].sort((a, b) => { + const scoreA = similarityScores[a._id] ?? -Infinity; + const scoreB = similarityScores[b._id] ?? -Infinity; + if (scoreB !== scoreA) { + return scoreB - scoreA; + } + return a.name.localeCompare(b.name); + }); + + return { + ...res, + data: sortedAlumni, + }; + } + + return res; + }, + [getPaginatedOpenAlumni, similarityScores] + ); + return (
@@ -95,10 +182,10 @@ const Connect = () => {
- + pageType="connect" key={`${search.query}_${search.industry.join(",")}`} - fetchData={getPaginatedOpenAlumni} + fetchData={fetchAndSortAlumni} useServerPagination listClassName=" relative z-10 @@ -112,7 +199,12 @@ const Connect = () => { " paginatorContent={{ setPerPage: true, goToPage: true }} TileComponent={(props) => ( -
+
+ {props.data.similarityScore !== undefined && ( +
+ {(props.data.similarityScore * 100).toFixed(0)}% match +
+ )}
)} From 4604c10ae116bb689f81a0eca35c80ec49a67dd1 Mon Sep 17 00:00:00 2001 From: Alec Lichtenberger Date: Fri, 27 Feb 2026 14:35:43 -0800 Subject: [PATCH 5/6] Similarity Scores are somewhat functional --- .../src/controllers/SimilarityController.ts | 28 ++++++------ backend/src/controllers/userController.ts | 23 ++++------ backend/src/routes/userRoutes.ts | 2 +- backend/src/validators/userValidator.ts | 21 +++++++++ .../src/components/connect/AlumniTile.tsx | 45 +++++++++++-------- frontend/src/pages/Connect.tsx | 16 +++---- 6 files changed, 79 insertions(+), 56 deletions(-) diff --git a/backend/src/controllers/SimilarityController.ts b/backend/src/controllers/SimilarityController.ts index 95c670c..3c22e40 100644 --- a/backend/src/controllers/SimilarityController.ts +++ b/backend/src/controllers/SimilarityController.ts @@ -174,21 +174,21 @@ export async function generateSimilarityScore( Compute a similarity score between a student and an alumni using the following weighted components: - - Career alignment: semantic similarity between student major, interests, and alumni role/specializations - - Skills overlap: Jaccard similarity of skill sets - - Project relevance: semantic similarity between student projects and alumni expertise - - Organization alignment: company match and organizational overlap - - Personal fit: hobby overlap - - School affinity: same school bonus - - Respond in the following JSON format (no markdown, pure JSON): + - Career alignment: semantic similarity between student major, interests, and alumni role/specializations (0-100) + - Skills overlap: Jaccard similarity of skill sets (0-100) + - Project relevance: semantic similarity between student projects and alumni expertise (0-100) + - Organization alignment: company match and organizational overlap (0-100) + - Personal fit: hobby overlap (0-100) + - School affinity: same school bonus (0-100) + + Respond in the following JSON format (no markdown, pure JSON) with numeric values between 0 and 100: { - "careerScore" : "Similarity Score for career", - "skillScore" : "Similarity Score for skills", - "projectScore" : "Similarity Score for projects", - "organizationScore" : "Similarity Score for organization", - "personalScore" : "Similarity Score for personal", - "schoolScore" : "Similarity Score for school" + "careerScore": 80, + "skillScore": 70, + "projectScore": 60, + "organizationScore": 50, + "personalScore": 40, + "schoolScore": 90 }`; try { diff --git a/backend/src/controllers/userController.ts b/backend/src/controllers/userController.ts index 5b3030b..6fa2c31 100644 --- a/backend/src/controllers/userController.ts +++ b/backend/src/controllers/userController.ts @@ -477,20 +477,15 @@ export const getBatchSimilarityScores = asyncHandler(async (req, res, next) => { StudentData, AlumniData, ); - const career = similarityScore.careerScore; - const skill = similarityScore.skillScore; - const project = similarityScore.projectScore; - const organization = similarityScore.organizationScore; - const personal = similarityScore.personalScore; - const school = similarityScore.schoolScore; - - const finalScore = - 0.3 * career + - 0.25 * skill + - 0.15 * project + - 0.1 * organization + - 0.1 * personal + - 0.1 * school; + + const career = similarityScore.careerScore / 100; + const skill = similarityScore.skillScore / 100; + const project = similarityScore.projectScore / 100; + const organization = similarityScore.organizationScore / 100; + const personal = similarityScore.personalScore / 100; + const school = similarityScore.schoolScore / 100; + + const finalScore = (career + skill + project + organization + personal + school) / 6; return { alumniId: alumniUser._id, diff --git a/backend/src/routes/userRoutes.ts b/backend/src/routes/userRoutes.ts index d9ad811..2fe4294 100644 --- a/backend/src/routes/userRoutes.ts +++ b/backend/src/routes/userRoutes.ts @@ -27,7 +27,7 @@ userRouter.get( userRouter.post( "/batch-similarity-scores/:studentId", - userValidator.getSimilaritiesValidator, + userValidator.getBatchSimilarityScoresValidator, userController.getBatchSimilarityScores, ); diff --git a/backend/src/validators/userValidator.ts b/backend/src/validators/userValidator.ts index c1033c4..6e7c207 100644 --- a/backend/src/validators/userValidator.ts +++ b/backend/src/validators/userValidator.ts @@ -312,3 +312,24 @@ export const getSimilaritiesValidator = [ .isLength({ min: 1 }) .withMessage("alumni id is required."), ]; + +export const getBatchSimilarityScoresValidator = [ + param("studentId") + .isString() + .withMessage("studentId must be a string.") + .trim() + .isLength({ min: 1 }) + .withMessage("studentId is required."), + body("alumniIds") + .isArray({ min: 1 }) + .withMessage("alumniIds must be a non-empty array.") + .custom((value) => { + if (!Array.isArray(value) || value.length === 0) { + throw new Error("alumniIds must be a non-empty array."); + } + if (!value.every((id) => typeof id === "string")) { + throw new Error("All alumni IDs must be strings."); + } + return true; + }), +]; diff --git a/frontend/src/components/connect/AlumniTile.tsx b/frontend/src/components/connect/AlumniTile.tsx index 3c76cd9..2bdc1e2 100644 --- a/frontend/src/components/connect/AlumniTile.tsx +++ b/frontend/src/components/connect/AlumniTile.tsx @@ -5,7 +5,7 @@ import { LuMail, LuBuilding2, LuBriefcase } from "react-icons/lu"; import AlumniProfileModal from "./AlumniProfileModal"; interface AlumniTileProps { - data: Alumni; + data: Alumni & { similarityScore?: number }; } const AlumniTile: React.FC = ({ data }) => { @@ -38,25 +38,32 @@ const AlumniTile: React.FC = ({ data }) => { " > {/* Card header */} -
-
- {data.profilePicture ? ( - {data.name} - ) : ( - - {data.name.charAt(0)} - - )} -
-
-

- {data.name} -

+
+
+
+ {data.profilePicture ? ( + {data.name} + ) : ( + + {data.name.charAt(0)} + + )} +
+
+

+ {data.name} +

+
+ {data.similarityScore !== undefined && ( +
+ {(data.similarityScore * 100).toFixed(0)} +
+ )}
{/* Card body */} diff --git a/frontend/src/pages/Connect.tsx b/frontend/src/pages/Connect.tsx index 49a041a..eff36e9 100644 --- a/frontend/src/pages/Connect.tsx +++ b/frontend/src/pages/Connect.tsx @@ -32,7 +32,7 @@ const Connect = () => { async (alumni: Alumni[]) => { if (!user?.type || user.type !== "STUDENT") return; if (!user._id) return; - + loadingScores || alumni.length === 0 || hasFetchedScores; setLoadingScores(true); //Load the state for the scores allow for skeleton page at a later date const scores: Record = {}; @@ -111,9 +111,14 @@ const Connect = () => { return a.name.localeCompare(b.name); }); + const sortedAlumniWithScores = sortedAlumni.map(alumnus => ({ + ...alumnus, + similarityScore: similarityScores[alumnus._id] ?? 0, + })); + return { ...res, - data: sortedAlumni, + data: sortedAlumniWithScores, }; } @@ -199,12 +204,7 @@ const Connect = () => { " paginatorContent={{ setPerPage: true, goToPage: true }} TileComponent={(props) => ( -
- {props.data.similarityScore !== undefined && ( -
- {(props.data.similarityScore * 100).toFixed(0)}% match -
- )} +
)} From abc6d6727aea70c53926d7fbfe7d15b4a83856d4 Mon Sep 17 00:00:00 2001 From: Alec Lichtenberger Date: Wed, 4 Mar 2026 16:30:35 -0800 Subject: [PATCH 6/6] Final Commit --- .../src/controllers/SimilarityController.ts | 39 +++++++++---------- backend/src/controllers/userController.ts | 19 +++++---- .../src/components/connect/AlumniTile.tsx | 8 +--- frontend/src/pages/Connect.tsx | 11 ++++-- 4 files changed, 39 insertions(+), 38 deletions(-) diff --git a/backend/src/controllers/SimilarityController.ts b/backend/src/controllers/SimilarityController.ts index 3c22e40..be0e33b 100644 --- a/backend/src/controllers/SimilarityController.ts +++ b/backend/src/controllers/SimilarityController.ts @@ -153,10 +153,9 @@ export async function generateSimilarityScore( You are an expert career mentor analyzing similarities between a student and an alumni. STUDENT PROFILE: - - Name: ${student.name} - - School: ${student.school || "Not provided"} + + - Major: ${student.major || "Not provided"} - - Class Level: ${student.classLevel || "Not provided"} - Field of Interest: ${student.fieldOfInterest?.join(", ") || "Not provided"} - Skills: ${student.skills?.join(", ") || "Not provided"} - Hobbies: ${student.hobbies?.join(", ") || "Not provided"} @@ -164,7 +163,7 @@ export async function generateSimilarityScore( - Companies of Interest: ${student.companiesOfInterest?.join(", ") || "Not provided"} ALUMNI PROFILE: - - Name: ${alumni.name} + - Position: ${alumni.position || "Not provided"} - Company: ${alumni.company || "Not provided"} - Specializations: ${alumni.specializations?.join(", ") || "Not provided"} @@ -174,27 +173,27 @@ export async function generateSimilarityScore( Compute a similarity score between a student and an alumni using the following weighted components: - - Career alignment: semantic similarity between student major, interests, and alumni role/specializations (0-100) - - Skills overlap: Jaccard similarity of skill sets (0-100) - - Project relevance: semantic similarity between student projects and alumni expertise (0-100) - - Organization alignment: company match and organizational overlap (0-100) - - Personal fit: hobby overlap (0-100) - - School affinity: same school bonus (0-100) + - Career alignment: (0-100) + - Skills overlap: (0-100) + - Project relevance: (0-100) + - Organization alignment: (0-100) + - Personal fit: (0-100) + - School affinity: (0-100) - Respond in the following JSON format (no markdown, pure JSON) with numeric values between 0 and 100: - { - "careerScore": 80, - "skillScore": 70, - "projectScore": 60, - "organizationScore": 50, - "personalScore": 40, - "schoolScore": 90 - }`; + Return ONLY this JSON format (no functions, no code): + { + "careerScore": , + "skillScore": , + "projectScore": , + "organizationScore": , + "personalScore": , + + }`; try { const message = await groq.chat.completions.create({ model: "llama-3.1-8b-instant", - max_tokens: 1024, + max_tokens: 512, response_format: { type: "json_object" }, messages: [ { diff --git a/backend/src/controllers/userController.ts b/backend/src/controllers/userController.ts index 6fa2c31..370aa64 100644 --- a/backend/src/controllers/userController.ts +++ b/backend/src/controllers/userController.ts @@ -406,8 +406,6 @@ export const getAlumniSimilarities = asyncHandler(async (req, res, next) => { }); }); - - export const getBatchSimilarityScores = asyncHandler(async (req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) { @@ -422,7 +420,9 @@ export const getBatchSimilarityScores = asyncHandler(async (req, res, next) => { } if (!Array.isArray(alumniIds) || alumniIds.length === 0) { - return next(createHttpError(400, "alumniIds array is required and must not be empty")); + return next( + createHttpError(400, "alumniIds array is required and must not be empty"), + ); } const studentUser = await User.findById(studentId).exec(); @@ -477,28 +477,31 @@ export const getBatchSimilarityScores = asyncHandler(async (req, res, next) => { StudentData, AlumniData, ); - + const career = similarityScore.careerScore / 100; const skill = similarityScore.skillScore / 100; const project = similarityScore.projectScore / 100; const organization = similarityScore.organizationScore / 100; const personal = similarityScore.personalScore / 100; - const school = similarityScore.schoolScore / 100; - const finalScore = (career + skill + project + organization + personal + school) / 6; + const finalScore = + (career + skill + project + organization + personal) / 6; return { alumniId: alumniUser._id, similarityScore: finalScore, }; } catch (error) { - console.error(`Error computing similarity score for alumni ${alumniUser._id}:`, error); + console.error( + `Error computing similarity score for alumni ${alumniUser._id}:`, + error, + ); return { alumniId: alumniUser._id, similarityScore: 0, }; } - }) + }), ); res.status(200).json({ diff --git a/frontend/src/components/connect/AlumniTile.tsx b/frontend/src/components/connect/AlumniTile.tsx index 2bdc1e2..a114964 100644 --- a/frontend/src/components/connect/AlumniTile.tsx +++ b/frontend/src/components/connect/AlumniTile.tsx @@ -5,7 +5,7 @@ import { LuMail, LuBuilding2, LuBriefcase } from "react-icons/lu"; import AlumniProfileModal from "./AlumniProfileModal"; interface AlumniTileProps { - data: Alumni & { similarityScore?: number }; + data: Alumni; } const AlumniTile: React.FC = ({ data }) => { @@ -59,11 +59,7 @@ const AlumniTile: React.FC = ({ data }) => {
- {data.similarityScore !== undefined && ( -
- {(data.similarityScore * 100).toFixed(0)} -
- )} +
{/* Card body */} diff --git a/frontend/src/pages/Connect.tsx b/frontend/src/pages/Connect.tsx index eff36e9..6542ce1 100644 --- a/frontend/src/pages/Connect.tsx +++ b/frontend/src/pages/Connect.tsx @@ -32,12 +32,12 @@ const Connect = () => { async (alumni: Alumni[]) => { if (!user?.type || user.type !== "STUDENT") return; if (!user._id) return; - loadingScores || alumni.length === 0 || hasFetchedScores; + setLoadingScores(true); //Load the state for the scores allow for skeleton page at a later date const scores: Record = {}; try { - const alumniToScore = alumni.slice(0, 20); + const alumniToScore = alumni.slice(0, 10); const alumniIds = alumniToScore.map(a => a._id); const res = await getBatchSimilarityScores(user._id!, alumniIds); @@ -78,10 +78,13 @@ const Connect = () => { return; } + setHasFetchedScores(false); + setLoadingScores(true); + try { const res = await getAlumni({ page: 0, - perPage: 20, + perPage: 10, query: search.query || undefined, industry: search.industry, }); @@ -95,7 +98,7 @@ const Connect = () => { }; loadFirstPageWithScores(); - }, [user, hasFetchedScores, search, fetchSimilarityScores]); + }, [user?._id, search.query, search.industry.join(",")]); const fetchAndSortAlumni = useCallback( async (page: number, perPage: number) => {