gst-to-gradphile

USAGE: node gst-to-gradphile ['update']

ACADORG should by GST organization code, eg. ‘ASBIO’ if ‘update’ indicates that pre-existing allstudents.json will be updated

function: combines output of fetch-gst-plans-extended (_gst_student_plans_extended.json)

TODO:

chain in a bash script? get organization, login and password run fetch-gst subprograms put in errors if can’t find faculty file…

add rules for when we 1) generate allstudents de novo from gst vs. 2) update from gst (and which fields should be overwriten on update from gst)

add new fields from gst tracks & plans (indicated by //***** ) to academic_plan_codes.yaml schema add privacy flags to schemata/student_schema to include in directory, include photo, include address

Ask Dave Yancey: get test scores of inactive students as well get residency, gender, ethnicity scores on plans-extended (just like active tracks) get committees of inactive students as well get objects of inactive students as well ask about webservice for graduate faculty too

figure out academic_subplans

run or trigger refactor_plans.js to update dependent variables reconcile committees (to add first_name, last_name, dept, fsuid and email) with dept.faculty write interface to active directory to map emplid to FSUID

Command Lines Switches


// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------

if (typeof process.argv[2] == "undefined") {
	console.log("\n");
	console.log("You must specify the academic organization, eg ASBIO, as a command line argument");
	console.log("USAGE: node " + process.argv[1] + " <ACADORG>  ['update'] [FIXEDDATE MM/DD/YYYY]");
	process.exit(1);
	console.log("\n");
}
var org_abbr = process.argv[2];

var updating = false;

var FIXEDDATE = false;
var TODAYSDATE= new Date();

var faculty_path = __dirname + '/../source_data/dept/faculty.csv';

var output_students_path = __dirname+'/' + org_abbr + '_allstudents_old_format.json';
var pre_output_students_path =  __dirname+'/' + org_abbr + '_allstudents_old_format_preupdate.json';
var input_students_path = __dirname + '/../source_data/students/allstudents_old_format.json';

// read command line options



for (let 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("update")) {
		updating = 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);
        }
     }

}


Dependencies

We need to read in fsuid and password from GST_SECRETS.sh to query LDAP to look up committee members


const fs = require('fs');
const bc = require('../bc_utilities.js');
require('../logging')(__filename);

// login needed to retrieve ldap_info

var username_regex = new RegExp('FSUID=(.+)\n', 'gm');

var password_regex = new RegExp('PASSWORD=(.+)[\n$]', 'gm');

var secrets = fs.readFileSync(__dirname + "/GST_SECRETS.sh").toString('utf8');

// console.log(secrets);

const password = password_regex.exec(secrets)[1];
const username = username_regex.exec(secrets)[1];

Date utilities

Useful for converting dates to GST termcodes for the current term, eg.

10/15/2019 -> 2019/9

GST and gradphile use several different codes to specific an academic term;

months

for easy comparison of terms, ie to find earliest term, or time between terms, the terms can be converted to an integer number of months from 0/0000 (although there was no year 0000)

termcode

4-digit year + term index, eg.

fourdigit code

abbreviated form of term code, by eliminating the century digit from the year, eg

term_key

a more explicit key used in gradphile hashmaps:

This program approximates terms use rough demarcations of 1/1, 5/1 and 8/25 (the actual dates for specific terms vary year to year, and are listed insource_data/univ/fsu-terms.yaml).

So a date can be converted to a term


var date_now = TODAYSDATE;
var todays_date = (date_now.getMonth() + 1) + "/" + date_now.getDate() + "/" + date_now.getFullYear();
var todays_date_string = date_now.toString();

function fourdigit2termcode(fourdigit) {

	// fourdigit code leaves out century digit: 2019/9 -> 2199

	if (0 == fourdigit.length) {
		return null;
	}

	var month = fourdigit % 10;
	var year_code = Math.floor(fourdigit / 10);
	var year = Math.floor(year_code / 100) * 1000 + year_code % 100;
	if (year < 2000) {
		year += 900;
	}

	//  console.log(fourdigit + " --> " + year.toString() + '/' + month.toString());
	return year.toString() + '/' + month.toString();

}

