Skip to content

Commit 33b9b12

Browse files
File changes
1 parent d40670d commit 33b9b12

17 files changed

+1527
-91
lines changed

functions/aiMentorMatcher.ts

Lines changed: 65 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,60 +8,48 @@ Deno.serve(async (req) => {
88
if (!user) {
99
return Response.json({ error: 'Unauthorized' }, { status: 401 });
1010
}
11-
12-
const { user_email, skills_to_learn } = await req.json();
13-
const targetEmail = user_email || user.email;
14-
15-
// Get user's profile
16-
const userProfile = await base44.entities.UserProfile.filter({
17-
user_email: targetEmail
18-
});
19-
20-
if (!userProfile[0]) {
21-
return Response.json({ error: 'User profile not found' }, { status: 404 });
11+
12+
const { menteeEmail } = await req.json();
13+
14+
// Fetch mentee profile
15+
const [menteeProfile] = await base44.entities.UserProfile.filter({ user_email: menteeEmail });
16+
17+
if (!menteeProfile) {
18+
return Response.json({ error: 'Mentee profile not found' }, { status: 404 });
2219
}
23-
24-
// Get all potential mentors (users with expertise)
25-
const allProfiles = await base44.asServiceRole.entities.UserProfile.filter({
26-
user_email: { $ne: targetEmail }
27-
});
28-
20+
21+
// Fetch all potential mentors (experienced users)
22+
const allProfiles = await base44.asServiceRole.entities.UserProfile.filter({});
2923
const potentialMentors = allProfiles.filter(p =>
30-
p.expertise_areas && p.expertise_areas.length > 0
24+
p.user_email !== menteeEmail &&
25+
(p.skills?.length || 0) >= 5 &&
26+
new Date(p.start_date) < new Date(menteeProfile.start_date)
3127
);
28+
29+
// Use AI to analyze best matches
30+
const analysisPrompt = `Analyze mentor-mentee compatibility for employee onboarding:
3231
33-
// Use AI to match mentors
34-
const matches = await base44.integrations.Core.InvokeLLM({
35-
prompt: `Match this user with the best internal mentors:
36-
37-
USER SEEKING MENTORSHIP:
38-
Email: ${targetEmail}
39-
Department: ${userProfile[0].department || 'N/A'}
40-
Job Title: ${userProfile[0].job_title || 'N/A'}
41-
Current Skills: ${userProfile[0].skill_levels?.map(s => `${s.skill} (${s.level})`).join(', ') || 'None'}
42-
Skills to Learn: ${skills_to_learn?.join(', ') || userProfile[0].skill_interests?.join(', ') || 'General growth'}
43-
Learning Goals: ${userProfile[0].learning_goals?.join(', ') || 'N/A'}
32+
Mentee Profile:
33+
- Email: ${menteeEmail}
34+
- Role: ${menteeProfile.role || 'Not specified'}
35+
- Department: ${menteeProfile.department || 'Not specified'}
36+
- Skills: ${menteeProfile.skills?.map(s => s.skill_name).join(', ') || 'None listed'}
37+
- Interests: ${menteeProfile.interests?.join(', ') || 'None listed'}
38+
- Career aspirations: ${menteeProfile.career_aspirations || 'Not specified'}
4439
45-
POTENTIAL MENTORS (${potentialMentors.length} available):
46-
${potentialMentors.slice(0, 20).map(p => `
47-
- ${p.user_email}
48-
Department: ${p.department || 'N/A'}
49-
Job Title: ${p.job_title || 'N/A'}
50-
Expertise: ${p.expertise_areas?.join(', ') || 'None'}
51-
Skills: ${p.skill_levels?.map(s => `${s.skill} (${s.level})`).join(', ') || 'None'}
40+
Potential Mentors (${potentialMentors.length}):
41+
${potentialMentors.slice(0, 10).map((p, i) => `
42+
${i + 1}. ${p.user_email}
43+
Role: ${p.role || 'Unknown'}
44+
Department: ${p.department || 'Unknown'}
45+
Skills: ${p.skills?.map(s => s.skill_name).join(', ') || 'None'}
46+
Experience: ${p.career_history?.length || 0} positions
5247
`).join('\n')}
5348
54-
Find the top 5 best mentor matches considering:
55-
1. Expertise alignment with learning goals
56-
2. Department/role relevance
57-
3. Skill level compatibility (mentor should be advanced in areas user wants to learn)
58-
59-
For each match provide:
60-
- mentor_email
61-
- match_score (0-100)
62-
- matching_skills (array of overlapping skills)
63-
- mentorship_areas (what they can teach)
64-
- reasoning (why this is a good match)`,
49+
Return the top 3 mentor matches with scores and reasoning.`;
50+
51+
const matches = await base44.integrations.Core.InvokeLLM({
52+
prompt: analysisPrompt,
6553
response_json_schema: {
6654
type: "object",
6755
properties: {
@@ -72,24 +60,47 @@ For each match provide:
7260
properties: {
7361
mentor_email: { type: "string" },
7462
match_score: { type: "number" },
75-
matching_skills: { type: "array", items: { type: "string" } },
76-
mentorship_areas: { type: "array", items: { type: "string" } },
77-
reasoning: { type: "string" }
63+
skill_overlap_score: { type: "number" },
64+
department_alignment: { type: "boolean" },
65+
reasoning: { type: "string" },
66+
suggested_goals: {
67+
type: "array",
68+
items: { type: "string" }
69+
}
7870
}
7971
}
8072
}
8173
}
8274
}
8375
});
84-
76+
77+
// Save top matches to database
78+
for (const match of matches.matches.slice(0, 3)) {
79+
const existing = await base44.entities.MentorMatch.filter({
80+
mentor_email: match.mentor_email,
81+
mentee_email: menteeEmail
82+
});
83+
84+
if (existing.length === 0) {
85+
await base44.asServiceRole.entities.MentorMatch.create({
86+
mentor_email: match.mentor_email,
87+
mentee_email: menteeEmail,
88+
match_score: match.match_score,
89+
matching_criteria: {
90+
skill_overlap: match.skill_overlap_score,
91+
department_alignment: match.department_alignment
92+
},
93+
status: 'suggested',
94+
goals: match.suggested_goals?.map(g => ({ goal: g, progress: 0 })) || []
95+
});
96+
}
97+
}
98+
8599
return Response.json({
86100
success: true,
87-
user_email: targetEmail,
88101
matches: matches.matches
89102
});
90-
91103
} catch (error) {
92-
console.error('Mentor matching error:', error);
93104
return Response.json({ error: error.message }, { status: 500 });
94105
}
95106
});
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { createClientFromRequest } from 'npm:@base44/[email protected]';
2+
3+
Deno.serve(async (req) => {
4+
try {
5+
const base44 = createClientFromRequest(req);
6+
const user = await base44.auth.me();
7+
8+
if (!user) {
9+
return Response.json({ error: 'Unauthorized' }, { status: 401 });
10+
}
11+
12+
const { userEmail } = await req.json();
13+
14+
// Fetch user activity data
15+
const [profile] = await base44.entities.UserProfile.filter({ user_email: userEmail });
16+
const [userPoints] = await base44.entities.UserPoints.filter({ user_email: userEmail });
17+
const participations = await base44.entities.Participation.filter({ user_email: userEmail });
18+
const recognitions = await base44.entities.Recognition.filter({ sender_email: userEmail });
19+
const teamMemberships = await base44.entities.TeamMembership.filter({ user_email: userEmail });
20+
21+
// Calculate time since registration
22+
const accountAge = profile?.created_date ?
23+
Math.floor((Date.now() - new Date(profile.created_date).getTime()) / (1000 * 60 * 60 * 24)) : 0;
24+
25+
// Build activity summary
26+
const activitySummary = {
27+
accountAgeDays: accountAge,
28+
profileComplete: !!(profile?.role && profile?.department && profile?.bio),
29+
hasProfilePicture: !!profile?.profile_picture_url,
30+
skillsCount: profile?.skills?.length || 0,
31+
eventsAttended: participations.filter(p => p.status === 'attended').length,
32+
eventsRegistered: participations.length,
33+
recognitionsSent: recognitions.length,
34+
teamsJoined: teamMemberships.length,
35+
totalPoints: userPoints?.total_points || 0,
36+
currentLevel: userPoints?.current_level || 1
37+
};
38+
39+
// AI generates proactive tips
40+
const tipsPrompt = `Generate 3 personalized onboarding tips for a new employee:
41+
42+
User Activity:
43+
- Account age: ${activitySummary.accountAgeDays} days
44+
- Profile complete: ${activitySummary.profileComplete ? 'Yes' : 'No'}
45+
- Profile picture: ${activitySummary.hasProfilePicture ? 'Yes' : 'No'}
46+
- Skills listed: ${activitySummary.skillsCount}
47+
- Events registered: ${activitySummary.eventsRegistered}
48+
- Events attended: ${activitySummary.eventsAttended}
49+
- Recognitions sent: ${activitySummary.recognitionsSent}
50+
- Teams joined: ${activitySummary.teamsJoined}
51+
- Total points: ${activitySummary.totalPoints}
52+
- Level: ${activitySummary.currentLevel}
53+
54+
Based on their activity, suggest:
55+
1. Next best action to take
56+
2. Missing opportunity they should explore
57+
3. Motivational tip or milestone celebration
58+
59+
Make tips friendly, specific, and actionable.`;
60+
61+
const tips = await base44.integrations.Core.InvokeLLM({
62+
prompt: tipsPrompt,
63+
response_json_schema: {
64+
type: "object",
65+
properties: {
66+
tips: {
67+
type: "array",
68+
items: {
69+
type: "object",
70+
properties: {
71+
title: { type: "string" },
72+
message: { type: "string" },
73+
action: { type: "string" },
74+
action_url: { type: "string" },
75+
icon: { type: "string" }
76+
}
77+
}
78+
},
79+
priority_action: { type: "string" },
80+
motivation_message: { type: "string" }
81+
}
82+
}
83+
});
84+
85+
return Response.json({
86+
success: true,
87+
activitySummary,
88+
tips: tips.tips,
89+
priorityAction: tips.priority_action,
90+
motivationMessage: tips.motivation_message
91+
});
92+
} catch (error) {
93+
return Response.json({ error: error.message }, { status: 500 });
94+
}
95+
});
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { createClientFromRequest } from 'npm:@base44/[email protected]';
2+
3+
Deno.serve(async (req) => {
4+
try {
5+
const base44 = createClientFromRequest(req);
6+
const user = await base44.auth.me();
7+
8+
if (!user || user.role !== 'admin') {
9+
return Response.json({ error: 'Unauthorized' }, { status: 403 });
10+
}
11+
12+
const { lookbackDays = 30 } = await req.json();
13+
14+
// Fetch wellness and engagement data
15+
const wellnessLogs = await base44.asServiceRole.entities.WellnessLog.filter({});
16+
const participations = await base44.asServiceRole.entities.Participation.filter({});
17+
const recognitions = await base44.asServiceRole.entities.Recognition.filter({});
18+
const userPoints = await base44.asServiceRole.entities.UserPoints.filter({});
19+
20+
// Group by user
21+
const userWellnessActivity = wellnessLogs.reduce((acc, log) => {
22+
if (!acc[log.user_email]) {
23+
acc[log.user_email] = { logs: 0, totalValue: 0 };
24+
}
25+
acc[log.user_email].logs += 1;
26+
acc[log.user_email].totalValue += log.value;
27+
return acc;
28+
}, {});
29+
30+
const userEngagement = participations.reduce((acc, p) => {
31+
if (!acc[p.user_email]) {
32+
acc[p.user_email] = { events: 0 };
33+
}
34+
acc[p.user_email].events += 1;
35+
return acc;
36+
}, {});
37+
38+
recognitions.forEach(r => {
39+
if (!userEngagement[r.sender_email]) {
40+
userEngagement[r.sender_email] = { events: 0, recognitions: 0 };
41+
}
42+
userEngagement[r.sender_email].recognitions = (userEngagement[r.sender_email].recognitions || 0) + 1;
43+
});
44+
45+
// Build correlation dataset
46+
const correlationData = Object.keys(userWellnessActivity).map(email => ({
47+
email,
48+
wellnessLogs: userWellnessActivity[email].logs,
49+
wellnessTotal: userWellnessActivity[email].totalValue,
50+
eventsAttended: userEngagement[email]?.events || 0,
51+
recognitionsSent: userEngagement[email]?.recognitions || 0,
52+
totalPoints: userPoints.find(p => p.user_email === email)?.total_points || 0
53+
}));
54+
55+
// AI analysis
56+
const analysisPrompt = `Analyze correlation between wellness activity and employee engagement:
57+
58+
Dataset (${correlationData.length} employees):
59+
${correlationData.slice(0, 10).map(d => `
60+
- Email: ${d.email}
61+
Wellness logs: ${d.wellnessLogs}
62+
Wellness total: ${d.wellnessTotal}
63+
Events attended: ${d.eventsAttended}
64+
Recognitions sent: ${d.recognitionsSent}
65+
Total points: ${d.totalPoints}
66+
`).join('\n')}
67+
68+
Analyze:
69+
1. Correlation strength between wellness activity and engagement
70+
2. Key patterns and insights
71+
3. Recommendations for HR to improve both wellness and engagement
72+
4. Suggested wellness challenge goals based on engagement data`;
73+
74+
const analysis = await base44.integrations.Core.InvokeLLM({
75+
prompt: analysisPrompt,
76+
response_json_schema: {
77+
type: "object",
78+
properties: {
79+
correlation_strength: {
80+
type: "string",
81+
enum: ["strong_positive", "moderate_positive", "weak", "no_correlation"]
82+
},
83+
correlation_score: { type: "number" },
84+
key_insights: {
85+
type: "array",
86+
items: { type: "string" }
87+
},
88+
patterns: {
89+
type: "array",
90+
items: {
91+
type: "object",
92+
properties: {
93+
pattern: { type: "string" },
94+
impact: { type: "string" }
95+
}
96+
}
97+
},
98+
recommendations: {
99+
type: "array",
100+
items: {
101+
type: "object",
102+
properties: {
103+
action: { type: "string" },
104+
expected_outcome: { type: "string" },
105+
priority: { type: "string" }
106+
}
107+
}
108+
},
109+
suggested_challenge_goals: {
110+
type: "object",
111+
properties: {
112+
steps_daily: { type: "number" },
113+
meditation_minutes: { type: "number" },
114+
hydration_glasses: { type: "number" }
115+
}
116+
}
117+
}
118+
}
119+
});
120+
121+
return Response.json({
122+
success: true,
123+
analysis,
124+
sampleSize: correlationData.length
125+
});
126+
} catch (error) {
127+
return Response.json({ error: error.message }, { status: 500 });
128+
}
129+
});

0 commit comments

Comments
 (0)