コード例 #1
0
ファイル: Program.cs プロジェクト: uvadev/UVACanvasAccess
        public static async Task Main(string[] args)
        {
            var home = new AppHome("uva_super_report");

            Console.WriteLine($"Using config path: {home.ConfigPath}");

            if (!home.ConfigPresent())
            {
                Console.WriteLine("Need to generate a config file.");

                home.CreateConfig(new DocumentSyntax {
                    Tables =
                    {
                        new TableSyntax("tokens")
                        {
                            Items =
                            {
                                { "token", "PUT_TOKEN_HERE"                      }
                            }
                        },
                        new TableSyntax("limits")
                        {
                            Items =
                            {
                                { "sample_skip",                                              0 },
                                { "sample_take",                                              0 },
                                { "courses_per_teacher",                                      0 },
                                { "assignments_per_course",                                   0 },
                                { "submissions_per_assignment",                               0 },
                                { "teachers_only",              false                           }
                            }
                        }
                    }
                });

                Console.WriteLine("Created a new config file. Please go put in your token.");
                return;
            }

            Console.WriteLine("Found config file.");

            var config = home.GetConfig();

            Debug.Assert(config != null, nameof(config) + " != null");

            var token = config.GetTable("tokens").Get <string>("token");

            var limits                   = config.GetTable("limits");
            var sampleTake               = (int)limits.GetOr <long>("sample_take");
            var sampleSkip               = (int)limits.GetOr <long>("sample_skip");
            var coursesPerTeacher        = (int)limits.GetOr <long>("courses_per_teacher");
            var assignmentsPerCourse     = (int)limits.GetOr <long>("assignments_per_course");
            var submissionsPerAssignment = (int)limits.GetOr <long>("submissions_per_assignment");
            var teachersOnly             = limits.GetOr <bool>("teachers_only");

            Console.WriteLine($"SKIPPING {sampleSkip} users.");
            Console.WriteLine($"TAKING {(sampleTake == default ? "ALL" : sampleTake.ToString())} users.");
            Console.WriteLine($"TAKING {(coursesPerTeacher == default ? "ALL" : coursesPerTeacher.ToString())} courses per teacher.");
            Console.WriteLine($"TAKING {(assignmentsPerCourse == default ? "ALL" : assignmentsPerCourse.ToString())} assignments per course.");
            Console.WriteLine($"TAKING {(submissionsPerAssignment == default ? "ALL" : submissionsPerAssignment.ToString())} submissions per assignment.");

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

            var api = new Api(token, "https://uview.instructure.com/api/v1/");

            var studentsObj                    = new JObject();
            var teachersObj                    = new JObject();
            var coursesObj                     = new JObject();
            var submissionsObj                 = new JObject();
            var assignmentsOverallObj          = new JObject();
            var assignmentsIndividualObj       = new JObject();
            var individualCoursePerformanceObj = new JObject();
            var overallCoursePerformanceObj    = new JObject();
            var teacherPerformanceObj          = new JObject();

            var started = DateTime.Now;

            var document = new JObject {
                ["teachers"]                    = teachersObj,
                ["students"]                    = studentsObj,
                ["courses"]                     = coursesObj,
                ["submissions"]                 = submissionsObj,
                ["assignmentsOverall"]          = assignmentsOverallObj,
                ["assignmentsIndividual"]       = assignmentsIndividualObj,
                ["individualCoursePerformance"] = individualCoursePerformanceObj,
                ["overallCoursePerformance"]    = overallCoursePerformanceObj,
                ["teacherPerformance"]          = teacherPerformanceObj,
                ["limits"] = new JObject {
                    ["sampleTake"]               = sampleTake,
                    ["sampleSkip"]               = sampleSkip,
                    ["coursesPerTeacher"]        = coursesPerTeacher,
                    ["assignmentsPerCourse"]     = assignmentsPerCourse,
                    ["submissionsPerAssignment"] = submissionsPerAssignment,
                    ["teachersOnly"]             = teachersOnly
                },
                ["dateStarted"] = started.ToIso8601Date()
            };

            var sample = api.StreamUsers()
                         .Where(u => !u.Name.ToLowerInvariant().Contains("test"))
                         .Where(u => !u.SisUserId?.StartsWith("pG") ?? false);

            if (teachersOnly)
            {
                Console.WriteLine("TAKING TEACHERS ONLY.");
                sample = sample.WhereAwait(async u => await u.IsTeacher());
            }

            sample = sample.Skip(sampleSkip);

            if (sampleTake != default)
            {
                sample = sample.Take(sampleTake);
            }

            await foreach (var user in sample)
            {
                try {
                    if (await user.IsTeacher())
                    {
                        #region CurrentUserIsTeacher

                        if (!teachersObj.ContainsKey(user.Id.ToString()))
                        {
                            teachersObj[user.Id.ToString()] = new JObject {
                                ["sisId"]    = user.SisUserId,
                                ["fullName"] = user.Name
                            };
                        }

                        var enrollmentsStream = api.StreamUserEnrollments(user.Id, TeacherEnrollment.Yield())
                                                .SelectAwait(async e => (e, await api.GetCourse(e.CourseId)))
                                                .Where(ec => !ec.Item2.Name.ToLowerInvariant().Contains("advisory"));

                        if (coursesPerTeacher != default)
                        {
                            enrollmentsStream = enrollmentsStream.Take(coursesPerTeacher);
                        }

                        await foreach (var(enrollment, course) in enrollmentsStream)
                        {
                            if (!coursesObj.ContainsKey(course.Id.ToString()))
                            {
                                coursesObj[course.Id.ToString()] = new JObject {
                                    ["sisId"] = course.SisCourseId,
                                    ["name"]  = course.Name
                                };
                            }

                            if (!overallCoursePerformanceObj.ContainsKey(course.Id.ToString()))
                            {
                                var studentEnrollments =
                                    api.StreamCourseEnrollments(course.Id, StudentEnrollment.Yield());

                                var courseScores = new List <decimal>();
                                await foreach (var studentEnrollment in studentEnrollments)
                                {
                                    var grades = studentEnrollment.Grades;

                                    if (!individualCoursePerformanceObj.ContainsKey(enrollment.Id.ToString()))
                                    {
                                        individualCoursePerformanceObj[enrollment.Id.ToString()] = new JObject {
                                            ["studentId"]            = user.Id,
                                            ["courseId"]             = enrollment.CourseId,
                                            ["currentLetterGrade"]   = grades.CurrentGrade,
                                            ["finalLetterGrade"]     = grades.FinalGrade,
                                            ["currentScore"]         = grades.CurrentScore,
                                            ["finalScore"]           = grades.FinalScore,
                                            ["activitySecondsSpent"] = studentEnrollment.TotalActivityTime,
                                            ["lastAttendedAt"]       = studentEnrollment.LastAttendedAt?.ToIso8601Date(),
                                            ["lastActivityAt"]       = studentEnrollment.LastActivityAt?.ToIso8601Date()
                                        };
                                    }

                                    var currentScore = grades.CurrentScore;
                                    courseScores.Add(string.IsNullOrEmpty(currentScore) ? 0
                                                                                        : Convert.ToDecimal(grades.CurrentScore));
                                }

                                if (!courseScores.Any())
                                {
                                    continue;
                                }

                                var courseScoreStats = new Stats(courseScores);
                                var pass             = courseScores.Count(s => s > 66.5m);

                                overallCoursePerformanceObj[course.Id.ToString()] = new JObject {
                                    ["gradesInSample"]               = courseScores.Count,
                                    ["meanCourseScore"]              = courseScoreStats.Mean,
                                    ["modeCourseScore"]              = courseScoreStats.Mode,
                                    ["25thPercentileCourseScore"]    = courseScoreStats.Q1,
                                    ["medianCourseScore"]            = courseScoreStats.Median,
                                    ["75thPercentileCourseScore"]    = courseScoreStats.Q3,
                                    ["courseScoreStandardDeviation"] = courseScoreStats.Sigma,
                                    ["coursePassCount"]              = pass,
                                    ["courseFailCount"]              = courseScores.Count - pass
                                };
                            }

                            var assignments = (await api.StreamCourseAssignments(course.Id)
                                               .Where(a => a.Published)
                                               .ToListAsync())
                                              .DistinctBy(a => a.Id);

                            if (assignmentsPerCourse != default)
                            {
                                assignments = assignments.Take(assignmentsPerCourse);
                            }

                            foreach (var assignment in assignments)
                            {
                                var allSubmissionsStream = api.StreamSubmissionVersions(course.Id, assignment.Id);

                                if (submissionsPerAssignment != default)
                                {
                                    allSubmissionsStream = allSubmissionsStream.Take(submissionsPerAssignment);
                                }

                                var allSubmissions = await allSubmissionsStream.ToListAsync();

                                // just listing every submission here
                                foreach (var submission in allSubmissions)
                                {
                                    var submitter = await api.GetUser(submission.UserId);

                                    submissionsObj[$"c_{course.Id}|a_{assignment.Id}|u_{submitter.Id}|n_{submission.Attempt??0}"] = new JObject {
                                        ["assignmentId"]        = assignment.Id,
                                        ["courseId"]            = course.Id,
                                        ["userId"]              = submitter.Id,
                                        ["versionNumber"]       = submission.Attempt,
                                        ["submissionDate"]      = submission.SubmittedAt,
                                        ["pointsEarned"]        = submission.Score,
                                        ["dateSubmitted"]       = submission.SubmittedAt,
                                        ["dateGraded"]          = submission.GradedAt,
                                        ["gradeMatchesCurrent"] = submission.GradeMatchesCurrentSubmission,
                                        ["late"]    = submission.Late ?? false,
                                        ["missing"] = submission.Missing ?? false
                                    };
                                }

                                // now narrow down for assignment performance
                                var submissions = allSubmissions.Where(s => s.Score != null)
                                                  .GroupBy(s => s.UserId)
                                                  .Select(sg => sg.First())
                                                  .ToList();

                                if (!submissions.Any())
                                {
                                    continue;
                                }

                                var scores = submissions.Select(s => s.Score)
                                             .Cast <decimal>()
                                             .ToList();

                                var stats = new Stats(scores);

                                if (assignmentsOverallObj.ContainsKey($"c_{course.Id}|a_{assignment.Id}"))
                                {
                                    Console.WriteLine($"WARN: Duplicated assignment?\nc={course.Id}\na={assignment.Id}\n\n{assignment.ToPrettyString()}\nSkipping!------\n");
                                    continue;
                                }

                                assignmentsOverallObj[$"c_{course.Id}|a_{assignment.Id}"] = new JObject {
                                    ["assignmentName"]         = assignment.Name,
                                    ["assignmentId"]           = assignment.Id,
                                    ["courseId"]               = course.Id,
                                    ["teacherId"]              = user.Id,
                                    ["countedInFinalGrade"]    = !(assignment.OmitFromFinalGrade ?? false),
                                    ["pointsPossible"]         = assignment.PointsPossible,
                                    ["createdDate"]            = assignment.CreatedAt.ToIso8601Date(),
                                    ["dueDate"]                = assignment.DueAt?.ToIso8601Date(),
                                    ["gradesInSample"]         = submissions.Count,
                                    ["meanScore"]              = stats.Mean,
                                    ["modeScore"]              = stats.Mode,
                                    ["25thPercentileScore"]    = stats.Q1,
                                    ["medianScore"]            = stats.Median,
                                    ["75thPercentileScore"]    = stats.Q3,
                                    ["scoreStandardDeviation"] = stats.Sigma
                                };

                                // here we're listing the submissions per user that actually count for grades
                                foreach (var submission in submissions)
                                {
                                    var submitter = await api.GetUser(submission.UserId);

                                    Debug.Assert(!assignmentsIndividualObj.ContainsKey($"c_{course.Id}|a_{assignment.Id}|u_{submitter.Id}"));

                                    var score = submission.Score.Value;
                                    var z     = Convert.ToDouble(score - stats.Mean) / stats.Sigma;
                                    var iqr   = stats.Q3 - stats.Q1;

                                    TimeSpan?minutesSubmittedBeforeDueDate = null;
                                    if (submission.SubmittedAt != null && assignment.DueAt != null)
                                    {
                                        minutesSubmittedBeforeDueDate = assignment.DueAt.Value - submission.SubmittedAt.Value;
                                    }

                                    assignmentsIndividualObj[$"c_{course.Id}|a_{assignment.Id}|u_{submitter.Id}"] = new JObject {
                                        ["assignmentId"]   = assignment.Id,
                                        ["courseId"]       = course.Id,
                                        ["userId"]         = submitter.Id,
                                        ["submissionDate"] = submission.SubmittedAt,
                                        ["pointsEarned"]   = score,
                                        ["z"]              = z,
                                        ["isUnusual"]      = Math.Abs(z) > 1.96,
                                        ["isMinorOutlier"] = score <stats.Q1 - iqr * 1.5m ||
                                                                    score> stats.Q3 + iqr * 1.5m,
                                        ["isMajorOutlier"] = score <stats.Q1 - iqr * 3m ||
                                                                    score> stats.Q3 + iqr * 3m,
                                        ["minutesSubmittedBeforeDueDate"] = minutesSubmittedBeforeDueDate?.TotalMinutes
                                    };
                                }
                            }
                        }

                        teacherPerformanceObj[user.Id.ToString()] = new JObject {
                        };

                        #endregion
                    }
                    else
                    {
                        #region CurrentUserIsStudent

                        if (!studentsObj.ContainsKey(user.Id.ToString()))
                        {
                            var lastLogin = api.StreamUserAuthenticationEvents(user.Id)
                                            .Where(e => e.Event == EventType.Login)
                                            .Select(e => e.CreatedAt)
                                            .DefaultIfEmpty()
                                            .MaxAsync();

                            studentsObj[user.Id.ToString()] = new JObject {
                                ["sisId"]     = user.SisUserId,
                                ["fullName"]  = user.Name,
                                ["lastLogin"] = (await lastLogin).ToIso8601Date()
                            };
                        }

                        await foreach (var enrollment in api.StreamUserEnrollments(user.Id))
                        {
                            if (!coursesObj.ContainsKey(enrollment.CourseId.ToString()))
                            {
                                var course = await api.GetCourse(enrollment.CourseId);

                                coursesObj[course.Id.ToString()] = new JObject {
                                    ["sisId"] = course.SisCourseId,
                                    ["name"]  = course.Name
                                };
                            }
                        }

                        #endregion
                    }
                } catch (Exception e) {
                    Console.WriteLine($"Caught an exception while processing user id {user.Id}\n{e}\n-------\n");
                }
            }

            document["dateCompleted"] = DateTime.Now.ToIso8601Date();
            document["usersInReport"] = studentsObj.Count + teachersObj.Count;

            var outPath = Path.Combine(home.NsDir, $"SuperReport_{started.Ticks}.json");
            File.WriteAllText(outPath, document.ToString(Indented) + "\n");
            Console.WriteLine($"Wrote report to {outPath}");
        }