function dayMonthYear2termcode(dayMonthYear) {

	// daymonthyear dd-mm-YYYY

	var month2Int = {

		"JAN": 1,
		"FEB": 2,
		"MAR": 3,
		"APR": 4,
		"MAY": 5,
		"JUN": 6,
		"JUL": 7,
		"AUG": 8,
		"SEP": 9,
		"OCT": 10,
		"NOV": 11,
		"DEC": 12

	};

	if (0 == dayMonthYear.length) {
		return null;
	}
	var fields = dayMonthYear.split("-");

	var date = parseInt(fields[0], 10);
	var month = month2Int[fields[1]];
	var year = parseInt(fields[2], 10) + 2000;

	var term = 1;

	if ((month == 8 && date > 25) || (month >= 9)) {
		term = 9;
	}
	else if (month >= 5) {
		term = 6;
	}

	//  console.log(fourdigit + " --> " + year.toString() + '/' + month.toString());
	return year.toString() + '/' + term.toString();

}

function termcode2months(termcode) {
	let comp = termcode.split('/');
	return parseInt(comp[0], 10) * 12 + parseInt(comp[1], 10);
}

Match Student


function match_student_with_gst_record_by_emplid(students, gst_record) {

	// return a matching student, or create a new one and add it to students array

	let record_id = parseInt(gst_record["EMPLID"], 10);
	for (let i = 0; i < students.length; i++) {
		var student = students[i];
		//console.log("compare student " + students["emplid"] + " record " + record_id);
		if (student["emplid"] == record_id) {
			return students[i];
		}
	}

	return null;

} // match_student_with_gst_record_by_emplid

Match Plan


function match_plan_with_gst_record_by_trackid(student, gst_record) {

	// return a matching student, or create a new one and add it to students array

	let track_id = parseInt(gst_record["STUDENTTRACKID"], 10);
	var plan_keys = Object.keys(student['academic_plans']);
	for (let i = 0; i < plan_keys.length; i++) {
		var plan_code = plan_keys[i];
		//console.log("compare student " + students["emplid"] + " record " + record_id);
		if (student['academic_plans'][plan_code]["gst_trackid"] == track_id) {
			return plan_code;
		}
	}

	return null;

} // match_plan_with_gst_record_by_trackid

Update Students

Load GST Data

These file should have been previously downloaded by fetch-gst-*.js scripts, which connect to GST via SOAP interface and download a series of json files.


// var gst_tracks = require(__dirname+'/'+org_abbr+'_gst_active_tracks_extended.json');
// update_active_students_from_gst_tracks(allstudents,gst_tracks);

var gst_plans = bc.readJSONfromAppDir(__dirname,'/'+org_abbr+'_gst_student_plans_extended.json');
var gst_scores = bc.readJSONfromAppDir(__dirname,'/'+org_abbr+'_gst_testscores.json');
var gst_cmtes = bc.readJSONfromAppDir(__dirname,'/'+org_abbr+'_gst_cmtes.json');
var gst_objects = bc.readJSONfromAppDir(__dirname,'/'+org_abbr+'_gst_student_objects.json');

keep list of keys that need to be updated


function updatedFromGST(student,key) {

// keep track if student updated, and keep an array of updated keys,  in a map accessed by today's date

     
     if (bc.isUndefinedOrIsNull(student.updated_gst_keys)) {
          student.updated_gst_keys = {};
          
             
     }
     if (bc.isUndefinedOrIsNull( student.updated_gst_keys[todays_date_string])) {
          student['_updated_from_gst'] = true;
           student['_last_updated_from_gst'] = todays_date_string;
           student.updated_gst_keys[todays_date_string] = [];
    }  
     
     student.updated_gst_keys[todays_date_string].push(key);

}


Update Personal Info

function update_active_students_from_gst_tracks(students, gst_tracks) {

	for (var i = 0; i < gst_tracks.length; i++) {
		var gst_record = gst_tracks[i];
		let student = match_student_with_gst_record_by_emplid(students, gst_record);
		if (null == student) {
			// create a new student
			student = {};
			student["emplid"] = parseInt(gst_record["EMPLID"], 10);
			student["academic_plans"] = {};

			students.push(student);
		}

		update_student_from_gst_record(student, gst_record);
		// console.log(student);
	} // next gst_record

} // update_active_students_from_gst_tracks

