From 0f1aaa3343fcd5d3414318c1b9550165b4e2c721 Mon Sep 17 00:00:00 2001 From: Jaeseok Kang <123261952+ziesski@users.noreply.github.com> Date: Tue, 7 Nov 2023 13:11:00 -0500 Subject: [PATCH 01/11] [Refactor:RainbowGrades] Student.cpp patch (#62) This is student.cpp file patch by @KCony Mainly to address calculation of each normalized grade. " Grades should be compared by the normalized grade only, not normalized grade multiplied by percentages. Otherwise, a grade of 90 for the gradeable with percentage 0.1 will always be considered lower than a grade of 60 for the gradeable with percentage 0.2 ", which we don't want, if we were to drop lowest grade. (gradeable) Also additional logics implemented to handle extra credit accurately. Additionally when running patch to default sample course, it was failing assert(sum_percentage <= 1.0) mostly seemed to be precision issue with floating-point arithmetic. Handled it by padding tolerance of 0.00001 Overall changes has be reviewed and tested. However, it would be best if Barb runs it with previous term data to check the behavior, given that there is slight logic change. --------- Co-authored-by: Konstantin Kuzmin --- student.cpp | 74 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/student.cpp b/student.cpp index 4c61a89..211cda3 100644 --- a/student.cpp +++ b/student.cpp @@ -143,7 +143,21 @@ bool operator<(const score_object &a, const score_object &b) { float p2 = b.percentage; float sm2 = b.scale_max; float my_max2 = std::max(m2,sm2); - return p1 * s1 / my_max1 < p2 * s2 / my_max2; + // Grades should be compared by the normalized grade only, not normalized grade multiplied by percentages. Otherwise, a grade of 90 for the gradeable + // with percentage 0.1 will always be considered lower than a grade of 60 for the gradeable with percentage 0.2, since 0.1 * 0.9 < 0.2 * 0.6 + bool result; + // If any of the two scores has a max of 0 (but not both of them), it means it is extra credit, so it will always be considered larger, so that we do not + // remove extra credit assignment scores under the "drop the lowest" rule + if (((m1 == 0.0) && (m2 != 0.0)) || ((m1 != 0.0) && (m2 == 0.0))) { + result = (m1 == 0.0) ? false : true; + } + else if (std::abs(s1 / my_max1 - s2 / my_max2) > 0.001) { + result = (s1 / my_max1 < s2 / my_max2); + } + // otherwise, order by percentage decreasing, so that if two scores are equal, the gradable with the higher percentage is considered smaller. + // For example, for p1==0.1, s1/my_max1 == 0.9 and p2==0.2, s2/my_max2 == 0.9, the operator would return false. + else result = (p1 > p2); + return result; } float Student::GradeablePercent(GRADEABLE_ENUM g) const { @@ -167,16 +181,20 @@ float Student::GradeablePercent(GRADEABLE_ENUM g) const { //Do one pass to get the defined item scores float nonzero_sum = 0; int nonzero_count = 0; + int non_extra_credit_count = 0; for (int i = 0; i < GRADEABLES[g].getCount(); i++) { //float s = getGradeableItemGrade(g,i).getValue(); std::string id = GRADEABLES[g].getID(i); if(!id.empty()){ + if (GRADEABLES[g].getItemMaximum(id) > 0) { + non_extra_credit_count++; + } float m = std::max(GRADEABLES[g].getItemMaximum(id),GRADEABLES[g].getScaleMaximum(id)); if(m > 0){ nonzero_sum += m; nonzero_count++; } - } + } } //If there are no gradeables with a max >0, bucket is 0% anyway @@ -190,8 +208,10 @@ float Student::GradeablePercent(GRADEABLE_ENUM g) const { float s = getGradeableItemGrade(g,i).getValue(); std::string id = GRADEABLES[g].getID(i); float m = nonzero_sum/nonzero_count; - if(!id.empty() && GRADEABLES[g].isReleased(id)){ + //if(!id.empty() && GRADEABLES[g].isReleased(id)){ + if(!id.empty()){ m = GRADEABLES[g].getItemMaximum(id); + std::cout << "m" << m << std::endl; } float p = GRADEABLES[g].getItemPercentage(id); float sm = GRADEABLES[g].getScaleMaximum(id); @@ -200,9 +220,11 @@ float Student::GradeablePercent(GRADEABLE_ENUM g) const { // sort the scores (smallest first) std::sort(scores.begin(),scores.end()); - - assert (GRADEABLES[g].getRemoveLowest() >= 0 && - GRADEABLES[g].getRemoveLowest() < GRADEABLES[g].getCount()); + //to check that the number of "drop the lowest" is less than the number of non extra credit gradeables, + // i.e., it is not allowed to drop all non extra credit gradeables + assert (GRADEABLES[g].getRemoveLowest() >= 0 && ( + (non_extra_credit_count > 0 && GRADEABLES[g].getRemoveLowest() < non_extra_credit_count)) || + (GRADEABLES[g].getRemoveLowest() == 0)); // sum the remaining (higher) scores float sum_max = 0; @@ -222,6 +244,8 @@ float Student::GradeablePercent(GRADEABLE_ENUM g) const { // sum the remaining (higher) scores float sum = 0; + // to also sum the remaining (higher) percentages + float sum_percentage = 0; for (int i = GRADEABLES[g].getRemoveLowest(); i < GRADEABLES[g].getCount(); i++) { float s = scores[i].score; float m = scores[i].max; @@ -243,10 +267,23 @@ float Student::GradeablePercent(GRADEABLE_ENUM g) const { if (my_max > 0) { sum += p * s / my_max; } + // to add percentages only for non-extra credit gradeables + if (m != 0.0) { + sum_percentage += p; + } + } + assert(sum_percentage <= 1.0); + if (sum_max == 0) { // pure extra credit category + sum_percentage = 1.0; } - float percentage = GRADEABLES[g].hasSortedWeight() ? sum : GRADEABLES[g].getPercent() * sum; + + //float percentage = GRADEABLES[g].hasSortedWeight() ? sum : GRADEABLES[g].getPercent() * sum; + float percentage = GRADEABLES[g].hasSortedWeight() ? sum : GRADEABLES[g].getPercent() * sum / sum_percentage; + // std::cout << "sum: " << sum << "; GRADEABLES[g].getPercent() * sum / sum_percentage: " << (GRADEABLES[g].getPercent() * sum / sum_percentage) << "; sum_percentage: " << sum_percentage << std::endl; float percentage_upper_clamp = GRADEABLES[g].getBucketPercentageUpperClamp(); + // 1 line added by Konstantin Kuzmin 20230823T181400 + // std::cout << "percentage: " << percentage << "; percentage_upper_clamp: " << (percentage_upper_clamp) << "; GRADEABLES[g].hasSortedWeight():" << GRADEABLES[g].hasSortedWeight() << std::endl; if (percentage_upper_clamp > 0) { percentage = std::min(percentage, percentage_upper_clamp); } @@ -327,17 +364,17 @@ float Student::lowest_test_counts_half_pct() const { } std::sort(scores.begin(),scores.end()); - // then sum the scores + // then sum the scores float sum = 0.5 * scores[0]; float weight_total = 0.5; for (int i = 1; i < num_tests; i++) { sum += scores[i]; weight_total += 1.0; } - + // renormalize! sum *= float(num_tests) / weight_total; - + // scale to percent; return 100 * GRADEABLES[GRADEABLE_ENUM::TEST].getPercent() * sum / float (GRADEABLES[GRADEABLE_ENUM::TEST].getMaximum()); } @@ -363,7 +400,7 @@ int Student::getAllowedLateDays(int which_lecture) const { if(earn_late_days_from_polls){ total = getPollPoints(); } - + for (unsigned int i = 0; i < GLOBAL_earned_late_days.size(); i++) { if (total >= GLOBAL_earned_late_days[i]) { answer++; @@ -375,7 +412,7 @@ int Student::getAllowedLateDays(int which_lecture) const { answer++; } } - + return std::max(current_allowed_late_days,answer); } @@ -395,7 +432,7 @@ int Student::getUsedLateDays() const { float Student::overall_b4_moss() const { float answer = 0; - for (unsigned int i = 0; i < ALL_GRADEABLES.size(); i++) { + for (unsigned int i = 0; i < ALL_GRADEABLES.size(); i++) { GRADEABLE_ENUM g = ALL_GRADEABLES[i]; answer += GradeablePercent(g); } @@ -407,7 +444,7 @@ std::string Student::grade(bool flag_b4_moss, Student *lowest_d) const { if (section == "null") return ""; if (!flag_b4_moss && manual_grade != "") return manual_grade; - + float over = overall(); if (flag_b4_moss) { over = overall_b4_moss(); @@ -417,12 +454,13 @@ std::string Student::grade(bool flag_b4_moss, Student *lowest_d) const { // some criteria that might indicate automatic failure of course // (instructor can override with manual grade) + // 14 lines commented out by Konstantin Kuzmin to prevent Rainbow Grades from auto failing anyone 20230505T090900 //Old (pre Su2019) DS method - int failed_lab = (GradeablePercent(GRADEABLE_ENUM::LAB) < 1.01 * lowest_d->GradeablePercent(GRADEABLE_ENUM::LAB) ) ? true : false; + /*int failed_lab = (GradeablePercent(GRADEABLE_ENUM::LAB) < 1.01 * lowest_d->GradeablePercent(GRADEABLE_ENUM::LAB) ) ? true : false; int failed_hw = (GradeablePercent(GRADEABLE_ENUM::HOMEWORK) < 0.95 * lowest_d->GradeablePercent(GRADEABLE_ENUM::HOMEWORK) ) ? true : false; int failed_testA = (GradeablePercent(GRADEABLE_ENUM::TEST) < 0.90 * lowest_d->GradeablePercent(GRADEABLE_ENUM::TEST) ) ? true : false; int failed_testB = (GradeablePercent(GRADEABLE_ENUM::EXAM) < 0.90 * lowest_d->GradeablePercent(GRADEABLE_ENUM::EXAM) ) ? true : false; - int failed_testC = (GradeablePercent(GRADEABLE_ENUM::TEST) + GradeablePercent(GRADEABLE_ENUM::EXAM) < + int failed_testC = (GradeablePercent(GRADEABLE_ENUM::TEST) + GradeablePercent(GRADEABLE_ENUM::EXAM) < 0.90 * lowest_d->GradeablePercent(GRADEABLE_ENUM::TEST) + lowest_d->GradeablePercent(GRADEABLE_ENUM::EXAM) ) ? true : false; if (failed_lab || failed_hw || ( failed_testA + @@ -432,7 +470,7 @@ std::string Student::grade(bool flag_b4_moss, Student *lowest_d) const { ((Student*)this)->other_note += "SHOULD AUTO FAIL"; return "F"; - } + }*/ /* @@ -446,7 +484,7 @@ std::string Student::grade(bool flag_b4_moss, Student *lowest_d) const { } } */ - + // otherwise apply the cutoffs if (over >= CUTOFFS["A"]) return "A"; if (over >= CUTOFFS["A-"]) return "A-"; From db9d68d68e4b278c7c8d6cad9338912011dcf561 Mon Sep 17 00:00:00 2001 From: Rita Lei <117528498+RitaLei123@users.noreply.github.com> Date: Tue, 7 Nov 2023 13:13:00 -0500 Subject: [PATCH 02/11] [Refactor: RainbowGrades] Academic sanction (#61) Closes https://github.com/Submitty/Submitty/issues/9725 Changed all the mossify and moss instances to academic_sanction in three documents (main.cpp, student.cpp, student.h) in the Rainbow Grades directory. These changes were made to avoid potential copyright issues. image --------- Co-authored-by: Barb Cutler --- constants_and_globals.h | 2 +- main.cpp | 18 +++++++++--------- output.cpp | 14 +++++++------- student.cpp | 30 +++++++++++++++--------------- student.h | 14 +++++++------- 5 files changed, 39 insertions(+), 39 deletions(-) diff --git a/constants_and_globals.h b/constants_and_globals.h index 63b880f..6053170 100644 --- a/constants_and_globals.h +++ b/constants_and_globals.h @@ -11,7 +11,7 @@ class Student; // What sections to display in the output table extern bool DISPLAY_INSTRUCTOR_NOTES; extern bool DISPLAY_EXAM_SEATING; -extern bool DISPLAY_MOSS_DETAILS; +extern bool DISPLAY_ACADEMIC_SANCTION_DETAILS; extern bool DISPLAY_FINAL_GRADE; extern bool DISPLAY_GRADE_SUMMARY; extern bool DISPLAY_GRADE_DETAILS; diff --git a/main.cpp b/main.cpp index 487d87d..dc70565 100644 --- a/main.cpp +++ b/main.cpp @@ -124,7 +124,7 @@ std::string BONUS_FILE; bool DISPLAY_INSTRUCTOR_NOTES = false; bool DISPLAY_EXAM_SEATING = false; -bool DISPLAY_MOSS_DETAILS = false; +bool DISPLAY_ACADEMIC_SANCTION_DETAILS = false; bool DISPLAY_FINAL_GRADE = false; bool DISPLAY_GRADE_SUMMARY = false; bool DISPLAY_GRADE_DETAILS = false; @@ -151,8 +151,8 @@ void PrintExamRoomAndZoneTable(const std::string &id, nlohmann::json &mj, Studen bool by_overall(const Student* s1, const Student* s2) { - float s1_overall = s1->overall_b4_moss(); - float s2_overall = s2->overall_b4_moss(); + float s1_overall = s1->overall_b4_academic_sanction(); + float s2_overall = s2->overall_b4_academic_sanction(); if (s1 == AVERAGE_STUDENT_POINTER) return true; if (s2 == AVERAGE_STUDENT_POINTER) return false; @@ -373,8 +373,8 @@ void preprocesscustomizationfile(const std::string &now_string, DISPLAY_INSTRUCTOR_NOTES = true; } else if (token == "exam_seating") { DISPLAY_EXAM_SEATING = true; - } else if (token == "moss_details") { - DISPLAY_MOSS_DETAILS = true; + } else if (token == "academic_sanction_details") { + DISPLAY_ACADEMIC_SANCTION_DETAILS = true; } else if (token == "final_grade") { DISPLAY_FINAL_GRADE = true; } else if (token == "grade_summary") { @@ -765,8 +765,8 @@ void preprocesscustomizationfile(const std::string &now_string, DISPLAY_INSTRUCTOR_NOTES = true; } else if (token == "exam_seating") { DISPLAY_EXAM_SEATING = true; - } else if (token == "moss_details") { - DISPLAY_MOSS_DETAILS = true; + } else if (token == "academic_sanction_details") { + DISPLAY_ACADEMIC_SANCTION_DETAILS = true; } else if (token == "final_grade") { DISPLAY_FINAL_GRADE = true; } else if (token == "grade_summary") { @@ -1073,7 +1073,7 @@ void processcustomizationfile(const std::string &now_string, Student *s = GetStudent(students,username); //std::cout << "USERNAME " << username << std::endl; assert (s != NULL); - s->mossify(hw,penalty); + s->academic_sanction(hw,penalty); s->set_event_academic_integrity(true); } } else if (token == "final_cutoff") { @@ -1765,7 +1765,7 @@ int main(int argc, char* argv[]) { DISPLAY_INSTRUCTOR_NOTES = false; DISPLAY_EXAM_SEATING = true; - DISPLAY_MOSS_DETAILS = false; + DISPLAY_ACADEMIC_SANCTION_DETAILS = false; DISPLAY_FINAL_GRADE = false; DISPLAY_GRADE_SUMMARY = false; DISPLAY_GRADE_DETAILS = false; diff --git a/output.cpp b/output.cpp index 862b1a4..cbb25b7 100644 --- a/output.cpp +++ b/output.cpp @@ -607,7 +607,7 @@ void start_table_output( bool /*for_instructor*/, std::cout << "DISPLAY FINAL GRADE" << std::endl; student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff","FINAL GRADE")); student_data.push_back(counter); table.set(0,counter++,TableCell(grey_divider)); - if (DISPLAY_MOSS_DETAILS) { + if (DISPLAY_ACADEMIC_SANCTION_DETAILS) { table.set(0,counter++,TableCell("ffffff","RAW GRADE")); table.set(0,counter++,TableCell(grey_divider)); } @@ -946,14 +946,14 @@ void start_table_output( bool /*for_instructor*/, if (DISPLAY_FINAL_GRADE) { std::string g = this_student->grade(false,sd); color = GradeColor(g); - if (this_student->getMossPenalty() < -0.00000001) { + if (this_student->getAcademicSanctionPenalty() < -0.00000001) { g += "@"; } assert (color.size()==6); table.set(myrow,counter++,TableCell(color,g,"",0,CELL_CONTENTS_VISIBLE,"center")); table.set(myrow,counter++,TableCell(grey_divider)); - if (DISPLAY_MOSS_DETAILS) { + if (DISPLAY_ACADEMIC_SANCTION_DETAILS) { std::string g2 = this_student->grade(true,sd); color = GradeColor(g2); table.set(myrow,counter++,TableCell(color,g2,"",0,CELL_CONTENTS_VISIBLE,"center")); @@ -1176,12 +1176,12 @@ void end_table(std::ofstream &ostr, bool for_instructor, Student *s) { ostr << "

* = 1 late day used

" << std::endl; - bool print_moss_message = false; - if (s != NULL && s->getMossPenalty() < -0.0000001) { - print_moss_message = true; + bool print_academic_sanction_message = false; + if (s != NULL && s->getAcademicSanctionPenalty() < -0.0000001) { + print_academic_sanction_message = true; } - if (print_moss_message) { + if (print_academic_sanction_message) { ostr << "@ = Academic Integrity Violation penalty

 

\n"; } diff --git a/student.cpp b/student.cpp index 211cda3..963ea3a 100644 --- a/student.cpp +++ b/student.cpp @@ -35,7 +35,7 @@ Student::Student() { rotating_section = -1; zones = std::vector(GRADEABLES[GRADEABLE_ENUM::TEST].getCount(),""); - moss_penalty = 0; + academic_sanction_penalty = 0; cached_hw = -1; // other grade-like data @@ -430,7 +430,7 @@ int Student::getUsedLateDays() const { // ============================================================================================= -float Student::overall_b4_moss() const { +float Student::overall_b4_academic_sanction() const { float answer = 0; for (unsigned int i = 0; i < ALL_GRADEABLES.size(); i++) { GRADEABLE_ENUM g = ALL_GRADEABLES[i]; @@ -439,15 +439,15 @@ float Student::overall_b4_moss() const { return answer; } -std::string Student::grade(bool flag_b4_moss, Student *lowest_d) const { +std::string Student::grade(bool flag_b4_academic_sanction, Student *lowest_d) const { if (section == "null") return ""; - if (!flag_b4_moss && manual_grade != "") return manual_grade; - + if (!flag_b4_academic_sanction && manual_grade != "") return manual_grade; + float over = overall(); - if (flag_b4_moss) { - over = overall_b4_moss(); + if (flag_b4_academic_sanction) { + over = overall_b4_academic_sanction(); } @@ -503,7 +503,7 @@ std::string Student::grade(bool flag_b4_moss, Student *lowest_d) const { -void Student::mossify(const std::string &gradeable, float penalty) { +void Student::academic_sanction(const std::string &gradeable, float penalty) { // if the penalty is "a whole or partial letter grade".... float average_letter_grade = (CUTOFFS["A"]-CUTOFFS["B"] + @@ -513,11 +513,11 @@ void Student::mossify(const std::string &gradeable, float penalty) { int item; LookupGradeable(gradeable,g,item); if (!GRADEABLES[g].hasCorrespondence(gradeable)) { - std::cerr << "WARNING -- NO GRADEABLE TO MOSSIFY" << std::endl; + std::cerr << "WARNING -- NO GRADEABLE TO ACADEMIC SANCTION" << std::endl; } else { int which = GRADEABLES[g].getCorrespondence(gradeable).first; if (!(getGradeableItemGrade(g,which).getValue() > 0)) { - std::cerr << "WARNING: the grade for this " <= 0); - moss_penalty += -0.0000002; - moss_penalty += -average_letter_grade * penalty; + academic_sanction_penalty += -0.0000002; + academic_sanction_penalty += -average_letter_grade * penalty; std::stringstream foo; foo << std::setprecision(2) << std::fixed << penalty; @@ -553,11 +553,11 @@ void Student::ManualGrade(const std::string &grade, const std::string &message) } -void Student::outputgrade(std::ostream &ostr,bool flag_b4_moss,Student *lowest_d) const { - std::string g = grade(flag_b4_moss,lowest_d); +void Student::outputgrade(std::ostream &ostr,bool flag_b4_academic_sanction,Student *lowest_d) const { + std::string g = grade(flag_b4_academic_sanction,lowest_d); std::string color = GradeColor(g); - if (moss_penalty < -0.01) { + if (academic_sanction_penalty < -0.01) { ostr << "" << g << " @"; } else { ostr << "" << g << ""; diff --git a/student.h b/student.h index 34a9a20..afa0e77 100644 --- a/student.h +++ b/student.h @@ -108,7 +108,7 @@ class Student { int getPollsIncorrect() const; float getPollPoints() const; int getUsedLateDays() const; - float getMossPenalty() const { return moss_penalty; } + float getAcademicSanctionPenalty() const { return academic_sanction_penalty; } void setCurrentAllowedLateDays(int d) { current_allowed_late_days = d; } void setDefaultAllowedLateDays(int d) { default_allowed_late_days = d; } @@ -176,7 +176,7 @@ class Student { void setGradeableItemGrade_AcademicIntegrity(GRADEABLE_ENUM g, int i, float value, bool academic_integrity, int late_days_used=0, const std::string ¬e="",const std::string &status=""); void setGradeableItemGrade_border(GRADEABLE_ENUM g, int i, float value, const std::string &event="", int late_days_used=0, const std::string ¬e="",const std::string &status=""); - void mossify(const std::string &gradeable, float penalty); + void academic_sanction(const std::string &gradeable, float penalty); void set_event_academic_integrity(bool value) {academic_integrity = value;} void set_event_grade_inquiry(bool value) {grade_inquiry = value;} @@ -217,14 +217,14 @@ class Student { // HELPER FUNCTIONS float GradeablePercent(GRADEABLE_ENUM g) const; - float overall() const { return overall_b4_moss() + moss_penalty; } + float overall() const { return overall_b4_academic_sanction() + academic_sanction_penalty; } float adjusted_test(int i) const; float adjusted_test_pct() const; float lowest_test_counts_half_pct() const; float quiz_normalize_and_drop(int num) const; - float overall_b4_moss() const; - std::string grade(bool flag_b4_moss, Student *lowest_d) const; - void outputgrade(std::ostream &ostr,bool flag_b4_moss,Student *lowest_d) const; + float overall_b4_academic_sanction() const; + std::string grade(bool flag_b4_academic_sanction, Student *lowest_d) const; + void outputgrade(std::ostream &ostr,bool flag_b4_academic_sanction,Student *lowest_d) const; private: @@ -260,7 +260,7 @@ class Student { std::map > all_item_grades; std::vector zones; - float moss_penalty; + float academic_sanction_penalty; float cached_hw; int rank; From 4b9121948c0f2d7ee66b6084217d375134c34368 Mon Sep 17 00:00:00 2001 From: Jaeseok Kang <123261952+ziesski@users.noreply.github.com> Date: Tue, 28 Nov 2023 00:50:52 -0500 Subject: [PATCH 03/11] [Refactor:RainbowGrades] Student.cpp tolerance (#64) Forgot one print debugging statement, and tolerance padding for assertion was missing (Related #62 ) --------- Co-authored-by: Konstantin Kuzmin Co-authored-by: Barb Cutler --- .gitignore | 1 + .idea/.gitignore | 8 ++++++++ .idea/RainbowGrades.iml | 8 ++++++++ .idea/modules.xml | 8 ++++++++ .idea/vcs.xml | 6 ++++++ student.cpp | 5 ++--- 6 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/RainbowGrades.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore index 27dda55..a48374f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ *.log *.out *.synctex.gz +/.idea/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/RainbowGrades.iml b/.idea/RainbowGrades.iml new file mode 100644 index 0000000..bc2cd87 --- /dev/null +++ b/.idea/RainbowGrades.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c0c2bfc --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/student.cpp b/student.cpp index 963ea3a..8f4a529 100644 --- a/student.cpp +++ b/student.cpp @@ -272,7 +272,8 @@ float Student::GradeablePercent(GRADEABLE_ENUM g) const { sum_percentage += p; } } - assert(sum_percentage <= 1.0); + const float tolerance = 0.000001; + assert(sum_percentage <= 1.0 + tolerance); if (sum_max == 0) { // pure extra credit category sum_percentage = 1.0; } @@ -442,9 +443,7 @@ float Student::overall_b4_academic_sanction() const { std::string Student::grade(bool flag_b4_academic_sanction, Student *lowest_d) const { if (section == "null") return ""; - if (!flag_b4_academic_sanction && manual_grade != "") return manual_grade; - float over = overall(); if (flag_b4_academic_sanction) { over = overall_b4_academic_sanction(); From 770525e3271681ccf191b016f2cd2114e8d36328 Mon Sep 17 00:00:00 2001 From: Jaeseok Kang <123261952+ziesski@users.noreply.github.com> Date: Tue, 12 Dec 2023 18:07:44 -0800 Subject: [PATCH 04/11] [RainbowGrades:Feature] add display options (#66) Currently in web-based customization page, we have display option, which is acts like a switch to enable and disable contents that needs to be displayed to students. There are currently 7 options, which are available, but we will expand these features to 14 options. It is a valid checkbox on the Web GUI, but RainbowGrades cannot take the new 7 options, and will silently break if the new options are checked. Hence, this PR will fix it by adding a placeholder to handle additional tokens in display options. --- main.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/main.cpp b/main.cpp index dc70565..3ade731 100644 --- a/main.cpp +++ b/main.cpp @@ -775,12 +775,26 @@ void preprocesscustomizationfile(const std::string &now_string, DISPLAY_GRADE_DETAILS = true; } else if (token == "display_rank_to_individual"){ DISPLAY_RANK_TO_INDIVIDUAL = true; + } else if (token == "display_benchmark") { + continue; + } else if (token == "benchmark_percent") { + continue; + } else if (token == "section") { + continue; + } else if (token == "messages") { + continue; + } else if (token == "warning") { + continue; + } else if (token == "manual_grade"){ + continue; + } else if (token == "final_cutoff"){ + continue; } else { std::cout << "OOPS " << token << std::endl; exit(0); } } - + //std::cout << "4" << std::endl; // Set Display Benchmark From 93885d58e19cb138e35dc7cb091145898232464c Mon Sep 17 00:00:00 2001 From: Jaeseok Kang <123261952+ziesski@users.noreply.github.com> Date: Tue, 12 Dec 2023 18:08:53 -0800 Subject: [PATCH 05/11] [Refactor:RainbowGrades] add back moss_detail (#65) This change will allow hand written configs to accept both `academic_sanction_details` and `moss_details` --- main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.cpp b/main.cpp index 3ade731..7df3183 100644 --- a/main.cpp +++ b/main.cpp @@ -765,7 +765,7 @@ void preprocesscustomizationfile(const std::string &now_string, DISPLAY_INSTRUCTOR_NOTES = true; } else if (token == "exam_seating") { DISPLAY_EXAM_SEATING = true; - } else if (token == "academic_sanction_details") { + } else if (token == "academic_sanction_details" || token == "moss_details") { DISPLAY_ACADEMIC_SANCTION_DETAILS = true; } else if (token == "final_grade") { DISPLAY_FINAL_GRADE = true; From c788ffce08d004e6e38c6022ef66c9bf6955ad25 Mon Sep 17 00:00:00 2001 From: Thomas Orifici <143554378+taorif25@users.noreply.github.com> Date: Tue, 12 Dec 2023 22:01:10 -0500 Subject: [PATCH 06/11] [Feature:RainbowGrades] Adds Legend to Instructor Gradebook (#67) Certain gradeables have colored outlines to represent different statuses. Currently, the instructor gradebook does not provide a legend for these statuses. This PR adds that legend. New Behavior: ![image](https://github.com/Submitty/RainbowGrades/assets/143554378/64a7eb05-9bbe-4557-b176-fc152db42c95) closes [#9764](https://github.com/Submitty/Submitty/issues/9674) --- output.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/output.cpp b/output.cpp index cbb25b7..c181863 100644 --- a/output.cpp +++ b/output.cpp @@ -1186,13 +1186,13 @@ void end_table(std::ofstream &ostr, bool for_instructor, Student *s) { } // Description of border outline that are in effect - if (s != NULL) + if (for_instructor || s != NULL) { - if (s->get_event_bad_status() || s->get_event_grade_inquiry() || s->get_event_overridden() || s->get_event_academic_integrity()) + if (for_instructor || (s != NULL && (s->get_event_bad_status() || s->get_event_grade_inquiry() || s->get_event_overridden() || s->get_event_academic_integrity()))) { ostr << "\n"; ostr << "\n"; - if (s->get_event_academic_integrity()) + if (for_instructor || (s != NULL && s->get_event_academic_integrity())) { ostr << "\n"; ostr << ""; ostr << "\n"; } - if (s->get_event_overridden()) + if (for_instructor || (s != NULL && s->get_event_overridden())) { ostr << "\n"; ostr << ""; ostr << "\n"; } - if (s->get_event_grade_inquiry()) + if (for_instructor || (s != NULL && s->get_event_grade_inquiry())) { ostr << "\n"; ostr << ""; ostr << "\n"; } - if (s->get_event_bad_status()) + if (for_instructor || (s != NULL && s->get_event_bad_status())) { ostr << "\n"; ostr << "
"; @@ -1203,7 +1203,7 @@ void end_table(std::ofstream &ostr, bool for_instructor, Student *s) { ostr << "
"; @@ -1214,7 +1214,7 @@ void end_table(std::ofstream &ostr, bool for_instructor, Student *s) { ostr << "
"; @@ -1225,7 +1225,7 @@ void end_table(std::ofstream &ostr, bool for_instructor, Student *s) { ostr << "
"; From ae4d4dd77a28042aa0a75a197be5eabbc6a3d497 Mon Sep 17 00:00:00 2001 From: Thomas Orifici <143554378+taorif25@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:38:24 -0500 Subject: [PATCH 07/11] [Feature:RainbowGrades] Display Extensions in RainbowGrades (#63) Previously, no information on a student's late day exceptions were provided in rainbow grades. Now, all excused extension information is collected and stored in each student object. A row was added to the main rainbow grades table to display a student's total number of extensions, and a breakout table was added underneath the main table to display information about each individual extension. Additonally, a blue outline was added to table cell with excused extensions and text giving extension information is listed on hover. Picture of changes to tables: ![image](https://github.com/Submitty/RainbowGrades/assets/143554378/fa8ab970-20bb-4382-a8c0-a1baf664297c) Hover Feature: ![image](https://github.com/Submitty/RainbowGrades/assets/143554378/cf243ae3-5f67-4f19-b379-77f791e44a51) --------- Co-authored-by: Jaeseok Kang Co-authored-by: Barb Cutler --- main.cpp | 39 ++++++++++++++++++++++++++++++++++++++- output.cpp | 36 ++++++++++++++++++++++++++++++++---- student.cpp | 32 +++++++++++++++++++++++++++++--- student.h | 15 +++++++++++++-- table.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++++--------- table.h | 5 ++++- 6 files changed, 160 insertions(+), 20 deletions(-) diff --git a/main.cpp b/main.cpp index 7df3183..f4ebd26 100644 --- a/main.cpp +++ b/main.cpp @@ -1387,7 +1387,13 @@ void load_student_grades(std::vector &students) { event = "Open"; s->set_event_grade_inquiry(true); } - s->setGradeableItemGrade_border(g,which,score,event,late_days_charged,other_note,status); + int late_day_exceptions = itr2->value("late_day_exceptions",0); + std::string reason_for_exception = itr2->value("reason_for_exception",""); + if (late_day_exceptions > 0) { + event = "Extension"; + s->set_event_extension(true); + } + s->setGradeableItemGrade_border(g,which,score,event,late_days_charged,other_note,status,late_day_exceptions,reason_for_exception); } } @@ -1736,6 +1742,35 @@ void loadAllowedLateDays(std::vector &students) { } } +void SaveExtensionReports(const std::vector &students) { + system ("mkdir -p student_extension_reports"); + for (size_t i=0;i > > gradeablesWithExtensions = students[i]->getItemsWithExceptions(); + std::string username = students[i]->getUserName(); + std::ofstream student_ostr("student_extension_reports/"+username+".html"); + assert (student_ostr.good()); + if (gradeablesWithExtensions.size() == 0) { + continue; + } + student_ostr << "

Excused Absence Extensions for: " << username << "

" << std::endl; + student_ostr << "" << std::endl; + student_ostr << "" << std::endl; + for (size_t i2=0;i2(gradeablesWithExtensions[i2]); + GRADEABLE_ENUM g = std::get<0>(std::get<1>(gradeablesWithExtensions[i2])); + int index = std::get<1>(std::get<1>(gradeablesWithExtensions[i2])); + std::string gradeable_id = GRADEABLES[g].getID(index); + std::string gradeable_name = ""; + if (GRADEABLES[g].hasCorrespondence(gradeable_id)) { + gradeable_name = GRADEABLES[g].getCorrespondence(gradeable_id).second; + } + student_ostr << "" << std::endl; + } + student_ostr << "
GradeableDays ExtendedReason
" << gradeable_name << "" + << item.getLateDayExceptions() << "" + << item.getReasonForException() << "
" << std::endl; + } +} int main(int argc, char* argv[]) { @@ -1757,6 +1792,8 @@ int main(int argc, char* argv[]) { LoadPolls(students); SavePollReports(students); + SaveExtensionReports(students); + loadAllowedLateDays(students); // ====================================================================== diff --git a/output.cpp b/output.cpp index c181863..deb3ef5 100644 --- a/output.cpp +++ b/output.cpp @@ -677,6 +677,7 @@ void start_table_output( bool /*for_instructor*/, //Late days headers student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff","ALLOWED LATE DAYS")); student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff","USED LATE DAYS")); + student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff","EXCUSED EXTENSIONS")); student_data.push_back(counter); table.set(0,counter++,TableCell(grey_divider)); } } @@ -1033,12 +1034,17 @@ void start_table_output( bool /*for_instructor*/, std::string status = this_student->getGradeableItemGrade(g,j).getStatus(); std::string event = this_student->getGradeableItemGrade(g,j).getEvent(); bool Academic_integrity = this_student->getGradeableItemGrade(g,j).getAcademicIntegrity(); + std::string reason = this_student->getGradeableItemGrade(g,j).getReasonForException(); + std::string gID = GRADEABLES[g].getID(j); + std::string userName = this_student->getUserName(); if (status.find("Bad") != std::string::npos) { details += " " + status; } int late_days_used = this_student->getGradeableItemGrade(g,j).getLateDaysUsed(); + int daysExtended = this_student->getGradeableItemGrade(g,j).getLateDayExceptions(); assert (color.size()==6); - table.set(myrow,counter++,TableCell(grade,color,1,details,late_days_used,visible,event,Academic_integrity)); + std::string a = "right"; + table.set(myrow,counter++,TableCell(grade,color,1,details,late_days_used,visible,event,Academic_integrity,a,1,0,reason,gID,userName,daysExtended)); } table.set(myrow,counter++,TableCell(grey_divider)); @@ -1081,6 +1087,9 @@ void start_table_output( bool /*for_instructor*/, int used = this_student->getUsedLateDays(); color = coloritcolor(allowed-used+2, 5+2, 3+2, 2+2, 1+2, 0+2); table.set(myrow,counter++,TableCell(color,used,"",0,CELL_CONTENTS_VISIBLE,"right")); + int exceptions = this_student->getLateDayExceptions(); + color = coloritcolor(exceptions,5,4,3,2,2); + table.set(myrow,counter++,TableCell(color,exceptions,"",0,CELL_CONTENTS_VISIBLE,"right")); } else { color="ffffff"; // default_color; table.set(myrow,counter++,TableCell(color,"")); @@ -1089,6 +1098,7 @@ void start_table_output( bool /*for_instructor*/, table.set(myrow,counter++,TableCell(color,"")); table.set(myrow,counter++,TableCell(color,"")); table.set(myrow,counter++,TableCell(color,"")); + table.set(myrow,counter++,TableCell(color,"")); } table.set(myrow,counter++,TableCell(grey_divider)); } @@ -1188,7 +1198,7 @@ void end_table(std::ofstream &ostr, bool for_instructor, Student *s) { // Description of border outline that are in effect if (for_instructor || s != NULL) { - if (for_instructor || (s != NULL && (s->get_event_bad_status() || s->get_event_grade_inquiry() || s->get_event_overridden() || s->get_event_academic_integrity()))) + if (for_instructor || (s != NULL && (s->get_event_bad_status() || s->get_event_grade_inquiry() || s->get_event_overridden() || s->get_event_academic_integrity() || s->get_event_extension()))) { ostr << "\n"; ostr << "\n"; @@ -1210,7 +1220,18 @@ void end_table(std::ofstream &ostr, bool for_instructor, Student *s) { ostr << ""; ostr << ""; ostr << ""; + ostr << "\n"; + } + if (for_instructor || (s != NULL && s->get_event_extension())) + { + ostr << "\n"; + ostr << ""; + ostr << ""; ostr << "\n"; } @@ -1221,7 +1242,7 @@ void end_table(std::ofstream &ostr, bool for_instructor, Student *s) { ostr << ""; ostr << ""; ostr << ""; ostr << "\n"; } @@ -1243,6 +1264,13 @@ void end_table(std::ofstream &ostr, bool for_instructor, Student *s) { if (s != NULL) { + std::ifstream istr2("student_extension_reports/"+s->getUserName()+".html"); + if (istr2.good()) { + std::string tmp_s; + while (getline(istr2,tmp_s)) { + ostr << tmp_s; + } + } std::ifstream istr("student_poll_reports/"+s->getUserName()+".html"); if (istr.good()) { std::string tmp_s; diff --git a/student.cpp b/student.cpp index 8f4a529..05e6ed0 100644 --- a/student.cpp +++ b/student.cpp @@ -106,13 +106,15 @@ void Student::setGradeableItemGrade_AcademicIntegrity(GRADEABLE_ENUM g, int i, f itr->second[i] = ItemGrade(value,late_days_used,note,status,temp,academic_integrity); } -void Student::setGradeableItemGrade_border(GRADEABLE_ENUM g, int i, float value, const std::string &event, int late_days_used, const std::string ¬e, const std::string &status) { +void Student::setGradeableItemGrade_border(GRADEABLE_ENUM g, int i, float value, const std::string &event, + int late_days_used, const std::string ¬e, const std::string &status, int exceptions, const std::string &reason) { assert (i >= 0 && i < GRADEABLES[g].getCount()); std::map >::iterator itr = all_item_grades.find(g); assert (itr != all_item_grades.end()); assert (int(itr->second.size()) > i); + bool academic_integrity = false; - itr->second[i] = ItemGrade(value,late_days_used,note,status,event); + itr->second[i] = ItemGrade(value,late_days_used,note,status,event,academic_integrity,exceptions,reason); } @@ -211,7 +213,7 @@ float Student::GradeablePercent(GRADEABLE_ENUM g) const { //if(!id.empty() && GRADEABLES[g].isReleased(id)){ if(!id.empty()){ m = GRADEABLES[g].getItemMaximum(id); - std::cout << "m" << m << std::endl; + // std::cout << "m" << m << std::endl; } float p = GRADEABLES[g].getItemPercentage(id); float sm = GRADEABLES[g].getScaleMaximum(id); @@ -429,6 +431,30 @@ int Student::getUsedLateDays() const { return answer; } + int Student::getLateDayExceptions() const { + int answer = 0; + for (std::map >::const_iterator itr = all_item_grades.begin(); itr != all_item_grades.end(); itr++) { + for (std::size_t i = 0; i < itr->second.size(); i++) { + answer += itr->second[i].getLateDayExceptions(); + } + } + return answer; + } + + std::vector > > Student::getItemsWithExceptions() const { + std::vector > > result; + for (std::map >::const_iterator itr = all_item_grades.begin(); itr != all_item_grades.end(); itr++) { + for (std::size_t i = 0; i < itr->second.size(); i++) { + if (itr->second[i].getLateDayExceptions()>0) { + std::tuple indexes{itr->first,i}; + std::tuple > itemInfo{itr->second[i],indexes}; + result.push_back(itemInfo); + } + } + } + return result; + } + // ============================================================================================= float Student::overall_b4_academic_sanction() const { diff --git a/student.h b/student.h index afa0e77..e34f42e 100644 --- a/student.h +++ b/student.h @@ -21,12 +21,14 @@ extern std::vector GLOBAL_earned_late_days; class ItemGrade { public: - ItemGrade(float v, int ldu=0, const std::string& n="", const std::string &s="", const std::string &e="", bool ai=false) { + ItemGrade(float v, int ldu=0, const std::string& n="", const std::string &s="", const std::string &e="", bool ai=false, int de=0, const std::string& r="") { value = v; late_days_used = ldu; note = n; event = e; academic_integrity = ai; + late_day_exceptions = de; + reason_for_exception = r; if (s != "UNKONWN") { status = s; @@ -55,6 +57,8 @@ class ItemGrade { return adjusted_value; } int getLateDaysUsed() const { return late_days_used; } + int getLateDayExceptions() const { return late_day_exceptions; } + const std::string& getReasonForException() const { return reason_for_exception; } const std::string& getNote() const { return note; } const std::string& getStatus() const { return status; } const std::string& getEvent() const { return event; } @@ -63,10 +67,12 @@ class ItemGrade { private: float value; int late_days_used; + int late_day_exceptions; bool academic_integrity; std::string note; std::string status; std::string event; + std::string reason_for_exception; }; //==================================================================== @@ -108,6 +114,8 @@ class Student { int getPollsIncorrect() const; float getPollPoints() const; int getUsedLateDays() const; + int getLateDayExceptions() const; + std::vector > > getItemsWithExceptions() const; float getAcademicSanctionPenalty() const { return academic_sanction_penalty; } void setCurrentAllowedLateDays(int d) { current_allowed_late_days = d; } @@ -174,7 +182,7 @@ class Student { void setTestZone(int which_test, const std::string &zone) { zones[which_test] = zone; } void setGradeableItemGrade(GRADEABLE_ENUM g, int i, float value, int late_days_used=0, const std::string ¬e="",const std::string &status=""); void setGradeableItemGrade_AcademicIntegrity(GRADEABLE_ENUM g, int i, float value, bool academic_integrity, int late_days_used=0, const std::string ¬e="",const std::string &status=""); - void setGradeableItemGrade_border(GRADEABLE_ENUM g, int i, float value, const std::string &event="", int late_days_used=0, const std::string ¬e="",const std::string &status=""); + void setGradeableItemGrade_border(GRADEABLE_ENUM g, int i, float value, const std::string &event="", int late_days_used=0, const std::string ¬e="",const std::string &status="",int exceptions=0, const std::string &reason=""); void academic_sanction(const std::string &gradeable, float penalty); @@ -182,10 +190,12 @@ class Student { void set_event_grade_inquiry(bool value) {grade_inquiry = value;} void set_event_overridden(bool value) {overridden = value;} void set_event_bad_status(bool value) {bad_status = value;} + void set_event_extension(bool value) {extension = value;} bool get_event_academic_integrity() {return academic_integrity;} bool get_event_grade_inquiry() {return grade_inquiry;} bool get_event_overridden() {return overridden;} bool get_event_bad_status() {return bad_status;} + bool get_event_extension() {return extension;} // other grade-like data void setNumericID(const std::string& r_id) { numeric_id = r_id; } @@ -247,6 +257,7 @@ class Student { bool grade_inquiry = false; bool overridden = false; bool bad_status = false; + bool extension = false; // registration status std::string section; diff --git a/table.cpp b/table.cpp index 09e2e74..23926f4 100644 --- a/table.cpp +++ b/table.cpp @@ -74,7 +74,8 @@ TableCell::TableCell(const std::string& c, float d, int precision, const std::st TableCell::TableCell(float d, const std::string& c, int precision, const std::string& n, int ldu, - CELL_CONTENTS_STATUS v,const std::string& e,bool ai, const std::string& a, int s, int /*r*/) { + CELL_CONTENTS_STATUS v,const std::string& e,bool ai, const std::string& a, + int s, int /*r*/,const std::string& reason,const std::string& gID,const std::string& userName, int daysExtended) { assert (c.size() == 6); assert (precision >= 0); color=c; @@ -93,17 +94,24 @@ TableCell::TableCell(float d, const std::string& c, int precision, const std::st rotate = 0; academic_integrity = ai; event = e; + if (reason != "") { + hoverText = "class=\"hoverable-cell\" data-hover-text=\""+userName+" received a "+std::to_string(daysExtended)+" day extension due to "+reason+" on "+gID+"\" "; + } + else hoverText = ""; if (event == "Bad"){ bad_status = true; - override = inquiry = false; + override = inquiry = extension = false; } else if ( event == "Overridden"){ override = true; - bad_status = inquiry = false; + bad_status = inquiry = extension = false; } else if (event == "Open"){ inquiry = true; - bad_status = override = false; + bad_status = override = extension = false; + } else if (event == "Extension"){ + extension = true; + inquiry = bad_status = override = false; } else { - inquiry = bad_status = override = false; + inquiry = bad_status = override = extension = false; } } @@ -120,15 +128,15 @@ std::ostream& operator<<(std::ostream &ostr, const TableCell &c) { mark = "@"; } else if (c.override){ outline = "outline:4px solid #fcca03; outline-offset: -4px;"; + } else if (c.extension){ + outline = "outline:4px solid #0066e0; outline-offset: -4px;"; } else if (c.inquiry){ outline = "outline:4px dashed #1cfc03; outline-offset: -4px;"; } else if (c.bad_status){ outline = "outline:4px solid #fc0303; outline-offset: -4px;"; } - // ostr << "
"; - ostr << " Grade override "; + ostr << " Grade Override "; + ostr << "
"; + ostr << ""; + ostr << ""; + ostr << " Excused Absence Extension "; ostr << "
"; - ostr << " Grade inquiry in progress "; + ostr << " Grade Inquiry in Progress "; ostr << "
"; - ostr << ""; - + ostr << ""; if (0) { //rotate == 90) { ostr << "

"; } @@ -236,10 +244,37 @@ void Table::output(std::ostream& ostr, if (last_update != "") { ostr << "Information last updated: " << last_update << "
\n"; } + + ostr << ""; + + ostr << " 
\n"; ostr << "\n"; } - + + + if (transpose) { for (std::vector::iterator c = which_data.begin(); c != which_data.end(); c++) { ostr << "\n"; diff --git a/table.h b/table.h index e81e6b2..ad07ac4 100644 --- a/table.h +++ b/table.h @@ -24,7 +24,8 @@ class TableCell { TableCell(const std::string& c , float d , int precision, const std::string& n="", int ldu=0, CELL_CONTENTS_STATUS v=CELL_CONTENTS_VISIBLE, const std::string& a="right", int s=1, int r=0); TableCell(float d ,const std::string& c , int precision, const std::string& n="", int ldu=0, - CELL_CONTENTS_STATUS v=CELL_CONTENTS_VISIBLE, const std::string& e="", bool ai = false, const std::string& a="right", int s=1, int r=0); + CELL_CONTENTS_STATUS v=CELL_CONTENTS_VISIBLE, const std::string& e="", bool ai = false, const std::string& a="right", + int s=1, int r=0, const std::string& reason="",const std::string& gID="",const std::string& userName="",int daysExtended=0); std::string make_cell_string(bool csv_mode) const; std::string color; @@ -35,6 +36,8 @@ class TableCell { bool inquiry = false; bool bad_status = false; bool override = false; + bool extension = false; + std::string hoverText = ""; std::string align; enum CELL_CONTENTS_STATUS visible; int span; From d46107d0d5d15a7ecea22403274fdf2fd8f06c38 Mon Sep 17 00:00:00 2001 From: Jaeseok Kang <123261952+ziesski@users.noreply.github.com> Date: Wed, 13 Dec 2023 03:35:02 -0800 Subject: [PATCH 08/11] [Feature:RainbowGrades] Version conflict borderline (#69) The new change will now include version conflict in request of @KCony This is related to /Submitty/Submitty/issues/9674 And has connected PR in /Submitty/Submitty/pull/10063 If there is a conflict between TA graded version and Automated version, we call it version conflict. The current border-outline does not indicate if it is One corner case I can mention is that version conflict will also count canceled submission. Screen Shot 2023-12-13 at 3 33 52 AMScreen Shot 2023-12-13 at 2 43 27 AM --- main.cpp | 16 +++++++++++++--- output.cpp | 32 ++++++++++++++++++++++++++++++++ student.h | 21 +++++++++++++++------ table.cpp | 47 ++++++++++++++++++++++++++++++++++------------- table.h | 7 +++++-- 5 files changed, 99 insertions(+), 24 deletions(-) diff --git a/main.cpp b/main.cpp index f4ebd26..eb33b3d 100644 --- a/main.cpp +++ b/main.cpp @@ -1377,9 +1377,14 @@ void load_student_grades(std::vector &students) { event = "Bad"; s->set_event_bad_status(true); } - if (status_check == "Overridden") { - event = "Overridden"; - s->set_event_overridden(true); + std::string version_conflict = itr2->value("version_conflict", ""); + if (version_conflict == "true") { + event = "Version_conflict"; + s->set_event_version_conflict(true); + } + if (status_check == "Cancelled") { + event = "Cancelled"; + s->set_event_cancelled(true); } std::string inquiry = itr2->value("inquiry", ""); if ((inquiry != "None") && (inquiry != "Resolved") && (inquiry != "")) { @@ -1393,6 +1398,11 @@ void load_student_grades(std::vector &students) { event = "Extension"; s->set_event_extension(true); } + // Above itr2 status check, but in order of priority + if (status_check == "Overridden") { + event = "Overridden"; + s->set_event_overridden(true); + } s->setGradeableItemGrade_border(g,which,score,event,late_days_charged,other_note,status,late_day_exceptions,reason_for_exception); } } diff --git a/output.cpp b/output.cpp index deb3ef5..26015bd 100644 --- a/output.cpp +++ b/output.cpp @@ -1202,6 +1202,15 @@ void end_table(std::ofstream &ostr, bool for_instructor, Student *s) { { ostr << "\n"; ostr << "
\n"; + ostr << "\n"; + ostr << ""; + ostr << ""; + ostr << "\n"; if (for_instructor || (s != NULL && s->get_event_academic_integrity())) { ostr << "\n"; @@ -1246,6 +1255,29 @@ void end_table(std::ofstream &ostr, bool for_instructor, Student *s) { ostr << ""; ostr << "\n"; } + if (for_instructor || (s != NULL && s->get_event_cancelled())) + { + ostr << "\n"; + ostr << ""; + ostr << ""; + ostr << "\n"; + } + if (for_instructor || (s != NULL && s->get_event_version_conflict())) + { + ostr << "\n"; + ostr << ""; + ostr << ""; + ostr << "\n"; + } if (for_instructor || (s != NULL && s->get_event_bad_status())) { ostr << "\n"; diff --git a/student.h b/student.h index e34f42e..21e322f 100644 --- a/student.h +++ b/student.h @@ -186,16 +186,23 @@ class Student { void academic_sanction(const std::string &gradeable, float penalty); + //set in order of priority - top to bottom void set_event_academic_integrity(bool value) {academic_integrity = value;} - void set_event_grade_inquiry(bool value) {grade_inquiry = value;} void set_event_overridden(bool value) {overridden = value;} - void set_event_bad_status(bool value) {bad_status = value;} void set_event_extension(bool value) {extension = value;} + void set_event_grade_inquiry(bool value) {grade_inquiry = value;} + void set_event_cancelled(bool value) {cancelled = value;} + void set_event_version_conflict(bool value) {version_conflict = value;} + void set_event_bad_status(bool value) {bad_status = value;} + + //bool in order of priority - top to bottom bool get_event_academic_integrity() {return academic_integrity;} - bool get_event_grade_inquiry() {return grade_inquiry;} bool get_event_overridden() {return overridden;} - bool get_event_bad_status() {return bad_status;} bool get_event_extension() {return extension;} + bool get_event_grade_inquiry() {return grade_inquiry;} + bool get_event_cancelled() {return cancelled;} + bool get_event_version_conflict() {return version_conflict;} + bool get_event_bad_status() {return bad_status;} // other grade-like data void setNumericID(const std::string& r_id) { numeric_id = r_id; } @@ -254,10 +261,12 @@ class Student { int current_allowed_late_days; int default_allowed_late_days; bool academic_integrity = false; - bool grade_inquiry = false; bool overridden = false; - bool bad_status = false; bool extension = false; + bool grade_inquiry = false; + bool version_conflict = false; + bool cancelled = false; + bool bad_status = false; // registration status std::string section; diff --git a/table.cpp b/table.cpp index 23926f4..e0e2d03 100644 --- a/table.cpp +++ b/table.cpp @@ -94,24 +94,35 @@ TableCell::TableCell(float d, const std::string& c, int precision, const std::st rotate = 0; academic_integrity = ai; event = e; + if (reason != "") { hoverText = "class=\"hoverable-cell\" data-hover-text=\""+userName+" received a "+std::to_string(daysExtended)+" day extension due to "+reason+" on "+gID+"\" "; + } else { + hoverText = "class=\"hoverable-cell\" data-hover-text=\""+userName+" received a "+std::to_string(daysExtended)+" day extension without specified reason on "+gID+"\" "; } - else hoverText = ""; - if (event == "Bad"){ - bad_status = true; - override = inquiry = extension = false; - } else if ( event == "Overridden"){ + +// Bool in order of priority - top to bottom +// Don't think we need this logic, but leaving it as sort of assert + if (event == "Overridden"){ override = true; - bad_status = inquiry = extension = false; - } else if (event == "Open"){ - inquiry = true; - bad_status = override = extension = false; + bad_status = inquiry = extension = version_conflict = cancelled = false; } else if (event == "Extension"){ extension = true; - inquiry = bad_status = override = false; + inquiry = bad_status = override = version_conflict = cancelled = false; + } else if (event == "Open"){ + inquiry = true; + bad_status = override = extension = version_conflict = cancelled = false; + } else if (event == "Cancelled"){ + cancelled = true; + inquiry = bad_status = override = extension = version_conflict = false; + } else if (event == "Version_conflict"){ + version_conflict = true; + inquiry = bad_status = override = extension = cancelled = false; + } else if (event == "Bad"){ + bad_status = true; + override = inquiry = extension = version_conflict = cancelled = false; } else { - inquiry = bad_status = override = extension = false; + inquiry = bad_status = override = extension = version_conflict = cancelled = false; } } @@ -132,11 +143,21 @@ std::ostream& operator<<(std::ostream &ostr, const TableCell &c) { outline = "outline:4px solid #0066e0; outline-offset: -4px;"; } else if (c.inquiry){ outline = "outline:4px dashed #1cfc03; outline-offset: -4px;"; + } else if (c.cancelled){ + outline = "outline:4px dashed #0a0a0a; outline-offset: -4px;"; + } else if (c.version_conflict){ + outline = "outline:4px dashed #fc0303; outline-offset: -4px;"; } else if (c.bad_status){ outline = "outline:4px solid #fc0303; outline-offset: -4px;"; } - - ostr << "
"; + ostr << ""; + ostr << ""; + ostr << " Border-Outline is ranked from top to bottom
"; + ostr << "Higher ranked outline will over-write
"; + ostr << "
"; + ostr << ""; + ostr << ""; + ostr << " Cancelled submission "; + ostr << "
"; + ostr << ""; + ostr << ""; + ostr << " Version conflict = version conflict between
"; + ostr << "           TA graded version and Active version
"; + ostr << "
"; + + if (c.extension){ + ostr << ""; + } else { + ostr << ""; + + } + if (0) { //rotate == 90) { ostr << "

"; } diff --git a/table.h b/table.h index ad07ac4..8159cd2 100644 --- a/table.h +++ b/table.h @@ -32,11 +32,14 @@ class TableCell { std::string data; std::string event; int late_days_used; + // Bool in order of priority - top to bottom bool academic_integrity = false; - bool inquiry = false; - bool bad_status = false; bool override = false; bool extension = false; + bool inquiry = false; + bool cancelled = false; + bool version_conflict = false; + bool bad_status = false; std::string hoverText = ""; std::string align; enum CELL_CONTENTS_STATUS visible; From 69041bba6fddf7604c0c7d7dd3119bb22d0d9c93 Mon Sep 17 00:00:00 2001 From: Jaeseok Kang <123261952+ziesski@users.noreply.github.com> Date: Wed, 13 Dec 2023 03:44:06 -0800 Subject: [PATCH 09/11] [Refactor:RainbowGrades] remove explain table (#70) It will remove a table with explanation related to #69 Barb hit merge 1 second before I remove above line --- output.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/output.cpp b/output.cpp index 26015bd..56760fd 100644 --- a/output.cpp +++ b/output.cpp @@ -1202,15 +1202,6 @@ void end_table(std::ofstream &ostr, bool for_instructor, Student *s) { { ostr << "\n"; ostr << "\n"; - ostr << "\n"; - ostr << ""; - ostr << ""; - ostr << "\n"; if (for_instructor || (s != NULL && s->get_event_academic_integrity())) { ostr << "\n"; From a7455989a51d5796fc133d09b6af7c707a7651da Mon Sep 17 00:00:00 2001 From: Barb Cutler Date: Tue, 30 Apr 2024 10:24:38 -0700 Subject: [PATCH 10/11] [Feature:Developer] add title check workflow (#73) Co-authored-by: Barb Cutler --- .github/workflows/pr_title_check.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/pr_title_check.yml diff --git a/.github/workflows/pr_title_check.yml b/.github/workflows/pr_title_check.yml new file mode 100644 index 0000000..5bbd9ac --- /dev/null +++ b/.github/workflows/pr_title_check.yml @@ -0,0 +1,20 @@ +name: 'Submitty PR Title Check' +on: + pull_request: + # check when PR + # * is created, + # * title is edited, and + # * new commits are added (to ensure failing title blocks merging) + types: [opened, reopened, edited, synchronize] + +jobs: + title-check: + runs-on: ubuntu-latest + steps: + # + # pull request titles format rules here: + # https://submitty.org/developer/how_to_contribute#how-to-make-a-pull-request-pr-to-submitty + # + # [:] + # + - uses: submitty/action-pr-title@main From 9c672475a80071ec4bd77cadf02bcd9b0d63af91 Mon Sep 17 00:00:00 2001 From: Barb Cutler Date: Tue, 30 Apr 2024 11:15:13 -0700 Subject: [PATCH 11/11] [Bugfix:RainbowGrades] replace poll status with end_date (#72) Submini polls now feature an option to use a timer to automatically close/end the poll https://github.com/Submitty/Submitty/pull/10184 This PR updates the parsing of the output files from generate grade summaries to handle both the old format ("status") and the new format ("end_date") Note: The end_date does not currently contain a timestamp. This code may need further revision if/when that is added. --------- Co-authored-by: Barb Cutler --- submini_polls.cpp | 65 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/submini_polls.cpp b/submini_polls.cpp index 72d59a8..fb835bf 100644 --- a/submini_polls.cpp +++ b/submini_polls.cpp @@ -80,6 +80,18 @@ std::string convert_date(const std::string &date) { return out.str(); } +std::string getToday() { + static std::chrono::time_point now = std::chrono::system_clock::now(); + time_t tt = std::chrono::system_clock::to_time_t(now); + tm local_tm = *localtime(&tt); + std::stringstream ss; + ss << local_tm.tm_year + 1900 << "-" + << std::setw(2) << std::setfill('0') << local_tm.tm_mon + 1 << "-" + << std::setw(2) << std::setfill('0') << local_tm.tm_mday; + std::string today_string = ss.str(); + return today_string; +} + // ======================================================================================= // ======================================================================================= // Load all poll data @@ -101,6 +113,7 @@ void LoadPolls(const std::vector &students) { std::ifstream data_file("raw_data/polls/poll_questions.json"); if (!data_file.good()) return; nlohmann::json j_data = nlohmann::json::parse(data_file); + std::string today_string = getToday(); for (nlohmann::json::iterator itr = j_data.begin(); itr != j_data.end(); itr++) { int poll_id = itr->find("id")->get(); @@ -110,9 +123,47 @@ void LoadPolls(const std::vector &students) { std::string question_type = "single-response-multiple-correct"; nlohmann::json::iterator itr2 = itr->find("question_type"); if (itr2 != itr->end()) question_type = itr2->get(); - std::string status = itr->find("status")->get(); + + std::string status = "ended"; + // original format: status stored as string, "closed", "open", or "ended" + itr2 = itr->find("status"); + if (itr2 != itr->end()) status = itr2->get(); + // new format (April 2024): polls have an end time + std::string end_time = ""; + itr2 = itr->find("end_time"); + if (itr2 != itr->end()) { + if (itr2->is_null()) { + // if end_time is NULL that means the poll was never opened + status = "closed"; + } else { + end_time = itr->find("end_time")->get(); + assert (end_time.size() == 10); + assert (today_string.size() == 10); + if (end_time < today_string) { + // if end_time is in the past, then the poll was used/released to the class and is now ended + status = "ended"; + } else { + // otherwise, the poll might either be currently open or will be open in the future + // NOTE: this may need revision in a future PR, if timestamps are added to the end_time, + // to make sure we handle polls from today correctly + status = "closed"; + } + } + } assert (status == "ended" || status == "closed" || status == "open"); + assert (release_date.size() == 10); + assert (today_string.size() == 10); + if (release_date < today_string && status != "ended") { + // this should only happen if a poll was scheduled for a date in the past, but it wasn't used on that date. + // NOTE: this may need revision in a future PR, if timestamps are added to the end_time or + // if logic / handling of unreleased past polls changes. + std::cout << "Date inconsistency - this poll wasn't used/released." << std::endl; + std::cout << " today = " << today_string << " poll release date = " << release_date << std::endl; + std::cout << " setting release_date to infinity" << std::endl; + release_date = "9999-01-01"; + } + std::string lecture; int which; group_polls_by_date(poll_id,release_date,lecture,which); @@ -235,17 +286,7 @@ void LoadPolls(const std::vector &students) { // into their individual rainbow grades report). // void SavePollReports(const std::vector &students) { - - // make a string representing today's date: yyyy-mm-dd - static std::chrono::time_point now = std::chrono::system_clock::now(); - time_t tt = std::chrono::system_clock::to_time_t(now); - tm local_tm = *localtime(&tt); - std::stringstream ss; - ss << local_tm.tm_year + 1900 << "-" - << std::setw(2) << std::setfill('0') << local_tm.tm_mon + 1 << "-" - << std::setw(2) << std::setfill('0') << local_tm.tm_mday; - std::string today_string = ss.str(); - + std::string today_string = getToday(); std::cout << "TODAY " << today_string << std::endl; std::ofstream late_days_ostr("late_days.csv");
"; - ostr << ""; - ostr << ""; - ostr << " Border-Outline is ranked from top to bottom
"; - ostr << "Higher ranked outline will over-write
"; - ostr << "