Skip to content

Commit

Permalink
derive student achievements
Browse files Browse the repository at this point in the history
  • Loading branch information
dhenkel92 committed Aug 16, 2024
1 parent ec94dcc commit 8f1554a
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 8 deletions.
148 changes: 141 additions & 7 deletions common/achievement/derive.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import {
pupil as Pupil,
student as Student,
achievement_action_type_enum as AchievementActionType,
achievement_template_for_enum as AchievementTemplateFor,
achievement_type_enum as AchievementType,
achievement_template,
pupil_screening_status_enum,
Prisma,
} from '@prisma/client';
import { User, getPupil } from '../user';
import { User, getPupil, getStudent } from '../user';
import { prisma } from '../prisma';
import { achievement_with_template } from './types';
import { getAchievementTemplates, TemplateSelectEnum } from './template';
import { createRelation, EventRelationType } from './relation';

const PupilNewMatchGroup = 'pupil_new_match';
const PupilNewMatchGroupOrder = 3;
const StudentNewMatchGroup = 'student_new_match';
const StudentNewMatchGroupOrder = 3;

const GhostAchievements: { [key: string]: achievement_template } = {
pupil_new_match_1: {
Expand Down Expand Up @@ -65,6 +68,54 @@ const GhostAchievements: { [key: string]: achievement_template } = {
achievedImage: null,
sequentialStepName: 'Gespräch mit Lern-Fair absolvieren',
},
student_new_match_1: {
id: -1,
templateFor: AchievementTemplateFor.Match,
group: StudentNewMatchGroup,
groupOrder: 1,
type: AchievementType.SEQUENTIAL,
image: 'gamification/achievements/release/finish_onboarding/two_pieces/step_1.png',
tagline: 'Starte eine Lernpatenschaft',
title: 'Neue Lernunterstützung',
subtitle: null,
description:
'Es war großartig, dich am {{date}} besser kennenzulernen und freuen uns, dass du gemeinsam mit uns die Bildungschancen von Schüler:innen verbessern möchtest. Um dir eine:n passende:n Lernpartner:in zuzuweisen, bitten wir dich zunächst, eine Anfrage auf unserer Plattform zu stellen. Hier kannst du die Fächer und Jahrgangsstufe angeben, die für dich passend sind. Wir freuen uns auf den Start!',
footer: null,
actionName: 'Anfrage stellen',
actionRedirectLink: '/matching',
actionType: AchievementActionType.Action,
condition: 'false', // This will ensure that an evaluation will always fail
conditionDataAggregations: {},
isActive: true,
achievedDescription: null,
achievedFooter: null,
achievedImage: null,
sequentialStepName: 'Anfrage stellen',
},
student_new_match_2: {
id: -1,
templateFor: AchievementTemplateFor.Match,
group: StudentNewMatchGroup,
groupOrder: 2,
type: AchievementType.SEQUENTIAL,
image: 'gamification/achievements/release/finish_onboarding/two_pieces/step_2.png',
tagline: 'Starte eine Lernpatenschaft',
title: 'Neue Lernunterstützung',
subtitle: null,
description:
'Fantastisch, deine Anfrage ist eingegangen! Bevor wir dir deine:n ideale:n Lernpartner:in vermitteln können, möchten wir gerne kurz per Zoom mit dir sprechen. Unser Ziel ist es, die perfekte Person für dich zu finden und genau zu verstehen, was du dir wünschst. Buche doch gleich einen Termin für unser Gespräch – wir sind schon ganz gespannt auf dich!',
footer: null,
actionName: 'Termin buchen',
actionRedirectLink: 'https://calendly.com',
actionType: AchievementActionType.Action,
condition: 'false',
conditionDataAggregations: {},
isActive: true,
achievedDescription: null,
achievedFooter: null,
achievedImage: null,
sequentialStepName: 'Gespräch mit Lern-Fair absolvieren',
},
};

// Large parts of our user communication are event based, i.e. users get a notification for an appointment,
Expand All @@ -80,16 +131,12 @@ export async function deriveAchievements(user: User, realAchievements: achieveme

if (user.pupilId) {
const pupil = await getPupil(user);

// await derivePupilOnboarding(pupil, result);
await derivePupilMatching(user, pupil, result, realAchievements);
}

if (user.studentId) {
// const student = await getStudent(user);
// await deriveStudentOnboarding(student, result);
// await deriveStudentMatching(student, result);
// ...
const student = await getStudent(user);
await deriveStudentMatching(user, student, result, realAchievements);
}

return result;
Expand Down Expand Up @@ -185,3 +232,90 @@ async function derivePupilMatching(user: User, pupil: Pupil, result: achievement
result.push(...ghosts);
}
}

interface StudentNewMatchGhostContext extends Prisma.JsonObject {
lastScreeningDate: string | null;
}

async function deriveStudentMatching(user: User, student: Student, result: achievement_with_template[], userAchievements: achievement_with_template[]) {
const hasRequest = student.openMatchRequestCount > 0;
const successfulScreenings = await prisma.screening.findMany({
where: { studentId: student.id, success: true },
orderBy: { createdAt: 'desc' },
});

const newMatchAchievements = userAchievements.filter(
(row) => row.template.group === StudentNewMatchGroup && row.template.groupOrder === StudentNewMatchGroupOrder
);

const ctx: StudentNewMatchGhostContext = {
lastScreeningDate: null,
};
if (successfulScreenings.length > 0) {
ctx.lastScreeningDate = successfulScreenings[0].updatedAt.toISOString();
}
for (let i = 0; i < student.openMatchRequestCount; i++) {
const ghosts = await generateStudentMatching(null, user, hasRequest, successfulScreenings.length > 0, ctx);
result.push(...ghosts);
}

for (const userAchievement of newMatchAchievements) {
const ghosts = await generateStudentMatching(userAchievement, user, hasRequest, successfulScreenings.length > 0, ctx);
result.push(...ghosts);
}
}

async function generateStudentMatching(
achievement: achievement_with_template | null,
user: User,
hasRequest: boolean,
hasSuccessfulScreening: boolean,
ctx: StudentNewMatchGhostContext
): Promise<achievement_with_template[]> {
const result: achievement_with_template[] = [];
// Generating a ramdom relation to be able to show multiple sequences of this kind in parallel
const randomRelation = createRelation(EventRelationType.Match, Math.random()) + '-tmp';
if (!achievement) {
const groups = await getAchievementTemplates(TemplateSelectEnum.BY_GROUP);
if (!groups.has(StudentNewMatchGroup) || groups.get(StudentNewMatchGroup).length === 0) {
throw new Error('group template not found!');
}
// If there is no real achievement yet, we have to fake the first one in the row as well
result.push({
id: -1,
templateId: -1,
userId: user.userID,
isSeen: true,
template: groups.get(StudentNewMatchGroup)[0],
context: ctx,
recordValue: null,
achievedAt: null,
relation: randomRelation,
});
}

result.push({
id: -1,
templateId: -1,
userId: user.userID,
isSeen: true,
template: GhostAchievements.student_new_match_1,
context: ctx,
recordValue: null,
achievedAt: hasRequest || achievement ? new Date() : null,
relation: achievement?.relation ?? randomRelation,
});

result.push({
id: -1,
templateId: -1,
userId: user.userID,
isSeen: true,
template: GhostAchievements.student_new_match_2,
context: ctx,
recordValue: null,
achievedAt: hasSuccessfulScreening || achievement ? new Date() : null,
relation: achievement?.relation ?? randomRelation,
});
return result;
}
3 changes: 3 additions & 0 deletions common/achievement/metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ const batchOfMetrics = [
createMetric('pupil_match_create', ['tutee_matching_success'], () => {
return 1;
}),
createMetric('student_match_create', ['tutor_matching_success'], () => {
return 1;
}),
];

export function registerAchievementMetrics() {
Expand Down
77 changes: 76 additions & 1 deletion seed-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,81 @@ void (async function setupDevDB() {
actionName: '{{var:student.firstname}} kontaktieren',
actionRedirectLink: '/chat',
actionType: achievement_action_type_enum.Action,
condition: 'pupil_verified_events > 0',
conditionDataAggregations: JSON.parse('{"pupil_verified_events":{"metric":"pupil_onboarding_verified","aggregator":"count"}}'),
isActive: true,
},
});
await prisma.achievement_template.create({
data: {
templateFor: achievement_template_for_enum.Match,
group: 'pupil_new_match',
groupOrder: 5,
sequentialStepName: 'Erstes Gespräch absolvieren',
type: achievement_type_enum.SEQUENTIAL,
title: 'Neue Lernunterstützung',
tagline: '{{name}}',
subtitle: null,
footer: null,
achievedFooter: 'Wow! Du hast alle Schritte abgeschlossen.',
description:
'Wow, die Vorfreude steigt – bald startet eure gemeinsame Reise! 🚀 Wir wünschen dir viel Spaß bei deinem ersten Termin in der Lernunterstützung mit {{name}} und hoffen, dass ihr euch gut versteht und alles klappt. Für dein erstes Treffen haben wir einen Leitfaden zusammengestellt, der dir hilfreiche Tipps, Tricks und spannende Gesprächsthemen bietet. Nutze ihn, um dich optimal vorzubereiten und das Beste aus eurer Zusammenarbeit herauszuholen!',
achievedDescription:
'Herzlichen Glückwunsch zu deinem erfolgreichen ersten Termin in der Lernunterstützung mit {{name}}! Möge diese Begegnung der Beginn einer spannenden und produktiven Lernreise sein. Wir sind sicher, dass eure Zusammenarbeit von Freude und Erfolg geprägt sein wird. Auf eine inspirierende Zeit des gemeinsamen Lernens!',
image: 'gamification/achievements/release/finish_onboarding/three_pieces/empty_state.png',
achievedImage: null,
actionName: 'Zum Termin',
actionRedirectLink: 'isso',
actionType: achievement_action_type_enum.Appointment,
condition: 'pupil_verified_events > 0',
conditionDataAggregations: JSON.parse('{"pupil_verified_events":{"metric":"pupil_onboarding_verified","aggregator":"count"}}'),
isActive: true,
},
});
await prisma.achievement_template.create({
data: {
templateFor: achievement_template_for_enum.Match,
group: 'student_new_match',
groupOrder: 3,
sequentialStepName: 'Lernpartner:in erhalten',
type: achievement_type_enum.SEQUENTIAL,
title: 'Neue Lernunterstützung',
tagline: 'Starte eine Lernpatenschaft',
subtitle: null,
footer: null,
achievedFooter: null,
description: '',
achievedDescription: null,
image: 'gamification/achievements/release/finish_onboarding/three_pieces/empty_state.png',
achievedImage: null,
actionName: 'Warten',
actionRedirectLink: null,
actionType: achievement_action_type_enum.Wait,
condition: 'student_match_create > 0',
conditionDataAggregations: JSON.parse('{"student_match_create":{"metric":"student_match_create","aggregator":"count"}}'),
isActive: true,
},
});
await prisma.achievement_template.create({
data: {
templateFor: achievement_template_for_enum.Match,
group: 'student_new_match',
groupOrder: 4,
sequentialStepName: 'Lernpartner:in kontaktieren',
type: achievement_type_enum.SEQUENTIAL,
title: 'Neue Lernunterstützung',
tagline: '{{name}}',
subtitle: null,
footer: null,
achievedFooter: null,
description:
'Hurra, wir haben eine:n Lernpartner:in für dich gefunden! 🎉{{var:student.firstname}} ist super motiviert, dir in {{var:matchSubjects}} unter die Arme zu greifen. Um möglichst schnell mit {{var:student.firstname}} loszulegen, kontaktieren {{var:student.firstname}} über den Chat und schlage einen Termin für ein erstes Gespräch vor. {{var:student.firstname}} kann es kaum erwarten, dich kennenzulernen und gemeinsam mit dir durchzustarten!',
achievedDescription: null,
image: 'gamification/achievements/release/finish_onboarding/three_pieces/empty_state.png',
achievedImage: null,
actionName: '{{var:pupil.firstname}} kontaktieren',
actionRedirectLink: '/chat',
actionType: achievement_action_type_enum.Action,
condition: 'student_verified_events > 0',
conditionDataAggregations: JSON.parse('{"student_verified_events":{"metric":"student_onboarding_verified","aggregator":"count"}}'),
isActive: true,
Expand All @@ -894,7 +969,7 @@ void (async function setupDevDB() {
await prisma.achievement_template.create({
data: {
templateFor: achievement_template_for_enum.Match,
group: 'pupil_new_match',
group: 'student_new_match',
groupOrder: 5,
sequentialStepName: 'Erstes Gespräch absolvieren',
type: achievement_type_enum.SEQUENTIAL,
Expand Down

0 comments on commit 8f1554a

Please sign in to comment.