From 1df486e0bf7828e7aa039d54d2914c65a73a4154 Mon Sep 17 00:00:00 2001 From: Barb Cutler Date: Sat, 9 Jun 2018 19:52:45 -0400 Subject: [PATCH] move files from main Submitty repo & switch to most recent nlohmann::json library --- .gitignore | 10 + MakefileHelper | 153 ++++ SAMPLE_Makefile | 11 + SAMPLE_customization.json | 150 +++ benchmark.cpp | 119 +++ benchmark.h | 41 + constants_and_globals.h | 68 ++ curve.h | 81 ++ grade.h | 15 + gradeable.h | 227 +++++ iclicker.cpp | 240 +++++ iclicker.h | 69 ++ main.cpp | 1812 +++++++++++++++++++++++++++++++++++++ output.cpp | 1234 +++++++++++++++++++++++++ parsexml.py | 147 +++ student.cpp | 485 ++++++++++ student.h | 249 +++++ table.cpp | 158 ++++ table.h | 70 ++ zone.cpp | 468 ++++++++++ 20 files changed, 5807 insertions(+) create mode 100644 .gitignore create mode 100644 MakefileHelper create mode 100644 SAMPLE_Makefile create mode 100644 SAMPLE_customization.json create mode 100644 benchmark.cpp create mode 100644 benchmark.h create mode 100644 constants_and_globals.h create mode 100644 curve.h create mode 100644 grade.h create mode 100644 gradeable.h create mode 100644 iclicker.cpp create mode 100644 iclicker.h create mode 100644 main.cpp create mode 100644 output.cpp create mode 100644 parsexml.py create mode 100644 student.cpp create mode 100644 student.h create mode 100644 table.cpp create mode 100644 table.h create mode 100644 zone.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..27dda55 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +*.autosave +*.orig* +*~ +*.o +*.pyc +*.aux +*.log +*.out +*.synctex.gz diff --git a/MakefileHelper b/MakefileHelper new file mode 100644 index 0000000..3675029 --- /dev/null +++ b/MakefileHelper @@ -0,0 +1,153 @@ +.PHONY: default pull push all compile overall section lab hw test quiz final iclicker zone clean src_clean remove_json_comments + +default: overall + +# ============================================================================= +# This is the helper Makefile + +# Don't directly use this Makefile, it should be included from the +# per-course Makefile with these variables set: + +ifndef USERNAME +$(error Variable USERNAME not set) +endif + +ifndef RAINBOW_GRADES_DIRECTORY +$(error Variable RAINBOW_GRADES_DIRECTORY not set) +endif + +ifndef HWSERVER +$(error Variable HWSERVER not set) +endif + +ifndef REPORTS_DIRECTORY +$(error Variable REPORTS_DIRECTORY not set) +endif + +GRADING_DIRECTORY := $(RAINBOW_GRADES_DIRECTORY)/../grading + + +nlohmann_json_dir=${RAINBOW_GRADES_DIRECTORY}/../vendor/nlohmann/json + +${nlohmann_json_dir}: + mkdir -p ${nlohmann_json_dir} + wget https://github.com/nlohmann/json/releases/download/v3.1.2/include.zip -O ${RAINBOW_GRADES_DIRECTORY}/../vendor/nlohmann_json.zip + unzip -o ${RAINBOW_GRADES_DIRECTORY}/../vendor/nlohmann_json.zip -d ${nlohmann_json_dir} + + +# Grab this file from the main Submitty repo +json_syntax_checker.py = ${RAINBOW_GRADES_DIRECTORY}/../misc_tools/json_syntax_checker.py +${json_syntax_checker.py}: + mkdir -p ${RAINBOW_GRADES_DIRECTORY}/../misc_tools/ + wget https://raw.githubusercontent.com/Submitty/Submitty/master/grading/json_syntax_checker.py -O ${json_syntax_checker.py} + + +# ============================================================================= + +pull: + mkdir -p raw_data/ + rsync -azP ${USERNAME}@${HWSERVER}:${REPORTS_DIRECTORY}/all_grades/ raw_data/ + +push: + rsync -azP individual_summary_html/* ${USERNAME}@${HWSERVER}:${REPORTS_DIRECTORY}/summary_html/ + +flags = -g +memory_debug : memory_flags = -m32 + +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Darwin) + flags = -g -std=c++11 +endif + +ifeq ($(UNAME_S),Linux) + flags = -g -std=c++0x +endif + +ifeq ($(findstring CYGWIN,$(UNAME_S)),CYGWIN) + flags = -g -std=c++11 +endif + +json_include = -I${nlohmann_json_dir}/include/ + + +remove_json_comments: customization.json + cpp -xc++ $< | sed -e '/^#/d' > customization_no_comments.json + python3 ${json_syntax_checker.py} customization_no_comments.json + grep '{"file":' customization_no_comments.json > iclicker_file_list.txt || true + python3 ${RAINBOW_GRADES_DIRECTORY}/parsexml.py iclicker_file_list.txt + +process_grades.out: \ + ${RAINBOW_GRADES_DIRECTORY}/main.cpp \ + ${RAINBOW_GRADES_DIRECTORY}/output.cpp \ + ${RAINBOW_GRADES_DIRECTORY}/table.cpp \ + ${RAINBOW_GRADES_DIRECTORY}/student.cpp \ + ${RAINBOW_GRADES_DIRECTORY}/iclicker.cpp \ + ${RAINBOW_GRADES_DIRECTORY}/zone.cpp \ + ${RAINBOW_GRADES_DIRECTORY}/benchmark.cpp + g++ -Wall ${flags} ${memory_flags} ${json_include} $^ -g -o $@ + +individual_summary_html all_students_summary_html: + mkdir -p $@ + +compile: remove_json_comments process_grades.out individual_summary_html all_students_summary_html + +clean: + rm -f process_grades.out + +src_clean: clean + rm -f ${json_syntax_checker.py} + rm -rf ${nlohmann_json_dir} + +all: pull overall push + +# ============================================================================= +# the different sorting orders & details of tables + +overall: ${nlohmann_json_dir} ${json_syntax_checker.py} compile + ./process_grades.out by_overall + +memory_debug: compile + drmemory -- ./process_grades.out by_overall + # valgrind ./process_grades.out by_overall + +iclicker: compile + ./process_grades.out by_iclicker + +name: compile + ./process_grades.out by_name + +section: compile + ./process_grades.out by_section + +lab: compile + ./process_grades.out by_lab + +hw: compile + ./process_grades.out by_hw + +test: compile + ./process_grades.out by_test + +quiz: compile + ./process_grades.out by_quiz + +exam: compile + ./process_grades.out by_exam + +reading: compile + ./process_grades.out by_reading + +project: compile + ./process_grades.out by_project + +participation: compile + ./process_grades.out by_participation + +test_exam: compile + ./process_grades.out by_test_and_exam + +zone: compile + ./process_grades.out by_zone + + +# ============================================================================= diff --git a/SAMPLE_Makefile b/SAMPLE_Makefile new file mode 100644 index 0000000..414a79f --- /dev/null +++ b/SAMPLE_Makefile @@ -0,0 +1,11 @@ +# ============================================================================= +# EDIT THESE VARIABLES + +USERNAME=username +RAINBOW_GRADES_DIRECTORY=//RainbowGrades +HWSERVER=submitty.cs.rpi.edu +REPORTS_DIRECTORY=/var/local/submitty/courses///reports + +# ============================================================================= + +include ${RAINBOW_GRADES_DIRECTORY}/MakefileHelper diff --git a/SAMPLE_customization.json b/SAMPLE_customization.json new file mode 100644 index 0000000..c07d6d8 --- /dev/null +++ b/SAMPLE_customization.json @@ -0,0 +1,150 @@ +/* +This JSON is based on the automatically generated customization for +the development course "sample" as of August 15, 2017. +It is intended as a simple example, with additional documentation online. +Going forward we expect more users to use a web interface to generate this file. +*/ +{ + "display_benchmark": [ + "average", + "stddev", + "perfect", + "lowest_a-", + "lowest_b-", + "lowest_c-", + "lowest_d" + ], + "section": { + "1": "ta2, ta3", + "2": "manne", + "3": "manne, ta3", + "4": "manne", + "5": "manne", + "6": "manne", + "7": "manne", + "8": "manne", + "9": "TBA", + "10": "TBA" + }, + "messages": [ + "sample Course", + "Note: Please be patient with data entry/grade corrections for the most recent lab, homework, and test.", + "Please contact your graduate lab TA if a grade remains missing or incorrect for more than a week." + ], + "display": [ + "instructor_notes", + "grade_summary", + "grade_details" + ], + "benchmark_percent": { + "lowest_c-": 0.7, + "lowest_d": 0.6, + "lowest_b-": 0.8, + "lowest_a-": 0.9 + }, + "gradeables": [ + { + "count": 4, + "percent": 0.75, + "type": "test", + "ids": [ + { + "max": 5.0, + "id": "future_no_tas_test", + "released": false + }, + { + "max": 5.0, + "id": "future_tas_test", + "released": false + }, + { + "max": 5.0, + "id": "grading_test", + "released": false + }, + { + "max": 5.0, + "id": "grades_released_test" + } + ] + }, + { + "count": 4, + "percent": 0.11, + "type": "lab", + "ids": [ + { + "max": 2.0, + "id": "future_no_tas_lab", + "released": false + }, + { + "max": 2.0, + "id": "future_tas_lab", + "released": false + }, + { + "max": 2.0, + "id": "grading_lab", + "released": false + }, + { + "max": 2.0, + "id": "grades_released_lab" + } + ] + }, + { + "count": 12, + "percent": 0.14, + "type": "homework", + "ids": [ + { + "max": 12.0, + "id": "open_homework", + "released": false + }, + { + "max": 12.0, + "id": "open_team_homework", + "released": false + }, + { + "max": 12.0, + "id": "closed_homework", + "released": false + }, + { + "max": 12.0, + "id": "grading_homework", + "released": false + }, + { + "max": 5.0, + "id": "grades_released_homework_onlyta" + }, + { + "max": 10, + "id": "grades_released_homework_onlyauto" + }, + { + "max": 22.0, + "id": "grades_released_homework_autota" + }, + { + "max": 26.0, + "id": "grades_released_homework_autohiddenEC" + }, + { + "max": 12.0, + "id": "grades_released_homework_onlytaEC" + }, + { + "max": 12.0, + "id": "grades_released_homework_onlytaPenalty" + } + ] + } + ] +} diff --git a/benchmark.cpp b/benchmark.cpp new file mode 100644 index 0000000..2ca3dcb --- /dev/null +++ b/benchmark.cpp @@ -0,0 +1,119 @@ +#include +#include + +#include "benchmark.h" + +std::vector > DISPLAY_BENCHMARKS; +int Benchmark::next_benchmark_position = 0; + +int FIND_BENCHMARK(const std::string& s) { + for (std::size_t i = 0; i < DISPLAY_BENCHMARKS.size(); i++) { + if (DISPLAY_BENCHMARKS[i].first == s) return i; + } + return -1; +} + +Benchmark::Benchmark(const std::string& s) { + assert (FIND_BENCHMARK(s) == -1); + assert (s == "extracredit" || + s == "perfect" || + s == "average" || + s == "stddev" || + s == "lowest_a-" || + s == "lowest_b-" || + s == "lowest_c-" || + s == "lowest_d" || + s == "failing"); + name = s; + visible = false; + percentage = -1; + color = "ffffff"; + position = -1; + DISPLAY_BENCHMARKS.push_back(std::make_pair(s,*this)); +} + +Benchmark& Benchmark::GetBenchmark(const std::string& s) { + assert (s == "extracredit" || + s == "perfect" || + s == "average" || + s == "stddev" || + s == "lowest_a-" || + s == "lowest_b-" || + s == "lowest_c-" || + s == "lowest_d" || + s == "failing"); + int i = FIND_BENCHMARK(s); + if (i == -1) { + Benchmark b(s); + i = FIND_BENCHMARK(s); + } + assert (i != -1); + return DISPLAY_BENCHMARKS[i].second; +} + + +void DisplayBenchmark(const std::string& s) { + Benchmark& b = Benchmark::GetBenchmark(s); + b.visible=true; + b.position = Benchmark::next_benchmark_position; + Benchmark::next_benchmark_position++; +} + +int NumVisibleBenchmarks() { + return Benchmark::next_benchmark_position; +} + +int WhichVisibleBenchmark(const std::string& s) { + for (std::size_t i = 0; i < DISPLAY_BENCHMARKS.size(); i++) { + if (DISPLAY_BENCHMARKS[i].first == s) { return DISPLAY_BENCHMARKS[i].second.position; } + } + return -1; +} + +void SetBenchmarkPercentage(const std::string& s, float v) { + assert (s == "lowest_a-" || + s == "lowest_b-" || + s == "lowest_c-" || + s == "lowest_d"); + Benchmark& b = Benchmark::GetBenchmark(s); + assert (v >= 0.0 && v <= 1.0); + b.percentage = v; +} + + +float GetBenchmarkPercentage(const std::string& s) { + assert (s == "lowest_a-" || + s == "lowest_b-" || + s == "lowest_c-" || + s == "lowest_d"); + Benchmark& b = Benchmark::GetBenchmark(s); + return b.percentage; +} + + + +void SetBenchmarkColor(const std::string& s, const std::string& c) { + assert (s == "extracredit" || + s == "perfect" || + s == "lowest_a-" || + s == "lowest_b-" || + s == "lowest_c-" || + s == "lowest_d" || + s == "failing"); + Benchmark& b = Benchmark::GetBenchmark(s); + assert (c.size() == 6); + b.color = c; +} + + +const std::string& GetBenchmarkColor(const std::string& s) { + assert (s == "extracredit" || + s == "perfect" || + s == "lowest_a-" || + s == "lowest_b-" || + s == "lowest_c-" || + s == "lowest_d" || + s == "failing"); + Benchmark& b = Benchmark::GetBenchmark(s); + return b.color; +} diff --git a/benchmark.h b/benchmark.h new file mode 100644 index 0000000..ad239d8 --- /dev/null +++ b/benchmark.h @@ -0,0 +1,41 @@ +#include +#include + +class Benchmark { +public: + Benchmark(const std::string& s); + + friend int NumVisibleBenchmarks(); + friend int WhichVisibleBenchmark(const std::string& s); + friend void DisplayBenchmark(const std::string& s); + friend void SetBenchmarkPercentage(const std::string& s, float v); + friend float GetBenchmarkPercentage(const std::string& s); + + friend void SetBenchmarkColor(const std::string& s, const std::string& c); + friend const std::string& GetBenchmarkColor(const std::string& s); + +private: + + static Benchmark& GetBenchmark(const std::string& s); + + int position; + std::string name; + bool visible; + float percentage; + std::string color; + static int next_benchmark_position; +}; + + +int NumVisibleBenchmarks(); +int WhichVisibleBenchmark(const std::string& s); +void DisplayBenchmark(const std::string& s); +void SetBenchmarkPercentage(const std::string& s, float v); +float GetBenchmarkPercentage(const std::string& s); + +void SetBenchmarkColor(const std::string& s, const std::string& c); +const std::string& GetBenchmarkColor(const std::string& s); + + +extern std::vector > DISPLAY_BENCHMARKS; + diff --git a/constants_and_globals.h b/constants_and_globals.h new file mode 100644 index 0000000..e303ce3 --- /dev/null +++ b/constants_and_globals.h @@ -0,0 +1,68 @@ +#ifndef __CONSTANTS_H__ +#define __CONSTANTS_H__ + +#define MAX_STRING_LENGTH 10000 + +#include +#include "grade.h" +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_FINAL_GRADE; +extern bool DISPLAY_GRADE_SUMMARY; +extern bool DISPLAY_GRADE_DETAILS; +extern bool DISPLAY_ICLICKER; +extern bool DISPLAY_LATE_DAYS; + +// ========================================================== +// messages for zone assignment +extern std::string GLOBAL_EXAM_TITLE; +extern std::string GLOBAL_EXAM_DATE; +extern std::string GLOBAL_EXAM_TIME; +extern std::string GLOBAL_EXAM_DEFAULT_ROOM; + +extern float GLOBAL_MIN_OVERALL_FOR_ZONE_ASSIGNMENT; + +extern std::vector MESSAGES; + + +// ========================================================== +extern Student* PERFECT_STUDENT_POINTER; + +extern bool TEST_IMPROVEMENT_AVERAGING_ADJUSTMENT; +extern float LATE_DAY_PERCENTAGE_PENALTY; +extern bool LOWEST_TEST_COUNTS_HALF; + +extern int QUIZ_NORMALIZE_AND_DROP; + +// ========================================================== +extern std::map sectionNames; +extern std::map sectionColors; + + +extern std::map CUTOFFS; +extern std::map grade_counts; +extern std::map grade_avg; +extern int took_final; +extern int dropped; +extern int auditors; +extern float LATE_DAY_PERCENTAGE_PENALTY; + +// ========================================================== +#define ICLICKER_RECENT 12 +#define ICLICKER_PRIORITY 0.666 +extern float MAX_ICLICKER_TOTAL; + + + +// ========================================================== +// PROTOTYPES + +bool validSection(int section); + + +#endif // __CONSTANTS_H__ diff --git a/curve.h b/curve.h new file mode 100644 index 0000000..4d3526e --- /dev/null +++ b/curve.h @@ -0,0 +1,81 @@ +#include +#include +#include +#include + + +class Curve { + +public: + + // ========================= + // CONSTRUCTOR + Curve() { + add("PERFECT",-1,-1); + add("AVERAGE",-1,-1); + } + + static Curve* DataStructuresCurve() { + Curve *curve = new Curve(); + curve->add("LOWEST A-", 0.30, -1); + curve->add("LOWEST B-", 0.30, -1); + curve->add("LOWEST C-", 0.30, -1); + curve->add("LOWEST D", -1, 0.5); + return curve; + } + + + static Curve* TypicalCurve() { + Curve *curve = new Curve(); + curve->add("LOWEST A-", -1, 0.9); + curve->add("LOWEST B-", -1, 0.8); + curve->add("LOWEST C-", -1, 0.7); + curve->add("LOWEST D", -1, 0.6); + return curve; + } + + // ========================= + // ACCESSORS + + int num() const { + assert (names.size() == target_distributions.size() && names.size() == fixed_percentages.size()); + return names.size(); + } + + const std::string& getName(int i) const { + assert (i >= 0 && i < num()); + return names[i]; + } + float getTargetDistribution(int i) const { + assert (i >= 0 && i < num()); + return target_distributions[i]; + } + float getFixedPercentage(int i) const { + assert (i >= 0 && i < num()); + return fixed_percentages[i]; + } + + // ========================= + // MODIFIER + void add(const std::string &name, float distribution, float percentage) { + names.push_back(name); + target_distributions.push_back(distribution); + fixed_percentages.push_back(percentage); + } + + +private: + + // ========================= + // REPRESENTATION + + // e.g. "perfect", "average", "lowest a-", etc. + std::vector names; + + // e.g., 30% A/A-, 30% B+/B/B-, 30% C+/C/C-, 10% D/F + std::vector target_distributions; + + // e.g., 90% / 80% / 70% / 60% + std::vector fixed_percentages; + +}; diff --git a/grade.h b/grade.h new file mode 100644 index 0000000..4763fee --- /dev/null +++ b/grade.h @@ -0,0 +1,15 @@ +#ifndef _GRADE_H_ +#define _GRADE_H_ + +#include + +class Grade { +public: + Grade(const std::string &v) : value(v) {} + std::string value; +}; + + +bool operator< (const Grade &a, const Grade &b); + +#endif diff --git a/gradeable.h b/gradeable.h new file mode 100644 index 0000000..e21ab50 --- /dev/null +++ b/gradeable.h @@ -0,0 +1,227 @@ +#ifndef _GRADEABLE_H_ +#define _GRADEABLE_H_ + +#include +#include +#include + + +enum class GRADEABLE_ENUM { + HOMEWORK, ASSIGNMENT, PROBLEM_SET, + QUIZ, TEST, EXAM, + EXERCISE, LECTURE_EXERCISE, READING, LAB, RECITATION, + PROJECT, PARTICIPATION, NOTE, + NONE }; + + +inline std::string gradeable_to_string(const GRADEABLE_ENUM &g) { + if (g == GRADEABLE_ENUM::HOMEWORK) { return "HOMEWORK"; } + if (g == GRADEABLE_ENUM::ASSIGNMENT) { return "ASSIGNMENT"; } + if (g == GRADEABLE_ENUM::PROBLEM_SET) { return "PROBLEM_SET"; } + if (g == GRADEABLE_ENUM::QUIZ) { return "QUIZ"; } + if (g == GRADEABLE_ENUM::TEST) { return "TEST"; } + if (g == GRADEABLE_ENUM::EXAM) { return "EXAM"; } + if (g == GRADEABLE_ENUM::EXERCISE) { return "EXERCISE"; } + if (g == GRADEABLE_ENUM::LECTURE_EXERCISE) { return "LECTURE_EXERCISE"; } + if (g == GRADEABLE_ENUM::READING) { return "READING"; } + if (g == GRADEABLE_ENUM::LAB) { return "LAB"; } + if (g == GRADEABLE_ENUM::RECITATION) { return "RECITATION"; } + if (g == GRADEABLE_ENUM::PROJECT) { return "PROJECT"; } + if (g == GRADEABLE_ENUM::PARTICIPATION) { return "PARTICIPATION"; } + if (g == GRADEABLE_ENUM::NOTE) { return "NOTE"; } + if (g == GRADEABLE_ENUM::NONE) { return "NONE"; } + std::cerr << "ERROR! UNKNOWN GRADEABLE" << std::endl; + exit(0); +} + +inline std::string spacify(const std::string &s) { + std::string tmp = ""; + for (int i = 0; i < s.size(); i++) { + tmp += std::string(1,s[i]) + " "; + } + return tmp; +} + + +// =============================================================================== + +class Gradeable { + +public: + + // CONSTRUTORS + Gradeable() { count=0;percent=0;remove_lowest=0; } + Gradeable(int c, float p) : count(c),percent(p) { remove_lowest=0; } + + // ACCESSORS + int getCount() const { return count; } + float getPercent() const { return percent; } + float getMaximum() const { + if (maximums.size() == 0) return 0; + assert (maximums.size() > 0); + int max_sum = 0; + for (std::map::const_iterator itr = maximums.begin(); + itr != maximums.end(); itr++) { + max_sum += itr->second; + } + return max_sum * getCount() / maximums.size(); + } + int getRemoveLowest() const { return remove_lowest; } + + std::string getID(int index) const { + std::map >::const_iterator itr = correspondences.begin(); + while (itr != correspondences.end()) { + if (itr->second.first == index) return itr->first; + itr++; + } + return ""; + } + + void isResubmit(int index, + std::string &original_id, std::string &resubmit_id, + float &autograde_replacement_percentage) { + std::string id = getID(index); + if (original_ids.find(id) == original_ids.end()) return; + assert (resubmit_ids.find(id) != resubmit_ids.end()); + assert (autograde_replacement_percentages.find(id) != autograde_replacement_percentages.end()); + original_id = original_ids.find(id)->second; + resubmit_id = resubmit_ids.find(id)->second; + autograde_replacement_percentage = autograde_replacement_percentages.find(id)->second; + } + + bool hasCorrespondence(const std::string &id) const { + /* + for (std::map >::const_iterator itr = correspondences.begin(); + itr != correspondences.end(); itr++) { + std::cout << "looking for " << id << " " << itr->first << std::endl; + } + */ + std::map >::const_iterator itr = correspondences.find(id); + return (itr != correspondences.end()); + } + + const std::pair& getCorrespondence(const std::string& id) { + assert (hasCorrespondence(id)); + return correspondences.find(id)->second; + } + + bool isReleased(const std::string &id) const { + assert (released.find(id) != released.end()); + return released.find(id)->second; + } + float getItemMaximum(const std::string &id) const { + assert (maximums.find(id) != maximums.end()); + return maximums.find(id)->second; + } + float getScaleMaximum(const std::string &id) const { + if (scale_maximums.find(id) == scale_maximums.end()) { + return -1; + } + return scale_maximums.find(id)->second; + } + float getItemPercentage(const std::string &id) const { + if (item_percentages.find(id) == item_percentages.end()) + return -1; + else + return item_percentages.find(id)->second; + } + float getClamp(const std::string &id) const { + assert (clamps.find(id) != clamps.end()); + return clamps.find(id)->second; + } + + // MODIFIERS + void setRemoveLowest(int r) { remove_lowest=r; } + + int setCorrespondence(const std::string& id) { + assert (!hasCorrespondence(id)); + //std::cout << "SET CORR " << id << std::endl; + assert (int(correspondences.size()) < count); + int index = correspondences.size(); + correspondences[id] = std::make_pair(index,""); + return index; + } + + void setCorrespondenceName(const std::string& id, const std::string& name) { + assert (hasCorrespondence(id)); + assert (correspondences[id].second == ""); + correspondences[id].second = name; + } + + void setReleased(const std::string&id, bool is_released) { + assert (hasCorrespondence(id)); + assert (released.find(id) == released.end()); + released[id] = is_released; + } + + void setMaximum(const std::string&id, float maximum) { + assert (hasCorrespondence(id)); + assert (maximums.find(id) == maximums.end()); + maximums[id] = maximum; + } + void setScaleMaximum(const std::string&id, float scale_maximum) { + assert (hasCorrespondence(id)); + assert (scale_maximums.find(id) == scale_maximums.end()); + scale_maximums[id] = scale_maximum; + } + void setItemPercentage(const std::string&id, float item_percentage) { + assert (hasCorrespondence(id)); + assert (item_percentages.find(id) == item_percentages.end()); + item_percentages[id] = item_percentage; + } + void setClamp(const std::string&id, float clamp) { + assert (hasCorrespondence(id)); + assert (clamps.find(id) == clamps.end()); + clamps[id] = clamp; + } + + void setResubmissionValues(const std::string &id, + const std::string &original_id, const std::string &resubmit_id, + const std::string &title, + float autograde_replacement_percentage) { + setCorrespondenceName(id,title); + original_ids[id] = original_id; + resubmit_ids[id] = resubmit_id; + autograde_replacement_percentages[id] = autograde_replacement_percentage; + } + +private: + + // REPRESENTATION + int count; + float percent; + int remove_lowest; + std::map > correspondences; + std::map maximums; + std::map scale_maximums; + std::map item_percentages; + std::map clamps; + std::map released; + std::map original_ids; + std::map resubmit_ids; + std::map autograde_replacement_percentages; +}; + +// =============================================================================== + +extern std::vector ALL_GRADEABLES; + +extern std::map GRADEABLES; + + +inline void LookupGradeable(const std::string &id, + GRADEABLE_ENUM &g_e, int &i) { + for (std::size_t k = 0; k < ALL_GRADEABLES.size(); k++) { + GRADEABLE_ENUM e = ALL_GRADEABLES[k]; + Gradeable g = GRADEABLES[e]; + if (g.hasCorrespondence(id)) { + g_e = e; + i = g.getCorrespondence(id).first; + return; + } + } + assert (0); +} + + +#endif diff --git a/iclicker.cpp b/iclicker.cpp new file mode 100644 index 0000000..e4e7420 --- /dev/null +++ b/iclicker.cpp @@ -0,0 +1,240 @@ +#include +#include +#include +#include + +#include "iclicker.h" +#include "student.h" + +extern std::vector ICLICKER_QUESTION_NAMES; +extern float MAX_ICLICKER_TOTAL; + +std::vector GLOBAL_earned_late_days; + +std::map LECTURE_DATE_CORRESPONDENCES; + +Student* GetStudent(const std::vector &students, const std::string& name); + +//Internally change .xml paths to .csv paths +void iClickerQuestion::handleXMLFilename(const std::string& f){ + filename = f; + if(filename.substr(filename.size()-4) == ".xml"){ + filename.resize(filename.size()-3); + filename += "csv"; + } +} + +Date dateFromFilename(const std::string& filename_with_directory) { + + std::string filename = filename_with_directory; + while (true) { + std::string::size_type pos = filename.find('/'); + if (pos == std::string::npos) break; + filename = filename.substr(pos+1,filename.size()-pos-1); + } + + assert (filename.size() == 15); + assert (filename[0] == 'L'); + // assert (filename[7] == '_'); + assert (filename.substr(11,4) == ".csv"); + + Date answer; + + answer.year = 2000 + atoi(filename.substr(1,2).c_str()); + answer.month = atoi(filename.substr(3,2).c_str()); + answer.day = atoi(filename.substr(5,2).c_str()); + + //std::cout << "YEAR " << answer.year << std::endl; + //std::cout << "MONTH " << answer.month << std::endl; + //std::cout << "DAY " << answer.day << std::endl; + + assert (answer.month >= 1 && answer.month <= 12); + assert (answer.day >= 1 && answer.day <= 31); + + return answer; +} + + +std::string ReadRemote(std::istream &istr) { + char c; + std::string answer; + if (!istr.good()) return ""; + istr >> c; + if (!istr.good()) return ""; + if (c != '#') return answer; + answer = "#"; + while (1) { + istr >> c; + if (c == ',') break; + answer.push_back(c); + } + if (answer == "#T24RLR15") { + std::cerr << "ERROR! " << answer << " is not a valid remote ID, this is the clicker model #" << std::endl; + return ""; + } + return answer; +} + +std::string ReadQuoted(std::istream &istr) { + char c; + std::string answer; + //bool success = true; + if ( !(istr >> c) || c != '"') { + //std::cout << success << " OOPS not quote '" << c << "'" << std::endl; + } + + while (istr >> c) { + if (c == '"') break; + answer.push_back(c); + } + //std::cout << "FOUND " << answer < GLOBAL_CLICKER_MAP; + + +void MatchClickerRemotes(std::vector &students, const std::string &remotes_filename) { + if (remotes_filename == "") return; + std::cout << "READING CLICKER REMOTES FILE: " << remotes_filename << std::endl; + + std::ifstream istr(remotes_filename.c_str()); + if (!istr) return; + + while (1) { + std::string remote = ReadRemote(istr); + std::string username = ReadQuoted(istr); + if (username == "") break; + if (remote == "") { + std::cerr << "ERROR! blank remoteid for " << username << std::endl; + continue; + } + //std::cout << "tokens: " << remote << " " << username << std::endl; + Student *s = GetStudent(students,username); + if (s == NULL) { + std::cout << "BAD USERNAME FOR CLICKER MATCHING " << username << std::endl; + exit(0); + continue; + } + assert (s != NULL); + if (s->getRemoteID().size() != 0) { + std::cout << "student " << username << " has multiple remotes (replacing a lost remote)" << std::endl; + } + s->setRemoteID(remote); + //std::cout << "MATCH " << username << " " << remote << std::endl; + if (GLOBAL_CLICKER_MAP.find(remote) != GLOBAL_CLICKER_MAP.end()) { + std::cout << "ERROR! already have this clicker assigned " << remote << " " << s->getUserName() << std::endl; + } + assert (GLOBAL_CLICKER_MAP.find(remote) == GLOBAL_CLICKER_MAP.end()); + GLOBAL_CLICKER_MAP[remote] = username; + } +} + + +std::string getItem(const std::string &line, int which) { + std::string::size_type comma_before = 0; + for (int i = 0; i < which; i++) { + comma_before = line.find(',',comma_before)+1; + assert (comma_before != std::string::npos); + } + int comma_after = line.find(',',comma_before); + return line.substr(comma_before,comma_after-comma_before); +} + + +void AddClickerScores(std::vector &students, std::vector > > iclicker_questions) { + + for (unsigned int which_lecture = 0; which_lecture < iclicker_questions.size(); which_lecture++) { + //std::cout << "which lecture = " << which_lecture << std::endl; + std::vector >& lecture = iclicker_questions[which_lecture]; + //for (unsigned int which_question = 0; which_question < lecture.size(); which_question++) { + for (std::size_t which_question = 0; which_question < lecture.size(); which_question++){ + //unsigned int which_question = it->first; + //iClickerQuestion& question = lecture[which_question]; + + std::stringstream ss; + ss << which_lecture << "." << which_question+1; + + for(unsigned int i=0; i::iterator itr = GLOBAL_CLICKER_MAP.find(remoteid); + if (itr == GLOBAL_CLICKER_MAP.end()) { + //std::cout << "UNKNOWN CLICKER: " << remoteid << " " << std::endl; + std::cout << " " << remoteid; + continue; + } + assert(itr != GLOBAL_CLICKER_MAP.end()); + std::string username = itr->second; + Student *s = GetStudent(students, username); + assert(s != NULL); + + iclicker_answer_enum grade = ICLICKER_NOANSWER; + if (question.participationQuestion()) + grade = ICLICKER_PARTICIPATED; + else { + if (question.isCorrectAnswer(item[0])) { + grade = ICLICKER_CORRECT; + } else { + grade = ICLICKER_INCORRECT; + } + } + + s->addIClickerAnswer(ss.str(), item[0], grade); + //s.seti + + } + std::cout << std::endl; + } + } + } +} diff --git a/iclicker.h b/iclicker.h new file mode 100644 index 0000000..f061367 --- /dev/null +++ b/iclicker.h @@ -0,0 +1,69 @@ +#ifndef _ICLICKER_H_ +#define _ICLICKER_H_ + +#include +#include +#include +#include +#include + + +#define MAX_LECTURES 28 + +extern std::vector GLOBAL_earned_late_days; + +class Student; + +class Date { +public: + int year; + int month; + int day; + + std::string getStringRep() { + std::stringstream ss; + ss << std::setw(2) << std::setfill('0') << month + << "/" + << std::setw(2) << std::setfill('0') << day + << "/" + << std::setw(4) << year; + return ss.str(); + } +}; + + +enum iclicker_answer_enum { ICLICKER_NOANSWER, ICLICKER_INCORRECT, ICLICKER_PARTICIPATED, ICLICKER_CORRECT }; + +extern std::vector ICLICKER_QUESTION_NAMES; + +extern std::map LECTURE_DATE_CORRESPONDENCES; + +// ========================================================== + +class iClickerQuestion { +public: + iClickerQuestion(const std::string& f, int q, const std::string& ca) { + handleXMLFilename(f); + which_question = q; + assert (which_question >= 0 && which_question < 20); + correct_answer = ca; + } + + const std::string& getFilename() const { return filename; } + int getColumn() const { return 4 + (which_question-1)*6; } + bool participationQuestion() const { return correct_answer == "ABCDE"; } + bool isCorrectAnswer(char c) { return correct_answer.find(c) != std::string::npos; } + void handleXMLFilename(const std::string& f); + +private: + std::string filename; + int which_question; + std::string correct_answer; +}; + +// ========================================================== + +void MatchClickerRemotes(std::vector &students, const std::string &remotes_filename); +void AddClickerScores(std::vector &students, std::vector > > iclicker_questions); + +#endif diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..bee53d5 --- /dev/null +++ b/main.cpp @@ -0,0 +1,1812 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "benchmark.h" + +std::string GLOBAL_sort_order; + + +int GLOBAL_ACTIVE_TEST_ZONE = 0; + +#include "student.h" +#include "iclicker.h" +#include "gradeable.h" +#include "grade.h" +#include + +// defined in iclicker.cpp +std::string ReadQuoted(std::istream &istr); +void suggest_curves(std::vector &students); + +std::string GLOBAL_recommend_id = ""; + + +//==================================================================== +// DIRECTORIES & FILES + +std::string ICLICKER_ROSTER_FILE = "./iclicker_Roster.txt"; +std::string OUTPUT_FILE = "./output.html"; +std::string CUSTOMIZATION_FILE = "./customization_no_comments.json"; + +std::string RAW_DATA_DIRECTORY = "./raw_data/"; +std::string INDIVIDUAL_FILES_OUTPUT_DIRECTORY = "./individual_summary_html/"; +std::string ALL_STUDENTS_OUTPUT_DIRECTORY = "./all_students_summary_html/"; + +nlohmann::json GLOBAL_CUSTOMIZATION_JSON; + + +//==================================================================== +// INFO ABOUT GRADING FOR COURSE + +std::vector ALL_GRADEABLES; + +std::map GRADEABLES; + +float LATE_DAY_PERCENTAGE_PENALTY = 0; +bool TEST_IMPROVEMENT_AVERAGING_ADJUSTMENT = false; +bool LOWEST_TEST_COUNTS_HALF = false; + +int QUIZ_NORMALIZE_AND_DROP = 0; + +std::vector ICLICKER_QUESTION_NAMES; +float MAX_ICLICKER_TOTAL; + +std::map CUTOFFS; + +std::map grade_counts; +std::map grade_avg; +int took_final = 0; +int auditors = 0; +int dropped = 0; + +Student* PERFECT_STUDENT_POINTER; +Student* AVERAGE_STUDENT_POINTER; +Student* STDDEV_STUDENT_POINTER; + +//==================================================================== +// INFO ABOUT NUMBER OF SECTIONS + +std::map sectionNames; +std::map sectionColors; + +bool validSection(int section) { + + nlohmann::json::iterator itr = GLOBAL_CUSTOMIZATION_JSON.find("section"); + assert (itr != GLOBAL_CUSTOMIZATION_JSON.end()); + assert (itr->is_object()); + + nlohmann::json::iterator itr2 = itr->find(std::to_string(section)); + if (itr2 == itr->end()) return false; + return true; + + //return (sectionNames.find(section) != sectionNames.end()); +} + + +std::string sectionName(int section) { + std::map::const_iterator itr = sectionNames.find(section); + if (itr == sectionNames.end()) + return "NONE"; + return itr->second; +} + + + + +//==================================================================== + +std::string GLOBAL_EXAM_TITLE = "exam title uninitialized"; +std::string GLOBAL_EXAM_DATE = "exam date uninitialized"; +std::string GLOBAL_EXAM_TIME = "exam time uninitialized"; +std::string GLOBAL_EXAM_DEFAULT_ROOM = "exam default room uninitialized"; +std::string GLOBAL_EXAM_SEATING = ""; +std::string GLOBAL_SEATING_SPACING = ""; +std::string GLOBAL_EXAM_SEATING_COUNT = ""; +std::string GLOBAL_LEFT_RIGHT_HANDEDNESS = ""; + +float GLOBAL_MIN_OVERALL_FOR_ZONE_ASSIGNMENT = 0.1; + +int BONUS_WHICH_LECTURE = -1; +std::string BONUS_FILE; + +//==================================================================== +// INFO ABOUT OUTPUT FORMATTING + +bool DISPLAY_INSTRUCTOR_NOTES = false; +bool DISPLAY_EXAM_SEATING = false; +bool DISPLAY_MOSS_DETAILS = false; +bool DISPLAY_FINAL_GRADE = false; +bool DISPLAY_GRADE_SUMMARY = false; +bool DISPLAY_GRADE_DETAILS = false; +bool DISPLAY_ICLICKER = false; +bool DISPLAY_LATE_DAYS = false; + + +std::vector MESSAGES; + + +//==================================================================== + +std::ofstream priority_stream("priority.txt"); +std::ofstream late_days_stream("late_days.txt"); + +void PrintExamRoomAndZoneTable(std::ofstream &ostr, Student *s, const nlohmann::json &special_message); + +//==================================================================== + + + + + +//==================================================================== +// sorting routines + + +bool by_overall(const Student* s1, const Student* s2) { + float s1_overall = s1->overall_b4_moss(); + float s2_overall = s2->overall_b4_moss(); + + if (s1 == AVERAGE_STUDENT_POINTER) return true; + if (s2 == AVERAGE_STUDENT_POINTER) return false; + if (s1 == STDDEV_STUDENT_POINTER) return true; + if (s2 == STDDEV_STUDENT_POINTER) return false; + + if (s1_overall > s2_overall+0.0001) return true; + if (fabs (s1_overall - s2_overall) < 0.0001 && + s1->getSection() == 0 && + s2->getSection() != 0) + return true; + + return false; +} + + +bool by_test_and_exam(const Student* s1, const Student* s2) { + float val1 = s1->GradeablePercent(GRADEABLE_ENUM::TEST) + s1->GradeablePercent(GRADEABLE_ENUM::EXAM); + float val2 = s2->GradeablePercent(GRADEABLE_ENUM::TEST) + s2->GradeablePercent(GRADEABLE_ENUM::EXAM); + + if (val1 > val2) return true; + if (fabs (val1-val2) < 0.0001 && + s1->getSection() == 0 && + s2->getSection() != 0) + return true; + + return false; +} + + + + +// FOR GRADEABLES +class GradeableSorter { +public: + GradeableSorter(GRADEABLE_ENUM g) : g_(g) {} + bool operator()(Student *s1, Student *s2) { + return (s1->GradeablePercent(g_) > s2->GradeablePercent(g_) || + (s1->GradeablePercent(g_) == s2->GradeablePercent(g_) && + by_overall(s1,s2))); + } +private: + GRADEABLE_ENUM g_; +}; + + +// FOR OTHER THINGS + + +bool by_name(const Student* s1, const Student* s2) { + return (s1->getLastName() < s2->getLastName() || + (s1->getLastName() == s2->getLastName() && + s1->getPreferredName() < s2->getPreferredName())); +} + + +bool by_section(const Student *s1, const Student *s2) { + if (s2->getIndependentStudy() == true && s1->getIndependentStudy() == false) return false; + if (s2->getIndependentStudy() == false && s1->getIndependentStudy() == true) return false; + if (s2->getSection() <= 0 && s1->getSection() <= 0) { + return by_name(s1,s2); + } + if (s2->getSection() == 0) return true; + if (s1->getSection() == 0) return false; + if (s1->getSection() < s2->getSection()) return true; + if (s1->getSection() > s2->getSection()) return false; + return by_name(s1,s2); +} + +bool by_iclicker(const Student* s1, const Student* s2) { + return (s1->getIClickerTotalFromStart() > s2->getIClickerTotalFromStart()); +} + + +// sorting function for letter grades +bool operator< (const Grade &a, const Grade &b) { + if (a.value == b.value) return false; + + if (a.value == "A") return true; + if (b.value == "A") return false; + if (a.value == "A-") return true; + if (b.value == "A-") return false; + + if (a.value == "B+") return true; + if (b.value == "B+") return false; + if (a.value == "B") return true; + if (b.value == "B") return false; + if (a.value == "B-") return true; + if (b.value == "B-") return false; + + if (a.value == "C+") return true; + if (b.value == "C+") return false; + if (a.value == "C") return true; + if (b.value == "C") return false; + if (a.value == "C-") return true; + if (b.value == "C-") return false; + + if (a.value == "D+") return true; + if (b.value == "D+") return false; + if (a.value == "D") return true; + if (b.value == "D") return false; + + if (a.value == "F") return true; + if (b.value == "F") return false; + + return false; +} + +//==================================================================== +/* +void gradeable_helper(std::ifstream& istr, GRADEABLE_ENUM g) { + int c; float p, m; + istr >> c >> p >> m; + assert (GRADEABLES.find(g) == GRADEABLES.end()); + + Gradeable answer (c,p,m); + GRADEABLES.insert(std::make_pair(g,answer)); + assert (GRADEABLES[g].getCount() >= 0); + assert (GRADEABLES[g].getPercent() >= 0.0 && GRADEABLES[g].getPercent() <= 1.0); + assert (GRADEABLES[g].getMaximum() >= 0.0); +} +*/ + +bool string_to_gradeable_enum(const std::string &s, GRADEABLE_ENUM &return_value) { + std::string s2; + for (unsigned int i = 0; i < s.size(); ++i) { + s2.push_back(std::tolower(s[i])); + } + std::replace( s2.begin(), s2.end(), '-', '_'); + if (s2 == "hw" || s2 == "homework") { return_value = GRADEABLE_ENUM::HOMEWORK; return true; } + if (s2 == "assignment") { return_value = GRADEABLE_ENUM::ASSIGNMENT; return true; } + if (s2 == "problem_set") { return_value = GRADEABLE_ENUM::PROBLEM_SET; return true; } + if (s2 == "quiz" || s2 == "quizze") { return_value = GRADEABLE_ENUM::QUIZ; return true; } + if (s2 == "test") { return_value = GRADEABLE_ENUM::TEST; return true; } + if (s2 == "exam") { return_value = GRADEABLE_ENUM::EXAM; return true; } + if (s2 == "exercise") { return_value = GRADEABLE_ENUM::EXERCISE; return true; } + if (s2 == "lecture_exercise") { return_value = GRADEABLE_ENUM::LECTURE_EXERCISE; return true; } + if (s2 == "reading") { return_value = GRADEABLE_ENUM::READING; return true; } + if (s2 == "lab") { return_value = GRADEABLE_ENUM::LAB; return true; } + if (s2 == "recitation") { return_value = GRADEABLE_ENUM::RECITATION; return true; } + if (s2 == "project") { return_value = GRADEABLE_ENUM::PROJECT; return true; } + if (s2 == "participation") { return_value = GRADEABLE_ENUM::PARTICIPATION; return true; } + if (s2 == "instructor_note" || s2 == "note") { return_value = GRADEABLE_ENUM::NOTE; return true; } + if (s2 == "note") { return_value = GRADEABLE_ENUM::NOTE; return true; } + if (s2.substr(0,4) == "none") { return_value = GRADEABLE_ENUM::NOTE; return true; } + return false; +} + +//==================================================================== + + +void preprocesscustomizationfile(std::vector &students) { + /* + std::ifstream istr(CUSTOMIZATION_FILE.c_str()); + assert (istr); + std::string token; + + while (istr >> token) { + if (token[0] == '#') { + // comment line! + char line[MAX_STRING_LENGTH]; + istr.getline(line,MAX_STRING_LENGTH); + + } else if (token.size() > 4 && token.substr(0,4) == "num_") { + + GRADEABLE_ENUM g; + // also take 's' off the end + bool success = string_to_gradeable_enum(token.substr(4,token.size()-5),g); + + if (success) { + gradeable_helper(istr,g); + + ALL_GRADEABLES.push_back(g); + + } else { + std::cout << "UNKNOWN GRADEABLE: " << token.substr(4,token.size()-5) << std::endl; + exit(0); + } + + } else if (token == "hackmaxprojects") { + + char line[MAX_STRING_LENGTH]; + istr.getline(line,MAX_STRING_LENGTH); + std::stringstream ss(line); + std::vector items; + std::string i; + while (ss >> i) { + items.push_back(i); + } + std::cout << "HACK MAX " << items.size() << std::endl; + + std::cout << " HMP=" << HACKMAXPROJECTS.size() << std::endl; + + HACKMAXPROJECTS.push_back(items); + + } else if (token == "display") { + istr >> token; + + if (token == "instructor_notes") { + 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 == "final_grade") { + DISPLAY_FINAL_GRADE = true; + } else if (token == "grade_summary") { + DISPLAY_GRADE_SUMMARY = true; + } else if (token == "grade_details") { + DISPLAY_GRADE_DETAILS = true; + } else if (token == "iclicker") { + DISPLAY_ICLICKER = true; + + } else { + std::cout << "OOPS " << token << std::endl; + exit(0); + } + char line[MAX_STRING_LENGTH]; + istr.getline(line,MAX_STRING_LENGTH); + + } else if (token == "display_benchmark") { + istr >> token; + DisplayBenchmark(token); + + } else if (token == "benchmark_percent") { + istr >> token; + float value; + istr >> value; + + SetBenchmarkPercentage(token,value); + + } else if (token == "benchmark_color") { + istr >> token; + std::string color; + istr >> color; + + SetBenchmarkColor(token,color); + + } else if (token == "use") { + + istr >> token; + + if (token == "late_day_penalty") { + istr >> LATE_DAY_PERCENTAGE_PENALTY; + assert (LATE_DAY_PERCENTAGE_PENALTY >= 0.0 && LATE_DAY_PERCENTAGE_PENALTY < 0.25); + char line[MAX_STRING_LENGTH]; + istr.getline(line,MAX_STRING_LENGTH); + } else if (token == "test_improvement_averaging_adjustment") { + TEST_IMPROVEMENT_AVERAGING_ADJUSTMENT = true; + } else if (token == "quiz_normalize_and_drop_two") { + QUIZ_NORMALIZE_AND_DROP_TWO = true; + } else if (token == "lowest_test_counts_half") { + LOWEST_TEST_COUNTS_HALF = true; + } else { + std::cout << "ERROR: unknown use " << token << std::endl; + exit(0); + } + + } else if (token == "remove_lowest") { + istr >> token; + if (token == "HOMEWORK") { + int num; + istr >> num; + assert (num >= 0 && num < GRADEABLES[GRADEABLE_ENUM::HOMEWORK].getCount()); + GRADEABLES[GRADEABLE_ENUM::HOMEWORK].setRemoveLowest(num); + } + } + } + */ + // --------------------------------------------------------------------------------------- + + std::ifstream istr(CUSTOMIZATION_FILE.c_str()); + assert (istr.good()); + nlohmann::json j = nlohmann::json::parse(istr); + + std::string token; + float p_score,a_score,b_score,c_score,d_score; + + Student *perfect = GetStudent(students,"PERFECT"); + Student *student_average = GetStudent(students,"AVERAGE"); + Student *student_stddev = GetStudent(students,"STDDEV"); + Student *lowest_a = GetStudent(students,"LOWEST A-"); + Student *lowest_b = GetStudent(students,"LOWEST B-"); + Student *lowest_c = GetStudent(students,"LOWEST C-"); + Student *lowest_d = GetStudent(students,"LOWEST D"); + + //std::cout << "0" << std::endl; + + + // load gradeables + nlohmann::json all_gradeables = j["gradeables"]; + int num_gradeables = all_gradeables.size(); + + float sum_of_percents = 0; + + for (int i = 0; i < num_gradeables; i++) { + nlohmann::json one_gradeable_type = all_gradeables[i]; + GRADEABLE_ENUM g; + + // TYPE + nlohmann::json::iterator itr = one_gradeable_type.find("type"); + assert (itr != one_gradeable_type.end()); + std::string gradeable_type = itr->get(); + bool success = string_to_gradeable_enum(gradeable_type, g); + if (!success) { + std::cout << "UNKNOWN GRADEABLE TYPE: " << gradeable_type << std::endl; + exit(0); + } + + // PERCENT + itr = one_gradeable_type.find("percent"); + assert (itr != one_gradeable_type.end()); + float gradeable_total_percent = itr->get(); + assert (gradeable_total_percent >= 0); + sum_of_percents += gradeable_total_percent; + + int count = one_gradeable_type.value("count",-1); + + nlohmann::json ids_list = one_gradeable_type["ids"]; + for (unsigned int k = 0; k < ids_list.size(); k++) { + nlohmann::json ids = ids_list[k]; + std::string gradeable_id = ids.value("id",""); + assert (gradeable_id != ""); + } + if (count == -1) { + count = ids_list.size(); + } + //If we ever have more than INT_MAX ids, this will wrap. + //Needed to resolve int vs uint comparison and this is preferable since + //presumably the json library is spitting out an int and the customization.json + //also assumes int. + assert (int(ids_list.size()) <= count); + + Gradeable answer (count,gradeable_total_percent); //,m); + GRADEABLES.insert(std::make_pair(g,answer)); + assert (GRADEABLES[g].getCount() >= 0); + assert (GRADEABLES[g].getPercent() >= 0.0 && GRADEABLES[g].getPercent() <= 1.0); + + // Set remove lowest for gradeable + int num = one_gradeable_type.value("remove_lowest", 0); + assert (num == 0 || (num >= 0 && num < GRADEABLES[g].getCount())); + GRADEABLES[g].setRemoveLowest(num); + ALL_GRADEABLES.push_back(g); + } + + // Set Benchmark Percent + + std::vector benchmark_percents; + + nlohmann::json benchmarkPercent = j["benchmark_percent"]; + for (nlohmann::json::iterator itr = benchmarkPercent.begin(); itr != benchmarkPercent.end(); itr++) { + token = itr.key(); + float value; + value = itr.value(); + SetBenchmarkPercentage(token,value); + benchmark_percents.push_back(token); + } + + + + perfect = new Student();perfect->setUserName("PERFECT"); + student_average = new Student();student_average->setUserName("AVERAGE"); + student_stddev = new Student();student_stddev->setUserName("STDDEV"); + + lowest_a = new Student();lowest_a->setUserName("LOWEST A-");lowest_a->setLegalFirstName("approximate"); + lowest_b = new Student();lowest_b->setUserName("LOWEST B-");lowest_b->setLegalFirstName("approximate"); + lowest_c = new Student();lowest_c->setUserName("LOWEST C-");lowest_c->setLegalFirstName("approximate"); + lowest_d = new Student();lowest_d->setUserName("LOWEST D"); lowest_d->setLegalFirstName("approximate"); + + PERFECT_STUDENT_POINTER = perfect; + AVERAGE_STUDENT_POINTER = student_average; + STDDEV_STUDENT_POINTER = student_stddev; + + for (int i = 0; i < num_gradeables; i++) { + + nlohmann::json one_gradeable_type = all_gradeables[i]; + + GRADEABLE_ENUM g; + nlohmann::json::iterator itr = one_gradeable_type.find("type"); + assert (itr != one_gradeable_type.end()); + std::string gradeable_type = itr->get(); + bool success = string_to_gradeable_enum(gradeable_type, g); + if (!success) { + std::cout << "UNKNOWN GRADEABLE: " << gradeable_type << std::endl; + exit(0); + } + nlohmann::json ids_list = one_gradeable_type["ids"]; + + for (unsigned int k = 0; k < ids_list.size(); k++) { + nlohmann::json grade_id = ids_list[k]; + std::string token_key = grade_id.value("id",""); + assert (token_key != ""); + + int which = GRADEABLES[g].setCorrespondence(token_key); + p_score = grade_id.value("max", 0.0); + + std::vector curve = grade_id.value("curve",std::vector()); + + a_score = GetBenchmarkPercentage("lowest_a-")*p_score; + b_score = GetBenchmarkPercentage("lowest_b-")*p_score; + c_score = GetBenchmarkPercentage("lowest_c-")*p_score; + d_score = GetBenchmarkPercentage("lowest_d")*p_score; + + if (!curve.empty()) { + assert(curve.size() == benchmark_percents.size()); + for (std::size_t x = 0; x < benchmark_percents.size(); x++) { + if (benchmark_percents[x] == "lowest_a-") a_score = curve[x]; + if (benchmark_percents[x] == "lowest_b-") b_score = curve[x]; + if (benchmark_percents[x] == "lowest_c-") c_score = curve[x]; + if (benchmark_percents[x] == "lowest_d") d_score = curve[x]; + } + } + + c_score = std::min(b_score,c_score); + d_score = std::min(c_score,d_score); + + bool released = grade_id.value("released",true); + GRADEABLES[g].setReleased(token_key,released); + + float maximum = grade_id.value("max",0); + GRADEABLES[g].setMaximum(token_key,maximum); + + if (grade_id.find("scale_max") != grade_id.end()) { + float scale_maximum = grade_id.value("scale_max",0); + assert (scale_maximum > 0); + GRADEABLES[g].setScaleMaximum(token_key,scale_maximum); + } + if (grade_id.find("percent") != grade_id.end()) { + float item_percentage = grade_id.value("percent",-1.0); + assert (item_percentage >= 0 && item_percentage <= 1.0); + GRADEABLES[g].setItemPercentage(token_key,item_percentage); + } + float clamp = grade_id.value("clamp",-1); + GRADEABLES[g].setClamp(token_key,clamp); + + if (grade_id.find("autograde_replacement_percentage") != grade_id.end()) { + assert (grade_id.find("original_id") != grade_id.end()); + assert (grade_id.find("resubmit_id") != grade_id.end()); + assert (grade_id.find("title") != grade_id.end()); + + std::string o_id = grade_id.value("original_id",""); + std::string r_id = grade_id.value("resubmit_id",""); + std::string t = grade_id.value("title",""); + float a_r_p = grade_id.value("autograde_replacement_percentage",0.5); + + GRADEABLES[g].setResubmissionValues(token_key,o_id,r_id,t,a_r_p); + } + + assert (p_score >= a_score && + a_score >= b_score && + b_score >= c_score && + c_score >= d_score); + + assert (which >= 0 && which < GRADEABLES[g].getCount()); + if (GRADEABLES[g].isReleased(token_key)) { + perfect->setGradeableItemGrade(g,which, p_score); + lowest_a->setGradeableItemGrade(g,which, a_score); + lowest_b->setGradeableItemGrade(g,which, b_score); + lowest_c->setGradeableItemGrade(g,which, c_score); + lowest_d->setGradeableItemGrade(g,which, d_score); + } + + //std::cout << "it makes it to exam data" << std::endl; + + nlohmann::json exam_data = grade_id["exam_data"]; + //std::cout << exam_data << std::endl; + if (!exam_data.empty()) { + //std::cout << "makes it into exam data" << std::endl; + int active = exam_data["active"].get(); + if (active == 1) { + + GLOBAL_ACTIVE_TEST_ZONE = k; + + for (nlohmann::json::iterator itr2 = (exam_data).begin(); itr2 != (exam_data).end(); itr2++) { + std::string token2 = itr2.key(); + //std::cout << token2 << std::endl; + if (token2 == "exam_title") { + std::string value = itr2.value(); + GLOBAL_EXAM_TITLE = value; + } else if (token2 == "exam_date") { + std::string value = itr2.value(); + GLOBAL_EXAM_DATE = value; + } else if (token2 == "exam_time") { + std::string value = itr2.value(); + GLOBAL_EXAM_TIME = value; + } else if (token2 == "exam_default_room") { + std::string value = itr2.value(); + GLOBAL_EXAM_DEFAULT_ROOM = value; + } else if (token2 == "min_overall_for_zone_assignment") { + float value = itr2.value(); + GLOBAL_MIN_OVERALL_FOR_ZONE_ASSIGNMENT = value; + } else if (token2 == "exam_seating") { + std::cout << "TOKEN IS EXAM SEATING" << std::endl; + std::string value = itr2.value(); + GLOBAL_EXAM_SEATING = value; + } else if (token2 == "seating_spacing") { + std::cout << "TOKEN IS SEATING SPACING" << std::endl; + std::string value = itr2.value(); + GLOBAL_SEATING_SPACING = value; + } else if (token2 == "exam_seating_count") { + std::cout << "TOKEN IS EXAM SEATING COUNT" << std::endl; + std::string value = itr2.value(); + GLOBAL_EXAM_SEATING_COUNT = value; + } else if (token2 == "left_right_handedness") { + std::cout << "TOKEN IS LEFT RIGHT HANDEDNESS" << std::endl; + std::string value = itr2.value(); + GLOBAL_LEFT_RIGHT_HANDEDNESS = value; + } + } + } + } + } + } + students.push_back(perfect); + students.push_back(student_average); + students.push_back(student_stddev); + students.push_back(lowest_a); + students.push_back(lowest_b); + students.push_back(lowest_c); + students.push_back(lowest_d); + + //std::cout << "3" << std::endl; + + // get display optioons + std::vector displays = j["display"].get >(); + for (std::size_t i=0; i displayBenchmark = j["display_benchmark"].get >(); + for (std::size_t i=0; i= 0.0 && LATE_DAY_PERCENTAGE_PENALTY <= 0.5); + } else if (token == "test_improvement_averaging_adjustment") { + TEST_IMPROVEMENT_AVERAGING_ADJUSTMENT = true; + } else if (token == "quiz_normalize_and_drop") { + QUIZ_NORMALIZE_AND_DROP = itr.value(); + } else if (token == "lowest_test_counts_half") { + LOWEST_TEST_COUNTS_HALF = true; + } else { + std::cout << "ERROR: unknown use " << token << std::endl; + exit(0); + } + } + + //std::cout << "9" << std::endl; +} + + +void MakeRosterFile(std::vector &students) { + + std::sort(students.begin(),students.end(),by_name); + + std::ofstream ostr("./iclicker_Roster.txt"); + + + for (unsigned int i = 0; i < students.size(); i++) { + std::string foo = "active"; + if (students[i]->getLastName() == "") continue; + if (students[i]->getSection() <= 0 || students[i]->getSection() > 10) continue; + if (students[i]->getGradeableItemGrade(GRADEABLE_ENUM::TEST,0).getValue() < 1) { + //std::cout << "STUDENT DID NOT TAKE TEST 1 " << students[i]->getUserName() << std::endl; + foo = "inactive"; + } + std::string room = students[i]->getExamRoom(); + std::string zone = students[i]->getExamZone(); + if (room == "") room = "DCC 308"; + if (zone == "") zone = "SEE INSTRUCTOR"; + + + +#if 0 + ostr + << std::left << std::setw(15) << students[i]->getLastName() + << std::left << std::setw(13) << students[i]->getPreferredName() + << std::left << std::setw(12) << students[i]->getUserName() + << std::left << std::setw(12) << room + << std::left << std::setw(10) << zone + << std::endl; + + ostr + << students[i]->getLastName() << "," + << students[i]->getPreferredName() << "," + << students[i]->getUserName() << std::endl; + +#else + + ostr + << students[i]->getSection() << "\t" + << students[i]->getLastName() << "\t" + << students[i]->getPreferredName() << "\t" + << students[i]->getUserName() << "\t" + //<< foo + << std::endl; + +#endif + } + +} + + +// defined in zone.cpp +void LoadExamSeatingFile(const std::string &zone_counts_filename, + const std::string &zone_assignments_filename, + const std::string &seating_spacing, + const std::string &left_right_handedness, + std::vector &students); + +void load_student_grades(std::vector &students); + +void load_bonus_late_day(std::vector &students, int which_lecture, std::string bonus_late_day_file); + +void processcustomizationfile(std::vector &students) { + + + std::ifstream istr(CUSTOMIZATION_FILE.c_str()); + assert (istr.good()); + nlohmann::json j = nlohmann::json::parse(istr); + GLOBAL_CUSTOMIZATION_JSON = j; + + + SetBenchmarkPercentage("lowest_a-",0.9); + SetBenchmarkPercentage("lowest_b-",0.8); + SetBenchmarkPercentage("lowest_c-",0.7); + SetBenchmarkPercentage("lowest_d",0.6); + + SetBenchmarkColor("extracredit","aa88ff"); // dark purple + SetBenchmarkColor("perfect" ,"c8c8ff"); // purple + SetBenchmarkColor("lowest_a-" ,"c8ffc8"); // green + SetBenchmarkColor("lowest_b-" ,"ffffc8"); // yellow + SetBenchmarkColor("lowest_c-" ,"ffc8c8"); // orange + SetBenchmarkColor("lowest_d" ,"ff0000"); // red + SetBenchmarkColor("failing" ,"c80000"); // dark red + + preprocesscustomizationfile(students); + + load_student_grades(students); + + std::string token,token2; + + std::string iclicker_remotes_filename; + std::vector > > iclicker_questions(MAX_LECTURES+1); + + for (nlohmann::json::iterator itr = j.begin(); itr != j.end(); itr++) { + token = itr.key(); + //std::cout << token << std::endl; + if (token == "section") { + // create sections + int counter = 0; + for (nlohmann::json::iterator itr2 = (itr.value()).begin(); itr2 != (itr.value()).end(); itr2++) { + std::string temp = itr2.key(); + int section = std::stoi(temp); + std::string section_name = itr2.value(); + std::cout << "MAKE ASSOCIATION " << section << " " << section_name << std::endl; + //assert (!validSection(section)); + sectionNames[section] = section_name; + if (sectionColors.find(section_name) == sectionColors.end()) { + if (counter == 0) { + sectionColors[section_name] = "ccffcc"; // lt green + } else if (counter == 1) { + sectionColors[section_name] = "ffcccc"; // lt salmon + } else if (counter == 2) { + sectionColors[section_name] = "ffffaa"; // lt yellow + } else if (counter == 3) { + sectionColors[section_name] = "ccccff"; // lt blue-purple + } else if (counter == 4) { + sectionColors[section_name] = "aaffff"; // lt cyan + } else if (counter == 5) { + sectionColors[section_name] = "ffaaff"; // lt magenta + } else if (counter == 6) { + sectionColors[section_name] = "88ccff"; // blue + } else if (counter == 7) { + sectionColors[section_name] = "cc88ff"; // purple + } else if (counter == 8) { + sectionColors[section_name] = "88ffcc"; // mint + } else if (counter == 9) { + sectionColors[section_name] = "ccff88"; // yellow green + } else if (counter == 10) { + sectionColors[section_name] = "ff88cc"; // pink + } else if (counter == 11) { + sectionColors[section_name] = "ffcc88"; // orange + } else if (counter == 12) { + sectionColors[section_name] = "ffff33"; // yellow + } else if (counter == 13) { + sectionColors[section_name] = "ff33ff"; // magenta + } else if (counter == 14) { + sectionColors[section_name] = "33ffff"; // cyan + } else if (counter == 15) { + sectionColors[section_name] = "6666ff"; // blue-purple + } else if (counter == 16) { + sectionColors[section_name] = "66ff66"; // green + } else if (counter == 17) { + sectionColors[section_name] = "ff6666"; // red + } else { + sectionColors[section_name] = "aaaaaa"; // grey + } + counter++; + } + } + } else if (token == "messages") { + // general message at the top of the file + for (nlohmann::json::iterator itr2 = (itr.value()).begin(); itr2 != (itr.value()).end(); itr2++) { + MESSAGES.push_back(*itr2); + } + } else if (token == "warning") { + // EWS early warning system [ per student ] + std::vector warning_list = j[token].get >(); + for (std::size_t i = 0; i(); + std::string message = warning["msg"].get(); + std::vector ids; + float value = warning["value"].get(); + nlohmann::json j_ids = warning["ids"]; + for (std::size_t k = 0; k < j_ids.size(); k++) { + ids.push_back(j_ids[k].get()); + } + + std::cout << "search for " << message << std::endl; + for (std::size_t S = 0; S < students.size(); S++) { + Student *s = students[S]; + if (!validSection(students[S]->getSection())) continue; + //std::cout << "student " << s->getUserName() << std::endl; + float v = 0; + for (std::size_t k = 0; k < ids.size(); k++) { + GRADEABLE_ENUM g; + int item; + //std::cout << "GRADEABLE_ENUM " << (int)g << " " << item << std::endl; + LookupGradeable(ids[k],g,item); + v += s->getGradeableItemGrade(g,item).getValue(); + } + + if (v < value) { + std::stringstream ss; + ss << " " << v << " < " << value; + + s->addWarning(message + ss.str()); + } + } + + } + + } else if (token == "recommend") { + // UTA/mentor recommendations [ per student ] + std::vector recommend_list = j[token].get >(); + for (std::size_t i = 0; i < recommend_list.size(); i++) { + nlohmann::json recommend_user = recommend_list[i]; + std::string username = recommend_user["user"].get(); + std::string message = recommend_user["msg"].get(); + Student *s = GetStudent(students,username); + if (s == NULL) { + std::cout << username << std::endl; + } + assert (s != NULL); + s->addRecommendation(message); + } + } else if (token == "note") { + // other grading note [ per student ] + std::vector note_list = j[token].get >(); + for (std::size_t i = 0; i < note_list.size(); i++) { + nlohmann::json note_user = note_list[i]; + std::string username = note_user["user"].get(); + std::string message = note_user["msg"].get(); + Student *s = GetStudent(students,username); + if (s == NULL) { + std::cout << username << std::endl; + } + assert (s != NULL); + s->addNote(message); + } + } else if (token == "earned_late_days") { + DISPLAY_LATE_DAYS = true; + std::vector earnedLateDays = j[token].get >(); + GLOBAL_earned_late_days.clear(); + for (std::size_t i = 0; i < earnedLateDays.size(); i++) { + float tmp = earnedLateDays[i]; + assert (GLOBAL_earned_late_days.size() == 0 || tmp > GLOBAL_earned_late_days.back()); + GLOBAL_earned_late_days.push_back(tmp); + } + } else if (token == "iclicker_ids") { + iclicker_remotes_filename = itr.value(); + } else if (token == "iclicker") { + for (nlohmann::json::iterator itr2 = (itr.value()).begin(); itr2 != (itr.value()).end(); itr2++) { + std::string temp = itr2.key(); + std::vector iclickerLectures = j[token][temp]; + int which_lecture = std::stoi(temp); + + //Each step through this loop is one {} line inside a lecture, the naming of lecture vs lectures here is confusing + for (std::size_t i = 0; i < iclickerLectures.size(); i++) { + nlohmann::json iclickerLecture = iclickerLectures[i]; + iclicker_questions[which_lecture].push_back(std::vector()); + + + int which_column = iclickerLecture["column"].get(); + std::string correct_answer = iclickerLecture["answer"].get(); + assert (which_lecture >= 1 && which_lecture <= MAX_LECTURES); + + + //Code to support multiple iClicker files for one question + nlohmann::json j_filenames = iclickerLecture["file"]; + std::vector filenames; + for (std::size_t k = 0; k < j_filenames.size(); k++) { + filenames.push_back(j_filenames[k].get()); + } + + for (std::size_t k=0; k audit_list = itr.value(); + for (std::size_t i = 0; i < audit_list.size(); i++) { + std::string username = audit_list[i]; + Student *s = GetStudent(students,username); + assert (s != NULL); + assert (s->getAudit() == false); + s->setAudit(); + s->addNote("AUDIT"); + } + } else if (token == "withdraw") { + std::vector withdraw_list = itr.value(); + for (std::size_t i = 0; i < withdraw_list.size(); i++) { + std::string username = withdraw_list[i]; + Student *s = GetStudent(students,username); + assert (s != NULL); + assert (s->getWithdraw() == false); + s->setWithdraw(); + s->addNote("LATE WITHDRAW"); + } + } else if (token == "independentstudy") { + std::vector independent_study_list = itr.value(); + for (std::size_t i = 0; i < independent_study_list.size(); i++) { + std::string username = independent_study_list[i]; + Student *s = GetStudent(students,username); + assert (s != NULL); + assert (s->getIndependentStudy() == false); + s->setIndependentStudy(); + s->addNote("INDEPENDENT STUDY"); + } + } else if (token == "manual_grade") { + for (nlohmann::json::iterator itr2 = (itr.value()).begin(); itr2 != (itr.value()).end(); itr2++) { + + std::string username = (itr2.value())["user"].get(); + std::string grade = (itr2.value())["grade"].get(); + std::string note = (itr2.value())["note"].get(); + + Student *s = GetStudent(students,username); + assert (s != NULL); + s->ManualGrade(grade,note); + } + } else if (token == "plagiarism") { + for (nlohmann::json::iterator itr2 = (itr.value()).begin(); itr2 != (itr.value()).end(); itr2++) { + std::string username = (itr2.value())["user"].get(); + std::string hw = (itr2.value())["gradeable"].get(); + float penalty = (itr2.value())["penalty"].get(); + //assert (hw >= 1 && hw <= 10); + assert (penalty >= -0.01 && penalty <= 1.01); + Student *s = GetStudent(students,username); + assert (s != NULL); + s->mossify(hw,penalty); + } + } else if (token == "final_cutoff") { + for (nlohmann::json::iterator itr2 = (itr.value()).begin(); itr2 != (itr.value()).end(); itr2++) { + std::string grade = itr2.key(); + float cutoff = itr2.value(); + assert (grade == "A" || + grade == "A-" || + grade == "B+" || + grade == "B" || + grade == "B-" || + grade == "C+" || + grade == "C" || + grade == "C-" || + grade == "D+" || + grade == "D"); + CUTOFFS[grade] = cutoff; + } + } else if (token == "gradeables") { + continue; + } else if (token == "bonus_latedays") { + nlohmann::json bonusJson = j[token]; + for (nlohmann::json::iterator itr2 = bonusJson.begin(); itr2 != bonusJson.end(); itr2++) { + std::string bonus = itr2.key(); + BONUS_WHICH_LECTURE = std::stoi(bonus); + BONUS_FILE = j[token][bonus]; + std::cout << "BONUS LATE DAYS" << std::endl; + + if (BONUS_FILE != "") { + load_bonus_late_day(students,BONUS_WHICH_LECTURE,BONUS_FILE); + } + } + } else { + if (token == "display" || token == "display_benchmark" || token == "benchmark_percent") { + continue; + } else if (token == "use" || token == "hackmaxprojects" || token == "benchmark_color") { + continue; + } + std::cout << "ERROR: UNKNOWN TOKEN X " << token << std::endl; + } + } + + if (GLOBAL_EXAM_SEATING_COUNT != "" && GLOBAL_EXAM_SEATING != "") { + LoadExamSeatingFile(GLOBAL_EXAM_SEATING_COUNT,GLOBAL_EXAM_SEATING,GLOBAL_SEATING_SPACING,GLOBAL_LEFT_RIGHT_HANDEDNESS,students); + } + MakeRosterFile(students); + MatchClickerRemotes(students, iclicker_remotes_filename); + AddClickerScores(students,iclicker_questions); +} + + + +void load_student_grades(std::vector &students) { + + Student *perfect = GetStudent(students,"PERFECT"); + assert (perfect != NULL); + + Student *student_average = GetStudent(students,"AVERAGE"); + assert (student_average != NULL); + + Student *student_stddev = GetStudent(students,"STDDEV"); + assert (student_stddev != NULL); + + + std::string command2 = "ls -1 " + RAW_DATA_DIRECTORY + "*.json > files_json.txt"; + + system(command2.c_str()); + + std::ifstream files_istr("files_json.txt"); + assert(files_istr); + std::string filename; + int count = 0; + while (files_istr >> filename) { + std::ifstream istr(filename.c_str()); + assert(istr.good()); + nlohmann::json j = nlohmann::json::parse(istr); + + Student *s = new Student(); + + count++; + + std::ifstream customization_istr(CUSTOMIZATION_FILE.c_str()); + assert (customization_istr.good()); + nlohmann::json customization_j = nlohmann::json::parse(customization_istr); + + std::string participation_gradeable_id = ""; + std::string participation_component = ""; + std::string understanding_gradeable_id = ""; + std::string understanding_component = ""; + std::string recommendation_gradeable_id = ""; + std::string recommendation_text = ""; + + if (customization_j.find("participation") != customization_j.end()) { + participation_gradeable_id = customization_j["participation"]["id"].get(); + participation_component = customization_j["participation"]["component"].get(); + } + if (customization_j.find("understanding") != customization_j.end()) { + understanding_gradeable_id = customization_j["understanding"]["id"].get(); + understanding_component = customization_j["understanding"]["component"].get(); + } + if (customization_j.find("recommendation") != customization_j.end()) { + recommendation_gradeable_id = customization_j["recommendation"]["id"].get(); + recommendation_text = customization_j["recommendation"]["text"].get(); + } + + for (nlohmann::json::iterator itr = j.begin(); itr != j.end(); itr++) { + std::string token = itr.key(); + // std::cout << "token: " << token << "!" << std::endl; + GRADEABLE_ENUM g; + bool gradeable_enum_success = string_to_gradeable_enum(token,g); + if (!gradeable_enum_success && token != "Other" && token != "rubric" && token != "Test") { + // non gradeables + if (token == "user_id") { + s->setUserName(j[token].get()); + } else if (token == "legal_first_name") { + s->setLegalFirstName(j[token].get()); + } else if (token == "preferred_first_name") { + if (!j[token].is_null()) { + s->setPreferredFirstName(j[token].get()); + } + } else if (token == "last_name") { + s->setLastName(j[token].get()); + } else if (token == "last_update") { + s->setLastUpdate(j[token].get()); + } else if (token == "registration_section") { + int a; + if(!j[token].is_null()) { + a = j[token].get(); + if (!validSection(a)) { + // the "drop" section is 0 (really should be NULL) + if (a != 0) { + std::cerr << "WARNING: invalid section " << a << std::endl; + } + } + } + else{ + a = 0; + } + s->setSection(a); + + } else if (token == "default_allowed_late_days") { + int value = 0; + if (!j[token].is_null()) { + if (j[token].is_string()) { + std::string s_value = j[token].get(); + value = std::stoi(s_value); + } else { + value = j[token].get(); + } + } + s->setDefaultAllowedLateDays(value); + if (value > 0) { + DISPLAY_LATE_DAYS = true; + } + } else { + std::cout << "UNKNOWN TOKEN Y '" << token << "'" << std::endl; + exit(0); + } + } else { + for (nlohmann::json::iterator itr2 = (itr.value()).begin(); itr2 != (itr.value()).end(); itr2++) { + int which; + bool invalid = false; + std::string gradeable_id = (*itr2).value("id","ERROR BAD ID"); + std::string gradeable_name = (*itr2).value("name",gradeable_id); + std::string status; + if (itr2 != (itr.value()).end() && (*itr2).is_string()) { + status = (*itr2).value("status","NOT ELECTRONIC"); + } else { + status = "NO SUBMISSION"; + } + float score = (*itr2).value("score",0.0); + if (status.find("Bad") != std::string::npos) { + assert (score == 0); + } else { + assert (status == "NO SUBMISSION" || status == "NOT ELECTRONIC" || status == "Good" || status == "Late"); + } + + std::string other_note = ""; + nlohmann::json::iterator itr3 = itr2->find("components"); + if (itr3 != itr2->end()) { + for (std::size_t i = 0; i < itr3->size(); i++) { + std::string component_title = "placeholder"; + assert ((*itr3)[i].find("title") != (*itr3)[i].end()); + if ((*itr3)[i].find("title")->is_string()) { + component_title = (*itr3)[i].value("title",""); + } else { + // title is sometimes a number... convert to string + assert ((*itr3)[i].find("title")->is_number()); + component_title = std::to_string((*itr3)[i].value("title",0)); + } + std::string component_comment = (*itr3)[i].value("comment",""); + if (component_title == "Notes" && component_comment != "") { + other_note += " " + component_comment; + } + } + } + + // Search through the gradeable categories as needed to find where this item belongs + // (e.g. project may be prefixed by "hw", or exam may be prefixed by "test") + for (unsigned int i = 0; i < ALL_GRADEABLES.size(); i++) { + GRADEABLE_ENUM g2 = ALL_GRADEABLES[i]; + if (GRADEABLES[g2].hasCorrespondence(gradeable_id)) { + g = g2; + } + } + if (!GRADEABLES[g].hasCorrespondence(gradeable_id)) { + invalid = true; + //std::cerr << "ERROR! cannot find a category for this item " << gradeable_id << std::endl; + } else { + invalid = false; + const std::pair& c = GRADEABLES[g].getCorrespondence(gradeable_id); + which = c.first; + if (c.second == "") { + GRADEABLES[g].setCorrespondenceName(gradeable_id,gradeable_name); + } else { + assert (c.second == gradeable_name); + } + } + + if (gradeable_id == GLOBAL_recommend_id) { + nlohmann::json::iterator rec = itr2->find("text"); + if (rec != itr2->end()) { + for (std::size_t i = 0; i < (*rec).size(); i++) { + for (auto itr8 = (*rec)[i].begin(); itr8 != (*rec)[i].end(); itr8++) { + std::string r = itr8.value(); + s->addRecommendation(std::string(" ") + r); + } + } + } + } + + if (!invalid) { + assert (which >= 0); + assert (score >= 0.0); + int late_days_charged = itr2->value("days_charged",0); + if (itr3 != itr2->end()) { + if (score <= 0) { + if (s->getUserName() != "") { + assert (late_days_charged == 0); + } + } + } + float clamp = -1; + clamp = GRADEABLES[g].getClamp(gradeable_id); + if (clamp > 0) { + score = std::min(clamp,score); + } + if (status.find("Bad") != std::string::npos) { + assert (late_days_charged == 0); + } + if (GRADEABLES[g].isReleased(gradeable_id)) { + s->setGradeableItemGrade(g,which,score,late_days_charged,other_note,status); + } + } + + } + } + } + + float participation = 0; + float understanding = 0; + std::string recommendation; + if (participation_gradeable_id != "") { + std::vector notes = j["Note"]; + for (int x = 0; x < notes.size(); x++) { + if (notes[x]["id"] == participation_gradeable_id) { + nlohmann::json scores = notes[x]["component_scores"]; + for (int y = 0; y < scores.size(); y++) { + if (scores[y].find(participation_component) != scores[y].end()) { + participation = scores[y][participation_component].get(); + } + } + } + } + } + if (understanding_gradeable_id != "") { + std::vector notes = j["Note"]; + for (int x = 0; x < notes.size(); x++) { + if (notes[x]["id"] == understanding_gradeable_id) { + nlohmann::json scores = notes[x]["component_scores"]; + for (int y = 0; y < scores.size(); y++) { + if (scores[y].find(understanding_component) != scores[y].end()) { + understanding = scores[y][understanding_component].get(); + } + } + } + } + } + if (recommendation_gradeable_id != "") { + std::vector notes = j["Note"]; + for (int x = 0; x < notes.size(); x++) { + if (notes[x]["id"] == recommendation_gradeable_id) { + nlohmann::json values = notes[x]["text"]; + for (int y = 0; y < values.size(); y++) { + if (values[y].find(recommendation_text) != values[y].end()) { + if (values[y][recommendation_text].is_string()) { + recommendation = values[y][recommendation_text].get(); + } else { + std::cout << "error in recommendation text type for " << s->getUserName() << std::endl; + } + } + } + } + } + } + + s->setParticipation(participation); + s->setUnderstanding(understanding); + if (recommendation != "") { + s->addRecommendation(recommendation); + } + + + // lookup and compute resubmit/replacement gradeable items + for (unsigned int i = 0; i < ALL_GRADEABLES.size(); i++) { + GRADEABLE_ENUM g = ALL_GRADEABLES[i]; + for (int which = 0; which < GRADEABLES[g].getCount(); which++) { + std::string original_id; + std::string resubmit_id; + float autograde_replacement_percentage; + GRADEABLES[g].isResubmit(which,original_id,resubmit_id,autograde_replacement_percentage); + if (original_id == "") continue; + + float original_autograde = 0; + float original_tagrade = 0; + float resubmit_autograde = 0; + + for (nlohmann::json::iterator itr = j.begin(); itr != j.end(); itr++) { + std::string token = itr.key(); + + if (itr.value().is_array()) { + for (int e = 0; e < itr.value().size(); e++) { + if (!itr.value()[e].is_object()) continue; + if (itr.value()[e].value("id","") == original_id) { + original_autograde = itr.value()[e].value("autograding_score",0.0); + original_tagrade = itr.value()[e].value("tagrading_score",0.0); + + // WORKAROUND -- bug in gradesummaries tagrading for unsubmitted assignment + // ALSO NEED TO DEAL WITH VERSION CONFLICTS BETTER + float original_score = itr.value()[e].value("score",0.0); + if (original_score < original_tagrade + original_autograde) { + original_tagrade = 0; + original_autograde = 0; + } + //std::cout << "student " << std::left << std::setw(10) << s->getUserName() << std::endl; + //std::cout << " CHECK " << original_score << " " << original_autograde << " " << original_tagrade << std::endl; + assert (fabs( original_score -( original_autograde + original_tagrade)) < 0.1); + // END WORKAROUND + + } + if (itr.value()[e].value("id","") == resubmit_id) { + resubmit_autograde = itr.value()[e].value("autograding_score",0); + // SIMILAR WORKAROUND + float resubmit_score = itr.value()[e].value("score",0); + if (resubmit_score == 0) { + resubmit_autograde = 0; + } + // END WORKAROUND + + } + } + } + } + float new_autograde = + original_autograde*(1-autograde_replacement_percentage) + + resubmit_autograde*(autograde_replacement_percentage); + float score = + original_tagrade + + std::max(original_autograde,new_autograde); + if (original_autograde < new_autograde) { + std::cout << "student " << std::left << std::setw(10) << s->getUserName() << " had grade increase for "; + std::cout << original_id << ": " + << std::right << std::setw(5) << original_tagrade << " + " + << std::right << std::setw(5) << original_autograde << " / " + << std::right << std::setw(5) << resubmit_autograde << " -> " + << std::right << std::setw(5) << new_autograde << " = " + << std::right << std::setw(5) << score << std::endl; + } + s->setGradeableItemGrade(g,which,score); + } + } + + students.push_back(s); + } + +} + + +void start_table_open_file(bool full_details, + const std::vector &students, int S, int month, int day, int year, + enum GRADEABLE_ENUM which_gradeable_enum); + +void start_table_output(bool full_details, + const std::vector &students, int S, int month, int day, int year, + Student *sp, Student *sa, Student *sb, Student *sc, Student *sd); + +void end_table(std::ofstream &ostr, bool full_details, Student *s); + + + +// ============================================================================================= +// ============================================================================================= +// ============================================================================================= + + + +void output_helper(std::vector &students, std::string &GLOBAL_sort_order) { + + Student *sp = GetStudent(students,"PERFECT"); + Student *student_average = GetStudent(students,"AVERAGE"); + Student *student_stddev = GetStudent(students,"STDDEV"); + Student *sa = GetStudent(students,"LOWEST A-"); + Student *sb = GetStudent(students,"LOWEST B-"); + Student *sc = GetStudent(students,"LOWEST C-"); + Student *sd = GetStudent(students,"LOWEST D"); + assert (sp != NULL); + assert (student_average != NULL); + assert (student_stddev != NULL); + assert (sa != NULL); + assert (sb != NULL); + assert (sc != NULL); + assert (sd != NULL); + + std::string command = "rm -f " + OUTPUT_FILE + " " + INDIVIDUAL_FILES_OUTPUT_DIRECTORY + "*html"; + system(command.c_str()); + command = "rm -f " + OUTPUT_FILE + " " + INDIVIDUAL_FILES_OUTPUT_DIRECTORY + "*json"; + system(command.c_str()); + + // get todays date; + time_t now = time(0); + struct tm * now2 = localtime( & now ); + int month = now2->tm_mon+1; + int day = now2->tm_mday; + int year = now2->tm_year+1900; + + start_table_open_file(true,students,-1,month,day,year,GRADEABLE_ENUM::NONE); + start_table_output(true,students,-1,month,day,year, sp,sa,sb,sc,sd); + + int next_rank = 1; + int last_section = -1; + + for (int S = 0; S < (int)students.size(); S++) { + //int rank = next_rank; + if (students[S] == sp || + students[S] == student_average || + students[S] == student_stddev || + students[S] == sa || + students[S] == sb || + students[S] == sc || + students[S] == sd || + // students[S]->getUserName() == "" || + !validSection(students[S]->getSection())) { + //rank = -1; + } else { + if (GLOBAL_sort_order == std::string("by_section") && + last_section != students[S]->getSection()) { + last_section = students[S]->getSection(); + //next_rank = rank = 1; + next_rank = 1; + } + next_rank++; + } + Student *this_student = students[S]; + assert (this_student != NULL); + } + + + for (int S = 0; S < (int)students.size(); S++) { + + nlohmann::json mj; + + std::string file2 = INDIVIDUAL_FILES_OUTPUT_DIRECTORY + students[S]->getUserName() + "_message.html"; + std::string file2_json = INDIVIDUAL_FILES_OUTPUT_DIRECTORY + students[S]->getUserName() + "_message.json"; + std::ofstream ostr2(file2.c_str()); + std::ofstream ostr2_json(file2_json.c_str()); + + mj["username"] = students[S]->getUserName(); + mj["building"] = "DCC"; + mj["room"] = "308"; + mj["zone"] = "A"; + + ostr2_json << mj.dump(4); + + +#if 0 + if (students[S]->hasPriorityHelpStatus()) { + ostr2 << "