function update_student_from_gst_record(student, gst_record) {

	// PERSONAL INFO
	// overwrite name, email, and address etc from gst
	student["fsuid"] = gst_record["FSUID"].toLowerCase();
	student['full_name'] = gst_record["STUDENTNAME"].trim();

	var name_parts = student['full_name'].split(",");
	name_parts[1] = name_parts[1].trim();
	var first_name_parts = name_parts[1].split(" ");
	if (bc.isUndefinedOrIsNull(student['first_name'])) {
		student['first_name'] = first_name_parts[0];
	}
	if (bc.isUndefinedOrIsNull(student['last_name'])) {
		student['last_name'] = name_parts[0].trim();
	}
	student["campus_email"] = gst_record["CAMP_EMAIL"]; //*****
	student["preferred_email"] = gst_record["PREF_EMAIL"]; //*****
	// TODO: make sure students know that they can put dept email as their preferred email
	if (org_abbr != "ASBIO") {
		student["email"] = gst_record["PREF_EMAIL"];
	}

	var address_lines = new Array;
	if (gst_record["MAIL_ADDRESS1"].length > 0) {
		address_lines.push(gst_record["MAIL_ADDRESS1"]);
	}
	if (gst_record["MAIL_ADDRESS2"].length > 0) {
		address_lines.push(gst_record["MAIL_ADDRESS2"]);
	}
	if (gst_record["MAIL_CITY"].length > 0) {
		address_lines.push(gst_record["MAIL_CITY"]);
	}
	if (gst_record["MAIL_STATE"].length > 0) {
		address_lines.push(gst_record["MAIL_STATE"]);
	}
	if (gst_record["MAIL_POSTAL"].length > 0) {
		address_lines.push(gst_record["MAIL_POSTAL"]);
	}

	student["address"] = "";
	for (var i = 0; i < address_lines.length; i++) {
		student["address"] = student["address"] + address_lines[i];
		if (i < address_lines.length - 1) {
			student["address"] = student["address"] + ", ";
		}
	}

	student["idcard_image_url"] = "https://images.its.fsu.edu/fsucardimages/" + gst_record["IMAGE_NAME"];
	if (typeof student["image_url"] == undefined || student["image_url"] == null) {
		student["image_url"] = student["idcard_image_url"];
	}
	student["visa_permit_type"] = gst_record["VISA_PERMIT_TYPE"];
	student["date_of_birth"] = gst_record["BIRTHDAY"];
	student["country"] = gst_record["COUNTRY"];

	if (bc.notUndefinedAndNotNull(gst_record["GENDER"])) {
		student["sex"] = gst_record["GENDER"];
	}
	if (bc.notUndefinedAndNotNull(gst_record["RESIDENCY"])) {
		student["residency_code"] = gst_record["RESIDENCY"];
	}
	if (bc.notUndefinedAndNotNull(gst_record["ETHNIC_GRP_CD"])) {
		student["ethnicity"] = gst_record["ETHNIC_GRP_CD"];
	}

} // update_student_from_gst_record

Update Student Plans

function match_student_plan_with_gst_plan_code(student, gst_plan_code) {
	if (Object.keys(student.academic_plans).includes(gst_plan_code)) {
		return student.academic_plans[gst_plan_code];
	}
	return null;
} // match_student_plan_with_gst_plan_code

function update_students_from_gst_plans(students, gst_plans) {

	for (var i = 0; i < gst_plans.length; i++) {
		var gst_plan = gst_plans[i];
		let student = match_student_with_gst_record_by_emplid(students, gst_plan);

		if (null == student) {
			// create a new student
			student = {};
			student["emplid"] = parseInt(gst_plan["EMPLID"], 10);
			student["academic_plans"] = {};
			students.push(student);
		}

		// TODO: add initial plan code (or fake it?)

		update_student_from_gst_record(student, gst_plan);
		update_plan_from_gst_plan(student, gst_plan);
		//console.log(gst_plan);
		//console.log(student);

	} // next gst_record

} // update_plans_from_gst_plans