コード例 #2
0
ファイル: Program.cs プロジェクト: uvadev/UVACanvasAccess
        public static async Task Main(string[] args)
        {
            var home = new AppHome("export_attendance_columns_csv");

            Console.WriteLine($"Using config path: {home.ConfigPath}");

            if (!home.ConfigPresent())
            {
                Console.WriteLine("Need to generate a config file.");

                home.CreateConfig(new DocumentSyntax {
                    Tables =
                    {
                        new TableSyntax("tokens")
                        {
                            Items =
                            {
                                { "token", "PUT_TOKEN_HERE"          }
                            }
                        },
                        new TableSyntax("options")
                        {
                            Items =
                            {
                                { "include_hidden", false            }
                            }
                        },
                        new TableSyntax("debug")
                        {
                            Items =
                            {
                                { "limit_to",                     -1 }
                            }
                        }
                    }
                });

                Console.WriteLine("Created a new config file. Please go put in your token and input path.");
                return;
            }

            Console.WriteLine("Found config file.");

            var config = home.GetConfig();

            Debug.Assert(config != null, nameof(config) + " != null");

            var token = config.GetTable("tokens")
                        .Get <string>("token");

            var includeHidden = config.GetTable("options")
                                .Get <bool>("include_hidden");

            var courseLimit = config.GetTable("debug")
                              .Get <long>("limit_to");

            var api = new Api(token, "https://uview.instructure.com/api/v1/");

            if (courseLimit > 0)
            {
                Console.WriteLine($"[DEBUG] Limited to course id {courseLimit}");
            }

            var table = new StringBuilder("course_id,column_name,student_id,enrollment_id,column_value\n");

            try {
                var courses = courseLimit <= 0 ? api.StreamCourses()
                                               : (await api.GetCourse(Convert.ToUInt64(courseLimit))).YieldAsync();

                await foreach (var course in courses)
                {
                    try {
                        var enrollments = api.StreamCourseEnrollments(course.Id, StudentEnrollment.Yield())
                                          .ToDictionaryAsync(e => e.UserId);
                        await foreach (var col in api.StreamCustomGradebookColumns(course.Id, includeHidden))
                        {
                            await foreach (var datum in api.StreamColumnEntries(col.Id, course.Id))
                            {
                                (await enrollments).TryGetValue(datum.UserId, out var thisEnrollment);
                                table.Append($"{course.Id}," +
                                             $"\"{col.Title}\"," +
                                             $"{datum.UserId}," +
                                             $"{thisEnrollment?.Id.ToString() ?? "NULL"}" +
                                             $",\"{datum.Content}\"\n");
                            }
                        }
                    } catch (Exception e) {
                        Console.WriteLine($"Threw up during course {course.Id}:\n{e}\nContinuing onwards.");
                    }
                }

                var now     = DateTime.Now;
                var outPath = Path.Combine(home.NsDir, $"AttendanceColumns_{now.Year}_{now.Month}_{now.Day}.csv");
                File.WriteAllText(outPath, table.ToString());
                Console.WriteLine($"Wrote report to {outPath}");
            } catch (Exception e) {
                Console.WriteLine($"Threw up:\n{e}");
            }
        }