PRIORITY HELP QUEUE

" << std::endl; + priority_stream << std::left << std::setw(15) << students[S]->getSection() + << std::left << std::setw(15) << students[S]->getUserName() + << std::left << std::setw(15) << students[S]->getPreferredName() + << std::left << std::setw(15) << students[S]->getLastName() << std::endl; + + + } + + if (MAX_ICLICKER_TOTAL > 0) { + ostr2 << "recent iclicker = " << students[S]->getIClickerRecent() << " / 12.0" << std::endl; + } +#endif + + + nlohmann::json::iterator special_message_itr = GLOBAL_CUSTOMIZATION_JSON.find("special_message"); + nlohmann::json special_message; + if (special_message_itr != GLOBAL_CUSTOMIZATION_JSON.end()) { + special_message = *special_message_itr; + } + + PrintExamRoomAndZoneTable(ostr2,students[S],special_message); + + int prev = students[S]->getAllowedLateDays(0); + + for (int i = 1; i <= MAX_LECTURES; i++) { + int tmp = students[S]->getAllowedLateDays(i); + if (prev != tmp) { + + std::map::iterator itr = LECTURE_DATE_CORRESPONDENCES.find(i); + if (itr == LECTURE_DATE_CORRESPONDENCES.end()) { + continue; + } + Date &d = itr->second; + late_days_stream << students[S]->getUserName() << "," + << d.getStringRep() << "," + << tmp << std::endl; + prev = tmp; + } + } + } +} + + +// ============================================================================================= +// ============================================================================================= +// ============================================================================================= + +void load_bonus_late_day(std::vector &students, + int which_lecture, + std::string bonus_late_day_file) { + + std::cout << "LOAD BONUS LATE" << std::endl; + + std::ifstream istr(bonus_late_day_file.c_str()); + if (!istr.good()) { + std::cerr << "ERROR! could not open " << bonus_late_day_file << std::endl; + exit(1); + } + + std::string username; + while (istr >> username) { + Student *s = GetStudent(students,username); + if (s == NULL) { + //std::cerr << "ERROR! bad username " << username << " cannot give bonus late day " << std::endl; + //exit(1); + } else { + //std::cout << "BONUS DAY FOR USER " << username << std::endl; + s->add_bonus_late_day(which_lecture); + //std::cout << "add bonus late day for " << username << std::endl; + } + } + +} + + + +int main(int argc, char* argv[]) { + + //std::string sort_order = "by_overall"; + + if (argc > 1) { + assert (argc == 2); + GLOBAL_sort_order = argv[1]; + } + + std::vector students; + processcustomizationfile(students); + + // ====================================================================== + // SUGGEST CURVES + suggest_curves(students); + + // ====================================================================== + // SORT + std::sort(students.begin(),students.end(),by_overall); + + + if (GLOBAL_sort_order == std::string("by_overall")) { + std::sort(students.begin(),students.end(),by_overall); + } else if (GLOBAL_sort_order == std::string("by_name")) { + std::sort(students.begin(),students.end(),by_name); + } else if (GLOBAL_sort_order == std::string("by_section")) { + std::sort(students.begin(),students.end(),by_section); + } else if (GLOBAL_sort_order == std::string("by_zone")) { + + DISPLAY_INSTRUCTOR_NOTES = false; + DISPLAY_EXAM_SEATING = true; + DISPLAY_MOSS_DETAILS = false; + DISPLAY_FINAL_GRADE = false; + DISPLAY_GRADE_SUMMARY = false; + DISPLAY_GRADE_DETAILS = false; + DISPLAY_ICLICKER = false; + + std::sort(students.begin(),students.end(),by_name); + + } else if (GLOBAL_sort_order == std::string("by_iclicker")) { + std::sort(students.begin(),students.end(),by_iclicker); + + DISPLAY_ICLICKER = true; + + } else if (GLOBAL_sort_order == std::string("by_test_and_exam")) { + std::sort(students.begin(),students.end(),by_test_and_exam); + + } else { + assert (GLOBAL_sort_order.size() > 3); + GRADEABLE_ENUM g; + // take off "by_" + std::string tmp = GLOBAL_sort_order.substr(3,GLOBAL_sort_order.size()-3); + bool success = string_to_gradeable_enum(tmp,g); + if (success) { + std::sort(students.begin(),students.end(), GradeableSorter(g) ); + } + else { + std::cerr << "UNKNOWN SORT OPTION " << GLOBAL_sort_order << std::endl; + std::cerr << " Usage: " << argv[0] << " [ by_overall | by_name | by_section | by_zone | by_iclicker | by_lab | by_exercise | by_reading | by_hw | by_test | by_exam | by_test_and_exam ]" << std::endl; + exit(1); + } + } + + + + // ====================================================================== + // COUNT + + for (unsigned int s = 0; s < students.size(); s++) { + + Student *this_student = students[s]; + + if (this_student->getLastName() == "" || this_student->getFirstName() == "") { + continue; + } + if (this_student->getAudit()) { + auditors++; + continue; + } + + Student *sd = GetStudent(students,"LOWEST D"); + + if (validSection(this_student->getSection())) { + std::string student_grade = this_student->grade(false,sd); + grade_counts[student_grade]++; + grade_avg[student_grade]+=this_student->overall(); + } else { + dropped++; + } + if (GRADEABLES[GRADEABLE_ENUM::EXAM].getCount() != 0) { + if (this_student->getGradeableItemGrade(GRADEABLE_ENUM::EXAM,GRADEABLES[GRADEABLE_ENUM::EXAM].getCount()-1).getValue() > 0) { + took_final++; + } + } + + } + + int runningtotal = 0; + for (std::map::iterator itr = grade_counts.begin(); + itr != grade_counts.end(); itr++) { + runningtotal += itr->second; + + grade_avg[itr->first] /= float(itr->second); + } + + // ====================================================================== + // OUTPUT + + output_helper(students,GLOBAL_sort_order); + +} + + +void suggest_curves(std::vector &students) { + + Student *student_average = GetStudent(students,"AVERAGE"); + Student *student_stddev = GetStudent(students,"STDDEV"); + + for (unsigned int i = 0; i < ALL_GRADEABLES.size(); i++) { + GRADEABLE_ENUM g = ALL_GRADEABLES[i]; + + for (int item = 0; item < GRADEABLES[g].getCount(); item++) { + + std::string gradeable_id = GRADEABLES[g].getID(item); + if (gradeable_id == "") continue; + + const std::string& gradeable_name = GRADEABLES[g].getCorrespondence(gradeable_id).second; + + std::cout << gradeable_to_string(g) << " " << gradeable_id << " " << gradeable_name/* << " statistics & suggested curve"*/ << std::endl; + std::vector scores; + + std::map section_counts; + + for (unsigned int S = 0; S < students.size(); S++) { + if (students[S]->getSection() > 0 && students[S]->getGradeableItemGrade(g,item).getValue() > 0) { + scores.push_back(students[S]->getGradeableItemGrade(g,item).getValue()); + section_counts[students[S]->getSection()]++; + } + } + if (scores.size() > 0) { + //std::cout << " " << scores.size() << " submitted" << std::endl; + std::sort(scores.begin(),scores.end()); + float sum = 0; + for (unsigned int x = 0; x < scores.size(); x++) { + sum+=scores[x]; + } + float average = sum / float(scores.size()); + std::cout << " average=" << std::setprecision(2) << std::fixed << average; + + student_average->setGradeableItemGrade(g,item,average); + + sum = 0; + for (unsigned int x = 0; x < scores.size(); x++) { + sum+=(average-scores[x])*(average-scores[x]); + } + float stddev = sqrt(sum/float(scores.size())); + std::cout << " stddev=" << std::setprecision(2) << std::fixed << stddev; + student_stddev->setGradeableItemGrade(g,item,stddev); + + std::cout << " suggested curve:"; + + std::cout << " A- cutoff=" << scores[int(0.70*scores.size())]; + std::cout << " B- cutoff=" << scores[int(0.45*scores.size())]; + std::cout << " C- cutoff=" << scores[int(0.20*scores.size())]; + std::cout << " D cutoff=" << scores[int(0.10*scores.size())]; + std::cout << std::endl; + } + + + int total = 0; + std::cout << " "; + for (std::map::iterator itr = section_counts.begin(); itr != section_counts.end(); itr++) { + std::cout << " sec#" << itr->first << "=" << itr->second << " "; + total += itr->second; + } + std::cout << " TOTAL = " << total << std::endl; + } + } +} + +// ============================================================================================= +// ============================================================================================= +// ============================================================================================= + diff --git a/output.cpp b/output.cpp new file mode 100644 index 0000000..534d5a4 --- /dev/null +++ b/output.cpp @@ -0,0 +1,1234 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "student.h" +#include "iclicker.h" +#include "grade.h" +#include "table.h" +#include "benchmark.h" + +#include + +#define grey_divider "aaaaaa" + +#include "constants_and_globals.h" + +extern std::string OUTPUT_FILE; +extern std::string ALL_STUDENTS_OUTPUT_DIRECTORY; + +extern Student* AVERAGE_STUDENT_POINTER; +extern Student* STDDEV_STUDENT_POINTER; + +extern std::string GLOBAL_sort_order; + +extern int GLOBAL_ACTIVE_TEST_ZONE; + +// ========================================================== + +std::string HEX(int h) { + std::stringstream ss; + ss << std::hex << std::setw(2) << std::setfill('0') << h; + return ss.str(); +} + +int UNHEX(std::string s) { + assert (s.size() == 2); + int h; + std::stringstream ss(s); + ss >> std::hex >> h; + assert (h >= 0 && h <= 255); + return h; +} + + +// colors for grades +const std::string GradeColor(const std::string &grade) { + if (grade == "A" ) return HEX(200)+HEX(200)+HEX(255); + else if (grade == "A-") return HEX(200)+HEX(235)+HEX(255); + else if (grade == "B+") return HEX(219)+HEX(255)+HEX(200); + else if (grade == "B" ) return HEX(237)+HEX(255)+HEX(200); + else if (grade == "B-") return HEX(255)+HEX(255)+HEX(200); + else if (grade == "C+") return HEX(255)+HEX(237)+HEX(200); + else if (grade == "C" ) return HEX(255)+HEX(219)+HEX(200); + else if (grade == "C-") return HEX(255)+HEX(200)+HEX(200); + else if (grade == "D+") return HEX(255)+HEX(100)+HEX(100); + else if (grade == "D" ) return HEX(255)+HEX( 0)+HEX( 0); + else if (grade == "F" ) return HEX(200)+HEX( 0)+HEX( 0); + else return "ffffff"; +} + +// ========================================================== + +float compute_average(const std::vector &vals) { + assert (vals.size() > 0); + float total = 0; + for (std::size_t i = 0; i < vals.size(); i++) { + total += vals[i]; + } + return total / float (vals.size()); +} + + +float compute_stddev(const std::vector &vals, float average) { + assert (vals.size() > 0); + float total = 0; + for (std::size_t i = 0; i < vals.size(); i++) { + total += (vals[i]-average)*(vals[i]-average); + } + return sqrt(total / float (vals.size()) ); +} + +// ========================================================== + +int convertYear(const std::string &major) { + if (major == "FR") return 1; + if (major == "SO") return 2; + if (major == "JR") return 3; + if (major == "SR") return 4; + if (major == "FY") return 5; + if (major == "GR") return 6; + else return 10; +} + +int convertMajor(const std::string &major) { + if (major == "CSCI") return 20; + if (major == "ITWS" || major == "ITEC") return 19; + if (major == "CSYS") return 18; + if (major == "GSAS") return 17; + if (major == "MATH") return 16; + if (major == "COGS" || major == "PSYC") return 15; + if (major == "ELEC") return 14; + if (major == "PHYS" || major == "APHY") return 13; + if (major == "BMGT" || + major == "ISCI" || + major == "ENGR" || + major == "USCI" || + major == "DSIS" || + major == "ECON" || + major == "EART" || + major == "CHEG" || + major == "MECL" || + major == "MGTE" || + major == "UNGS" || + major == "BMED" || + major == "MECL" || + major == "BFMB" || + major == "ARCH" || + major == "FERA" || + major == "CHEM" || + major == "MGMT" || + major == "NUCL" || + major == "MATL" || + major == "") return 0; + else return 10; +} + +// ========================================================== + +class Color { +public: + Color(int r_=0, int g_=0, int b_=0) : r(r_),g(g_),b(b_) {} + Color(const std::string& s) { + r = UNHEX(s.substr(0,2)); + g = UNHEX(s.substr(2,2)); + b = UNHEX(s.substr(4,2)); + } + int r,g,b; +}; + +std::string coloritcolor(float val, + float perfect, + float a, + float b, + float c, + float d) { + + //check for nan + if (val != val) return "ffffff"; + if (std::isinf(val)) return "00ff00"; + + //std::cout << "coloritcolor " << val << " " << perfect << " " << a << " " << b << " " << c << " " << d << std::endl; + assert (perfect >= a && + a >= b && + b >= c && + c >= d && + d >= 0); + + if (val < 0.00001) return "ffffff"; + else if (val > perfect) return GetBenchmarkColor("extracredit"); + else { + float alpha; + Color c1,c2; + + static Color perfect_color(GetBenchmarkColor("perfect")); + static Color a_color(GetBenchmarkColor("lowest_a-")); + static Color b_color(GetBenchmarkColor("lowest_b-")); + static Color c_color(GetBenchmarkColor("lowest_c-")); + static Color d_color(GetBenchmarkColor("lowest_d")); + + if (val >= a) { + if (fabs(perfect-a) < 0.0001) alpha = 0; + else alpha = (perfect-val)/float(perfect-a); + c1 = perfect_color; + c2 = a_color; + } + else if (val >= b) { + if (fabs(a-b) < 0.0001) alpha = 0; + else alpha = (a-val)/float(a-b); + c1 = a_color; + c2 = b_color; + } + else if (val >= c) { + if (fabs(b-c) < 0.0001) alpha = 0; + else alpha = (b-val)/float(b-c); + c1 = b_color; + c2 = c_color; + } + else if (val >= d) { + if (fabs(c-d) < 0.0001) alpha = 0; + else alpha = (c-val)/float(c-d); + c1 = c_color; + c2 = d_color; + } + else { + return GetBenchmarkColor("failing"); + } + + float red = (1-alpha) * c1.r + (alpha) * c2.r; + float green = (1-alpha) * c1.g + (alpha) * c2.g; + float blue = (1-alpha) * c1.b + (alpha) * c2.b; + + return HEX(red) + HEX(green) + HEX(blue); + + } +} + +void coloritcolor(std::ostream &ostr, + float val, + float perfect, + float a, + float b, + float c, + float d) { + ostr << coloritcolor(val,perfect,a,b,c,d); +} + +void colorit_year(std::ostream &ostr, const std::string& s) { + if (s == "FR") { + ostr << "" << s << ""; + } else if (s == "SO") { + ostr << "" << s << ""; + } else if (s == "JR") { + ostr << "" << s << ""; + } else if (s == "SR") { + ostr << "" << s << ""; + } else if (s == "GR") { + ostr << "" << s << ""; + } else if (s == "FY") { + ostr << "" << s << ""; + } else if (s == "") { + ostr << " "; + } else { + std::cout << "EXIT " << s << std::endl; + exit(0); + } +} + + + +void colorit_major(std::ostream &ostr, const std::string& s) { + int m = convertMajor(s); + ostr << "" << s << ""; +} + + + +void colorit_section(std::ostream &ostr, + int section, bool for_instructor, const std::string &color) { + + std::string section_name; + + if (validSection(section)) + section_name = sectionNames[section]; + std::string section_color = sectionColors[section_name]; + + if (section == 0) { + section_color=color; + } + + if (for_instructor) { + if (section != 0) { + ostr << "" << section << " (" << section_name << ")"; + } else { + ostr << " " << std::endl; + } + } else { + if (section != 0) { + ostr << "" << section << ""; + } else { + ostr << " " << std::endl; + } + } + +} + +void colorit_section2(int section, std::string &color, std::string &label) { + std::string section_name; + if (validSection(section)) { + section_name = sectionNames[section]; + color = sectionColors[section_name]; + std::stringstream ss; + ss << section << " (" << sectionNames[section] << ")"; + label = ss.str(); + } +} + + + +void colorit(std::ostream &ostr, + float val, + float perfect, + float a, + float b, + float c, + float d, + int precision=1, + bool centered=false, + std::string bonus_text="") { + if (centered) + ostr << ""; + if (val < 0.0000001) { + ostr << " "; + } else if (precision == 1) { + ostr << std::dec << val << " " << bonus_text; + } else { + assert (precision == 0); + ostr << std::dec << (int)val; + } + ostr << ""; +} + +// ========================================================== + +void PrintExamRoomAndZoneTable(std::ofstream &ostr, Student *s, const nlohmann::json &special_message) { + + if (special_message.size() > 0) { + + ostr << "\n"; + ostr << "\n"; + ostr << "
\n"; + ostr << "\n"; + + assert (special_message.find("title") != special_message.end()); + std::string title = special_message.value("title","MISSING TITLE"); + + assert (special_message.find("description") != special_message.end()); + std::string description = special_message.value("description","provided_files.zip"); + + ostr << "