function update_plan_from_gst_plan(student, gst_plan) {

	// need all actve fields for past students too

	var gst_plan_code = gst_plan["LAST_ACAD_PLAN"];

	var plan = match_student_plan_with_gst_plan_code(student, gst_plan_code);

	if (null == plan) {
		//	console.log("PLAN MISMATCH " + student.last_name + " " + student.fsuid + " " + gst_plan_code);
		plan = {};
		student.academic_plans[gst_plan_code] = plan;
	}

	// console.log('about to get admit_term: ' + gst_plan["ADMIT_TERM"]);
	let admit_term = fourdigit2termcode(gst_plan["ADMIT_TERM"]);
	//	console.log('about to get last_term: ' + gst_plan["COMPLETION_TERM"]);

	let last_term = null; // last_term will be null if  status is "Discontinued" or "Deceased"
	
	
	/* 
	     PROG_STATUS_TEXT options:
	          "Active in Program"
	          "Leave of Absence"
	          "Completed Program"
	          "Deceased"
	          "Dismissed" ? haven't seen
	     
	*/

     if (gst_plan["PROG_STATUS_TEXT"] == "Completed Program") {
     
          last_term = fourdigit2termcode(gst_plan["COMPLETION_TERM"]);
     }
     else if (gst_plan["PROG_STATUS_TEXT"] == "Deceased") {
     
     	  if (bc.notUndefinedAndNotNull(gst_plan["DISCONTINUED"])) {
          	last_term = dayMonthYear2termcode(gst_plan["DISCONTINUED"]);
          }
          // but discontinued may not be entered for deceased
          if (null == last_term) {
               last_term = "2014/9"; // for Yize Shao fsuid ys11b
          }
     
     }
	else if (gst_plan["PROG_STATUS_TEXT"] != "Active in Program" && gst_plan["PROG_STATUS_TEXT"] != "Leave of Absence") {
     // catch everything else?
		if (null == last_term && bc.notUndefinedAndNotNull(gst_plan["DISCONTINUED"])) {
			// TODO: current GST doesn't give a completion term unless the student completed,
			// so we have to fake for did not finish students...
			// 3/2018: Yancey extended to "Discontinued" students,
			// but not for dismissed or deceased?
			last_term = dayMonthYear2termcode(gst_plan["DISCONTINUED"]);
			//console.log(gst_plan["STUDENTNAME"] + " discontinued: " + gst_plan["DISCONTINUED"] + " -> " + last_term);
		}

	}

	if (("DISCONTINUED" in gst_plan) && gst_plan["DISCONTINUED"].length > 0) {
		plan["discontinued"] = dayMonthYear2termcode(gst_plan["DISCONTINUED"]);
	}
	else {
		plan["discontinued"] = null;
	}

// NOTE: we don't trust GST admit terms, because they set it to time of admission after a major plan change?
// but we trust GST last terms, because they know when student officially graduated/left
	plan["gst_admit_term"] = admit_term;
	plan["gst_last_term"] = last_term;
	
	
	if (bc.isUndefinedOrIsNull(plan["admit_term"])) {
		plan["admit_term"] = admit_term;
	}
	else if (plan["admit_term"] != admit_term) {
		plan["admit_term_mismatch"] = plan["admit_term"] + " <-> gst: " + admit_term;
	}
	
	
	if (bc.isUndefinedOrIsNull(plan["last_term"])) {
		plan["last_term"] = last_term;
	} 
	else	if (plan["last_term"] != last_term) {
		plan["last_term_mismatch"] = plan["last_term"] + " <-> gst: " + last_term;
		plan["last_term"] = last_term;
	}

	if (last_term != null) {
		// TODO: translate gst status to gradphile outcomes, or vice versa

		// TODO: check if student just switched plans, so -> transferred

		var gst_status_to_gradphile_outcome = {

			"Completed Program": "graduated",
			"Discontinued": "did not finish",
			"Dismissed": "did not finish",
			"Deceased": "did not finish"

		};

		if (null != gst_status_to_gradphile_outcome[gst_plan["PROG_STATUS_TEXT"]]) {
			plan['outcome'] = gst_status_to_gradphile_outcome[gst_plan["PROG_STATUS_TEXT"]];
			plan['outcome_comment'] = gst_plan["PROG_STATUS_TEXT"];
		}
	}

	plan["status"] = gst_plan["PROG_STATUS_TEXT"]; //*****
	plan["campus"] = gst_plan["CAMPUS"]; //*****
	plan["academic_career"] = gst_plan["ACAD_CAREER"]; //*****
	plan["academic_career_text"] = gst_plan["ACAD_CAREER_TEXT"]; //*****
	plan["career_number"] = gst_plan["STDNT_CAR_NBR"]; //*****

	plan["academic_subplans"] = new Array; //*****
	if (gst_plan["LAST_ACAD_SUBPLAN"].length > 0) {
		let subplan = {};
		subplan["academic_subplan"] = gst_plan["LAST_ACAD_SUBPLAN"]; //*****
		subplan["academic_subplan_name"] = gst_plan["ACAD_SUBPLAN_TEXT"]; //*****
		plan["academic_subplans"].push(subplan);
	}
	if (gst_plan["LAST_ACAD_SUBPLAN2"].length > 0) {
		let subplan = {};
		subplan["academic_subplan"] = gst_plan["LAST_ACAD_SUBPLAN2"]; //*****
		subplan["academic_subplan_name"] = gst_plan["ACAD_SUBPLAN2_TEXT"]; //*****
		plan["academic_subplans"].push(subplan);
	}
	if (gst_plan["LAST_ACAD_SUBPLAN3"].length > 0) {
		let subplan = {};
		subplan["academic_subplan"] = gst_plan["LAST_ACAD_SUBPLAN3"]; //*****
		subplan["academic_subplan_name"] = gst_plan["LAST_ACAD_SUBPLAN3_TEXT"]; //*****
		plan["academic_subplans"].push(subplan);
	}
	if (plan["academic_subplans"].length == 0) {
		plan["academic_subplans"] = null;
	}

	plan["degree_checkout_status"] = gst_plan["CHKOUT_STAT_TEXT"]; //*****

	plan["gst_trackid"] = gst_plan["STUDENTTRACKID"]; //*****
	plan["academic_program"] = gst_plan["LAST_ACAD_PROG"]; //*****
	plan["academic_program_text"] = gst_plan["ACAD_PROG_TEXT"]; //*****

	if (gst_plan["DOCTORALCANDIDATE"] == "Y") {
		plan["candidacy_term"] = gst_plan["ADMCANDIDACYYEAR"] + "/" + parseInt(gst_plan["ADMCANDIDACYMONTH"],10);
	}

	// TODO: run through plan and null out all zero-length fields

} // update_plan_from_gst_plan

