Skip to content

Commit 42e330a

Browse files
committed
learning score with 2 decimals and adding when user donates. Advancing #106
1 parent 99dd865 commit 42e330a

File tree

3 files changed

+109
-17
lines changed

3 files changed

+109
-17
lines changed

apps/nextjs/app/[lang]/profile/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ export default function ProfileForm({ params }: PageProps) {
509509
</h3>
510510
<div className="w-32 h-32 bg-blue-100 rounded-full flex items-center justify-center">
511511
<span className="text-4xl font-bold text-blue-600">
512-
{profile.learningscore || 0}
512+
{(profile.learningscore || 0).toFixed(2)}
513513
</span>
514514
</div>
515515
</div>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { Kysely } from 'kysely'
2+
3+
export async function up(db: Kysely<any>): Promise<void> {
4+
// First, drop the view that depends on the column
5+
await db.schema.dropView('view_user_scores').ifExists().execute();
6+
7+
// Alter the column type to support floating-point numbers
8+
await db.schema
9+
.alterTable('usuario')
10+
.alterColumn('learningscore', (ac) => ac.setDataType('double precision'))
11+
.execute();
12+
13+
// Recreate the view with the updated column type
14+
await db.schema
15+
.createView('view_user_scores')
16+
.as(
17+
db.selectFrom('usuario').select([
18+
'id as user_id',
19+
'learningscore',
20+
'profilescore',
21+
])
22+
)
23+
.execute();
24+
}
25+
26+
export async function down(db: Kysely<any>): Promise<void> {
27+
// Drop the view before altering the column back
28+
await db.schema.dropView('view_user_scores').ifExists().execute();
29+
30+
// Revert the column type back to integer
31+
// Note: This might cause data loss if there are fractional values
32+
await db.schema
33+
.alterTable('usuario')
34+
.alterColumn('learningscore', (ac) => ac.setDataType('integer'))
35+
.execute();
36+
37+
// Recreate the view with the original column type
38+
await db.schema
39+
.createView('view_user_scores')
40+
.as(
41+
db.selectFrom('usuario').select([
42+
'id as user_id',
43+
'learningscore',
44+
'profilescore',
45+
])
46+
)
47+
.execute();
48+
}

apps/nextjs/lib/scores.ts

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,61 @@ import type { Insertable, Selectable, Updateable } from 'kysely'
55

66
import type { DB, Usuario, CourseUsuario } from '@/db/db.d'
77

8+
const USD_TO_SLE_RATE = 22;
9+
const SLE_TO_SCORE_RATIO = 10;
10+
811
interface CourseData {
912
[key: number]: number
1013
}
1114

15+
/**
16+
* Adds a calculated learning score to a user's profile based on a donation amount.
17+
*
18+
* @param db The Kysely database instance.
19+
* @param userId The ID of the user to whom the score will be added.
20+
* @param donationAmountUSD The amount of the donation in USD.
21+
* @returns The new total learning score.
22+
*/
23+
export async function addDonationToLearningScore(
24+
db: Kysely<DB>,
25+
userId: string,
26+
donationAmountUSD: number
27+
): Promise<number> {
28+
const scoreToAdd = (donationAmountUSD * USD_TO_SLE_RATE) / SLE_TO_SCORE_RATIO;
29+
30+
// Round to 2 decimal places to avoid floating point inaccuracies
31+
const roundedScoreToAdd = Math.round(scoreToAdd * 100) / 100;
32+
33+
const user = await db
34+
.selectFrom('usuario')
35+
.where('id', '=', userId)
36+
.select('learningscore')
37+
.executeTakeFirst();
38+
39+
if (!user) {
40+
throw new Error(`User with ID ${userId} not found.`);
41+
}
42+
43+
const currentScore = user.learningscore || 0;
44+
const newLearningScore = currentScore + roundedScoreToAdd;
45+
46+
await db
47+
.updateTable('usuario')
48+
.set({
49+
learningscore: newLearningScore,
50+
updated_at: new Date(),
51+
})
52+
.where('id', '=', userId)
53+
.execute();
54+
55+
return newLearningScore;
56+
}
57+
58+
1259
/**
1360
* Calculates and updates all scores for a user, including global scores
1461
* and course-specific metrics, within a single database transaction.
62+
* This function now preserves the fractional part of the learning score (from donations).
1563
*
1664
* @param db - The Kysely database instance.
1765
* @param user - The user object, as selected from the database.
@@ -22,7 +70,9 @@ export async function updateUserAndCoursePoints(
2270
user: Selectable<Usuario>,
2371
courseId: number | null,
2472
): Promise<number> {
25-
console.log("OJO updateUserAndCoursePoints. user=", user)
73+
// Preserve the fractional part of the learning score, which comes from donations.
74+
const donationScore = (user.learningscore || 0) % 1;
75+
2676
// Process each guide the user has answered
2777
const guidesUsuario = await sql<any>`
2878
SELECT *
@@ -32,13 +82,11 @@ export async function updateUserAndCoursePoints(
3282
WHERE guide_usuario.usuario_id = ${user.id}
3383
`.execute(db)
3484

35-
console.log("OJO guidesUsuario=", guidesUsuario)
3685
const pointsGuidesCourse: CourseData = {}
3786
const earnedCourse: CourseData = {}
3887
const amountGuidesCourse: CourseData = {}
3988

4089
for (const guideUsuario of guidesUsuario.rows) {
41-
console.log(" OJO guideUsuario=", guideUsuario)
4290
const courseId = guideUsuario.proyectofinanciero_id;
4391
if (pointsGuidesCourse[courseId] ==
4492
undefined) {
@@ -65,14 +113,12 @@ export async function updateUserAndCoursePoints(
65113
}
66114
for (const cId of courseIds) {
67115
const currentCourseId = Number(cId);
68-
console.log(" OJO courseId=", currentCourseId)
69116
const userCourse = (await db
70117
.selectFrom('course_usuario')
71118
.where('usuario_id', '=', user.id)
72119
.where('proyectofinanciero_id', '=', currentCourseId)
73120
.selectAll()
74121
.execute()) || []
75-
console.log(" OJO userCourse=", userCourse)
76122
if (userCourse.length == 0) {
77123
const cp: Insertable<CourseUsuario> = {
78124
usuario_id: user.id,
@@ -82,23 +128,20 @@ export async function updateUserAndCoursePoints(
82128
amountscholarship: 0,
83129
percentagecompleted: 0,
84130
}
85-
const icp = await db
131+
await db
86132
.insertInto('course_usuario')
87133
.values(cp)
88134
.returningAll()
89135
.executeTakeFirstOrThrow()
90-
console.log('After insert icp=', icp)
91136
}
92137
const courseGuidesCountResult = await db
93138
.selectFrom('cor1440_gen_actividadpf')
94139
.where('proyectofinanciero_id', '=', currentCourseId)
95140
.select(db.fn.countAll().as('count'))
96141
.executeTakeFirst()
97142
const totalGuidesInCourse = Number(courseGuidesCountResult?.count) || 0
98-
console.log(" OJO totalGuidesInCourse=", totalGuidesInCourse)
99143
const percentd = totalGuidesInCourse > 0 ?
100144
((amountGuidesCourse[currentCourseId] || 0) / totalGuidesInCourse) * 100 : 0
101-
console.log(" OJO percentd=", percentd)
102145

103146
const updateCourseUsuario = {
104147
guidespoints: pointsGuidesCourse[currentCourseId] || 0,
@@ -114,29 +157,30 @@ export async function updateUserAndCoursePoints(
114157
}
115158

116159

117-
// 3. Calculate global Learning Score
160+
// 3. Calculate global Learning Score from courses and guides (the integer part)
118161
const totalGuidePointsResult = await db
119162
.selectFrom('guide_usuario')
120163
.where('usuario_id', '=', user.id)
121164
.select(db.fn.sum('points').as('total_points'))
122165
.executeTakeFirst()
123166
const totalGuidePoints = Number(totalGuidePointsResult?.total_points) || 0
124-
console.log("OJO totalGuidePoints=", totalGuidePoints)
125167

126168
const totalCoursePointsResult = await db
127169
.selectFrom('course_usuario')
128170
.where('usuario_id', '=', user.id)
129171
.select(db.fn.sum('points').as('total_points'))
130172
.executeTakeFirst()
131173
const totalCoursePoints = Number(totalCoursePointsResult?.total_points) || 0
132-
console.log("OJO totalCoursePoints=", totalCoursePoints)
133174

134-
const learningscore = totalGuidePoints + totalCoursePoints
135-
console.log("OJO learningscore=", learningscore)
175+
// This is the score from educational activities
176+
const baseLearningscore = totalGuidePoints + totalCoursePoints
177+
178+
// Combine the base score with the preserved donation score
179+
const finalLearningscore = baseLearningscore + donationScore;
136180

137181
// 4. Update the main usuario table
138182
const uUsuario: Updateable<Usuario> = {
139-
learningscore: learningscore,
183+
learningscore: finalLearningscore,
140184
updated_at: new Date(),
141185
}
142186

@@ -145,5 +189,5 @@ export async function updateUserAndCoursePoints(
145189
.set(uUsuario)
146190
.where('id', '=', user.id).execute()
147191

148-
return learningscore
192+
return finalLearningscore
149193
}

0 commit comments

Comments
 (0)