コード例 #3
0
        internal static async Task Truancy(string token, string outPath, TomlTable truancyConfig, bool shortInterval = false)
        {
            if (!Database.UseSis)
            {
                Console.WriteLine("Please enable SIS to run the Truancy report.");
                return;
            }

            if (truancyConfig == null)
            {
                Console.WriteLine("Truancy config table is null.");
                return;
            }

            var sisIdYear         = truancyConfig.Get <string>("sis_id_year");
            var subaccountsFilter = truancyConfig.Get <TomlArray>("subaccounts").Cast <string>().ToArray();

            Console.WriteLine("Running Truancy...");

            var api = new Api(token, "https://uview.instructure.com/api/v1/");

            var sb = new StringBuilder("user_id,sis_id,last_access,first_name,last_name,grade,phone,cell,address," +
                                       "city,state,zip,mother_name,father_name,guardian_name,mother_email,father_email,guardian_email," +
                                       "mother_cell,father_cell,guardian_cell,dob,entry_date,gender,school," +
                                       "district_of_residence,age,ethnicity,language,guardian_relationship,has_sped,has_504,failing_courses");

            await using var enumerationDb = await Database.Connect();

            await using var dataDb = await Database.Connect();

            await foreach (var sis in enumerationDb.GetTruancyCheckStudents())
            {
                try {
                    var user = await api.StreamUsers(sis)
                               .FirstOrDefaultAsync(u => u.SisUserId == sis);

                    if (user == null)
                    {
                        Console.WriteLine($"Warning: User with sis `{sis}` does not seem to exist in Canvas.");
                        sb.Append($"\n?,{sis},indeterminate,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?");
                        continue;
                    }

                    var mostRecent = await api.StreamUserPageViews(user.Id)
                                     .Where(pv => pv.Links?.RealUser == null ||
                                            pv.Links.RealUser.Value == user.Id)               // ignore masqueraded views
                                     .Where(pv => pv.Url.Length > "https://".Length)          // ignore weird blank requests
                                     .Where(pv => !string.IsNullOrWhiteSpace(pv.UserAgent))   // ignore weird blank user agents
                                     .FirstOrDefaultAsync();

                    if (!shortInterval)
                    {
                        if (mostRecent != default && mostRecent.CreatedAt.AddDays(8) >= DateTime.Now)
                        {
                            continue;
                        }
                    }
                    else
                    {
                        if (mostRecent != default &&
                            mostRecent.CreatedAt.AddDays(2) >= DateTime.Now ||
                            mostRecent.CreatedAt.AddDays(8) < DateTime.Now)
                        {
                            continue;
                        }
                    }

                    var failingCourses = new LinkedList <Course>();

                    await foreach (var e in api.StreamUserEnrollments(user.Id, StudentEnrollment.Yield()))
                    {
                        var course = await api.GetCourse(e.CourseId, includes : Api.IndividualLevelCourseIncludes.Term);

                        if (course.Term?.EndAt != null && course.Term.EndAt < DateTime.Now)
                        {
                            continue;
                        }

                        // Concluded enrollments are sometimes reported by the api as active, so we have to try our best
                        // to disregard "active" enrollments in courses from past years.

                        if ("active" != e.EnrollmentState)
                        {
                            continue;
                        }

                        // Disregard if the course is unpublished...
                        if ("available" != course.WorkflowState)
                        {
                            continue;
                        }

                        // Disregard if no SIS
                        if (string.IsNullOrWhiteSpace(course.SisCourseId))
                        {
                            continue;
                        }

                        // Disregard if the SIS doesn't follow the standard course format with the current year
                        var m = CourseSisIdPattern.Match(course.SisCourseId);
                        if (!m.Success || m.Groups["year"].Value != sisIdYear)
                        {
                            continue;
                        }

                        var subaccountNames = await GetAccountSet(api, await api.GetAccount(course.AccountId))
                                              .ThenApply(s => s.Select(a => a?.Name));

                        if (!subaccountsFilter.Intersect(subaccountNames).Any())
                        {
                            continue;
                        }

                        var grade = e.Grades?.CurrentGrade;
                        var score = e.Grades?.CurrentScore;

                        if (string.IsNullOrWhiteSpace(grade) && string.IsNullOrWhiteSpace(score))
                        {
                            failingCourses.AddLast(course);
                        }
                        else if ("F" == grade)
                        {
                            failingCourses.AddLast(course);
                        }
                        else if (double.TryParse(score, out var nScore) && nScore <= 66)
                        {
                            failingCourses.AddLast(course);
                        }
                    }

                    if (!failingCourses.Any())
                    {
                        continue;
                    }

                    var dtStr = mostRecent?.CreatedAt.ToString("yyyy-MM-dd'T'HH':'mm':'ssK") ?? "never";
                    var data  = await dataDb.GetTruancyInfo(sis);

                    var failingCourseNames = "\"" + string.Join(";", failingCourses.Select(c => c.Name).Distinct()) + "\"";

                    sb.Append($"\n{user.Id},{sis},{dtStr},{data.FirstName},{data.LastName},{data.Grade},{data.Phone}," +
                              $"{data.Cell},{data.Address},{data.City},{data.State},{data.Zip},{data.MotherName}," +
                              $"{data.FatherName},{data.GuardianName},{data.MotherEmail},{data.FatherEmail}," +
                              $"{data.GuardianEmail},{data.MotherCell},{data.FatherCell},{data.GuardianCell}," +
                              $"{data.DateOfBirth},{data.EntryDate},{data.Gender},{data.School}," +
                              $"{data.ResidenceDistrictName},{data.Age},{data.Ethnicity},{data.Language}," +
                              $"\"{data.GuardianRelationship}\",{data.HasSped},{data.Has504},{failingCourseNames}");
                } catch (Exception e) {
                    Console.WriteLine($"Warning: exception during user with sis `{sis}`\n{e}");
                }
            }

            File.WriteAllText(outPath, sb.ToString());
            Console.WriteLine($"Wrote report to {outPath} at {DateTime.Now:HH':'mm':'ss}");
        }