using Azure.Cosmos; using Azure.Messaging.ServiceBus; using TEAMModelOS.SDK.DI; using TEAMModelOS.SDK.Extension; using TEAMModelOS.SDK; using TEAMModelOS.SDK.Models; using TEAMModelOS.SDK.Models.Service; using HTEXLib.COMM.Helpers; using ClouDASLibx; using System.Text.Json; using System.Collections.Generic; using System.Threading.Tasks; using System; using System.Linq; using TEAMModelOS.SDK.Models.Service.BI; using Azure.Storage.Blobs.Models; using System.IO; using System.Text; using System.Text.Json.Nodes; using Newtonsoft.Json.Linq; using TEAMModelOS.SDK.Models.Cosmos.Student; using HTEXLib.Helpers.ShapeHelpers; using TEAMModelOS.Models.Dto; using TEAMModelOS.SDK.Models.Cosmos.Common; using DocumentFormat.OpenXml.Office2010.Excel; using DocumentFormat.OpenXml.Drawing.Charts; using DocumentFormat.OpenXml.Vml; using System.Net.Http; using Newtonsoft.Json; using System.Net; using System.Security.Policy; using Microsoft.Extensions.Configuration; using DocumentFormat.OpenXml.EMMA; using Microsoft.Extensions.Options; using TEAMModelOS.Models; using Microsoft.AspNetCore.Razor.TagHelpers; using HtmlAgilityPack; using Azure.Storage.Blobs; using System.ComponentModel; namespace TEAMModelOS.FunctionV4 { public class TriggerExam { public static async Task Trigger(CoreAPIHttpService _coreAPIHttpService, AzureCosmosFactory _azureCosmos, AzureServiceBusFactory _serviceBus, AzureStorageFactory _azureStorage, DingDing _dingDing, CosmosClient client, JsonElement input, TriggerData data, IHttpClientFactory _httpClient, IConfiguration _configuration, HttpTrigger _httpTrigger,AzureRedisFactory _azureRedis) { List examClassResults = new(); List examSubjects = new(); try { if ((data.status != null && data.status.Value == 404)) { await client.GetContainer(Constant.TEAMModelOS, "Common").DeleteItemStreamAsync(data.id, new PartitionKey(data.code)); ActivityList activity = input.ToObject(); //await IESActivityService.DeleteActivity(_coreAPIHttpService, client, _dingDing, activity); var table_cancel = _azureStorage.GetCloudTableClient().GetTableReference("ChangeRecord"); List records = await table_cancel.FindListByDict(new Dictionary() { { "RowKey", data.id } }); foreach (var record in records) { try { await table_cancel.DeleteSingle(record.PartitionKey, record.RowKey); await _serviceBus.GetServiceBusClient().CancelMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), record.sequenceNumber); } catch (Exception) { continue; } } await BIStats.SetTypeAddStats(client, _dingDing, data.school, "Exam", -1, careDate: data.startTime);//BI统计增/减量 return; } ExamInfo info = await client.GetContainer(Constant.TEAMModelOS, "Common").ReadItemAsync(data.id, new Azure.Cosmos.PartitionKey($"{data.code}")); if (info != null) { if (info.scope.Equals("teacher", StringComparison.OrdinalIgnoreCase) || info.scope.Equals("private", StringComparison.OrdinalIgnoreCase)) { await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIterator(queryText: $"select value(c) from c where c.examId = '{info.id}'", requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"ExamClassResult-{info.creatorId}") })) { using var json = await JsonDocument.ParseAsync(item.ContentStream); if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0) { foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray()) { examClassResults.Add(obj.ToObject()); } } } } else { await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIterator(queryText: $"select value(c) from c where c.examId = '{info.id}'", requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"ExamClassResult-{data.school}") })) { using var json = await JsonDocument.ParseAsync(item.ContentStream); if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0) { foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray()) { examClassResults.Add(obj.ToObject()); } } } } var table = _azureStorage.GetCloudTableClient().GetTableReference("ChangeRecord"); string PartitionKey = string.Format("{0}{1}{2}", info.code, "-", info.progress); List records = await table.FindListByDict(new Dictionary() { { "RowKey", data.id }, { "PartitionKey", PartitionKey } }); //处理科目信息 List sub = new List(); School sc = new(); if (!string.IsNullOrEmpty(info.school) && !info.school.Equals("SYSTEM_NO_SCHOOL")) { sc = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync(info.school, new Azure.Cosmos.PartitionKey("Base")); } foreach (ExamSubject subject in info.subjects) { sub.Add(subject.id); } //整合名单 List classes = ExamService.getClasses(info.classes, info.stuLists); //ChangeRecord record = await client.GetContainer(Constant.TEAMModelOS, "Common").ReadItemAsync(input.Id, new Azure.Cosmos.PartitionKey($"{info.progress}")); switch (info.progress) { case "pending": var message = new ServiceBusMessage(new { id = data.id, progress = "going", code = data.code }.ToJsonString()); message.ApplicationProperties.Add("name", "Exam"); if (records.Count > 0) { try { await _serviceBus.GetServiceBusClient().CancelMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), records[0].sequenceNumber); } catch (Exception) { } long start = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), message, DateTimeOffset.FromUnixTimeMilliseconds(data.startTime)); records[0].sequenceNumber = start; await table.SaveOrUpdate(records[0]); //await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(record, record.id, new Azure.Cosmos.PartitionKey($"{record.code}")); } else { long start = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), message, DateTimeOffset.FromUnixTimeMilliseconds(data.startTime)); //string pk = String.Format("{0}{1}{2}", info.code, "-", "pending"); ChangeRecord changeRecord = new ChangeRecord { RowKey = data.id, PartitionKey = PartitionKey, sequenceNumber = start, msgId = message.MessageId }; await table.Save(changeRecord); //await client.GetContainer(Constant.TEAMModelOS, "Common").CreateItemAsync(changeRecord, new Azure.Cosmos.PartitionKey($"{changeRecord.code}")); } break; case "going": try { //向学生或醍摩豆账号发起通知 #region //Notice notice = new Notice() //{ // msgId = info.id, // creation = info.startTime, // expire = info.endTime, // creatorId = info.creatorId, // stuids = studentss, // tmdids = tmdids, // type = "notice",//评测参加通知 // priority = "normal", // school = info.school, // scope = info.scope, // //data = new { }.ToJsonString() // body = new Body { sid = info.id, scode = info.code, spk = info.pk, biztype = "exam-join" } //}; //var messageBlob = new ServiceBusMessage(notice.ToJsonString()); //messageBlob.ApplicationProperties.Add("name", "Notice"); //await _serviceBus.GetServiceBusClient().SendMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), messageBlob); #endregion //List classes = new List(); if (info!=null && info.scope.Equals("private")) { await SystemService.RecordAccumulateData(_azureRedis, _dingDing, new SDK.Models.Dtos.Accumulate { client="web", count=1, id=info.id, key="exam-going", name=info.name, scope="teacher", target=info.creatorId }); } //处理活动中间件 List members = await Activity(_coreAPIHttpService, info, classes, client, _dingDing, sub, examClassResults); if (examClassResults.Count == 0) { List>> tasks = new List>>(); foreach (string cla in classes) { int m = 0; RGroupList rGroup = new(); rGroup = members.Where(m => m.id.Equals(cla)).FirstOrDefault(); foreach (ExamSubject subject in info.subjects) { string classCode = string.Empty; string cname = string.Empty; if (string.IsNullOrEmpty(info.school) || !info.scope.Equals("school", StringComparison.OrdinalIgnoreCase)) { classCode = "ExamClassResult-" + info.creatorId; } else { classCode = "ExamClassResult-" + info.school; } cname = members.Where(m => m.id.Equals(cla)).FirstOrDefault()?.name; // 预处理活动中间件信息 ExamClassResult result = new() { code = classCode, examId = info.id, id = Guid.NewGuid().ToString(), subjectId = subject.id, year = info.year, scope = info.scope }; result.info.id = cla; result.info.name = cname; List ans = new(); List> anses = new(); List> marks = new(); List ansPoint = new(); List<(string sId, string scode)> ids = new(); foreach (double p in info.papers[m].point) { //Details details = new Details(); //ans.Add(new List()); anses.Add(new List()); marks.Add(new List
()); ansPoint.Add(-1); } var sresponse = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync(cla, new Azure.Cosmos.PartitionKey($"Class-{info.school}")); if (sresponse.Status == 200) { using var json = await JsonDocument.ParseAsync(sresponse.ContentStream); Class classroom = json.ToObject(); foreach (SDK.Models.Period period in sc.period) { if (period.id.Equals(classroom.periodId)) { foreach (Semester semester in period.semesters) { if (semester.start == 1) { int year = DateTimeOffset.UtcNow.Year; int month = DateTimeOffset.UtcNow.Month; int day = DateTimeOffset.UtcNow.Day; int time = 0; if (month == semester.month) { time = day >= semester.day ? 0 : 1; } else { time = month > semester.month ? 0 : 1; } int eyear = year - time; result.gradeId = (eyear - classroom.year).ToString(); } } } } //result.info.id = classroom.id; //result.info.name = classroom.name; //result.gradeId = classroom.year.ToString(); //处理班级人数 /* await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryStreamIterator(queryText: $"select c.id from c where c.classId = '{classroom.id}'", requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"Base-{info.school}") })) { using var json_stu = await JsonDocument.ParseAsync(item.ContentStream); if (json_stu.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0) { var accounts = json_stu.RootElement.GetProperty("Documents").EnumerateArray(); while (accounts.MoveNext()) { JsonElement account = accounts.Current; ids.Add(account.GetProperty("id").GetString()); } } }*/ } /*if (info.scope.Equals("private", StringComparison.OrdinalIgnoreCase)) { var stuResponse = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemStreamAsync(cla, new Azure.Cosmos.PartitionKey($"GroupList")); if (stuResponse.Status == 200) { using var json = await JsonDocument.ParseAsync(stuResponse.ContentStream); GroupList stuList = json.ToObject(); //result.info.id = stuList.id; result.info.name = stuList.name; //处理发布对象为自选名单(个人) foreach (Member stus in stuList.members) { if (!ids.Contains(stus.id)) { ids.Add(stus.id); } } } } else { var stuResponse = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync(cla, new Azure.Cosmos.PartitionKey($"GroupList-{info.school}")); if (stuResponse.Status == 200) { using var json = await JsonDocument.ParseAsync(stuResponse.ContentStream); GroupList stuList = json.ToObject(); //result.info.id = stuList.id; result.info.name = stuList.name; //处理发布对象为自选名单(校本) foreach (Member stus in stuList.members) { if (!ids.Contains(stus.id)) { ids.Add(stus.id); } } } }*/ foreach (RMember member in rGroup.members) { ids.Add((member.id, member.code)); } //ids = members.Where(c => c.id.Equals(cla)).SelectMany(m => m.members).Select(g => g.id).ToList(); List blobs = new(); if (info.qamode == 2) { blobs = info.papers.Where(c => c.subjectId.Equals(subject.id)).Select(c => c.blob).ToList(); } foreach (var (sId, scode) in ids) { result.mark.Add(marks); result.studentIds.Add(sId); //result.scIds.Add(scode ?? ""); result.studentAnswers.Add(ans); result.studentScores.Add(ansPoint); result.ans.Add(anses); result.sum.Add(0); result.status.Add(1); if (info.qamode == 2) { result.paper.Add(blobs[new Random().Next(blobs.Count)]); } } //result.progress = info.progress; result.school = info.school; m++; tasks.Add(client.GetContainer(Constant.TEAMModelOS, "Common").CreateItemAsync(result, new PartitionKey($"{result.code}"))); } } await tasks.TaskPage(10); } else { //处理单科结算时科目与试卷信息匹配的问题 int gno = 0; foreach (ExamSubject subject in info.subjects) { if (subject.classCount == classes.Count) { await createClassResultAsync(info, examClassResults, subject, gno, _azureCosmos, _dingDing, _azureStorage, _httpClient, _configuration); } gno++; } var isScore = examClassResults.SelectMany(e => e.studentScores).ToList().Exists(c => c.Contains(-1)); int newStatus = 0; if (!isScore) { newStatus = 1; } Settlement settlement = await getMore(client, info); long nowTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); //判断评分状态是否发生变化,便于实时的更新评测基本信息 if (info.average != settlement.score || info.sStatus != newStatus) { info.sRate = settlement.rate; info.sStatus = newStatus; info.updateTime = nowTime; info.average = settlement.score; await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(info, info.id, new PartitionKey(info.code)); } } } catch (Exception e) { await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-{info.id}-评测going状态异常{e.Message}\n{e.StackTrace}\n", GroupNames.醍摩豆服務運維群組); } finally { try { /*//新增serviceBus 消息释放后处理table string pkey = string.Format("{0}{1}{2}", info.code, "-", "pending"); await table.DeleteSingle(pkey, data.id);*/ // 发送信息通知 var messageEnd = new ServiceBusMessage(new { id = data.id, progress = "finish", code = data.code }.ToJsonString()); messageEnd.ApplicationProperties.Add("name", "Exam"); if (records.Count > 0) { long end = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), messageEnd, DateTimeOffset.FromUnixTimeMilliseconds(data.endTime)); await _serviceBus.GetServiceBusClient().CancelMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), records[0].sequenceNumber); records[0].sequenceNumber = end; await table.SaveOrUpdate(records[0]); //await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(record, record.id, new Azure.Cosmos.PartitionKey($"{record.code}")); } else { long end = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), messageEnd, DateTimeOffset.FromUnixTimeMilliseconds(data.endTime)); //string pk = String.Format("{0}{1}{2}", info.code, "-", "going"); ChangeRecord changeRecord = new ChangeRecord { RowKey = data.id, PartitionKey = PartitionKey, sequenceNumber = end, msgId = messageEnd.MessageId }; await table.Save(changeRecord); //await client.GetContainer(Constant.TEAMModelOS, "Common").CreateItemAsync(changeRecord, new Azure.Cosmos.PartitionKey($"{changeRecord.code}")); } } catch (Exception e) { await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-{info.id}-ChangeRecord{e.Message}\n{e.StackTrace}\n", GroupNames.醍摩豆服務運維群組); } } break; case "finish": int fno = 0; try { //await SetLearnRecordContent(info, data, _azureStorage, _azureCosmos); //用来判定是否完成评分 //bool isScore = true; await resultStatus(client, examClassResults); await Activity(_coreAPIHttpService, info, classes, client, _dingDing, sub, examClassResults); foreach (ExamSubject subject in info.subjects) { await createClassResultAsync(info, examClassResults, subject, fno, _azureCosmos, _dingDing, _azureStorage, _httpClient, _configuration); fno++; } Settlement settlement = await getMore(client, info); var isScore = examClassResults.SelectMany(e => e.studentScores).ToList().Exists(c => c.Contains(-1)); int newStatus = 0; if (!isScore) { newStatus = 1; } //判断均分是否发生变化,便于实时的更新评测基本信息 if (info.sRate != settlement.rate || info.average != settlement.score || info.sStatus != newStatus || info.qRate != settlement.qrate) { info.sRate = settlement.rate; info.average = settlement.score; info.sStatus = newStatus; info.lostStu = settlement.stus; info.stuCount = settlement.total; info.qRate = settlement.qrate; //新增数据推送 obj => Portrait Portrait portrait = new() { schoolCode = info.school, periodId = info.period.id, subjectId = "subject_intelligence" }; //作答合格率 var sta = examClassResults.SelectMany(x => x.status).ToList(); /* var stus = examClassResults.SelectMany(x => x.studentIds).ToList(); var stuScores = examClassResults.SelectMany(x => x.studentScores).ToList();*/ var ansCount = sta.Where(x => x == 0).ToList(); var persent = ansCount.Count * 1.0 / sta.Count * 100; var period = sc.period.Where(x => x.id.Equals(info.period.id)).FirstOrDefault(); List subjects = info.subjects.Select(x => x.id).ToList(); //获取学期信息 if (null != period && persent >= 60 && !subjects.Contains("subject_painting") && !subjects.Contains("subject_music")) { var (currSemester, studyYear, currSemesterDate, date, nextSemester) = SchoolService.GetSemester(period, info.startTime); //获取学生信息 (List rmembers, List groups) = await GroupListService.GetMemberByListids(_coreAPIHttpService, client, _dingDing, info.classes, info.school); foreach (var member in rmembers) { if (info.lostStu.Contains(member.id)) { continue; } PortraitStudent student = new() { studentId = member.id, name = member.name, classId = member.classId }; var ec = examClassResults.Where(c => c.studentIds.Contains(member.id)).ToList(); double scores = 0; List<(string subjectId, double score)> subScore = new(); foreach (var eclass in ec) { int index_stu = eclass.studentIds.IndexOf(member.id); scores += eclass.sum[index_stu]; subScore.Add((eclass.subjectId, eclass.sum[index_stu])); } SemesterData semesterData = new() { examName = info.name, examId = info.id, examDate = info.startTime, examType = info.examType?.name, year = info.year, semesterId = currSemester.id, totalScore = info.papers.SelectMany(x => x.point).Sum(), sumScore = scores, excellenceRate = 0, passRate = 0, }; int index = 0; foreach (var sj in info.subjects) { ItemScore item = new() { id = sj.id, name = sj.name, score = subScore.Where(c => c.subjectId.Equals(sj.id)).FirstOrDefault().score, time = info.startTime, totalScore = info.papers[index].point.Sum(), type = sj.id }; index++; semesterData.itemScore.Add(item); } student.semesterData.Add(semesterData); portrait.students.Add(student); } string location = $"{Environment.GetEnvironmentVariable("Option:Location")}"; var (status, json) = await _httpTrigger.RequestHttpTrigger(portrait, location, "upsert-student-portrait"); } //PortraitStudent student = new(); //处理试卷活动结束统计账户信息 List idList = await GroupListService.GetFinishMemberInfo(_coreAPIHttpService, client, _dingDing, info.school, info.classes, info.stuLists, null); info.staffIds = idList; await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(info, info.id, new Azure.Cosmos.PartitionKey(info.code)); } await SetLearnRecordContent(info, data, _azureStorage, _azureCosmos); } catch (Exception e) { await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-{info.id}-评测finish状态异常{e.Message}\n{e.StackTrace}\n", GroupNames.醍摩豆服務運維群組); } /* finally { //新增serviceBus 消息释放后处理table string pk = string.Format("{0}{1}{2}", info.code, "-", "going"); await table.DeleteSingle(pk, data.id); }*/ break; } } } catch (CosmosException e) { await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-CosmosDB异常{e.Message}\n{e.StackTrace}\n{e.Status}", GroupNames.醍摩豆服務運維群組); } catch (Exception e) { await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-评测结算异常{e.Message}\n{e.StackTrace}\n", GroupNames.醍摩豆服務運維群組); } } private static async Task SetLearnRecordContent(ExamInfo info, TriggerData data, AzureStorageFactory _azureStorage, AzureCosmosFactory _azureCosmos) { try { if (info.papers.Count > 0) { for (int i = 0; i < info.papers.Count; i++) {// 每一個科目的試卷 string rootName = ""; //if (info.school == "SYSTEM_NO_SCHOOL") if (info.scope == "private") {// 未入校老師的評量 rootName = info.creatorId; } else {// 如果有學校代碼 rootName = info.school; } if (_azureStorage.GetBlobContainerClient(rootName).GetBlobClient($"{info.papers[i].blob}/index.json").Exists()) {// 如果試卷的blob存在,再開始 BlobDownloadResult paperblobDownload = await _azureStorage.GetBlobContainerClient(rootName).GetBlobClient($"{info.papers[i].blob}/index.json").DownloadContentAsync(); PaperIndex paperIndex = paperblobDownload.Content.ToObjectFromJson(); List learnRecordItems = new(); StringBuilder sbsql = new StringBuilder($" SELECT * FROM c WHERE c.examId = '{info.id}' and c.pk = 'ExamClassResult' and c.subjectId = '{info.subjects[i].id}' "); #region === 開始拼裝資料 === if (info.classes.Count > 0 || info.stuLists.Count > 0) { // 組合SQL if (info.classes.Count > 0) {// 按照classes取ans.json sbsql.Append($" and ARRAY_CONTAINS({System.Text.Json.JsonSerializer.Serialize(info.classes)}, c.info.id)"); } else if (info.stuLists.Count > 0) { sbsql.Append($" and ARRAY_CONTAINS({System.Text.Json.JsonSerializer.Serialize(info.stuLists)}, c.info.id)"); } // 取學生答案及學生名單 var client = _azureCosmos.GetCosmosClient(); Dictionary> stuDic = new Dictionary>(); if (!string.IsNullOrWhiteSpace(info.school) && !stuDic.ContainsKey(info.school)) { stuDic.Add(info.school, new List()); } List studentAnswersList = new List(); List stuListForSql = new List(); await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Common).GetItemQueryIterator(queryText: sbsql.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"ExamClassResult-{rootName}") })) { studentAnswersList.Add(item); stuListForSql = stuListForSql.Union(item.studentIds).ToList(); } //取得學校的學生名單 List stuOpenIdList = new List(); if(!string.IsNullOrWhiteSpace(info.school)) { StringBuilder stusql = new StringBuilder($"SELECT VALUE c.id FROM c WHERE c.pk = 'Base' AND ARRAY_CONTAINS({System.Text.Json.JsonSerializer.Serialize(stuListForSql)}, c.id)"); await foreach (string item in client.GetContainer(Constant.TEAMModelOS, Constant.Student).GetItemQueryIterator(queryText: stusql.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base-{info.school}") })) { stuDic[info.school].Add(item); } } // 按照取出的學生答案blob 對答案 組合資料 foreach (var studentAnswers in studentAnswersList) {// 每一個班級 foreach (var studentAnswersBlob in studentAnswers.studentAnswers) {// 每一個學生 if (studentAnswersBlob.Count > 0) {// 有學生的答案才開始 if (_azureStorage.GetBlobContainerClient(rootName).GetBlobClient($"/exam/{studentAnswersBlob[0]}").Exists()) {// 如果blob存在才開始 BlobDownloadResult ansblobDownload = await _azureStorage.GetBlobContainerClient(rootName).GetBlobClient($"/exam/{studentAnswersBlob[0]}").DownloadContentAsync(); List> ansList = JsonConvert.DeserializeObject>>(ansblobDownload.Content.ToString()); // 從blob路徑取出學生id string[] arr = studentAnswersBlob[0].Split('/'); string stuid = arr[arr.Length - 2]; #region ===StartExam 開始課堂=== LearnRecordItem LRItem_StartExam = new(); LRItem_StartExam.verb = "StartExam"; LRItem_StartExam.actor = getStuId(info.school, stuid, stuDic); LRItem_StartExam.time = info.createTime; LRItem_StartExam.ID = info.id; LRItem_StartExam.Desc = info.name; LRItem_StartExam.Correct = null; LRItem_StartExam.Choices = null; LRItem_StartExam.ExamQuesQty = info.papers[i].point.Count; LRItem_StartExam.TotalScore = 0; foreach (var pointItem in info.papers[i].point) { LRItem_StartExam.TotalScore = LRItem_StartExam.TotalScore + pointItem; } LRItem_StartExam.Success = null; learnRecordItems.Add(LRItem_StartExam); #endregion #region ===評量題目=== for (int j = 0; j < ansList.Count; j++) { #region === 判斷題型 === // 這五種題型才記錄 if (paperIndex.slides[j].type == "single" || paperIndex.slides[j].type == "multiple" || paperIndex.slides[j].type == "judge" || paperIndex.slides[j].type == "complete" || paperIndex.slides[j].type == "subjective") { LearnRecordItem learnRecordItem = new(); // 單選 if (paperIndex.slides[j].type == "single") { learnRecordItem.verb = "AnsSingle"; } // 複選 if (paperIndex.slides[j].type == "multiple") { learnRecordItem.verb = "AnsMultiple"; } // 是非 if (paperIndex.slides[j].type == "judge") { learnRecordItem.verb = "AnsJudge"; } // 填充 if (paperIndex.slides[j].type == "complete") { learnRecordItem.verb = "AnsComplete"; } // 問答 if (paperIndex.slides[j].type == "subjective") { learnRecordItem.verb = "AnsSubjective"; } learnRecordItem.actor = getStuId(info.school, stuid, stuDic); learnRecordItem.time = data.endTime; string[] arrurlsingle = paperIndex.slides[j].url.Split('.'); learnRecordItem.ID = arrurlsingle[0]; #region === 設定Correct Choices Desc=== if (_azureStorage.GetBlobContainerClient(rootName).GetBlobClient($"{info.papers[i].blob}/{paperIndex.slides[j].url}").Exists()) { BlobDownloadResult itemblobDownload = await _azureStorage.GetBlobContainerClient(rootName).GetBlobClient($"{info.papers[i].blob}/{paperIndex.slides[j].url}").DownloadContentAsync(); QuestionData questionData = itemblobDownload.Content.ToObjectFromJson(); setCorrectChoices(questionData, learnRecordItem); // 使用HtmlAgilityPack解析HTML HtmlDocument doc = new HtmlDocument(); doc.LoadHtml(questionData.item[0].question); // 先刪除所有標籤 foreach (var imgNode in doc.DocumentNode.Descendants("img").ToArray()) { imgNode.Remove(); } // 再取純文字 learnRecordItem.Desc = doc.DocumentNode.InnerText; } #endregion learnRecordItem.ExamQuesQty = null; learnRecordItem.TotalScore = null; learnRecordItem.Points = paperIndex.slides[j].scoring == null ? new List() : paperIndex.slides[j].scoring.knowledge; //比對答案 learnRecordItem.Success = paperIndex.slides[j].scoring == null ? null : (paperIndex.slides[j].scoring.ans.SequenceEqual(ansList[j]) ? true : false); learnRecordItems.Add(learnRecordItem); } #endregion } #endregion #region ===EndExam 結束課堂=== LearnRecordItem LRItem_EndExam = new(); LRItem_EndExam.verb = "EndExam"; LRItem_EndExam.actor = getStuId(info.school, stuid, stuDic); LRItem_EndExam.time = info.endTime; LRItem_EndExam.ID = info.id; LRItem_EndExam.Desc = info.name; LRItem_EndExam.Correct = null; LRItem_EndExam.Choices = null; LRItem_EndExam.ExamQuesQty = info.papers[i].point.Count; LRItem_EndExam.TotalScore = 0; foreach (var pointItem in info.papers[i].point) { LRItem_EndExam.TotalScore = LRItem_EndExam.TotalScore + pointItem; } LRItem_EndExam.Success = null; learnRecordItems.Add(LRItem_EndExam); #endregion } } } } } #endregion if (learnRecordItems.Count > 0) { #region 寫入blob // 容器名称 string containerName = "twmoeld"; // 新文件的名称 string blobName = $"/{DateTime.Now.ToString("yyyyMMdd")}/{info.id}.json"; // 要上传的文件内容 string fileContent = Newtonsoft.Json.JsonConvert.SerializeObject(learnRecordItems); // 获取容器引用 BlobContainerClient containerClient = _azureStorage.GetBlobContainerClient(containerName); // 获取 Blob 客户端 BlobClient blobClient = containerClient.GetBlobClient(blobName); // 将文件内容上传到 Blob using (MemoryStream stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(fileContent))) { await blobClient.UploadAsync(stream, true); } #endregion } } } } } catch (Exception ex) { } } /// /// 取得學習紀錄的actor /// /// /// /// /// private static string getStuId(string school, string stuid, Dictionary> stuDic) { if (!string.IsNullOrWhiteSpace(school) && stuDic.ContainsKey(school) && stuDic[school].Contains(stuid)) {// 校內帳號用組合的 "Base-hbgl,473891247381" return $"Base-{school.Trim()},{stuid}"; } else {// 未入校老師的評量 return stuid; } } private static void setCorrectChoices(QuestionData questionData, LearnRecordItem learnRecordItem) { #region === Correct Choices === #region === 是非題邏輯 === if (questionData.exercise.type == "judge" && questionData.exercise.answer.Count > 0) {// 如果是是非題 正確答案要用true false的方式設定 if (questionData.exercise.answer[0] == "A") { learnRecordItem.Correct = new string[] { "true" }; } else { learnRecordItem.Correct = new string[] { "false" }; } } else {// 防呆 去html標籤 if (questionData.exercise.answer.Count > 0) { List anslist = new(); foreach (var answer in questionData.exercise.answer) { // 使用HtmlAgilityPack解析HTML HtmlDocument doc = new HtmlDocument(); doc.LoadHtml(answer); // 再取純文字 anslist.Add(doc.DocumentNode.InnerText); } learnRecordItem.Correct = anslist; } else { learnRecordItem.Correct = questionData.exercise.answer; } } #endregion if (questionData.item[0].option.Count > 0) {// 如果有選項資料 記錄起來 foreach (var option in questionData.item[0].option) { ChoicesItem item = new(); item.id = option.code; if (questionData.exercise.type == "judge") {// 如果是是非題 固定選項 if (option.code == "A") { item.description.zhTW = "是"; } else { item.description.zhTW = "否"; } } else { if (string.IsNullOrWhiteSpace(option.value)) { item.description.zhTW = $"選項{option.code}"; } else { // 使用HtmlAgilityPack解析HTML HtmlDocument doc = new HtmlDocument(); doc.LoadHtml(option.value); // 再取純文字 item.description.zhTW = doc.DocumentNode.InnerText; } } learnRecordItem.Choices.Add(item); } } #endregion } public static async Task resultStatus(CosmosClient client, List examClassResults) { List>> tasks = new(); //结算单科单班的标准差和平均分 foreach (ExamClassResult classResult in examClassResults) { //标记单科单班总得分 double subScore = 0; //标准差 double sPowSum = 0; List newSumScore = new List(); List effectiveScore = new List(); int lostStu = classResult.status.Where(c => c == 1).ToList().Count; var scount = classResult.studentIds.Count - lostStu; int index = 0; if (classResult.status.Count > 0) { foreach (var sta in classResult.status) { if (sta == 1) { for (int i = 0; i < classResult.studentScores[index].Count; i++) { if (classResult.studentScores[index][i] == -1) { classResult.studentScores[index][i] = 0; } } } else if (sta == 0) { //var ans = classResult.studentAnswers[index]; //该学生缺考,历史数据的判定 //if (ans.Count == 0) //{ for (int i = 0; i < classResult.studentScores[index].Count; i++) { if (classResult.studentScores[index][i] == -1) { classResult.studentScores[index][i] = 0; } } /*if (ans.Count == 0) { classResult.status[index] = 1; } */ //} } index++; } } else { List sta = new(); foreach (var id in classResult.studentIds) { sta.Add(0); } classResult.status = sta; foreach (var ans in classResult.studentAnswers) { //该学生缺考,历史数据的判定 if (ans.Count == 0) { for (int i = 0; i < classResult.studentScores[index].Count; i++) { if (classResult.studentScores[index][i] == -1) { classResult.studentScores[index][i] = 0; } } classResult.status[index] = 1; } index++; } } int sIndex = 0; foreach (List sc in classResult.studentScores) { List newSc = new List(); foreach (double ssc in sc) { if (ssc == -1) { newSc.Add(0); } else { newSc.Add(ssc); } } double nc = Math.Round(newSc.Sum(), 2); if (classResult.status[sIndex] == 0) { effectiveScore.Add(nc); }; newSumScore.Add(nc); subScore += nc; classResult.sum[sIndex] = nc; sIndex++; } double rateScore = scount > 0 ? Math.Round(subScore * 1.0 / scount, 2) : 0; //有效分数 foreach (double scs in effectiveScore) { sPowSum += Math.Pow(scs - rateScore, 2); } classResult.standard = Math.Round(scount > 0 ? Math.Pow(sPowSum / scount, 0.5) : 0, 2); classResult.average = scount > 0 ? Math.Round(subScore / scount, 2) : 0; classResult.progress = true; tasks.Add(client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(classResult, classResult.id, new PartitionKey($"{classResult.code}"))); } await Task.WhenAll(tasks); } public static async Task getMore(CosmosClient client, ExamInfo info) { //计算单次考试简易统计信息 Settlement settlement = new(); List examResults = new(); await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryIterator( queryText: $"select value(c) from c where c.examId = '{info.id}'", requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"ExamResult-{info.id}") })) { examResults.Add(item); } if (examResults.Count > 0) { //记录某次考试所有学生得分总分 double score = 0; double allScore = 0; int stuCount = 0; //标准差 double powSum = 0; List losStu = new List(); //当前完成考试得人数 int total = examResults[0].studentIds.Distinct().Count(); //先与第一个值取并集 losStu = losStu.Union(examResults[0].lostStus).ToList(); foreach (ExamResult examResult in examResults) { //取交集 losStu = losStu.Intersect(examResult.lostStus).ToList(); } //处理缺考的学生 List stus = examResults[0].studentIds.Except(losStu).ToList(); foreach (ExamResult examResult in examResults) { foreach (var id in stus) { int index = examResult.studentIds.IndexOf(id); score += examResult.studentScores[index].Sum(); } } stuCount = total - losStu.Count; double NewsRateScore = stuCount > 0 ? Math.Round(score * 1.0 / stuCount, 2) : 0; foreach (PaperSimple simple in info.papers) { allScore += simple.point.Sum(); } //计算全科标准差 foreach (string id in stus) { double sc = 0; foreach (ExamResult result in examResults) { if (result.studentScores.Count > 0) { sc += result.studentScores[result.studentIds.IndexOf(id)].Sum(); } } powSum += Math.Pow(sc - NewsRateScore, 2); } info.standard = Math.Round(total - losStu.Count > 0 ? Math.Pow(powSum / (total - losStu.Count), 0.5) : 0, 2); double NewsRate = allScore > 0 ? Math.Round(NewsRateScore / allScore * 100, 2) : 0; double qrate = Math.Round(total > 0 ? losStu.Count * 1.0 / total * 100 : 0, 2); settlement.rate = NewsRate; settlement.score = NewsRateScore; settlement.stus = losStu; settlement.total = total; settlement.qrate = qrate; } return settlement; } //处理全部学生选题计数 public static async Task examRecordCount(ExamInfo info, ExamSubject subject, DingDing _dingDing, int no, ExamResult er, List classResults, AzureCosmosFactory _azureCosmos) { try { List scores = new List(); foreach (List sc in er.studentScores) { scores.Add(sc.Sum()); } //确定高分组 最低分数 //var srowallorder = scores.EnumerateIndexed().OrderByDescending(g => g.Item2); //各生總分排序 //var srowallorder = scores.OrderByDescending(g => g); double[] point = StringHelper.ListTodouble(er.paper.point); double[,] result = StringHelper.ListToDouble(er.studentScores); var cdm = new ClouDASMatrix(result, point); scores.Sort((s1, s2) => { return s2.CompareTo(s1); }); int rhwCount = cdm.SH.Count(); double rhw = cdm.SH.LastOrDefault().Item2; int rhlCount = cdm.SL.Count(); double rhl = cdm.SL.FirstOrDefault().Item2; List phs = new(); List pls = new(); //分别获取高分组低分组人员 foreach (var ph in cdm.SH) { phs.Add(er.studentIds[ph.Item1]); } foreach (var pl in cdm.SL) { pls.Add(er.studentIds[pl.Item1]); } /* if (scores.Count > 3) { rhwCount = Convert.ToInt32(Math.Floor(scores.Count * 0.27)); rhw = rhwCount > 0 ? scores[rhwCount - 1] : 0; rhlCount = Convert.ToInt32(Math.Ceiling(scores.Count * 0.73)); rhl = rhlCount > 0 ? scores[rhlCount] : 0; } else { rhwCount = Convert.ToInt32(Math.Floor(scores.Count * 0.27)); rhw = rhwCount > 0 ? scores[rhwCount] : 0; rhlCount = Convert.ToInt32(Math.Ceiling(scores.Count * 0.73)); rhl = rhlCount > 0 ? scores[rhlCount - 1] : 0; }*/ /*int rhwCount = Convert.ToInt32(Math.Floor(scores.Count * 0.27)); double rhw = rhwCount > 0 ? scores[rhwCount-1] : 0; //var rhww = srowallorder.Take(Convert.ToInt32(Math.Floor(scores.Count * 0.27))); //确定低分组 最高分数 //scores.Sort((s1, s2) => { return s1.CompareTo(s2); }); int rhlCount = Convert.ToInt32(Math.Ceiling(scores.Count * 0.73)); double rhl = rhlCount > 0 ? scores[rhlCount-1] : 0;*/ //var rhll = srowallorder.Skip(Convert.ToInt32(Math.Ceiling(scores.Count * 0.73))); //存放高分组学生ID List phId = new(); List plId = new(); List>> opth = new(); List>> optl = new(); List akp = await knowledgeCount(info, subject, _dingDing, no, classResults, rhwCount, rhw, rhlCount, rhl, _azureCosmos); List afp = await fieldCount(info, subject, _dingDing, no, classResults, rhwCount, rhw, rhlCount, rhl, _azureCosmos); int PHCount = 0; int PLCount = 0; foreach (ExamClassResult classResult in classResults) { if (classResult.subjectId.Equals(subject.id)) { foreach (string id in classResult.studentIds) { int index = classResult.studentIds.IndexOf(id); if (classResult.studentScores.Count > 0) { if (phs.Contains(id)) { if (classResult.ans.Count > 0) { opth.Add(classResult.ans[index]); PHCount++; continue; } } if (pls.Contains(id)) { if (classResult.ans.Count > 0) { optl.Add(classResult.ans[index]); PLCount++; continue; } } } } } } er.phc = getMore(info, no, opth); er.plc = getMore(info, no, optl); er.afp = afp; er.akp = akp; } catch (Exception ex) { await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-{info.id}-评测作答记录结算异常{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組); } } public static async Task> Activity(CoreAPIHttpService _coreAPIHttpService, ExamInfo info, List classes, CosmosClient client, DingDing _dingDing, List sub, List results) { List<(string pId, List gid)> ps = new List<(string pId, List gid)>(); if (info.groupLists.Count > 0) { var group = info.groupLists; foreach (var gp in group) { foreach (KeyValuePair> pp in gp) { ps.Add((pp.Key, pp.Value)); } } } (List tchList, List classLists) = await GroupListService.GetMemberByListids(_coreAPIHttpService, client, _dingDing, classes, info.school, ps); /* var addStudentsCls = tchList.FindAll(x => x.type == 2); var addTmdidsCls = tchList.FindAll(x => x.type == 1); List stuActivities = new List(); List tmdActivities = new List(); var papers = info.papers.GroupBy(c => c.subjectId).Select(x => new { subject = x.Key, blob = x.ToList().Select(z => z.blob).ToList() }); bool flag = info.papers.Exists(c => c.subjectId != null); if (addTmdidsCls.IsNotEmpty()) { addTmdidsCls.ForEach(x => { HashSet classIds = new HashSet(); classLists.ForEach(z => { z.members.ForEach(y => { if (y.id.Equals(x.id) && y.type == 1) { classIds.Add(z.id); } }); }); bool iss = false; //标记学生作答状态 int ts = 0; if (results.Count > 0) { foreach (ExamClassResult exam in results) { int index = exam.studentIds.IndexOf(x.id); if (index != -1) { if (exam.studentAnswers[index].Count > 0) { bool flag = exam.studentScores[index].Exists(x => x == -1); if (!flag) { ts = 1; iss = true; break; } else { ts = 0; } } else { ts = -1; } } } } else { ts = -1; } tmdActivities.Add(new StuActivity { pk = "Activity", id = info.id, code = $"Activity-{x.id}", type = "Exam", name = info.name, source = info.source, startTime = info.startTime, endTime = info.endTime, scode = info.code, scope = info.scope, school = info.school, creatorId = info.creatorId, subjects = sub, blob = null, paper = flag ? papers.Select(c => new { c.subject, blob = c.blob[new Random().Next(c.blob.Count)] }) : "", owner = info.owner, createTime = info.createTime, taskStatus = ts, qamode = info.qamode, ext = new Dictionary() { { "type", info.examType.ToJsonString().ToObject() }, { "subjects", info.subjects.ToJsonString().ToObject() } }, sStatus = iss ? 1 : 0, classIds = classIds.ToList() }); ; }); } if (addStudentsCls.IsNotEmpty()) { addStudentsCls.ForEach(x => { HashSet classIds = new HashSet(); classLists.ForEach(z => { z.members.ForEach(y => { if (y.id.Equals(x.id) && y.code.Equals(info.school) && y.type == 2) { classIds.Add(z.id); } }); }); bool iss = false; //标记学生作答状态 int ts = 0; if (results.Count > 0) { foreach (ExamClassResult exam in results) { int index = exam.studentIds.IndexOf(x.id); if (index != -1) { if (exam.studentAnswers[index].Count > 0) { bool flag = exam.studentScores[index].Exists(x => x == -1); if (!flag) { ts = 1; iss = true; break; } else { ts = 0; } } else { ts = -1; } } } } else { ts = -1; } stuActivities.Add(new StuActivity { pk = "Activity", id = info.id, code = $"Activity-{x.code.Replace("Base-", "")}-{x.id}", type = "Exam", name = info.name, source = info.source, startTime = info.startTime, endTime = info.endTime, scode = info.code, scope = info.scope, school = info.school, creatorId = info.creatorId, subjects = sub, blob = null, paper = flag ? papers.Select(c => new { c.subject, blob = c.blob[new Random().Next(c.blob.Count)] }) : "", owner = info.owner, classIds = classIds.ToList(), createTime = info.createTime, qamode = info.qamode, ext = new Dictionary() { { "type", info.examType.ToJsonString().ToObject() }, { "subjects", info.subjects.ToJsonString().ToObject() } }, taskStatus = ts, sStatus = iss ? 1 : 0, }); }); } await IESActivityService.SaveStuActivity(client, _dingDing, stuActivities, tmdActivities, null); */ return classLists; } public static async Task> knowledgeCount(ExamInfo info, ExamSubject subject, DingDing _dingDing, int no, List classResults, double rhwCount, double rhw, double rhlCount, double rhl, AzureCosmosFactory _azureCosmos) { List akp = new(); try { int phcount = 0; int plcount = 0; //存放并去重知识点 HashSet kname = new HashSet(); if (info.papers[no].knowledge.Count > 0) { info.papers[no].knowledge.ForEach(kno => { kno.ForEach(k => { kname.Add(k); }); }); List knowledgeName = new List(); foreach (string cla in kname) { knowledgeName.Add(cla); } for (int k = 0; k < knowledgeName.Count; k++) { if (null == knowledgeName[k]) { knowledgeName.Remove(knowledgeName[k]); } } foreach (ExamClassResult classResult in classResults) { if (classResult.subjectId.Equals(subject.id)) { //List phc = new List(); List ph = new List(); List pl = new List(); List pc = new List(); List persent = new List(); int lostStu = classResult.status.Where(c => c == 1).ToList().Count; for (int i = 0; i < knowledgeName.Count; i++) { //初始化单个知识点得分 double score = 0; int n = 0; int phCount = 0; int plCount = 0; int pCount = 0; double classScore = 0; foreach (List str in info.papers[no].knowledge) { if (str.Contains(knowledgeName[i])) { var itemPersent = str.Count > 0 ? 1 / Convert.ToDouble(str.Count) : 0; classScore += info.papers[no].point.Count > 0 ? (info.papers[no].point[n] * itemPersent) : 0; foreach (string id in classResult.studentIds) { int index = classResult.studentIds.IndexOf(id); if (classResult.studentScores.Count > 0) { if (classResult.studentScores[index].Count > 0) { double realScore = classResult.studentScores[index][n] == -1 ? 0 : classResult.studentScores[index][n] * itemPersent; score += realScore; //score += classResult.studentScores[index][n] == -1 ? 0 : classResult.studentScores[index][n] * itemPersent; if (classResult.studentScores[index].Sum() >= rhw && phcount < rhwCount) { if (classResult.studentScores[index][n] <= 0) { phCount++; } phcount++; continue; } if (classResult.studentScores[index].Sum() <= rhl && plcount < (info.stuCount - rhlCount)) { if (classResult.studentScores[index][n] <= 0) { plCount++; } plcount++; continue; } if (classResult.studentScores[index][n] <= 0) { pCount++; } } } } } n++; } pc.Add(pCount); ph.Add(phCount); pl.Add(plCount); double per = classResult.studentIds.Count - lostStu > 0 ? Math.Round(score / (classResult.studentIds.Count - lostStu), 2) : 0; persent.Add(classScore > 0 ? Math.Round(per * 1.0 / classScore, 2) : 0); } classResult.phc = ph; classResult.plc = pl; classResult.pc = pc; classResult.krate = persent; } await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(classResult, classResult.id, new Azure.Cosmos.PartitionKey($"{classResult.code}")); } foreach (string k in knowledgeName) { double score = 0; double allScore = 0; int count = 0; int m = 0; foreach (List str in info.papers[no].knowledge) { if (str.Contains(k)) { var itemPersent = str.Count > 0 ? 1 / Convert.ToDouble(str.Count) : 0; allScore += info.papers[no].point.Count > 0 ? info.papers[no].point[m] * itemPersent : 0; } m++; } foreach (ExamClassResult result in classResults) { if (result.subjectId.Equals(subject.id)) { int n = 0; foreach (List str in info.papers[no].knowledge) { if (str.Contains(k)) { var itemPersent = str.Count > 0 ? 1 / Convert.ToDouble(str.Count) : 0; if (result.studentScores.Count > 0) { foreach (List sc in result.studentScores) { double tsc = sc[n] == -1 ? 0 : sc[n]; score += (tsc * itemPersent); } } } n++; } count += result.studentIds.Count; } } double per = count - info.lostStu.Count > 0 ? Math.Round(score / (count - info.lostStu.Count), 2) : 0; akp.Add(allScore > 0 ? Math.Round(per / allScore, 2) : 0); } } return akp; } catch (Exception ex) { await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-{info.id}-评测知识点结算异常{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組); return null; } } public static async Task> fieldCount(ExamInfo info, ExamSubject subject, DingDing _dingDing, int no, List classResults, double rhwCount, double rhw, double rhlCount, double rhl, AzureCosmosFactory _azureCosmos) { List afp = new(); try { int phcount = 0; int plcount = 0; //存放并去重知识点 List knowledgeName = new List() { 1, 2, 3, 4, 5, 6 }; /* knowledgeName.Add(1); knowledgeName.Add(2); knowledgeName.Add(3); knowledgeName.Add(4); knowledgeName.Add(5); knowledgeName.Add(6);*/ foreach (ExamClassResult classResult in classResults) { if (classResult.subjectId.Equals(subject.id)) { //List phc = new List(); List ph = new List(); List pl = new List(); List pc = new List(); List persent = new List(); int lostStu = classResult.status.Where(c => c == 1).ToList().Count; for (int i = 0; i < knowledgeName.Count; i++) { //初始化单个知识点得分 double score = 0; int n = 0; int phCount = 0; int plCount = 0; int pCount = 0; double classScore = 0; if (info.papers[no].field.Count > 0) { foreach (int str in info.papers[no].field) { if (str == knowledgeName[i]) { var itemPersent = 1; classScore += info.papers[no].point.Count > 0 ? info.papers[no].point[n] * itemPersent : 0; foreach (string id in classResult.studentIds) { int index = classResult.studentIds.IndexOf(id); if (classResult.studentScores.Count > 0) { if (classResult.studentScores[index].Count > 0) { score += classResult.studentScores[index][n] == -1 ? 0 : classResult.studentScores[index][n]; if (classResult.studentScores[index].Sum() >= rhw && phcount < rhwCount) { if (classResult.studentScores[index][n] <= 0) { phCount++; } phcount++; continue; } if (classResult.studentScores[index].Sum() <= rhl && plcount < (info.stuCount - rhlCount)) { if (classResult.studentScores[index][n] <= 0) { plCount++; } plcount++; continue; } if (classResult.studentScores[index][n] <= 0) { pCount++; } } } } } n++; } pc.Add(pCount); ph.Add(phCount); pl.Add(plCount); double per = classResult.studentIds.Count - lostStu > 0 ? Math.Round(score / (classResult.studentIds.Count - lostStu), 2) : 0; persent.Add(classScore > 0 ? Math.Round(per / classScore, 2) : 0); } } classResult.fphc = ph; classResult.fplc = pl; classResult.fpc = pc; classResult.frate = persent; } await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(classResult, classResult.id, new Azure.Cosmos.PartitionKey($"{classResult.code}")); } for (int i = 0; i < knowledgeName.Count; i++) { double score = 0; double allScore = 0; int count = 0; int m = 0; foreach (int str in info.papers[no].field) { if (str == knowledgeName[i]) { var itemPersent = 1; allScore += info.papers[no].point.Count > 0 ? info.papers[no].point[m] * itemPersent : 0; } m++; } foreach (ExamClassResult result in classResults) { if (result.subjectId.Equals(subject.id)) { int n = 0; foreach (int str in info.papers[no].field) { var itemPersent = 1; if (str == knowledgeName[i]) { if (result.studentScores.Count > 0) { foreach (List sc in result.studentScores) { double tsc = sc[n] == -1 ? 0 : sc[n]; score += tsc * itemPersent; } } } n++; } count += result.studentIds.Count; } } double per = count - info.lostStu.Count > 0 ? Math.Round(score / (count - info.lostStu.Count), 2) : 0; afp.Add(allScore > 0 ? Math.Round(per / allScore, 2) : 0); } return afp; } catch (Exception ex) { await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-{info.id}-评测认知层次结算异常{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組); return null; } } //处理选题计数 public static List> getMore(ExamInfo info, int no, List>> list) { List> recorde = new List>(); try { for (int i = 0; i < info.papers[no].answers.Count; i++) { if (info.papers[no].answers[i].Count <= 0) { recorde.Add(new Dictionary()); continue; } Dictionary optCount = new Dictionary(); foreach (List> stu in list) { if (stu.Count == info.papers[no].answers.Count) { var item = stu[i]; foreach (string opt in item) { if (optCount.ContainsKey(opt)) { optCount[opt] = optCount[opt] + 1; } else { optCount[opt] = 1; } } } } recorde.Add(optCount); } return recorde; } catch (Exception) { return recorde; } } public static async Task createClassResultAsync(ExamInfo info, List examClassResults, ExamSubject subject, int no, AzureCosmosFactory _azureCosmos, DingDing _dingDing, AzureStorageFactory _azureStorage, IHttpClientFactory _httpClient, IConfiguration _configuration) { //保证试卷信息与科目信息同步 ExamResult result = new ExamResult(); //人数总和 int Count = 0; int m = 0; double score = 0; //标准差 //double powSum = 0; double allScore = info.papers[no].point.Sum(); List classRanges = new List(); List lostStu = new List(); List csRate = new List(); List>> opt = new List>>(); //List>> tasks = new(); foreach (ExamClassResult classResult in examClassResults) { double classSrate = 0; if (classResult.subjectId.Equals(subject.id)) { foreach (List> op in classResult.ans) { opt.Add(op); } //记录缺考学生索引位置 int index_stu = 0; foreach (var ans in classResult.studentAnswers) { if (ans.Count == 0) { if (!lostStu.Contains(classResult.studentIds[index_stu])) { lostStu.Add(classResult.studentIds[index_stu]); } } else { if (classResult.status[index_stu] == 1) { if (!lostStu.Contains(classResult.studentIds[index_stu])) { lostStu.Add(classResult.studentIds[index_stu]); } } } index_stu++; } int index = 0; foreach (List scores in classResult.studentScores) { List newScores = new List(); //int count = 0; if (scores.Count > 0) { foreach (double sc in scores) { newScores.Add(sc > -1 ? sc : 0); } } else { newScores.Add(0); } classSrate += newScores.Sum(); score += newScores.Sum(); result.studentScores.Add(newScores); index++; } //处理班级信息 ClassRange range = new ClassRange(); range.id = classResult.info.id; range.name = classResult.info.name; range.gradeId = classResult.gradeId; List ran = new List(); int stuCount = classResult.studentIds.Count; //todo 与实际的缺考人数比较 int lostStus = classResult.status.Where(c => c == 1).ToList().Count; Count += stuCount; if (m == 0) { ran.Add(0); ran.Add(stuCount - 1); } else { ran.Add(Count - stuCount); ran.Add(Count - 1); } m++; range.range = ran; classRanges.Add(range); //处理学生ID foreach (string id in classResult.studentIds) { result.studentIds.Add(id); } if (allScore > 0) { csRate.Add(classResult.studentIds.Count - lostStus > 0 ? Math.Round(classSrate * 1.0 / (classResult.studentIds.Count - lostStus), 2) : 0 / allScore); } else { csRate.Add(0); } //powSum += Math.Pow(classSrate - result.average, 2); //处理选项计数内容 } //tasks.Add(_azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(classResult, classResult.id, new PartitionKey($"{classResult.code}"))); } //await tasks.TaskPage(10); /*foreach (string id in result.lostStus) { if (!examClassResults[0].studentIds.Contains(id)) { int index = result.lostStus.IndexOf(id); result.lostStus.RemoveAt(index); } }*/ //处理人员变更时被移除的人员 result.lostStus = lostStu; List stuIds = examClassResults.SelectMany(x => x.studentIds).ToList(); if (result.lostStus.Count > 0) { for (int i = 0; i < result.lostStus.Count; i++) { if (!stuIds.Contains(result.lostStus[i])) { result.lostStus.RemoveAt(i); } } } result.record = getMore(info, no, opt); result.average = result.studentIds.Count - result.lostStus.Count > 0 ? Math.Round(score * 1.0 / (result.studentIds.Count - result.lostStus.Count), 2) : 0; double powSum = 0; //int sco = 0; result.studentIds.ForEach(x => { double sc = result.studentScores[result.studentIds.IndexOf(x)].Sum(); if (!result.lostStus.Contains(x)) { powSum += Math.Pow(sc - result.average, 2); } }); result.standard = Math.Round((result.studentIds.Count - result.lostStus.Count) > 0 ? Math.Pow(powSum / (result.studentIds.Count - result.lostStus.Count), 0.5) : 0, 2); result.csRate = csRate; result.sRate = allScore > 0 ? Math.Round(result.average / allScore * 100, 2) : 0; result.classes = classRanges; result.code = "ExamResult-" + info.id; result.school = info.school; result.id = subject.id; result.examId = info.id; result.subjectId = subject.id; result.year = info.year; result.paper = info.papers[no]; //result.point = info.papers[j].point; result.scope = info.scope; result.name = info.name; result.time = info.startTime; if (info.qamode != 2) { await examRecordCount(info, subject, _dingDing, no, result, examClassResults, _azureCosmos); } await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Common").UpsertItemAsync(result, new Azure.Cosmos.PartitionKey($"ExamResult-{info.id}")); List errorItems = new(); string code = string.Empty; string pk = string.Empty; string table = "School"; if (info.scope.Equals("school")) { code = $"ErrorItems-{info.school}"; pk = $"Item-{info.school}"; } else { code = "ErrorItems"; pk = $"Item-{info.creatorId}"; table = "Teacher"; } string sqlqueryText = $"select value(c) from c where c.activityId = '{info.id}'and c.subjectId = '{info.subjects[no].id}' and c.stuId in ({string.Join(",", result.studentIds.Select(x => $"'{x}'"))})"; await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "Student").GetItemQueryIterator(queryText: sqlqueryText, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{code}") })) { errorItems.Add(item); } try { /* bool isError = examClassResults.SelectMany(c => c.status).ToList().Exists(z => z == 1); if () { }*/ if (errorItems.Count == 0 && info.qamode != 2) { // 新增逻辑 收集错题内容 BlobDownloadResult index_json; if (info.scope.Equals("school")) { index_json = await _azureStorage.GetBlobContainerClient($"{info.school}").GetBlobClient($"{info.papers[no].blob}/index.json").DownloadContentAsync(); } else { index_json = await _azureStorage.GetBlobContainerClient($"{info.creatorId}").GetBlobClient($"{info.papers[no].blob}/index.json").DownloadContentAsync(); } //BlobDownloadResult index_json = await _azureStorage.GetBlobContainerClient($"{info.school}").GetBlobClient($"{info.papers[no].blob}/index.json").DownloadContentAsync(); JsonElement RecordingJson = JsonDocument.Parse(new MemoryStream(Encoding.UTF8.GetBytes(index_json.Content.ToString()))).RootElement; RecordingJson.TryGetProperty("slides", out JsonElement slides); int qamode = info.qamode; if (RecordingJson.TryGetProperty("qamode", out JsonElement element)) { qamode = element.GetInt32(); } var sdes = slides.ToObject>(); List attachments = new List(); if (qamode == 1) { attachments = RecordingJson.GetProperty("attachments").ToObject>(); if (attachments.Count == 0) { return; } } List urls = new(); foreach (var ne in sdes) { if (!ne.type.Equals("compose")) { urls.Add(ne.url); } } // 获取整体的题目ID集合 List ids = new(); List<(string id, string pid, int level, string type, List knows)> itemInfos = new(); foreach (string url in urls) { string id = url.Replace(".json", ""); BlobDownloadResult index_item_json; if (info.scope.Equals("school")) { index_item_json = await _azureStorage.GetBlobContainerClient($"{info.school}").GetBlobClient($"{info.papers[no].blob}/{url}").DownloadContentAsync(); } else { index_item_json = await _azureStorage.GetBlobContainerClient($"{info.creatorId}").GetBlobClient($"{info.papers[no].blob}/{url}").DownloadContentAsync(); } JsonElement itemJson = JsonDocument.Parse(new MemoryStream(Encoding.UTF8.GetBytes(index_item_json.Content.ToString()))).RootElement; itemJson.TryGetProperty("exercise", out JsonElement exercise); //itemJson.TryGetProperty("pid", out JsonElement pid); var item_json = exercise.ToObject(); //JObject keys = JObject.Parse(index_item_json.Content.ToString()); string type = item_json.type; int level = item_json.level; var knowledge = item_json.knowledges; //string pid = keys.Value("pid"); if (itemJson.TryGetProperty("pid", out JsonElement pid)) { itemInfos.Add((id, pid.ToString(), level, type, knowledge)); } else { itemInfos.Add((id, "", level, type, knowledge)); } } /* double[] point = StringHelper.ListTodouble(result.paper.point); double[,] res = StringHelper.ListToDouble(result.studentScores); var cdm = new ClouDASMatrix(res, point);*/ //需要努力的题目 //var ss = cdm.StriveTopic; int n = 0; List>> task_error = new(); List errors = new(); foreach (var sIds in result.studentIds) { ErrorItems error = new() { ttl = -1, code = code, id = Guid.NewGuid().ToString(), stuId = sIds, school = info.school, activityId = info.id, subjectId = result.subjectId, time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() }; int index_stu = result.studentIds.IndexOf(sIds); //顺序学生错题的索引 var itemScore = result.studentScores[index_stu]; List index = new(); int index_item = 0; foreach (var sc in itemScore) { if (sc == 0) { index.Add(index_item + 1); } index_item++; } if (index.Count == 0) { continue; } //int[] item_index = ss[n]; foreach (var item in index) { Items items = new() { id = urls[item - 1].Replace(".json", ""), blob = info.papers[no].blob }; items.level = itemInfos.Where(c => c.id.Equals(items.id)).FirstOrDefault().level; items.type = itemInfos.Where(c => c.id.Equals(items.id)).FirstOrDefault().type; items.pId = itemInfos.Where(c => c.id.Equals(items.id)).FirstOrDefault().pid; items.knowledge = itemInfos.Where(c => c.id.Equals(items.id)).FirstOrDefault().knows; items.qamode = qamode; error.its.Add(items); } errors.Add(error); n++; task_error.Add(_azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student").CreateItemAsync(error, new PartitionKey($"{error.code}"))); } //string connect = _configuration.GetValue("HaBookAuth:Crtmabank") + "https://malearnfunction.chinacloudsites.cn/api/crtmabank"; string location = $"{Environment.GetEnvironmentVariable("Option:Location")}"; string urlAction = string.Empty; string accessKey = string.Empty; if (location.Contains("China")) { if (location.Equals("China")) //大陸正式站 { urlAction = "https://malearn.teammodel.cn"; accessKey = "2BcXFR_hvzG1pZjqIkaM7Dx74Hcu6m0PwwOacFpDpq44AzFuHJBRXA=="; } else if (location.Equals("China-Dep") || location.Equals("China-Test")) //大陸測試站 { urlAction = "https://malearn-rc.teammodel.cn"; accessKey = "lghWhJduNiAlo-e8isqEoROjdR7DAC-50XNtanIwHKYlAzFu1aog_A=="; } } else if (location.Contains("Global")) { if (location.Equals("Global")) //國際正式站 { urlAction = "https://malearn.teammodel.net"; accessKey = "I-2lTcdggJkZWSBwOXQIm4oHx-huwX3d0wLe-9pgojThAzFuq_KNFg=="; } else if (location.Equals("Global-Test")) //國際測試站 { urlAction = "https://malearn-rc.teammodel.net"; accessKey = "_l4Cb_tHIRBw_iv3ZuwVqjkMwjg4_HtDaxhAmZ8OwJraAzFu_DAY8A=="; } } string connect = $"{urlAction}/api/crtmabank"; var htc = _httpClient.CreateClient(); if (htc.DefaultRequestHeaders.Contains("x-functions-key")) { htc.DefaultRequestHeaders.Remove("x-functions-key"); } htc.DefaultRequestHeaders.Add("x-functions-key", accessKey); ///智慧題庫school欄位修正 foreach (ErrorItems error in errors) { error.school = (info.scope.Equals("school")) ? info.school : string.Empty; } string paramJson = JsonConvert.SerializeObject(errors); var content = new StringContent(paramJson, Encoding.UTF8, "application/json"); var response = await htc.PostAsync(connect, content); if ((int)response.StatusCode == 200) { await task_error.TaskPage(10); } } } catch (Exception e) { await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-{info.id}-评测错题异常{e.Message}\n{e.StackTrace}\n", GroupNames.醍摩豆服務運維群組); } } public class Settlement { public double rate { get; set; } public double score { get; set; } public List stus { get; set; } = new List(); public int total { get; set; } public double qrate { get; set; } } #region ===學習記錄用類別=== private class PaperIndex { public PaperIndex() { slides = new List(); } public List slides { get; set; } } private class Slide { /// /// blob 路徑 /// public string url { get; set; } /// /// 題型 /// public string type { get; set; } /// /// scoring /// public Scoring scoring { get; set; } } private class Exercise { /// /// 题目类型 /// public string type { get; set; } /// /// 难度 /// public int level { get; set; } /// /// 知识点 /// public List knowledges { get; set; } } private class Scoring { /// /// knowledge /// public List knowledge { get; set; } /// /// 答案 /// public List ans { get; set; } } private class StudentAnswers { /// /// 學生作答 blob 路徑 /// public List> studentAnswers { get; set; } /// /// 學生ID /// public List studentIds { get; set; } = new(); } private class QuestionData { public QuestionData() { item = new List(); } public QuestionExercise exercise { get; set; } public List item { get; set; } } private class QuestionItem { public QuestionItem() { option = new List(); } public string question { get; set; } public List option { get; set; } } private class OptionItem { public string code { get; set; } public string value { get; set; } } private class QuestionExercise { public string type { get; set; } public List knowledge { get; set; } public List answer { get; set; } } #endregion } }