Update Committees

function update_committees_from_gst_cmtes(students, gst_cmtes) {
	for (var trackid in gst_cmtes) {
		var gst_cmte = gst_cmtes[trackid];
		var gst_record = {};
		gst_record["EMPLID"] = gst_cmtes[trackid][0]['EMPLID_STU'];

		// console.log(gst_cmtes[trackid]);
		// console.log("EMPLID_STU: " + gst_cmtes[trackid][0]['EMPLID_STU']);

		// console.log("CMTE Trackid: " + trackid + " emplid: " + gst_record["EMPLID"]);

		var student = match_student_with_gst_record_by_emplid(students, gst_record);

		// console.log("CMTE Trackid: " + trackid + " emplid: " + gst_record["EMPLID"] + " " + student.last_name)

		if (null != student) {
			update_student_from_gst_cmte(student, trackid, gst_cmte);
		}

	} // next gst_record

} // update_committees_from_gst_cmtes

function update_student_from_gst_cmte(student, trackid, gst_cmte) {

	for (let plan_code in student.academic_plans) {
		var plan = student.academic_plans[plan_code];
		// console.log(plan["gst_trackid"] + '==' + gst_cmte[0]["STUDENTTRACKID"]);

		if (plan["gst_trackid"] == gst_cmte[0]["STUDENTTRACKID"]) {

			if (gst_cmte.length > 0) {

				// overwrite pre-exisiting local committees
				plan["chair"] = null;
				plan["co_chair1"] = null;
				plan["co_chair2"] = null;
				plan["univ_rep"] = null;
				plan["members"] = [];

				for (var i = 0; i < gst_cmte.length; i++) {

					let gst_member = gst_cmte[i];

					let member = {};
					member["name"] = gst_member["FACULTYNAME"];
					member["full_name"] = gst_member["FACULTYNAME"];
					member["emplid"] = parseInt(gst_member["EMPLID_FAC"], 10);
					member["updated"] = gst_member["DATEUPDATED"];

					if (gst_member["ROLE"] == "C") {

						plan["chair"] = member;
						if (bc.isUndefinedOrIsNull(plan["professor_declared_date"])) {
							plan["professor_declared_date"] = gst_member["DATEUPDATED"];
						}
					}
					else if (gst_member["ROLE"] == "X") {

						if (bc.isUndefinedOrIsNull(plan["professor_declared_date"])) {
							plan["professor_declared_date"] = gst_member["DATEUPDATED"];
						}
						if (bc.isUndefinedOrIsNull(plan["co_chair1"])) {
							plan["co_chair1"] = member;
						}
						else {
							plan["co_chair2"] = member;
						}
					}
					else if (gst_member["ROLE"] == "O") {
						plan["univ_rep"] = member;
					}
					else if (gst_member["ROLE"] == "M") {
						if (bc.isUndefinedOrIsNull(plan["committee_established_date"])) {
							plan["committee_established_date"] = gst_member["DATEUPDATED"];
						}
						plan["members"].push(member);
					}
				} // next gst_member
			} // has some committee members
		} // found track
	} // next plan
} // update_student_from_gst_cmte

