Skip to content

Commit

Permalink
Merge pull request #80 from mnqrt/feat/adrian/gpa-calculator
Browse files Browse the repository at this point in the history
Feat/adrian/gpa calculator
  • Loading branch information
mnqrt authored Aug 6, 2024
2 parents 0a3eb97 + 34851a1 commit 629dcc7
Show file tree
Hide file tree
Showing 7 changed files with 335 additions and 54 deletions.
57 changes: 57 additions & 0 deletions main/fasilkom_courses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
IK_COURSES = [
#Semester 0, Empty
[],

#Semester 1
["UIGE600004", "CSGE601012", "UIGE600003", "CSGE601010", "CSGE601020", "CSCM601150"],

#Semester 2
["UIGE600006", "CSGE601021", "CSGE601011", "CSCM601252", "CSCM601213"],

#Semester 3
["CSGE602022", "CSGE602040", "CSCM602055", "CSGE602012", "CSGE602013"],

#Semester 4
["CSCM602223", "CSGE602024", "CSGE602023", "CSCM602241", "CSGE602070"],

#Semester 5
["CSCM603154", "CSGE603130", "CSCM603117", "CSCM603142", "CSCM603125"],

#Semester 6
["CSCM603228", "CSGE602091"],

#Semester 7
["CSGE614093", "CSGE604097"],

#Semester 8
["CSGE604099"]
]

SI_COURSES = [
#Semester 0, Empty
[],

#Semester 1
["UIGE600004", "CSGE601012", "UIGE600003", "CSGE601010", "CSGE601020", "CSIM601190", "CSIM601191"],

#Semester 2
["UIGE600006", "CSGE601021", "CSGE601011", "CSIM601280", "CSIM601251"],

#Semester 3
["CSGE602022", "CSGE602040", "CSIM602155", "CSGE602012"],

#Semester 4
["CSGE602024", "CSGE602023", "CSGE602070", "CSIM602263", "CSIM602281"],

#Semester 5
["CSIM603026", "CSIM603154", "CSIM603182", "CSIM603183"],

#Semester 6
["CSIM603229", "CSGE603130", "CSGE602091"],

#Semester 7
["CSGE614093", "CSGE604097"],

#Semester 8
["CSGE604099"]
]
27 changes: 27 additions & 0 deletions main/migrations/0016_auto_20240727_0233.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 3.1.2 on 2024-07-27 02:33

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('main', '0015_usergpa_semester_mutu'),
]

operations = [
migrations.CreateModel(
name='ScoreSubcomponent',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('subcomponent_number', models.PositiveIntegerField()),
('subcomponent_score', models.FloatField(null=True)),
('score_component', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.scorecomponent')),
],
),
migrations.AddConstraint(
model_name='scoresubcomponent',
constraint=models.UniqueConstraint(fields=('score_component', 'subcomponent_number'), name='unique_number'),
),
]
18 changes: 18 additions & 0 deletions main/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,22 @@ class CourseSemester(models.Model):
class Meta:
constraints = [
UniqueConstraint(fields=['semester', 'course'], name='unique_semester_course')
]

class ScoreSubcomponent(models.Model):
"""
This class describes the subcomponent from a score component
Ex: There is a Quiz Component, so the subcomponent might be:
- Quiz 1
- Quiz 2
- Quiz 3
- ...etc
"""
score_component = models.ForeignKey(ScoreComponent, on_delete=CASCADE)
subcomponent_number = models.PositiveIntegerField()
subcomponent_score = models.FloatField(null=True)

class Meta:
constraints = [
UniqueConstraint(fields=['score_component', 'subcomponent_number'], name='unique_number')
]
9 changes: 8 additions & 1 deletion main/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from rest_framework import serializers
from django.db.models import Avg

from .models import Calculator, Course, Profile, Review, ScoreComponent, Tag, Bookmark, UserCumulativeGPA, UserGPA, CourseSemester
from .models import Calculator, Course, Profile, Review, ScoreComponent, Tag, Bookmark, UserCumulativeGPA, UserGPA, CourseSemester, ScoreSubcomponent

# class CurriculumSerializer(serializers.ModelSerializer):
# class Meta:
Expand Down Expand Up @@ -271,6 +271,7 @@ def get_course_name(self, obj):

class ScoreComponentSerializer(serializers.ModelSerializer):
calculator_id = serializers.SerializerMethodField('get_calculator_id')
score = serializers.SerializerMethodField('get_score')

class Meta:
model = ScoreComponent
Expand All @@ -279,6 +280,12 @@ class Meta:
def get_calculator_id(self, obj):
return obj.calculator.id

def get_score(self, obj):
all_subcomponent = ScoreSubcomponent.objects.filter(score_component=obj)
is_all_null = all(subcomponent.subcomponent_score is None for subcomponent in all_subcomponent)
score_rendered = -1 if is_all_null else obj.score
return score_rendered

