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; + + +}