refactor_plans.js
postprocessing program called after every download from mongodb:
mongodb -> allstudents_old_format.json
-> refactor_plans
(this program) -> allstudent.json
-> jekyll build
this program carries out any necessary refactoring, and calculates a number of derived values like times, residency, and name => “last_name, first_name”
Table of Contents
- Usage
- Options
- Counters
- Dependencies
- Constants
- Correct_plan_code
- Disability
- Student Name
- Missing image
- Update Degrees
- update_registration_flags
- _removeInvalidKeysFromStudent
Usage
node refactor_plans [-i <path_to_old_format_file>] [-o <path_to_new_format_file>] [<SWITCH>/-<SWITCH>]
node refactor_plans
without args, will refactor /jekyll/_data/allstudents_old_format.json
to /jekyll/_data/allstudents.json
node refactor_plans <input_students_path> <output_students_path>
will refactor old file at input_students_path
to new file saved at output_students_path
Options
Input and output files
-i <input_students_path>
will refactor old file at input_students_path
; default is /jekyll/_data/allstudents_old_format.json
-o <output_students_path>
will output to new file saved at output_students_path
; default is /jekyll/_data/allstudents.json
Command line switches
if prefixed with a dash “-“, switch is set to false. If just switch name, switch is set to true. If switch is not present, default switch value is used
-
DEBUG [ default = false ]
-
SKIP_PRE_2000 [ default = true ] don’t save grads from before 2000, to count down on size of output file
-
UPDATE_ARCHIVES [ default = true ] update the archive directories (ie reset cmte fsuids in directories containing documents)
-
ADD_DERIVED_VALUES [ default = true ] add calculated values, like time since admission, time since prelim, residency
-
DELETE_OLD_FIELDS [ default = false ] get rid of deprecated fields
-
SAVE_REFACTORED_PLANS [ default = true ] save the refactored plans as “allstudents.json” or argv[3]
-
VERBOSE [ default = false ]
-
FIXEDDATE [ default = false ] use next argument in form MM/DD/YYYY as current date
Here we declare the command line flags and set them to the default value. We then read the command line flags from process.argv, if present.
// set commandline flags defaults
var DEBUG = false;
var SKIP_PRE_2000 = true; // don't save grads from before 2000
var UPDATE_ARCHIVES = true; // update the archive directories (ie reset cmte fsuids in directories containing documents)
var ADD_DERIVED_VALUES = true; // add calcualted values, like time since admission, time since prelim, residency
var DELETE_OLD_FIELDS = false; // get rid of deprecated fields
var SAVE_REFACTORED_PLANS = true; // save the refactored plans as "allstudents.json" or argv[3]
var VERBOSE = false;
var FIXEDDATE = false; // use next argument in form YYYYMMDD as current date
var TODAYSDATE = new Date();
var input_students_path = __dirname + '/jekyll/_data/allstudents_old_format.json';
var output_students_path = __dirname + '/jekyll/_data/allstudents_lit.json';
// read command line options
for (var i = 0; i < process.argv.length; i++) {
if ("-i" == process.argv[i] && i < process.argv.length - 1) {
input_students_path = process.argv[i + 1];
}
if ("-o" == process.argv[i] && i < process.argv.length - 1) {
output_students_path = process.argv[i + 1];
}
if (-1 != process.argv[i].indexOf("DEBUG")) {
DEBUG = process.argv[i].startsWith("-") ? false : true;
}
if (-1 != process.argv[i].indexOf("SKIP_PRE_2000")) {
SKIP_PRE_2000 = process.argv[i].startsWith("-") ? false : true;
}
if (-1 != process.argv[i].indexOf("UPDATE_ARCHIVES")) {
UPDATE_ARCHIVES = process.argv[i].startsWith("-") ? false : true;
}
if (-1 != process.argv[i].indexOf("ADD_DERIVED_VALUES")) {
ADD_DERIVED_VALUES = process.argv[i].startsWith("-") ? false : true;
}
if (-1 != process.argv[i].indexOf("DELETE_OLD_FIELDS")) {
DELETE_OLD_FIELDS = process.argv[i].startsWith("-") ? false : true;
}
if (-1 != process.argv[i].indexOf("SAVE_REFACTORED_PLANS")) {
console.log("Save_factored_plans switch");
SAVE_REFACTORED_PLANS = process.argv[i].startsWith("-") ? false : true;
}
if (-1 != process.argv[i].indexOf("VERBOSE")) {
console.log("Verbose switch");
VERBOSE = process.argv[i].startsWith("-") ? false : true;
}
if (-1 != process.argv[i].indexOf("FIXEDDATE")) {
console.log("Date switch");
FIXEDDATE = process.argv[i].startsWith("-") ? false : true;
TODAYSDATE= new Date(process.argv[i+1]);
if (isNaN(TODAYSDATE)) {
console.log("BAD FIXEDDATE");
process.exit(1);
}
}
}
console.log("refactor_plans");
if (VERBOSE) {
console.log("input_students_path = " + input_students_path);
console.log("output_students_path = " + output_students_path);
console.log("DEBUG = " + DEBUG);
console.log("SKIP_PRE_2000 = " + SKIP_PRE_2000);
console.log("UPDATE_ARCHIVES = " + UPDATE_ARCHIVES);
console.log("ADD_DERIVED_VALUES = " + ADD_DERIVED_VALUES);
console.log("DELETE_OLD_FIELDS = " + DELETE_OLD_FIELDS);
console.log("SAVE_REFACTORED_PLANS = " + SAVE_REFACTORED_PLANS);
console.log("VERBOSE = " + VERBOSE);
}
//----------------------------------------------------------------------------------------
Counters
We set up counters for tracking changes made by this script.
Note especially deleted_keys
which tracks which deprecated keys were removed from student records.
// counters for tracking changes made by this script
var added_ms_plan = 0;
var added_phd_plan = 0;
var members_misspelling = 0;
var added_initial_plan = 0;
var added_plan = 0;
var updated_committee = 0;
var updated_chair = 0;
var updated_cochair = 0;
var updated_univ_rep = 0;
var updated_member = 0;
var removed_awards = 0;
var removed_rotations = 0;
var removed_teaching = 0;
var removed_seminars = 0;
var removed_program_code = 0;
var fixed_current_plan_code = 0;
var moved_current_position = 0;
var moved_GRE = 0;
var complete_bowers = 0;
var admit_bowers = 0;
var degree_awarded = 0;
var reset_admit_term = 0;
var reset_last_term = 0;
var deleted_field = 0;
var removed_invalid_keys = 0;
var current_position_remapped = 0;
var current_position_location_remapped = 0;
var ug_country_remapped = 0;
var deleted_keys = [];
//----------------------------------------------------------------------------------------
Dependencies
We load the node module dependencies:
- fs,
- read-yaml
- logging, tagged by file
- bc_utilities.
// Node modules
var fs = require('fs');
var readYAML = require('read-yaml');
require('./logging')(__filename);
var bc = require("./bc_utilities.js");
//----------------------------------------------------------------------------------------
Constants
We load 3 categories of constants:
-
constants which are invariant across departments, such as country codes, and student schemata
-
department specific constants, such as the academic_plan_codes from
/jekyll/_data/dept/academic_plan_codes.yaml
-
term data and time data, specifically the current and upcoming terms as term keys (eg “2020_spring”), and the corresponding student registration data from
/jekyll/_data/term_data/<term_key>/registations.json
// Constants
// load constants which are invariant across departments
const key_remappings = bc.readJSONfromAppDir(__dirname,'/plan_key_remappings.json');
const iso3 = bc.readJSONfromAppDir(__dirname,'/jekyll/_data/const/iso3.json');
const lacs = bc.readJSONfromAppDir(__dirname,'/jekyll/_data/const/lacs.json');
const country_names = bc.readJSONfromAppDir(__dirname,'/jekyll/_data/const/names.json');
// for validation of allstudents.json
const student_schema = readYAML.sync(__dirname + '/jekyll/_data/schemata/student_schema.yaml');
var current_keys = [];
for (let i = 0; i < student_schema.length; i++) {
if (student_schema[i].category != "derived") {
current_keys.push(student_schema[i].key);
}
}
// console.log(current_keys);
// load departmental academic plans (varies by each department)
const academic_plans = readYAML.sync(__dirname + '/jekyll/_data/dept/academic_plan_codes.yaml');
//----------------------------------------------------------------------------------------
We look up the current term by running through the terms in /jekyll/_data/univ/fsu-terms.yaml
, and compare today’s date to start and end time of each term.
const fsu_terms = readYAML.sync(__dirname + '/jekyll/_data/univ/fsu-terms.yaml');
let todays_term = "2000_fall";
let today_timestamp = TODAYSDATE.getTime();
for (var term_key in fsu_terms) {
let term_record = fsu_terms[term_key];
let term_start = Date.parse(term_record.begin_date);
let term_end = Date.parse(term_record.end_date);
if (term_start <= today_timestamp && today_timestamp <= term_end ) {
todays_term = term_key;
}
}
function termkeyWithOffset(term_key,offset) {
let parts = term_key.split("_");
let year = parts[0];
let term_name = parts[1];
for (let off = 0; off < offset; off++) {
if (term_name == "spring") {
term_name = "summer";
}
else if (term_name == "summer") {
term_name = "fall";
}
else {
term_name = "spring";
year++;
}
}
return (year + "_" + term_name);
}
const current_term = todays_term;
const next_term = termkeyWithOffset(todays_term, 1);
const next_term2 = termkeyWithOffset(todays_term, 2);
// load in registration for current term and future terms
function load_registrations(term) {
let term_directory = __dirname + "/jekyll/_data/term_data/" + term + "/";
let registration_path = term_directory + "registrations.json";
if (fs.existsSync(registration_path)) {
let reg_string = fs.readFileSync(registration_path);
return JSON.parse(reg_string);
}
else return [];
}
const current_registrations = load_registrations(current_term);
const next_registrations = load_registrations(next_term);
const next2_registrations = load_registrations(next_term2);
// current date info
const date_now = TODAYSDATE;
const month = 1 + date_now.getMonth();
const now_string = date_now.getFullYear() + '-' + month + '-' + date_now.getDate();
const day_now = Date.parse(now_string);
const time_now = day_now / 1000; // time_now is a unix seconds timestamp
//----------------------------------------------------------------------------------------
function currentPlanCode(student) {
// we converted student.academic_plan_code to an array
if (Array.isArray( student.academic_plan_code) ) {
return student.academic_plan_code[0];
}
return student.academic_plan_code;
}
//----------------------------------------------------------------------------------------
Correct_plan_code
At one point, we had refactored academic_plancode to either “current_academic_plan_code” or “current_academic_plan”,
function correct_plan_code(student) {
// correct for missing academic_plan_code
if (bc.notUndefinedAndNotNull(student.current_academic_plan_code)) {
if (bc.isUndefinedOrIsNull(student.academic_plan_code)) {
student.academic_plan_code = student.current_academic_plan_code;
fixed_current_plan_code++;
console.log("FIXED ACADEMIC PLAN FROM CURR CODE: " + student.last_name + " " + student.fsuid + " acad: " + student.academic_plan_code);
}
else {
if (student.academic_plan_code != student.current_academic_plan_code) {
console.log("MISMATCHED CURRENT ACADEMIC PLAN CODES: " + student.last_name + " " + student.fsuid + "Curr code: " + student.current_academic_plan_code + " acad: " + student.academic_plan_code);
}
}
}
if (bc.notUndefinedAndNotNull(student.current_academic_plan)) {
if (bc.isUndefinedOrIsNull(student.academic_plan_code)) {
student.academic_plan_code = student.current_academic_plan;
fixed_current_plan_code++;
console.log("FIXED ACADEMIC PLAN FROM CURR: " + student.last_name + " " + student.fsuid + " acad: " + student.academic_plan_code);
}
else {
if (student.academic_plan_code != student.current_academic_plan) {
console.log("MISMATCHED CURRENT ACADEMIC PLAN CODES: " + student.last_name + " " + student.fsuid + "Curr: " + student.current_academic_plan_code + " acad: " + student.academic_plan_code);
}
}
}
} // correct_plan_code
//----------------------------------------------------------------------------------------
function confirm_student_has_plan(student) {
if (bc.isUndefinedOrIsNull(student.academic_plans)) {
if (bc.notUndefinedAndNotNull(student.academic_plan_code)) {
console.log(student.fsuid + ": HAS NO PLANS but HAS PLAN_CODE " + student.academic_plan_code);
student.academic_plans = { };
student.academic_plans[student.academic_plan_code] = {};
}
else {
console.log(student.fsuid + ": HAS NO PLANS");
// process.exit(1);
}
}
else if (bc.notUndefinedAndNotNull(student.academic_plan_code)) {
if (bc.isUndefinedOrIsNull(student.academic_plans[student.academic_plan_code])) {
console.log(student.fsuid + ": HAS NO PLAN for CURRENT ACADEMIC PLAN CODE");
process.exit(1);
}
}
}
//----------------------------------------------------------------------------------------
const new_key = [
"current_position",
"current_position_location",
"current_position_type",
"current_position_updated",
"current_url",
"current_email",
"current_comment",
"current_address",
"current_phone"
];
const old_phd_key = [
"phd_current_postgrad_position",
"phd_current_position_location",
"phd_current_position_type",
"phd_current_position_updated",
"phd_current_url",
"phd_current_email",
"phd_current_comment",
"phd_current_address",
"phd_current_phone"
];
const old_ms_key = [
"ms_current_postgrad_position",
"ms_current_position_location",
"ms_current_position_type",
"ms_current_position_updated",
"ms_current_url",
"ms_current_email",
"ms_current_comment",
"ms_current_address",
"ms_current_phone"
];
var delete_old_fields = function(student) {
// delete all the old fields that have been refactored
if (DELETE_OLD_FIELDS) {
// compare all keys in student with keys in student_schema
var student_keys = Object.keys(student);
for (var i = 0; i < student_keys.length; i++) {
if (-1 == current_keys.indexOf(student_keys[i])) {
if ("academic_plans" == student_keys[i]) {
console.log("******************************");
}
if (-1 == deleted_keys.indexOf(student_keys[i])) {
deleted_keys.push(student_keys[i]);
}
delete student[student_keys[i]];
deleted_field++;
}
}
} // delete old fields
}; // delete old fields
//----------------------------------------------------------------------------------------
function cleanup_student(student) {
// NOTE: eventually we can remove redundant fields that are predefined for each plan_code (like name, abbr, degree, degree type, degree_product)
// because these could be looked up in a plan_codes table
//NOTE: we should validate all term codes YYYY/M to make sure they comply with /1 /6 and /9
var plan_codes = Object.keys(student.academic_plans);
// if (bc.notUndefinedAndNotNull(student.academic_plans)) {
// plan_codes = Object.keys(student.academic_plans);
// }
// else {
//
// console.log(student.last_name + " has no ACADEMIC PLANS");
//
// }
var earliest_admit_term = '3000/9';
var latest_last_term = '1000/1';
var spaceString = new String(" ");
var slashString = new String("/");
for (var i = 0; i < plan_codes.length; i++) {
let plan_code = plan_codes[i];
// make sure student overall admit_term and last_term are earliest and lastest terms across all plans
// make sure that within a plan, that admit_term is set to admit_bowers and last_term is set to complete_bowers
// make sure if complete_bowers than degree was awarded and awarded in that term
// 4/1/2019: use GST terms not Bowers terms...
if (bc.notUndefinedAndNotNull(student.academic_plans[plan_code].admit_bowers)) {
if (student.academic_plans[plan_code].admit_term != student.academic_plans[plan_code].admit_bowers) {
console.log('Student: ' + student.last_name + " mismatch admit: " + student.academic_plans[plan_code].admit_term + " vs. bowers: " + student.academic_plans[plan_code].admit_bowers);
// student.academic_plans[plan_code].admit_term = student.academic_plans[plan_code].admit_bowers;
admit_bowers++;
}
}
if (bc.notUndefinedAndNotNull(student.academic_plans[plan_code].complete_bowers)) {
if (student.academic_plans[plan_code].last_term != student.academic_plans[plan_code].complete_bowers) {
console.log('Student: ' + student.last_name + " mismatch last: " + student.academic_plans[plan_code].last_term + " vs. bowers: " + student.academic_plans[plan_code].complete_bowers);
// student.academic_plans[plan_code].last_term = student.academic_plans[plan_code].complete_bowers;
complete_bowers++;
}
if (bc.isUndefinedOrIsNull( student.academic_plans[plan_code].degree_awarded )) {
student.academic_plans[plan_code].degree_awarded = student.academic_plans[plan_code].degree;
// student.academic_plans[plan_code].degree_awarded_term = student.academic_plans[plan_code].complete_bowers;
student.academic_plans[plan_code].outcome = "graduated";
degree_awarded++;
}
}
if (bc.notUndefinedAndNotNull(student.academic_plans[plan_code]['admit_term'])) {
// replace space typo found in some students
student.academic_plans[plan_code].admit_term = student.academic_plans[plan_code].admit_term.replace(spaceString, slashString);
if (earliest_admit_term > student.academic_plans[plan_code].admit_term) {
earliest_admit_term = student.academic_plans[plan_code].admit_term;
}
}
if (bc.notUndefinedAndNotNull(student.academic_plans[plan_code]['last_term'])) {
// replace space typo found in some students
student.academic_plans[plan_code].last_term = student.academic_plans[plan_code].last_term.replace(spaceString, slashString);
if (latest_last_term < student.academic_plans[plan_code].last_term) {
latest_last_term = student.academic_plans[plan_code].last_term;
}
}
// remove null awards, rotations, seminars, and teaching (unless they have a valid date)
if (bc.notUndefinedAndNotNull(student.academic_plans[plan_code]['awards'])) {
for (let a = student.academic_plans[plan_code].awards.length - 1; a >= 0; a--) {
if (student.academic_plans[plan_code].awards[a].date == null) {
student.academic_plans[plan_code].awards.splice(a, 1);
removed_awards++;
}
}
}
if (bc.notUndefinedAndNotNull(student.academic_plans[plan_code]['rotations'])) {
for (let a = student.academic_plans[plan_code].rotations.length - 1; a >= 0; a--) {
if (student.academic_plans[plan_code].rotations[a].date == null) {
student.academic_plans[plan_code].rotations.splice(a, 1);
removed_rotations++;
}
}
}
if (bc.notUndefinedAndNotNull(student.academic_plans[plan_code]['teaching'])) {
for (let a = student.academic_plans[plan_code].teaching.length - 1; a >= 0; a--) {
if (student.academic_plans[plan_code].teaching[a].date == null) {
student.academic_plans[plan_code].teaching.splice(a, 1);
removed_teaching++;
}
}
}
if (bc.notUndefinedAndNotNull(student.academic_plans[plan_code]['seminars'])) {
for (let a = student.academic_plans[plan_code].seminars.length - 1; a >= 0; a--) {
if (student.academic_plans[plan_code].seminars[a].date == null) {
student.academic_plans[plan_code].seminars.splice(a, 1);
removed_seminars++;
}
}
}
// clean up the chairs and co-chairs, so that either 1 chair, or 2 co-chairs
if (bc.notUndefinedAndNotNull(student.academic_plans[plan_code].chair )) {
if (bc.notUndefinedAndNotNull(student.academic_plans[plan_code].co_chair1) && bc.isUndefinedOrIsNull(student.academic_plans[plan_code].co_chair2)) {
student.academic_plans[plan_code].co_chair2 = student.academic_plans[plan_code].co_chair1;
student.academic_plans[plan_code].co_chair1 = student.academic_plans[plan_code].chair;
student.academic_plans[plan_code].chair = null;
}
else if (bc.isUndefinedOrIsNull(student.academic_plans[plan_code].co_chair1) && bc.notUndefinedAndNotNull(student.academic_plans[plan_code].co_chair2)) {
student.academic_plans[plan_code].co_chair1 = student.academic_plans[plan_code].chair;
student.academic_plans[plan_code].chair = null;
}
}
if (bc.isUndefinedOrIsNull(student.academic_plans[plan_code].chair)) {
if (bc.notUndefinedAndNotNull(student.academic_plans[plan_code].co_chair1) && bc.isUndefinedOrIsNull(student.academic_plans[plan_code].co_chair2)) {
student.academic_plans[plan_code].chair = student.academic_plans[plan_code].co_chair1;
student.academic_plans[plan_code].co_chair1 = null;
}
else if (bc.isUndefinedOrIsNull(student.academic_plans[plan_code].co_chair1) && bc.notUndefinedAndNotNull(student.academic_plans[plan_code].co_chair2)) {
student.academic_plans[plan_code].chair = student.academic_plans[plan_code].co_chair2;
student.academic_plans[plan_code].co_chair2 = null;
}
}
// remove rotations from everyone except NEURSCIBPD
if (plan_code != 'NEURSCIBPD') {
if (bc.notUndefinedAndNotNull(student.academic_plans[plan_code]['rotations'])) {
delete student.academic_plans[plan_code].rotations;
}
}
// remove redundant academic_program_code
if (bc.notUndefinedAndNotNull(student.academic_plans[plan_code]['academic_program_code'])) {
delete student.academic_plans[plan_code].academic_program_code;
removed_program_code++;
}
} // next plan_code
// make sure admit and last term are updated from plans
if (earliest_admit_term != '3000/9') {
if (student.admit_term != earliest_admit_term) {
student.admit_term = earliest_admit_term;
reset_admit_term++;
}
}
if (latest_last_term != '1000/1') {
// only recent last term for graduated students
if (bc.notUndefinedAndNotNull( student.academic_plan_code)) {
if (bc.notUndefinedAndNotNull(student.last_term )) {
if (student.last_term != latest_last_term) {
student.last_term = latest_last_term;
reset_last_term++;
}
}
}
}
} // cleanup_student
//----------------------------------------------------------------------------------------
function term_code_to_unixtime(term) {
// note that day of term start dates is approximate for each term
var year = parseInt(term.substring(0, 4), 10);
var month = parseInt(term.substring(5), 10);
var day = 1;
if (month == 6) {
month = 5;
day = 10;
}
else if (month == 9) {
month = 8;
day = 20;
}
var term_time = Date.parse(year + '-' + month + '-' + day);
return (term_time / 1000);
}
//----------------------------------------------------------------------------------------
function endterm_code_to_unixtime(term) {
// note that day of term end dates is approximate for each term
var year = parseInt(term.substring(0, 4), 10);
var month = parseInt(term.substring(5), 10);
var day = 1;
if (month == 1) {
month = 5;
day = 5;
}
else if (month == 6) {
month = 8;
day = 5;
}
else if (month == 9) {
month = 12;
day = 20;
}
var term_time = Date.parse(year + '-' + month + '-' + day);
return (term_time / 1000);
}
//----------------------------------------------------------------------------------------
// TIME-DEPENDENT DERIVED VALUES
function setAge(student) {
// get age from date of birth string
if (bc.isUndefinedOrIsNull( student.date_of_birth )) {
return;
}
var birthfields = student.date_of_birth.split('/');
if (3 > birthfields.length) {
return;
}
var birth_date = new Date();
birth_date.setFullYear(parseInt(birthfields[2], 10));
birth_date.setMonth(parseInt(birthfields[0], 10));
birth_date.setDate(parseInt(birthfields[1], 10));
student.age = time_now - birth_date / 1000;
student.age = student.age / 31557600.0;
student.age = Math.round(student.age * 10) / 10;
student.age_at_admission = student.time_entered - birth_date / 1000;
student.age_at_admission = student.age_at_admission / 31557600.0;
student.age_at_admission = Math.round(student.age_at_admission * 10) / 10;
if (student.time_left != null) {
student.age_at_graduation = student.time_left - birth_date / 1000;
student.age_at_graduation = student.age_at_graduation / 31557600.0;
student.age_at_graduation = Math.round(student.age_at_graduation * 10) / 10;
}
else {
student.age_at_graduation = null;
}
// round to tenth of year
}
//----------------------------------------------------------------------------------------
function calculate_times(student) {
// set derived time values:
// time values:
// time_entered, time_in_program, years_in_program (to 0.1 years), program_year
// time_of_prelims, time_since_prelims, years_since_prelims (to 0.1 years)
// term_entered (as integer 1,6, or 9)
student.time_now = time_now;
if (typeof student.admit_term != 'undefined' && student.admit_term != null) {
student.time_entered = term_code_to_unixtime(student.admit_term);
student.term_entered = parseInt(student.admit_term.substring(5), 10);
student.academic_year_entered = parseInt(student.admit_term.substring(0, 4), 10);
if (student.term_entered < 9) {
student.academic_year_entered--;
}
// zero padded last two digits of entered year
let yy = student.academic_year_entered % 100;
student.academic_year_entered_label = (yy < 10) ? ("0" + yy) : "" + yy;
}
else {
console.log(student.fsuid + ": ============NO ADMIT TERM=============");
}
// adjust timeline start to start of academic year of entry
if (student.term_entered == 9) {
student.time_line_start = student.time_entered;
}
else if (student.term_entered == 1) {
student.time_line_start = student.time_entered - 10512000;
}
else if (student.term_entered == 6) {
student.time_line_start = student.time_entered - 21024000;
}
// if student has graduated or left
if (typeof student.last_term != 'undefined' && student.last_term != null) {
student.time_left = endterm_code_to_unixtime(student.last_term);
student.time_since_left = time_now - student.time_left;
student.years_since_left = student.time_since_left / 31557600.0;
// round to tenth of year
student.years_since_left = Math.round(student.years_since_left * 10) / 10;
// reckon academic time in program, meaning time relative to start of time line (academic year of entry)
student.term_left = parseInt(student.last_term.substring(5), 10);
student.academic_year_left = parseInt(student.last_term.substring(0, 4), 10);
if (student.term_left < 9) {
student.academic_year_left--;
}
// zero padded last two digits of entered year
let yy = student.academic_year_left % 100;
student.academic_year_left_label = (yy < 10) ? ("0" + yy) : "" + yy;
}
else {
student.time_left = null;
}
if (student.time_left != null) {
student.time_in_program = student.time_left - student.time_entered;
student.academic_time_in_program = student.time_left - student.time_line_start;
}
else {
// student hasn't graduated or left
// reckon academic time in program, meaning time relative to start of time line (academic year of entry)
student.time_in_program = time_now - student.time_entered;
student.academic_time_in_program = time_now - student.time_line_start;
}
// get age now, and age at admission/graduation from date of birth string
setAge(student);
student.years_in_program = student.time_in_program / 31557600.0;
// round to tenth of year
student.years_in_program = Math.round(student.years_in_program * 10) / 10;
student.program_year = Math.ceil(student.time_in_program / 31557600.0);
// NOTE: refactored times for timeline inside of plan instead of student
// console.log("About to look at academic plans for " + student.last_name + " " + student.fsuid);
if (bc.notUndefinedAndNotNull( student.academic_plans)) {
let plan_codes = Object.keys(student.academic_plans);
for (let i = 0; i < plan_codes.length; i++) {
calculate_plan_times(student.fsuid,student.academic_plans[plan_codes[i]]);
}
}
// reset some student values from current plan values
var plan_code = currentPlanCode(student);
var plan = student.academic_plans[plan_code];
if (bc.notUndefinedAndNotNull(plan)) {
student.years_in_plan = plan.years_in_program;
}
else {
student.years_in_plan = student.years_in_program;
}
// if ( student.last_name == "Wilson") {
// console.log("start: " + student.admit_term +
// " end: " + student.last_term +
// " time in years: " + student.years_in_program );
// }
}
//----------------------------------------------------------------------------------------
function determine_residency(student) {
// figure out student.residency status
// student should have canonical GST residency and citizenship
// but there are number of derived fields that are dependent on residency and citizenship
/*
residency_code [FLRS, NFLR, RFAL, NRAL] -- from GST
residency [ usa, intl]
lac ["lac", null] lac if intl and country is a lac country
usa_or_lac [true,false]
in_state_tuition_rate [true,false] true if FLRS or LC
citizenship = iso3 code for citizenship country
country [iso3code] from GST
country_name string
minority = [true,false]
in_state_tuition_rate [true, false] -- true if FLRS or LAC
*/
// see _data/univ/residencyValues.json for possible values
/* best test for international is if they have a visa ? */
if (student.residency_code == 'FLRS' || student.residency_code == 'NFLR') {
student.residency = "usa";
}
else if (student.residency_code == 'NRAL') {
student.residency = "intl";
}
else if (bc.notUndefinedAndNotNull(student.visa_permit_type)) {
student.residency = "intl";
}
else {
student.residency = "usa";
}
/* set lac status */
student.lac = false;
if ("intl" == student.residency) {
if (student.country) {
if (lacs[student.country]) {
student.lac = true;
}
}
}
student.usa_or_lac = ("usa" == student.residency || student.lac );
/* set citizenship */
if (student.residency != 'usa') {
if (student.country) {
student.citizenship = student.country;
student.country_name = country_names[student.country];
}
}
else {
student.citizenship = 'USA';
student.country_name = "United States";
}
// only 3 residency codes are non-resident for out of state tuition
student.in_state_tuition_rate =
(student.residency_code == 'NFLR' ||
student.residency_code == "NRAL" ||
student.residency_code == "RANF") ? false : true;
// if ethnicity is BL, HI, NA, or Multiple then minority = true
student.minority = false;
student.underrepresented_ethnicity = false;
student.minority_noncitizen = false;
if (student.ethnicity == "AMIND" ||
student.ethnicity == "HISPA" ||
student.ethnicity == "PACIF" ||
student.ethnicity == "BLACK" ||
student.ethnicity == "MULTIPLE") {
student.underrepresented_ethnicity = true;
if (student.residency == 'usa') {
student.minority = true;
}
else {
student.minority_noncitizen = true;
}
}
}
//----------------------------------------------------------------------------------------
function calculate_plan_times(fsuid, plan) {
plan.time_now = time_now;
if (typeof plan.admit_term != 'undefined' && plan.admit_term != null) {
plan.time_entered = term_code_to_unixtime(plan.admit_term);
plan.term_entered = parseInt(plan.admit_term.substring(5), 10);
plan.academic_year_entered = parseInt(plan.admit_term.substring(0, 4), 10);
if (plan.term_entered < 9) {
plan.academic_year_entered--;
}
// zero padded last two digits of entered year
let yy = plan.academic_year_entered % 100;
plan.academic_year_entered_label = (yy < 10) ? ("0" + yy) : "" + yy;
}
else {
console.log(fsuid + ": ============NO ADMIT TERM for PLAN " + plan.plan_code + "=============");
}
// adjust timeline start to start of academic year of entry
if (plan.term_entered == 9) {
plan.time_line_start = plan.time_entered;
}
else if (plan.term_entered == 1) {
plan.time_line_start = plan.time_entered - 10512000;
}
else if (plan.term_entered == 6) {
plan.time_line_start = plan.time_entered - 21024000;
}
// if student has graduated or left
if (typeof plan.last_term != 'undefined' && plan.last_term != null) {
plan.time_left = endterm_code_to_unixtime(plan.last_term);
plan.time_since_left = time_now - plan.time_left;
plan.years_since_left = plan.time_since_left / 31557600.0;
// round to tenth of year
plan.years_since_left = Math.round(plan.years_since_left * 10) / 10;
// reckon academic time in program, meaning time relative to start of time line (academic year of entry)
plan.term_left = parseInt(plan.last_term.substring(5), 10);
plan.academic_year_left = parseInt(plan.last_term.substring(0, 4), 10);
if (plan.term_left < 9) {
plan.academic_year_left--;
}
// zero padded last two digits of entered year
let yy = plan.academic_year_left % 100;
plan.academic_year_left_label = (yy < 10) ? ("0" + yy) : "" + yy;
}
else {
plan.time_left = null;
}
if (plan.time_left != null) {
plan.time_in_program = plan.time_left - plan.time_entered;
plan.academic_time_in_program = plan.time_left - plan.time_line_start;
}
else {
// student hasn't graduated or left
// reckon academic time in program, meaning time relative to start of time line (academic year of entry)
plan.time_in_program = time_now - plan.time_entered;
plan.academic_time_in_program = time_now - plan.time_line_start;
}
plan.years_in_program = plan.time_in_program / 31557600.0;
// round to tenth of year
plan.years_in_program = Math.round(plan.years_in_program * 10) / 10;
plan.program_year = Math.ceil(plan.time_in_program / 31557600.0);
// to speed up drawing of time line, we pre-calculate the times of 5 events: committee_established, program_of_studies, prelims, proposal, and defense
// get time since prelims, but avoid parsing a null value if prelims haven't been passed yet -->
// convert prelim date (or current date if missing) to YYYY-MM-DD format
if (plan.prelim_exam_passed_date) {
plan.time_of_prelims = Date.parse(plan.prelim_exam_passed_date);
plan.time_of_prelims = plan.time_of_prelims / 1000;
plan.time_since_prelims = time_now - plan.time_of_prelims;
plan.years_since_prelims = plan.time_since_prelims / 31557600.0;
// round to tenth of year
plan.years_since_prelims = Math.round(plan.years_since_prelims * 10) / 10;
// console.log("time: " + plan.time_of_prelims + " since: " + plan.time_since_prelims + " years: " + plan.years_since_prelims);
}
else {
plan.time_of_prelims = null;
plan.years_since_prelims = 0;
}
if (plan.candidacy_term) {
plan.time_of_candidacy = endterm_code_to_unixtime(plan.candidacy_term);
// plan.time_of_candidacy = plan.time_of_candidacy; already in unix seconds
plan.time_since_candidacy = time_now - plan.time_of_candidacy;
plan.years_since_candidacy = plan.time_since_candidacy / 31557600.0;
// round to tenth of year
plan.years_since_candidacy = Math.round(plan.years_since_candidacy * 10) / 10;
// console.log("time: " + plan.time_of_candidacy + " since: " + plan.time_since_candidacy + " years: " + plan.years_since_candidacy);
}
else {
plan.time_of_candidacy = null;
plan.years_since_candidacy = 0;
}
// get time since committee established
if (plan.committee_established_date) {
plan.time_of_committee_established = Date.parse(plan.committee_established_date);
plan.time_of_committee_established = plan.time_of_committee_established / 1000;
plan.time_since_committee_established = time_now - plan.time_of_committee_established;
plan.years_since_committee_established = plan.time_since_committee_established / 31557600.0;
// round to tenth of year
plan.years_since_committee_established = Math.round(plan.years_since_committee_established * 10) / 10;
}
else {
plan.time_of_committee_established = null;
plan.years_since_committee_established = 0;
}
// get time since program of studies filed
if (plan.program_of_studies_approved_date) {
plan.time_of_studies = Date.parse(plan.program_of_studies_approved_date);
plan.time_of_studies = plan.time_of_studies / 1000;
plan.time_since_studies = time_now - plan.time_of_studies;
plan.years_since_studies = plan.time_since_studies / 31557600.0;
// round to tenth of year
plan.years_since_studies = Math.round(plan.years_since_studies * 10) / 10;
}
else {
plan.time_of_studies = null;
plan.years_since_studies = 0;
}
// get time since proposal
if (plan.proposal_defense_passed_date) {
plan.time_of_proposal = Date.parse(plan.proposal_defense_passed_date);
plan.time_of_proposal = plan.time_of_proposal / 1000;
plan.time_since_proposal = time_now - plan.time_of_proposal;
plan.years_since_proposal = plan.time_since_proposal / 31557600.0;
// round to tenth of year
plan.years_since_proposal = Math.round(plan.years_since_proposal * 10) / 10;
}
else {
plan.time_of_proposal = null;
plan.years_since_proposal = 0;
}
// get time since defense_date
if (plan.defense_date) {
plan.time_of_defense = Date.parse(plan.defense_date);
plan.time_of_defense = plan.time_of_defense / 1000;
plan.time_since_defense = time_now - plan.time_of_defense;
plan.years_since_defense = plan.time_since_defense / 31557600.0;
// round to tenth of year
plan.years_since_defense = Math.round(plan.years_since_defense * 10) / 10;
}
else {
plan.time_of_defense = null;
plan.years_since_defense = 0;
}
// console.log("start: " + plan.admit_term + " end: " + plan.last_term + " time in years: " + plan.years_in_program );
} // calculate_plan_times
//----------------------------------------------------------------------------------------
function update_archive_directories(student) {
// make sure that there is a ./jekyll/archive/<fsuid>/ directory to store pdf documents,
// and that it has an updated .cassowary_users file containing the student fsuid and the committee fsuids
var archive_directory_path = __dirname + '/jekyll/archive';
if (!fs.existsSync(archive_directory_path)) {
fs.mkdirSync(archive_directory_path);
}
var archive_path = __dirname + '/jekyll/archive/' + student.fsuid;
if (!fs.existsSync(archive_path)) {
fs.mkdirSync(archive_path);
}
var cassowary_file = archive_path + "/.cassowary_users";
var cmteIDs = arrayOfCommitteeFSUIDs(student);
cmteIDs.push(student.fsuid);
var archive_users = cmteIDs.join(" ");
fs.writeFile(cassowary_file, archive_users, function(err) {
if (err) {
return console.log("Error Writing Cassoway Users for " + student.fsuid + ":" + err);
}
});
}
//----------------------------------------------------------------------------------------
function arrayOfCommitteeFSUIDs(student) {
var cmteIDs = new Array;
// console.log(student.fsuid +": arrayOfCommitteeFSUIDs");
// console.log(student.academic_plans);
if (bc.notUndefinedAndNotNull(student.academic_plan_code)){
let plan = student.academic_plans[student.academic_plan_code];
if (bc.notUndefinedAndNotNull(plan.chair)) {
cmteIDs.push(plan.chair.fsuid);
}
if (bc.notUndefinedAndNotNull(plan.co_chair1)) {
cmteIDs.push(plan.co_chair1.fsuid);
}
if (bc.notUndefinedAndNotNull(plan.co_chair2)) {
cmteIDs.push(plan.co_chair2.fsuid);
}
if (bc.notUndefinedAndNotNull(plan.univ_rep)) {
cmteIDs.push(plan.univ_rep.fsuid);
}
if (bc.notUndefinedAndNotNull(plan.members)) {
let start_length = plan.members.length;
bc.removeInvalidKeysFromObject(plan.members);
let end_length = plan.members.length;
if (start_length != end_length) {
console.log(student.fsuid + " Committee Members cleaned");
}
for (var i = 0; i < plan.members.length; i++) {
if (bc.isUndefinedOrIsNull(plan.members[i])) {
console.log(plan.members);
}
if (bc.isUndefinedOrIsNull(plan.members[i].fsuid)) {
console.log(student.fsuid + " Committee Member Missing fsuid");
console.log(plan.members);
// process.exit(1);
}
else {
cmteIDs.push(plan.members[i].fsuid);
}
}
}
}
return cmteIDs;
}
//----------------------------------------------------------------------------------------
Disability
The university currently doesn’t track disability very well, but we’ll add a disability field to the student record for future expansion.
Default value is “no”.
function set_disability(student) {
if (bc.isUndefinedOrIsNull(student.disability) ) {
student.disability = "no";
}
} // set_disability
//----------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------
Student Name
As a convenience, we set student.name to last_name, first_name. Note we also have the student.full_name with “official” name as recognized by university via GST.
function set_name(student) {
student.name = student.last_name + ", " + student.first_name;
} // set name
//----------------------------------------------------------------------------------------
Missing image
In gradbeta, the Bio department photograph of the student is used as their image_url. If that is missing, or we’re using GST data, then we use the id_card_image.
function set_missing_image(student) {
// TODO: add privacy feature?
if (bc.isUndefinedOrIsNull(student.image_url)) {
if (bc.notUndefinedAndNotNull(student.idcard_image_url)) {
student.image_url = student.idcard_image_url;
}
}
}
//----------------------------------------------------------------------------------------
function remap_old_fields(student) {
if (bc.notUndefinedAndNotNull(student.current_position)) {
if (bc.notUndefinedAndNotNull ( student.current_postgrad_position) ) {
student.current_postgrad_position = student.current_postgrad_position + " (" + student.current_position + ")";
}
else {
student.current_postgrad_position = student.current_position;
}
current_position_remapped++;
}
if (bc.notUndefinedAndNotNull(student.current_position_location) ) {
if (bc.isUndefinedOrIsNull (student.current_postgrad_location)) {
student.current_postgrad_location = student.current_position_location;
}
else {
student.current_postgrad_location = student.current_position_location;
}
current_position_location_remapped++;
}
if (bc.notUndefinedAndNotNull (student.ug_country )) {
if (bc.isUndefinedOrIsNull( student.undergrad_country)) {
student.undergrad_country = student.ug_country;
}
ug_country_remapped++;
}
} // remap_old_fields
function fix_degree_awarded_outcome_and_thesisproposal(student) {
var plan_keys = Object.keys(student.academic_plans);
for (var i = 0; i < plan_keys.length; i++) {
var plan_code = plan_keys[i];
var plan = student.academic_plans[plan_code];
if (bc.notUndefinedAndNotNull(plan.degree_awarded)) {
plan.outcome = "graduated";
}
if (bc.notUndefinedAndNotNull(plan.thesis_proposal_defense_passed_date)) {
plan.proposal_defense_passed_date = plan.thesis_proposal_defense_passed_date;
delete plan.thesis_proposal_defense_passed_date;
}
}
}
//----------------------------------------------------------------------------------------
Update Degrees
With every rebuild, the student may have been awarded a new degree. So we check if any plans have been completed, and add them to a comma delimited list of student.degrees.
(TODO: If a plancode is not recognized, maybe from another department, then the degree is not added…)
function update_degrees_awarded(student) {
var keys = Object.keys(student.academic_plans);
for (var i = 0; i < keys.length; i++) {
var plan_code = keys[i];
if (student.academic_plans[plan_code].outcome == 'graduated') {
if (bc.notUndefinedAndNotNull( academic_plans[plan_code])) {
let degree = academic_plans[plan_code].degree;
if (typeof student.degrees == 'undefined' || student.degrees == null ||
student.degrees.length == 0) {
student.degrees = degree;
}
else {
if (-1 == student.degrees.indexOf(degree)) {
student.degrees = student.degrees + ", " + degree;
}
}
}
}
}
}
//----------------------------------------------------------------------------------------
update_registration_flags
function update_registration_flags(student) {
// TODO: put all registration into map, so we can look backwards
//
// TODO: figure out how to set prelim flag true if prelims have been taken
// registered_for_BSC8985
// registered_for_bsc8964
// registered_for_BSC8976
function termkey2code(termkey) {
console.log(termkey);
let parts = termkey.split("_");
let month = 1;
if ("summer" == parts[1]) {
month = 6;
}
if ("fall" == parts[1]) {
month = 9;
}
return (parts[0] + "/" + month);
}
// must have an active plan
let plan_code = student.academic_plan_code;
if (bc.isUndefinedOrIsNull(plan_code)) {
return;
}
let registration_requirements = [];
for (let i in academic_plans[plan_code].requirements) {
let requirement = academic_plans[plan_code].requirements[i];
if (requirement.value_type == "registration") {
registration_requirements.push(requirement);
}
}
function check_registration(registrations,course_code) {
return registrations.reduce(function(accumulator, item) {
if (item.emplid == student.emplid) {
accumulator = accumulator || item.classes.reduce(function(acc, class_item) {
acc = acc || class_item.class_code.includes(course_code);
return acc;
}, false);
}
return accumulator;
}, false);
}
// TODO: check all terms for all registrations, not just adjacent terms, for historical update
for (let i in registration_requirements) {
let course_code = registration_requirements[i].course_value;
let key = registration_requirements[i].key;
// if (typeof student.academic_plans[plan_code][key] != "undefined" && student.academic_plans[plan_code][key] != null ) {
// preserve pre-existing flags
student.academic_plans[plan_code][key] = check_registration(current_registrations,course_code) ? termkey2code(current_term) : student.academic_plans[plan_code][key];
student.academic_plans[plan_code][key] = check_registration(next_registrations,course_code) ? termkey2code(next_term) : student.academic_plans[plan_code][key];
student.academic_plans[plan_code][key] = check_registration(next2_registrations,course_code) ? termkey2code(next_term2) : student.academic_plans[plan_code][key];
// if ( student.academic_plans[plan_code][key]) {
// console.log(student.fsuid + " " + key + " registered");
// }
// }
}
}
//----------------------------------------------------------------------------------------
_removeInvalidKeysFromStudent
call this routine on each student in $scope.students before sending to eve/mongodb to strip out all keys with null or empty values from the data structure. removeInvalids recursively removes undefined/nulls/empty/whitespace from object, subobjects, and subarrays. Compact arrays with null entries; delete keys from objects with null value.
see stackoverflow: recursively-remove-null-values-from-javascript-object
var _removeInvalidKeysFromStudent = function(student) {
let old_student = JSON.stringify(student,null," ");
bc.removeInvalidKeysFromObject(student);
let new_student = JSON.stringify( student,null," ");
if (old_student != new_student) {
removed_invalid_keys++;
}
};
//----------------------------------------------------------------------------------------
var refactor = function(students) {
var num_students = students.length;
console.log("Num: " + num_students);
for (let i = num_students - 1; i >= 0; i--) {
// get admit time in months
// don't copy over students admitted prior to fall 2000
if (typeof students[i].admit_term != 'undefined' || students[i].admit_term != null) {
let parts = students[i].admit_term.split("/");
var admit_year = parseInt(parts[0],10);
var admit_month = parseInt(parts[1],10);
}
else {
admit_month = 0;
admit_year = 0;
console.log(" MISSING ADMIT TERM: " + students[i].fsuid);
}
let admit_time = (admit_month * 1) + (admit_year * 12);
// 24081 = fall 2006
// 24009 = fall 2000
// 23985 = fall 1998 (when houpt arrived)
if (admit_time < 23985 && SKIP_PRE_2000) {
students.splice(i, 1);
}
else {
// a few left over fields that need remapping for mongo-sourced data
// should not be required for GST-sourced data
correct_plan_code(students[i]);
confirm_student_has_plan(students[i]);
remap_old_fields(students[i]);
delete_old_fields(students[i]);
cleanup_student(students[i]);
fix_degree_awarded_outcome_and_thesisproposal(students[i]);
// these apply to all data, whether mongo or GST-sourced data
set_disability(students[i]); // set disability to "no" by default
set_missing_image(students[i]);
update_degrees_awarded(students[i]);
// get rid of nulls or empty fields
_removeInvalidKeysFromStudent(students[i]);
// These changes applied after every download from mongo
// to update things and calculate derived times, residency, and name
if (UPDATE_ARCHIVES) {
update_archive_directories(students[i]);
}
if (ADD_DERIVED_VALUES) {
update_registration_flags(students[i]);
calculate_times(students[i]);
determine_residency(students[i]);
set_name(students[i]);
}
// get rid of nulls or empty fields
_removeInvalidKeysFromStudent(students[i]);
}
}
num_students = students.length;
console.log("Reduced Num: " + num_students);
if (VERBOSE) {
console.log("added_ms_plan: " + added_ms_plan);
console.log("added_phd_plan: " + added_phd_plan);
console.log("members_misspelling: " + members_misspelling);
console.log("added_initial_plan: " + added_initial_plan);
console.log("added_plan: " + added_plan);
console.log("updated_committee: " + updated_committee);
console.log("updated_chair: " + updated_chair);
console.log("updated_cochair: " + updated_cochair);
console.log("updated_univ_rep: " + updated_univ_rep);
console.log("updated_member: " + updated_member);
console.log("removed_awards: " + removed_awards);
console.log("removed_rotations: " + removed_rotations);
console.log("removed_teaching: " + removed_teaching);
console.log("removed_seminars: " + removed_seminars);
console.log("removed_program_code: " + removed_program_code);
console.log("fixed_current_plan_code: " + fixed_current_plan_code);
console.log("moved_current_position: " + moved_current_position);
console.log("moved_GRE: " + moved_GRE);
console.log("complete_bowers: " + complete_bowers);
console.log("admit_bowers: " + admit_bowers);
console.log("degree_awarded: " + degree_awarded);
console.log("reset_admit_term: " + reset_admit_term);
console.log("reset_last_term: " + reset_last_term);
console.log("current_position_remapped: " + current_position_remapped);
console.log("current_position_location_remapped: " + current_position_remapped);
console.log("ug_country_remapped: " + ug_country_remapped);
console.log("deleted_field: " + deleted_field);
console.log("removed_invalid_keys: " + removed_invalid_keys);
for (let i = 0; i < deleted_keys.length; i++) {
console.log(" " + deleted_keys[i]);
}
} // end verbose
}; // refactor
//----------------------------------------------------------------------------------------
function save(students) {
if (SAVE_REFACTORED_PLANS) {
bc.backupFile(output_students_path);
fs.writeFile(output_students_path, JSON.stringify(students, null, "\t"), function(err) {
if (err) {
return console.log(err);
}
else {
// make backup copy of this refactored allstudents.json
bc.backupFile(output_students_path);
}
});
}
}
//----------------------------------------------------------------------------------------
```js
var allstudents = bc.readJSONfromCWD(input_students_path);
console.log(“\n\n\n”);
refactor(allstudents);
save(allstudents);