def get_full_student_profiles(sids): benchmark = get_benchmarker('get_full_student_profiles') benchmark('begin') if not sids: return [] benchmark('begin SIS profile query') profile_results = data_loch.get_student_profiles(sids) benchmark('end SIS profile query') if not profile_results: return [] profiles_by_sid = _get_profiles_by_sid(profile_results) profiles = [] for sid in sids: profile = profiles_by_sid.get(sid) if profile: profiles.append(profile) benchmark('begin photo merge') _merge_photo_urls(profiles) benchmark('end photo merge') scope = get_student_query_scope() benchmark('begin ASC profile merge') _merge_asc_student_profile_data(profiles_by_sid, scope) benchmark('end ASC profile merge') if 'COENG' in scope or 'ADMIN' in scope: benchmark('begin COE profile merge') _merge_coe_student_profile_data(profiles_by_sid) benchmark('end COE profile merge') return profiles
def response_with_students_csv_download(sids, benchmark): rows = [] for student in get_student_profiles(sids=sids): profile = student.get('profile') profile = profile and json.loads(profile) rows.append({ 'first_name': profile.get('firstName'), 'last_name': profile.get('lastName'), 'sid': profile.get('sid'), 'email': profile.get('sisProfile', {}).get('emailAddress'), 'phone': profile.get('sisProfile', {}).get('phoneNumber'), }) benchmark('end') def _norm(row, key): value = row.get(key) return value and value.upper() return response_with_csv_download( rows=sorted( rows, key=lambda r: (_norm(r, 'last_name'), _norm(r, 'first_name'), _norm(r, 'sid'))), filename_prefix='cohort', fieldnames=['first_name', 'last_name', 'sid', 'email', 'phone'], )
def get_full_student_profiles(sids): if not sids: return [] profile_results = data_loch.get_student_profiles(sids) if not profile_results: return [] profiles_by_sid = { row['sid']: json.loads(row['profile']) for row in profile_results } profiles = [] for sid in sids: profile = profiles_by_sid.get(sid) if profile: profiles.append(profile) scope = get_student_query_scope() if 'UWASC' in scope or 'ADMIN' in scope: athletics_profiles = data_loch.get_athletics_profiles(sids) for row in athletics_profiles: profile = profiles_by_sid.get(row['sid']) if profile: profile['athleticsProfile'] = json.loads(row['profile']) if 'COENG' in scope or 'ADMIN' in scope: coe_profiles = data_loch.get_coe_profiles(sids) if coe_profiles: for row in coe_profiles: profile = profiles_by_sid.get(row['sid']) if profile: profile['coeProfile'] = json.loads(row['profile']) return profiles
def update_all_for_term(cls, term_id): app.logger.info('Starting alert update') enrollments_for_term = data_loch.get_enrollments_for_term(str(term_id)) no_activity_alerts_enabled = cls.no_activity_alerts_enabled() infrequent_activity_alerts_enabled = cls.infrequent_activity_alerts_enabled( ) for row in enrollments_for_term: enrollments = json.loads(row['enrollment_term']).get( 'enrollments', []) for enrollment in enrollments: cls.update_alerts_for_enrollment( row['sid'], term_id, enrollment, no_activity_alerts_enabled, infrequent_activity_alerts_enabled) if app.config['ALERT_HOLDS_ENABLED'] and str( term_id) == current_term_id(): holds = data_loch.get_sis_holds() for row in holds: hold_feed = json.loads(row['feed']) cls.update_hold_alerts(row['sid'], term_id, hold_feed.get('type'), hold_feed.get('reason')) if app.config['ALERT_WITHDRAWAL_ENABLED'] and str( term_id) == current_term_id(): profiles = data_loch.get_student_profiles() for row in profiles: profile_feed = json.loads(row['profile']) if 'withdrawalCancel' in (profile_feed.get('sisProfile') or {}): cls.update_withdrawal_cancel_alerts(row['sid'], term_id) app.logger.info('Alert update complete')
def update_curated_group_lists(): """Remove no-longer-accessible students from curated group lists.""" from boac.models.curated_group import CuratedGroup for curated_group in CuratedGroup.query.all(): all_sids = CuratedGroupStudent.get_sids(curated_group.id) available_students = [s['sid'] for s in data_loch.get_student_profiles(all_sids)] if len(all_sids) > len(available_students): unavailable_sids = set(all_sids) - set(available_students) app.logger.info(f'Deleting inaccessible SIDs from curated group {curated_group.id}: {unavailable_sids}') for sid in unavailable_sids: CuratedGroup.remove_student(curated_group.id, sid)
def test_get_student_profiles(self, app): import json sid = '11667051' student_profiles = data_loch.get_student_profiles([sid]) assert len(student_profiles) == 1 student = student_profiles[0] assert student['sid'] == sid assert student['gender'] == 'Different Identity' assert student['minority'] is False sis_profile = json.loads(student_profiles[0]['profile'])['sisProfile'] assert sis_profile['academicCareer'] == 'UGRD'
def update_curated_cohort_lists(): """Remove no-longer-accessible students from curated cohort lists.""" from boac.models.curated_cohort import CuratedCohort for cohort in CuratedCohort.query.all(): member_sids = [s.sid for s in cohort.students] available_students = [s['sid'] for s in data_loch.get_student_profiles(member_sids)] if len(member_sids) > len(available_students): cohort_id = cohort.id unavailable_sids = set(member_sids) - set(available_students) app.logger.info(f'Deleting inaccessible SIDs from cohort ID {cohort_id} : {unavailable_sids}') for sid in unavailable_sids: CuratedCohort.remove_student(cohort_id, sid)
def test_get_student_profiles(self): import json sid = '11667051' student_profiles = data_loch.get_student_profiles([sid]) assert len(student_profiles) == 1 student = student_profiles[0] assert student['sid'] == sid profile = json.loads(student['profile']) assert profile['demographics']['gender'] == 'Different Identity' assert profile['demographics']['underrepresented'] is False assert profile['sisProfile']['academicCareer'] == 'UGRD'
def update_all_for_term(cls, term_id): app.logger.info('Starting alert update') enrollments_for_term = data_loch.get_enrollments_for_term(str(term_id)) no_activity_alerts_enabled = cls.no_activity_alerts_enabled() infrequent_activity_alerts_enabled = cls.infrequent_activity_alerts_enabled( ) for row in enrollments_for_term: enrollments = json.loads(row['enrollment_term']).get( 'enrollments', []) for enrollment in enrollments: cls.update_alerts_for_enrollment( sid=row['sid'], term_id=term_id, enrollment=enrollment, no_activity_alerts_enabled=no_activity_alerts_enabled, infrequent_activity_alerts_enabled= infrequent_activity_alerts_enabled, ) profiles = data_loch.get_student_profiles() if app.config['ALERT_WITHDRAWAL_ENABLED'] and str( term_id) == current_term_id(): for row in profiles: sis_profile_feed = json.loads( row['profile']).get('sisProfile') or {} if sis_profile_feed.get('withdrawalCancel', {}).get('termId') == str(term_id): cls.update_withdrawal_cancel_alerts(row['sid'], term_id) sids = [p['sid'] for p in profiles] for sid, academic_standing_list in get_academic_standing_by_sid( sids).items(): standing = next((s for s in academic_standing_list if s['termId'] == str(term_id)), None) if standing and standing['status'] in ('DIS', 'PRO', 'SUB'): cls.update_academic_standing_alerts( action_date=standing['actionDate'], sid=standing['sid'], status=standing['status'], term_id=term_id, ) app.logger.info('Alert update complete')
def get_full_student_profiles(sids): benchmark = get_benchmarker('get_full_student_profiles') benchmark('begin') if not sids: return [] benchmark('begin SIS profile query') profile_results = data_loch.get_student_profiles(sids) benchmark('end SIS profile query') if not profile_results: return [] profiles_by_sid = _get_profiles_by_sid(profile_results) profiles = [] for sid in sids: profile = profiles_by_sid.get(sid) if profile: profiles.append(profile) benchmark('begin photo merge') _merge_photo_urls(profiles) benchmark('end photo merge') scope = get_student_query_scope() if 'UWASC' in scope or 'ADMIN' in scope: benchmark('begin ASC profile merge') athletics_profiles = data_loch.get_athletics_profiles(sids) for row in athletics_profiles: profile = profiles_by_sid.get(row['sid']) if profile: profile['athleticsProfile'] = json.loads(row['profile']) benchmark('end ASC profile merge') if 'COENG' in scope or 'ADMIN' in scope: benchmark('begin COE profile merge') coe_profiles = data_loch.get_coe_profiles(sids) if coe_profiles: for coe_profile in coe_profiles: sid = coe_profile['sid'] _merge_coe_student_profile_data(profiles_by_sid.get(sid), coe_profile) benchmark('end COE profile merge') return profiles
def response_with_students_csv_download(sids, fieldnames, benchmark): rows = [] getters = { 'first_name': lambda profile: profile.get('firstName'), 'last_name': lambda profile: profile.get('lastName'), 'sid': lambda profile: profile.get('sid'), 'email': lambda profile: profile.get('sisProfile', {}).get('emailAddress'), 'phone': lambda profile: profile.get('sisProfile', {}).get('phoneNumber'), 'majors': lambda profile: ';'.join([ plan.get('description') for plan in profile.get('sisProfile', {}).get('plans', []) if plan.get('status') == 'Active' ], ), 'level': lambda profile: profile.get('sisProfile', {}).get('level', {}).get( 'description'), 'terms_in_attendance': lambda profile: profile.get('sisProfile', {}).get('termsInAttendance'), 'expected_graduation_date': lambda profile: profile.get('sisProfile', {}).get( 'expectedGraduationTerm', {}).get('name'), 'units_completed': lambda profile: profile.get('sisProfile', {}).get('cumulativeUnits'), 'term_gpa': lambda profile: profile.get('termGpa'), 'cumulative_gpa': lambda profile: profile.get('sisProfile', {}).get('cumulativeGPA'), 'program_status': lambda profile: profile.get('sisProfile', {}).get( 'academicCareerStatus'), } term_gpas = get_term_gpas_by_sid(sids, as_dicts=True) for student in get_student_profiles(sids=sids): profile = student.get('profile') profile = profile and json.loads(profile) student_term_gpas = term_gpas.get(profile['sid']) profile['termGpa'] = student_term_gpas[sorted( student_term_gpas)[-1]] if student_term_gpas else None row = {} for fieldname in fieldnames: row[fieldname] = getters[fieldname](profile) rows.append(row) benchmark('end') def _norm(row, key): value = row.get(key) return value and value.upper() return response_with_csv_download( rows=sorted( rows, key=lambda r: (_norm(r, 'last_name'), _norm(r, 'first_name'), _norm(r, 'sid'))), filename_prefix='cohort', fieldnames=fieldnames, )
def response_with_students_csv_download(sids, fieldnames, benchmark): term_id_last = previous_term_id(current_term_id()) term_id_previous = previous_term_id(term_id_last) rows = [] getters = { 'first_name': lambda profile: profile.get('firstName'), 'last_name': lambda profile: profile.get('lastName'), 'sid': lambda profile: profile.get('sid'), 'email': lambda profile: profile.get('sisProfile', {}).get('emailAddress'), 'phone': lambda profile: profile.get('sisProfile', {}).get('phoneNumber'), 'majors': lambda profile: ';'.join([ plan.get('description') for plan in profile.get('sisProfile', {}).get('plans', []) if plan.get('status') == 'Active' ], ), 'intended_majors': lambda profile: ';'.join([ major.get('description') for major in profile.get('sisProfile', {}).get('intendedMajors') ], ), 'level_by_units': lambda profile: profile.get('sisProfile', {}).get('level', {}).get( 'description'), 'minors': lambda profile: ';'.join([ plan.get('description') for plan in profile.get('sisProfile', {}).get('plansMinor', []) if plan.get('status') == 'Active' ], ), 'subplans': lambda profile: ';'.join([ subplan for subplan in profile.get('sisProfile', {}).get('subplans', []) ]), 'terms_in_attendance': lambda profile: profile.get('sisProfile', {}).get('termsInAttendance'), 'expected_graduation_term': lambda profile: profile.get('sisProfile', {}).get( 'expectedGraduationTerm', {}).get('name'), 'units_completed': lambda profile: profile.get('sisProfile', {}).get('cumulativeUnits'), f'term_gpa_{term_id_previous}': lambda profile: profile.get('termGpa', {}).get(term_id_previous), f'term_gpa_{term_id_last}': lambda profile: profile.get('termGpa', {}).get(term_id_last), 'cumulative_gpa': lambda profile: profile.get('sisProfile', {}).get('cumulativeGPA'), 'program_status': lambda profile: ';'.join( list( set([ plan.get('status') for plan in profile.get('sisProfile', {}).get('plans', []) ], ), ), ), 'academic_standing': lambda profile: profile.get('academicStanding'), 'transfer': lambda profile: 'Yes' if profile.get('sisProfile', {}).get('transfer') else '', 'intended_major': lambda profile: ', '.join([ major.get('description') for major in (profile.get('sisProfile', {}).get('intendedMajors') or []) ]), 'units_in_progress': lambda profile: profile.get('enrolledUnits', {}), } academic_standing = get_academic_standing_by_sid(sids, as_dicts=True) term_gpas = get_term_gpas_by_sid(sids, as_dicts=True) term_units = get_term_units_by_sid(current_term_id(), sids) def _get_last_element(results): return results[sorted(results)[-1]] if results else None def _add_row(student_profile): student_profile['academicStanding'] = _get_last_element( academic_standing.get(student_profile['sid'])) student_profile['termGpa'] = term_gpas.get(student_profile['sid'], {}) student_profile['enrolledUnits'] = term_units[student_profile['sid']] row = {} for fieldname in fieldnames: row[fieldname] = getters[fieldname](student_profile) rows.append(row) students = get_student_profiles(sids=sids) for student in students: profile = student.get('profile') if profile: _add_row(json.loads(profile)) remaining_sids = list(set(sids) - set([s.get('sid') for s in students])) if remaining_sids: for profile in get_historical_student_profiles(remaining_sids): _add_row(profile) benchmark('end') return response_with_csv_download( rows=sorted( rows, key=lambda r: (_norm(r, 'last_name'), _norm(r, 'first_name'), _norm(r, 'sid'))), filename_prefix='cohort', fieldnames=fieldnames, )