浏览代码

收取學生評量歷程資料後上傳Blob

jeff 1 年之前
父节点
当前提交
4dbe443763

+ 468 - 48
TEAMModelOS.FunctionV4/CosmosDB/TriggerExam.cs

@@ -35,6 +35,8 @@ using DocumentFormat.OpenXml.EMMA;
 using Microsoft.Extensions.Options;
 using TEAMModelOS.Models;
 using Microsoft.AspNetCore.Razor.TagHelpers;
+using HtmlAgilityPack;
+using Azure.Storage.Blobs;
 
 namespace TEAMModelOS.FunctionV4
 {
@@ -108,10 +110,11 @@ namespace TEAMModelOS.FunctionV4
                     //处理科目信息
                     List<string> sub = new List<string>();
                     School sc = new();
-                    if (!string.IsNullOrEmpty(info.school) && !info.school.Equals("SYSTEM_NO_SCHOOL")) {
+                    if (!string.IsNullOrEmpty(info.school) && !info.school.Equals("SYSTEM_NO_SCHOOL"))
+                    {
                         sc = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>(info.school, new Azure.Cosmos.PartitionKey("Base"));
                     }
-                    
+
                     foreach (ExamSubject subject in info.subjects)
                     {
                         sub.Add(subject.id);
@@ -234,7 +237,7 @@ namespace TEAMModelOS.FunctionV4
 
                                                 using var json = await JsonDocument.ParseAsync(sresponse.ContentStream);
                                                 Class classroom = json.ToObject<Class>();
-                                                
+
                                                 foreach (SDK.Models.Period period in sc.period)
                                                 {
                                                     if (period.id.Equals(classroom.periodId))
@@ -426,10 +429,11 @@ namespace TEAMModelOS.FunctionV4
                             int fno = 0;
                             try
                             {
+                                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)
                                 {
@@ -461,14 +465,15 @@ namespace TEAMModelOS.FunctionV4
                                     };
                                     //作答合格率
                                     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 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<string> subjects = info.subjects.Select(x => x.id).ToList();
                                     //获取学期信息
-                                    if (null != period && persent >= 60 && !subjects.Contains("subject_painting") && !subjects.Contains("subject_music")) {
+                                    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<RMember> rmembers, List<RGroupList> groups) = await GroupListService.GetMemberByListids(_coreAPIHttpService, client, _dingDing, info.classes, info.school);
@@ -487,7 +492,8 @@ namespace TEAMModelOS.FunctionV4
                                             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) {
+                                            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]));
@@ -524,9 +530,9 @@ namespace TEAMModelOS.FunctionV4
 
                                             portrait.students.Add(student);
                                         }
-                                        string location = $"{Environment.GetEnvironmentVariable("Option:Location")}";  
+                                        string location = $"{Environment.GetEnvironmentVariable("Option:Location")}";
                                         var (status, json) = await _httpTrigger.RequestHttpTrigger(portrait, location, "upsert-student-portrait");
-                                    }                                  
+                                    }
                                     //PortraitStudent student = new();
                                     //处理试卷活动结束统计账户信息
                                     List<FMember> idList = await GroupListService.GetFinishMemberInfo(_coreAPIHttpService, client, _dingDing, info.school, info.classes, info.stuLists, null);
@@ -534,6 +540,7 @@ namespace TEAMModelOS.FunctionV4
 
                                     await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync<ExamInfo>(info, info.id, new Azure.Cosmos.PartitionKey(info.code));
                                 }
+                                //SetLearnRecordContent(info, data, _azureStorage);
                             }
                             catch (Exception e)
                             {
@@ -555,6 +562,345 @@ namespace TEAMModelOS.FunctionV4
 
         }
 