Update Committee Faculty Members

GST only has name and emplid of the committee members.

Here we add the department, fsuid, and email from the local gradphile _data/dept/faculty file, or by querying the LDAP server

REVISION: just use LDAP server…


function reconcile_committees_with_faculty_data(students) {


//	var parse = require('csv-parse/lib/sync');

//	var faculty_csv = fs.readFileSync(faculty_path);
//	var faculty = parse(faculty_csv, {
//		'columns': true,
//		'objname': "fsuid"
//	});
	// console.log(faculty);
 
	for (var i = 0; i < students.length; i++) {
		var student = students[i];
		for (var plan_code in student.academic_plans) {
			var plan = student.academic_plans[plan_code];
			reconcile_plan_committee(plan, null);
		}
	}

} // reconcile_committees_with_faculty_data

function reconcile_plan_committee(plan, faculty) {

	if (bc.notUndefinedAndNotNull(plan["chair"])) {
		match_member_with_faculty(plan["chair"], faculty);
	}
	if (bc.notUndefinedAndNotNull(plan["co_chair1"])) {
		match_member_with_faculty(plan["co_chair1"], faculty);
	}
	if (bc.notUndefinedAndNotNull(plan["co_chair2"])) {
		match_member_with_faculty(plan["co_chair2"], faculty);
	}
	if (bc.notUndefinedAndNotNull(plan["univ_rep"])) {
		match_member_with_faculty(plan["univ_rep"], faculty);
	}
	if (bc.notUndefinedAndNotNull(plan["members"])) {
		// console.log(plan["members"]);
		for (var i = 0; i < plan["members"].length; i++) {

			match_member_with_faculty(plan["members"][i], faculty);
		}

		// plan["members"] = remove_duplicate_members(plan["members"]);
	}
}

function match_member_with_faculty(member, faculty) {

	if (member['dept'] && member['fsuid'] && member['email']) {

		return;
	}
	var foundProf = false;

//  for (var prof_key in faculty) {
//  		var prof = faculty[prof_key];
//  		//console.log(member["emplid"] + " == " + prof.emplid);
//  		if ((parseInt(member["emplid"], 10) == parseInt(prof.emplid, 10)) ||
//  			(member["fsuid"] == prof.fsuid)) {
//  			member['emplid'] = prof.emplid;
//  			member['dept'] = prof.dept;
//  			member['fsuid'] = prof.fsuid;
//  			member['email'] = prof.email;
//  			foundProf = true;
//  			break;
//  		}
//  
//  	}

	if (!foundProf && member['emplid']) {

		// try getting from ldap
		//  console.log(faculty);
		// console.log(member);
		var ldap_info = bc.getLDAPInfo(username, password, member["emplid"]);
		// console.log("ldapinfo: " + JSON.stringify(ldap_info, null, "	"));

		if (null != ldap_info.fsuid) {
			member['fsuid'] = ldap_info.fsuid;
		}
		else {
			member['fsuid'] = member['emplid'];
		}

		if (null != ldap_info.department) {
			member['dept'] = ldap_info.department;
		}
		if (null != ldap_info.email) {
			member['email'] = ldap_info.email;
		}
		if (null != ldap_info.givenName) {
			member['givenName'] = ldap_info.givenName;
		}
	} // looked up in ldap

} // match_member_with_faculty

