From 89850776527775fc760d20caeb38c4f5e5c5a842 Mon Sep 17 00:00:00 2001 From: Manas Solanki Date: Fri, 26 Jan 2018 11:20:33 +0530 Subject: [PATCH] Assessment Module Report (#12605) * updated course wise assessment report * added new report Final assessment grades --- erpnext/education/api.py | 5 +- .../assessment_criteria.py | 6 +- .../course_wise_assessment_report.html | 8 +- .../course_wise_assessment_report.js | 28 +- .../course_wise_assessment_report.py | 298 ++++++++++-------- .../final_assessment_grades/__init__.py | 0 .../final_assessment_grades.js | 38 +++ .../final_assessment_grades.json | 20 ++ .../final_assessment_grades.py | 85 +++++ 9 files changed, 349 insertions(+), 139 deletions(-) create mode 100644 erpnext/education/report/final_assessment_grades/__init__.py create mode 100644 erpnext/education/report/final_assessment_grades/final_assessment_grades.js create mode 100644 erpnext/education/report/final_assessment_grades/final_assessment_grades.json create mode 100644 erpnext/education/report/final_assessment_grades/final_assessment_grades.py diff --git a/erpnext/education/api.py b/erpnext/education/api.py index ea4da69504..99fb36e52c 100644 --- a/erpnext/education/api.py +++ b/erpnext/education/api.py @@ -269,7 +269,10 @@ def get_grade(grading_scale, percentage): :param Percentage: Score Percentage Percentage """ grading_scale_intervals = {} - for d in frappe.get_all("Grading Scale Interval", fields=["grade_code", "threshold"], filters={"parent": grading_scale}): + if not hasattr(frappe.local, 'grading_scale'): + grading_scale = frappe.get_all("Grading Scale Interval", fields=["grade_code", "threshold"], filters={"parent": grading_scale}) + frappe.local.grading_scale = grading_scale + for d in frappe.local.grading_scale: grading_scale_intervals.update({d.threshold:d.grade_code}) intervals = sorted(grading_scale_intervals.keys(), key=float, reverse=True) for interval in intervals: diff --git a/erpnext/education/doctype/assessment_criteria/assessment_criteria.py b/erpnext/education/doctype/assessment_criteria/assessment_criteria.py index e666a74959..eadc0de95b 100644 --- a/erpnext/education/doctype/assessment_criteria/assessment_criteria.py +++ b/erpnext/education/doctype/assessment_criteria/assessment_criteria.py @@ -6,5 +6,9 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +STD_CRITERIA = ["total", "total score", "total grade", "maximum score", "score", "grade"] + class AssessmentCriteria(Document): - pass + def validate(self): + if self.assessment_criteria.lower() in STD_CRITERIA: + frappe.throw("Can't create standard criteria. Please rename the criteria") \ No newline at end of file diff --git a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.html b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.html index 60300b0528..e46a5e764a 100644 --- a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.html +++ b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.html @@ -6,9 +6,15 @@ {% } %}

{%= __("Assessment Report") %}


+
{%= __("Academic Year: ") %} {%= filters.academic_year %}
+{% if (filters.academic_term){ %} +
{%= __("Academic Term: ") %} {%= filters.academic_term %}
+{% } %}
{%= __("Course Code: ") %} {%= filters.course %}
{%= __("Assessment Group: ") %} {%= filters.assessment_group %}
-
{%= __("Assessment Plan: ") %} {%= data_to_be_printed[0]["assessment_plan"] %}
+{% if (filters.student_group){ %} +
{%= __("Student Group: ") %} {%= filters.student_group %}
+{% } %}
diff --git a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.js b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.js index 42b19ebf06..8c42d48641 100644 --- a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.js +++ b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.js @@ -4,18 +4,17 @@ frappe.query_reports["Course wise Assessment Report"] = { "filters": [ { - "fieldname":"assessment_group", - "label": __("Assessment Group"), + "fieldname":"academic_year", + "label": __("Academic Year"), "fieldtype": "Link", - "options": "Assessment Group", - "reqd": 1, - "get_query": function() { - return{ - filters: { - 'is_group': 0 - } - }; - } + "options": "Academic Year", + "reqd": 1 + }, + { + "fieldname":"academic_term", + "label": __("Academic Term"), + "fieldtype": "Link", + "options": "Academic Term" }, { "fieldname":"course", @@ -29,6 +28,13 @@ frappe.query_reports["Course wise Assessment Report"] = { "label": __("Student Group"), "fieldtype": "Link", "options": "Student Group" + }, + { + "fieldname":"assessment_group", + "label": __("Assessment Group"), + "fieldtype": "Link", + "options": "Assessment Group", + "reqd": 1 } ] }; diff --git a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.py b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.py index ff172381b3..a50ad7b91e 100644 --- a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.py +++ b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.py @@ -5,129 +5,189 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import flt -from collections import defaultdict +from collections import defaultdict, OrderedDict from erpnext.education.api import get_grade def execute(filters=None): - data = [] - + data, chart, grades = [], [], [] args = frappe._dict() + grade_wise_analysis = defaultdict(dict) + + args["academic_year"] = filters.get("academic_year") + args["course"] = filters.get("course") args["assessment_group"] = filters.get("assessment_group") + + args["academic_term"] = filters.get("academic_term") + args["student_group"] = filters.get("student_group") + if args["assessment_group"] == "All Assessment Groups": frappe.throw(_("Please select the assessment group other than 'All Assessment Groups'")) - args["course"] = filters.get("course") - args["student_group"] = filters.get("student_group") + returned_values = get_formatted_result(args, get_assessment_criteria=True) + student_dict = returned_values["student_details"] + result_dict = returned_values["assessment_result"] + assessment_criteria_dict = returned_values["assessment_criteria"] + + for student in result_dict: + student_row = {} + student_row["student"] = student + student_row["student_name"] = student_dict[student] + for criteria in assessment_criteria_dict: + scrub_criteria = frappe.scrub(criteria) + if criteria in result_dict[student][args.course][args.assessment_group]: + student_row[scrub_criteria] = result_dict[student][args.course][args.assessment_group][criteria]["grade"] + student_row[scrub_criteria + "_score"] = result_dict[student][args.course][args.assessment_group][criteria]["score"] + + # create the list of possible grades + if student_row[scrub_criteria] not in grades: + grades.append(student_row[scrub_criteria]) + + # create the dict of for gradewise analysis + if student_row[scrub_criteria] not in grade_wise_analysis[criteria]: + grade_wise_analysis[criteria][student_row[scrub_criteria]] = 1 + else: + grade_wise_analysis[criteria][student_row[scrub_criteria]] += 1 + else: + student_row[frappe.scrub(criteria)] = "" + student_row[frappe.scrub(criteria)+ "_score"] = "" + data.append(student_row) + + assessment_criteria_list = [d for d in assessment_criteria_dict] + columns = get_column(assessment_criteria_dict) + chart = get_chart_data(grades, assessment_criteria_list, grade_wise_analysis) + + return columns, data, None, chart - # find all assessment plan and related details linked with the given filters - def get_assessment_details(): - if args["student_group"]: - cond = "and ap.student_group=%(student_group)s" +def get_formatted_result(args, get_assessment_criteria=False, get_course=False): + cond, cond1, cond2, cond3, cond4 = " ", " ", " ", " ", " " + args_list = [args.academic_year] + + if args.course: + cond = " and ar.course=%s" + args_list.append(args.course) + + if args.academic_term: + cond1 = " and ar.academic_term=%s" + args_list.append(args.academic_term) + + if args.student_group: + cond2 = " and ar.student_group=%s" + args_list.append(args.student_group) + + create_total_dict = False + group_type = frappe.get_value("Assessment Group", args.assessment_group, "is_group") + if group_type: + from frappe.desk.treeview import get_children + assessment_groups = [d.get("value") for d in get_children("Assessment Group", + args.assessment_group) if d.get("value") and not d.get("expandable")] + cond3 = " and ar.assessment_group in (%s)"%(', '.join(['%s']*len(assessment_groups))) + else: + assessment_groups = [args.assessment_group] + cond3 = " and ar.assessment_group=%s" + args_list += assessment_groups + + if args.students: + cond4 = " and ar.student in (%s)"%(', '.join(['%s']*len(args.students))) + args_list += args.students + + assessment_result = frappe.db.sql(''' + SELECT + ar.student, ar.student_name, ar.academic_year, ar.academic_term, ar.program, ar.course, + ar.assessment_plan, ar.grading_scale, ar.assessment_group, ar.student_group, + ard.assessment_criteria, ard.maximum_score, ard.grade, ard.score + FROM + `tabAssessment Result` ar, `tabAssessment Result Detail` ard + WHERE + ar.name=ard.parent and ar.docstatus=1 and ar.academic_year=%s {0} {1} {2} {3} {4} + ORDER BY + ard.assessment_criteria'''.format(cond, cond1, cond2, cond3, cond4), + tuple(args_list), as_dict=1) + + # create the nested dictionary structure as given below: + # ..... + # "Total Score" -> assessment criteria used for totaling and args.assessment_group -> for totaling all the assesments + + student_details = {} + formatted_assessment_result = defaultdict(dict) + assessment_criteria_dict = OrderedDict() + course_dict = OrderedDict() + total_maximum_score = None + if not (len(assessment_groups) == 1 and assessment_groups[0] == args.assessment_group): + create_total_dict = True + + # add the score for a given score and recalculate the grades + def add_score_and_recalculate_grade(result, assessment_group, assessment_criteria): + formatted_assessment_result[result.student][result.course][assessment_group]\ + [assessment_criteria]["maximum_score"] += result.maximum_score + formatted_assessment_result[result.student][result.course][assessment_group]\ + [assessment_criteria]["score"] += result.score + tmp_grade = get_grade(result.grading_scale, ((formatted_assessment_result[result.student][result.course] + [assessment_group][assessment_criteria]["score"])/(formatted_assessment_result[result.student] + [result.course][assessment_group][assessment_criteria]["maximum_score"]))*100) + formatted_assessment_result[result.student][result.course][assessment_group]\ + [assessment_criteria]["grade"] = tmp_grade + + # create the assessment criteria "Total Score" with the sum of all the scores of the assessment criteria in a given assessment group + def add_total_score(result, assessment_group): + if "Total Score" not in formatted_assessment_result[result.student][result.course][assessment_group]: + formatted_assessment_result[result.student][result.course][assessment_group]["Total Score"] = frappe._dict({ + "assessment_criteria": "Total Score", "maximum_score": result.maximum_score, "score": result.score, "grade": result.grade}) else: - cond = '' + add_score_and_recalculate_grade(result, assessment_group, "Total Score") - assessment_plan = frappe.db.sql(''' - select - ap.name, ap.student_group, ap.grading_scale, apc.assessment_criteria, apc.maximum_score as max_score - from - `tabAssessment Plan` ap, `tabAssessment Plan Criteria` apc - where - ap.assessment_group=%(assessment_group)s and ap.course=%(course)s and - ap.name=apc.parent and ap.docstatus=1 {0} - order by - apc.assessment_criteria'''.format(cond), (args), as_dict=1) + for result in assessment_result: + if result.student not in student_details: + student_details[result.student] = result.student_name - assessment_plan_list = list(set([d["name"] for d in assessment_plan])) - if not assessment_plan_list: - frappe.throw(_("No assessment plan linked with this assessment group")) + assessment_criteria_details = frappe._dict({"assessment_criteria": result.assessment_criteria, + "maximum_score": result.maximum_score, "score": result.score, "grade": result.grade}) - assessment_criteria_list = list(set([(d["assessment_criteria"],d["max_score"]) for d in assessment_plan])) - student_group_list = list(set([d["student_group"] for d in assessment_plan])) - total_maximum_score = flt(sum([flt(d[1]) for d in assessment_criteria_list])) - grading_scale = assessment_plan[0]["grading_scale"] + if not formatted_assessment_result[result.student]: + formatted_assessment_result[result.student] = defaultdict(dict) + if not formatted_assessment_result[result.student][result.course]: + formatted_assessment_result[result.student][result.course] = defaultdict(dict) - return assessment_plan_list, assessment_criteria_list, total_maximum_score, grading_scale, student_group_list + if not create_total_dict: + formatted_assessment_result[result.student][result.course][result.assessment_group]\ + [result.assessment_criteria] = assessment_criteria_details + add_total_score(result, result.assessment_group) + + # create the total of all the assessment groups criteria-wise + elif create_total_dict: + if not formatted_assessment_result[result.student][result.course][args.assessment_group]: + formatted_assessment_result[result.student][result.course][args.assessment_group] = defaultdict(dict) + formatted_assessment_result[result.student][result.course][args.assessment_group]\ + [result.assessment_criteria] = assessment_criteria_details + elif result.assessment_criteria not in formatted_assessment_result[result.student][result.course][args.assessment_group]: + formatted_assessment_result[result.student][result.course][args.assessment_group]\ + [result.assessment_criteria] = assessment_criteria_details + elif result.assessment_criteria in formatted_assessment_result[result.student][result.course][args.assessment_group]: + add_score_and_recalculate_grade(result, args.assessment_group, result.assessment_criteria) + + add_total_score(result, args.assessment_group) + + total_maximum_score = formatted_assessment_result[result.student][result.course][args.assessment_group]\ + ["Total Score"]["maximum_score"] + if get_assessment_criteria: + assessment_criteria_dict[result.assessment_criteria] = formatted_assessment_result[result.student][result.course]\ + [args.assessment_group][result.assessment_criteria]["maximum_score"] + if get_course: + course_dict[result.course] = total_maximum_score + + if get_assessment_criteria and total_maximum_score: + assessment_criteria_dict["Total Score"] = total_maximum_score + + return { + "student_details": student_details, + "assessment_result": formatted_assessment_result, + "assessment_criteria": assessment_criteria_dict, + "course_dict": course_dict + } - # get all the result and make a dict map student as the key and value as dict of result - def get_result_map(): - result_dict = defaultdict(dict) - kounter = defaultdict(dict) - assessment_result = frappe.db.sql('''select ar.student, ard.assessment_criteria, ard.grade, ard.score - from `tabAssessment Result` ar, `tabAssessment Result Detail` ard - where ar.assessment_plan in (%s) and ar.name=ard.parent and ar.docstatus=1 - order by ard.assessment_criteria''' %', '.join(['%s']*len(assessment_plan_list)), - tuple(assessment_plan_list), as_dict=1) - - for result in assessment_result: - if "total_score" in result_dict[result.student]: - total_score = result_dict[result.student]["total_score"] + result.score - else: - total_score = result.score - total = get_grade(grading_scale, (total_score/total_maximum_score)*100) - - if result.grade in kounter[result.assessment_criteria]: - kounter[result.assessment_criteria][result.grade] += 1 - else: - kounter[result.assessment_criteria].update({result.grade: 1}) - - if "Total" not in kounter: - kounter["Total"] = {} - - if "total" in result_dict[result.student]: - prev_grade = result_dict[result.student]["total"] - prev_grade_count = kounter["Total"].get(prev_grade) - 1 - kounter["Total"].update({prev_grade: prev_grade_count}) - latest_grade_count = kounter["Total"].get(total)+1 if kounter["Total"].get(total) else 1 - kounter["Total"].update({total: latest_grade_count}) - - result_dict[result.student].update({ - frappe.scrub(result.assessment_criteria): result.grade, - frappe.scrub(result.assessment_criteria)+"_score": result.score, - "total_score": total_score, - "total": total - }) - - return result_dict, kounter - - # make data from the result dict - def get_data(): - student_list = frappe.db.sql('''select sgs.student, sgs.student_name - from `tabStudent Group` sg, `tabStudent Group Student` sgs - where sg.name = sgs.parent and sg.name in (%s) - order by sgs.group_roll_number asc''' %', '.join(['%s']*len(student_group_list)), - tuple(student_group_list), as_dict=1) - - for student in student_list: - student.update(result_dict[student.student]) - return student_list - - - # get chart data - def get_chart(): - grading_scale = frappe.db.get_value("Assessment Plan", list(assessment_plan_list)[0], "grading_scale") - grades = frappe.db.sql_list('''select grade_code from `tabGrading Scale Interval` where parent=%s''', - (grading_scale)) - criteria_list = [d[0] for d in assessment_criteria_list] + ["Total"] - return get_chart_data(grades, criteria_list, kounter) - - - assessment_plan_list, assessment_criteria_list, total_maximum_score, grading_scale,\ - student_group_list = get_assessment_details() - result_dict, kounter = get_result_map() - data = get_data() - - columns = get_column(assessment_criteria_list, total_maximum_score) - chart = get_chart() - data_to_be_printed = [{ - "assessment_plan": ", ".join(assessment_plan_list) - }] - - return columns, data, None, chart, data_to_be_printed - -def get_column(assessment_criteria, total_maximum_score): +def get_column(assessment_criteria): columns = [{ "fieldname": "student", "label": _("Student ID"), @@ -143,40 +203,28 @@ def get_column(assessment_criteria, total_maximum_score): }] for d in assessment_criteria: columns.append({ - "fieldname": frappe.scrub(d[0]), - "label": d[0], + "fieldname": frappe.scrub(d), + "label": d, "fieldtype": "Data", "width": 110 }) columns.append({ - "fieldname": frappe.scrub(d[0]) +"_score", - "label": "Score(" + str(int(d[1])) + ")", + "fieldname": frappe.scrub(d) +"_score", + "label": "Score(" + str(int(assessment_criteria[d])) + ")", "fieldtype": "Float", "width": 100 }) - columns += [{ - "fieldname": "total", - "label": "Total", - "fieldtype": "Data", - "width": 100 - }, - { - "fieldname": "total_score", - "label": "Total Score("+ str(int(total_maximum_score)) + ")", - "fieldtype": "Float", - "width": 110 - }] - return columns -def get_chart_data(grades, assessment_criteria_list, kounter): + +def get_chart_data(grades, criteria_list, kounter): grades = sorted(grades) datasets = [] for grade in grades: tmp = frappe._dict({"values":[], "title": grade}) - for criteria in assessment_criteria_list: + for criteria in criteria_list: if grade in kounter[criteria]: tmp["values"].append(kounter[criteria][grade]) else: @@ -185,7 +233,7 @@ def get_chart_data(grades, assessment_criteria_list, kounter): return { "data": { - "labels": assessment_criteria_list, + "labels": criteria_list, "datasets": datasets }, "type": 'bar', diff --git a/erpnext/education/report/final_assessment_grades/__init__.py b/erpnext/education/report/final_assessment_grades/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/education/report/final_assessment_grades/final_assessment_grades.js b/erpnext/education/report/final_assessment_grades/final_assessment_grades.js new file mode 100644 index 0000000000..ba0a42ff90 --- /dev/null +++ b/erpnext/education/report/final_assessment_grades/final_assessment_grades.js @@ -0,0 +1,38 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Final Assessment Grades"] = { + "filters": [ + { + "fieldname":"academic_year", + "label": __("Academic Year"), + "fieldtype": "Link", + "options": "Academic Year", + "reqd": 1 + }, + { + "fieldname":"student_group", + "label": __("Student Group"), + "fieldtype": "Link", + "options": "Student Group", + "reqd": 1, + "get_query": function() { + return{ + filters: { + "group_based_on": "Batch", + "academic_year": frappe.query_report_filters_by_name.academic_year.value + } + }; + } + }, + { + "fieldname":"assessment_group", + "label": __("Assessment Group"), + "fieldtype": "Link", + "options": "Assessment Group", + "reqd": 1 + } + + ] +} diff --git a/erpnext/education/report/final_assessment_grades/final_assessment_grades.json b/erpnext/education/report/final_assessment_grades/final_assessment_grades.json new file mode 100644 index 0000000000..1efbb6e66d --- /dev/null +++ b/erpnext/education/report/final_assessment_grades/final_assessment_grades.json @@ -0,0 +1,20 @@ +{ + "add_total_row": 0, + "apply_user_permissions": 1, + "creation": "2018-01-22 17:04:43.412054", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "letter_head": "Shishuvan Secondary School", + "modified": "2018-01-22 17:04:43.412054", + "modified_by": "Administrator", + "module": "Education", + "name": "Final Assessment Grades", + "owner": "Administrator", + "ref_doctype": "Assessment Result", + "report_name": "Final Assessment Grades", + "report_type": "Script Report", + "roles": [] +} \ No newline at end of file diff --git a/erpnext/education/report/final_assessment_grades/final_assessment_grades.py b/erpnext/education/report/final_assessment_grades/final_assessment_grades.py new file mode 100644 index 0000000000..efc9aff083 --- /dev/null +++ b/erpnext/education/report/final_assessment_grades/final_assessment_grades.py @@ -0,0 +1,85 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from collections import defaultdict + +from erpnext.education.report.course_wise_assessment_report.course_wise_assessment_report import get_formatted_result +from erpnext.education.report.course_wise_assessment_report.course_wise_assessment_report import get_chart_data + + +def execute(filters=None): + columns, data, grades = [], [], [] + args = frappe._dict() + course_wise_analysis = defaultdict(dict) + + args["academic_year"] = filters.get("academic_year") + assessment_group = args["assessment_group"] = filters.get("assessment_group") + + student_group = filters.get("student_group") + args.students = frappe.db.sql_list("select student from `tabStudent Group Student` where parent=%s", (student_group)) + + values = get_formatted_result(args, get_course=True) + student_details = values.get("student_details") + assessment_result = values.get("assessment_result") + course_dict = values.get("course_dict") + + for student in args.students: + student_row = {} + student_row["student"] = student + student_row["student_name"] = student_details[student] + for course in course_dict: + scrub_course = frappe.scrub(course) + if assessment_group in assessment_result[student][course]: + student_row["grade_" + scrub_course] = assessment_result[student][course][assessment_group]["Total Score"]["grade"] + student_row["score_" + scrub_course] = assessment_result[student][course][assessment_group]["Total Score"]["score"] + + # create the list of possible grades + if student_row["grade_" + scrub_course] not in grades: + grades.append(student_row["grade_" + scrub_course]) + + # create the dict of for gradewise analysis + if student_row["grade_" + scrub_course] not in course_wise_analysis[course]: + course_wise_analysis[course][student_row["grade_" + scrub_course]] = 1 + else: + course_wise_analysis[course][student_row["grade_" + scrub_course]] += 1 + + data.append(student_row) + + course_list = [d for d in course_dict] + columns = get_column(course_dict) + chart = get_chart_data(grades, course_list, course_wise_analysis) + return columns, data, None, chart + + +def get_column(course_dict): + columns = [{ + "fieldname": "student", + "label": _("Student ID"), + "fieldtype": "Link", + "options": "Student", + "width": 90 + }, + { + "fieldname": "student_name", + "label": _("Student Name"), + "fieldtype": "Data", + "width": 160 + }] + for course in course_dict: + columns.append({ + "fieldname": "grade_" + frappe.scrub(course), + "label": course, + "fieldtype": "Data", + "width": 110 + }) + columns.append({ + "fieldname": "score_" + frappe.scrub(course), + "label": "Score(" + str(course_dict[course]) + ")", + "fieldtype": "Float", + "width": 100 + }) + + return columns