-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathdegree_requirements.ts
5470 lines (5004 loc) · 239 KB
/
degree_requirements.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// DW analysis files go here
import {makeArray} from "jquery";
import DroppableEventUIParam = JQueryUI.DroppableEventUIParam;
import exp from "constants";
import {isBooleanObject} from "util/types";
import fs from "fs";
import csv from "csv-parse/lib/sync";
import CmdTs from "cmd-ts";
import CmdTsFs from "cmd-ts/dist/esm/batteries/fs";
// global list of CSCI Tech Electives. Used sporadically in a few places so it's easier to have it as a global.
let TECH_ELECTIVE_LIST: TechElectiveDecision[] = []
/** For checking whether Path course attributes are correct or not */
class CourseWithAttrs {
readonly codes: Set<string> = new Set<string>()
readonly title: string
readonly courses: CourseTaken[] = []
readonly attributes: Set<string> = new Set<string>()
constructor(code: string, title: string) {
this.title = title
this.addCode(code)
}
addCode(code: string) {
if (this.codes.has(code)) {
return
}
const coursePattern = /(?<subject>[A-Z]{2,4})(?<number>\d{4})(?<term>[ABC])?/
const m = code.match(coursePattern)
if (m == null) {
throw new Error(`problem parsing course: ${code}`)
}
this.codes.add(code)
}
finalize() {
for (const code of this.codes) {
const coursePattern = /(?<subject>[A-Z]{2,4})(?<number>\d{4})(?<term>[ABC])?/
const m = code.match(coursePattern)
const ct = new CourseTaken(
m!.groups!['subject'],
// NB: we strip the term suffix (e.g., the 'A' in BCHE4597A)
m!.groups!['number'],
'',
null,
1.0,
GradeType.ForCredit,
'A',
202330,
Array.from(this.attributes).map(a => `ATTRIBUTE=${a}`).join(';'),
true)
this.courses.push(ct)
}
}
codesStr(): string {
//return this.courses.map(i => i.code()).join(' ')
return [...this.codes].join(' ')
}
toString(): string {
const js = {
codes: Array.from(this.codes),
attrs: Array.from(this.attributes)
}
return JSON.stringify(js)
}
}
async function analyzeCourseAttributeSpreadsheet(csvFilePath: string) {
const fs = require('fs')
const csv = require('csv-parse/sync')
const csvStringify = require('csv-stringify/sync')
const fileContent = fs.readFileSync(csvFilePath, { encoding: 'utf-8' })
let cattrCsvRecords = csv.parse(fileContent, {
delimiter: ',',
columns: true,
}) as CsvRecord[]
console.log(`parsed ${cattrCsvRecords.length} CSV records`)
// 1: BUILD UP LIST OF ALL COURSES
// each object has ≥1 course code (like "ACCT1010") and a list of attributes (like "EUHS")
const AllCourses: CourseWithAttrs[] = []
let currentCourse = new CourseWithAttrs(cattrCsvRecords[0]['Primary Course'], cattrCsvRecords[0]['Course Title'])
const skippedCourses = []
for (const r of cattrCsvRecords) {
const pc = r['Primary Course']
const title = r['Course Title']
const crs = r['Course']
const rawAttrs = r['Attributes']
const attrs = rawAttrs.split(',').map(a => a.split(':')[0].trim())
if (!currentCourse.codes.has(pc)) {
try {
// start a new course
const cc = new CourseWithAttrs(pc, title)
// store previous course
currentCourse.finalize()
AllCourses.push(currentCourse)
currentCourse = cc
} catch (e) {
// console.log(`couldn't parse course ${pc}, skipping...`)
skippedCourses.push(pc)
continue
}
}
currentCourse.addCode(pc)
currentCourse.addCode(crs)
attrs.forEach(a => currentCourse.attributes.add(a))
}
// push the last course
currentCourse.finalize()
AllCourses.push(currentCourse)
if (skippedCourses.length > 0) {
console.log(`skipped ${skippedCourses.length} courses that couldn't be parsed: ${skippedCourses}`)
}
console.log(`checking ${AllCourses.length} courses...`)
// 2: CHECK ATTRIBUTES
const attrsToCheck = [
CourseAttribute.Engineering,
CourseAttribute.Math,
CourseAttribute.NatSci,
CourseAttribute.SocialScience,
CourseAttribute.Humanities,
CourseAttribute.TBS,
CourseAttribute.CsciRestrictedTechElective,
CourseAttribute.CsciUnrestrictedTechElective
]
const mutuallyExclusiveAttrs = [
CourseAttribute.Engineering,
CourseAttribute.Math,
CourseAttribute.NatSci,
CourseAttribute.SocialScience,
CourseAttribute.Humanities,
CourseAttribute.TBS
]
let errorsFound: {codes: string, title: string, reason: string}[] = []
for (const pathCourse of AllCourses.slice(0)) {
const errorReasons = []
// check if attrs are consistent across xlists
const allAttrLists = pathCourse.courses.map(xl => xl.attributes)
const biggestAttrList = [...allAttrLists]
.sort((a,b) => b.length - a.length)[0]
const biggestAttrSet = new Set<CourseAttribute>(biggestAttrList)
const biggestIsSuperset = allAttrLists.every(al => al.every(a => biggestAttrSet.has(a)))
if (!biggestIsSuperset) {
errorReasons.push('attributes inconsistent across crosslists ' +
allAttrLists
.map((al,i) => {
return {c: pathCourse.courses[i].code(), al: al}
})
.filter(cal => cal.al.length > 0)
.map(cal => cal.c + ' has ' + cal.al.map(a => a).join(',')).join(', '))
// don't bother reporting on attrs, since we aren't sure what the right answer is
errorsFound.push({
codes: pathCourse.codesStr(),
title: pathCourse.title,
reason: errorReasons.join('; ')
})
continue
}
// check if attributes are non-mutually-exclusive
const attrIntersection = mutuallyExclusiveAttrs.filter(mea => biggestAttrSet.has(mea))
if (attrIntersection.length > 1) {
errorReasons.push('has incompatible attributes ' + attrIntersection)
}
// check if Path course has correct attrs
for (const atc of attrsToCheck) {
if (biggestAttrSet.has(atc) && !pathCourse.attributes.has(atc)) {
if (atc == CourseAttribute.Engineering && pathCourse.attributes.has(CourseAttribute.MathNatSciEngr)) {
errorReasons.push(`replace ${CourseAttribute.MathNatSciEngr} with ${CourseAttribute.Engineering}`)
} else {
errorReasons.push('missing attribute ' + atc)
}
}
if (!biggestAttrSet.has(atc) && pathCourse.attributes.has(atc)) {
errorReasons.push('has incorrect attribute ' + atc)
}
}
// no more EUMS
if (pathCourse.attributes.has(CourseAttribute.MathNatSciEngr) && !biggestAttrSet.has(CourseAttribute.Engineering)) {
errorReasons.push('has deprecated attribute ' + CourseAttribute.MathNatSciEngr)
}
if (errorReasons.length > 0) {
errorsFound.push({
codes: pathCourse.codesStr(),
title: pathCourse.title,
reason: errorReasons.join('; ')
})
}
}
// check that CSCI TE list does not intersect SEAS No-Credit List
{
for (const te of TECH_ELECTIVE_LIST) {
const subjectNumber = te.course4d.split(' ')
const teCourse = new CourseTaken(
subjectNumber[0],
subjectNumber[1],
te.title,
null,
1.0,
GradeType.ForCredit,
'C',
202510,
'',
true)
if (teCourse.suhSaysNoCredit()) {
errorsFound.push({
codes: te.course4d,
title: te.title,
reason: 'CSCI TE on the No-Credit List'
})
}
}
}
// ignore study abroad, transfer credit and credit away courses
const badTitles = ['study abroad', 'transfer credit', 'trans credit', 'credit away']
errorsFound = errorsFound.filter(e => !badTitles.some(bad => e.title.toLowerCase().includes(bad)))
errorsFound = errorsFound.filter(e => // filter out non-SEAS IS courses
!e.title.toLowerCase().includes('independent study') ||
e.reason.includes('has deprecated attribute EUMS')
)
const badSubjects = ['BCHE', 'BMB', 'CAMB', 'GCB', 'IMUN'] // filter out some PSOM courses
errorsFound = errorsFound.filter(e => !badSubjects.some(bad => e.codes.includes(bad)))
// sort by severity
const order = [
'has incorrect attribute',
'missing attribute',
'replace EUMS',
'has deprecated attribute EUMS',
// SUH ambiguity
'has incompatible attributes',
'attributes inconsistent across crosslists',
];
// const orderMap = new Map(order.map((item, index) => [item, index]));
errorsFound.sort((a,b) => {
if (a.reason < b.reason) return -1
if (a.reason > b.reason) return 1
return 0
})
errorsFound.sort((a, b) => {
const aIndex = order.findIndex(o => a.reason.startsWith(o))
const bIndex = order.findIndex(o => b.reason.startsWith(o))
return aIndex - bIndex
});
const csvStr = csvStringify.stringify(errorsFound,
{
header: true,
columns: {
codes: 'Course (including crosslists)',
title: 'Course Title',
reason: 'Issue',
}
})
fs.writeFileSync('course-attr-problems.csv', csvStr)
const suhInconsistencies =
errorsFound.filter(e => e.reason.includes('attributes inconsistent across')).length +
errorsFound.filter(e => e.reason.includes('has incompatible attributes')).length
const attrMissing = errorsFound.filter(e => e.reason.includes('missing attribute')).length
const attrWrong = errorsFound.filter(e => e.reason.includes('has incorrect attribute')).length
console.log(`SUMMARY: ${suhInconsistencies} SUH inconsistencies, ${attrMissing} missing attrs and ${attrWrong} wrong attrs`)
}
interface ProbationStudentInfo {
name: string
pennid: number
email: string
coursesTaken: CourseTaken[]
}
function generateApcProbationList(csvFilePath: string) {
const fs = require('fs')
const csv = require('csv-parse/sync')
// start probation risk spreadsheet
const probationRiskFile = `${AnalysisOutputDir}apc-probation-risk.csv`
fs.writeFileSync(probationRiskFile, "PennID;name;email;overall GPA;stem GPA;fall CUs;spring CUs;AY CUs;probation risk\n")
const fileContent = fs.readFileSync(csvFilePath, {encoding: 'utf-8'})
const courseTakenCsvRecords = csv.parse(fileContent, {
delimiter: ',',
columns: true,
}) as CsvRecord[]
const coursesForStudent = new Map<number,ProbationStudentInfo>();
courseTakenCsvRecords.forEach((row) => {
const pennid = Number.parseInt(row['pennid'])
myAssert(!Number.isNaN(pennid))
if (!coursesForStudent.has(pennid)) {
const psi = {
name: row['student name'],
pennid: pennid,
email: row['email'],
coursesTaken: []
}
coursesForStudent.set(pennid, psi)
}
const existingCourses = coursesForStudent.get(pennid)
const cus = Number.parseFloat(row['CUs'])
myAssert(!Number.isNaN(cus))
const term = Number.parseInt(row['term'])
myAssert(!Number.isNaN(term))
const newCourse = new CourseTaken(
row['course subject'],
row['course number'],
row['course title'],
null,
cus,
row['grade mode'] == "P" ? GradeType.PassFail : GradeType.ForCredit,
row['grade'],
term,
'',
true)
existingCourses!.coursesTaken.push(newCourse)
})
let numStudentsFlagged = 0
coursesForStudent.forEach((psi, pennid) => {
const result = run([], new Degrees(), psi.coursesTaken)
const pcr = probationRisk(result, psi.coursesTaken, [PREVIOUS_TERM,CURRENT_TERM])
if (pcr.hasProbationRisk()) {
fs.appendFileSync(probationRiskFile, `${pennid}; ${psi.name}; ${psi.email}; ${pcr.overallGpa.toFixed(2)}; ${pcr.stemGpa.toFixed(2)}; ${pcr.fallCUs}; ${pcr.springCUs}; ${pcr.fallCUs + pcr.springCUs}; ${pcr}\n`)
numStudentsFlagged += 1
}
})
console.log(`processed ${courseTakenCsvRecords.length} courses from ${coursesForStudent.size} students`)
console.log(`${numStudentsFlagged} students flagged for probation risk`)
}
const AnalysisOutputDir = "/Users/devietti/Projects/irs/dw-analysis/"
const DraggableDataGetCourseTaken = "CourseTaken"
const DraggableOriginalRequirement = "OriginalDegreeRequirement"
const DroppableDataGetDegreeRequirement = "DegreeRequirement"
export const SsHTbsTag = "SS/H/TBS"
const SshDepthTag = "SSH Depth Requirement"
const Math1400RetroTitle = "Calculus 1 retro credit"
/** grades indicating a completed course, as opposed to I, NR, GR or 'IN PROGRESS' which indicate non-completion */
const CompletedGrades = ["A+","A","A-","B+","B","B-","C+","C","C-","D+","D","F","P","TR"]
/** academic terms during which students could take unlimited P/F courses */
const CovidTerms = [202010, 202020, 202030, 202110]
const CURRENT_TERM = 202410
const PREVIOUS_TERM = 202330
enum CourseAttribute {
Writing = "AUWR",
Math = "EUMA",
NatSci = "EUNS",
MathNatSciEngr = "EUMS",
Engineering = "EUNG",
SocialScience = "EUSS",
Humanities = "EUHS",
TBS = "EUTB",
NonEngr = "EUNE",
SasLanguage = "AULA",
SasLastLanguage = "AULL",
SasAdvancedLanguage = "AULA",
RoboTechElective = "EMRT",
RoboGeneralElective = "EMRE",
NetsLightTechElective = "NetsLightTE",
NetsFullTechElective = "NetsFullTE",
CsciRestrictedTechElective = "EUCR",
CsciUnrestrictedTechElective = "EUCU",
}
/** 1.5 CU Natural Science courses with labs */
const CoursesWithLab15CUs = [
"BIOL 1101", "BIOL 1102",
"PHYS 0150", "PHYS 0151",
"PHYS 0170", "PHYS 0171",
"ESE 1120" //, "ESE 2240"
]
/** 0.5 CU standalone Natural Science lab courses */
const StandaloneLabCourses05CUs = [
"BIOL 1123",
"CHEM 1101", "CHEM 1102",
"PHYS 0050", "PHYS 0051",
"MEAM 1470",
]
const CoursesWith15CUsToSplit = [
"EESC 1090", "MUSC 2700", "MUSC 2710"
]
// only allowed for 40cu students: https://ugrad.seas.upenn.edu/student-handbook/courses-requirements/engineering-courses/
const EngrLinkingCourses = [
"MGMT 1010",
"MGMT 2370",
"FNCE 2170",
"MGMT 1110",
"OIDD 2900",
"FNCE 2030",
"FNCE 1010",
"ACCT 1020",
"STAT 4350",
]
const CsciEthicsCourses = ["EAS 2030", "CIS 4230", "CIS 5230", "LAWM 5060", "VIPR 1200", "VIPR 1210"]
const CsciProjectElectives = [
"NETS 2120","CIS 3410","CIS 3500",
"CIS 4120","CIS 5120",
"CIS 4410","CIS 5410",
"CIS 4500","CIS 5500",
"CIS 4550","CIS 5550",
"CIS 4600","CIS 5600",
"CIS 5050","CIS 5530",
"ESE 3500"
]
const AscsProjectElectives = CsciProjectElectives.concat(["CIS 4710","CIS 5710","CIS 3800","CIS 4480","CIS 5480"])
const ArinCogSciCourses = [
"COGS 1001",
"LING 0500",
"LING 2500",
"LING 3810",
"PHIL 1710",
"PHIL 2640",
"PHIL 4721",
"PHIL 4840",
"PSYC 0001",
"PSYC 1210",
"PSYC 1340",
"PSYC 1230",
"PSYC 1310",
"PSYC 2737",
]
const ArinProjectElectives = [
"CIS 3500",
"CIS 4300",
"CIS 5300",
"CIS 4810",
"CIS 5810",
"ESE 3060",
"ESE 3600",
"ESE 4210",
"NETS 2120",
]
const ArinAiElectives = [
"CIS 4210",
"CIS 5210",
"ESE 2000",
"CIS 4190",
"CIS 5190",
"CIS 5200",
"ESE 2100",
"ESE 2240",
"ESE 3040",
"ESE 4210",
"CIS 4300",
"CIS 5300",
"CIS 4810",
"CIS 5810",
"CIS 3500",
"CIS 4300",
"CIS 5300",
"CIS 4810",
"CIS 5810",
"ESE 3060",
"ESE 3600",
"ESE 4210",
"NETS 2120",
"CIS 3333",
"CIS 6200",
"CIS 6250",
"ESE 4380",
"ESE 5380",
"ESE 5140",
"ESE 5460",
"ESE 6450",
"ESE 6740",
"ESE 3030",
"ESE 5000",
"ESE 5050",
"ESE 5060",
"ESE 6050",
"ESE 6060",
"ESE 6180",
"ESE 6190",
"BE 5210",
"CIS 4120",
"CIS 5120",
"CIS 4500",
"CIS 5500",
"CIS 5360",
"CIS 5800",
"CIS 6500",
"MEAM 5200",
"MEAM 6200",
"ESE 4040",
"ESE 6150",
"ESE 6500",
"NETS 3120",
"NETS 4120",
]
const DatsMinorStatBucket = ["BIOL 2510","CIS 2610","ESE 3010","STAT 4300","STAT 4760"]
const DatsMinorDataMgmtBucket = ["CIS 4500","CIS 5500","CIS 4550","CIS 5550","NETS 2130","OIDD 1050","STAT 4750"]
const DatsMinorModelingBucket = ["NETS 3120","MKTG 2710","OIDD 3250","OIDD 3530","STAT 4330"]
const DatsMinorDataCentricProgrammingBucket = ["CIS 1050","ENGR 1050","ESE 3050","OIDD 3110","STAT 4050","STAT 4700"]
const DatsMinorDataAnalysisBucket = ["CIS 4190","CIS 5190","CIS 4210","CIS 5210","CIS 5300","MKTG 2120","MKTG 3090","OIDD 4100",
"STAT 4220","STAT 4710","STAT 4740","STAT 5200"]
const DatsMinorElectives = DatsMinorStatBucket.concat(
DatsMinorDataMgmtBucket,
DatsMinorModelingBucket,
DatsMinorDataCentricProgrammingBucket,
DatsMinorDataAnalysisBucket)
const SseIseElectives = [
"CIS 2400", "CIS 4500", "CIS 5500",
"ESE 3050", "ESE 3250", "ESE 4070", "ESE 4200", "ESE 5000", "ESE 5040", "ESE 5050", "ESE 5120",
"ESE 520", // NB: doesn't have a 4-digit equivalent, seems retired
"ESE 5280", "ESE 5310", "ESE 5450", "ESE 6050", "ESE 6190",
"NETS 2120", "NETS 3120", "NETS 4120",
]
const SseSpaOnlyOne = [
"CIS 3310",
"ECON 4320",
"ECON 4480",
"ECON 4210",
"ENGR 566",
"FNCE 2030",
"FNCE 2070",
"FNCE 2090",
"FNCE 2370",
"FNCE 2390",
"FNCE 2500",
"FNCE 2250",
"FNCE 2800",
"FNCE 394",
"FNCE 894",
"FNCE 7210",
"FNCE 9110",
"MGMT 3000",
"MGMT 3005",
"MGMT 3500",
"MGMT 246",
"OIDD 1050",
"OIDD 2450",
"OPIM 2630",
"BEPP 2630",
"STAT 4710",
"STAT 4700",
]
const SseSpaList = [
"PHYS 2280",
"BE 5400",
"BE 5550",
"BE 5410",
"BE 3090",
"BE 5660",
"ESE 5660",
"BE 5840",
"BIOL 437",
"ESE 5430",
"EAS 3010",
"CBE 3750",
"CBE 543",
"OIDD 2610",
"ENVS 3550",
"BEPP 2610",
"BEPP 3050",
"EAS 4010",
"EAS 4020",
"EAS 4030",
"ENMG 5020",
"MEAM 5030",
"OIDD 2200",
"OIDD 2240",
"OIDD 3190",
"OIDD 3530",
"FNCE 2370",
"FNCE 3920",
"ECON 4130",
"STAT 5200",
"CPLN 5010",
"CPLN 5050",
"CPLN 5200",
"CPLN 5500",
"CPLN 6210",
"CPLN 5500",
"CPLN 6500",
"CPLN 654",
"CPLN 7500",
"ESE 5500",
"ESE 5500",
"CBE 520",
"ESE 4070",
"ESE 5070",
"ESE 408",
"MEAM 410",
"MEAM 5100",
"MEAM 5200",
"MEAM 6200",
"ESE 6500",
"CIS 5190",
"CIS 5200",
"CIS 5210",
"CIS 5810",
"ESE 5460",
"ESE 6500",
"STAT 4760",
]
const BeEthicsCourses = [
"EAS 2030",
"HSOC 1330",
"SOCI 2971",
"HSOC 2457",
"PHIL 1342",
"PHIL 4330",
"PPE 072",
"LGST 1000",
"LGST 2200",
"NURS 3300",
"NURS 5250",
]
const SeniorDesign1stSem = ["CIS 4000","CIS 4100","ESE 4500","MEAM 4450","BE 4950"]
const SeniorDesign2ndSem = ["CIS 4010","CIS 4110","ESE 4510","MEAM 4460","BE 4960"]
const WritingSeminarSsHTbs = new Map<string,CourseAttribute>([
["WRIT 0020", CourseAttribute.Humanities],
["WRIT 009", CourseAttribute.Humanities],
["WRIT 0100", CourseAttribute.Humanities],
["WRIT 0110", CourseAttribute.Humanities],
["WRIT 012", CourseAttribute.Humanities],
["WRIT 0130", CourseAttribute.Humanities],
["WRIT 0140", CourseAttribute.Humanities],
["WRIT 0150", CourseAttribute.Humanities],
["WRIT 0160", CourseAttribute.SocialScience],
["WRIT 0170", CourseAttribute.SocialScience],
["WRIT 0200", CourseAttribute.Humanities],
["WRIT 0210", CourseAttribute.TBS],
["WRIT 0220", CourseAttribute.TBS],
["WRIT 023", CourseAttribute.Humanities],
["WRIT 024", CourseAttribute.TBS],
["WRIT 0250", CourseAttribute.Humanities],
["WRIT 0260", CourseAttribute.Humanities],
["WRIT 0270", CourseAttribute.Humanities],
["WRIT 0280", CourseAttribute.SocialScience],
["WRIT 029", CourseAttribute.SocialScience],
["WRIT 0300", CourseAttribute.Humanities],
["WRIT 0310", CourseAttribute.TBS],
["WRIT 032", CourseAttribute.Humanities],
["WRIT 0330", CourseAttribute.Humanities],
["WRIT 0340", CourseAttribute.SocialScience],
["WRIT 035", CourseAttribute.SocialScience],
["WRIT 036", CourseAttribute.Humanities],
["WRIT 0370", CourseAttribute.SocialScience],
["WRIT 0380", CourseAttribute.SocialScience],
["WRIT 0390", CourseAttribute.Humanities],
["WRIT 0400", CourseAttribute.TBS],
["WRIT 0410", CourseAttribute.Humanities],
["WRIT 042", CourseAttribute.Humanities],
["WRIT 047", CourseAttribute.Humanities],
["WRIT 0480", CourseAttribute.SocialScience],
["WRIT 0490", CourseAttribute.Humanities],
["WRIT 0500", CourseAttribute.SocialScience],
["WRIT 0550", CourseAttribute.SocialScience],
["WRIT 056", CourseAttribute.Humanities],
["WRIT 0580", CourseAttribute.Humanities],
["WRIT 0590", CourseAttribute.SocialScience],
["WRIT 0650", CourseAttribute.TBS],
["WRIT 066", CourseAttribute.Humanities],
["WRIT 0670", CourseAttribute.Humanities],
["WRIT 0680", CourseAttribute.Humanities],
["WRIT 0730", CourseAttribute.Humanities],
["WRIT 0740", CourseAttribute.TBS],
["WRIT 075", CourseAttribute.SocialScience],
["WRIT 0760", CourseAttribute.SocialScience],
["WRIT 0770", CourseAttribute.SocialScience],
["WRIT 0820", CourseAttribute.Humanities],
["WRIT 0830", CourseAttribute.Humanities],
["WRIT 084", CourseAttribute.Humanities],
["WRIT 085", CourseAttribute.SocialScience],
["WRIT 086", CourseAttribute.Humanities],
["WRIT 087", CourseAttribute.Humanities],
["WRIT 0880", CourseAttribute.SocialScience],
["WRIT 0890", CourseAttribute.SocialScience],
["WRIT 090", CourseAttribute.TBS],
["WRIT 0910", CourseAttribute.Humanities],
["WRIT 0920", CourseAttribute.SocialScience],
["WRIT 125", CourseAttribute.Humanities],
//WRIT 135 & WRIT 138 PEER TUTOR TRAINING, count as FEs
])
const NetsFullTechElectives = new Set<string>([
"BEPP 2800",
"BIOL 4536",
"CIS 2400",
"CIS 2620",
"CIS 3310",
"CIS 3410",
"CIS 3500",
"CIS 3800",
"CIS 4190",
"CIS 4210",
"CIS 4500",
"CIS 4600",
"CIS 5110",
"CIS 5150",
"CIS 5190",
"CIS 5210",
"CIS 5300",
"CIS 5450",
"CIS 5500",
"CIS 5520",
"CIS 5530",
"CIS 5600",
"CIS 5800",
"CIS 6250",
"CIS 6770",
"COMM 4590",
"EAS 5070",
"EAS 5460",
"ECON 103",
"ECON 2200",
"ECON 2310",
"ECON 4130",
"ECON 4240",
"ECON 4430",
"ECON 4450",
"ECON 4480",
"ECON 4490",
"ECON 4510",
"ESE 2150",
"ESE 2240",
"ESE 302",
"ESE 3050",
"ESE 3250",
"ESE 3600",
"ESE 4000",
"ESE 4210",
"ESE 5390",
"ESE 5400",
"ESE 5430",
"FNCE 206",
"FNCE 2070",
"FNCE 2370",
"MATH 2410",
"MATH 3600",
"MKTG 2120",
"MKTG 2710",
"MKTG 3520",
"MKTG 4760",
"MKTG 7710",
"MKTG 8520",
"NETS 2130",
"OIDD 2450",
"OIDD 3190",
"OIDD 352",
"OIDD 3530",
"OIDD 6130",
"OIDD 9000",
"OIDD 9040",
"OIDD 9150",
"SOCI 5351",
"STAT 4310",
"STAT 4320",
"STAT 4330",
"STAT 4420",
"STAT 4710",
"STAT 4760",
"STAT 5110",
"STAT 5200",
"STAT 5420",
])
const NetsLightTechElectives = new Set<string>([
"EAS 3060",
"EAS 4030",
"EAS 5060",
"EAS 5450",
"ECON 4460",
"ESE 5670",
"LGST 2220",
"MKTG 2700",
"MKTG 7120",
"OIDD 2240",
"OIDD 2610",
"OIDD 316",
"OIDD 3210",
"OIDD 4690",
"SOCI 5350",
"STAT 4350",
])
// CIS MSE imported 16 Oct 2022
const CisMseNonCisElectivesRestrictions1 = new Set<string>([
"EAS 500",
"EAS 5100",
"EAS 5120",
"EAS 5450",
"EAS 5460",
"EAS 5900",
"EAS 5950",
"IPD 5150",
"IPD 5290",
"IPD 5720",
"EDUC 6577",
"NPLD 7920",
])
const CisMseNonCisElectivesRestrictions2 = new Set<string>([
"FNCE 7370",
"GAFL 5310",
"GEOL 5700",
"LARP 7430",
"STAT 7770",
])
const CisMseNonCisElectives = new Set<string>([
"BE 5160",
"BE 5210",
"BE 5300",
"PHYS 5585",
"BE 5670",
"GCB 5670",
"CRIM 502",
"CRIM 6002",
"EAS 5070",
"ECON 6100",
"ECON 6110",
"ECON 7100",
"ECON 7110",
"ECON 713",
"EDUC 5299",
"EDUC 545",
"EDUC 5152",
"ENM 5020",
"ENM 5030",
"ENM 5220",
"ENM 5400",
"ESE 5000",
"ESE 5040",
"ESE 5050",
"ESE 5070",
"ESE 5140",
"ESE 5160",
"ESE 5190",
"ESE 520",
"ESE 5300",
"ESE 5310",
"ESE 5320",
"ESE 534",
"ESE 5350",
"ESE 5390",
"ESE 5400",
"ESE 5420",
"ESE 5430",
"ESE 5460",
"ESE 5440",
"ESE 5450",
"ESE 575",
"ESE 576",
"ESE 5900",
"ESE 6050",
"ESE 6180",
"ESE 6500",
"ESE 6650",
"ESE 6740",
"ESE 6760",
"ESE 6800",
"ESE 6800",
"FNAR 5025",
"GCB 5360",
"LAW 5770",
"LING 5150",
"LING 5250",
"LING 545",
"LING 546",
"LING 549",
"MATH 5000",
"MATH 5020",
"MATH 5080",
"MATH 5130",
"MATH 5140",
"MATH 5300",
"MATH 5460",
"STAT 530",
"MATH 547",
"STAT 531",
"MATH 570",
"MATH 571",
"MATH 574",
"MATH 5800",
"MATH 5810",
"MATH 582",
"MATH 5840",
"MATH 586",
"BIOL 5860",
"MATH 690",
"MATH 691",
"MEAM 5100",
"MEAM 5200",
"MEAM 521",
"MEAM 6200",
"MEAM 625",
"MEAM 6460",
"MSE 5610",
"MSE 5750",
"FNCE 611",
"FNCE 7170",
"FNCE 720",
"FNCE 7210",
"REAL 7210",
"FNCE 7250",
"FNCE 7380",
"FNCE 7500",
"FNCE 8920",
"MKTG 7120",
"MKTG 8520",
"OIDD 6530",
"OIDD 6540",
"OIDD 6700",
"OIDD 9500",
"OIDD 9340",
"STAT 5100",
"STAT 5000",
"STAT 5030",
"STAT 5110",
"STAT 5120",
"STAT 550",
"STAT 5150",
"STAT 5200",
"STAT 530",
"STAT 531",
"STAT 5330",
"STAT 553",
"STAT 5420",
"STAT 5710",
"STAT 7010",
"STAT 7050",
"STAT 5350",
"STAT 7110",
"STAT 7700",
"STAT 7220",
"STAT 900",
"STAT 9280",
"STAT 9300",
"STAT 9700",
"STAT 9740",
"STAT 9910",
"STAT 9910",
])
const RoboAiBucket = ["CIS 4190","CIS 5190","CIS 5200","CIS 4210","CIS 5210","ESE 6500"]
const RoboRobotDesignAnalysisBucket = ["MEAM 5100","MEAM 5200","MEAM 6200"]
const RoboControlBucket = ["ESE 5000","ESE 5050","ESE 6190","MEAM 5130","MEAM 5170"]
const RoboPerceptionBucket = ["CIS 5800","CIS 5810","CIS 6800"]
const RoboFoundationalCourses = RoboAiBucket.concat(
RoboRobotDesignAnalysisBucket,
RoboControlBucket,
RoboPerceptionBucket
)
// ROBO imported 19 Oct 2022 from https://www.grasp.upenn.edu/academics/masters-degree-program/curriculum-information/technical-electives/
const RoboTechElectives = new Set<string>([
"BE 5210",
"BE 5700",
"CIS 5020",
"CIS 5110",
"CIS 5150",
"CIS 5190",
"CIS 5200",
"CIS 5210",
"CIS 5260",
"CIS 5300",
"CIS 5400",
"CIS 5410",
"CIS 5450",
"CIS 5600",
"CIS 5620",
"CIS 5630",
"CIS 5640",
"CIS 5650",
"CIS 5800",
"CIS 5810",
"CIS 6100",
"CIS 6200",
"CIS 6250",
"CIS 6800",
"ENM 5100",