Update Progression Items


function update_progression_from_gst_objects(students, gst_objects) {

	for (var i = 0; i < gst_objects.length; i++) {
		var gst_object = gst_objects[i];
		var student = match_student_with_gst_record_by_emplid(students, gst_object);
		if (null != student) {
			var plan_code = match_plan_with_gst_record_by_trackid(student, gst_object);
			if (null != plan_code) {
			update_student_from_gst_object(student, plan_code, gst_object);
			}
		}

	} // next gst_record

}

function update_student_from_gst_object(student, plan_code, gst_object) {
	/*
	{
		"STUDENTNAME": "Darrow, Emily M",
		"EMPLID": "000076998",
		"ACADPLANTEXT": "Biological Science - PHD",
		"ACADPROGTEXT": "DOCT",
		"STUDENTTRACKID": "82457",
		"STUDENTOBJECTID": "428934",
		"OBJECTSTATUS": "C",
		"STATUSDESCR": "Completed *",
		"OBJECTDATE": "",
		"COMMENTS": "",
		"SORTCODE": "1",
		"gst_id": "3",
		"plan_key": "program_of_studies_approved_date"
	}
	*/

	let plan = student['academic_plans'][plan_code];
	let plan_key = gst_object.plan_key;
	let value_type = gst_object.value_type;

	// TODO: how to handle "completed" objects that have no date?

	if ("" != gst_object["OBJECTDATE"]) {
		// TODO: make sure date is in expected format: MM/dd/YYYY
		plan[plan_key] = gst_object["OBJECTDATE"];
	}
	else if ("C" == gst_object["OBJECTSTATUS"] || "P" == gst_object["OBJECTSTATUS"] || "E" == gst_object["OBJECTSTATUS"]) {
		// don't overwrite if we already have it in local gradbeta database
		if (bc.isUndefinedOrIsNull(plan[plan_key])) {
			if ("date" == value_type) {
			plan[plan_key] = todays_date;
			}
			else {
			plan[plan_key] = gst_object["STATUSDESCR"];
			}
		}
	}

}

Update Test Scores


function update_scores_from_gst_scores(students, gst_scores) {

	for (var i = 0; i < gst_scores.length; i++) {
		var gst_score = gst_scores[i];
		var student = match_student_with_gst_record_by_emplid(students, gst_score);

		if (null != student) {
			update_student_from_gst_score(student, gst_score);
		}

	} // next gst_record

} // update_scores_from_gst_scores

function update_student_from_gst_score(student, gst_score) {

	if (gst_score["TEST_TYPE"] == "GRE") {
		if (gst_score["SUBJECT_TEST_DESC"] == "Verbal (Prior to 8/1/11)") {
			student["GRE_verbal"] = gst_score["TEST_SCORE"];
		}
		if (gst_score["SUBJECT_TEST_DESC"] == "Quantitative (Prior to 8/1/11)") {
			student["GRE_quantitative"] = gst_score["TEST_SCORE"];
		}
		if (gst_score["SUBJECT_TEST_DESC"] == "Verbal") {
			student["GRE_verbal"] = gst_score["TEST_SCORE"];
		}
		if (gst_score["SUBJECT_TEST_DESC"] == "Quantitative") {
			student["GRE_quantitative"] = gst_score["TEST_SCORE"];
		}
		if (gst_score["SUBJECT_TEST_DESC"] == "Writing") {
			student["GRE_writing"] = gst_score["TEST_SCORE"];
		}
	}

	if (gst_score["TEST_TYPE"] == "TOEFL") {
		if (gst_score["SUBJECT_TEST_DESC"] == "IBT Listening") {
			student["toefl_listen"] = gst_score["TEST_SCORE"];
		}
		if (gst_score["SUBJECT_TEST_DESC"] == "IBT Reading") {
			student["toefl_read"] = gst_score["TEST_SCORE"];
		}
		if (gst_score["SUBJECT_TEST_DESC"] == "IBT Speaking") {
			student["toefl_speak"] = gst_score["TEST_SCORE"];
		}
		if (gst_score["SUBJECT_TEST_DESC"] == "IBT Total Score") {
			student["toefl"] = gst_score["TEST_SCORE"];
		}
		if (gst_score["SUBJECT_TEST_DESC"] == "IBT Writing") {
			student["toefl_write"] = gst_score["TEST_SCORE"];
		}
	}

} // update_student_from_gst_score