class UserCumulativeGPASerializer(serializers.ModelSerializer):
user = serializers.SerializerMethodField('get_user')
class Meta:
Expand Down
5 changes: 3 additions & 2 deletions main/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django import conf
from rest_framework import routers
from django.urls import path, include
from .views_gpa_calculator import course_semester, gpa_calculator, gpa_calculator_with_semester, course_semester_with_course_id, course_component
from .views_gpa_calculator import course_semester, gpa_calculator, gpa_calculator_with_semester, course_semester_with_course_id, course_component, course_subcomponent
from .views_calculator import calculator, score_component
from .views import like, tag, bookmark, account, leaderboard
from .views_review import ds_review, review
Expand All @@ -25,6 +25,7 @@
path('calculator-gpa/<str:given_semester>', gpa_calculator_with_semester, name="gpa-calculator-with-semester"),
path('course-semester', course_semester, name='course-semester'),
path('course-semester/<str:course_id>', course_semester_with_course_id, name="course-with-id"),
path('course-component', course_component, name="course-component")
path('course-component', course_component, name="course-component"),
path('course-subcomponent', course_subcomponent, name="course-subcomponent")
] + router.urls

67 changes: 38 additions & 29 deletions main/utils.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from datetime import datetime
import math
import os
from django.contrib.auth.models import User
from django.core.paginator import Paginator
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from rest_framework import status

from .models import Course, Profile, UserCumulativeGPA, UserGPA
from .models import Calculator, Course, Profile, ScoreComponent, UserCumulativeGPA, UserGPA, ScoreSubcomponent
from .fasilkom_courses import IK_COURSES, SI_COURSES


def process_sso_profile(sso_profile):
Expand Down Expand Up @@ -145,38 +147,19 @@ def update_semester_gpa(user_cumulative_gpa: UserCumulativeGPA,
delete_semester_gpa(user_cumulative_gpa, old_sks, old_gpa)
add_semester_gpa(user_cumulative_gpa, new_sks, new_gpa)

def get_course_with_prefix(term, course_code):
return Course.objects.filter(term=term, code__startswith=course_code)

MATKUL_WAJIB_UI_CODE = "UIGE"
MATKUL_WAJIB_FASILKOM_CODE = "CSGE"
MATKUL_WAJIB_IK_CODE = "CSCM"
MATKUL_WAJIB_SI_CODE = "CSIM"
FIRST_TERM_IK_CODE = ["UIGE600004", "UIGE600003", "CSGE601010", "CSGE601012", "CSGE601020", "CSCM601150"]
FIRST_TERM_SI_CODE = ["UIGE600004", "UIGE600003", "CSGE601010", "CSGE601012", "CSGE601020", "CSIM601191", "CSIM601190"]
def get_course_by_code(course_code):
return Course.objects.filter(code=course_code).first()

def get_fasilkom_courses(study_program):
courses_by_program = IK_COURSES if "Ilmu Komputer" in study_program else SI_COURSES
study_program_courses = [[]]
for term in range(1,9):

if term == 1:
try:
course_term_list = FIRST_TERM_IK_CODE if "Ilmu Komputer" in study_program else FIRST_TERM_SI_CODE
term_course = [get_course_with_prefix(term, code)[0] for code in course_term_list]
study_program_courses.append(term_course)
except Exception as e:
study_program_courses.append([])
print("Inconsistent course code, try /update-course")
continue

courses_in_term = courses_by_program[term]
term_course = []
term_course.extend(get_course_with_prefix(term, MATKUL_WAJIB_UI_CODE))
term_course.extend(get_course_with_prefix(term, MATKUL_WAJIB_FASILKOM_CODE))

if "Ilmu Komputer" in study_program:
term_course.extend(get_course_with_prefix(term, MATKUL_WAJIB_IK_CODE))
else :
term_course.extend(get_course_with_prefix(term, MATKUL_WAJIB_SI_CODE))
for course_code in courses_in_term:
course = get_course_by_code(course_code)
if course != None:
term_course.append(course)

study_program_courses.append(term_course)
return study_program_courses
Expand All @@ -199,4 +182,30 @@ def get_score(score: float) -> float :
if score < 85:
return 3.7 # A-

return 4.0 # A
return 4.0 # A

def get_null_sum_from_component(score_component: ScoreComponent) -> float :
frequency = ScoreSubcomponent.objects.filter(score_component=score_component).count()
null_sum = 0.0
score_subcomponents = ScoreSubcomponent.objects.filter(score_component=score_component)
for score_subcomponent in score_subcomponents:
null_contribution = 0 if score_subcomponent.subcomponent_score != None else score_component.weight / frequency
null_sum += null_contribution
return null_sum

def get_null_sum_from_calculator(calculator: Calculator) -> float :
null_sum = 0.0
score_components = ScoreComponent.objects.filter(calculator=calculator)
for score_component in score_components:
null_sum += get_null_sum_from_component(score_component)
return null_sum

def get_recommended_score(calculator: Calculator, target_score: int) -> float :
current_score = calculator.total_score
score_left = max(target_score - current_score, 0)
percentage_left = get_null_sum_from_calculator(calculator)

if percentage_left == 0:
return 0

return score_left / percentage_left * 100
Loading

0 comments on commit 629dcc7

Please sign in to comment.