def merge_sis_profile_plans(academic_status, sis_profile): sis_profile['plans'] = [] for student_plan in academic_status.get('studentPlans', []): academic_plan = student_plan.get('academicPlan', {}) # SIS majors come in five flavors. if academic_plan.get('type', {}).get('code') not in ['MAJ', 'SS', 'SP', 'HS', 'CRT']: continue plan = academic_plan.get('plan', {}) major = plan.get('description') plan_feed = { 'degreeProgramUrl': degree_program_url_for_major(major), 'description': major, } # Find the latest expected graduation term from any plan. expected_graduation_term = student_plan.get('expectedGraduationTerm', {}).get('id') if expected_graduation_term and expected_graduation_term > sis_profile.get('expectedGraduationTerm', {}).get('id', '0'): sis_profile['expectedGraduationTerm'] = { 'id': expected_graduation_term, 'name': term_name_for_sis_id(expected_graduation_term), } # Add program unless plan code indicates undeclared. if plan.get('code') != '25000U': program = student_plan.get('academicPlan', {}).get('academicProgram', {}).get('program', {}) plan_feed['program'] = program.get('description') # Add plan unless it's a duplicate. if not next((p for p in sis_profile['plans'] if p.get('description') == plan_feed.get('description')), None): sis_profile['plans'].append(plan_feed)
def refresh_current_term_index(self): today = datetime.now(pytz.utc).astimezone( pytz.timezone(app.config['TIMEZONE'])).date() current_term = self.get_sis_current_term(today) if current_term: current_term_id = current_term['term_id'] # If today is one month or less before the end of the current term, or if the current term is summer, # include the next term. if current_term_id[3] == '5' or (current_term['term_ends'] - timedelta(weeks=4)) < today: future_term_id = next_term_id(current_term['term_id']) # ... and if the upcoming term is Summer, include the next Fall term as well. if future_term_id[3] == '5': future_term_id = next_term_id(future_term_id) else: future_term_id = current_term_id with rds.transaction() as transaction: transaction.execute( f'TRUNCATE {rds_schema}.current_term_index') columns = ['current_term_name', 'future_term_name'] values = tuple([ current_term['term_name'], term_name_for_sis_id(future_term_id) ]) if transaction.execute( f'INSERT INTO {rds_schema}.current_term_index ({", ".join(columns)}) VALUES {values} ' ): transaction.commit() else: transaction.rollback() raise BackgroundJobError( 'Error refreshing RDS current term index.')
def map_sis_enrollments(sis_enrollments): student_enrollments_map = {} for key, all_sids_grp in groupby(sis_enrollments, operator.itemgetter('sis_term_id')): term_id = str(key) term_name = berkeley.term_name_for_sis_id(term_id) student_enrollments_map[term_id] = {} for sid, all_enrs_grp in groupby(all_sids_grp, operator.itemgetter('sid')): term_enrollments = merge_enrollment(all_enrs_grp, term_id, term_name) student_enrollments_map[term_id][sid] = term_enrollments return student_enrollments_map
def merge_sis_profile_plans(academic_status, sis_profile): plans = [] plans_minor = [] subplans = set() for student_plan in academic_status.get('studentPlans', []): academic_plan = student_plan.get('academicPlan', {}) # SIS majors come in five flavors, plus a sixth for minors. if academic_plan.get('type', {}).get('code') not in ['MAJ', 'SS', 'SP', 'HS', 'CRT', 'MIN']: continue plan = academic_plan.get('plan', {}) description = plan.get('description') plan_feed = { 'degreeProgramUrl': degree_program_url_for_major(description), 'description': description, } # Find the latest expected graduation term from any plan. expected_graduation_term = student_plan.get('expectedGraduationTerm', {}).get('id') if expected_graduation_term and expected_graduation_term > sis_profile.get('expectedGraduationTerm', {}).get('id', '0'): sis_profile['expectedGraduationTerm'] = { 'id': expected_graduation_term, 'name': term_name_for_sis_id(expected_graduation_term), } program = student_plan.get('academicPlan', {}).get('academicProgram', {}).get('program', {}) plan_feed['program'] = program.get('formalDescription') or program.get('description') # Add plan status. plan_status = student_plan.get('statusInPlan', {}).get('status') if plan_status: plan_feed['status'] = plan_status.get('formalDescription') or plan_status.get('description') # We generally prefer the 'formalDescription', but our formality has limits. if plan_feed['status'] == 'Active in Program': plan_feed['status'] = 'Active' else: # A plan with no status is considered discontinued. (NS-689) plan_feed['status'] = 'Discontinued' # Add plan unless it's a duplicate. if academic_plan.get('type', {}).get('code') == 'MIN': plan_collection = plans_minor else: plan_collection = plans if not next((p for p in plan_collection if p.get('description') == plan_feed.get('description')), None): plan_collection.append(plan_feed) # Add any subplans. for academic_subplan in student_plan.get('academicSubPlans', []): subplan_description = academic_subplan.get('subPlan', {}).get('description') if subplan_description: subplans.add(subplan_description) sis_profile['plans'] = sorted(plans, key=itemgetter('description')) sis_profile['plansMinor'] = sorted(plans_minor, key=itemgetter('description')) sis_profile['subplans'] = sorted(list(subplans))
def generate_term_feeds(self, sids, feed_file): enrollment_stream = queries.stream_sis_enrollments(sids=sids) term_gpa_stream = queries.stream_term_gpas(sids=sids) term_gpa_tracker = {'term_id': '9999', 'sid': '', 'term_gpas': []} row_count = 0 try: term_gpa_results = groupby(term_gpa_stream, lambda r: (str(r['term_id']), r['sid'])) for term_id, term_enrollments_grp in groupby( enrollment_stream, operator.itemgetter('sis_term_id')): term_id = str(term_id) term_name = berkeley.term_name_for_sis_id(term_id) for sid, enrollments_grp in groupby( term_enrollments_grp, operator.itemgetter('sid')): term_feed = None for is_dropped, enrollments_subgroup in groupby( enrollments_grp, operator.itemgetter('dropped')): if not is_dropped: term_feed = merge_enrollment( enrollments_subgroup, term_id, term_name) else: if not term_feed: term_feed = empty_term_feed(term_id, term_name) append_drops(term_feed, enrollments_subgroup) while term_gpa_tracker['term_id'] > term_id or ( term_gpa_tracker['term_id'] == term_id and term_gpa_tracker['sid'] < sid): (term_gpa_tracker['term_id'], term_gpa_tracker['sid'] ), term_gpa_tracker['term_gpas'] = next( term_gpa_results) if term_gpa_tracker[ 'term_id'] == term_id and term_gpa_tracker[ 'sid'] == sid: append_term_gpa(term_feed, term_gpa_tracker['term_gpas']) feed_file.write( encoded_tsv_row([sid, term_id, json.dumps(term_feed)]) + b'\n') row_count += 1 finally: enrollment_stream.close() term_gpa_stream.close() return row_count
def get_merged_enrollment_terms(uid, cs_id, term_ids, canvas_courses_feed, canvas_site_map): enrollment_results = queries.get_sis_enrollments(uid) or [] enrollments_by_term = {} for key, group in groupby(enrollment_results, key=operator.itemgetter('sis_term_id')): enrollments_by_term[str(key)] = list(group) drops_and_midterms_result = queries.get_sis_api_drops_and_midterms(cs_id, term_ids) or [] term_feeds = {} for term_id in term_ids: enrollments = enrollments_by_term.get(term_id, []) term_name = berkeley.term_name_for_sis_id(term_id) drops_and_midterms = next((json.loads(row['feed']) for row in drops_and_midterms_result if str(row['term_id']) == str(term_id)), {}) term_feed = merge_enrollment(cs_id, enrollments, term_id, term_name, drops_and_midterms) for site in canvas_courses_feed: merge_canvas_course_site(term_feed, site, canvas_site_map) sort_canvas_course_sites(term_feed) term_feeds[term_id] = term_feed return term_feeds
def refresh_current_term_index(self): today = datetime.now(pytz.utc).astimezone( pytz.timezone(app.config['TIMEZONE'])).date() current_term = self.get_sis_current_term(today) if current_term: term_id = current_term['term_id'] # Check if the advance enrollment period has started for the next two upcoming terms. future_term_id = term_id for _ in range(2): term_id = next_term_id(term_id) term = self.get_sis_term_for_id(term_id) advance_enrollment_period = 0 if term_id[3] == '2': advance_enrollment_period = 95 elif term_id[3] == '5': advance_enrollment_period = 124 elif term_id[3] == '8': advance_enrollment_period = 140 if term['term_begins'] - timedelta( days=advance_enrollment_period) < today: future_term_id = term_id with rds.transaction() as transaction: transaction.execute( f'TRUNCATE {rds_schema}.current_term_index') columns = ['current_term_name', 'future_term_name'] values = tuple([ current_term['term_name'], term_name_for_sis_id(future_term_id) ]) if transaction.execute( f'INSERT INTO {rds_schema}.current_term_index ({", ".join(columns)}) VALUES {values} ' ): transaction.commit() else: transaction.rollback() raise BackgroundJobError( 'Error refreshing RDS current term index.')
def test_term_name_for_sis_id(self): assert berkeley.term_name_for_sis_id('2178') == 'Fall 2017' assert berkeley.term_name_for_sis_id('1978') == 'Fall 1997' assert berkeley.term_name_for_sis_id('1760') == 'Winter 1976'