Cleanup

Reset and Valid First (admit) and Last Terms


function reset_admit_and_last_term(student) {

	var earliest_admit_months = 99999;
	var latest_last_months = -1;
	var has_active_plan = false;

	student["gst_track_id"] = [];
	student["all_tracks"] = {};
	
	for (let plan_code in student.academic_plans) {

		let plan = student.academic_plans[plan_code];
		// console.log(student.fsuid + " " + plan.admit_term);
		let admit_months = termcode2months(plan.admit_term);
		if (admit_months < earliest_admit_months) {
			earliest_admit_months = admit_months;
		}

		if (bc.notUndefinedAndNotNull(plan.last_term)) {
			let last_months = termcode2months(plan.last_term);
			if (last_months > latest_last_months) {
			latest_last_months = last_months;
			}
		}
		
		student["all_tracks"][plan["gst_trackid"]] = plan_code;

		if (plan.status == "Active in Program" && bc.isUndefinedOrIsNull(plan.last_term)) {
			has_active_plan = true;

			// TODO: allow for multiple plans with plan code...
			// if (bc.isUndefinedOrIsNull(student["academic_plan_code"])) {
			//				student["academic_plan_code"] = [];
			//	}
			// student["academic_plan_code"].push(plan_code);

			student["academic_plan_code"] = plan_code;
			student["gst_track_id"].push(plan["gst_trackid"]);

		}

		if (null != plan["discontinued"]) {
			student["discontinued"] = plan["discontinued"];
			// NOTE: does a student only have 1 final discontinued?
		}
		
		
	}
	if (latest_last_months > 0 && !has_active_plan) {
		student["last_term"] = Math.floor(latest_last_months / 12) + '/' + latest_last_months % 12;
	}
	else {
		student["last_term"] = null;
	}
	if (earliest_admit_months < 99999) {
		student["admit_term"] = Math.floor(earliest_admit_months / 12) + '/' + earliest_admit_months % 12;
	}

} // reset_admit_and_last_term

Load Current Student data

if the “updating” option is included, the we read in allstudents_old_format.json from source_data/students/, and the GST data will be merged with the current data.

If not “updating”, then a de novo allstudents array is instantiated.


var allstudents;

if (updating) {



	allstudents = bc.readJSON(input_students_path);
	
	allstudents.sort(function(a,b) {

	if (a.fsuid < b.fsuid) {
		return -1;
	}
	if (a.fsuid > b.fsuid) {
		return 1;
	}
	return 0;
	
});

 fs.writeFileSync(pre_output_students_path, JSON.stringify(allstudents, null, '\t'));


}
else {
	allstudents = new Array;
}

Do the Updating.


update_students_from_gst_plans(allstudents,gst_plans);

update_scores_from_gst_scores(allstudents,gst_scores);

update_committees_from_gst_cmtes(allstudents,gst_cmtes);

reconcile_committees_with_faculty_data(allstudents);

update_progression_from_gst_objects(allstudents,gst_objects);

Clean up

Make sure the admit_term and last_term keys are consistent.

Remove any undefined, null, or empty objects.


for (let i = 0; i< allstudents.length;i++) {
	reset_admit_and_last_term(allstudents[i]);
	bc.removeInvalidKeysFromObject(allstudents[i]);
}

Save Updated Students

sort into fsuid order. if updating from mongodb file, remove _id keys


// sort by fsuid
allstudents.sort(function(a,b) {

	if (a.fsuid < b.fsuid) {
		return -1;
	}
	if (a.fsuid > b.fsuid) {
		return 1;
	}
	return 0;
	
});

if (updating) {
	// delete mongo id to allow for uploading back into mongo
	allstudents.forEach(function(a) {

		if (bc.notUndefinedAndNotNull(a["_id"])) {
			delete a["_id"];
		}

	});

}


fs.writeFile(output_students_path,JSON.stringify(allstudents, null, '\t'),function(err) {
	if (err) {
		return console.log(err);
	}

}

); // fsWrite

console.log("\ngst-to-gradphile: found " + allstudents.length + " student records\n\n");