" << title << "

" << std::endl; + + assert (special_message.find("files") != special_message.end()); + nlohmann::json files = *(special_message.find("files")); + int num_files = files.size(); + assert (num_files >= 1); + + std::string username = s->getUserName(); + int A = 54059; /* a prime */ + int B = 76963; /* another prime */ + int FIRSTH = 37; /* also prime */ + + unsigned int tmp = FIRSTH; + for (std::size_t i = 0; i < username.size(); i++) { + tmp = (tmp * A) ^ (username[i] * B); + s++; + } + + int which = (tmp % num_files)+1; + std::string filename = files.value(std::to_string(which),""); + assert (filename != ""); + + ostr << " \n"; + ostr << "
" << description << "
\n"; + ostr << "
\n"; + } + + // ============================================================== + + + if ( DISPLAY_EXAM_SEATING == false) return; + + std::string room = GLOBAL_EXAM_DEFAULT_ROOM; + std::string zone = "SEE INSTRUCTOR"; + std::string time = GLOBAL_EXAM_TIME; + std::string row = ""; + std::string seat = ""; + if (s->getSection() == 0) { + //room = ""; + //zone = ""; + time = ""; + } + if (s->getExamRoom() == "") { + //std::cout << "NO ROOM FOR " << s->getUserName() << std::endl; + } else { + room = s->getExamRoom(); + zone = s->getExamZone(); + row = s->getExamRow(); + seat = s->getExamSeat(); + if (s->getExamTime() != "") { + time = s->getExamTime(); + } + } + if (zone == "SEE_INSTRUCTOR") { + zone = "SEE INSTRUCTOR"; + } + + +#if 1 + + ostr << "\n"; + // ostr << "
\n"; + ostr << "\n"; + + if (s->getExamZoneImage() != "") { + ostr << "\n"; + } + + + ostr << "
\n"; + ostr << "\n"; + ostr << " \n"; + ostr << " \n"; + ostr << " \n"; + ostr << " \n"; + if (row != "N/A" && row !="") { + ostr << " \n"; + } + if (seat != "N/A" && seat !="") { + ostr << " \n"; + } + ostr << "
" << GLOBAL_EXAM_TITLE << "
" << GLOBAL_EXAM_DATE << "" << time << "
Your room assignment: " << room << "
Your zone assignment: " << zone << "
Your row assignment: " << row << "
Your seat assignment: " << seat << "
\n"; + ostr << "
getExamZoneImage() + "\">
\n"; + +#else + + + ostr << "\n"; + ostr << "\n"; + ostr << "
\n"; + ostr << "\n"; + ostr << " \n"; + // ostr << " \n"; + //ostr << " \n"; + + + + + if (zone == "SEE INSTRUCTOR") { + zone = "10"; + } + + std::string foo = "http://www.cs.rpi.edu/academics/courses/fall15/csci1200/hw/10_pokemon/"; + + ostr << " \n"; + ostr << " \n"; + ostr << " \n"; + ostr << " \n"; + ostr << " \n"; + + ostr << "
" << GLOBAL_EXAM_TITLE << "
" << GLOBAL_EXAM_DATE << "" << time << "
Your room assignment: " << room << "
Your list assignment: List" << zone << ".txt
Small Input: PokedexSmall" << zone << ".txt
Small Input Obfuscate: PokedexSmallObfuscate" << zone << ".txt
Small Output Obfuscate: OutputSmallObfuscate" << zone << ".txt
Small Output Obfuscate w/ Breeding: OutputSmallObfuscateBreeding" << zone << ".txt
\n"; + ostr << "
\n"; + + + +#endif + + + std::string x1 = s->getExamZone(); + std::string x2 = s->getZone(GLOBAL_ACTIVE_TEST_ZONE); + + if (x2.size() > 0) { + assert (x1.size() > 0); + if (x1.size() > 1 || std::toupper(x1[0]) != std::toupper(x2[0]) || x2.find('?') != std::string::npos || x2.size() == 1) { + std::cout << "WRONG ZONE" << s->getUserName() << " " << x1 << " " < &students, int rank, int month, int day, int year, + enum GRADEABLE_ENUM which_gradeable_enum) { + + /* + ostr.exceptions ( std::ofstream::failbit | std::ofstream::badbit ); + try { + ostr.open(filename.c_str()); + } + catch (std::ofstream::failure e) { + std::cout << "FAILED TO OPEN " << filename << std::endl; + std::cerr << "Exception opening/reading file"; + exit(0); + } + */ +} + + +void SelectBenchmarks(std::vector &select_students, const std::vector &students, + Student *sp, Student *sa, Student *sb, Student *sc, Student *sd) { + int myrow = 1; + + int offset = select_students.size(); + select_students.resize(select_students.size()+NumVisibleBenchmarks()); + + for (unsigned int stu= 0; stu < students.size(); stu++) { + std::string default_color="ffffff"; + Student *this_student = students[stu]; + myrow++; + + int which; + + if (this_student->getLastName() == "") { + if (this_student == sp) { + which = WhichVisibleBenchmark("perfect"); + if (which >= 0) select_students[offset+which]=myrow; + } else if (this_student == AVERAGE_STUDENT_POINTER) { + which = WhichVisibleBenchmark("average"); + if (which >= 0) select_students[offset+which]=myrow; + } else if (this_student == STDDEV_STUDENT_POINTER) { + which = WhichVisibleBenchmark("stddev"); + if (which >= 0) select_students[offset+which]=myrow; + } else if (this_student == sa) { + which = WhichVisibleBenchmark("lowest_a-"); + if (which >= 0) select_students[offset+which]=myrow; + } else if (this_student == sb) { + which = WhichVisibleBenchmark("lowest_b-"); + if (which >= 0) select_students[offset+which]=myrow; + } else if (this_student == sc) { + which = WhichVisibleBenchmark("lowest_c-"); + if (which >= 0) select_students[offset+which]=myrow; + } else if (this_student == sd) { + which = WhichVisibleBenchmark("lowest_d"); + if (which >= 0) select_students[offset+which]=myrow; + } + } + } +} + + +void start_table_output( bool for_instructor, + const std::vector &students, int rank, int month, int day, int year, + Student *sp, Student *sa, Student *sb, Student *sc, Student *sd) { + + std::vector all_students; + std::vector select_students; + std::vector instructor_data; + std::vector student_data; + + Table table; + + + // ===================================================================================================== + // ===================================================================================================== + // DEFINE HEADER ROW + int counter = 0; + table.set(0,counter++,TableCell("ffffff","#")); + table.set(0,counter++,TableCell("ffffff","SECTION")); + if (DISPLAY_INSTRUCTOR_NOTES) { + table.set(0,counter++,TableCell("ffffff","part.")); + table.set(0,counter++,TableCell("ffffff","under.")); + table.set(0,counter++,TableCell("ffffff","notes")); + } + student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff","USERNAME")); + int last_name_counter=counter; table.set(0,counter++,TableCell("ffffff","LAST")); + + if (DISPLAY_INSTRUCTOR_NOTES || DISPLAY_FINAL_GRADE) { + table.set(0,counter++,TableCell("ffffff","FIRST (LEGAL)")); + } + student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff","FIRST")); + student_data.push_back(last_name_counter); + student_data.push_back(counter); table.set(0,counter++,TableCell(grey_divider)); + + if (DISPLAY_EXAM_SEATING) { + student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff","exam room")); + student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff","exam zone")); + student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff","exam row")); + student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff","exam seat")); + student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff","exam time")); + student_data.push_back(counter); table.set(0,counter++,TableCell(grey_divider)); + } + + student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff","OVERALL")); + student_data.push_back(counter); table.set(0,counter++,TableCell(grey_divider)); + + if (DISPLAY_FINAL_GRADE) { + 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) { + table.set(0,counter++,TableCell("ffffff","RAW GRADE")); + table.set(0,counter++,TableCell(grey_divider)); + } + } + + // ---------------------------- + // % OF OVERALL AVERAGE FOR EACH GRADEABLE + for (unsigned int i = 0; i < ALL_GRADEABLES.size(); i++) { + enum GRADEABLE_ENUM g = ALL_GRADEABLES[i]; + if (g == GRADEABLE_ENUM::NOTE) { + assert (GRADEABLES[g].getPercent() < 0.01); + continue; + } + student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff",gradeable_to_string(g)+" %")); + } + student_data.push_back(counter); table.set(0,counter++,TableCell(grey_divider)); + + // ---------------------------- + // DETAILS OF EACH GRADEABLE + if (DISPLAY_GRADE_DETAILS) { + for (unsigned int i = 0; i < ALL_GRADEABLES.size(); i++) { + GRADEABLE_ENUM g = ALL_GRADEABLES[i]; + for (int j = 0; j < GRADEABLES[g].getCount(); j++) { + if (g != GRADEABLE_ENUM::NOTE) { + student_data.push_back(counter); + } + std::string gradeable_id = GRADEABLES[g].getID(j); + std::string gradeable_name = ""; + if (GRADEABLES[g].hasCorrespondence(gradeable_id)) { + gradeable_name = GRADEABLES[g].getCorrespondence(gradeable_id).second; + //gradeable_name = spacify(gradeable_name); + } + table.set(0,counter++,TableCell("ffffff",gradeable_name)); + } + if (g != GRADEABLE_ENUM::NOTE) { + student_data.push_back(counter); + } + table.set(0,counter++,TableCell(grey_divider)); + + if (g == GRADEABLE_ENUM::TEST && TEST_IMPROVEMENT_AVERAGING_ADJUSTMENT) { + for (int j = 0; j < GRADEABLES[g].getCount(); j++) { + student_data.push_back(counter); + std::string gradeable_id = GRADEABLES[g].getID(j); + std::string gradeable_name = ""; + if (GRADEABLES[g].hasCorrespondence(gradeable_id)) { + gradeable_name = "Adjusted " + GRADEABLES[g].getCorrespondence(gradeable_id).second; + } + table.set(0,counter++,TableCell("ffffff",gradeable_name)); + } + student_data.push_back(counter); table.set(0,counter++,TableCell(grey_divider)); + } + } + + if (DISPLAY_LATE_DAYS) { + 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(grey_divider)); + } + } + + + + + // ---------------------------- + // ICLICKER + if (DISPLAY_ICLICKER && ICLICKER_QUESTION_NAMES.size() > 0) { + + student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff","iclicker status")); + student_data.push_back(counter); table.set(0,counter++,TableCell(grey_divider)); + student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff","ICLICKER TOTAL")); + //student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff","ICLICKER RECENT")); + student_data.push_back(counter); table.set(0,counter++,TableCell(grey_divider)); + + /* + ostr << " " + << "ICLICKER QUESTIONS
CORRECT(green)=1.0, INCORRECT(red)=0.5, POLL(yellow)=1.0, NO ANSWER(white)=0.0
30.0 iClicker points = 3rd late day, 60.0 iClicker pts = 4th late day, 90.0 iClicker pts = 5th late day
≥8.0/12.0 most recent=Priority Help Queue (iClicker status highlighted in blue)"; + */ + + for (unsigned int i = 0; i < ICLICKER_QUESTION_NAMES.size(); i++) { + student_data.push_back(counter); table.set(0,counter++,TableCell("ffffff",ICLICKER_QUESTION_NAMES[i])); + } + student_data.push_back(counter); table.set(0,counter++,TableCell(grey_divider)); + + } + + // ===================================================================================================== + // ===================================================================================================== + // HORIZONTAL GRAY DIVIDER + for (int i = 0; i < table.numCols(); i++) { + table.set(1,i,TableCell(grey_divider)); + } + + + // header row + select_students.push_back(0); + select_students.push_back(1); + select_students.push_back(-1); // replace this with the real student! + select_students.push_back(1); + + + std::map student_correspondences; + + // ===================================================================================================== + // ===================================================================================================== + // ALL OF THE STUDENTS + + SelectBenchmarks(select_students,students,sp,sa,sb,sc,sd); + + + int myrank = 1; + int myrow = 1; + int last_section = -1; + for (unsigned int stu= 0; stu < students.size(); stu++) { + + Student *this_student = students[stu]; + + std::string default_color="ffffff"; + + myrow++; + counter = 0; + if (this_student->getLastName() == "") { + if (this_student == sp) { + default_color= coloritcolor(5,5,4,3,2,1); + } else if (this_student == sa) { + default_color= coloritcolor(4,5,4,3,2,1); + } else if (this_student == sb) { + default_color= coloritcolor(3,5,4,3,2,1); + } else if (this_student == sc) { + default_color= coloritcolor(2,5,4,3,2,1); + } else if (this_student == sd) { + default_color= coloritcolor(1,5,4,3,2,1); + } + assert (default_color.size()==6); + table.set(myrow,counter++,TableCell(default_color,"")); + } else { + //std::cout << " WHO? " << this_student->getUserName() << std::endl; + student_correspondences[myrow] = this_student->getUserName(); + if (GLOBAL_sort_order == "by_section" && this_student->getSection() != last_section) { + myrank=1; + last_section = this_student->getSection(); + } + if (validSection(this_student->getSection())) { + assert (default_color.size()==6); + table.set(myrow,counter++,TableCell(default_color,std::to_string(myrank))); + myrank++; + } else { + assert (default_color.size()==6); + table.set(myrow,counter++,TableCell(default_color,"")); + } + } + + + std::string section_color = default_color; + std::string section_label = ""; + colorit_section2(this_student->getSection(),section_color,section_label); + assert (section_color.size()==6); + table.set(myrow,counter++,TableCell(section_color,section_label)); + + if (DISPLAY_INSTRUCTOR_NOTES) { + float participation = this_student->getParticipation(); + std::string color = coloritcolor(participation,5,4,3,2,1); + table.set(myrow,counter++,TableCell(color,participation,1)); + float understanding = this_student->getUnderstanding(); + color = coloritcolor(understanding,5,4,3,2,1); + table.set(myrow,counter++,TableCell(color,understanding,1)); + std::string notes; + std::vector ews = this_student->getEarlyWarnings(); + for (std::size_t i = 0; i < ews.size(); i++) { + notes += ews[i]; + } + std::string other_note = this_student->getOtherNote(); + std::string recommendation = this_student->getRecommendation(); + std::string THING = + ""+notes+" " + + ""+other_note+" " + + ""+recommendation+""; + assert (default_color.size()==6); + table.set(myrow,counter++,TableCell(default_color,THING)); + } + + //counter+=3; + assert (default_color.size()==6); + table.set(myrow,counter++,TableCell(default_color,this_student->getUserName())); + table.set(myrow,counter++,TableCell(default_color,this_student->getLastName())); + if (DISPLAY_INSTRUCTOR_NOTES || DISPLAY_FINAL_GRADE) { + table.set(myrow,counter++,TableCell(default_color,this_student->getFirstName())); + } + table.set(myrow,counter++,TableCell(default_color,this_student->getPreferredName())); + table.set(myrow,counter++,TableCell(grey_divider)); + + + if (DISPLAY_EXAM_SEATING) { + + std::string room = GLOBAL_EXAM_DEFAULT_ROOM; + std::string zone = "SEE INSTRUCTOR"; + std::string row = ""; + std::string seat = ""; + std::string time = GLOBAL_EXAM_TIME; + + if (this_student->getSection() == 0) { //LastName() == "") { + room = ""; + zone = ""; + time = ""; + } + if (this_student->getExamRoom() == "") { + //std::cout << "NO ROOM FOR " << this_student->getUserName() << std::endl; + } else { + room = this_student->getExamRoom(); + zone = this_student->getExamZone(); + row = this_student->getExamRow(); + seat = this_student->getExamSeat(); + if (this_student->getExamTime() != "") { + time = this_student->getExamTime(); + } + } + if (zone == "SEE_INSTRUCTOR") { + zone = "SEE INSTRUCTOR"; + } + + table.set(myrow,counter++,TableCell("ffffff",room)); + table.set(myrow,counter++,TableCell("ffffff",zone)); + table.set(myrow,counter++,TableCell("ffffff",row)); + table.set(myrow,counter++,TableCell("ffffff",seat)); + table.set(myrow,counter++,TableCell("ffffff",time)); + table.set(myrow,counter++,TableCell(grey_divider)); + } + + + float grade; + if (this_student->getUserName() == "AVERAGE" || + this_student->getUserName() == "STDDEV") { + // Special case for overall average and standard deviation. + // Mathematically, we can't simply add the std dev for the + // different gradeables. Note also: the average isn't a simple + // addition either, since blank scores for a specific gradeable + // are omitted from the average. + std::vector vals; + for (unsigned int S = 0; S < students.size(); S++) { + if (validSection(students[S]->getSection())) { + vals.push_back(students[S]->overall()); + } + } + float tmp_average = compute_average(vals); + if (this_student->getUserName() == "AVERAGE") { + grade = tmp_average; + } else { + float tmp_std_dev = compute_stddev(vals,tmp_average); + grade = tmp_std_dev; + } + } else { + grade = this_student->overall(); + } + + std::string color = coloritcolor(grade, + sp->overall(), + sa->overall(), + sb->overall(), + sc->overall(), + sd->overall()); + if (this_student == STDDEV_STUDENT_POINTER) color="ffffff"; + assert (color.size()==6); + table.set(myrow,counter++,TableCell(color,grade,2)); + table.set(myrow,counter++,TableCell(grey_divider)); + + + if (DISPLAY_FINAL_GRADE) { + std::string g = this_student->grade(false,sd); + color = GradeColor(g); + if (this_student->getMossPenalty() < -0.01) { + 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) { + std::string g2 = this_student->grade(true,sd); + color = GradeColor(g2); + table.set(myrow,counter++,TableCell(color,g2,"",0,CELL_CONTENTS_VISIBLE,"center")); + table.set(myrow,counter++,TableCell(grey_divider)); + } + } + + // ---------------------------- + // % OF OVERALL AVERAGE FOR EACH GRADEABLE + for (unsigned int i = 0; i < ALL_GRADEABLES.size(); i++) { + enum GRADEABLE_ENUM g = ALL_GRADEABLES[i]; + if (g == GRADEABLE_ENUM::NOTE) { + assert (GRADEABLES[g].getPercent() < 0.01); + continue; + } + + float grade; + if (this_student->getUserName() == "AVERAGE" || + this_student->getUserName() == "STDDEV") { + // Special case for per gradeable average and standard deviation. + // Mathematically, we can't simply add the std dev for the + // different gradeables. Note also: the average isn't a simple + // addition either, since blank scores for a specific gradeable + // are omitted from the average. + std::vector vals; + for (unsigned int S = 0; S < students.size(); S++) { + if (validSection(students[S]->getSection())) { + vals.push_back(students[S]->GradeablePercent(g)); + } + } + float tmp_average = compute_average(vals); + if (this_student->getUserName() == "AVERAGE") { + grade = tmp_average; + } else { + float tmp_std_dev = compute_stddev(vals,tmp_average); + grade = tmp_std_dev; + } + } else { + grade = this_student->GradeablePercent(g); + } + + std::string color = coloritcolor(grade, + sp->GradeablePercent(g), + sa->GradeablePercent(g), + sb->GradeablePercent(g), + sc->GradeablePercent(g), + sd->GradeablePercent(g)); + if (this_student == STDDEV_STUDENT_POINTER) color="ffffff"; + assert (color.size()==6); + table.set(myrow,counter++,TableCell(color,grade,2)); + } + table.set(myrow,counter++,TableCell(grey_divider)); + + // ---------------------------- + // DETAILS OF EACH GRADEABLE + if (DISPLAY_GRADE_DETAILS) { + for (unsigned int i = 0; i < ALL_GRADEABLES.size(); i++) { + GRADEABLE_ENUM g = ALL_GRADEABLES[i]; + enum CELL_CONTENTS_STATUS visible = CELL_CONTENTS_VISIBLE; + if (g == GRADEABLE_ENUM::TEST) { + visible = CELL_CONTENTS_NO_DETAILS; + } + for (int j = 0; j < GRADEABLES[g].getCount(); j++) { + float grade = this_student->getGradeableItemGrade(g,j).getValue(); + std::string color = coloritcolor(grade, + sp->getGradeableItemGrade(g,j).getValue(), + sa->getGradeableItemGrade(g,j).getValue(), + sb->getGradeableItemGrade(g,j).getValue(), + sc->getGradeableItemGrade(g,j).getValue(), + sd->getGradeableItemGrade(g,j).getValue()); + if (this_student == STDDEV_STUDENT_POINTER) color="ffffff"; + std::string details; + details = this_student->getGradeableItemGrade(g,j).getNote(); + std::string status = this_student->getGradeableItemGrade(g,j).getStatus(); + + if (status.find("Bad") != std::string::npos) { + details += " " + status; + } + int late_days_used = this_student->getGradeableItemGrade(g,j).getLateDaysUsed(); + assert (color.size()==6); + table.set(myrow,counter++,TableCell(color,grade,1,details,late_days_used,visible)); + } + table.set(myrow,counter++,TableCell(grey_divider)); + + // FIXME + if (g == GRADEABLE_ENUM::TEST && TEST_IMPROVEMENT_AVERAGING_ADJUSTMENT) { + for (int j = 0; j < GRADEABLES[g].getCount(); j++) { + float grade = this_student->adjusted_test(j); + std::string color = coloritcolor(this_student->adjusted_test(j), + sp->adjusted_test(j), + sa->adjusted_test(j), + sb->adjusted_test(j), + sc->adjusted_test(j), + sd->adjusted_test(j)); + if (this_student == STDDEV_STUDENT_POINTER) color="ffffff"; + assert (color.size()==6); + table.set(myrow,counter++,TableCell(color,grade,1,"",0,visible)); + } + table.set(myrow,counter++,TableCell(grey_divider)); + } + } + + if (DISPLAY_LATE_DAYS) { + // LATE DAYS + if (this_student->getLastName() != "") { + int allowed = this_student->getAllowedLateDays(100); + std::string color = coloritcolor(allowed,5,4,3,2,2); + table.set(myrow,counter++,TableCell(color,allowed,"",0,CELL_CONTENTS_VISIBLE,"right")); + 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")); + } else { + color="ffffff"; // default_color; + table.set(myrow,counter++,TableCell(color,"")); + table.set(myrow,counter++,TableCell(color,"")); + } + table.set(myrow,counter++,TableCell(grey_divider)); + } + } + + + + // ---------------------------- + // ICLICKER + if (DISPLAY_ICLICKER && ICLICKER_QUESTION_NAMES.size() > 0) { + + if (this_student->getRemoteID().size() != 0) { // && this_student->hasPriorityHelpStatus()) { + table.set(myrow,counter++,TableCell("ccccff","registered")); + //} else if (this_student->getRemoteID() != "") { + //table.set(myrow,counter++,TableCell("ffffff","registered")); + } else if (this_student->getLastName() == "") { + table.set(myrow,counter++,TableCell("ffffff"/*default_color*/,"")); + } else { + table.set(myrow,counter++,TableCell("ffcccc","no iclicker registration")); + } + table.set(myrow,counter++,TableCell(grey_divider)); + + if (this_student->getLastName() != "" || + this_student->getUserName() == "PERFECT") { + float grade = this_student->getIClickerTotalFromStart(); + std::string color = coloritcolor(grade, + MAX_ICLICKER_TOTAL, + 0.90*MAX_ICLICKER_TOTAL, + 0.80*MAX_ICLICKER_TOTAL, + 0.60*MAX_ICLICKER_TOTAL, + 0.40*MAX_ICLICKER_TOTAL); + table.set(myrow,counter++,TableCell(color,grade,1)); + /* + grade = this_student->getIClickerRecent(); + color = coloritcolor(grade, + ICLICKER_RECENT, + 0.90*ICLICKER_RECENT, + 0.80*ICLICKER_RECENT, + 0.60*ICLICKER_RECENT, + 0.40*ICLICKER_RECENT); + table.set(myrow,counter++,TableCell(color,grade,1)); + */ + } else { + color="ffffff"; // default_color; + table.set(myrow,counter++,TableCell(color,"")); + //table.set(myrow,counter++,TableCell(color,"")); + } + + table.set(myrow,counter++,TableCell(grey_divider)); + for (unsigned int i = 0; i < ICLICKER_QUESTION_NAMES.size(); i++) { + std::pair answer = this_student->getIClickerAnswer(ICLICKER_QUESTION_NAMES[i]); + std::string thing = answer.first; + std::string color = "ffffff"; //default_color; + if (answer.second == ICLICKER_CORRECT) { + color = "aaffaa"; + } else if (answer.second == ICLICKER_PARTICIPATED) { + color = "ffffaa"; + } else if (answer.second == ICLICKER_INCORRECT) { + color = "ffaaaa"; + } else { + assert (answer.second == ICLICKER_NOANSWER); + } + table.set(myrow,counter++,TableCell(color,thing,"",0,CELL_CONTENTS_VISIBLE_INSTRUCTOR,"center")); + } + table.set(myrow,counter++,TableCell(grey_divider)); + } + } + + + + for (int i = 0; i < table.numCols(); i++) { + instructor_data.push_back(i); + } + // need to add 2, for the perfect & average student. + for (unsigned int i = 0; i < students.size()+2; i++) { + all_students.push_back(i); + } + + std::cout << "WRITE ALL.html" << std::endl; + std::ofstream ostr2(OUTPUT_FILE); + + GLOBAL_instructor_output = true; + table.output(ostr2, all_students,instructor_data); + + end_table(ostr2,true,NULL); + ostr2.close(); + + std::stringstream ss; + ss << ALL_STUDENTS_OUTPUT_DIRECTORY << "output_" << month << "_" << day << "_" << year << ".html"; + + std::string command = "cp -f output.html " + ss.str(); + std::cout << "RUN COMMAND " << command << std::endl; + system(command.c_str()); + + + for (std::map::iterator itr = student_correspondences.begin(); + itr != student_correspondences.end(); itr++) { + + select_students[2] = itr->first; + std::string filename = "individual_summary_html/" + itr->second + "_summary.html"; + std::ofstream ostr3(filename.c_str()); + assert (ostr3.good()); + + Student *s = GetStudent(students,itr->second); + std::string last_update; + if (s != NULL) { + last_update = s->getLastUpdate(); + } + GLOBAL_instructor_output = false; + + table.output(ostr3, select_students,student_data,true,true,last_update); + + end_table(ostr3,false,s); + } + + Student* s = NULL; + if (rank != -1) { + s = students[rank]; + assert (s != NULL); + } +} + + + + +void end_table(std::ofstream &ostr, bool for_instructor, Student *s) { + + + ostr << "

* = 1 late day used

" << std::endl; + + if (GLOBAL_instructor_output == false && + DISPLAY_ICLICKER) { + + ostr << "

IClicker Legend:
   CORRECT(green)=1.0
   INCORRECT(red)=0.5
   POLL(yellow)=1.0
   NO ANSWER(white)=0.0
" << std::endl; + if (s != NULL) { + ostr << "Initial number of allowed late days: " << s->getDefaultAllowedLateDays() << "
" << std::endl; + } + if(!GLOBAL_earned_late_days.empty()) { + ostr << "Extra late days earned after iclicker points: "; + for (std::size_t i = 0; i < GLOBAL_earned_late_days.size(); i++) { + ostr << GLOBAL_earned_late_days[i]; + if (i < GLOBAL_earned_late_days.size() - 1) { + ostr << ", "; + } + } + ostr << "
" << std::endl; + } + ostr << "

" << std::endl; + + + //ostr << GLOBAL_earned_late + + //25.0 iClicker points = 3rd late day, 50.0 iClicker pts = 4th late day, 75.0 iClicker pts = 5th late day
≥8.0/12.0 most recent=Priority Help Queue (iClicker status highlighted in blue)"; + } + + ostr << "

 

\n"; + + + + bool print_moss_message = false; + if (s != NULL && s->getMossPenalty() < -0.01) { + print_moss_message = true; + } + + if (print_moss_message) { + ostr << "@ = final grade with Academic Integrity Violation penalty

 

\n"; + } + + if (DISPLAY_FINAL_GRADE) { // && students.size() > 50) { + + int total_A = grade_counts[Grade("A")] + grade_counts[Grade("A-")]; + int total_B = grade_counts[Grade("B+")] + grade_counts[Grade("B")] + grade_counts[Grade("B-")]; + int total_C = grade_counts[Grade("C+")] + grade_counts[Grade("C")] + grade_counts[Grade("C-")]; + int total_D = grade_counts[Grade("D+")] + grade_counts[Grade("D")]; + int total_passed = total_A + total_B + total_C + total_D; + int total_F = grade_counts[Grade("F")]; + int total_blank = grade_counts[Grade("")]; + assert (total_blank == 0); + int total = total_passed + total_F + auditors + total_blank + dropped; + + ostr << "

\n"; + + + + ostr << "\n"; + // ostr << "
\n"; + ostr << "\n"; + ostr << ""; + ostr << ""; + ostr << ""; + ostr << ""; + ostr << "\n"; + if (for_instructor) { + ostr << "\n"; + // ostr << "\n"; + ostr << "\n"; + ostr << "\n"; + ostr << "\n"; + ostr << "\n"; + ostr << "\n"; + } + ostr << "\n"; + + ostr << "\n"; + ostr << ""; + ostr << "\n"; + ostr << "\n"; + + + + ostr << "\n"; + ostr << ""; + ostr << "\n"; + } + + if (for_instructor) { + ostr << "\n"; + ostr << "\n"; + ostr << "\n"; + ostr << "\n"; + ostr << "\n"; + } + ostr << "\n"; + + + + ostr << "
FINAL GRADEAA-B+BB-C+CC-D+DFdroppedaudittook finaltotal passeddroppedtotal
# of students"<"<"; + ostr << ""<"<"<"; + ostr << ""<"<"<"; + ostr << ""<"<\n"; + + if (for_instructor) { + ostr << ""<\n"; + //ostr << "" << grade_counts[Grade("")]<<""<\n"; + ostr << ""<\n"; + ostr << ""<\n"; + ostr << ""<\n"; + ostr << ""<\n"; + } + ostr << "
average OVERALL
of students with
this FINAL GRADE
"<"<"; + ostr << ""<"<"<"; + ostr << ""<"<"<"; + + if (for_instructor) { + ostr << ""<"<\n"; + } else { + ostr << "     "<\n"; + //ostr << ""<\n"; + ostr << "     

\n"; + + } + + ostr.close(); +} diff --git a/parsexml.py b/parsexml.py new file mode 100644 index 0000000..f983fe9 --- /dev/null +++ b/parsexml.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +import csv +import xml.etree.ElementTree as ET +import sys +import os.path + + +class QuestionData: + final_answer = "" + final_answer_time = 0 + first_answer = "" + attempts = 0 + first_answer_time = 0 + + def __init__(self,final_answer,final_answer_time,attempts,first_answer,first_answer_time): + self.final_answer = final_answer + self.final_answer_time = final_answer_time + self.first_answer = first_answer + self.first_answer_time = first_answer_time + self.attempts = attempts + + +def xml_to_csv(xml_filename): + """ + Parses .xml files generated by newer versions of iClicker software in SessionData + A CSV file will be written to the same path as the XML file, so it is important that any path, be it + absolute or relative, is included in the xml_filename argument. The CSV file is not a perfect replica of + older (i.e. iClicker 6) CSV files, but is our best approximation at this time. It should be enough for + Rainbow Grades to function properly. + """ + + csv_filename = xml_filename[:-3] + "csv" + try: + with open(xml_filename,"r") as readfile: + tree = ET.parse(xml_filename) + root = tree.getroot() + + questions_in_order = [] + start_times = {} + stop_times = {} + user_question_data = {} + + for child in root: + if child.tag == "p": # This is a polling tag + question = child.attrib["qn"] + start_times[question] = child.attrib["strt"] + stop_times[question] = child.attrib["stp"] + questions_in_order.append(question) + question_votes = {} + for qchild in child: + if qchild.tag == "v": # This is a voting tag + clicker_id = qchild.attrib["id"] + if clicker_id not in user_question_data: + user_question_data[clicker_id] = {} + user_question_data[clicker_id][question] = {} + if "fans" in qchild.attrib: + user_question_data[clicker_id][question] = QuestionData(qchild.attrib["ans"], + qchild.attrib["fanst"], + qchild.attrib["att"], + qchild.attrib["fans"], + qchild.attrib["tm"]) + + question_votes[clicker_id] = qchild.attrib["ans"] + + with open(csv_filename, 'w') as writefile: + csvwriter = csv.writer(writefile) # Need to change dialect to be iclicker compliant + + # Write the header + # Right now we don't have min reply/min correct in XML land, instead we have MinPart_S + next_row = ["Scoring"] + if "perf" in root.attrib: + performance = root.attrib["perf"] + else: + performance = -1 + + if "part" in root.attrib: + participation = root.attrib["part"] + else: + participation = 1 + csvwriter.writerow(["Scoring", "Performance = " + performance, + "Participation = " + participation, "Min Reply = 2", + "Min Correct = 0", + " "]) + next_row = ["Question", " ", " "] + for i in range(len(questions_in_order)): + next_row = next_row + ["Question " + str(i + 1), "Score", "Final Answer Time", "Number of Attempts", + "First Response", "Time"] + csvwriter.writerow(next_row) + next_row = ["Start Time", " ", " "] + for question in questions_in_order: + next_row = next_row + [" " + start_times[question], " ", " ", " ", " ", " "] + csvwriter.writerow(next_row) + + next_row = ["Stop Time", " ", " "] + first_stop = True + for question in questions_in_order: + if not first_stop: + next_row = next_row + [" " + stop_times[question], " ", " ", " ", " ", " "] + else: + next_row = next_row + [stop_times[question], " ", " ", " ", " ", " "] + first_stop = False + csvwriter.writerow(next_row) + + next_row = ["Correct Answer", " ", " "] + first_stop = True + for question in questions_in_order: + if not first_stop: + next_row = next_row + [" ", " ", " ", " ", " ", " "] + else: + next_row = next_row + ["", " ", " ", " ", " ", " "] + first_stop = False + csvwriter.writerow(next_row) + + for user in sorted(user_question_data.keys()): + next_row = [user, "", "0"] + for question in questions_in_order: + if question in user_question_data[user]: + qd = user_question_data[user][question] + next_row = next_row + [qd.final_answer, 0, qd.final_answer_time, qd.attempts, + qd.first_answer, qd.first_answer_time] + else: + next_row = next_row + ["", "", "", "", "", ""] + csvwriter.writerow(next_row) + + except IOError as e: + print("File I/O error: {}".format(e)) + exit(-1) + + +if __name__ == '__main__': + if len(sys.argv) != 2: + print("Correct usage is {} [file with iclicker {\"file\":...} entries]".format(sys.argv[0])) + exit(-1) + + files = [] + + try: + with open(sys.argv[1]) as json_file: + for line in json_file: + # Extract just the filenames of the session data + files += [x.strip()[1:-1] for x in line.split("[")[1].split("]")[0].split(",")] + except IOError as e: + print("Error reading JSON excerpt: {}".format(e)) + + for filename in files: + if len(filename) >= 4 and filename[-4:] == ".xml": + xml_to_csv(filename) diff --git a/student.cpp b/student.cpp new file mode 100644 index 0000000..4d6326f --- /dev/null +++ b/student.cpp @@ -0,0 +1,485 @@ +#include "student.h" + +const std::string GradeColor(const std::string &grade); + +// ============================================================================================= +// ============================================================================================= +// CONSTRUCTOR + +Student::Student() { + + // personal data + // (defaults to empty string) + lefty = false; + + // registration status + section = 0; + audit = false; + withdraw = false; + independentstudy = false; + + default_allowed_late_days = 0; + current_allowed_late_days = 0; + + // grade data + for (unsigned int i = 0; i < ALL_GRADEABLES.size(); i++) { + GRADEABLE_ENUM g = ALL_GRADEABLES[i]; + all_item_grades[g] = std::vector(GRADEABLES[g].getCount(),ItemGrade(0)); + } + // (iclicker defaults to empty map) + + zones = std::vector(GRADEABLES[GRADEABLE_ENUM::TEST].getCount(),""); + moss_penalty = 0; + cached_hw = -1; + + // other grade-like data + // (remote id defaults to empty vector) + academic_integrity_form = false; + participation = 0; + understanding = 0; + + // info about exam assignments + // (defaults to empty string) + + // per student notes + // (defaults to empty string) +} + + + + +// lookup a student by username +Student* GetStudent(const std::vector &students, const std::string& username) { + for (unsigned int i = 0; i < students.size(); i++) { + if (students[i]->getUserName() == username) return students[i]; + } + return NULL; +} + + + + + +// ============================================================================================= +// ============================================================================================= +// accessor & modifier for grade data + +const ItemGrade& Student::getGradeableItemGrade(GRADEABLE_ENUM g, int i) const { + static ItemGrade emptyItemGrade(0); + //std::cout << "i " << i << " count " << GRADEABLES[g].getCount() << std::endl; + if (i >= GRADEABLES[g].getCount()) { + return emptyItemGrade; + } + assert (i >= 0 && i < GRADEABLES[g].getCount()); + std::map >::const_iterator itr = all_item_grades.find(g); + assert (itr != all_item_grades.end()); + assert (int(itr->second.size()) > i); + + return itr->second[i]; //return value; +} + + + +void Student::setGradeableItemGrade(GRADEABLE_ENUM g, int i, float value, + int late_days_used, const std::string ¬e, const std::string &status) { + 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); + itr->second[i] = ItemGrade(value,late_days_used,note,status); +} + + + +// ============================================================================================= +// GRADER CALCULATION HELPER FUNCTIONS + +class score_object { +public: + score_object(float s,float m,float p,float sm):score(s),max(m),percentage(p),scale_max(sm){} + float score; + float max; + float percentage; + float scale_max; +}; + +bool operator<(const score_object &a, const score_object &b) { + return a.score < b.score; +} + +float Student::GradeablePercent(GRADEABLE_ENUM g) const { + if (GRADEABLES[g].getCount() == 0) return 0; + //if (GRADEABLES[g].getMaximum() == 0) return 0; + //assert (GRADEABLES[g].getMaximum() > 0); + assert (GRADEABLES[g].getPercent() >= 0); + + // special rules for tests + if (g == GRADEABLE_ENUM::TEST && TEST_IMPROVEMENT_AVERAGING_ADJUSTMENT) { + return adjusted_test_pct(); + } + + // normalize & drop lowest # + if (g == GRADEABLE_ENUM::QUIZ && QUIZ_NORMALIZE_AND_DROP > 0) { + return quiz_normalize_and_drop(QUIZ_NORMALIZE_AND_DROP); + } + + if (g == GRADEABLE_ENUM::TEST && LOWEST_TEST_COUNTS_HALF) { + return lowest_test_counts_half_pct(); + } + + // collect the scores in a vector + std::vector scores; + for (int i = 0; i < GRADEABLES[g].getCount(); i++) { + float s = getGradeableItemGrade(g,i).getValue(); + std::string id = GRADEABLES[g].getID(i); + float m = GRADEABLES[g].getItemMaximum(id); + float p = GRADEABLES[g].getItemPercentage(id); + float sm = GRADEABLES[g].getScaleMaximum(id); + scores.push_back(score_object(s,m,p,sm)); + } + + // sort the scores (smallest first) + std::sort(scores.begin(),scores.end()); + + assert (GRADEABLES[g].getRemoveLowest() >= 0 && + GRADEABLES[g].getRemoveLowest() < GRADEABLES[g].getCount()); + + // sum the remaining (higher) scores + float sum_max = 0; + for (int i = GRADEABLES[g].getRemoveLowest(); i < GRADEABLES[g].getCount(); i++) { + float m = scores[i].max; + sum_max += m; + } + + // sum the remaining (higher) scores + float sum = 0; + for (int i = GRADEABLES[g].getRemoveLowest(); i < GRADEABLES[g].getCount(); i++) { + float s = scores[i].score; + float m = scores[i].max; + float p = scores[i].percentage; + float sm = scores[i].scale_max; + float my_max = std::max(m,sm); + if (p < 0) { + assert (my_max > 0); + if (sum_max > 0) { + p = std::max(m,sm) / sum_max; + } else { + // pure extra credit category + p = std::max(m,sm) / my_max; + } + } + sum += p * s / my_max; + } + + return 100*GRADEABLES[g].getPercent()*sum; +} + + + + +float Student::adjusted_test(int i) const { + assert (i >= 0 && i < GRADEABLES[GRADEABLE_ENUM::TEST].getCount()); + float a = getGradeableItemGrade(GRADEABLE_ENUM::TEST,i).getValue(); + float b; + if (i+1 < GRADEABLES[GRADEABLE_ENUM::TEST].getCount()) { + b = getGradeableItemGrade(GRADEABLE_ENUM::TEST,i+1).getValue(); + } else { + assert (GRADEABLES[GRADEABLE_ENUM::EXAM].getCount() == 1); + b = getGradeableItemGrade(GRADEABLE_ENUM::EXAM,0).getValue(); + // HACK need to scale the final exam! + b *= 0.6667; + } + + if (a > b) return a; + return (a+b) * 0.5; +} + + +float Student::adjusted_test_pct() const { + float sum = 0; + for (int i = 0; i < GRADEABLES[GRADEABLE_ENUM::TEST].getCount(); i++) { + sum += adjusted_test(i); + } + float answer = 100 * GRADEABLES[GRADEABLE_ENUM::TEST].getPercent() * sum / float (GRADEABLES[GRADEABLE_ENUM::TEST].getMaximum()); + return answer; +} + + + +float Student::quiz_normalize_and_drop(int num) const { + + assert (num > 0); + + // collect the normalized quiz scores in a vector + std::vector scores; + for (int i = 0; i < GRADEABLES[GRADEABLE_ENUM::QUIZ].getCount(); i++) { + // the max for this quiz + float p = PERFECT_STUDENT_POINTER->getGradeableItemGrade(GRADEABLE_ENUM::QUIZ,i).getValue(); + // this students score + float v = getGradeableItemGrade(GRADEABLE_ENUM::QUIZ,i).getValue(); + scores.push_back(v/p); + } + + assert (scores.size() > std::size_t(num)); //Relies on the assert(num > 0) at the top of this function. + + // sort the scores + sort(scores.begin(),scores.end()); + + // sum up all but the lowest "num" scores + float sum = 0; + for (std::size_t i = num; i < scores.size(); i++) { + sum += scores[i]; + } + + // the overall percent of the final grade for quizzes + return 100 * GRADEABLES[GRADEABLE_ENUM::QUIZ].getPercent() * sum / float (scores.size()-num); +} + + +float Student::lowest_test_counts_half_pct() const { + + int num_tests = GRADEABLES[GRADEABLE_ENUM::TEST].getCount(); + assert (num_tests > 0); + + // first, collect & sort the scores + std::vector scores; + for (int i = 0; i < num_tests; i++) { + scores.push_back(getGradeableItemGrade(GRADEABLE_ENUM::TEST,i).getValue()); + } + std::sort(scores.begin(),scores.end()); + + // 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()); +} + + +// ============================================================================================= +// ============================================================================================= + +int Student::getAllowedLateDays(int which_lecture) const { + if (getSection() == 0) return 0; + + //int answer = 2; + + int answer = default_allowed_late_days; + + // average 4 questions per lecture 2-28 ~= 112 clicker questions + // 30 questions => 3 late days + // 60 questions => 4 late days + // 90 qustions => 5 late days + + float total = getIClickerTotal(which_lecture,0); + + for (unsigned int i = 0; i < GLOBAL_earned_late_days.size(); i++) { + if (total >= GLOBAL_earned_late_days[i]) { + answer++; + } + } + + for (unsigned int i = 0; i < bonus_late_days_which_lecture.size(); i++) { + if (bonus_late_days_which_lecture[i] <= which_lecture) { + answer++; + } + } + + return std::max(current_allowed_late_days,answer); + +} + +// get the total used late days +int Student::getUsedLateDays() 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].getLateDaysUsed(); + } + } + return answer; +} + +// ============================================================================================= + +float Student::overall_b4_moss() const { + float answer = 0; + for (unsigned int i = 0; i < ALL_GRADEABLES.size(); i++) { + GRADEABLE_ENUM g = ALL_GRADEABLES[i]; + answer += GradeablePercent(g); + } + return answer; +} + +std::string Student::grade(bool flag_b4_moss, Student *lowest_d) const { + + if (section == 0) return ""; + + if (!flag_b4_moss && manual_grade != "") return manual_grade; + + float over = overall(); + if (flag_b4_moss) { + over = overall_b4_moss(); + } + + + // some criteria that might indicate automatica failure of course + // (instructor can override with manual grade) + 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) < + 0.90 * lowest_d->GradeablePercent(GRADEABLE_ENUM::TEST) + lowest_d->GradeablePercent(GRADEABLE_ENUM::EXAM) ) ? true : false; + if (failed_lab || failed_hw || + ( failed_testA + + failed_testB + + failed_testC ) > 1) { + //std::cout << "SHOULD AUTO FAIL"; + + //((Student*)this)->other_note += "SHOULD AUTO FAIL"; + return "F"; + } + + + // otherwise apply the cutoffs + if (over >= CUTOFFS["A"]) return "A"; + if (over >= CUTOFFS["A-"]) return "A-"; + if (over >= CUTOFFS["B+"]) return "B+"; + if (over >= CUTOFFS["B"]) return "B"; + if (over >= CUTOFFS["B-"]) return "B-"; + if (over >= CUTOFFS["C+"]) return "C+"; + if (over >= CUTOFFS["C"]) return "C"; + if (over >= CUTOFFS["C-"]) return "C-"; + if (over >= CUTOFFS["D+"]) return "D+"; + if (over >= CUTOFFS["D"]) return "D"; + else return "F"; + + return "?"; +} + + + +void Student::mossify(const std::string &gradeable, float penalty) { + + // if the penalty is "a whole or partial letter grade".... + float average_letter_grade = (CUTOFFS["A"]-CUTOFFS["B"] + + CUTOFFS["B"]-CUTOFFS["C"] + + CUTOFFS["C"]-CUTOFFS["D"]) / 3.0; + + assert (GRADEABLES[GRADEABLE_ENUM::HOMEWORK].hasCorrespondence(gradeable)); + int which = GRADEABLES[GRADEABLE_ENUM::HOMEWORK].getCorrespondence(gradeable).first; + + if (!(getGradeableItemGrade(GRADEABLE_ENUM::HOMEWORK,which).getValue() > 0)) { + std::cerr << "WARNING: the grade for this homework is already 0, moss penalty error?" << std::endl; + } + setGradeableItemGrade(GRADEABLE_ENUM::HOMEWORK,which,0); + + // the penalty is positive + // but it will be multiplied by a negative and added to the total; + assert (penalty >= 0); + + moss_penalty += -0.0000001; + moss_penalty += -average_letter_grade * penalty; + + addWarning("[MOSS PENALTY " + std::to_string(penalty) + "]"); +} + + + +void Student::ManualGrade(const std::string &grade, const std::string &message) { + assert (grade == "A" || + grade == "A-" || + grade == "B+" || + grade == "B" || + grade == "B-" || + grade == "C+" || + grade == "C" || + grade == "C-" || + grade == "D+" || + grade == "D" || + grade == "F"); + manual_grade = grade; + other_note += "awarding a " + grade + " because " + message; +} + + +void Student::outputgrade(std::ostream &ostr,bool flag_b4_moss,Student *lowest_d) const { + std::string g = grade(flag_b4_moss,lowest_d); + + std::string color = GradeColor(g); + if (moss_penalty < -0.01) { + ostr << "" << g << " @"; + } else { + ostr << "" << g << ""; + } +} + + +// ============================================================================================= + +// zones for exams... +//const std::string& Student::getZone(int i) const { +std::string Student::getZone(int i) const { + assert (i >= 0 && i < GRADEABLES[GRADEABLE_ENUM::TEST].getCount()); return zones[i]; +} + + + +// ============================================================================================= + + +bool iclickertotalhelper(const std::string &clickername,int which_lecture) { + std::stringstream ss(clickername); + int foo; + ss >> foo; + if (foo <= which_lecture) return true; + return false; +} + + +void Student::addIClickerAnswer(const std::string& which_question, char which_answer, iclicker_answer_enum grade) { + iclickeranswers[which_question] = std::make_pair(which_answer,grade); } + +float Student::getIClickerRecent() const { + if (getUserName() == "PERFECT") { return std::min((int)ICLICKER_QUESTION_NAMES.size(),ICLICKER_RECENT); } + return getIClickerTotal(100, std::max(0,(int)ICLICKER_QUESTION_NAMES.size()-ICLICKER_RECENT)); +} + +float Student::getIClickerTotal(int which_lecture, int start) const { + if (getUserName() == "PERFECT") { return MAX_ICLICKER_TOTAL; } + float ans = 0; + for (unsigned int i = start; i < ICLICKER_QUESTION_NAMES.size(); i++) { + std::map >::const_iterator itr = iclickeranswers.find(ICLICKER_QUESTION_NAMES[i]); + if (itr == iclickeranswers.end()) continue; + if (!iclickertotalhelper(itr->first,which_lecture)) continue; + if (itr->second.second == ICLICKER_CORRECT || + itr->second.second == ICLICKER_PARTICIPATED) { + ans += 1.0; + } else if (itr->second.second == ICLICKER_INCORRECT) { + ans += 0.5; + } + } + return ans; +} + +std::pair Student::getIClickerAnswer(const std::string& which_question) const { + std::pair noanswer = std::make_pair("",ICLICKER_NOANSWER); + std::map >::const_iterator itr = iclickeranswers.find(which_question); + if (itr == iclickeranswers.end()) return noanswer; + + char x = itr->second.first; + std::string tmp(1,x); + iclicker_answer_enum val = itr->second.second; + return std::make_pair(tmp,val); +} + +// ============================================================================================= diff --git a/student.h b/student.h new file mode 100644 index 0000000..584dd2c --- /dev/null +++ b/student.h @@ -0,0 +1,249 @@ + +#ifndef _STUDENT_H_ +#define _STUDENT_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "iclicker.h" +#include "gradeable.h" +#include "constants_and_globals.h" + +//==================================================================== +//==================================================================== +// stores the grade for a single gradeable + +class ItemGrade { +public: + ItemGrade(float v, int ldu=0, const std::string& n="", const std::string &s="") { + value = v; + late_days_used = ldu; + note = n; + if (s != "UNKONWN") { + status = s; + } + } + float getValue() const { + + float adjusted_value = value; + if (late_days_used > 0) { + //std::cout << "LATE DAYS! " << late_days_used << std::endl; + // FIXME: Currently a flat penalty no matter how many days used + adjusted_value = (1-LATE_DAY_PERCENTAGE_PENALTY)*value; + //std::cout << "value " << value << "-> " << adjusted_value << std::endl; + } + + /* + // grab the maximum score for this homework + assert (PERFECT_STUDENT_POINTER != NULL); + std::map >::const_iterator ps_itr = PERFECT_STUDENT_POINTER->all_item_grades.find(g); + assert (ps_itr != all_item_grades.end()); + float ps_value = ps_itr->second[i].getValue(); + */ + // adjust the homework score + //value = std::max(0.0f, value - d*LATE_DAY_PERCENTAGE_PENALTY*ps_value); + + return adjusted_value; + } + int getLateDaysUsed() const { return late_days_used; } + const std::string& getNote() const { return note; } + const std::string& getStatus() const { return status; } +private: + float value; + int late_days_used; + std::string note; + std::string status; +}; + +//==================================================================== +//==================================================================== + +class Student { + +public: + + // --------------- + // CONSTRUCTOR + Student(); + + // --------------- + // ACCESSORS + + // personal data + const std::string& getUserName() const { return username; } + const std::string& getFirstName() const { return legal_first; } + const std::string& getPreferredName() const { if (preferred_first != "") return preferred_first; return legal_first; } + const std::string& getLastName() const { return last; } + const std::string& getLastUpdate() const { return lastUpdate; } + bool getLefty() const { return lefty; } + + // registration status + int getSection() const { return section; } + bool getAudit() const { return audit; } + bool getWithdraw() const { return withdraw; } + bool getIndependentStudy() const { return independentstudy; } + + // grade data + const ItemGrade& getGradeableItemGrade(GRADEABLE_ENUM g, int i) const; + std::string getZone(int i) const; + int getAllowedLateDays(int which_lecture) const; + int getUsedLateDays() const; + float getMossPenalty() const { return moss_penalty; } + + void setCurrentAllowedLateDays(int d) { current_allowed_late_days = d; } + void setDefaultAllowedLateDays(int d) { default_allowed_late_days = d; } + + int getDefaultAllowedLateDays() const { return default_allowed_late_days; } + + void add_bonus_late_day(int which_lecture) { bonus_late_days_which_lecture.push_back(which_lecture); } + + // other grade-like data + const std::vector& getRemoteID() const { return remote_id; } + bool getAcademicIntegrityForm() const { return academic_integrity_form; } + int getParticipation() const { return participation; } + int getUnderstanding() const { return understanding; } + + // info about exam assignments + const std::string& getExamRoom() const { return exam_room; } + const std::string& getExamZone() const { return exam_zone; } + std::string getExamRow() const { return exam_row;} + std::string getExamSeat() const { return exam_seat;} + const std::string& getExamTime() const { return exam_time; } + const std::string& getExamZoneImage() const { return exam_zone_image; } + + // per student notes + const std::string& getRecommendation() const { return ta_recommendation; } + const std::string& getOtherNote() const { return other_note; } + const std::vector& getEarlyWarnings() const { return early_warnings; } + + // --------------- + // MODIFIERS + + // personal data + void setUserName(const std::string &s) { username=s; } + void setLegalFirstName(const std::string &s) { legal_first=s; } + void setPreferredFirstName(const std::string &s) { preferred_first=s.substr(0,10); } + void setLastName(const std::string &s) { last=s; } + void setLefty() { lefty = true; } + void setLastUpdate(const std::string &s) { lastUpdate = s; } + + // registration status + void setSection(int x) { section = x; } + void setAudit() { audit = true; } + void setWithdraw() { withdraw = true; } + void setIndependentStudy() { independentstudy = true; } + + // grade data + 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 mossify(const std::string &gradeable, float penalty); + + // other grade-like data + void setRemoteID(const std::string& r_id) { remote_id.push_back(r_id); } + void setAcademicIntegrityForm() { academic_integrity_form = true; } + void setParticipation(int x) { participation = x; } + void setUnderstanding(int x) { understanding = x; } + + // info about exam assignments + void setExamRoom(const std::string &s) { exam_room = s; } + void setExamZone(const std::string &z, const std::string &r, const std::string &s) { exam_zone=z; exam_row=r; exam_seat=s; } + void setExamTime(const std::string &s) { exam_time = s; } + void setExamZoneImage(const std::string &s) { exam_zone_image = s; } + + // per student notes + void addWarning(const std::string &message) { early_warnings.push_back(message); } + void addRecommendation(const std::string &message) { ta_recommendation += message; } + void addNote(const std::string &message) { other_note += message; } + void ManualGrade(const std::string &grade, const std::string &message); + + + // --------------- + // I-CLICKER + + void addIClickerAnswer(const std::string& which_question, char which_answer, iclicker_answer_enum grade); + float getIClickerRecent() const; + float getIClickerTotalFromStart() const { return getIClickerTotal(100,0); } + float getIClickerTotal(int which_lecture, int start) const; + bool hasPriorityHelpStatus() const { + return (getIClickerRecent() >= ICLICKER_PRIORITY * float (ICLICKER_RECENT)); + } + std::pair getIClickerAnswer(const std::string& which_question) const; + + + // HELPER FUNCTIONS + float GradeablePercent(GRADEABLE_ENUM g) const; + float overall() const { return overall_b4_moss() + moss_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; + +private: + + // --------------- + // REPRESENTATION + + // personal data + std::string username; + std::string legal_first; + std::string preferred_first; + std::string last; + bool lefty; + + std::string lastUpdate; + + int current_allowed_late_days; + int default_allowed_late_days; + + // registration status + int section; + bool audit; + bool withdraw; + bool independentstudy; + + // grade data + std::map > all_item_grades; + std::map > iclickeranswers; + + std::vector zones; + float moss_penalty; + float cached_hw; + + // other grade-like data + std::vector remote_id; + bool academic_integrity_form; + int participation; + int understanding; + + std::vector bonus_late_days_which_lecture; + + // info about exam assignments + std::string exam_zone; + std::string exam_row; + std::string exam_seat; + std::string exam_room; + std::string exam_time; + std::string exam_zone_image; + + // per student notes + std::string ta_recommendation; + std::string other_note; + std::string manual_grade; + std::vector early_warnings; +}; + +//==================================================================== +//==================================================================== + +Student* GetStudent(const std::vector &students, const std::string& username); + +#endif diff --git a/table.cpp b/table.cpp new file mode 100644 index 0000000..acb15d6 --- /dev/null +++ b/table.cpp @@ -0,0 +1,158 @@ +#include +#include + +#include "table.h" +#include "constants_and_globals.h" + +bool GLOBAL_instructor_output = false; + +bool global_details = false; + +TableCell::TableCell(const std::string& c, const std::string& d, const std::string& n, int ldu, + CELL_CONTENTS_STATUS v, const std::string& a, int s, int r) { + assert (c.size() == 6); + color=c; + data=d; + note=n; + late_days_used=ldu, + visible=v; + align=a; + span=s; + rotate=r; +} + +TableCell::TableCell(const std::string& c, int d, const std::string& n, int ldu, + CELL_CONTENTS_STATUS v, const std::string& a, int s, int r) { + assert (c.size() == 6); + color=c; + data=std::to_string(d); + note=n; + late_days_used=ldu, + visible=v; + align=a; + span=s; + rotate=r; +} + +TableCell::TableCell(const std::string& c, float d, int precision, const std::string& n, int ldu, + CELL_CONTENTS_STATUS v, const std::string& a, int s, int r) { + assert (c.size() == 6); + assert (precision >= 0); + color=c; + if (fabs(d) > 0.0001) { + std::stringstream ss; + ss << std::setprecision(precision) << std::fixed << d; + data=ss.str(); span=s; + } else { + data = ""; + } + note=n; + late_days_used=ldu, + visible=v; + align=a; + span=s; + rotate = 0; +} + +std::ostream& operator<<(std::ostream &ostr, const TableCell &c) { + assert (c.color.size() == 6); + // ostr << ""; + ostr << ""; + if (0) { //rotate == 90) { + ostr << "

"; + } + ostr << ""; + std::string mynote = c.getNote(); + if ((c.data == "" && mynote=="") + || c.visible==CELL_CONTENTS_HIDDEN + || (c.visible==CELL_CONTENTS_VISIBLE_INSTRUCTOR && GLOBAL_instructor_output == false) + || (c.visible==CELL_CONTENTS_VISIBLE_STUDENT && GLOBAL_instructor_output == true)) { + ostr << "

"; + } else { + ostr << c.data; + if (c.late_days_used > 0) { + if (c.late_days_used > 3) { ostr << " (" << std::to_string(c.late_days_used) << "*)"; } + else { ostr << " " << std::string(c.late_days_used,'*'); } + } + if (mynote.length() > 0 && + mynote != " " && + (global_details + /* + || + c.visible==CELL_CONTENTS_HIDDEN + */ + ) + ) { + ostr << "
" << mynote << ""; + } + } + ostr << "
"; + if (0) { //rotate == 90) { + ostr << "

"; + } + ostr << ""; + return ostr; +} + + + +void Table::output(std::ostream& ostr, + std::vector which_students, + std::vector which_data, + bool transpose, + bool show_details, + std::string last_update) const { + + global_details = show_details; + //global_details = true; + + ostr << "\n"; + + + // ------------------------------------------------------------------------------- + // PRINT INSTRUCTOR SUPPLIED MESSAGES + for (unsigned int i = 0; i < MESSAGES.size(); i++) { + ostr << "" << MESSAGES[i] << "
\n"; + } + if (last_update != "") { + ostr << "Information last updated: " << last_update << "
\n"; + } + ostr << " 
\n"; + + + ostr << "\n"; + // ostr << "
\n"; + + if (transpose) { + for (std::vector::iterator c = which_data.begin(); c != which_data.end(); c++) { + ostr << "\n"; + for (std::vector::iterator r = which_students.begin(); r != which_students.end(); r++) { + ostr << cells[*r][*c] << "\n"; + } + ostr << "\n"; + } + } else { + for (std::vector::iterator r = which_students.begin(); r != which_students.end(); r++) { + ostr << "\n"; + for (std::vector::iterator c = which_data.begin(); c != which_data.end(); c++) { + ostr << cells[*r][*c] << "\n"; + } + ostr << "\n"; + } + } + + ostr << "
" << std::endl; +} diff --git a/table.h b/table.h new file mode 100644 index 0000000..3677bfa --- /dev/null +++ b/table.h @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include + +extern bool GLOBAL_instructor_output; + +enum CELL_CONTENTS_STATUS { CELL_CONTENTS_VISIBLE, CELL_CONTENTS_HIDDEN, CELL_CONTENTS_VISIBLE_STUDENT, CELL_CONTENTS_VISIBLE_INSTRUCTOR, CELL_CONTENTS_NO_DETAILS }; + +class TableCell { +public: + + // CONSTRUCTORS + TableCell(const std::string& c="ffcccc", const std::string& d="", const std::string& n="", int ldu=0, + CELL_CONTENTS_STATUS v=CELL_CONTENTS_VISIBLE, const std::string& a="left" , int s=1, int r=0); + TableCell(const std::string& c , int d , const std::string& n="", int ldu=0, + CELL_CONTENTS_STATUS v=CELL_CONTENTS_VISIBLE, const std::string& a="left" , int s=1, int r=0); + 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); + + std::string color; + std::string data; + int late_days_used; + std::string align; + enum CELL_CONTENTS_STATUS visible; + int span; + int rotate; + friend std::ostream& operator<<(std::ostream &ostr, const TableCell &c); + const std::string& getNote() const { return note; } +private: + std::string note; +}; + + + +class Table { + +public: + + Table() { + cells = std::vector>(1,std::vector(1)); + } + + void set(int r, int c, TableCell cell) { + while(r >= numRows()) { + cells.push_back(std::vector(numCols())); + } + while (c >= numCols()) { + for (int x = 0; x < numRows(); x++) { + cells[x].push_back(TableCell()); + } + } + cells[r][c] = cell; + } + + void output(std::ostream& ostr, + std::vector which_students, + std::vector which_data, + bool transpose=false, + bool show_details=false, + std::string last_update="") const; + + + int numRows() const { return cells.size(); } + int numCols() const { return cells[0].size(); } + +private: + std::vector> cells; +}; diff --git a/zone.cpp b/zone.cpp new file mode 100644 index 0000000..5c19a63 --- /dev/null +++ b/zone.cpp @@ -0,0 +1,468 @@ +#include +#include +#include + + +#include "student.h" +#include "constants_and_globals.h" + +//========================================================================== + +bool is_lefty_seat(const std::string& s) { + return s.back() == 'L'; +} + +class ZoneInfo { +public: + ZoneInfo() { max = 0; count = 0; } + std::string building; + std::string room; + std::string zone; + std::string image_url; + int max; + int count; + bool take_seat(std::string r, std::string s) { + assert (count < max); + std::vector >* available_seats; + if (is_lefty_seat(s)) { + available_seats = &available_lefty_seats; + } else { + available_seats = &available_nonlefty_seats; + } + for (std::vector >::iterator itr = available_seats->begin(); + itr != available_seats->end(); itr++) { + if (itr->first == r && itr->second == s) { + available_seats->erase(itr); + count++; + return true; + } + } + return false; + } + void assign_seat(std::string &r, std::string &s, bool is_lefty) { + if (num_available_seats() == 0) { + r = "N/A"; + s = "N/A"; + count++; + return; + } + + std::vector >* available_seats; + if (is_lefty) { + available_seats = &available_lefty_seats; + } else { + available_seats = &available_nonlefty_seats; + } + assert (available_seats->size() > 0); + assert (count < max); + + int random_seat = std::rand()%available_seats->size(); + std::vector >::iterator itr = available_seats->begin() + random_seat; + assert (itr != available_seats->end()); + r = itr->first; + s = itr->second; + available_seats->erase(itr); + count++; + } + void clear_seats() { + available_lefty_seats.clear(); + available_nonlefty_seats.clear(); + } + int num_available_seats() { + return available_lefty_seats.size() + available_nonlefty_seats.size(); + } + int num_lefty_seats() { + return available_lefty_seats.size(); + } + void add_seat(const std::string &r, const std::string &s) { + if (is_lefty_seat(s)) { + available_lefty_seats.push_back(std::make_pair(r,s)); + } else { + available_nonlefty_seats.push_back(std::make_pair(r,s)); + } + } +private: + std::vector > available_lefty_seats; + std::vector > available_nonlefty_seats; +}; + + +// random generator function: +int myrandomzone (int i) { return std::rand()%i;} + +//========================================================================== + +// More intuitive sort function to organize zones first by length (# +// of characters), then alphabetical. +class ZoneSorter { +public: + bool operator()(const std::string& a, const std::string& b) const { + return (a.size() < b.size() || + (a.size() == b.size() && a < b)); + } +}; + + +int CountLefties(std::vector &students,const std::string& lefty_file, float min_for_assignment) { + if (lefty_file == "") return 0; + std::ifstream istr(lefty_file.c_str()); + assert (istr.good()); + + int count = 0; + std::string user,handedness; + while (istr >> user >> handedness) { + Student *s = GetStudent(students,user); + assert (s != NULL); + if (handedness == "left") { + //std::cout << "LEFTY " << user << std::endl; + s->setLefty(); + if (s->getExamRoom() != "") { + std::cout << "ALREADY ASSIGNED ROOM " << user << std::endl; + } else if (s->overall() < GLOBAL_MIN_OVERALL_FOR_ZONE_ASSIGNMENT) { + std::cout << "NOT ASSIGNING LEFTY " << user << std::endl; + } else { + std::cout << "NEED SEAT FOR LEFTY " << user << std::endl; + count++; + } + } + } + return count; +} + +void LoadExamSeatingFile(const std::string &zone_counts_filename, + const std::string &zone_assignments_filename, + const std::string &seating_spacing, + const std::string &left_right_handedness, + std::vector &students) { + + + std::cout << "zone counts filename '" << zone_counts_filename << "'" << std::endl; + std::cout << "zone assignments filename '" << zone_assignments_filename << "'" << std::endl; + std::cout << "left_right_handedness '" << left_right_handedness << "'" << std::endl; + std::cout << "min overall " << GLOBAL_MIN_OVERALL_FOR_ZONE_ASSIGNMENT << std::endl; + + assert (zone_counts_filename != ""); + assert (zone_assignments_filename != ""); + + int num_lefties = CountLefties(students,left_right_handedness, GLOBAL_MIN_OVERALL_FOR_ZONE_ASSIGNMENT); + + std::cout << "NUM LEFTIES " << num_lefties << std::endl; + + // ============================================================ + // read in the desired zone counts + + std::map zones; + std::ifstream istr_zone_counts(zone_counts_filename.c_str()); + assert (istr_zone_counts.good()); + + int total_seats = 0; + ZoneInfo zi; + + std::string line; + + int lefty_desk_count = 0; + + std::string token; + istr_zone_counts >> token; + assert (token == "zone"); + while (1) { + + std::getline(istr_zone_counts,line); + std::stringstream ss(line); + + if (!(ss >> zi.zone >> zi.building >> zi.room >> zi.max)) { + std::cout << "MISFORMATTED ZONE COUNTS" << std::endl; exit(1); } + ss >> zi.image_url; + + zi.count=0; + zi.clear_seats(); + if (zones.find(zi.zone) != zones.end()) { + std::cerr << "\nERROR: duplicate zone " << zi.zone << " in " << zone_counts_filename << std::endl; + exit(0); + } + if (zi.max != -1) { + assert (zi.max >= 0); + total_seats += zi.max; + } + + bool read_another_zone = false; + while (istr_zone_counts >> token) { + //std::cout << "======================================\nTOKEN " << token << std::endl; + if (token[0] == '#') { + std::getline(istr_zone_counts,line); + //std::cout << "COMMENTED LINE " << line << std::endl; + continue; + } else if (token == "row") { + std::getline(istr_zone_counts,line); + std::stringstream ss(line); + std::string row,seat; + ss >> row >> token; + assert (token == ":"); + std::vector all_seats; + while (ss >> seat) { + all_seats.push_back(seat); + } + bool skip = false; + for (int i = 0; i < all_seats.size(); i++) { + seat = all_seats[i]; + //std::cout << "SEAT " << seat << std::endl; + if (seat.back() == 'X') { + //std::cout << "BROKEN DESK " << zi.zone << " "<< row << " " << seat << std::endl; + continue; + } + if (skip) { + skip = false; + //std::cout << "SKIPPING " << zi.zone << " "<< row << " " << seat << std::endl; + continue; + } + if (seat.back() == 'L') { + //std::cout << "LEFTY DESK " << zi.zone << " " << row << " " << seat << std::endl; + lefty_desk_count++; + } + skip = true; + //std::cout << "SEAT " << seat << std::endl; + zi.add_seat(row,all_seats[i]); + } + } else if (token == "zone") { + read_another_zone = true; + break; + } else { + assert (0); + } + if (token == "zone") { + read_another_zone = true; + break; + } + } + if (zi.max == -1) { + zi.max = zi.num_available_seats(); + total_seats += zi.max; + } + if (zi.num_available_seats() != 0 && + zi.max != zi.num_available_seats()) { + std::cout << "AVAILABLE SEATS FOR ZONE " << zi.zone << " are incorrect " << + zi.max << " max vs " << zi.num_available_seats() << " available" << std::endl; + exit(1); + } + zones.insert(std::make_pair(zi.zone,zi)); + if (!read_another_zone) break; + + } + std::cout << "TOTAL SEATS FOR EXAM " << total_seats << std::endl; + std::cout << "LEFTY DESK COUNT " << lefty_desk_count << std::endl; + + // if we don't have specific seating assignments in this zone, + // fill in with generic seats + for (std::map::iterator itr = zones.begin(); itr != zones.end(); itr++) { + int max = itr->second.max; + int na = itr->second.num_available_seats(); + assert (max >= na); + for (int i = 0; i < max-na; i++) { + itr->second.add_seat("N/A","N/A"); + } + } + + // ============================================================ + // read in any existing assignments... + + std::cout << "READING " << zone_assignments_filename << std::endl; + + int existing_assignments = 0; + { + std::ifstream istr_zone_assignments(zone_assignments_filename.c_str()); + if (istr_zone_assignments.good()) { + std::string line; + while (getline(istr_zone_assignments,line)) { + std::stringstream ss(line.c_str()); + std::string token,last,first,rcs,section,building,room,zone,row,seat,time; + ss >> last >> first >> rcs >> section >> building >> room >> zone >> row >> seat >> time; + + while (ss >> token) { time += " " + token; } + + //std::cout << "FOUND " << rcs << std::endl; + + if (last == "") break; + Student *s = GetStudent(students,rcs); + if (s == NULL) { + std::cout << "seating assignment... couldn't find this userid " << rcs << std::endl; + } + assert (s != NULL); + if (zone != "") { + std::map::iterator itr = zones.find(zone); + if (itr == zones.end()) { + std::cerr << "ERROR! this zone '" << zone << "' assigned to '" << s->getUserName() << "'does not exist!" << std::endl; + exit(1); + } + if (itr->second.max <= itr->second.count) { + std::cerr << "ERROR! this zone '" << zone << "' is full (max:" << itr->second.max << ")" << std::endl; + exit(1); + } + assert (itr->second.building == building); + assert (itr->second.room == room); + bool success = itr->second.take_seat(row,seat); + if (!success) { + std::cout << "ERROR COULD NOT TAKE SEAT " << zone << " row:" << row + << " seat:" << seat << std::endl; + exit(1); + } + + existing_assignments++; + s->setExamRoom(building+std::string(" ")+room); + s->setExamZone(zone,row,seat); + s->setExamZoneImage(itr->second.image_url); + if (time != "") { + s->setExamTime(time); + } else { + s->setExamTime(GLOBAL_EXAM_TIME); + } + } + } + } + } + std::cout << "EXISTING ASSIGNMENTS " << existing_assignments << std::endl; + + // ============================================================ + // make a vector of available seats + + // FIXME: this belongs once, at start of program + std::srand ( unsigned ( std::time(0) ) ); + + std::vector randomized_lefty_available; + std::vector randomized_nonlefty_available; + for (std::map::iterator itr = zones.begin(); itr != zones.end(); itr++) { + assert (itr->second.count <= itr->second.max); + int num_l = itr->second.num_lefty_seats(); + int num_nl = itr->second.num_available_seats() - num_l; + for (int i = 0; i < num_l; i++) { + randomized_lefty_available.push_back(itr->first); + } + for (int i = 0; i < num_nl; i++) { + randomized_nonlefty_available.push_back(itr->first); + } + } + std::cout << "AVAILABLE LEFTY SEATS " << randomized_lefty_available.size() << std::endl; + std::cout << "AVAILABLE NONLEFTY SEATS " << randomized_nonlefty_available.size() << std::endl; + std::random_shuffle ( randomized_lefty_available.begin(), randomized_lefty_available.end(), myrandomzone ); + std::random_shuffle ( randomized_nonlefty_available.begin(), randomized_nonlefty_available.end(), myrandomzone ); + + // ============================================================ + // do the assignments! + + int not_reg = 0; + int low_overall_grade = 0; + int new_zone_assign = 0; + int already_zoned = 0; + int next_lefty_za = 0; + int next_nonlefty_za = 0; + + for (unsigned int i = 0; i < students.size(); i++) { + + Student* &s = students[i]; + + if (s->getExamRoom() != "") { + already_zoned++; + } else if (!validSection(s->getSection())) { + not_reg++; + } else if (s->overall() < GLOBAL_MIN_OVERALL_FOR_ZONE_ASSIGNMENT) { + low_overall_grade++; + } else { + + if (s->getLefty()) { + if (next_lefty_za >= (int)randomized_lefty_available.size()) { + std::cout << "OOPS! we ran out of lefty exam seating" << std::endl; + } + assert (next_lefty_za < (int)randomized_lefty_available.size()); + ZoneInfo &next_zi = zones.find(randomized_lefty_available[next_lefty_za])->second; + s->setExamRoom(next_zi.building+std::string(" ")+next_zi.room); + std::string row,seat; + //std::cout << "ASSIGN student " << s->getUserName() << std::endl; + next_zi.assign_seat(row,seat,s->getLefty()); + s->setExamZone(next_zi.zone,row,seat); + s->setExamZoneImage(next_zi.image_url); + next_lefty_za++; + new_zone_assign++; + } else { + if (next_nonlefty_za >= (int)randomized_nonlefty_available.size()) { + std::cout << "OOPS! we ran out of nonlefty exam seating" << std::endl; + } + assert (next_nonlefty_za < (int)randomized_nonlefty_available.size()); + ZoneInfo &next_zi = zones.find(randomized_nonlefty_available[next_nonlefty_za])->second; + s->setExamRoom(next_zi.building+std::string(" ")+next_zi.room); + std::string row,seat; + //std::cout << "ASSIGN student " << s->getUserName() << std::endl; + next_zi.assign_seat(row,seat,s->getLefty()); + s->setExamZone(next_zi.zone,row,seat); + s->setExamZoneImage(next_zi.image_url); + next_nonlefty_za++; + new_zone_assign++; + } + } + } + + std::cout << "new zone assignments " << new_zone_assign << std::endl; + std::cout << "low overall grade (not assigning a zone) " << low_overall_grade << std::endl; + std::cout << "not registered in valid section " << not_reg << std::endl; + + assert (new_zone_assign <= + int(randomized_lefty_available.size()) + + int(randomized_nonlefty_available.size())); + + + // ============================================================ + // write out the assignments! + + if (1) { //new_zone_assign > 0) { + + std::ofstream ostr_zone_assignments(zone_assignments_filename.c_str()); + assert (ostr_zone_assignments.good()); + + for (unsigned int i = 0; i < students.size(); i++) { + + Student* &s = students[i]; + + if (s->getLastName() == "") continue; + + std::string f = s->getPreferredName(); + std::string l = s->getLastName(); + std::replace( f.begin(), f.end(), ' ', '_'); + std::replace( l.begin(), l.end(), ' ', '_'); + + ostr_zone_assignments << std::setw(20) << std::left << l << " "; + ostr_zone_assignments << std::setw(15) << std::left << f << " "; + ostr_zone_assignments << std::setw(12) << std::left << s->getUserName() << " "; + if (s->getSection()) + ostr_zone_assignments << std::setw(12) << std::left << s->getSection() << " "; + else + ostr_zone_assignments << std::setw(12) << std::left << "" << " "; + + ostr_zone_assignments << std::setw(10) << std::left << s->getExamRoom() << " "; + ostr_zone_assignments << std::setw(10) << std::left << s->getExamZone() << " "; + ostr_zone_assignments << std::setw(10) << std::left << s->getExamRow() << " "; + ostr_zone_assignments << std::setw(10) << std::left << s->getExamSeat() << " "; + ostr_zone_assignments << std::setw(10) << std::left << s->getExamTime(); + + ostr_zone_assignments << std::endl; + + } + + } + + // ============================================================ + // data for preparing exams + + + int total_assignments = 0; + for (std::map::iterator itr = zones.begin(); + itr != zones.end(); itr++) { + + std::cout << "ZONE " << std::left << std::setw(6) << itr->first + << " " << std::left << std::setw(10) << itr->second.building << " " + << " " << std::left << std::setw(4) << itr->second.room << " " + << " " << std::right << std::setw(4) << itr->second.count << " (" << itr->second.max-itr->second.count << " seats remain)" << std::endl; + + total_assignments += itr->second.count; + } + std::cout << "TOTAL " << total_assignments << std::endl; + + +}