diff --git a/backend/app/config.py b/backend/app/config.py index 04e893f..1d00a21 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -23,6 +23,16 @@ class Config: DESCRIPTION = 'System PRI' OPENAPI_VERSION = '3.0.2' + # Weights for project grade sheet + PRESENTATION_WEIGHT_FIRST_TERM = 1.5 + PRESENTATION_WEIGHT_SECOND_TERM = 1.5 + DOCUMENTATION_WEIGHT_FIRST_TERM = 2 + DOCUMENTATION_WEIGHT_SECOND_TERM = 1 + GROUP_WORK_WEIGHT_FIRST_TERM = 3 + GROUP_WORK_WEIGHT_SECOND_TERM = 3 + PRODUCT_PROJECT_WEIGHT_FIRST_TERM = 3.5 + PRODUCT_PROJECT_WEIGHT_SECOND_TERM = 4.5 + class ProductionConfig(Config): DB_SERVER = "0.0.0.0" diff --git a/backend/app/coordinator/routes/groups.py b/backend/app/coordinator/routes/groups.py index 2fbe993..effb160 100644 --- a/backend/app/coordinator/routes/groups.py +++ b/backend/app/coordinator/routes/groups.py @@ -8,6 +8,7 @@ from ..schemas import GroupSchema, GroupEditSchema, GroupsPaginationSchema, \ GroupCreateSchema, MessageSchema, GroupQuerySchema from ...dependencies import db from ...base.utils import paginate_models +from ..utils import load_weight_for_project_grade_sheet, calculate_points_for_one_term bp = APIBlueprint("groups", __name__, url_prefix="/groups") @@ -20,12 +21,26 @@ def list_groups(year_group_id: int, query: dict) -> dict: page = query.get('page') per_page = query.get('per_page') - groups_query = Group.search_by_name(year_group_id, search_name) + weights = load_weight_for_project_grade_sheet() + groups_query = Group.search_by_name(year_group_id, search_name) data = paginate_models(page, groups_query, per_page) + items = data['items'] + pgs = [] + for g in items: + if len(g.project_grade_sheet)==0: + pgs.append(None) + else: + pgs.append(g.project_grade_sheet[0]) + calculated_points = calculate_points_for_one_term(weights, pgs) + + for group, points in zip(items, calculated_points): + group.points_for_first_term = points[0] + group.points_for_second_term = points[1] + return { - "groups": data['items'], + "groups": items, "max_pages": data['max_pages'] } diff --git a/backend/app/coordinator/utils.py b/backend/app/coordinator/utils.py index 713fb9a..995faa2 100644 --- a/backend/app/coordinator/utils.py +++ b/backend/app/coordinator/utils.py @@ -1,4 +1,5 @@ import copy +import json from collections import defaultdict from datetime import datetime, timedelta from io import BytesIO @@ -6,6 +7,7 @@ from typing import Generator, Union, Any, List, Tuple from pathlib import Path import pandas as pd +from flask import current_app from reportlab.lib import colors from reportlab.lib.enums import TA_CENTER from reportlab.lib.styles import getSampleStyleSheet @@ -16,7 +18,7 @@ from reportlab.pdfbase.ttfonts import TTFont from werkzeug.datastructures import FileStorage from .exceptions import InvalidNameOrTypeHeaderException -from ..students.models import Student, Group +from ..students.models import Student, Group, ProjectGradeSheet from ..examination_schedule.models import TermOfDefence @@ -157,3 +159,51 @@ def generate_examination_schedule_pdf_file(title: str, nested_term_of_defences: pdf_value = pdf_buffer.getvalue() pdf_buffer.close() return pdf_value + + +def load_weight_for_project_grade_sheet() -> Union[dict, None]: + base_dir = current_app.config.get('BASE_DIR') + config_dir = base_dir / "config" + + with open(config_dir / "weights_project_grade_sheet.json") as f: + data = json.load(f) + + return data + + +def calculate_points_for_one_term(weights: dict, project_grade_sheets: List[ProjectGradeSheet]) -> list: + terms = [] + for pgs in project_grade_sheets: + if pgs is None: + terms.append((0, 0)) + continue + + first_term_points = { + 'nominator': 0, + 'denominator': 0, + } + second_term_points = { + 'nominator': 0, + 'denominator': 0, + } + for weight_key, weight_value in weights.items(): + points = first_term_points if weight_key.endswith('1') else second_term_points + try: + attribute_value = getattr(pgs, weight_key) + except AttributeError: + attribute_value = 0 + points['nominator'] += attribute_value * weight_value * 1 / 4 + points['denominator'] += weight_value + + try: + fp = first_term_points['nominator'] / first_term_points['denominator'] + except ZeroDivisionError: + fp = 0 + try: + sp = second_term_points['nominator'] / second_term_points['denominator'] + except ZeroDivisionError: + sp = 0 + + terms.append((round(fp, 2)*100, round(sp, 2)*100)) + + return terms diff --git a/backend/config/weights_project_grade_sheet.json b/backend/config/weights_project_grade_sheet.json new file mode 100644 index 0000000..bd9ac5f --- /dev/null +++ b/backend/config/weights_project_grade_sheet.json @@ -0,0 +1,58 @@ +{ + "presentation_required_content_1": 3, + "presentation_required_content_2": 1, + "presentation_was_compatible_1": 4, + "presentation_was_compatible_2": 4, + "presentation_showing_1": 4, + "presentation_showing_2": 6, + "presentation_answers_to_questions_from_committee_1": 2, + "presentation_answers_to_questions_from_committee_2": 2, + "documentation_project_vision_1": 3, + "documentation_project_vision_2": 0, + "documentation_requirements_1": 3, + "documentation_requirements_2": 2, + "documentation_for_clients_1": 0, + "documentation_for_clients_2": 2, + "documentation_for_developers_1": 1, + "documentation_for_developers_2": 2, + "documentation_license_1": 1, + "documentation_license_2": 1, + "group_work_regularity_1": 5, + "group_work_regularity_2": 5, + "group_work_division_of_work_1": 3, + "group_work_division_of_work_2": 3, + "group_work_contact_with_client_1": 4, + "group_work_contact_with_client_2": 4, + "group_work_management_of_risk_1": 2, + "group_work_management_of_risk_2": 3, + "group_work_work_methodology_1": 3, + "group_work_work_methodology_2": 3, + "group_work_management_of_source_code_1": 2, + "group_work_management_of_source_code_2": 2, + "group_work_devops_1": 0, + "group_work_devops_2": 3, + "products_project_complexity_of_product_1": 3, + "products_project_complexity_of_product_2": 5, + "products_project_access_to_application_1": 1, + "products_project_access_to_application_2": 1, + "products_project_security_issues_1": 0, + "products_project_security_issues_2": 2, + "products_project_access_to_test_application_1": 0, + "products_project_access_to_test_application_2": 5, + "products_project_acceptance_criteria_1": 5, + "products_project_acceptance_criteria_2": 5, + "products_project_expected_functionality_1": 2, + "products_project_expected_functionality_2": 2, + "products_project_promises_well_1": 5, + "products_project_promises_well_2": 0, + "products_project_has_been_implemented_1": 0, + "products_project_has_been_implemented_2": 3, + "products_project_is_useful_1": 3, + "products_project_is_useful_2": 5, + "products_project_prototype_1": 2, + "products_project_prototype_2": 0, + "products_project_tests_1": 0, + "products_project_tests_2": 4, + "products_project_technology_1": 1, + "products_project_technology_2": 2 +}