+        private static async Task SetLearnRecordContent(ExamInfo info, TriggerData data, AzureStorageFactory _azureStorage, AzureCosmosFactory _azureCosmos)
+        {
+            try
+            {
+                if (info.papers.Count > 0)
+                {
+                    // debug用
+                    //if (info.id == "d1dc1417-eeb0-4b4c-b1b3-7e9f99c95087")
+                    //{
+                    //}
+                    
+                    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<PaperIndex>();
+                            List<LearnRecordItem> 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)");
+                                    //sbsql.Append($" and c.info.id in (");
+                                    //foreach (var classes in info.classes)
+                                    //{// 組合所有classesid                                                                         
+                                    //    if (classes == info.classes[info.classes.Count - 1])
+                                    //    {
+                                    //        sbsql.Append($"'{classes}' )");
+                                    //    }
+                                    //    else
+                                    //    {
+                                    //        sbsql.Append($"'{classes}',");
+                                    //    }
+                                    //}
+                                }
+                                else if (info.stuLists.Count > 0)
+                                {
+                                    sbsql.Append($" and ARRAY_CONTAINS({System.Text.Json.JsonSerializer.Serialize(info.stuLists)}, c.info.id)");
+                                    //sbsql.Append($" and c.info.id in (");
+                                    //foreach (var stuLists in info.stuLists)
+                                    //{// 組合所有stuListsid                                                                         
+                                    //    if (stuLists == info.stuLists[info.stuLists.Count - 1])
+                                    //    {
+                                    //        sbsql.Append($"'{stuLists}' )");
+                                    //    }
+                                    //    else
+                                    //    {
+                                    //        sbsql.Append($"'{stuLists}',");
+                                    //    }
+                                    //}
+                                }
+                                // 取學生答案及學生名單
+                                var client = _azureCosmos.GetCosmosClient();
+                                Dictionary<string, List<string>> stuDic = new Dictionary<string, List<string>>();
+                                if (!string.IsNullOrWhiteSpace(info.school) && !stuDic.ContainsKey(info.school))
+                                {
+                                    stuDic.Add(info.school, new List<string>());
+                                }
+                                List<StudentAnswers> studentAnswersList = new List<StudentAnswers>();
+                                List<string> stuListForSql = new List<string>();
+                                await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Common).GetItemQueryIterator<StudentAnswers>(queryText: sbsql.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"ExamClassResult-{rootName}") }))
+                                {
+                                    studentAnswersList.Add(item);
+                                    stuListForSql.Union(item.studentIds);
+                                }
+                                //取得學校的學生名單
+                                if(!string.IsNullOrWhiteSpace(info.school))
+                                {
+                                    StringBuilder stusql = new StringBuilder($" SELECT c.id FROM c WHERE c.pk = 'Base' AND ARRAY_CONTAINS({System.Text.Json.JsonSerializer.Serialize(stuListForSql)}, c.id)");
+                                    await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Student).GetItemQueryIterator<string>(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<List<string>> ansList = JsonConvert.DeserializeObject<List<List<string>>>(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<QuestionData>();
+                                                            setCorrectChoices(questionData, learnRecordItem);
+                                                            // 使用HtmlAgilityPack解析HTML
+                                                            HtmlDocument doc = new HtmlDocument();
+                                                            doc.LoadHtml(questionData.item[0].question);
+                                                            // 先刪除所有<img>標籤  
+                                                            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<string>() : 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)
+            {
+
+            }
+        }
+
+
+        /// <summary>
+        /// 取得學習紀錄的actor
+        /// </summary>
+        /// <param name="learnRecordItem"></param>
+        /// <param name="school"></param>
+        /// <param name="stuid"></param>
+        /// <returns></returns>
+        private static string getStuId(string school, string stuid, Dictionary<string, List<string>> 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 = true;
+                }
+                else
+                {
+                    learnRecordItem.Correct = false;
+                }
+            }
+            else
+            {// 防呆 去html標籤
+                if (questionData.exercise.answer.Count > 0)
+                {
+                    List<string> 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<ExamClassResult> examClassResults)
         {
             List<Task<ItemResponse<ExamClassResult>>> tasks = new();
@@ -870,11 +1216,12 @@ namespace TEAMModelOS.FunctionV4
             var addTmdidsCls = tchList.FindAll(x => x.type == 1);
             List<StuActivity> stuActivities = new List<StuActivity>();
             List<StuActivity> tmdActivities = new List<StuActivity>();
-            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);
-            //var subs = info.papers.Select(z => z.subjectId).Distinct().ToList();
+            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 =>
@@ -941,7 +1288,8 @@ namespace TEAMModelOS.FunctionV4
                         creatorId = info.creatorId,
                         subjects = sub,
                         blob = null,
-                        paper = flag ? papers.Select(c => new {
+                        paper = flag ? papers.Select(c => new
+                        {
                             c.subject,
                             blob = c.blob[new Random().Next(c.blob.Count)]
                         }) : "",
@@ -1024,7 +1372,8 @@ namespace TEAMModelOS.FunctionV4
                         creatorId = info.creatorId,
                         subjects = sub,
                         blob = null,
-                        paper = flag ? papers.Select(c => new {
+                        paper = flag ? papers.Select(c => new
+                        {
                             c.subject,
                             blob = c.blob[new Random().Next(c.blob.Count)]
                         }) : "",
@@ -1454,9 +1803,10 @@ namespace TEAMModelOS.FunctionV4
                                 newScores.Add(sc > -1 ? sc : 0);
                             }
                         }
-                        else {
+                        else
+                        {
                             newScores.Add(0);
-                        }                      
+                        }
                         classSrate += newScores.Sum();
                         score += newScores.Sum();
                         result.studentScores.Add(newScores);
@@ -1594,12 +1944,12 @@ namespace TEAMModelOS.FunctionV4
                         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();
-                    //JObject jo = JObject.Parse(new MemoryStream(Encoding.UTF8.GetBytes(index_json.Content.ToString())).ToString());
                     JsonElement RecordingJson = JsonDocument.Parse(new MemoryStream(Encoding.UTF8.GetBytes(index_json.Content.ToString()))).RootElement;
                     RecordingJson.TryGetProperty("slides", out JsonElement slides);
                     var sdes = slides.ToObject<List<Slides>>();
                     List<string> attachments = new List<string>();
-                    if (info.qamode == 1) {
+                    if (info.qamode == 1)
+                    {
                         attachments = RecordingJson.GetProperty("attachments").ToObject<List<string>>();
                         if (attachments.Count == 0)
                         {
@@ -1607,7 +1957,7 @@ namespace TEAMModelOS.FunctionV4
                         }
                     }
                     List<string> urls = new();
-                  
+
                     foreach (var ne in sdes)
                     {
                         if (!ne.type.Equals("compose"))
@@ -1615,7 +1965,6 @@ namespace TEAMModelOS.FunctionV4
                             urls.Add(ne.url);
                         }
                     }
-                    
                     // 获取整体的题目ID集合
                     List<string> ids = new();
                     List<(string id, string pid, int level, string type, List<string> knows)> itemInfos = new();
@@ -1638,10 +1987,10 @@ namespace TEAMModelOS.FunctionV4
                         string pid = keys.Value<string>("pid");
                         itemInfos.Add((id, pid, level, type, knowledge));
                     }
-                    
-                   /* double[] point = StringHelper.ListTodouble(result.paper.point);
-                    double[,] res = StringHelper.ListToDouble(result.studentScores);
-                    var cdm = new ClouDASMatrix(res, point);*/
+
+                    /* 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;
@@ -1665,13 +2014,16 @@ namespace TEAMModelOS.FunctionV4
                         var itemScore = result.studentScores[index_stu];
                         List<int> index = new();
                         int index_item = 0;
-                        foreach (var sc in itemScore) {
-                            if (sc == 0) {
-                                index.Add(index_item+1);
+                        foreach (var sc in itemScore)
+                        {
+                            if (sc == 0)
+                            {
+                                index.Add(index_item + 1);
                             }
                             index_item++;
                         }
-                        if (index.Count == 0) {
+                        if (index.Count == 0)
+                        {
                             continue;
                         }
                         //int[] item_index = ss[n];
@@ -1700,25 +2052,25 @@ namespace TEAMModelOS.FunctionV4
                     string accessKey = string.Empty;
                     if (location.Contains("China"))
                     {
-                        if(location.Equals("China")) //大陸正式站
+                        if (location.Equals("China")) //大陸正式站
                         {
                             urlAction = "https://malearn.teammodel.cn";
                             accessKey = "2BcXFR_hvzG1pZjqIkaM7Dx74Hcu6m0PwwOacFpDpq44AzFuHJBRXA==";
                         }
-                        else if(location.Equals("China-Dep") || location.Equals("China-Test")) //大陸測試站
+                        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")) //國際正式站
+                        if (location.Equals("Global")) //國際正式站
                         {
                             urlAction = "https://malearn.teammodel.net";
                             accessKey = "I-2lTcdggJkZWSBwOXQIm4oHx-huwX3d0wLe-9pgojThAzFuq_KNFg==";
                         }
-                        else if(location.Equals("Global-Test")) //國際測試站
+                        else if (location.Equals("Global-Test")) //國際測試站
                         {
                             urlAction = "https://malearn-rc.teammodel.net";
                             accessKey = "_l4Cb_tHIRBw_iv3ZuwVqjkMwjg4_HtDaxhAmZ8OwJraAzFu_DAY8A==";
@@ -1738,14 +2090,17 @@ namespace TEAMModelOS.FunctionV4
                     }
                     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) {
+                    var response = await htc.PostAsync(connect, content);
+                    if ((int)response.StatusCode == 200)
+                    {
                         await task_error.TaskPage(10);
-                    }                  
+                    }
                 }
-            } catch (Exception e) {
+            }
+            catch (Exception e)
+            {
                 await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-{info.id}-评测错题异常{e.Message}\n{e.StackTrace}\n", GroupNames.醍摩豆服務運維群組);
-            }           
+            }
         }
 
         public class Settlement
@@ -1757,20 +2112,85 @@ namespace TEAMModelOS.FunctionV4
             public double qrate { get; set; }
         }
 
-        public class Slides
+        #region ===學習記錄用類別===  
+        private class PaperIndex
+        {
+            public PaperIndex()
+            {
+                slides = new List<Slide>();
+            }
+            public List<Slide> slides { get; set; }
+        }
+        private class Slide
         {
+            /// <summary>
+            /// blob 路徑
+            /// </summary>
             public string url { get; set; }
+            /// <summary>
+            /// 題型
+            /// </summary>
             public string type { get; set; }
-            public Scoring scoring { get; set; } = new Scoring();
+            /// <summary>
+            /// scoring
+            /// </summary>
+            public Scoring scoring { get; set; }
+
+        }
+        private class Scoring
+        {
+            /// <summary>
+            /// knowledge
+            /// </summary>
+            public List<string> knowledge { get; set; }
+            /// <summary>
+            /// 答案
+            /// </summary>
+            public List<string> ans { get; set; }
+
         }
+        private class StudentAnswers
+        {
+            /// <summary>
+            /// 學生作答 blob 路徑
+            /// </summary>
+            public List<List<string>> studentAnswers { get; set; }
+            /// <summary>
+            /// 學生ID
+            /// </summary>
+            public List<string> studentIds { get; set; } = new();
 
-        public class Scoring
+        }
+        private class QuestionData
         {
-            public double score { get; set; }
-            public List<string> knowledge { get; set; } = new List<string>();
-            public int field { get; set; } = new();
-            public List<string> ans { get; set; } = new List<string>();
+            public QuestionData()
+            {
+                item = new List<QuestionItem>();
+            }
+            public QuestionExercise exercise { get; set; }
+            public List<QuestionItem> item { get; set; }
+        }
+        private class QuestionItem
+        {
+            public QuestionItem()
+            {
+                option = new List<OptionItem>();
+            }
+            public string question { get; set; }
+            public List<OptionItem> 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<string> knowledge { get; set; }
+            public List<string> answer { get; set; }
         }
 
+        #endregion
     }
 }

+ 60 - 0
TEAMModelOS.SDK/Models/Cosmos/Common/LearnRecord.cs

@@ -0,0 +1,60 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace TEAMModelOS.SDK.Models.Cosmos.Common
+{
+    /// <summary>
+    /// 儲存學習記錄blob的格式
+    /// </summary>
+    public class LearnRecordItem
+    {
+        public LearnRecordItem()
+        {
+            Points = new List<string>();
+            Choices = new List<ChoicesItem>();
+        }
+        // EndExam, AnsSingle, AnsMultiple, AnsBuzzin, AnsJudge, AnsComplete, AnsSubjective, SubmitTask, SubmitHomework, PostDiscussion, ReponseDisucss
+        // 結束課堂, 單選, 複選, 搶權, 是非, 填充, 問答, 交作品, 交作業(IES5), 發起討論IES5討論區), 討論回應(IES5討論區)    
+        public string verb { get; set; }
+        //時間戳記
+        public long time { get; set; }
+        // TMID直接放, 校內帳號用組合的 "Base-hbgl,473891247381"
+        public string actor { get; set; }
+        // 唯一碼 (考試ExamID, 題目QID, 任務PageID, 互動題PageID)
+        public string ID { get; set; }
+        // 文字標題或描述或題目
+        public string Desc { get; set; }
+        // 知識點陣列, 裡面放字串, 相當於關鍵詞
+        public List<string> Points { get; set; }
+        // 知識點陣列, 裡面放字串, 相當於關鍵詞
+        public object Correct { get; set; }
+        // 知識點陣列, 裡面放字串, 相當於關鍵詞
+        public List<ChoicesItem> Choices { get; set; }
+        // 考試卷題數, 若無放 null
+        public int? ExamQuesQty { get; set; }
+        // 考試卷總分, 若無放 null
+        public double? TotalScore { get; set; }
+        // 單題答對與否, 若無放 null
+        public bool? Success { get; set; }
+    }
+    public class ChoicesItem
+    {
+        public ChoicesItem()
+        {
+            description = new ItemDesc();
+        }
+        public string id { get; set; }
+        public ItemDesc description { get; set; }
+    }
+    public class ItemDesc
+    {
+        [JsonProperty("zh-TW")]
+        public string? zhTW { get; set; }        
+    }
+
+}

+ 1 - 1
TEAMModelOS/Controllers/Both/ScoreCalcController.cs

@@ -1958,7 +1958,7 @@ namespace TEAMModelOS.Controllers
                 }
                 if (sb.Length > 0)
                 {
-                    sql_members = $"SELECT c.id, c.no, c.name FROM c WHERE c.id in ({sb.ToString().Remove(sb.Length - 1, 1)})";
+                    sql_members = $"SELECT c.id, c.no, c.name FROM c WHERE c.id in ({sb.ToString().Remove(sb.Length - 1, 1)}) and c.code = 'Base-{schoolId}'";
                     await foreach (var item in clientStudent.GetItemQueryIterator<ScoreCalcMember>(queryText: sql_members))
                     {
                         members.Add(item);

+ 585 - 0
TEAMModelOS/Controllers/Client/HiTeachController.cs

@@ -31,6 +31,9 @@ using TEAMModelOS.SDK.Models.Service;
 using TEAMModelOS.Models.Request;
 using Azure;
 using TEAMModelOS.Controllers.Both;
+using TEAMModelOS.SDK.Models.Cosmos.School;
+using TEAMModelOS.SDK.Helper.Common.DateTimeHelper;
+using Azure.Storage.Blobs;
 
 namespace TEAMModelOS.Controllers.Client
 {
@@ -132,10 +135,17 @@ namespace TEAMModelOS.Controllers.Client
                 var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
                 string msg = null;
                 msg = new { lesson_id = $"{_lessonId}", tmdid = $"{_tmdid}", grant_types = updates, school = $"{_school}", scope = $"{_scope}" }.ToJsonString();
+
+                //if (!string.IsNullOrWhiteSpace(_school.GetString()))
+                //{
+                //    SaveLearnRecordBlob(_school.GetString(), _lessonId.GetString());
+                //}
+
                 var messageChange = new ServiceBusMessage(msg);
                 messageChange.ApplicationProperties.Add("name", "LessonRecordEvent");
                 //await _dingDing.SendBotMsg($"{_option.Location},课堂id:{_lessonId} 更新事件,{msg}", GroupNames.醍摩豆服務運維群組);
                 await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageChange);
+                SetLearnRecordContent(_school, _tmdid, _lessonId, _scope);
                 return Ok(new { status = 200 });
             }
             catch (CosmosException ex) when (ex.Status == 404)
@@ -150,6 +160,480 @@ namespace TEAMModelOS.Controllers.Client
             }
         }
 
+
+        /// <summary>
+        /// 更新课堂记录
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        //[Authorize(Roles = "HiTeach")]
+        [ProducesDefaultResponseType]
+        [HttpPost("update-lesson-recordLearnRecord")]
+        public async Task<IActionResult> UpdateLessonRecordLearnRecord(JsonElement request)
+        {
+            List<LearnRecordItem> learnRecordItems = new List<LearnRecordItem>();
+            try
+            {
+                if (!request.TryGetProperty("lesson_id", out JsonElement _lessonId)) return BadRequest();
+                if (!request.TryGetProperty("scope", out JsonElement _scope)) return BadRequest();
+                if (!request.TryGetProperty("tmdid", out JsonElement _tmdid)) return BadRequest();
+                request.TryGetProperty("school", out JsonElement _school);
+
+                SetLearnRecordContent(_school, _tmdid, _lessonId, _scope);
+
+                return Ok(new { status = 200, learnRecordItems });
+            }
+            catch (Exception ex)
+            {
+                string sss = ex.Message;
+                return BadRequest();
+            }
+        }
+        /// <summary>
+        /// 設定及儲存學習紀錄
+        /// </summary>
+        /// <param name="_school"></param>
+        /// <param name="_tmdid"></param>
+        /// <param name="_lessonId"></param>
+        /// <param name="_scope"></param>
+        /// <returns></returns>
+        private async Task SetLearnRecordContent(JsonElement _school, JsonElement _tmdid, JsonElement _lessonId, JsonElement _scope)
+        {
+            try
+            {
+                string school = $"{_school}";
+                string tmdid = _tmdid.GetString();
+                string lessonId = _lessonId.GetString();
+                string rootName = "";
+                List<LearnRecordItem> learnRecordItems = new List<LearnRecordItem>();
+                if (_scope.GetString().Equals("school") && !string.IsNullOrWhiteSpace(_school.GetString()))
+                {
+                    rootName = school;
+                }
+                else if ($"{_scope}".Equals("private"))
+                {
+
+                    rootName = tmdid;
+                }
+
+                // 檢查 TimeLine 是否存在blob
+                if (_azureStorage.GetBlobContainerClient(rootName).GetBlobClient($"/records/{lessonId}/IES/TimeLine.json").Exists() &&
+                    _azureStorage.GetBlobContainerClient(rootName).GetBlobClient($"/records/{lessonId}/IES/IRS.json").Exists() &&
+                    _azureStorage.GetBlobContainerClient(rootName).GetBlobClient($"/records/{lessonId}/IES/base.json").Exists())
+                {
+                    // 開啟 TimeLine.json
+                    BlobDownloadResult TimeLineblobDownload = await _azureStorage.GetBlobContainerClient(rootName).GetBlobClient($"/records/{lessonId}/IES/TimeLine.json").DownloadContentAsync();
+                    TimeLineEvents timeLineEvents = TimeLineblobDownload.Content.ToObjectFromJson<TimeLineEvents>();
+
+                    // 開啟 IRS.json
+                    BlobDownloadResult IRSblobDownload = await _azureStorage.GetBlobContainerClient(rootName).GetBlobClient($"/records/{lessonId}/IES/IRS.json").DownloadContentAsync();
+                    List<IRSItem> iRSItems = IRSblobDownload.Content.ToObjectFromJson<List<IRSItem>>();
+
+                    // 開啟 base.json
+                    BlobDownloadResult baseblobDownload = await _azureStorage.GetBlobContainerClient(rootName).GetBlobClient($"/records/{lessonId}/IES/base.json").DownloadContentAsync();
+                    ScoreLessonBase lessonBase = baseblobDownload.Content.ToObjectFromJson<ScoreLessonBase>();
+
+                    // 開啟 Task.json
+                    BlobDownloadResult taskblobDownload = await _azureStorage.GetBlobContainerClient(rootName).GetBlobClient($"/records/{lessonId}/IES/Task.json").DownloadContentAsync();
+                    List<TaskItem> taskItems = taskblobDownload.Content.ToObjectFromJson<List<TaskItem>>();
+                    if (timeLineEvents.events.Count > 0)
+                    {
+
+                        for (int i = 0; i < timeLineEvents.events.Count; i++)
+                        {
+                            switch (timeLineEvents.events[i].Event)
+                            {
+                                // 互動
+                                case "PopQuesLoad":
+                                    for (int j = 0; j < iRSItems.Count; j++)
+                                    {
+                                        if (timeLineEvents.events[i].Pgid == iRSItems[j].pageID) // 比對 Pgid 找到 IRS.json 該活動的詳細資料
+                                        {
+                                            switch (iRSItems[j].question.exercise.type)
+                                            {
+                                                // 單選
+                                                case "single":
+                                                    await SetPopQuesLoadContent(lessonBase, school, lessonId, iRSItems[j], timeLineEvents.events[i], "AnsSingle", learnRecordItems);
+                                                    break;
+                                                // 複選
+                                                case "multiple":
+                                                    await SetPopQuesLoadContent(lessonBase, school, lessonId, iRSItems[j], timeLineEvents.events[i], "AnsMultiple", learnRecordItems);
+                                                    break;
+                                                // 是非
+                                                case "judge":
+                                                    await SetPopQuesLoadContent(lessonBase, school, lessonId, iRSItems[j], timeLineEvents.events[i], "AnsJudge", learnRecordItems);
+                                                    break;
+                                                // 填充
+                                                case "complete":
+                                                    await SetPopQuesLoadContent(lessonBase, school, lessonId, iRSItems[j], timeLineEvents.events[i], "AnsComplete", learnRecordItems);
+                                                    break;
+                                                // 問答
+                                                case "subjective":
+                                                    await SetPopQuesLoadContent(lessonBase, school, lessonId, iRSItems[j], timeLineEvents.events[i], "AnsSubjective", learnRecordItems);
+                                                    break;
+                                            }
+                                        }
+                                    }
+                                    break;
+                                // 搶權
+                                case "BuzrAns":
+                                    for (int j = 0; j < iRSItems.Count; j++)
+                                    {
+                                        if (timeLineEvents.events[i].Pgid == iRSItems[j].pageID && iRSItems[j].isBuzz) // 比對 Pgid 找到 IRS.json 該活動的詳細資料
+                                        {
+                                            for (int k = 0; k < iRSItems[j].buzzClients.Count; k++)
+                                            {
+                                                LearnRecordItem learnRecordItem = new();
+                                                for (int m = 0; m < lessonBase.student.Count; m++)
+                                                {// 比對學生座號 取學號
+                                                    if (lessonBase.student[m].seatID.ToString() == iRSItems[j].buzzClients[k])
+                                                    {
+                                                        if (lessonBase.student[k].type == 2)
+                                                        {
+                                                            await getSchoolCode(school, lessonId, lessonBase.student[k].id, learnRecordItem);
+                                                        }
+                                                        else
+                                                        {// 非校內帳號用直接用學號
+                                                            learnRecordItem.actor = lessonBase.student[k].id;
+                                                        }
+                                                    }
+                                                }
+                                                string[] arrymd = lessonBase.summary.date.Split('.');
+                                                string[] arrhms = lessonBase.summary.startTime.Split(':');
+                                                DateTime dt = new DateTime(int.Parse(arrymd[0]), int.Parse(arrymd[1]), int.Parse(arrymd[2]), int.Parse(arrhms[0]), int.Parse(arrhms[1]), int.Parse(arrhms[2]));
+                                                dt = dt.AddSeconds(timeLineEvents.events[i].Time);
+                                                learnRecordItem.verb = "AnsBuzzin";
+                                                learnRecordItem.time = dt.ToUnixTimestamp();
+                                                learnRecordItem.ID = timeLineEvents.events[i].Pgid;
+                                                if (iRSItems[j].question.item[0].question == "_popquiz_" || iRSItems[j].question.item[0].question == "")
+                                                {
+                                                    learnRecordItem.Desc = "隨堂問答";
+                                                }
+                                                else
+                                                {
+                                                    learnRecordItem.Desc = iRSItems[j].question.item[0].question;
+                                                }
+                                                learnRecordItem.Points = iRSItems[j].question.exercise.knowledges;
+
+                                                await setCorrectChoices(iRSItems[j], learnRecordItem);
+                                                learnRecordItem.ExamQuesQty = null;
+                                                learnRecordItem.TotalScore = null;
+                                                learnRecordItem.Success = null;
+                                                learnRecordItems.Add(learnRecordItem);
+                                            }
+                                        }
+                                    }
+                                    break;
+                                // 上傳作品
+                                case "WrkSpaceLoad":
+                                    for (int j = 0; j < taskItems.Count; j++)
+                                    {
+                                        if (timeLineEvents.events[i].Pgid == taskItems[j].pageID) // 比對 Pgid 找到 IRS.json 該活動的詳細資料
+                                        {
+                                            for (int k = 0; k < taskItems[j].clientWorks.Count; k++)
+                                            {
+                                                LearnRecordItem learnRecordItem = new();
+                                                for (int m = 0; m < lessonBase.student.Count; m++)
+                                                {// 比對學生座號 取學號
+                                                    if (lessonBase.student[m].seatID == taskItems[j].clientWorks[k].seatID)
+                                                    {
+                                                        if (lessonBase.student[k].type == 2)
+                                                        {
+                                                            await getSchoolCode(school, lessonId, lessonBase.student[k].id, learnRecordItem);
+                                                        }
+                                                        else
+                                                        {// 非校內帳號用直接用學號
+                                                            learnRecordItem.actor = lessonBase.student[k].id;
+                                                        }
+                                                    }
+                                                }
+
+                                                string[] arrymd = lessonBase.summary.date.Split('.');
+                                                string[] arrhms = taskItems[j].clientWorks[k].reciveTime.Split(':');
+                                                DateTime dt = new DateTime(int.Parse(arrymd[0]), int.Parse(arrymd[1]), int.Parse(arrymd[2]), int.Parse(arrhms[0]), int.Parse(arrhms[1]), int.Parse(arrhms[2]));
+                                                dt = dt.AddSeconds(timeLineEvents.events[i].Time);
+                                                learnRecordItem.verb = "SubmitTask";
+                                                learnRecordItem.time = dt.ToUnixTimestamp();
+                                                learnRecordItem.ID = timeLineEvents.events[i].Pgid;
+                                                learnRecordItem.Desc = "隨堂作品";
+                                                learnRecordItem.Correct = null;
+                                                learnRecordItem.Choices = null;
+                                                learnRecordItem.ExamQuesQty = null;
+                                                learnRecordItem.TotalScore = null;
+                                                learnRecordItem.Success = null;
+
+                                                learnRecordItems.Add(learnRecordItem);
+                                            }
+                                        }
+                                    }
+                                    break;
+                                case "FastPgPush":
+                                    for (int k = 0; k < lessonBase.student.Count; k++)
+                                    {
+                                        LearnRecordItem learnRecordItem = new();
+                                        for (int m = 0; m < lessonBase.student.Count; m++)
+                                        {// 比對學生座號 取學號
+
+                                            if (lessonBase.student[k].type == 2)
+                                            {
+                                                await getSchoolCode(school, lessonId, lessonBase.student[k].id, learnRecordItem);
+                                            }
+                                            else
+                                            {// 非校內帳號用直接用學號
+                                                learnRecordItem.actor = lessonBase.student[k].id;
+                                            }
+
+                                        }
+
+                                        string[] arrymd = lessonBase.summary.date.Split('.');
+                                        string[] arrhms = lessonBase.summary.startTime.Split(':');
+                                        DateTime dt = new DateTime(int.Parse(arrymd[0]), int.Parse(arrymd[1]), int.Parse(arrymd[2]), int.Parse(arrhms[0]), int.Parse(arrhms[1]), int.Parse(arrhms[2]));
+                                        dt = dt.AddSeconds(timeLineEvents.events[i].Time);
+                                        learnRecordItem.verb = "ViewPage";
+                                        learnRecordItem.time = dt.ToUnixTimestamp();
+                                        learnRecordItem.ID = timeLineEvents.events[i].Pgid;
+                                        learnRecordItem.Desc = null;
+                                        learnRecordItem.Correct = null;
+                                        learnRecordItem.Choices = null;
+                                        learnRecordItem.ExamQuesQty = null;
+                                        learnRecordItem.TotalScore = null;
+                                        learnRecordItem.Success = null;
+
+                                        learnRecordItems.Add(learnRecordItem);
+                                    }
+                                    break;
+                                case "ActStart":
+                                case "ActEnd":
+                                    for (int k = 0; k < lessonBase.student.Count; k++)
+                                    {
+                                        LearnRecordItem learnRecordItem = new();
+                                        for (int m = 0; m < lessonBase.student.Count; m++)
+                                        {// 比對學生座號 取學號
+
+                                            if (lessonBase.student[k].type == 2)
+                                            {
+                                                await getSchoolCode(school, lessonId, lessonBase.student[k].id, learnRecordItem);
+                                            }
+                                            else
+                                            {// 非校內帳號用直接用學號
+                                                learnRecordItem.actor = lessonBase.student[k].id;
+                                            }
+
+                                        }
+
+                                        string[] arrymd = lessonBase.summary.date.Split('.');
+                                        string[] arrhms = lessonBase.summary.startTime.Split(':');
+                                        DateTime dt = new DateTime(int.Parse(arrymd[0]), int.Parse(arrymd[1]), int.Parse(arrymd[2]), int.Parse(arrhms[0]), int.Parse(arrhms[1]), int.Parse(arrhms[2]));
+                                        dt = dt.AddSeconds(timeLineEvents.events[i].Time);
+                                        if (timeLineEvents.events[i].Event == "ActStart") { learnRecordItem.verb = "StartExam"; }
+                                        if (timeLineEvents.events[i].Event == "ActEnd") { learnRecordItem.verb = "EndExam"; }
+                                        learnRecordItem.time = dt.ToUnixTimestamp();
+                                        learnRecordItem.ID = lessonId;
+                                        learnRecordItem.Desc = lessonBase.summary.activityName;
+                                        learnRecordItem.Correct = null;
+                                        learnRecordItem.Choices = null;
+                                        learnRecordItem.ExamQuesQty = 0;
+                                        foreach (var item in timeLineEvents.events)
+                                        {
+                                            if (item.Event == "PopQuesLoad" || item.Event == "QuesLoad" || item.Event == "BuzrAns")
+                                            {
+                                                learnRecordItem.ExamQuesQty = learnRecordItem.ExamQuesQty + 1;
+                                            }
+                                        }
+                                        learnRecordItem.TotalScore = learnRecordItem.ExamQuesQty * 10;
+                                        learnRecordItem.Success = null;
+
+                                        learnRecordItems.Add(learnRecordItem);
+                                    }
+                                    break;
+
+
+                                // 測驗
+                                case "SPQStrt":
+                                    //// 開啟 .json
+                                    //BlobDownloadResult taskblobDownload = await _azureStorage.GetBlobContainerClient(school).GetBlobClient($"/records/{lessonId}/IES/Task.json").DownloadContentAsync();
+                                    //List<TaskItem> taskItems = taskblobDownload.Content.ToObjectFromJson<List<TaskItem>>();
+
+                                    break;
+                            }
+                        }
+
+
+                    }
+
+                    #region 寫入blob
+
+                    // 容器名称
+                    string containerName = "twmoeld";
+
+                    // 新文件的名称
+                    string blobName = $"/{DateTime.Now.ToString("yyyyMMdd")}/{lessonId}.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)
+            {
+
+            }
+        }
+        /// <summary>
+        /// 設定學習記錄互動的內容
+        /// </summary>
+        /// <param name="lessonBase"></param>
+        /// <param name="school"></param>
+        /// <param name="lessonId"></param>
+        /// <param name="iRSItem"></param>
+        /// <param name="timeLineEvent"></param>
+        /// <param name="verb"></param>
+        /// <param name="learnRecordItems"></param>
+        /// <returns></returns>
+        private async Task SetPopQuesLoadContent(ScoreLessonBase lessonBase, string school, string lessonId, IRSItem iRSItem, TimeLineEventPg timeLineEvent, string verb, List<LearnRecordItem> learnRecordItems)
+        {
+            for (int k = 0; k < lessonBase.student.Count; k++)
+            {
+                LearnRecordItem learnRecordItem = new();
+                if (lessonBase.student[k].type == 2)
+                {
+                    await getSchoolCode(school, lessonId, lessonBase.student[k].id, learnRecordItem);
+                }
+                else
+                {// 非校內帳號用直接用學號
+                    learnRecordItem.actor = lessonBase.student[k].id;
+                }
+                string[] arrymd = lessonBase.summary.date.Split('.');
+                string[] arrhms = lessonBase.summary.startTime.Split(':');
+                DateTime dt = new DateTime(int.Parse(arrymd[0]), int.Parse(arrymd[1]), int.Parse(arrymd[2]), int.Parse(arrhms[0]), int.Parse(arrhms[1]), int.Parse(arrhms[2]));
+                dt = dt.AddSeconds(timeLineEvent.Time);
+                learnRecordItem.verb = verb;
+                learnRecordItem.time = dt.ToUnixTimestamp();
+                learnRecordItem.ID = timeLineEvent.Pgid;
+                if (iRSItem.question.item[0].question == "_popquiz_" || iRSItem.question.item[0].question == "")
+                {
+                    learnRecordItem.Desc = "隨堂問答";
+                }
+                else
+                {
+                    learnRecordItem.Desc = iRSItem.question.item[0].question;
+                }
+                learnRecordItem.Points = iRSItem.question.exercise.knowledges;
+                await setCorrectChoices(iRSItem, learnRecordItem);
+                learnRecordItem.ExamQuesQty = null;
+                learnRecordItem.TotalScore = null;
+                iRSItem.clientAnswers.TryGetProperty("0", out JsonElement anwersArr);
+
+                List<List<string>> clientAnswersarr = anwersArr.ToJsonString().ToObject<List<List<string>>>();
+
+                if (iRSItem.question.exercise.answer.SequenceEqual(clientAnswersarr[k]))
+                {
+                    learnRecordItem.Success = true;
+                }
+                else
+                {
+                    learnRecordItem.Success = false;
+                }
+                learnRecordItems.Add(learnRecordItem);
+            }
+        }
+        /// <summary>
+        /// 取得學校簡碼
+        /// </summary>
+        /// <param name="school"></param>
+        /// <param name="lessonId"></param>
+        /// <param name="studentId"></param>
+        /// <param name="learnRecordItem"></param>
+        /// <returns></returns>
+        private async Task getSchoolCode(string school, string lessonId, string studentId, LearnRecordItem learnRecordItem)
+        {
+            if (string.IsNullOrWhiteSpace(school))
+            {// 如果沒有學校代碼 需要用lessonId去DB取學校代碼                       
+                GroupIdsFromLesson groupIdsFromLesson = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReadItemAsync<GroupIdsFromLesson>(lessonId, new PartitionKey($"LessonRecord"));
+                SchoolFromgroupIds schoolFromgroupIds = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReadItemAsync<SchoolFromgroupIds>(groupIdsFromLesson.groupIds[0], new PartitionKey($"GroupList"));
+                learnRecordItem.actor = $"Base-{schoolFromgroupIds.school.Trim()},{studentId}";
+            }
+            else
+            {// 如果有學校代碼  校內帳號用組合的 "Base-hbgl,473891247381"
+                learnRecordItem.actor = $"Base-{school.Trim()},{studentId}";
+            }
+        }
+        /// <summary>
+        /// 設定CorrectChoices的邏輯
+        /// </summary>
+        /// <param name="iRSItems"></param>
+        /// <param name="learnRecordItem"></param>
+        /// <returns></returns>
+        private async Task setCorrectChoices(IRSItem iRSItem, LearnRecordItem learnRecordItem)
+        {
+            #region === Correct Choices ===
+            #region === 是非題邏輯 ===
+            if (iRSItem.question.exercise.type == "judge" && iRSItem.question.exercise.answer.Count > 0)
+            {// 如果是是非題 正確答案要用true false的方式設定
+                if (iRSItem.question.exercise.answer[0] == "A")
+                {
+                    learnRecordItem.Correct = new string[] { "true" };
+                }
+                else
+                {
+                    learnRecordItem.Correct = new string[] { "false" }; 
+                }
+            }
+            else
+            {
+                learnRecordItem.Correct = iRSItem.question.exercise.answer;
+            }
+            #endregion
+            if (iRSItem.question.item[0].option.Count > 0)
+            {// 如果有選項資料 記錄起來
+                foreach (var option in iRSItem.question.item[0].option)
+                {
+                    ChoicesItem item = new();
+                    item.id = option.code;
+                    if (iRSItem.question.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
+                        {
+                            item.description.zhTW = option.value;
+                        }                        
+                    }
+                    learnRecordItem.Choices.Add(item);
+                }
+            }
+            #endregion
+        }
+
+
         /// <summary>
         /// 创建课堂开课记录(新版)
         /// </summary>
@@ -2373,5 +2857,106 @@ namespace TEAMModelOS.Controllers.Client
             public int memberCount { get; set; }
         }
 
+        #region ===學習記錄用類別===       
+
+        #region ===TimeLine.json===
+        private class TimeLineEvents
+        {
+            public TimeLineEvents()
+            {
+                events = new List<TimeLineEventPg>();
+            }
+            public List<TimeLineEventPg> events { get; set; }
+        }
+        private class TimeLineEventPg
+        {
+            public double Time { get; set; }
+            public string Pgid { get; set; }
+            public string Event { get; set; }
+        }
+        #endregion
+
+        #region ===IRS.json===
+        /// <summary>
+        /// IRS架構第一層
+        /// </summary>
+        private class IRSItem
+        {
+            public IRSItem()
+            {
+                buzzClients = new List<string>();
+            }
+            public string pageID { get; set; }
+            public IRSQuestion question { get; set; }
+            public JsonElement clientAnswers { get; set; }
+            public List<string> buzzClients { get; set; }
+            public bool isBuzz { get; set; }
+        }
+        /// <summary>
+        /// IRS架構第二層
+        /// </summary>
+        private class IRSQuestion
+        {
+            public IRSQuestion()
+            {
+                item = new List<QuestionItem>();
+            }
+            public QuestionExercise exercise { get; set; }
+            public List<QuestionItem> item { get; set; }
+        }
+        private class QuestionItem
+        {
+            public QuestionItem()
+            {
+                option = new List<OptionItem>();
+            }
+            public string question { get; set; }
+            public List<OptionItem> option { get; set; }
+        }
+        private class OptionItem
+        {
+            public string code { get; set; }
+            public string value { get; set; }
+        }
+
+        /// <summary>
+        /// IRS架構第三層
+        /// </summary>
+        private class QuestionExercise
+        {
+            public string type { get; set; }
+            public List<string> knowledges { get; set; }
+            public List<string> answer { get; set; }
+        }
+
+        #endregion
+
+        #region ===Task.json===
+
+        private class TaskItem
+        {
+            public List<ClientWork> clientWorks { get; set; }
+            public string pageID { get; set; }
+        }
+        private class ClientWork
+        {
+            public int seatID { get; set; }
+            public string reciveTime { get; set; }
+        }
+        #endregion
+
+        #region ===取學校代碼===
+        private class GroupIdsFromLesson
+        {
+            public List<string> groupIds { get; set; }
+        }
+        private class SchoolFromgroupIds
+        {
+            public string school { get; set; }
+        }
+
+        #endregion
+
+        #endregion
     }
 }