@@ -12,8 +12,12 @@ import calcADAPTScores from "./models/calcADAPTScores";
12
12
import ewsCourseSummary , {
13
13
IEWSCourseSummary_Raw ,
14
14
} from "./models/ewsCourseSummary" ;
15
+ import ewsActorSummary , {
16
+ IEWSActorSummary_Raw ,
17
+ } from "./models/ewsActorSummary" ;
15
18
import calcADAPTInteractionDays from "./models/calcADAPTInteractionDays" ;
16
19
import calcADAPTAssignments from "./models/calcADAPTAssignments" ;
20
+ import enrollments from "./models/enrollments" ;
17
21
18
22
class AnalyticsDataProcessor {
19
23
constructor ( ) { }
@@ -30,6 +34,7 @@ class AnalyticsDataProcessor {
30
34
//await this.compressTexbookInteractionsByDate();
31
35
//await this.compressTextbookNumInteractions(); // Should be ran after compressing textbookInteractionsByDate
32
36
await this . writeEWSCourseSummary ( ) ;
37
+ await this . writeEWSActorSummary ( ) ;
33
38
}
34
39
35
40
private async compressADAPTAllAssignments ( ) : Promise < boolean > {
@@ -101,24 +106,29 @@ class AnalyticsDataProcessor {
101
106
await connectDB ( ) ;
102
107
103
108
debugADP ( "[compressADAPTAssignments]: Starting aggregation..." ) ;
104
- await ADAPT . aggregate (
109
+ await Gradebook . aggregate (
105
110
[
106
111
{
107
112
$match : {
108
113
assignment_id : {
109
114
$exists : true ,
110
- $ne : "" ,
115
+ $nin : [ null , "" ] ,
111
116
} ,
112
117
} ,
113
118
} ,
114
119
{
115
120
$group : {
116
121
_id : {
117
- courseID : "$course_id " ,
118
- actor : "$anon_student_id " ,
122
+ courseID : "$class " ,
123
+ actor : "$email " ,
119
124
} ,
120
125
assignments : {
121
- $addToSet : "$assignment_id" ,
126
+ $addToSet : {
127
+ assignment_id : {
128
+ $toString : "$assignment_id" ,
129
+ } ,
130
+ score : "$assignment_percent" ,
131
+ } ,
122
132
} ,
123
133
} ,
124
134
} ,
@@ -133,6 +143,18 @@ class AnalyticsDataProcessor {
133
143
} ,
134
144
} ,
135
145
} ,
146
+ {
147
+ $match : {
148
+ actor : {
149
+ $exists : true ,
150
+ $nin : [ null , "" ] ,
151
+ } ,
152
+ courseID : {
153
+ $exists : true ,
154
+ $nin : [ null , "" ] ,
155
+ } ,
156
+ } ,
157
+ } ,
136
158
{
137
159
$merge : {
138
160
into : "calcADAPTAssignments" ,
@@ -678,6 +700,7 @@ class AnalyticsDataProcessor {
678
700
try {
679
701
await connectDB ( ) ;
680
702
703
+ debugADP ( "[writeEWSCourseSummary]: Starting aggregation..." ) ;
681
704
const coursesWAssignments = await calcADAPTAllAssignments . find ( ) ;
682
705
const courseAssignmentMap = new Map < string , string [ ] > ( ) ;
683
706
coursesWAssignments . forEach ( ( course ) => {
@@ -844,6 +867,7 @@ class AnalyticsDataProcessor {
844
867
} ) )
845
868
) ;
846
869
870
+ debugADP ( `[writeEWSCourseSummary]: Finished writing course summaries.` ) ;
847
871
return true ;
848
872
} catch ( err : any ) {
849
873
debugADP (
@@ -852,6 +876,215 @@ class AnalyticsDataProcessor {
852
876
return false ;
853
877
}
854
878
}
879
+
880
+ private async writeEWSActorSummary ( ) : Promise < boolean > {
881
+ try {
882
+ await connectDB ( ) ;
883
+
884
+ debugADP ( "[writeEWSActorSummary]: Starting aggregation..." ) ;
885
+ const actors = await enrollments . aggregate ( [
886
+ {
887
+ $group : {
888
+ _id : {
889
+ email : "$email" ,
890
+ courseID : "$courseID" ,
891
+ } ,
892
+ } ,
893
+ } ,
894
+ {
895
+ $project : {
896
+ _id : 0 ,
897
+ actor_id : "$_id.email" ,
898
+ course_id : "$_id.courseID" ,
899
+ } ,
900
+ } ,
901
+ ] ) ;
902
+
903
+ const actorWCourses = new Map < string , string [ ] > ( ) ;
904
+ actors . forEach ( ( actor ) => {
905
+ if ( actorWCourses . has ( actor . actor_id ) ) {
906
+ actorWCourses . get ( actor . actor_id ) ?. push ( actor . course_id ) ;
907
+ } else {
908
+ actorWCourses . set ( actor . actor_id , [ actor . course_id ] ) ;
909
+ }
910
+ } ) ;
911
+
912
+ const actorAssignments = await calcADAPTAssignments . aggregate (
913
+ [
914
+ {
915
+ $match : {
916
+ $or : Array . from ( actorWCourses . entries ( ) ) . map (
917
+ ( [ actorID , courseIDs ] ) => ( {
918
+ actor : actorID ,
919
+ courseID : { $in : courseIDs } ,
920
+ } )
921
+ ) ,
922
+ } ,
923
+ } ,
924
+ ] ,
925
+ {
926
+ allowDiskUse : true ,
927
+ }
928
+ ) ;
929
+
930
+ const actorSummaries : IEWSActorSummary_Raw [ ] = [ ] ;
931
+ for ( const [ actorID , courseIDs ] of Array . from ( actorWCourses . entries ( ) ) ) {
932
+ for ( const courseID of courseIDs ) {
933
+ const actorCourseAssignments = actorAssignments . filter (
934
+ ( assignment : { actor : string ; courseID : string } ) =>
935
+ assignment . actor === actorID && assignment . courseID === courseID
936
+ ) ;
937
+
938
+ const actorSummary : IEWSActorSummary_Raw = {
939
+ actor_id : actorID ,
940
+ course_id : courseID ,
941
+ assignments :
942
+ actorCourseAssignments
943
+ . at ( 0 )
944
+ ?. assignments . map (
945
+ ( assignment : { assignment_id : string ; score : number } ) => ( {
946
+ assignment_id : assignment . assignment_id ,
947
+ score : isNaN ( assignment . score ) ? 0 : assignment . score ,
948
+ } )
949
+ ) || [ ] ,
950
+ percent_seen : 0 ,
951
+ interaction_days : 0 ,
952
+ course_percent : 0 ,
953
+ } ;
954
+
955
+ actorSummaries . push ( actorSummary ) ;
956
+ }
957
+ }
958
+
959
+ const interactionDays = await calcADAPTInteractionDays . aggregate ( [
960
+ {
961
+ $group : {
962
+ _id : {
963
+ actor : "$actor" ,
964
+ courseID : "$courseID" ,
965
+ } ,
966
+ interaction_days : {
967
+ $sum : {
968
+ $size : "$days" ,
969
+ } ,
970
+ } ,
971
+ } ,
972
+ } ,
973
+ {
974
+ $project : {
975
+ _id : 0 ,
976
+ actor_id : "$_id.actor" ,
977
+ course_id : "$_id.courseID" ,
978
+ interaction_days : 1 ,
979
+ } ,
980
+ } ,
981
+ ] ) ;
982
+
983
+ interactionDays . forEach ( ( interaction ) => {
984
+ const actorSummary = actorSummaries . find (
985
+ ( summary ) =>
986
+ summary . actor_id === interaction . actor_id &&
987
+ summary . course_id === interaction . course_id
988
+ ) ;
989
+ if ( actorSummary ) {
990
+ actorSummary . interaction_days = interaction . interaction_days ;
991
+ }
992
+ } ) ;
993
+
994
+ const courseAssignments = await calcADAPTAllAssignments . aggregate ( [
995
+ {
996
+ $group : {
997
+ _id : "$courseID" ,
998
+ assignments_count : {
999
+ $sum : {
1000
+ $size : "$assignments" ,
1001
+ } ,
1002
+ } ,
1003
+ } ,
1004
+ } ,
1005
+ {
1006
+ $project : {
1007
+ _id : 0 ,
1008
+ courseID : "$_id" ,
1009
+ assignments_count : 1 ,
1010
+ } ,
1011
+ } ,
1012
+ ] ) ;
1013
+
1014
+ const courseAssignmentsMap = new Map < string , number > ( ) ;
1015
+ courseAssignments . forEach ( ( course ) => {
1016
+ courseAssignmentsMap . set ( course . courseID , course . assignments_count ) ;
1017
+ } ) ;
1018
+
1019
+ actorSummaries . forEach ( ( actorSummary ) => {
1020
+ const courseID = actorSummary . course_id ;
1021
+ const assignmentsCount = courseAssignmentsMap . get ( courseID ) ?? 0 ;
1022
+ actorSummary . percent_seen =
1023
+ ( actorSummary . assignments . length / assignmentsCount ) * 100 || 0 ;
1024
+ } ) ;
1025
+
1026
+ // For course_percent, find the latest gradebook entry for each actor in each course and use the overall_course_percent
1027
+ const latestGradebookEntries = await Gradebook . aggregate ( [
1028
+ {
1029
+ $group : {
1030
+ _id : {
1031
+ actor : "$email" ,
1032
+ courseID : "$class" ,
1033
+ } ,
1034
+ newestDocument : {
1035
+ $last : "$$ROOT" ,
1036
+ } ,
1037
+ } ,
1038
+ } ,
1039
+ {
1040
+ $project : {
1041
+ _id : 0 ,
1042
+ actor_id : "$_id.actor" ,
1043
+ course_id : "$_id.courseID" ,
1044
+ course_percent : "$newestDocument.overall_course_percent" ,
1045
+ } ,
1046
+ } ,
1047
+ ] ) ;
1048
+
1049
+ latestGradebookEntries . forEach ( ( entry ) => {
1050
+ const actorSummary = actorSummaries . find (
1051
+ ( summary ) =>
1052
+ summary . actor_id === entry . actor_id &&
1053
+ summary . course_id === entry . course_id
1054
+ ) ;
1055
+ if ( actorSummary ) {
1056
+ actorSummary . course_percent = entry . course_percent ;
1057
+ }
1058
+ } ) ;
1059
+
1060
+ // filter missing actor_id and course_id
1061
+ const filteredActorSummaries = actorSummaries . filter (
1062
+ ( summary ) => summary . actor_id && summary . course_id
1063
+ ) ;
1064
+
1065
+ // Write the actor summaries to the database
1066
+ await ewsActorSummary . bulkWrite (
1067
+ filteredActorSummaries . map ( ( summary ) => ( {
1068
+ updateOne : {
1069
+ filter : {
1070
+ actor_id : summary . actor_id ,
1071
+ course_id : summary . course_id ,
1072
+ } ,
1073
+ update : summary ,
1074
+ upsert : true ,
1075
+ } ,
1076
+ } ) )
1077
+ ) ;
1078
+
1079
+ debugADP ( `[writeEWSActorSummary]: Finished writing actor summaries.` ) ;
1080
+ return true ;
1081
+ } catch ( err : any ) {
1082
+ debugADP (
1083
+ err . message ?? "Unknown error occured while writing EWS actor summary"
1084
+ ) ;
1085
+ return false ;
1086
+ }
1087
+ }
855
1088
}
856
1089
857
1090
export default AnalyticsDataProcessor ;
0 commit comments