LessonETLService.cs 151 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931
  1. using Azure;
  2. using Azure.Storage.Blobs.Models;
  3. using DocumentFormat.OpenXml.Drawing.Charts;
  4. using DocumentFormat.OpenXml.Office2010.Excel;
  5. using DocumentFormat.OpenXml.Office2013.Drawing.ChartStyle;
  6. using DocumentFormat.OpenXml.Office2016.Drawing.ChartDrawing;
  7. using MathNet.Numerics;
  8. using MathNet.Numerics.Distributions;
  9. using Microsoft.Azure.Cosmos;
  10. using Microsoft.Azure.Cosmos.Linq;
  11. using Microsoft.Extensions.Configuration;
  12. using Microsoft.Extensions.Logging;
  13. using OfficeOpenXml;
  14. using OpenXmlPowerTools;
  15. using System;
  16. using System.Collections.Generic;
  17. using System.IO;
  18. using System.Linq;
  19. using System.Reflection;
  20. using System.Text;
  21. using System.Text.Json;
  22. using System.Text.RegularExpressions;
  23. using System.Threading.Tasks;
  24. using System.Xml;
  25. using TEAMModelOS.SDK;
  26. using TEAMModelOS.SDK.DI;
  27. using TEAMModelOS.SDK.Extension;
  28. using TEAMModelOS.SDK.Models;
  29. using TEAMModelOS.SDK.Models.Cosmos.Common;
  30. using TEAMModelOS.SDK.Models.Cosmos.OpenEntity;
  31. namespace HTEX.Lib.ETL.Lesson
  32. {
  33. public class LessonETLService
  34. {
  35. /// <summary>
  36. /// 生成学生student-analysis.json
  37. /// </summary>
  38. /// <param name="objectiveTypes"></param>
  39. /// <param name="azureStorage"></param>
  40. /// <param name="lessonLocal"></param>
  41. /// <returns></returns>
  42. public static async Task<(List<StudentLessonData> studentLessonDatas, List<StudentLessonItem> lessonItems)>
  43. DoStudentLessonData(List<string> objectiveTypes, AzureStorageFactory azureStorage, LessonLocal lessonLocal,DingDing _dingDing,
  44. CosmosClient client,string location
  45. )
  46. {
  47. List<StudentLessonData> studentLessonDatas = lessonLocal.studentLessonDatas.ToJsonString().ToObject<List<StudentLessonData>>();
  48. studentLessonDatas = LessonETLService.GetBaseInfo(lessonLocal.lessonBase!, studentLessonDatas, lessonLocal?.lessonRecord?.id);
  49. studentLessonDatas = LessonETLService.GetIRSData(lessonLocal.lessonBase!, lessonLocal.timeLineData!, lessonLocal.irsDatas, studentLessonDatas, lessonLocal.examDatas, lessonLocal?.lessonRecord?.id);
  50. studentLessonDatas = LessonETLService.GetCoworkData(lessonLocal.lessonBase!, lessonLocal.timeLineData!, lessonLocal.coworkDatas, studentLessonDatas, lessonLocal.lessonRecord.id);
  51. studentLessonDatas = LessonETLService.GetExamData(lessonLocal.lessonBase!, lessonLocal.timeLineData!, lessonLocal.examDatas, studentLessonDatas, objectiveTypes, lessonLocal.lessonRecord.id);
  52. studentLessonDatas = LessonETLService.GetSmartRatingData(lessonLocal.lessonBase!, lessonLocal.timeLineData!, lessonLocal.smartRatingDatas, studentLessonDatas, lessonLocal.lessonRecord.id);
  53. studentLessonDatas = LessonETLService.GetTaskData(lessonLocal.lessonBase!, lessonLocal.timeLineData!, lessonLocal.taskDatas, studentLessonDatas, lessonLocal.lessonRecord.id);
  54. var pickupData = LessonETLService.GetPickupData(lessonLocal.lessonBase!, lessonLocal. timeLineData!, studentLessonDatas, lessonLocal.lessonRecord.id);
  55. studentLessonDatas= pickupData.studentLessonDatas;
  56. List<StudentLessonItem> lessonItems = new List<StudentLessonItem>();
  57. LessonDataAnalysisCluster lessonDataAnalysis = null ;
  58. string owner = lessonLocal.lessonRecord.scope.Equals("school") ? lessonLocal.lessonRecord.school : lessonLocal.lessonRecord.tmdid;
  59. try
  60. {
  61. bool exists= await azureStorage.GetBlobContainerClient("0-public").GetBlobClient($"/lesson/analysis/analysis.json").ExistsAsync();
  62. if (exists)
  63. {
  64. BlobDownloadResult blobDownload = await azureStorage.GetBlobContainerClient("0-public").GetBlobClient($"/lesson/analysis/analysis.json").DownloadContentAsync();
  65. lessonDataAnalysis = blobDownload.Content.ToObjectFromJson<LessonDataAnalysisCluster>();
  66. lessonItems = LessonETLService.ProcessStudentDataV2(studentLessonDatas, lessonDataAnalysis);
  67. XmlDocument xmlDocument = new XmlDocument();
  68. var runtimePath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
  69. xmlDocument.Load($"{runtimePath}\\summary.xml");
  70. await LessonETLService.ExportToExcelAzureBlob(lessonItems,azureStorage, owner,$"{lessonLocal.lessonRecord.id}/student-analysis.xlsx", xmlDocument);
  71. }
  72. }
  73. catch (Exception ex)
  74. {
  75. if (!ex.Message.Contains("The specified blob does not exist"))
  76. {
  77. await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")},lesson/analysis/analysis.json转换异常,{ex.Message}{ex.StackTrace}", GroupNames.成都开发測試群組);
  78. }
  79. }
  80. await azureStorage.GetBlobContainerClient(owner).UploadFileByContainer(studentLessonDatas.ToJsonString(), "records", $"{lessonLocal.lessonRecord.id}/student-analysis.json");
  81. if (lessonLocal.lessonRecord.scope.Equals("school")&& !string.IsNullOrWhiteSpace(lessonLocal.lessonRecord.school) && location.Equals("China",StringComparison.OrdinalIgnoreCase))
  82. {
  83. School school = null;
  84. ResponseMessage response = await client.GetContainer(Constant.TEAMModelOS, Constant.School).ReadItemStreamAsync(lessonLocal.lessonRecord.school, new PartitionKey("Base"));
  85. if (response.IsSuccessStatusCode)
  86. {
  87. school = JsonDocument.Parse(response.Content).RootElement.ToObject<School>();
  88. }
  89. if (school!=null)
  90. {
  91. string? periodId = !string.IsNullOrWhiteSpace(lessonLocal.lessonRecord.periodId) ? lessonLocal.lessonRecord.periodId : school.period.FirstOrDefault()?.id;
  92. var period = school.period.Find(x => x.id.Equals(periodId));
  93. if (period!=null)
  94. {
  95. var semester = SchoolService.GetSemester(period, lessonLocal.lessonRecord.startTime);
  96. string pre_id = $"{semester.studyYear}-{semester.currSemester.id}";
  97. string code = $"StudentSemesterRecord-{school.id}";
  98. foreach (var studentLessonData in studentLessonDatas)
  99. {
  100. StudentSemesterRecord studentSemester = null;
  101. ResponseMessage responseMessage = await client.GetContainer(Constant.TEAMModelOS, Constant.Student).ReadItemStreamAsync($"{pre_id}-{studentLessonData.id}", new PartitionKey(code));
  102. if (responseMessage.IsSuccessStatusCode)
  103. {
  104. studentSemester= JsonDocument.Parse(responseMessage.Content).ToObject<StudentSemesterRecord>();
  105. }
  106. else
  107. {
  108. studentSemester= new StudentSemesterRecord
  109. {
  110. id= $"{pre_id}-{studentLessonData.id}",
  111. code=code,
  112. stuid= studentLessonData.id,
  113. userType=Constant.ScopeStudent,
  114. school=school.id,
  115. studyYear=semester.studyYear,
  116. semesterId=semester.currSemester.id,
  117. period= period?.id,
  118. };
  119. }
  120. var les = studentSemester.les.Find(x => x.id.Equals(lessonLocal.lessonRecord.id));
  121. if (les==null)
  122. {
  123. les = new StuLesson()
  124. {
  125. id= lessonLocal.lessonRecord.id,
  126. time= lessonLocal.lessonRecord.startTime,
  127. attend=0
  128. };
  129. studentSemester.les.Add(les);
  130. }
  131. else {
  132. les.time= lessonLocal.lessonRecord.startTime;
  133. }
  134. var lesson = studentSemester.lessons.Find(x => x.id.Equals(lessonLocal.lessonRecord.id));
  135. if (lesson!=null)
  136. {
  137. if (studentLessonData.cooperation>0 || studentLessonData.achieve>0|| studentLessonData.attitude>0 || studentLessonData.cowork>0 || studentLessonData.appraise>0)
  138. {
  139. les.attend=1;
  140. lesson.tmdid = lessonLocal.lessonRecord.tmdid;
  141. lesson.sid = lessonLocal.lessonRecord.subjectId;
  142. lesson.cid = lessonLocal.lessonRecord.courseId;
  143. lesson.hrate = studentLessonData.cooperation;
  144. lesson.crate = studentLessonData.achieve;
  145. lesson.trate = studentLessonData.attitude;
  146. lesson.xrate = studentLessonData.cowork;
  147. lesson.prate = studentLessonData.appraise;
  148. }
  149. else
  150. {
  151. if (studentLessonData.attend==1)
  152. {
  153. les.attend=1;
  154. }
  155. else
  156. {
  157. les.attend=0;
  158. }
  159. }
  160. }
  161. else
  162. {
  163. if (studentLessonData.cooperation>0 || studentLessonData.achieve>0|| studentLessonData.attitude>0 || studentLessonData.cowork>0 || studentLessonData.appraise>0)
  164. {
  165. les.attend=1;
  166. studentSemester.lessons.Add(new StuLessonLite
  167. {
  168. id=lessonLocal.lessonRecord.id,
  169. tmdid=lessonLocal.lessonRecord.tmdid,
  170. sid= lessonLocal.lessonRecord.subjectId,
  171. cid= lessonLocal.lessonRecord.courseId,
  172. hrate=studentLessonData.cooperation,
  173. crate=studentLessonData.achieve,
  174. trate=studentLessonData.attitude,
  175. xrate=studentLessonData.cowork,
  176. prate=studentLessonData.appraise
  177. });
  178. }
  179. else
  180. {
  181. if (studentLessonData.attend==1)
  182. {
  183. les.attend=1;
  184. }
  185. else
  186. {
  187. les.attend=0;
  188. }
  189. }
  190. }
  191. await client.GetContainer(Constant.TEAMModelOS, Constant.Student).UpsertItemAsync(studentSemester, new PartitionKey(studentSemester.code));
  192. }
  193. }
  194. }
  195. }
  196. return (studentLessonDatas, lessonItems);
  197. }
  198. public static void GenAnalysisData(string pathAnalysis, long newest, List<TechCount> techCounts)
  199. {
  200. var yearMonthDatas = techCounts.GroupBy(x => x.yearMonth).Select(x => new { key = x.Key, list = x.ToList() });
  201. // lessonDataAnalysisMonths = new List<LessonDataAnalysisMonth>();
  202. LessonDataAnalysisCluster lessonDataAnalysisCluster = new LessonDataAnalysisCluster();
  203. foreach (var yearMonthData in yearMonthDatas)
  204. {
  205. if (string.IsNullOrWhiteSpace(yearMonthData.key))
  206. {
  207. Console.WriteLine(yearMonthData.list.Select(x => x.lessonId).ToJsonString());
  208. }
  209. LessonDataAnalysisMonth lessonDataAnalysisMonth = new LessonDataAnalysisMonth() { updateTime= newest, yearMonth= yearMonthData.key };
  210. lessonDataAnalysisMonth.pscore = yearMonthData.list.SelectMany(x => x.pscore).ToList();
  211. lessonDataAnalysisMonth.tscore = yearMonthData.list.SelectMany(x => x.tscore).ToList();
  212. lessonDataAnalysisMonth.gscore = yearMonthData.list.SelectMany(x => x.gscore).ToList();
  213. lessonDataAnalysisMonth.irs= yearMonthData.list.Where(x => x.irsCount>0).Select(x => (double)x.irsCount).ToList();
  214. lessonDataAnalysisMonth.interactNormal= yearMonthData.list.Where(x => x.interactNormalCount > 0).Select(x => (double)x.interactNormalCount).ToList();
  215. lessonDataAnalysisMonth.task = yearMonthData.list.Where(x => x.taskCount > 0).Select(x => (double)x.taskCount).ToList();
  216. lessonDataAnalysisMonth.stuCowork=yearMonthData.list.SelectMany(x => x.stuCowork).ToList();
  217. lessonDataAnalysisMonth.groupCowork=yearMonthData.list.SelectMany(x => x.groupCowork).ToList();
  218. lessonDataAnalysisMonth.pickup= yearMonthData.list.Where(x => x.pickup.Count()>0).Select(x=>x.pickup).ToList();
  219. System.IO.File.WriteAllText(Path.Combine(pathAnalysis, $"{yearMonthData.key}-m-analysis.json"), lessonDataAnalysisMonth.ToJsonString());
  220. // lessonDataAnalysisMonths.Add( lessonDataAnalysisMonth);
  221. if (lessonDataAnalysisMonth.task.IsNotEmpty())
  222. {
  223. lessonDataAnalysisCluster.task.AddRange(lessonDataAnalysisMonth.task);
  224. }
  225. if (lessonDataAnalysisMonth.irs.IsNotEmpty())
  226. {
  227. lessonDataAnalysisCluster.irs.AddRange(lessonDataAnalysisMonth.irs);
  228. }
  229. if (lessonDataAnalysisMonth.interactNormal.IsNotEmpty())
  230. {
  231. lessonDataAnalysisCluster.interactNormal.AddRange(lessonDataAnalysisMonth.interactNormal);
  232. }
  233. if (lessonDataAnalysisMonth.pscore.IsNotEmpty())
  234. {
  235. lessonDataAnalysisCluster.pscore.AddRange(lessonDataAnalysisMonth.pscore);
  236. }
  237. if (lessonDataAnalysisMonth.gscore.IsNotEmpty())
  238. {
  239. lessonDataAnalysisCluster.gscore.AddRange(lessonDataAnalysisMonth.gscore);
  240. }
  241. if (lessonDataAnalysisMonth.tscore.IsNotEmpty())
  242. {
  243. lessonDataAnalysisCluster.tscore.AddRange(lessonDataAnalysisMonth.tscore);
  244. }
  245. if (lessonDataAnalysisMonth.stuCowork.IsNotEmpty())
  246. {
  247. lessonDataAnalysisCluster.stuCowork.AddRange(lessonDataAnalysisMonth.stuCowork);
  248. }
  249. if (lessonDataAnalysisMonth.groupCowork.IsNotEmpty())
  250. {
  251. lessonDataAnalysisCluster.groupCowork.AddRange(lessonDataAnalysisMonth.groupCowork);
  252. }
  253. lessonDataAnalysisCluster.pickup.AddRange(lessonDataAnalysisMonth.pickup);
  254. }
  255. //标准差偏差N倍,视为异常数据
  256. int thresholdMultiplier = 2;
  257. lessonDataAnalysisCluster.task= LessonETLService.CleanDataBySDThreshold(lessonDataAnalysisCluster.task.OrderBy(x => x), thresholdMultiplier);
  258. lessonDataAnalysisCluster.pscore= LessonETLService.CleanDataBySDThreshold(lessonDataAnalysisCluster.pscore.OrderBy(x => x), thresholdMultiplier);
  259. lessonDataAnalysisCluster.gscore= LessonETLService.CleanDataBySDThreshold(lessonDataAnalysisCluster.gscore.OrderBy(x => x), thresholdMultiplier);
  260. lessonDataAnalysisCluster.tscore= LessonETLService.CleanDataBySDThreshold(lessonDataAnalysisCluster.tscore.OrderBy(x => x), thresholdMultiplier);
  261. lessonDataAnalysisCluster.irs = LessonETLService.CleanDataBySDThreshold(lessonDataAnalysisCluster.irs.OrderBy(x => x), thresholdMultiplier);
  262. lessonDataAnalysisCluster.interactNormal=LessonETLService.CleanDataBySDThreshold(lessonDataAnalysisCluster.interactNormal.OrderBy(x => x), thresholdMultiplier);
  263. lessonDataAnalysisCluster.stuCowork=LessonETLService.CleanDataBySDThreshold(lessonDataAnalysisCluster.stuCowork.OrderBy(x => x), thresholdMultiplier);
  264. lessonDataAnalysisCluster.groupCowork=LessonETLService.CleanDataBySDThreshold(lessonDataAnalysisCluster.groupCowork.OrderBy(x => x), thresholdMultiplier);
  265. var all = lessonDataAnalysisCluster.interactNormal.OrderBy(x => x).ToArray();
  266. //超过60 80的
  267. double n = all.Max()+1;
  268. //var clusterInteract = HTEX.Lib.ETL.KMeansService.KMeansOptimized(d, 3);
  269. //foreach (var item in clusterInteract.OrderBy(x => x.Key))
  270. //{
  271. // lessonDataAnalysisCluster.clustersInteract.Add(new KeyValuePair<double, List<double>>(item.Value.Average(), item.Value));
  272. // Console.WriteLine($"dp:{item.Key} ,avg: {item.Value.Average()}, count: {item.Value.Count}, min:{item.Value.Min()}, max:{item.Value.Max()},weight:{item.Value.Count*1.0/d.Count()}");
  273. //}
  274. //IEnumerable<double> all = lessonDataAnalysisCluster.clustersInteract.SelectMany(x => x.Value);
  275. int pass = 0;
  276. for (var i = 1; i<n; i++)
  277. {
  278. var p = LessonETLService.GetPersent(all, i);
  279. if (p.persent>=60)
  280. {
  281. pass = i;
  282. break;
  283. }
  284. }
  285. var low = all.Where(x => x<pass).Average();
  286. int good = 0;
  287. for (var i = 1; i<n; i++)
  288. {
  289. var p = LessonETLService.GetPersent(all, i);
  290. if (p.persent>=80)
  291. {
  292. good = i;
  293. break;
  294. }
  295. }
  296. var medium = all.Where(x => x>=pass &&x<good).Average();
  297. var high = all.Where(x => x>=good).Average();
  298. lessonDataAnalysisCluster.interactGood=good;
  299. lessonDataAnalysisCluster.interactPass=pass;
  300. lessonDataAnalysisCluster.interactHigh=high;
  301. lessonDataAnalysisCluster.interactMedium=medium;
  302. lessonDataAnalysisCluster.interactLow=low;
  303. List<KeyValuePair<double, List<double>>> levelInteract = new List<KeyValuePair<double, List<double>>>()
  304. {
  305. new KeyValuePair<double, List<double>>(low, new List<double> { all.Where(x => x<pass).Min(), all.Where(x => x<pass).Max() }),
  306. new KeyValuePair<double, List<double>>(low, new List<double> { all.Where(x => x>=pass &&x<good).Min(), all.Where(x => x>=pass &&x<good).Max() }),
  307. new KeyValuePair<double, List<double>>(low, new List<double> { all.Where(x => x>=good).Min(), all.Where(x => x>=good).Max() })
  308. };
  309. lessonDataAnalysisCluster.levelInteract= levelInteract;
  310. //// 设置0 为自动规划聚类,11 则为自动规划后得到的数字。
  311. //var clusterInteract = KMeansService.KMeansOptimized(d,11,10);
  312. ////foreach (var item in clusterInteract)
  313. ////{
  314. //// Console.WriteLine($"dp:{item.Key} ,avg: {item.Value.Average()}, count: {item.Value.Count}, min:{item.Value.Min()}, max:{item.Value.Max()}");
  315. ////}
  316. //foreach (var item in clusterInteract.OrderBy(x => x.Key))
  317. //{
  318. // lessonDataAnalysisCluster.clustersInteract.Add(new KeyValuePair<double, List<double>>(item.Value.Average(), item.Value));
  319. // //Console.WriteLine($"dp:{item.Key} ,avg: {item.Value.Average()}, count: {item.Value.Count}, min:{item.Value.Min()}, max:{item.Value.Max()},weight:{item.Value.Count*1.0/d.Count()}");
  320. //}
  321. System.IO.File.WriteAllText(Path.Combine(pathAnalysis, "analysis.json"), lessonDataAnalysisCluster.ToJsonString());
  322. }
  323. public static async IAsyncEnumerable<TechCount> GetTeachCount(List<LessonRecord> lessonRecords, List<string> filesLessons, string pathLessons, List<string> ignore, List<string> objectiveTypes, AzureStorageFactory azureStorage,bool force)
  324. {
  325. foreach (var item in filesLessons)
  326. {
  327. if (item.EndsWith("-local.json"))
  328. {
  329. TechCount count = null;
  330. string lessonId = item.Split("\\").Last().Replace("-local.json", "");
  331. string countFile = item.Replace("-local.json", "-count.json");
  332. if (System.IO.File.Exists(countFile))
  333. {
  334. if (System.IO.File.Exists(countFile))
  335. {
  336. string contjson = await System.IO.File.ReadAllTextAsync(countFile);
  337. count = contjson.ToObject<TechCount>();
  338. }
  339. }
  340. if (string.IsNullOrWhiteSpace(count?.yearMonth)|| force)
  341. {
  342. string localjson = await System.IO.File.ReadAllTextAsync(item);
  343. var lessonLocal = localjson.ToObject<LessonLocal>();
  344. count = new TechCount();
  345. count.lessonId=item.Split("\\").Last().Replace("-local.json", "");
  346. count.examCount= lessonLocal.examDatas.Count;
  347. count.taskCount= lessonLocal.taskDatas.Count;
  348. count.irsCount= lessonLocal.irsDatas.Count;
  349. count.interactNormalCount=count.irsCount;
  350. count.coworkCount= lessonLocal.coworkDatas.Count;
  351. count.smartRatingCount= lessonLocal.smartRatingDatas.Count;
  352. count.timeCount=lessonLocal.sokratesDatas.Where(x => !ignore.Contains(x.Event) && !x.Event.Contains("End", StringComparison.OrdinalIgnoreCase)).GroupBy(x => x.Event).Select(x => new CodeLong() { code=x.Key, value= x.ToList().Count }).ToList();
  353. if (lessonLocal.lessonRecord!=null)
  354. {
  355. count.yearMonth=DateTimeOffset.FromUnixTimeMilliseconds(lessonLocal.lessonRecord.startTime).ToString("yyyyMM");
  356. if (lessonLocal?.lessonBase?.summary!=null)
  357. {
  358. count.smartRatingCountBase=lessonLocal.lessonBase.summary.smartRatingCount;
  359. count.irsCountBase=lessonLocal.lessonBase.summary.interactionCount;
  360. count.taskCountBase=lessonLocal.lessonBase.summary.collateTaskCount;
  361. count.coworkCountBase=lessonLocal.lessonBase.summary.coworkTaskCount;
  362. count.examCountBase=lessonLocal.lessonBase.summary.examCount;
  363. count.interactNormalCountBase= count.interactNormalCount;
  364. }
  365. if (lessonLocal?.lessonBase?.report?.clientSummaryList!=null)
  366. {
  367. count.pscore= lessonLocal.lessonBase.report.clientSummaryList.Where(x => x.score>0).Select(x => x.score);
  368. count.gscore= lessonLocal.lessonBase.report.clientSummaryList.Where(x => x.groupScore>0).Select(x => x.groupScore);
  369. count.tscore= lessonLocal.lessonBase.report.clientSummaryList.Where(x => x.interactScore>0).Select(x => x.interactScore);
  370. }
  371. ///处理学生数据
  372. {
  373. List<StudentLessonData> studentLessonDatas = lessonLocal.studentLessonDatas.ToJsonString().ToObject<List<StudentLessonData>>();
  374. studentLessonDatas = LessonETLService.GetBaseInfo(lessonLocal.lessonBase!, studentLessonDatas, lessonLocal?.lessonRecord?.id);
  375. studentLessonDatas = LessonETLService.GetIRSData(lessonLocal.lessonBase!, lessonLocal.timeLineData!, lessonLocal.irsDatas, studentLessonDatas, lessonLocal.examDatas, lessonLocal.lessonRecord.id);
  376. studentLessonDatas = LessonETLService.GetCoworkData(lessonLocal.lessonBase!, lessonLocal.timeLineData!, lessonLocal.coworkDatas, studentLessonDatas, lessonLocal.lessonRecord.id);
  377. studentLessonDatas = LessonETLService.GetExamData(lessonLocal.lessonBase!, lessonLocal.timeLineData!, lessonLocal.examDatas, studentLessonDatas, objectiveTypes, lessonLocal.lessonRecord.id);
  378. studentLessonDatas = LessonETLService.GetSmartRatingData(lessonLocal.lessonBase!, lessonLocal.timeLineData!, lessonLocal.smartRatingDatas, studentLessonDatas, lessonLocal.lessonRecord.id);
  379. studentLessonDatas = LessonETLService.GetTaskData(lessonLocal.lessonBase!, lessonLocal.timeLineData!, lessonLocal.taskDatas, studentLessonDatas, lessonLocal.lessonRecord.id);
  380. var pickupData = LessonETLService.GetPickupData(lessonLocal.lessonBase!, lessonLocal.timeLineData!, studentLessonDatas, lessonLocal.lessonRecord.id);
  381. studentLessonDatas= pickupData.studentLessonDatas;
  382. count.pickup= pickupData.pickup ;
  383. // var techCount = techCounts.Find(x => !string.IsNullOrWhiteSpace(x.lessonId) && !string.IsNullOrWhiteSpace(lessonLocal?.lessonRecord?.id) && x.lessonId.Equals(lessonLocal.lessonRecord.id));
  384. int sumUpload = 0;
  385. int taskCount = 0;
  386. int maxUpload = 0;
  387. HashSet<string> pickUp = new HashSet<string>();
  388. foreach (var stu in studentLessonDatas)
  389. {
  390. var countS = stu.taskRecord.itemRecords.Where(x => x.optCount>0);
  391. if (countS.Count()>0)
  392. {
  393. int stuUploadmax = stu.taskRecord.itemRecords.Where(x => x.optCount>0).Max(x => x.optCount);
  394. if (stuUploadmax> maxUpload)
  395. {
  396. maxUpload=stuUploadmax;
  397. }
  398. }
  399. int stuUpload = stu.taskRecord.itemRecords.Where(x => x.optCount>0).Sum(x => x.optCount);
  400. sumUpload+=stuUpload;
  401. if (stu.taskRecord.itemRecords.Count()> taskCount)
  402. {
  403. taskCount=stu.taskRecord.itemRecords.Count();
  404. }
  405. var stu_scores = stu.coworkRecord.itemRecords.Where(x => x.itemScore>0).Select(x => x.itemScore);
  406. if (stu_scores!=null && stu_scores.Count()>0)
  407. {
  408. count.stuCowork.AddRange(stu_scores);
  409. }
  410. var grp_scores = stu.group_coworkScore.Where(x => x>0);
  411. if (grp_scores!=null && grp_scores.Count()>0)
  412. {
  413. count.groupCowork.AddRange(grp_scores);
  414. }
  415. //if (stu.pickups.IsNotEmpty())
  416. //{
  417. // foreach (var pickup in stu.pickups)
  418. // {
  419. // pickUp.Add(pickup);
  420. // }
  421. //}
  422. }
  423. if (studentLessonDatas.Count>0&& taskCount>0 && maxUpload>0)
  424. {
  425. var avgUpload = sumUpload*1.0/(studentLessonDatas.Count *taskCount);
  426. count.upload.Add(new List<double>() { avgUpload, maxUpload });
  427. }
  428. //if (pickUp.Count>0)
  429. //{
  430. // count.pickup.AddRange(pickUp.ToList());
  431. //}
  432. string owner = lessonLocal.lessonRecord.scope.Equals("school") ? lessonLocal.lessonRecord.school : lessonLocal.lessonRecord.tmdid;
  433. // if (!azureStorage.GetBlobContainerClient(owner).GetBlobClient($"records/{lessonLocal.lessonRecord.id}/student-analysis.json").Exists())
  434. {
  435. }
  436. // if (!System.IO.File.Exists($"{pathLessons}\\MM{count.yearMonth}\\{lessonLocal.lessonRecord.id}-stu.json"))
  437. {
  438. }
  439. if (force)
  440. {
  441. await System.IO.File.WriteAllTextAsync($"{pathLessons}\\MM{count.yearMonth}\\{lessonLocal.lessonRecord.id}-count.json", count.ToJsonString());
  442. }
  443. else
  444. {
  445. if (lessonRecords.FindAll(x => x.id.Equals(lessonLocal.lessonRecord.id)).IsNotEmpty())
  446. {
  447. // await System.IO.File.WriteAllTextAsync($"{pathLessons}\\MM{count.yearMonth}\\{lessonLocal.lessonRecord.id}-stu.json", studentLessonDatas.ToJsonString());
  448. //只有不是强制更新的时候再去刷新线上的json文件。
  449. await azureStorage.GetBlobContainerClient(owner).UploadFileByContainer(studentLessonDatas.ToJsonString(), "records", $"{lessonLocal.lessonRecord.id}/student-analysis.json");
  450. await System.IO.File.WriteAllTextAsync($"{pathLessons}\\MM{count.yearMonth}\\{lessonLocal.lessonRecord.id}-count.json", count.ToJsonString());
  451. }
  452. }
  453. }
  454. }
  455. }
  456. if (count!=null)
  457. {
  458. yield return count;
  459. }
  460. }
  461. }
  462. }
  463. public static List<StudentLessonData> GetBaseInfo(LessonBase lessonBase, List<StudentLessonData> studentLessonDatas, string? id)
  464. {
  465. if (lessonBase?.report?.clientSummaryList !=null && lessonBase.report.clientSummaryList.IsNotEmpty())
  466. {
  467. foreach (var item in lessonBase.report.clientSummaryList)
  468. {
  469. var student = studentLessonDatas.Find(x => x.seatID!=null && x.seatID.Equals($"{item.seatID}"));
  470. if (student!= null)
  471. {
  472. student.gscore= item.groupScore;
  473. student.tscore= item.interactScore;
  474. student.pscore= item.score;
  475. }
  476. }
  477. }
  478. return studentLessonDatas;
  479. }
  480. public static async IAsyncEnumerable<LessonLocal> GetLessonLocal(List<LessonRecord> lessonRecords, List<string> localIds, AzureStorageFactory _azureStorage, string pathLessons)
  481. {
  482. foreach (var lessonRecord in lessonRecords)
  483. {
  484. string scope = lessonRecord.scope;
  485. string owner = lessonRecord.scope.Equals("school") ? lessonRecord.school : lessonRecord.tmdid;
  486. if (localIds.Contains(lessonRecord.id))
  487. {
  488. continue;
  489. }
  490. string yearMonthPath = DateTimeOffset.FromUnixTimeMilliseconds(lessonRecord.startTime).ToString("yyyyMM");
  491. LessonLocal lessonLocal = new LessonLocal { lessonRecord=lessonRecord };
  492. if (System.IO.File.Exists($"{pathLessons}\\MM{yearMonthPath}\\{lessonRecord.id}-local.json"))
  493. {
  494. string jsonp = await System.IO.File.ReadAllTextAsync($"{pathLessons}\\MM{yearMonthPath}\\{lessonRecord.id}-local.json");
  495. lessonLocal = jsonp.ToObject<LessonLocal>();
  496. }
  497. else
  498. {
  499. List<string> files = new List<string>()
  500. {
  501. $"/records/{lessonRecord.id}/IES/TimeLine.json",
  502. $"/records/{lessonRecord.id}/IES/base.json",
  503. $"/records/{lessonRecord.id}/IES/Task.json",
  504. $"/records/{lessonRecord.id}/IES/SmartRating.json",
  505. $"/records/{lessonRecord.id}/IES/IRS.json",
  506. $"/records/{lessonRecord.id}/IES/Cowork.json",
  507. $"/records/{lessonRecord.id}/Sokrates/SokratesRecords.json",
  508. };
  509. lessonLocal = new LessonLocal { lessonRecord=lessonRecord };
  510. lessonLocal = await GetLessonFiles(lessonLocal, files, owner, _azureStorage);
  511. }
  512. if (lessonLocal.lessonBase!=null && lessonLocal.lessonBase.student!=null)
  513. {
  514. var baseData = GetBaseData(lessonLocal.lessonBase!);
  515. lessonLocal.studentLessonDatas= baseData.studentLessonDatas;
  516. List<ExamData> examDatas = await GetExamInfo(lessonRecord, lessonLocal.timeLineData, _azureStorage, owner);
  517. lessonLocal.examDatas = examDatas;
  518. lessonLocal.sokratesDatas= lessonLocal.sokratesDatas.IsNotEmpty() ? lessonLocal.sokratesDatas : lessonLocal.timeLineData!=null ? lessonLocal.timeLineData.events : new List<TimeLineEvent>();
  519. }
  520. yield return lessonLocal;
  521. }
  522. }
  523. private static async Task<LessonLocal> GetLessonFiles(LessonLocal lessonLocal, List<string> files, string owner, AzureStorageFactory _azureStorage)
  524. {
  525. await Parallel.ForEachAsync(files, async (file, _) =>
  526. {
  527. try
  528. {
  529. var exists = _azureStorage.GetBlobContainerClient(owner).GetBlobClient(file).Exists();
  530. if (exists)
  531. {
  532. BlobDownloadResult blobDownloadResult = await _azureStorage.GetBlobContainerClient(owner).GetBlobClient(file).DownloadContentAsync();
  533. switch (true)
  534. {
  535. case bool when file.Contains("IES/TimeLine.json"):
  536. lessonLocal.timeLineData= blobDownloadResult.Content.ToObjectFromJson<TimeLineData>();
  537. break;
  538. case bool when file.Contains("IES/base.json"):
  539. lessonLocal.lessonBase= blobDownloadResult.Content.ToObjectFromJson<LessonBase>();
  540. break;
  541. case bool when file.Contains("IES/Task.json"):
  542. lessonLocal.taskDatas= blobDownloadResult.Content.ToObjectFromJson<List<TaskData>>();
  543. break;
  544. case bool when file.Contains("IES/SmartRating.json"):
  545. lessonLocal.smartRatingDatas= blobDownloadResult.Content.ToObjectFromJson<List<SmartRatingData>>();
  546. break;
  547. case bool when file.Contains("IES/IRS.json"):
  548. lessonLocal.irsDatas= blobDownloadResult.Content.ToObjectFromJson<List<IRSData>>();
  549. break;
  550. case bool when file.Contains("IES/Cowork.json"):
  551. lessonLocal.coworkDatas= blobDownloadResult.Content.ToObjectFromJson<List<CoworkData>>();
  552. break;
  553. case bool when file.Contains("Sokrates/SokratesRecords.json"):
  554. lessonLocal.sokratesDatas= blobDownloadResult.Content.ToObjectFromJson<List<TimeLineEvent>>();
  555. break;
  556. }
  557. }
  558. }
  559. catch (RequestFailedException ex)
  560. {
  561. Console.WriteLine($"{file},{ex.Message},{ex.StackTrace}");
  562. }
  563. catch (Exception ex)
  564. {
  565. Console.WriteLine($"{file},{ex.Message},{ex.StackTrace}");
  566. }
  567. });
  568. return lessonLocal;
  569. }
  570. /// <summary>
  571. /// 处理base.json的数据
  572. /// </summary>
  573. /// <param name="lessonRecord"></param>
  574. /// <param name="lessonBase"></param>
  575. /// <returns></returns>
  576. public static (LessonBase lessonBase, List<LocalStudent> studentLessonDatas) GetBaseData(LessonBase lessonBase)
  577. {
  578. //处理学生定位数据
  579. List<LocalStudent> studentLessonDatas = new List<LocalStudent>();
  580. int index = 0;
  581. try
  582. {
  583. if (lessonBase!=null)
  584. {
  585. lessonBase.student.ForEach(x =>
  586. {
  587. int attend = 0;
  588. var client = lessonBase.report.clientSummaryList.Find(y => y.seatID == x.seatID);
  589. if (client!=null)
  590. {
  591. attend=client.attendState;
  592. }
  593. studentLessonDatas.Add(new LocalStudent()
  594. {
  595. id = x.id,
  596. index = index,
  597. seatID =$"{x.seatID}",
  598. groupId = x.groupId,
  599. attend= attend
  600. });
  601. index++;
  602. });
  603. }
  604. }
  605. catch (Exception ex)
  606. {
  607. Console.WriteLine(lessonBase.ToJsonString());
  608. }
  609. return (lessonBase, studentLessonDatas);
  610. }
  611. /// <summary>
  612. ///单独处理挑人的逻辑
  613. ///是否从小组里面挑人。
  614. ///不需要去重页面,直接获取挑人大类 PickupResult
  615. ///小类处理:PickupRight , PickupOption , PickupNthGrp ,PickupEachGrp ,PickupDiff , PickupResult 挑人算不算互动?? 读取PickupMemberId "[\r\n 35\r\n]"
  616. /// </summary>
  617. /// <param name="lessonBase"></param>
  618. /// <param name="timeLineData"></param>
  619. /// <param name="studentLessonDatas"></param>
  620. /// <param name="lessonId"></param>
  621. /// <returns></returns>
  622. public static (List<StudentLessonData> studentLessonDatas, List<string> pickup) GetPickupData(LessonBase lessonBase, TimeLineData timeLineData, List<StudentLessonData> studentLessonDatas, string lessonId)
  623. {
  624. var enventsPickup = timeLineData?.events.Where(x => !string.IsNullOrWhiteSpace(x.Pgid) && x.Event.Equals("PickupResult"));
  625. List<string> pickup = new List<string>();
  626. if (enventsPickup.IsNotEmpty())
  627. {
  628. foreach (var item in enventsPickup)
  629. {
  630. List<int> mbrs = item.PickupMemberId.ToObject<List<int>>();
  631. // 挑人挑中 TT ,没有挑中 T1
  632. foreach (var studentLessonData in studentLessonDatas)
  633. {
  634. var mbr = mbrs.FindAll(x => studentLessonData.seatID!.Equals($"{x}"));
  635. if (mbr.IsNotEmpty())
  636. {
  637. foreach (var m in mbr)
  638. {
  639. studentLessonData.attend=1;
  640. //studentLessonData.interactRecord.interactRecords.Add(new ItemRecord()
  641. //{
  642. // resultWeight = InteractWeight.TT,
  643. // resultType=InteractReultType.TT,
  644. // itemType = string.IsNullOrWhiteSpace(item.PickupType) ? "PickupResult" : item.PickupType
  645. //});
  646. studentLessonData.pickups.Add(string.IsNullOrWhiteSpace(item.PickupType) ? "1--PickupResult" : $"1--{item.PickupType}");
  647. }
  648. }
  649. else
  650. {
  651. //处理未挑中的
  652. if (studentLessonData.attend==1)
  653. {
  654. studentLessonData.pickups.Add(string.IsNullOrWhiteSpace(item.PickupType) ? "0--PickupResult" : $"0--{item.PickupType}");
  655. }
  656. }
  657. }
  658. pickup.Add(item.PickupType);
  659. }
  660. }
  661. return (studentLessonDatas, pickup);
  662. }
  663. /// <summary>
  664. ///读取互动信息
  665. ///Event 过滤类型 'PopQuesLoad', 'ReAtmpAnsStrt', 'BuzrAns','BuzrLoad'
  666. /// 在IRS.json处理 'PopQuesLoad'互动问答 , 'ReAtmpAnsStrt' 二次作答 , 'BuzrAns' 抢权(新), 'BuzrLoad'抢权(旧)
  667. ///TimeLine.json 中找到对应类型,根据Pgid 去 IRS.json 中找到对应数据,从clientAnswers 的下标对应 base.json 中的 student 找到对应学生信息 clientAnswers.length > 1 则表示有二次作答
  668. ///读取IRS.json
  669. /// </summary>
  670. /// <param name="lessonBase"></param>
  671. /// <param name="timeLineData"></param>
  672. /// <param name="irsDatas"></param>
  673. /// <param name="studentLessonDatas"></param>
  674. /// <param name="examDatas"></param>
  675. /// <param name="itemFiles"></param>
  676. /// <returns></returns>
  677. public static List<StudentLessonData> GetIRSData(LessonBase lessonBase, TimeLineData timeLineData, List<IRSData> irsDatas, List<StudentLessonData> studentLessonDatas, List<ExamData> examDatas, string lessonId)
  678. {
  679. List<string> interactTypes = new List<string>() { "PopQuesLoad", "ReAtmpAnsStrt", "BuzrAns", "BuzrLoad" };
  680. //去重页面
  681. var enventsInteract = timeLineData?.events?.Where(x => !string.IsNullOrWhiteSpace(x.Pgid) && interactTypes.Contains(x.Event)).GroupBy(x => x.Pgid).Select(x => new { key = x.Key, list = x.ToList() });
  682. if (enventsInteract!= null && enventsInteract.Count()>0)
  683. {
  684. var keys = enventsInteract.Select(x => x.key).ToList();
  685. foreach (var item in enventsInteract)
  686. {
  687. ProcessIRSPageData(irsDatas, studentLessonDatas, examDatas, item);
  688. }
  689. //处理其他,评测类型的互动,因为有可能不会记录在TimeLine.json中
  690. var envents_other = timeLineData.events.Where(x => !string.IsNullOrWhiteSpace(x.Pgid) && !keys.Contains(x.Pgid)).GroupBy(x => x.Pgid).Select(x => new { key = x.Key, list = x.ToList() });
  691. if (envents_other!=null && envents_other.Count()>0)
  692. {
  693. foreach (var item in envents_other)
  694. {
  695. ProcessIRSPageData(irsDatas, studentLessonDatas, examDatas, item);
  696. }
  697. }
  698. }
  699. else
  700. {
  701. //处理其他,评测类型的互动,因为有可能不会记录在TimeLine.json中
  702. if (timeLineData!=null)
  703. {
  704. var envents_other = timeLineData.events.Where(x => !string.IsNullOrWhiteSpace(x.Pgid)).GroupBy(x => x.Pgid).Select(x => new { key = x.Key, list = x.ToList() });
  705. if (envents_other!=null && envents_other.Count()>0)
  706. {
  707. foreach (var item in envents_other)
  708. {
  709. ProcessIRSPageData(irsDatas, studentLessonDatas, examDatas, item);
  710. }
  711. }
  712. }
  713. else
  714. {
  715. foreach (var item in irsDatas.Select(x => x.pageID))
  716. {
  717. ProcessIRSPageData(irsDatas, studentLessonDatas, examDatas, new { key = item });
  718. }
  719. }
  720. }
  721. return studentLessonDatas;
  722. }
  723. private static List<StudentLessonData> ProcessIRSPageData(List<IRSData> irsDatas, List<StudentLessonData> studentLessonDatas, List<ExamData> examDatas, dynamic item)
  724. {
  725. var irsDataPages = irsDatas.Where(y => item.key.Equals(y.pageID));
  726. foreach (var irsDataPage in irsDataPages)
  727. {
  728. //检查是否设置正确答案。
  729. var answers_q = irsDataPage.question?["exercise"]?["answer"]?.ToJsonString().ToObject<List<string>>();
  730. //根据题去找对应的试卷和评测信息
  731. var question_id = $"{irsDataPage.question?["id"]}";
  732. var examData = examDatas.Where(x => x.paper!=null && x.paper.slides.Exists(x => !string.IsNullOrWhiteSpace(x.url) && x.url.Equals($"{question_id}.json"))).FirstOrDefault();
  733. List<string> answers = new List<string>();
  734. answers_q?.ForEach(x => {
  735. if (!string.IsNullOrWhiteSpace(x))
  736. {
  737. answers.Add(x);
  738. }
  739. });
  740. var _objective = irsDataPage.question?["exercise"]?["objective"];
  741. var scoreNode = irsDataPage.question?["exercise"]?["score"];
  742. var _type = irsDataPage.question?["exercise"]?["type"];
  743. var _answerType = irsDataPage.question?["exercise"]?["answerType"];//file,audio,text,image
  744. var qitem = irsDataPage.question?["item"]?.AsArray();
  745. if (qitem!=null && qitem.Count()>0)
  746. {
  747. for (var i = 0; i<qitem.Count(); i++)
  748. {
  749. qitem[i]!["question"]="";
  750. }
  751. }
  752. double questionScore = 0;
  753. bool objective = false;
  754. if (_objective!=null)
  755. {
  756. objective = _objective.GetValue<bool>();
  757. }
  758. //题型
  759. string type = string.Empty;
  760. if (_type!=null)
  761. {
  762. //题型
  763. type = _type.GetValue<string>();
  764. List<string> types = new List<string>() { "single", "multiple", "judge", "sortmultiple" };
  765. if (types.Contains(type))
  766. {
  767. objective = true;
  768. }
  769. else
  770. {
  771. objective = false;
  772. }
  773. }
  774. if (_answerType!=null)
  775. {
  776. _answerType.GetValue<string>();
  777. //暂不处理,可能存在依然传文字的情况
  778. //不是文本作答的处理,题目不是客观题,答案不记录
  779. //if (!_answerType.Equals("text"))
  780. //{
  781. // objective=false;
  782. // answers=new List<string>();
  783. //}
  784. }
  785. if (scoreNode!=null)
  786. {
  787. double.TryParse(scoreNode.ToString(), out questionScore);
  788. }
  789. string interactType = string.Empty;
  790. if (irsDataPage.clientAnswers.IsNotEmpty())
  791. {
  792. //第一个list是几轮,一次作答,二次作答, 第二个list是学生的下标, 第三个list是 答案
  793. List<List<List<string>>> clientAnswers = new List<List<List<string>>>();
  794. foreach (var key in irsDataPage.clientAnswers.Keys)
  795. {
  796. clientAnswers.Add(irsDataPage.clientAnswers[key]);
  797. }
  798. // 获取第一个列表的长度作为比较基准
  799. int firstListLength = clientAnswers.First().Count;
  800. bool isSameLength = true;
  801. // 遍历剩余的列表并检查它们的长度是否与第一个列表相同
  802. foreach (var innerList in clientAnswers.Skip(1))
  803. {
  804. if (innerList.Count != firstListLength)
  805. {
  806. isSameLength = false;
  807. break;
  808. }
  809. }
  810. //并检查学生集合的长度是否与第一个列表相同
  811. if (isSameLength && studentLessonDatas.Count()==firstListLength)
  812. {
  813. for (int index = 0; index< clientAnswers[0].Count; index++)
  814. {
  815. var student = studentLessonDatas[index];
  816. double studentScore = 0;
  817. if (examData!=null && examData.examClassResult.IsNotEmpty())
  818. {
  819. var examResultIndex = examData.examClassResult.First().studentIds.IndexOf(student.id);
  820. var questionIndex = examData.paper.slides.Select(x => x.url).ToList().IndexOf($"{question_id}.json");
  821. if (examResultIndex>=0
  822. && examData.examClassResult.First().studentScores.Count>=(examResultIndex+1) //防止索引越界
  823. && examData.examClassResult.First().studentScores[examResultIndex].Count>=(questionIndex+1)) //防止索引越界
  824. {
  825. //获取index学生在questionIndex题的分数
  826. studentScore = examData.examClassResult.First().studentScores[examResultIndex][questionIndex];
  827. }
  828. }
  829. //index 代表学生下标
  830. List<ItemRecord> interactRecords = new List<ItemRecord>();
  831. if (clientAnswers.Count==1)
  832. {
  833. //即问即答
  834. interactType = "PopQuesLoad";
  835. var ans0 = clientAnswers[0][index];
  836. var IS0 = GetInteractResultHasAnswer(answers, ans0, objective, type, questionScore, studentScore);
  837. interactRecords.Add(new ItemRecord()
  838. {
  839. resultWeight = IS0.weight,
  840. resultType=IS0.reultType,
  841. itemType= interactType,
  842. criterion= questionScore,
  843. itemScore= IS0.interactScore
  844. });
  845. }
  846. if (clientAnswers.Count==2)
  847. {
  848. //二次作答
  849. interactType="ReAtmpAnsStrt";
  850. var ans1 = clientAnswers[1][index];
  851. var IS1 = GetInteractResultHasAnswer(answers, ans1, objective, type, questionScore, studentScore);
  852. interactRecords.Add(new ItemRecord()
  853. {
  854. resultWeight = IS1.weight,
  855. resultType=IS1.reultType,
  856. itemType= interactType,
  857. criterion= questionScore,
  858. itemScore= IS1.interactScore
  859. });
  860. }
  861. if (clientAnswers.Count>2)
  862. {
  863. //三次作答
  864. interactType="TeAtmpAnsStrt";
  865. var ans2 = clientAnswers[2][index];
  866. var IS2 = GetInteractResultHasAnswer(answers, ans2, objective, type, questionScore, studentScore);
  867. interactRecords.Add(new ItemRecord()
  868. {
  869. resultWeight = IS2.weight,
  870. resultType=IS2.reultType,
  871. itemType= interactType,
  872. criterion= questionScore,
  873. itemScore= IS2.interactScore
  874. });
  875. }
  876. if (studentLessonDatas[index].attend==1)
  877. {
  878. studentLessonDatas[index].interactRecord.interactRecords.AddRange(interactRecords);
  879. }
  880. }
  881. }
  882. }
  883. //是否抢权作答的模式
  884. if (irsDataPage.isBuzz)
  885. {
  886. interactType = "BuzrAns";
  887. //处理参与抢权的
  888. Dictionary<string, ItemRecord> buzzParticipants = new Dictionary<string, ItemRecord>();
  889. foreach (var buzzParticipant in irsDataPage.buzzParticipants)
  890. {
  891. var studentData = studentLessonDatas.Find(x => x.seatID!.Equals(buzzParticipant));
  892. if (studentData != null)
  893. {
  894. buzzParticipants[buzzParticipant]=new ItemRecord() { resultWeight = InteractWeight.T1, itemType= interactType, resultType= InteractReultType.T1 };
  895. }
  896. }
  897. //处理抢权成功的
  898. foreach (var buzzClient in irsDataPage.buzzClients)
  899. {
  900. buzzParticipants[buzzClient]=new ItemRecord() { resultWeight = InteractWeight.TT, itemType= interactType, resultType= InteractReultType.TT };
  901. }
  902. foreach (var studentLessonData in studentLessonDatas)
  903. {
  904. if (buzzParticipants.ContainsKey(studentLessonData.seatID!))
  905. {
  906. //处理已经有抢权结果的数据
  907. studentLessonData.attend=1;
  908. studentLessonData.interactRecord.interactRecords.Add(buzzParticipants[studentLessonData.seatID!]);
  909. }
  910. else
  911. {
  912. if (studentLessonData.attend==1)
  913. {
  914. //处理未参与抢权的
  915. studentLessonData.interactRecord.interactRecords.Add(new ItemRecord() { resultWeight = InteractWeight.T0, itemType = interactType, resultType= InteractReultType.T0 });
  916. }
  917. }
  918. }
  919. }
  920. }
  921. return studentLessonDatas;
  922. }
  923. private static (double weight, string reultType, double interactScore) GetInteractResultHasAnswer(List<string>? answers, List<string> ans0, bool objective, string type, double questionScore, double studentScore)
  924. {
  925. //List<string> ans0 = new List<string>();
  926. //ans?.ForEach(x => {
  927. // if (!string.IsNullOrWhiteSpace(x))
  928. // {
  929. // ans0.Add(x);
  930. // }
  931. // else { ans.Add("");}
  932. //});
  933. double weight = InteractWeight.T0;
  934. string reultType = InteractReultType.T0;
  935. double interactScore = 0;
  936. if (answers.IsNotEmpty())
  937. {
  938. if (ans0.IsNotEmpty())
  939. {
  940. if (objective) //客观题
  941. {
  942. //标准答案等于作答的结果
  943. if (answers!.Count == ans0.Count)
  944. {
  945. if (answers.All(item => ans0.Contains(item)))
  946. {
  947. //完全正确
  948. weight= InteractWeight.TT;
  949. reultType= InteractReultType.TT;
  950. interactScore= studentScore==0 ? questionScore : studentScore;
  951. }
  952. else
  953. {
  954. //作答错误
  955. weight= InteractWeight.T1;
  956. reultType = InteractReultType.T1;
  957. interactScore= studentScore;
  958. }
  959. }
  960. //标准答案比作答的结果多
  961. else if (answers!.Count > ans0.Count)
  962. {
  963. if (ans0.All(item => answers.Contains(item)))
  964. {
  965. //部分正确
  966. weight= InteractWeight.TP;
  967. reultType = InteractReultType.TP;
  968. // 2 * 0.3 * 10= 6
  969. interactScore= studentScore==0 ? 1/(InteractWeight.TT-InteractWeight.T1) * (InteractWeight.TP-InteractWeight.T1) * questionScore : studentScore;
  970. }
  971. else
  972. {
  973. //作答错误
  974. weight= InteractWeight.T1;
  975. reultType = InteractReultType.T1;
  976. interactScore= studentScore;
  977. }
  978. }
  979. //标准答案比作答结果少
  980. else
  981. {
  982. //作答错误
  983. weight= InteractWeight.T1;
  984. reultType = InteractReultType.T1;
  985. interactScore= studentScore;
  986. }
  987. }
  988. else
  989. {
  990. //填空题
  991. if ("complete".Equals(type) && answers!.Count==ans0.Count)
  992. {
  993. bool hasT = false;
  994. bool hasF = false;
  995. for (int i = 0; i < answers!.Count; i++)
  996. {
  997. if (answers[i].Equals(ans0[i]))
  998. {
  999. hasT=true;
  1000. }
  1001. else
  1002. {
  1003. hasF=true;
  1004. }
  1005. }
  1006. if (hasT && !hasF)
  1007. {
  1008. //完全正确
  1009. weight= InteractWeight.TT;
  1010. reultType = InteractReultType.TT;
  1011. interactScore= studentScore==0 ? questionScore : studentScore;
  1012. }
  1013. else if (hasT && hasF)
  1014. {
  1015. //部分正确
  1016. weight= InteractWeight.TP;
  1017. reultType = InteractReultType.TP;
  1018. // 2 * 0.3 * 10= 6
  1019. interactScore= studentScore==0 ? 1/(InteractWeight.TT-InteractWeight.T1) * (InteractWeight.TP-InteractWeight.T1) * questionScore : studentScore;
  1020. }
  1021. else if (!hasT && hasF)
  1022. {
  1023. //没有正确的,但有错误的,代表参与了
  1024. weight= InteractWeight.T1;
  1025. reultType = InteractReultType.T1;
  1026. interactScore= studentScore;
  1027. }
  1028. else if (!hasT && !hasF)
  1029. {
  1030. //没有正确的,也没有错误的,代表没有作答
  1031. weight= InteractWeight.T0;
  1032. reultType = InteractReultType.T0;
  1033. interactScore= studentScore;
  1034. }
  1035. }
  1036. else
  1037. {
  1038. //主观题,完全匹配的
  1039. if (answers!.All(item => ans0.Contains(item)))
  1040. {
  1041. //完全正确
  1042. weight= InteractWeight.TT;
  1043. reultType = InteractReultType.TT;
  1044. interactScore= studentScore==0 ? questionScore : studentScore;
  1045. }
  1046. else
  1047. { // 使用LINQ查询来判断是否有匹配的答案
  1048. bool hasMatchingAnswer = answers!.Intersect(ans0).Any();
  1049. if (hasMatchingAnswer)
  1050. {
  1051. //主观题回答正确即为完全正确
  1052. weight= InteractWeight.TT;
  1053. reultType = InteractReultType.TT;
  1054. interactScore= studentScore==0 ? questionScore : studentScore;
  1055. }
  1056. else
  1057. {
  1058. //优先根据得分与标准分的占比算出得分率,如果没有得分率,如果是直接从互动,不知道是评测的 需要先去评测找作答得分。,则采用Levenshtein距离来评估两个字符串的相似度
  1059. //没有匹配上答案,则采用Levenshtein距离来评估两个字符串的相似度
  1060. if (questionScore>0)
  1061. {
  1062. if (studentScore>0)
  1063. {
  1064. weight = studentScore * 1.0 / questionScore* (InteractWeight.TT-InteractWeight.T1);
  1065. if (weight==InteractWeight.T1)
  1066. {
  1067. reultType = InteractReultType.T1;
  1068. interactScore=studentScore;
  1069. }
  1070. else if (weight>InteractWeight.TT)
  1071. {
  1072. reultType = InteractReultType.TT;
  1073. interactScore=studentScore==0 ? questionScore : studentScore;
  1074. }
  1075. else
  1076. {
  1077. reultType = InteractReultType.TP;
  1078. // 2 * 0.3 * 10= 6
  1079. interactScore= studentScore==0 ? 1/(InteractWeight.TT-InteractWeight.T1) * (InteractWeight.TP-InteractWeight.T1) * questionScore : studentScore;
  1080. }
  1081. }
  1082. else
  1083. {
  1084. weight=InteractWeight.T1+(CalculateSimilarity(answers![0], ans0[0]) *(InteractWeight.TT-InteractWeight.T1));
  1085. if (weight==InteractWeight.T1)
  1086. {
  1087. reultType = InteractReultType.T1;
  1088. interactScore=studentScore;
  1089. }
  1090. else if (weight>InteractWeight.TT)
  1091. {
  1092. reultType = InteractReultType.TT;
  1093. interactScore=studentScore==0 ? questionScore : studentScore;
  1094. }
  1095. else
  1096. {
  1097. reultType = InteractReultType.TP;
  1098. // 2 * 0.3 * 10= 6
  1099. interactScore= studentScore==0 ? 1/(InteractWeight.TT-InteractWeight.T1) * (InteractWeight.TP-InteractWeight.T1) * questionScore : studentScore;
  1100. }
  1101. }
  1102. }
  1103. else
  1104. {
  1105. weight=InteractWeight.T1+(CalculateSimilarity(answers![0], ans0[0]) *(InteractWeight.TT-InteractWeight.T1));
  1106. if (weight==InteractWeight.T1)
  1107. {
  1108. reultType = InteractReultType.T1;
  1109. interactScore=studentScore;
  1110. }
  1111. else if (weight>InteractWeight.TT)
  1112. {
  1113. reultType = InteractReultType.TT;
  1114. interactScore=studentScore==0 ? questionScore : studentScore;
  1115. }
  1116. else
  1117. {
  1118. reultType = InteractReultType.TP;
  1119. // 2 * 0.3 * 10= 6
  1120. interactScore= studentScore==0 ? 1/(InteractWeight.TT-InteractWeight.T1) * (InteractWeight.TP-InteractWeight.T1) * questionScore : studentScore;
  1121. }
  1122. }
  1123. }
  1124. }
  1125. }
  1126. }
  1127. }
  1128. else
  1129. {
  1130. //没有作答
  1131. weight= InteractWeight.T0;
  1132. reultType = InteractReultType.T0;
  1133. interactScore=studentScore;
  1134. }
  1135. }
  1136. else
  1137. {
  1138. //没有标准答案的情况
  1139. if (ans0.IsNotEmpty())
  1140. {
  1141. bool hasAns = false;
  1142. ans0.ForEach(x => {
  1143. if (!string.IsNullOrWhiteSpace(x))
  1144. {
  1145. hasAns = true;
  1146. }
  1147. });
  1148. if (hasAns)
  1149. {
  1150. //作答了
  1151. weight= InteractWeight.T1;
  1152. reultType = InteractReultType.T1;
  1153. interactScore=studentScore;
  1154. }
  1155. else
  1156. {
  1157. //没有作答
  1158. weight= InteractWeight.T0;
  1159. reultType = InteractReultType.T0;
  1160. interactScore=studentScore;
  1161. }
  1162. }
  1163. else
  1164. {
  1165. //没有作答
  1166. weight= InteractWeight.T0;
  1167. reultType = InteractReultType.T0;
  1168. interactScore=studentScore;
  1169. }
  1170. //如果教师手动给了分或AI评分
  1171. if (questionScore>0 && studentScore>0)
  1172. {
  1173. weight = studentScore * 1.0 / questionScore* (InteractWeight.TT-InteractWeight.T1);
  1174. if (weight==InteractWeight.T1)
  1175. {
  1176. reultType = InteractReultType.T1;
  1177. interactScore=studentScore;
  1178. }
  1179. else if (weight>InteractWeight.TT)
  1180. {
  1181. reultType = InteractReultType.TT;
  1182. interactScore=studentScore==0 ? questionScore : studentScore;
  1183. }
  1184. else
  1185. {
  1186. reultType = InteractReultType.TP;
  1187. interactScore= studentScore==0 ? 1/(InteractWeight.TT-InteractWeight.T1) * (InteractWeight.TP-InteractWeight.T1) * questionScore : studentScore;
  1188. }
  1189. }
  1190. }
  1191. return (weight, reultType, interactScore);
  1192. }
  1193. /// <summary>
  1194. /// C# 代码 如何判断两句话是否一个意思,非机器学习的算法。使用Levenshtein距离来评估两个字符串的相似度,但是不能判断它们是否表达了同一个意思,后续借助AI实现
  1195. /// </summary>
  1196. /// <param name="s1"></param>
  1197. /// <param name="s2"></param>
  1198. /// <returns></returns>
  1199. public static double CalculateSimilarity(string s1, string s2)
  1200. {
  1201. int n = s1.Length;
  1202. int m = s2.Length;
  1203. int[,] d = new int[n + 1, m + 1];
  1204. for (int i = 0; i <= n; i++)
  1205. {
  1206. d[i, 0] = i;
  1207. }
  1208. for (int j = 0; j <= m; j++)
  1209. {
  1210. d[0, j] = j;
  1211. }
  1212. for (int i = 1; i <= n; i++)
  1213. {
  1214. for (int j = 1; j <= m; j++)
  1215. {
  1216. int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
  1217. d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost);
  1218. }
  1219. }
  1220. return (1.0 - ((double)d[n, m] / Math.Max(s1.Length, s2.Length)));
  1221. }
  1222. public static async Task<List<ExamData>> GetExamInfo(LessonRecord item, TimeLineData? timeLineData, AzureStorageFactory _azureStorage/*,ILogger<LessonETLService> _logger*/, string owner)
  1223. {
  1224. //读取ExamData
  1225. List<ExamData> examDatas = new List<ExamData>();
  1226. try
  1227. {
  1228. var examPages = timeLineData?.events.Where(x => !string.IsNullOrWhiteSpace(x.Pgid) && !string.IsNullOrWhiteSpace(x.ExamId));
  1229. if (examPages!=null && examPages.Count()>0)
  1230. {
  1231. var examsFiles = await _azureStorage.GetBlobContainerClient(owner).List($"records/{item.id}/Exam");
  1232. var paperFiles = await _azureStorage.GetBlobContainerClient(owner).List($"records/{item.id}/ExamPaper");
  1233. foreach (var examsFile in examsFiles)
  1234. {
  1235. if (examsFile.EndsWith("Exam.json"))
  1236. {
  1237. ExamData? examData = null;
  1238. try
  1239. {
  1240. BlobDownloadResult examDataDownload = await _azureStorage.GetBlobContainerClient(owner).GetBlobClient(examsFile).DownloadContentAsync();
  1241. var str = examDataDownload.Content.ToString().Replace("\r\n", "").Replace("\ufeff", "").Replace("\"publish\": \"0\"", "\"publish\": 0").Replace("\"publish\": \"1\"", "\"publish\": 1");
  1242. examData= str.ToObject<ExamData>();
  1243. // examData = examDataDownload.Content.ToObjectFromJson<ExamData>();
  1244. }
  1245. catch (Exception ex)
  1246. {
  1247. if (!ex.Message.Contains("The specified blob does not exist"))
  1248. {
  1249. // _logger.LogError(ex, $"文件不存在:{examsFile}");
  1250. }
  1251. }
  1252. if (examData!=null && examData.exam.papers.IsNotEmpty())
  1253. {
  1254. string paperId = examData.exam.papers.First().id;
  1255. if (_azureStorage.GetBlobContainerClient(owner).GetBlobClient($"/records/{item.id}/ExamPaper/{paperId}/index.json").Exists())
  1256. {
  1257. LessonPaper lessonPaper = null;
  1258. try
  1259. {
  1260. BlobDownloadResult paperblobDownload = await _azureStorage.GetBlobContainerClient(owner).GetBlobClient($"/records/{item.id}/ExamPaper/{paperId}/index.json").DownloadContentAsync();
  1261. lessonPaper = paperblobDownload.Content.ToObjectFromJson<LessonPaper>();
  1262. examData.paper = lessonPaper;
  1263. }
  1264. catch (Exception ex)
  1265. {
  1266. if (!ex.Message.Contains("The specified blob does not exist"))
  1267. {
  1268. // _logger.LogError(ex, $"文件不存在:/records/{item.id}/ExamPaper/{paperId}/index.json");
  1269. }
  1270. }
  1271. }
  1272. examDatas.Add(examData);
  1273. }
  1274. }
  1275. }
  1276. }
  1277. }
  1278. catch (Exception ex)
  1279. {
  1280. // _logger.LogError(ex, ex.Message);
  1281. }
  1282. return examDatas;
  1283. }
  1284. /// <summary>
  1285. /// 获取课中评测数据
  1286. /// </summary>
  1287. /// <param name="lessonBase"></param>
  1288. /// <param name="timeLineData"></param>
  1289. /// <param name="examDatas"></param>
  1290. /// <param name="studentLessonDatas"></param>
  1291. /// <param name="objectiveTypes"></param>
  1292. /// <returns></returns>
  1293. public static List<StudentLessonData> GetExamData(LessonBase lessonBase, TimeLineData timeLineData, List<ExamData> examDatas, List<StudentLessonData> studentLessonDatas, List<string> objectiveTypes, string lessonId)
  1294. {
  1295. foreach (var examData in examDatas)
  1296. {
  1297. //直接取第一个元素的试卷,因为在HiTeach中,只会是一个试卷(一个科目),一个班参与。
  1298. var allocation = examData?.exam?.papers?.FirstOrDefault()?.point?.Sum();
  1299. var answersStd = examData?.exam?.papers?.FirstOrDefault()?.answers;
  1300. List<List<string>> answers = new List<List<string>>();
  1301. if (answersStd!=null)
  1302. {
  1303. answersStd.ForEach(x => //去除[""]此种类型的标准答案,转为[]
  1304. {
  1305. List<string> ans = new List<string>();
  1306. if (x.Count!=0)
  1307. {
  1308. if (x.Count==1)
  1309. {
  1310. if (string.IsNullOrWhiteSpace(x[0]))
  1311. {
  1312. answers.Add(ans);
  1313. }
  1314. else
  1315. {
  1316. answers.Add(x);
  1317. }
  1318. }
  1319. else
  1320. {
  1321. answers.Add(x);
  1322. }
  1323. }
  1324. else
  1325. {
  1326. answers.Add(ans);
  1327. }
  1328. });
  1329. }
  1330. examData?.examClassResult?.ForEach(item => {
  1331. //学生下标
  1332. int index = 0;
  1333. if (item.studentAnswersArray.Count()>0
  1334. && item.studentAnswersArray.Count() == item.studentIds.Count() //学生作答数量和学生id数量一致
  1335. && item.studentScores.Count()==item.studentIds.Count()) //学生分数和学生id数量一致
  1336. {
  1337. item.studentAnswersArray.ForEach(stu => {
  1338. var student = studentLessonDatas.Find(x => x.id!.Equals(item.studentIds[index]));
  1339. if (student!=null && student.attend==1)
  1340. {
  1341. //是否要判断主观题或者客观题, 多套试卷,有主观题的
  1342. //,如果没获得结果,
  1343. //主观题有回答的:608942756458532864\Clients\18782481024\Ans\27-4341670635487887360-examExchangeAnswerlist
  1344. //27 从1开始的学生序号-4341670635487887360评测编号,内容qNo 是从1开始的题号。
  1345. if (stu.IsNotEmpty() && answers.Count()==stu.Count && examData.exam.papers[0].type.Count()==answers.Count)
  1346. {
  1347. StudentExamRecord studentExam = new StudentExamRecord();
  1348. var studentScore = item.studentScores[index];
  1349. List<ItemRecord> answerRecords = new List<ItemRecord>();
  1350. //题目下标
  1351. int itemIndex = 0;
  1352. stu.ForEach(ans =>
  1353. {
  1354. bool objective = objectiveTypes.Contains(examData.exam.papers[0].type[itemIndex]);
  1355. var questionScore = examData.exam.papers[0].point[itemIndex];
  1356. string type = examData.exam.papers[0].type[itemIndex];
  1357. var res = GetInteractResultHasAnswer(answers[itemIndex], ans, objective, type, questionScore, studentScore[itemIndex]);
  1358. ItemRecord interactRecord = new ItemRecord()
  1359. {
  1360. itemType="SPQStrt",//类型
  1361. resultType=res.reultType,//作答结果类型
  1362. resultWeight=res.weight,//得分权重
  1363. criterion= questionScore,//标准分
  1364. itemScore= studentScore[itemIndex]//得分
  1365. };
  1366. answerRecords.Add(interactRecord);
  1367. itemIndex++;
  1368. });
  1369. studentExam.score= answerRecords.Where(x => x.itemScore>=0).Select(x => x.itemScore).Sum();//得分
  1370. studentExam.scoreRate= allocation.HasValue && allocation.Value>0 ? studentExam.score * 1.0/allocation.Value : 0;//得分率
  1371. studentExam.answerRate= answerRecords.Where(x => x.resultWeight>0).Count()*1.0/studentScore.Count();//作答率
  1372. studentExam.examId=examData.exam.id;
  1373. studentExam.itemRecords=answerRecords;
  1374. student.examRecords.Add(studentExam);
  1375. }
  1376. }
  1377. index++;
  1378. });
  1379. }
  1380. });
  1381. }
  1382. return studentLessonDatas;
  1383. }
  1384. /// <summary>
  1385. /// 协作参与率 态度计算
  1386. /// </summary>
  1387. /// <param name="lessonBase"></param>
  1388. /// <param name="timeLineData"></param>
  1389. /// <param name="coworkDatas"></param>
  1390. /// <param name="studentLessonDatas"></param>
  1391. /// <param name="lessonId"></param>
  1392. /// <returns></returns>
  1393. public static List<StudentLessonData> GetCoworkData(LessonBase lessonBase, TimeLineData timeLineData, List<CoworkData> coworkDatas, List<StudentLessonData> studentLessonDatas, string lessonId)
  1394. {
  1395. int p = 0;
  1396. foreach (var coworkData in coworkDatas)
  1397. {
  1398. var keys = coworkData.participateLevelList.Keys;
  1399. foreach (var key in keys)
  1400. {
  1401. var student = studentLessonDatas.Find(x => x.seatID!.Equals(key));
  1402. if (student!=null && student.attend==1)
  1403. {
  1404. var score = coworkData.participateLevelList[key];//协作得分,是否是经过指数计算的
  1405. var itemRecord = new ItemRecord { criterion=-1, itemType= coworkData.coworkType, itemScore=score, isGroup= coworkData.coworkType.Equals("Group") ? true : false };
  1406. //不能完全依赖
  1407. if (score>0)
  1408. {
  1409. student.coworkScore.Add(score);
  1410. itemRecord.resultWeight = InteractWeight.TP;
  1411. itemRecord.resultType = InteractReultType.TP;
  1412. }
  1413. else
  1414. {
  1415. itemRecord.resultWeight = InteractWeight.T0;
  1416. itemRecord.resultType = InteractReultType.T0;
  1417. }
  1418. student.coworkRecord.itemRecords.Add(itemRecord);
  1419. }
  1420. if (key.Contains("g", StringComparison.OrdinalIgnoreCase))
  1421. {
  1422. string groupId = key.Replace("g", "").Replace("G", "");
  1423. var score = coworkData.participateLevelList[key];
  1424. if (score>0)
  1425. {
  1426. var groupStu = studentLessonDatas.FindAll(x => x.attend==1 && !string.IsNullOrWhiteSpace(x.groupId) && x.groupId.Equals(groupId));
  1427. if (groupStu.IsNotEmpty())
  1428. {
  1429. foreach (var stu in groupStu)
  1430. {
  1431. stu.group_coworkScore.Add(score);
  1432. stu.coworkRecord.itemRecords[p].itemScore+=score;
  1433. stu.coworkRecord.itemRecords[p].resultWeight=InteractWeight.TP;
  1434. stu.coworkRecord.itemRecords[p].resultType=InteractReultType.TP;
  1435. }
  1436. }
  1437. }
  1438. }
  1439. }
  1440. var order = studentLessonDatas.Where(x => x.attend==1).OrderByDescending(x => x.coworkRecord.itemRecords[p].itemScore);
  1441. var maxItems = studentLessonDatas.FindAll(x => x.attend==1&& x.coworkRecord.itemRecords[p].itemScore==order.First().coworkRecord.itemRecords[p].itemScore);
  1442. var max = studentLessonDatas.FindAll(x => x.attend==1&& x.coworkRecord.itemRecords[p].itemScore==order.First().coworkRecord.itemRecords[p].itemScore).First().coworkRecord.itemRecords[p].itemScore;
  1443. var min = studentLessonDatas.FindAll(x => x.attend==1&& x.coworkRecord.itemRecords[p].itemScore==order.Last().coworkRecord.itemRecords[p].itemScore).First().coworkRecord.itemRecords[p].itemScore;
  1444. var sum = studentLessonDatas.Where(x => x.attend==1).Sum(x => x.coworkRecord.itemRecords[p].itemScore);
  1445. foreach (var student in studentLessonDatas)
  1446. {
  1447. if (student.attend==1 && student.coworkRecord.itemRecords.Count>=p+1 && student.coworkRecord.itemRecords[p].itemScore>0)
  1448. {
  1449. student.coworkRecord.itemRecords[p].resultType=InteractReultType.TP;
  1450. var data = MinMaxNormalization(min, max, student.coworkRecord.itemRecords[p].itemScore);
  1451. student.coworkRecord.itemRecords[p].resultWeight=InteractWeight.T1+ data * 1.0 / 100 * (InteractWeight.TT-InteractWeight.T1);
  1452. if (maxItems.Select(x => x.seatID).Contains(student.seatID))
  1453. {
  1454. student.coworkRecord.itemRecords[p].resultType= InteractReultType.TT;
  1455. student.coworkRecord.itemRecords[p].resultWeight= InteractWeight.TT;
  1456. }
  1457. }
  1458. }
  1459. p++;
  1460. }
  1461. return studentLessonDatas;
  1462. }
  1463. /// <summary>
  1464. /// 处理学生回推数据,并将回推纳入学习态度计算。
  1465. /// </summary>
  1466. /// <param name="lessonBase"></param>
  1467. /// <param name="timeLineData"></param>
  1468. /// <param name="taskDatas"></param>
  1469. /// <param name="studentLessonDatas"></param>
  1470. /// <returns></returns>
  1471. public static List<StudentLessonData> GetTaskData(LessonBase lessonBase, TimeLineData timeLineData, List<TaskData> taskDatas, List<StudentLessonData> studentLessonDatas, string lessonId)
  1472. {
  1473. //协作也算任务的一种,'WrkSpaceLoad' 作品收集, "isGroupItem": false,
  1474. int indexTask = 0;
  1475. foreach (var taskData in taskDatas)
  1476. {
  1477. //作品收集是全部人员都要参加
  1478. foreach (var student in studentLessonDatas)
  1479. {
  1480. if (student.attend==1)
  1481. {
  1482. var work = taskData.clientWorks.Find(x => $"{x.seatID}".Equals(student.seatID));
  1483. if (work!= null)
  1484. {
  1485. if (work.blobFiles.Count>0)
  1486. {
  1487. student.uploadCount.Add(work.blobFiles.Count);
  1488. }
  1489. student.taskRecord.itemRecords.Add(new ItemRecord { itemType="WrkSpaceLoad", itemScore=work.blobFiles.Count *10, resultWeight=InteractWeight.TT, resultType=InteractReultType.TT, isGroup= work.isGroupItem, optCount=work.blobFiles.Count });
  1490. }
  1491. else
  1492. {
  1493. student.taskRecord.itemRecords.Add(new ItemRecord { itemType="WrkSpaceLoad", itemScore=0, resultWeight=InteractWeight.T0, resultType=InteractReultType.T0, isGroup= false });
  1494. }
  1495. }
  1496. }
  1497. ////////
  1498. ///需要处理小组的情况,当前人员没有提交作品,但是有可能是小组其他人员提交了,需要判断一下。
  1499. ///
  1500. var students = studentLessonDatas.FindAll(x => x.attend==1 && x.taskRecord.itemRecords[indexTask].isGroup==true);
  1501. foreach (var student in students)
  1502. {
  1503. var groupStudents = studentLessonDatas.FindAll(x => x.id!=student.id && x.attend==1 && !string.IsNullOrWhiteSpace(x.groupId) && x.groupId.Equals(student.groupId));
  1504. foreach (var groupstudent in groupStudents)
  1505. {
  1506. groupstudent.taskRecord.itemRecords[indexTask].isGroup=true;
  1507. groupstudent.taskRecord.itemRecords[indexTask].optCount=student.taskRecord.itemRecords[indexTask].optCount;
  1508. groupstudent.taskRecord.itemRecords[indexTask].itemScore=student.taskRecord.itemRecords[indexTask].itemScore;
  1509. groupstudent.taskRecord.itemRecords[indexTask].resultWeight=student.taskRecord.itemRecords[indexTask].resultWeight;
  1510. groupstudent.taskRecord.itemRecords[indexTask].resultType=student.taskRecord.itemRecords[indexTask].resultType;
  1511. }
  1512. }
  1513. var groupDatas = taskData.clientWorks.FindAll(x => x.seatID==0 && x.isGroupItem);
  1514. foreach (var groupData in groupDatas)
  1515. {
  1516. var groupStudents = studentLessonDatas.FindAll(x => x.attend==1 && !string.IsNullOrWhiteSpace(x.groupId) && x.groupId.Equals(groupData.groupID));
  1517. foreach (var student in groupStudents)
  1518. {
  1519. student.taskRecord.itemRecords[indexTask].isGroup=true;
  1520. student.taskRecord.itemRecords[indexTask].optCount=groupData.blobFiles.Count;
  1521. student.taskRecord.itemRecords[indexTask].itemScore= 10* groupData.blobFiles.Count;
  1522. if (groupData.blobFiles.Count>0)
  1523. {
  1524. student.taskRecord.itemRecords[indexTask].resultWeight=InteractWeight.TT;
  1525. student.taskRecord.itemRecords[indexTask].resultType=InteractReultType.TT;
  1526. }
  1527. else
  1528. {
  1529. student.taskRecord.itemRecords[indexTask].resultWeight=InteractWeight.T0;
  1530. student.taskRecord.itemRecords[indexTask].resultType=InteractReultType.T0;
  1531. }
  1532. }
  1533. }
  1534. indexTask++;
  1535. }
  1536. return studentLessonDatas;
  1537. }
  1538. /// <summary>
  1539. ///评分参与率 态度计算
  1540. ///读取互评信息
  1541. ///评分相关 在SmartRating.json 处理 GrandRating 星光大评分, 投票Voting 和 PeerAssessment(All每人多件评分,Two随机分配互评, Self自评)
  1542. ///Event 过滤类型 'RatingStart'
  1543. ///smartRateSummary.mutualSummary.mutualType 互评【All(每人多件评分) Two(随机分配互评) Self(自评)】 smartRateSummary.meteor_VoteSummary 投票
  1544. ///读取SmartRating.json
  1545. /// </summary>
  1546. /// <param name="lessonBase"></param>
  1547. /// <param name="timeLineData"></param>
  1548. /// <param name="smartRatingDatas"></param>
  1549. /// <param name="studentLessonDatas"></param>
  1550. /// <returns></returns>
  1551. public static List<StudentLessonData> GetSmartRatingData(LessonBase lessonBase, TimeLineData timeLineData, List<SmartRatingData> smartRatingDatas, List<StudentLessonData> studentLessonDatas, string lessonId)
  1552. {
  1553. int index = 0;
  1554. foreach (var smartRatingData in smartRatingDatas)
  1555. {
  1556. string type = "";
  1557. //投票类型的
  1558. var keys_vote = smartRatingData.smartRateSummary?.meteor_VoteSummary?.Keys?.ToList();
  1559. if (keys_vote.IsNotEmpty())
  1560. {
  1561. type="Voting";
  1562. bool addData = false;
  1563. foreach (var key in keys_vote!)
  1564. {
  1565. try
  1566. {
  1567. //问题数据F:\lesson-local\632424798693232640-local.json pclxxx
  1568. if (smartRatingData.smartRateSummary!.voteDetailResult.TryGetValue(key, out var value))
  1569. {
  1570. var voteDetailResults = smartRatingData.smartRateSummary!.voteDetailResult[key];
  1571. foreach (var student in studentLessonDatas)
  1572. {
  1573. if (student.attend==1)
  1574. {
  1575. //投票是全员参与
  1576. var datasS = voteDetailResults.FindAll(x => x.id.Equals(student.seatID));
  1577. if (datasS.IsNotEmpty())
  1578. {
  1579. //T1,只有评论别人,没被别人评论 或者是评论了别人,但是没有被别人评论,
  1580. student.rateingRecord.itemRecords.Add(new ItemRecord { itemType=type, resultType=InteractReultType.T1, resultWeight = InteractWeight.T1 });
  1581. addData=true;
  1582. }
  1583. else
  1584. { //T0 是没有评论别人,也没被别人评论,
  1585. student.rateingRecord.itemRecords.Add(new ItemRecord { itemType=type, resultType=InteractReultType.T0, resultWeight = InteractWeight.T0 });
  1586. addData=true;
  1587. }
  1588. //T0 是没有评论别人,也没被别人评论,
  1589. //T1,只有评论别人,没被别人评论 或者是评论了别人,但是没有被别人评论,
  1590. //TP 有被别人评论,且评论了别人,
  1591. //TT是评论了别人,且被别人评论次数最高,或者分值最高。
  1592. }
  1593. }
  1594. }
  1595. }
  1596. catch (Exception ex)
  1597. {
  1598. throw new Exception($"{lessonId}\n{ex.Message}\n{ex.StackTrace}");
  1599. }
  1600. var meteor_VoteSummary = smartRatingData.smartRateSummary!.meteor_VoteSummary[key];
  1601. var order = meteor_VoteSummary.OrderByDescending(x => x.result);
  1602. var maxItems = meteor_VoteSummary.FindAll(x => x.result==order.First().result);
  1603. var max = meteor_VoteSummary.FindAll(x => x.result==order.First().result).First().result;
  1604. var min = meteor_VoteSummary.FindAll(x => x.result==order.Last().result).First().result;
  1605. var sum = meteor_VoteSummary.Sum(x => x.result);
  1606. //排名指数计算=( 当前值分数- 298) / (9992 - 298) * (99 - 60) + 60
  1607. //将每个人的积分转化为60-100
  1608. //排名 = (积分 - 最低积分) / (最高积分 - 最低积分) * (最大排名 - 最小排名) + 最小排名
  1609. foreach (var datasD in meteor_VoteSummary)
  1610. {
  1611. //有被人评论或投票
  1612. var student = studentLessonDatas.Find(x => x.seatID!.Equals(datasD.id));
  1613. if (student!=null)
  1614. {
  1615. if (index<student.rateingRecord.itemRecords.Count && student.rateingRecord.itemRecords[index].itemType!.Equals(type))
  1616. {
  1617. if (student.rateingRecord.itemRecords[index].resultType!.Equals(InteractReultType.T0))
  1618. {
  1619. //T1,只有评论别人,没被别人评论 或者是评论了别人,但是没有被别人评论,
  1620. student.rateingRecord.itemRecords[index].resultType= InteractReultType.T1;
  1621. student.rateingRecord.itemRecords[index].resultWeight= InteractWeight.T1;
  1622. }
  1623. else if (student.rateingRecord.itemRecords[index].resultType!.Equals(InteractReultType.T1))
  1624. {
  1625. //TP 有被别人评论,且评论了别人,
  1626. student.rateingRecord.itemRecords[index].resultType= InteractReultType.TP;
  1627. var data = MinMaxNormalization(min, max, datasD.result);
  1628. //student.rateingRecord.itemRecords[index].resultWeight= InteractWeight.TP;
  1629. student.rateingRecord.itemRecords[index].resultWeight=InteractWeight.T1+ data * 1.0 / 100 * (InteractWeight.TT-InteractWeight.T1);
  1630. //获得的票数
  1631. student.rateingRecord.itemRecords[index].itemScore=datasD.result;
  1632. //TT是评论了别人,且被别人评论次数最高,或者分值最高。
  1633. if (maxItems.Select(x => x.id).Contains(student.seatID))
  1634. {
  1635. student.rateingRecord.itemRecords[index].resultType= InteractReultType.TT;
  1636. student.rateingRecord.itemRecords[index].resultWeight= InteractWeight.TT;
  1637. }
  1638. }
  1639. }
  1640. }
  1641. }
  1642. if (addData)
  1643. {
  1644. index++;
  1645. }
  1646. }
  1647. }
  1648. //星光大评分,全员评分
  1649. var keys_GrandRating = smartRatingData.smartRateSummary?.scoreDetailResult?.Keys?.ToList();
  1650. if (keys_GrandRating.IsNotEmpty() && smartRatingData.smartRateSummary!=null && smartRatingData.smartRateSummary.meteor_ScoreSummary.IsNotEmpty())
  1651. {
  1652. bool addData = false;
  1653. type="GrandRating";
  1654. foreach (var student in studentLessonDatas)
  1655. {
  1656. if (student.attend==1)
  1657. {
  1658. if (keys_GrandRating!.Contains(student.seatID!))
  1659. {
  1660. //T1,只有评论别人,没被别人评论 或者是评论了别人,但是没有被别人评论,
  1661. student.rateingRecord.itemRecords.Add(new ItemRecord { itemType=type, resultType=InteractReultType.T1, resultWeight = InteractWeight.T1 });
  1662. addData = true;
  1663. }
  1664. else
  1665. {
  1666. //T0 是没有评论别人,也没被别人评论,
  1667. student.rateingRecord.itemRecords.Add(new ItemRecord { itemType=type, resultType=InteractReultType.T0, resultWeight = InteractWeight.T0 });
  1668. addData = true;
  1669. }
  1670. }
  1671. }
  1672. var order = smartRatingData.smartRateSummary.meteor_ScoreSummary.Where(x => x.result>0||!string.IsNullOrWhiteSpace(x.comment)).OrderByDescending(x => x.result);
  1673. if (order.Count()>0)
  1674. {
  1675. var maxItems = smartRatingData.smartRateSummary.meteor_ScoreSummary.FindAll(x => x.result==order.First().result);
  1676. var max = smartRatingData.smartRateSummary.meteor_ScoreSummary.FindAll(x => x.result==order.First().result).First().result;
  1677. var min = smartRatingData.smartRateSummary.meteor_ScoreSummary.FindAll(x => x.result==order.Last().result).First().result;
  1678. var sum = smartRatingData.smartRateSummary.meteor_ScoreSummary.Sum(x => x.result);
  1679. foreach (var meteor_ScoreSummary in smartRatingData.smartRateSummary.meteor_ScoreSummary)
  1680. {
  1681. var student = studentLessonDatas.Find(x => x.seatID!.Equals(meteor_ScoreSummary.id));
  1682. if (student!=null)
  1683. {
  1684. if (index<student.rateingRecord.itemRecords.Count && student.rateingRecord.itemRecords[index].itemType!.Equals(type))
  1685. {
  1686. if (student.rateingRecord.itemRecords[index].resultType!.Equals(InteractReultType.T0))
  1687. {
  1688. //T1,只有评论别人,没被别人评论 或者是评论了别人,但是没有被别人评论,
  1689. student.rateingRecord.itemRecords[index].resultType= InteractReultType.T1;
  1690. student.rateingRecord.itemRecords[index].resultWeight= InteractWeight.T1;
  1691. }
  1692. else if (student.rateingRecord.itemRecords[index].resultType!.Equals(InteractReultType.T1))
  1693. {
  1694. //TP 有被别人评论,且评论了别人,
  1695. student.rateingRecord.itemRecords[index].resultType= InteractReultType.TP;
  1696. var data = MinMaxNormalization(min, max, meteor_ScoreSummary.result);
  1697. //student.rateingRecord.itemRecords[index].resultWeight= InteractWeight.TP;
  1698. student.rateingRecord.itemRecords[index].resultWeight= InteractWeight.T1+ data * 1.0 / 100 * (InteractWeight.TT-InteractWeight.T1);
  1699. //被评论次数
  1700. student.rateingRecord.itemRecords[index].itemScore=meteor_ScoreSummary.result;
  1701. //TT是评论了别人,且被别人评论次数最高,或者分值最高。
  1702. if (maxItems.Select(x => x.id).Contains(student.seatID) &&student.rateingRecord.itemRecords[index].itemScore>0)
  1703. {
  1704. student.rateingRecord.itemRecords[index].resultType= InteractReultType.TT;
  1705. student.rateingRecord.itemRecords[index].resultWeight= InteractWeight.TT;
  1706. }
  1707. }
  1708. }
  1709. }
  1710. }
  1711. }
  1712. if (addData)
  1713. {
  1714. index++;
  1715. }
  1716. }
  1717. // 互评 PeerAssessment(All每人多件评分,Two随机分配互评, Self自评)
  1718. var keys_PeerAssessment = smartRatingData.smartRateSummary?.mutualDetailSummary?.Keys?.ToList();
  1719. if (keys_PeerAssessment.IsNotEmpty() && smartRatingData.smartRateSummary?.mutualSummary!=null
  1720. && smartRatingData.smartRateSummary.mutualSummary.mutualResults.IsNotEmpty()
  1721. && smartRatingData.smartRateSummary.mutualSummary.materialInfos.IsNotEmpty())
  1722. {
  1723. bool addData = false;
  1724. type="PeerAssessment";
  1725. foreach (var student in studentLessonDatas)
  1726. {
  1727. if (student.attend==1)
  1728. {
  1729. if (keys_PeerAssessment!.Contains(student.seatID!))
  1730. {
  1731. //T1,只有评论别人,没被别人评论 或者是评论了别人,但是没有被别人评论,
  1732. student.rateingRecord.itemRecords.Add(new ItemRecord { itemType=type, resultType=InteractReultType.T1, resultWeight = InteractWeight.T1 });
  1733. addData = true;
  1734. }
  1735. else
  1736. {
  1737. //T0 是没有评论别人,也没被别人评论,
  1738. student.rateingRecord.itemRecords.Add(new ItemRecord { itemType=type, resultType=InteractReultType.T0, resultWeight = InteractWeight.T0 });
  1739. addData = true;
  1740. }
  1741. }
  1742. }
  1743. var order = smartRatingData.smartRateSummary.mutualSummary.mutualResults.Where(x => x.result>0).OrderByDescending(x => x.result);
  1744. var maxItems = smartRatingData.smartRateSummary.mutualSummary.mutualResults.FindAll(x => x.result==order.First().result);
  1745. var max = smartRatingData.smartRateSummary.mutualSummary.mutualResults.FindAll(x => x.result==order.First().result).First().result;
  1746. var min = smartRatingData.smartRateSummary.mutualSummary.mutualResults.FindAll(x => x.result==order.Last().result).First().result;
  1747. var sum = smartRatingData.smartRateSummary.mutualSummary.mutualResults.Sum(x => x.result);
  1748. foreach (var mutualResult in smartRatingData.smartRateSummary.mutualSummary.mutualResults)
  1749. {
  1750. var student = studentLessonDatas.Find(x => x.seatID!.Equals(mutualResult.id));
  1751. if (student!=null)
  1752. {
  1753. if (index<student.rateingRecord.itemRecords.Count && student.rateingRecord.itemRecords[index].itemType!.Equals(type))
  1754. {
  1755. if (student.rateingRecord.itemRecords[index].resultType!.Equals(InteractReultType.T0))
  1756. {
  1757. //T1,只有评论别人,没被别人评论 或者是评论了别人,但是没有被别人评论,
  1758. student.rateingRecord.itemRecords[index].resultType= InteractReultType.T1;
  1759. student.rateingRecord.itemRecords[index].resultWeight= InteractWeight.T1;
  1760. }
  1761. else if (student.rateingRecord.itemRecords[index].resultType!.Equals(InteractReultType.T1))
  1762. {
  1763. //TP 有被别人评论,且评论了别人,
  1764. //最高分和最低分,票数最多和票数最少的占比来计算TP的占比
  1765. student.rateingRecord.itemRecords[index].resultType= InteractReultType.TP;
  1766. var data = MinMaxNormalization(min, max, mutualResult.result);
  1767. //student.rateingRecord.itemRecords[index].resultWeight= InteractWeight.TP;
  1768. student.rateingRecord.itemRecords[index].resultWeight= InteractWeight.T1+ data * 1.0 / 100 * (InteractWeight.TT-InteractWeight.T1);
  1769. student.rateingRecord.itemRecords[index].itemScore=mutualResult.result;
  1770. //TT是评论了别人,且被别人评论次数最高,或者分值最高。
  1771. if (maxItems.Select(x => x.id).Contains(student.seatID))
  1772. {
  1773. student.rateingRecord.itemRecords[index].resultType= InteractReultType.TT;
  1774. student.rateingRecord.itemRecords[index].resultWeight= InteractWeight.TT;
  1775. }
  1776. }
  1777. }
  1778. }
  1779. }
  1780. if (addData)
  1781. {
  1782. index++;
  1783. }
  1784. }
  1785. }
  1786. return studentLessonDatas;
  1787. }
  1788. /// <summary>
  1789. /// 最小-最大归一化(Min-Max Normalization)算法。这种算法通常用于将数据的特征值缩放到一个指定的范围内,通常是0到1之间,或者任何其他指定的范围。
  1790. /// </summary>
  1791. /// <returns></returns>
  1792. public static double MinMaxNormalization(double min, double max, double x, double minRank = 1, double maxRank = 100)
  1793. {
  1794. //排名指数计算=( 当前值分数- 298) / (9992 - 298) * (99 - 60) + 60
  1795. //将每个人的积分转化为60-100
  1796. //排名 = (积分 - 最低积分) / (最高积分 - 最低积分) * (最大排名 - 最小排名) + 最小排名
  1797. return x==0 ? 0 : max-min!=0 ? (x - min)*1.0 / (max - min) * (maxRank - minRank) + minRank : (x)*1.0 / (max) * (maxRank - minRank) + minRank;
  1798. }
  1799. /// <summary>
  1800. /// 处理学生课中数据
  1801. /// </summary>
  1802. /// <param name="studentLessonDatas"></param>
  1803. /// <param name="lessonDataAnalysis"></param>
  1804. /// <returns></returns>
  1805. public static List<StudentLessonItem> ProcessStudentDataV2(List<StudentLessonData> studentLessonDatas, LessonDataAnalysisCluster lessonDataAnalysis)
  1806. {
  1807. //历史记录的个人计分集合,通过“2倍标准差规则”移除异常值后得到的集合
  1808. var max_q = lessonDataAnalysis.pscore.Max();
  1809. //历史记录的互动计分集合,通过“2倍标准差规则”移除异常值后得到的集合
  1810. var max_t = lessonDataAnalysis.tscore.Max();
  1811. //历史记录的小组计分集合,通过“2倍标准差规则”移除异常值后得到的集合
  1812. var max_h = lessonDataAnalysis.gscore.Max();
  1813. var j = InteractWeight.T1;
  1814. double t = InteractWeight.TT;
  1815. List<StudentLessonItem> lessonItems = new List<StudentLessonItem>();
  1816. foreach (var studentLessonData in studentLessonDatas)
  1817. {
  1818. StudentLessonItem lessonItem = new StudentLessonItem() { studentId= studentLessonData.id! };
  1819. double u = 0.0;
  1820. if (studentLessonData.attend==1)
  1821. {
  1822. u=100.0;
  1823. }
  1824. //c个人计分指数,d互动计分指数,e小组计分指数
  1825. double d = 0, e = 0;
  1826. //本节课教师手动给学生的个人计分
  1827. var s = studentLessonData.pscore;
  1828. //个人计分指数
  1829. double c = GetPersent(lessonDataAnalysis.pscore, s).persent;// s*1.0/max_q;
  1830. {
  1831. //互动相关的计分
  1832. //课例互动次数
  1833. double n = studentLessonData.interactRecord.interactRecords.Count()*1.0;
  1834. if (n>0)
  1835. {
  1836. //是IES大陆正式站历史课例数据,自2024-03-01至2024-10-08日,互动指数或学法指数黄灯或绿灯,不包含醍摩豆学校及测试学校,课例时长超过5分钟的有效课例(10,680笔数据) 的IRS互动+抢权+挑人的次数集合,
  1837. //通过“2倍标准差规则” 移除异常值后得到的集合,再通过K-Means聚类算法得到高低位阶互动频次两个集合,并根据当前课例互动次数位阶的集合的质心值,该值定为m值
  1838. IEnumerable<double> all = lessonDataAnalysis.levelInteract.SelectMany(x => x.Value);
  1839. var currMacth = lessonDataAnalysis.levelInteract.FindAll(x => x.Value.Min()<=n && x.Value.Max()>=n);
  1840. KeyValuePair<double, List<double>> curr = new KeyValuePair<double, List<double>>();
  1841. if (currMacth!=null && currMacth.Count()>0)
  1842. {
  1843. curr = currMacth.MinBy(x => x.Key);
  1844. }
  1845. else
  1846. {
  1847. curr = lessonDataAnalysis.levelInteract.MaxBy(x => x.Key);
  1848. }
  1849. var p = LessonETLService.GetPersent(all, n);
  1850. var l = n<lessonDataAnalysis.interactPass ? lessonDataAnalysis.interactLow : n>lessonDataAnalysis.interactGood ? lessonDataAnalysis.interactHigh : lessonDataAnalysis.interactMedium;
  1851. //出题系数=当前互动次数与互动通过次数之间的比例*当前互动次数与互动中位数之间的比例*当前互动次数与互动高阶互动频次的比例
  1852. var m = (n*1.0/l) *(p.persent/100) * (curr.Value.Count*1.0/all.Count());
  1853. //学生作答次数
  1854. var w = studentLessonData.interactRecord.interactRecords.Where(x => x.resultWeight>=InteractWeight.T1).Count()*1.0;
  1855. //作答正确数(包括部分正确)
  1856. var r = studentLessonData.interactRecord.interactRecords.Where(x => x.resultWeight>InteractWeight.T1).Count()*1.0;
  1857. //有参与的权重集合60≤k(x)≤100
  1858. var kw = studentLessonData.interactRecord.interactRecords.Where(x => x.resultWeight>=InteractWeight.T1).Sum(x => x.resultWeight*1.0);
  1859. //有得分的权重集合60<e(x)≤100
  1860. var er = studentLessonData.interactRecord.interactRecords.Where(x => x.resultWeight>InteractWeight.T1).Sum(x => x.resultWeight*1.0);
  1861. //本节课的所有互动计分
  1862. var i = studentLessonData.interactRecord.interactRecords.Sum(x => x.itemScore*1.0);
  1863. //互动计分指数
  1864. d = GetPersent(lessonDataAnalysis.tscore, i).persent; //i*1.0/max_t;
  1865. //互动成效指数
  1866. var a = 1.0;
  1867. if (w==0)
  1868. {
  1869. a = kw/10/n;
  1870. }
  1871. else
  1872. {
  1873. if (r==0)
  1874. {
  1875. a = (kw/(10*w))*(w/n);
  1876. }
  1877. else
  1878. {
  1879. a = (kw/(10*w))*(w/n)+er/(r*10)*(r/w);
  1880. }
  1881. }
  1882. //c+a= 个人计分指数+ 个人互动成效指数
  1883. //互动成效
  1884. var f1 = Math.Round(190*1.0/(1+Math.Exp(-(a*m)))-95, 4);
  1885. if (f1==0)
  1886. {
  1887. if (d>0 && c==0)
  1888. {
  1889. f1=d*0.6;
  1890. }
  1891. else if (d==0 && c>0)
  1892. {
  1893. f1=c*0.6;
  1894. }
  1895. else
  1896. {
  1897. f1=(d*0.6+c*0.6)/2;
  1898. }
  1899. }
  1900. else
  1901. {
  1902. //如果有互动计分,则互动成效指数取0.8 0.2
  1903. var dy = (95-f1)/95/2;
  1904. if (dy>0.2)
  1905. {
  1906. dy=0.2;
  1907. }
  1908. if (dy<0.1) {
  1909. dy=0.1;
  1910. }
  1911. f1=f1*(dy>0.1? 1-dy : 1-dy*2)+d*dy+c*dy;
  1912. }
  1913. if (f1>95)
  1914. {
  1915. f1=95;
  1916. }
  1917. //var f1 = Math.Round(a*m);
  1918. lessonItem.hd_cx=f1;
  1919. //互动专注指数
  1920. // var b = ((w*w/n)+(r*r/w))*1.0*m;
  1921. // var f2 = Math.Round(200*1.0/(1+Math.Exp(-(b)))-100, 4);
  1922. double f2 = 0;
  1923. //if (w>0)
  1924. //{
  1925. // double xs = m/n>0.1 ? m/n/10 : m/n;
  1926. // if (xs>0.05)
  1927. // {
  1928. // xs=xs/3;
  1929. // }
  1930. // lessonItem.hd_xs=Math.Round(xs, 4);
  1931. // var b = xs* ((w/n)*0.8+ 0.2 * (w/n)* (r/w));
  1932. // f2 = Math.Round(200*1.0/(1+Math.Exp(-(b)))-100, 4);
  1933. // if (f2>1)
  1934. // {
  1935. // f2=100;
  1936. // }
  1937. // else
  1938. // {
  1939. // f2=f2*100;
  1940. // }
  1941. //}
  1942. if (w>0) {
  1943. //作答正确的也算在参与度中,只是占比比作答率占比更小,占20%,即 0.2 * (w/n)* (r/w)*100,计算结果可能会大于100,则强制限定
  1944. f2=(w/n)*100+ 0.2 * (w/n)* (r/w)*100;
  1945. if (f2>100) { f2=100; }
  1946. }
  1947. lessonItem.hd_cy=f2;
  1948. lessonItem.hd_cyc=w;
  1949. lessonItem.hd_fqc=n;
  1950. lessonItem.hd_zqc=r;
  1951. lessonItem.gr_jf=s;
  1952. }
  1953. //studentLessonData.achieve=f1;
  1954. //studentLessonData.attitude=f2;
  1955. // _logger.LogInformation($"{studentLessonData.id}=>学习成效:{f1}\t学习态度:{f2}\t互动次数:{n}\t参与次数:{w}\t正确次数:{r}\t个人计分:{s}\t{Math.Round(c, 2)}\t互动计分:{i}\t{Math.Round(d, 2)}");
  1956. }
  1957. {
  1958. //评测相关指数
  1959. double n = studentLessonData.examRecords.Count()*1.0;
  1960. if (n>0)
  1961. {
  1962. //题目数量
  1963. double nq = studentLessonData.examRecords.Sum(x => x.qcount)*1.0;
  1964. // double max_e = lessonDataAnalysis.exam.Max();
  1965. //得分率
  1966. double sum_s = studentLessonData.examRecords.Sum(x => x.scoreRate);
  1967. //作答率
  1968. double sum_a = studentLessonData.examRecords.Sum(x => x.answerRate);
  1969. double f8 = Math.Round(sum_s/n*100, 4);
  1970. double f9 = Math.Round(sum_a/n*100, 4);
  1971. lessonItem.pc_df=f8;
  1972. lessonItem.pc_zd=f9;
  1973. }
  1974. // _logger.LogInformation($"{studentLessonData.id}=>评测指数:{f8}\t得分率:{Math.Round(sum_s/n,4)}\t专注指数:{f9}\t作答率:{Math.Round(sum_a/n,4)}");
  1975. }
  1976. {
  1977. //小组相关指数
  1978. /* PickupNameLst PickupOption PickupNthGrp PickupGrp PickupRange PickupEachGrp PickupDiff PickupWrong PickupNoDiff PickupRight PickupGener PickupWtoW PickupWtoR PickupLSA_WordFreq PickupLSA_Classify Pickup0_49*/
  1979. var grpPicks = studentLessonData.pickups.Where(x => x.StartsWith("1--") && x.Contains("Grp", StringComparison.OrdinalIgnoreCase));
  1980. var groups= studentLessonDatas.Where(x => !string.IsNullOrWhiteSpace(x.groupId)).Select(x => x.groupId).Distinct();
  1981. int groupCount = 1;
  1982. int memberCount = studentLessonDatas.Count;
  1983. if (groups!=null &&groups.Count()>0)
  1984. {
  1985. groupCount=groups.Count();
  1986. var members = studentLessonDatas.Where(x => !string.IsNullOrWhiteSpace(x.groupId) && !string.IsNullOrWhiteSpace(studentLessonData.groupId)&&x.groupId.Equals(studentLessonData.groupId));
  1987. if (members!=null && members.Count()>0)
  1988. {
  1989. memberCount= members.Count();
  1990. }
  1991. }
  1992. var grpPickCount = grpPicks.Count();
  1993. double pickRate = 0;
  1994. double groupType = 0;
  1995. foreach (var grppick in grpPicks)
  1996. {
  1997. pickRate+= (1.0/groupCount) * (1.0/memberCount)* 100 ;
  1998. }
  1999. if (grpPickCount>0)
  2000. {
  2001. pickRate=100- Math.Round(pickRate/grpPickCount,4);
  2002. groupType+=1;
  2003. lessonItem.xz_tr=grpPickCount;
  2004. }
  2005. double coworkRate = 0;
  2006. if (studentLessonData.group_coworkScore.IsNotEmpty())
  2007. {
  2008. double coworkGrp = studentLessonData.group_coworkScore.Average();
  2009. var coworkData = GetPersent(lessonDataAnalysis.groupCowork, coworkGrp);
  2010. coworkRate= coworkData.persent;
  2011. groupType+=1;
  2012. lessonItem.xz_xz=studentLessonData.group_coworkScore.Sum();
  2013. }
  2014. double gscoreRate = 0;
  2015. if (studentLessonData.gscore>0)
  2016. {
  2017. var gscoreData = GetPersent(lessonDataAnalysis.gscore, studentLessonData.gscore);
  2018. gscoreRate= gscoreData.persent;
  2019. groupType+=1;
  2020. lessonItem.xz_jf=studentLessonData.gscore;
  2021. }
  2022. double groupTask = 0;
  2023. var groupTasks= studentLessonData.taskRecord.itemRecords.Where(x => x.isGroup);
  2024. if(groupTasks!=null && groupTasks.Count()>0)
  2025. {
  2026. double score = CalculateScore(groupTasks.Sum(x=>x.optCount));
  2027. groupType+=1;
  2028. lessonItem.xz_sc=groupTasks.Count();
  2029. }
  2030. double fxGrp = 0;
  2031. if (groupType>0) {
  2032. fxGrp = (pickRate+coworkRate+gscoreRate+groupTask)/groupType; }
  2033. if (groupType==2 &&(pickRate>0 && gscoreRate>0) )
  2034. {
  2035. fxGrp = (pickRate*0.8+coworkRate+gscoreRate*0.8+groupTask)/groupType;
  2036. }
  2037. if (groupType==1)
  2038. {
  2039. fxGrp=(pickRate+coworkRate+gscoreRate+groupTask)*0.8/groupType;
  2040. }
  2041. lessonItem.hz_nl=Math.Round(fxGrp,4);
  2042. }
  2043. {
  2044. //任务相关指数
  2045. double n = studentLessonData.taskRecord.itemRecords.Count()*1.0;
  2046. if (n>0)
  2047. {
  2048. double max_m = lessonDataAnalysis.task.Max();
  2049. double w = studentLessonData.taskRecord.itemRecords.Where(x => x.resultWeight>0).Count()*1.0;
  2050. double y = (10 *w/n+(j/t) *w)/max_m;
  2051. double l = max_m*(w*w/n+(j/t) * w)/n;
  2052. double f4 = Math.Round(190*1.0/(1+Math.Exp(-(y)))-95, 4);
  2053. double f5 = Math.Round(200*1.0/(1+Math.Exp(-(l)))-100, 4);
  2054. lessonItem.rw_fqc =n;
  2055. lessonItem.rw_cyc =w;
  2056. lessonItem.rw_zpc =studentLessonData.taskRecord.itemRecords.Sum(x=>x.optCount);
  2057. lessonItem.rw_cx =f4;
  2058. lessonItem.rw_cy =f5;
  2059. double score = 0;
  2060. if (w>0)
  2061. {
  2062. foreach (var item in studentLessonData.taskRecord.itemRecords)
  2063. {
  2064. score += CalculateScore(item.optCount);
  2065. }
  2066. }
  2067. lessonItem.rw_cx =score*1.0/n;
  2068. }
  2069. // _logger.LogInformation($"{studentLessonData.id}=>任务指数:{f4}\t专注指数:{f5}\t任务次数:{n}\t参与次数:{w}\t");
  2070. }
  2071. {
  2072. //评价相关指数
  2073. double n = studentLessonData.rateingRecord.itemRecords.Count()*1.0;
  2074. if (n>0)
  2075. {
  2076. var v = studentLessonData.rateingRecord.itemRecords.Where(x => x.itemType.Equals("Voting"));
  2077. double vc = v.Count()*1.0;
  2078. var g = studentLessonData.rateingRecord.itemRecords.Where(x => x.itemType.Equals("GrandRating"));
  2079. double gc = g.Count()*1.0;
  2080. var p = studentLessonData.rateingRecord.itemRecords.Where(x => x.itemType.Equals("PeerAssessment"));
  2081. double pc = p.Count()*1.0;
  2082. var vg = v.Sum(x => x.itemScore);
  2083. var vo = v.Sum(x => x.optCount);
  2084. double vs = vc/n* (vg+ vo);
  2085. var gg = g.Sum(x => x.itemScore);
  2086. var go = g.Sum(x => x.optCount);
  2087. double gs = gc/n* (gg+ go);
  2088. var pg = p.Sum(x => x.itemScore);
  2089. var po = p.Sum(x => x.optCount);
  2090. double ps = pc/n* (pg+ po);
  2091. double h = vs+ps+gs;
  2092. double f3 = Math.Round(190*1.0/(1+Math.Exp(-(h)))-95, 4);
  2093. studentLessonData.appraise=f3;
  2094. // _logger.LogInformation($"{studentLessonData.id}=>评价能力:{f3}\t评价次数:{n}\t投票次数:{vc}-{vg}-{vo}\t星光次数:{gc}-{gg}-{go}\t互评次数:{pc}-{pg}-{po}");
  2095. lessonItem.pj_nl =f3;
  2096. lessonItem.pj_cs =n;
  2097. lessonItem.pj_vc =vc;
  2098. lessonItem.pj_vg =vg;
  2099. lessonItem.pj_vo =vo;
  2100. lessonItem.pj_gc =gc;
  2101. lessonItem.pj_gg =gg;
  2102. lessonItem.pj_go =go;
  2103. lessonItem.pj_pc =pc;
  2104. lessonItem.pj_pg =pg;
  2105. lessonItem.pj_po =po;
  2106. }
  2107. }
  2108. {
  2109. //协作相关指数
  2110. var n = studentLessonData.coworkRecord.itemRecords.Count()*1.0;
  2111. if (n>0)
  2112. {
  2113. //总的协作成果数
  2114. var w = studentLessonData.coworkRecord.itemRecords.Where(x => x.resultWeight>0);
  2115. double ss = w.Sum(x => x.itemScore)*1.0;
  2116. double sw = w.Sum(x => x.resultWeight)*1.0;
  2117. double wc = w.Count()*1.0;
  2118. double x = 0.0;
  2119. if (wc>0)
  2120. {
  2121. x=sw/(j *wc);
  2122. }
  2123. double max_xzcg = 40;
  2124. double k = (wc*wc/n+x)/n+ wc*(ss/max_xzcg)* (wc/n);
  2125. double f6 = Math.Round(190*1.0/(1+Math.Exp(-(k)))-95, 4);
  2126. double f7 = Math.Round(200*1.0/(1+Math.Exp(-(k)))-100, 4);
  2127. lessonItem.xz_fqc =n;
  2128. lessonItem.xz_cyc =wc;
  2129. lessonItem.xz_cgf =ss;
  2130. lessonItem.xz_cx =f6;
  2131. lessonItem.xz_cy =f7;
  2132. }
  2133. //_logger.LogInformation($"{studentLessonData.id}=>协作指数:{f6}\t专注指数:{f7}\t协作次数:{n}\t参与次数:{wc}\t协作成果分数:{ss}\t{k}");
  2134. }
  2135. double xx_cx = 0, xx_cy = 0;
  2136. int avg_cx = 0, avg_cy = 0;
  2137. if (lessonItem.xz_cx>0)
  2138. {
  2139. avg_cx+=1;
  2140. }
  2141. if (lessonItem.pj_nl>0)
  2142. {
  2143. avg_cx+=1;
  2144. }
  2145. if (lessonItem.rw_cx>0)
  2146. {
  2147. avg_cx+=1;
  2148. }
  2149. if (lessonItem.pc_df>0)
  2150. {
  2151. avg_cx+=1;
  2152. }
  2153. if (lessonItem.hd_cx>0)
  2154. {
  2155. avg_cx+=1;
  2156. }
  2157. if (lessonItem.hz_nl>0)
  2158. {
  2159. avg_cx+=1;
  2160. }
  2161. if (avg_cx>0)
  2162. {
  2163. xx_cx+=Math.Round(lessonItem.hd_cx * 1.0/avg_cx+ lessonItem.pc_df* 1.0/avg_cx+ lessonItem.rw_cx* 1.0/avg_cx+ lessonItem.pj_nl* 1.0/avg_cx+ lessonItem.xz_cx* 1.0/avg_cx+ lessonItem.hz_nl* 1.0/avg_cx,4);
  2164. }
  2165. if (lessonItem.xz_cy>0)
  2166. {
  2167. avg_cy+=1;
  2168. }
  2169. if (lessonItem.pj_nl>0)
  2170. {
  2171. avg_cy+=1;
  2172. }
  2173. if (lessonItem.rw_cy>0)
  2174. {
  2175. avg_cy+=1;
  2176. }
  2177. if (lessonItem.pc_zd>0)
  2178. {
  2179. avg_cy+=1;
  2180. }
  2181. if (lessonItem.hd_cy>0)
  2182. {
  2183. avg_cy+=1;
  2184. }
  2185. if (lessonItem.hz_nl>0)
  2186. {
  2187. avg_cy+=1;
  2188. }
  2189. if (avg_cy>0)
  2190. {
  2191. xx_cy+=Math.Round(lessonItem.hd_cy * 1.0/avg_cy+ lessonItem.pc_zd* 1.0/avg_cy+ lessonItem.rw_cy* 1.0/avg_cy+ lessonItem.pj_nl* 1.0/avg_cy+ lessonItem.xz_cy* 1.0/avg_cy+ lessonItem.hz_nl* 1.0/avg_cy,4);
  2192. }
  2193. lessonItem.xx_cx=xx_cx;
  2194. lessonItem.xx_cy=xx_cy;
  2195. lessonItems.Add(lessonItem);
  2196. studentLessonData.achieve= lessonItem.xx_cx;
  2197. studentLessonData.attitude= lessonItem.xx_cy;
  2198. studentLessonData.appraise= lessonItem.pj_nl;
  2199. studentLessonData.cowork= lessonItem.xz_cx;
  2200. studentLessonData.cooperation=lessonItem.hz_nl;
  2201. }
  2202. return lessonItems;
  2203. }
  2204. static double CalculateScore(int n, double k = 1)
  2205. {
  2206. if (n == 0)
  2207. {
  2208. return 0;
  2209. }
  2210. else
  2211. {
  2212. double score = 60 + (40 / (1 + Math.Exp(-k * (n - 1))));
  2213. return Math.Max(80, Math.Min(score, 100));
  2214. }
  2215. }
  2216. /// <summary>
  2217. /// 使用标准差定义异常值。如果一个数字与平均值的偏差超过某个标准差倍数(例如2倍或3倍),则可以认为它是异常的。
  2218. /// </summary>
  2219. /// <param name="array"></param>
  2220. /// <returns></returns>
  2221. public static List<double> CleanDataBySDThreshold(IEnumerable<double> array, double thresholdMultiplier = 2)
  2222. {
  2223. if (array.Count() == 0) return new List<double>();
  2224. double average = Math.Round(array.Sum()*1.0/array.Count(), 4);
  2225. double variance = array.Select(x => Math.Round(Math.Pow(x - average, 2), 4)).Sum()*1.0/array.Count();
  2226. double standardDeviation = Math.Sqrt(Math.Round(variance, 4));
  2227. double threshold = Math.Round(thresholdMultiplier * standardDeviation);
  2228. List<double> datas = new List<double>();
  2229. foreach (double value in array)
  2230. {
  2231. double deviation = Math.Round(Math.Abs(value - average), 4);
  2232. if (deviation <= threshold)
  2233. {
  2234. datas.Add(value);
  2235. }
  2236. }
  2237. return datas;
  2238. }
  2239. /// <summary>
  2240. ///
  2241. /// </summary>
  2242. /// <param name="items"></param>
  2243. /// <param name="filePath"></param>
  2244. /// <returns></returns>
  2245. public static async Task ExportToExcelAzureBlob(List<StudentLessonItem> items, AzureStorageFactory azureStorage,string owner , string path , XmlDocument xmlDocument)
  2246. {
  2247. ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial;
  2248. using (var memoryStream = new MemoryStream())
  2249. {
  2250. using (ExcelPackage package = new ExcelPackage())
  2251. {
  2252. ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("学生课中数据");
  2253. // 获取类的属性
  2254. PropertyInfo[] properties = typeof(StudentLessonItem).GetProperties();
  2255. // 添加表头
  2256. int currentRow = 1;
  2257. for (int i = 0; i < properties.Length; i++)
  2258. {
  2259. string summary = Regex.Replace(GetPropertySummary(properties[i], xmlDocument), @"\s+", "");
  2260. worksheet.Cells[currentRow, i + 1].Value = summary;
  2261. }
  2262. // 填充数据
  2263. currentRow = 2;
  2264. foreach (var item in items)
  2265. {
  2266. for (int i = 0; i < properties.Length; i++)
  2267. {
  2268. worksheet.Cells[currentRow, i + 1].Value = properties[i].GetValue(item);
  2269. }
  2270. currentRow++;
  2271. }
  2272. // 设置表格样式
  2273. worksheet.Cells[worksheet.Dimension.Address].Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Left;
  2274. worksheet.Cells[worksheet.Dimension.Address].Style.VerticalAlignment = OfficeOpenXml.Style.ExcelVerticalAlignment.Top;
  2275. // 保存到文件流
  2276. await package.SaveAsAsync(memoryStream);
  2277. memoryStream.Position=0;
  2278. }
  2279. await azureStorage.GetBlobContainerClient(owner).UploadFileByContainer(memoryStream, "records", path);
  2280. }
  2281. }
  2282. /// <summary>
  2283. /// 导出Excel
  2284. /// </summary>
  2285. /// <param name="items"></param>
  2286. /// <param name="filePath"></param>
  2287. /// <returns></returns>
  2288. public static async Task ExportToExcelLocal(List<StudentLessonItem> items, string filePath, XmlDocument xmlDocument)
  2289. {
  2290. ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial;
  2291. using (ExcelPackage package = new ExcelPackage())
  2292. {
  2293. ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("学生课中数据");
  2294. // 获取类的属性
  2295. PropertyInfo[] properties = typeof(StudentLessonItem).GetProperties();
  2296. // 添加表头
  2297. int currentRow = 1;
  2298. for (int i = 0; i < properties.Length; i++)
  2299. {
  2300. string summary = Regex.Replace(GetPropertySummary(properties[i], xmlDocument), @"\s+", "");
  2301. worksheet.Cells[currentRow, i + 1].Value = summary;
  2302. }
  2303. // 填充数据
  2304. currentRow = 2;
  2305. foreach (var item in items)
  2306. {
  2307. for (int i = 0; i < properties.Length; i++)
  2308. {
  2309. worksheet.Cells[currentRow, i + 1].Value = properties[i].GetValue(item);
  2310. }
  2311. currentRow++;
  2312. }
  2313. // 设置表格样式
  2314. worksheet.Cells[worksheet.Dimension.Address].Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Left;
  2315. worksheet.Cells[worksheet.Dimension.Address].Style.VerticalAlignment = OfficeOpenXml.Style.ExcelVerticalAlignment.Top;
  2316. // 保存到文件
  2317. FileInfo fileInfo = new System.IO.FileInfo(filePath);
  2318. await package.SaveAsAsync(fileInfo);
  2319. }
  2320. }
  2321. private static string GetPropertySummary(PropertyInfo property, XmlDocument xmlDocument)
  2322. {
  2323. XmlNodeList? xmlNodeList = xmlDocument.DocumentElement?.SelectNodes("//member[@name='P:" + property.DeclaringType?.FullName + "." + property.Name + "']");
  2324. if (xmlNodeList!= null && xmlNodeList.Count > 0)
  2325. {
  2326. XmlNode? xmlNode = xmlNodeList[0];
  2327. if (xmlNode != null && xmlNode.FirstChild != null)
  2328. {
  2329. return xmlNode.FirstChild.InnerText;
  2330. }
  2331. }
  2332. return property.Name;
  2333. }
  2334. /// <summary>
  2335. /// 当前数超越集合的百分比
  2336. /// </summary>
  2337. /// <param name="nums"></param>
  2338. /// <param name="curr"></param>
  2339. /// <returns></returns>
  2340. public static (double persent, int count) GetPersent(IEnumerable<double> nums, double curr)
  2341. {
  2342. int count = 0;
  2343. foreach (var op in nums.OrderBy(x => x))
  2344. {
  2345. if (op <= curr)
  2346. {
  2347. count++;
  2348. }
  2349. else
  2350. {
  2351. break;
  2352. }
  2353. }
  2354. return (count *1.0/ nums.Count() * 100, count);
  2355. }
  2356. }
  2357. /// <summary>
  2358. /// 学生导出Excel的Entity
  2359. /// </summary>
  2360. public class StudentLessonItem
  2361. {
  2362. /// <summary>
  2363. /// 学生id
  2364. /// </summary>
  2365. public string? studentId { get; set; }
  2366. /// <summary>
  2367. /// 互动发起次数
  2368. /// </summary>
  2369. public double hd_fqc { get; set; } = 0;
  2370. /// <summary>
  2371. /// 互动参与次数
  2372. /// </summary>
  2373. public double hd_cyc { get; set; } = 0;
  2374. /// <summary>
  2375. /// 互动正确次数
  2376. /// </summary>
  2377. public double hd_zqc { get; set; } = 0;
  2378. /// <summary>
  2379. /// 个人计分
  2380. /// </summary>
  2381. public double gr_jf { get; set; } = 0;
  2382. /// <summary>
  2383. /// 互动成效指数
  2384. /// </summary>
  2385. public double hd_cx { get; set; } = 0;
  2386. /// <summary>
  2387. /// 互动专注指数
  2388. /// </summary>
  2389. public double hd_cy { get; set; } = 0;
  2390. /// <summary>
  2391. /// 评测得分率
  2392. /// </summary>
  2393. public double pc_df { get; set; } = 0;
  2394. /// <summary>
  2395. /// 评测作答率
  2396. /// </summary>
  2397. public double pc_zd { get; set; } = 0;
  2398. /// <summary>
  2399. /// 任务发起次数
  2400. /// </summary>
  2401. public double rw_fqc { get; set; } = 0;
  2402. /// <summary>
  2403. /// 任务参与次数
  2404. /// </summary>
  2405. public double rw_cyc { get; set; } = 0;
  2406. /// <summary>
  2407. /// 任务作品数
  2408. /// </summary>
  2409. public double rw_zpc { get; set; } = 0;
  2410. /// <summary>
  2411. /// 任务成效指数
  2412. /// </summary>
  2413. public double rw_cx { get; set; } = 0;
  2414. /// <summary>
  2415. /// 任务专注指数
  2416. /// </summary>
  2417. public double rw_cy { get; set; } = 0;
  2418. /// <summary>
  2419. /// 评价发起次数
  2420. /// </summary>
  2421. public double pj_cs { get; set; } = 0;
  2422. /// <summary>
  2423. /// 投票发起次数
  2424. /// </summary>
  2425. public double pj_vc { get; set; } = 0;
  2426. /// <summary>
  2427. /// 投票得票数
  2428. /// </summary>
  2429. public double pj_vg { get; set; } = 0;
  2430. /// <summary>
  2431. /// 投票次数
  2432. /// </summary>
  2433. public double pj_vo { get; set; } = 0;
  2434. /// <summary>
  2435. /// 星光发起次数
  2436. /// </summary>
  2437. public double pj_gc { get; set; } = 0;
  2438. /// <summary>
  2439. /// 星光得分数
  2440. /// </summary>
  2441. public double pj_gg { get; set; } = 0;
  2442. /// <summary>
  2443. /// 星光评分次数
  2444. /// </summary>
  2445. public double pj_go { get; set; } = 0;
  2446. /// <summary>
  2447. /// 互评发起次数
  2448. /// </summary>
  2449. public double pj_pc { get; set; } = 0;
  2450. /// <summary>
  2451. /// 互评得分数
  2452. /// </summary>
  2453. public double pj_pg { get; set; } = 0;
  2454. /// <summary>
  2455. /// 互评评分次数
  2456. /// </summary>
  2457. public double pj_po { get; set; } = 0;
  2458. /// <summary>
  2459. /// 评价能力
  2460. /// </summary>
  2461. public double pj_nl { get; set; } = 0;
  2462. /// <summary>
  2463. /// 协作发起次数
  2464. /// </summary>
  2465. public double xz_fqc { get; set; } = 0;
  2466. /// <summary>
  2467. /// 协作参与次数
  2468. /// </summary>
  2469. public double xz_cyc { get; set; } = 0;
  2470. /// <summary>
  2471. /// 协作成果分数
  2472. /// </summary>
  2473. public double xz_cgf { get; set; } = 0;
  2474. /// <summary>
  2475. /// 协作能力指数
  2476. /// </summary>
  2477. public double xz_cx { get; set; } = 0;
  2478. /// <summary>
  2479. /// 协作专注指数
  2480. /// </summary>
  2481. public double xz_cy { get; set; } = 0;
  2482. /// <summary>
  2483. /// 小组挑人
  2484. /// </summary>
  2485. public double xz_tr { get; set; } = 0;
  2486. /// <summary>
  2487. /// 小组计分
  2488. /// </summary>
  2489. public double xz_jf { get; set; } = 0;
  2490. /// <summary>
  2491. /// 小组协作成果分
  2492. /// </summary>
  2493. public double xz_xz { get; set; } = 0;
  2494. /// <summary>
  2495. /// 组任务上传数
  2496. /// </summary>
  2497. public double xz_sc { get; set; } = 0;
  2498. /// <summary>
  2499. /// 合作能力
  2500. /// </summary>
  2501. public double hz_nl { get; set; } = 0;
  2502. /// <summary>
  2503. /// 学习成效
  2504. /// </summary>
  2505. public double xx_cx { get; set; } = 0;
  2506. /// <summary>
  2507. /// 学习专注度
  2508. /// </summary>
  2509. public double xx_cy { get; set; } = 0;
  2510. }
  2511. /// <summary>
  2512. /// 历史课例的关键数据模型
  2513. /// </summary>
  2514. public class LessonDataAnalysisCluster : LessonDataAnalysisBase
  2515. {
  2516. /// <summary>
  2517. ///
  2518. /// </summary>
  2519. // public List<KeyValuePair<double, List<double>>> clustersInteract { get; set; } = new List<KeyValuePair<double, List<double>>>();
  2520. ///// <summary>
  2521. /////
  2522. ///// </summary>
  2523. //public List<KeyValuePair<double, List<double>>> clustersPscore { get; set; } = new List<KeyValuePair<double, List<double>>>();
  2524. ///// <summary>
  2525. /////
  2526. ///// </summary>
  2527. //public List<KeyValuePair<double, List<double>>> clustersTscore { get; set; } = new List<KeyValuePair<double, List<double>>>();
  2528. ///// <summary>
  2529. /////
  2530. ///// </summary>
  2531. //public List<KeyValuePair<double, List<double>>> clustersGscore { get; set; } = new List<KeyValuePair<double, List<double>>>();
  2532. public List<KeyValuePair<double, List<double>>> levelInteract { get; set; } = new List<KeyValuePair<double, List<double>>>();
  2533. public double interactPass { get; set; }
  2534. public double interactGood { get; set; }
  2535. public double interactLow { get; set; }
  2536. public double interactMedium { get; set; }
  2537. public double interactHigh { get; set; }
  2538. }
  2539. /// <summary>
  2540. ///
  2541. /// </summary>
  2542. public abstract class LessonDataAnalysisBase
  2543. {
  2544. ///// <summary>
  2545. ///// 协作次数
  2546. ///// </summary>
  2547. //public IEnumerable<double> cowork { get; set; } = new List<double>();
  2548. ///// <summary>
  2549. /////
  2550. ///// </summary>
  2551. //public IEnumerable<double> coworkBase { get; set; } = new List<double>();
  2552. /// <summary>
  2553. ///
  2554. /// </summary>
  2555. public List<double> task { get; set; } = new List<double>();
  2556. ///// <summary>
  2557. /////
  2558. ///// </summary>
  2559. //public IEnumerable<double> taskBase { get; set; } = new List<double>();
  2560. ///// <summary>
  2561. /////
  2562. ///// </summary>
  2563. //public IEnumerable<double> exam { get; set; } = new List<double>();
  2564. ///// <summary>
  2565. /////
  2566. ///// </summary>
  2567. //public IEnumerable<double> examBase { get; set; } = new List<double>();
  2568. ///// <summary>
  2569. /////
  2570. ///// </summary>
  2571. //public IEnumerable<double> smartRating { get; set; } = new List<double>();
  2572. ///// <summary>
  2573. /////
  2574. ///// </summary>
  2575. //public IEnumerable<double> smartRatingBase { get; set; } = new List<double>();
  2576. /// <summary>
  2577. ///
  2578. /// </summary>
  2579. public List<double> irs { get; set; } = new List<double>();
  2580. /// <summary>
  2581. ///
  2582. /// </summary>
  2583. public List<double> interactNormal { get; set; } = new List<double>();
  2584. /// <summary>
  2585. /// 个人计分
  2586. /// </summary>
  2587. public List<double> pscore { get; set; } = new List<double>();
  2588. /// <summary>
  2589. /// 小组计分
  2590. /// </summary>
  2591. public List<double> gscore { get; set; } = new List<double>();
  2592. /// <summary>
  2593. /// 互动计分
  2594. /// </summary>
  2595. public List<double> tscore { get; set; } = new List<double>();
  2596. /// <summary>
  2597. /// 作品上传数
  2598. /// </summary>
  2599. public List<List<double>> upload { get; set; } = new List<List<double>>();
  2600. /// <summary>
  2601. /// 学生协作成果数
  2602. /// </summary>
  2603. public List<double> stuCowork { get; set; } = new List<double>();
  2604. /// <summary>
  2605. /// 小组协作成果数
  2606. /// </summary>
  2607. public List<double> groupCowork { get; set; } = new List<double>();
  2608. /// <summary>
  2609. /// 挑人集合
  2610. /// </summary>
  2611. public List<List<string>> pickup { get; set; } = new List<List<string>>();
  2612. ///// <summary>
  2613. ///// 挑人集合-小组
  2614. ///// </summary>
  2615. //public List<List<string>> pickup_group { get; set; } = new List<List<string>>();
  2616. }
  2617. /// <summary>
  2618. /// 每月的课例模型数据
  2619. /// </summary>
  2620. public class LessonDataAnalysisMonth : LessonDataAnalysisBase
  2621. {
  2622. /// <summary>
  2623. /// 时间戳
  2624. /// </summary>
  2625. public long updateTime { get; set; }
  2626. /// <summary>
  2627. /// yyyyMM
  2628. /// </summary>
  2629. public string? yearMonth { get; set; }
  2630. }
  2631. public class LessonWeek
  2632. {
  2633. /// <summary>
  2634. /// 第几周
  2635. /// </summary>
  2636. public int week { get; set; }
  2637. /// <summary>
  2638. /// 课例id
  2639. /// </summary>
  2640. public string? id { get; set;}
  2641. /// <summary>
  2642. /// 课程id
  2643. /// </summary>
  2644. public string? cid { get; set; }
  2645. /// <summary>
  2646. /// 科目id
  2647. /// </summary>
  2648. public string? sid { get; set;}
  2649. /// <summary>
  2650. /// 学校
  2651. /// </summary>
  2652. public string?school { get; set; }
  2653. /// <summary>
  2654. /// 名单id
  2655. /// </summary>
  2656. public string? gid { get; set; }
  2657. /// <summary>
  2658. /// 学段id
  2659. /// </summary>
  2660. //public string? pid { get; set; }
  2661. public string? scope { get; set; }
  2662. }
  2663. /// <summary>
  2664. ///
  2665. /// </summary>
  2666. public class LessonLocal
  2667. {
  2668. /// <summary>
  2669. ///
  2670. /// </summary>
  2671. public LessonBase? lessonBase { get; set; }
  2672. /// <summary>
  2673. ///
  2674. /// </summary>
  2675. public TimeLineData? timeLineData { get; set; }
  2676. /// <summary>
  2677. ///
  2678. /// </summary>
  2679. public LessonRecord? lessonRecord { get; set; }
  2680. /// <summary>
  2681. ///
  2682. /// </summary>
  2683. public List<LocalStudent> studentLessonDatas { get; set; } = new List<LocalStudent>();
  2684. /// <summary>
  2685. ///
  2686. /// </summary>
  2687. public List<TaskData> taskDatas { get; set; } = new List<TaskData>();
  2688. /// <summary>
  2689. ///
  2690. /// </summary>
  2691. public List<SmartRatingData> smartRatingDatas { get; set; } = new List<SmartRatingData>();
  2692. /// <summary>
  2693. ///
  2694. /// </summary>
  2695. public List<IRSData> irsDatas { get; set; } = new List<IRSData>();
  2696. /// <summary>
  2697. ///
  2698. /// </summary>
  2699. public List<CoworkData> coworkDatas { get; set; } = new List<CoworkData>();
  2700. /// <summary>
  2701. ///
  2702. /// </summary>
  2703. public List<ExamData> examDatas { get; set; } = new List<ExamData>();
  2704. /// <summary>
  2705. ///
  2706. /// </summary>
  2707. public List<TimeLineEvent> sokratesDatas { get; set; } = new List<TimeLineEvent>();
  2708. }
  2709. /// <summary>
  2710. ///
  2711. /// </summary>
  2712. public class TechCount
  2713. {
  2714. public string? yearMonth { get; set; }
  2715. /// <summary>
  2716. ///
  2717. /// </summary>
  2718. public string? lessonId { get; set; }
  2719. /// <summary>
  2720. /// 评测数量
  2721. /// </summary>
  2722. public int examCount { get; set; }
  2723. /// <summary>
  2724. /// 任务数量
  2725. /// </summary>
  2726. public int taskCount { get; set; }
  2727. /// <summary>
  2728. /// IRS次数
  2729. /// </summary>
  2730. public int irsCount { get; set; }
  2731. /// <summary>
  2732. /// 互动次数
  2733. /// </summary>
  2734. //public int interactExamCount { get; set; }
  2735. /// <summary>
  2736. /// 互动次数
  2737. /// </summary>
  2738. public int interactNormalCount { get; set; }
  2739. /// <summary>
  2740. /// 协作次数
  2741. /// </summary>
  2742. public int coworkCount { get; set; }
  2743. /// <summary>
  2744. /// 智能评分次数
  2745. /// </summary>
  2746. public int smartRatingCount { get; set; }
  2747. /// <summary>
  2748. ///
  2749. /// </summary>
  2750. public List<CodeLong> timeCount { get; set; } = new List<CodeLong>();
  2751. /// <summary>
  2752. ///
  2753. /// </summary>
  2754. public IEnumerable<double> pscore { get; set; } = new List<double>();
  2755. /// <summary>
  2756. ///
  2757. /// </summary>
  2758. public IEnumerable<double> gscore { get; set; } = new List<double>();
  2759. /// <summary>
  2760. ///
  2761. /// </summary>
  2762. public IEnumerable<double> tscore { get; set; } = new List<double>();
  2763. /// <summary>
  2764. /// 评测数量
  2765. /// </summary>
  2766. public int examCountBase { get; set; }
  2767. /// <summary>
  2768. /// 任务数量
  2769. /// </summary>
  2770. public int taskCountBase { get; set; }
  2771. /// <summary>
  2772. /// IRS次数
  2773. /// </summary>
  2774. public int irsCountBase { get; set; }
  2775. /// <summary>
  2776. /// 互动次数
  2777. /// </summary>
  2778. //public int interactExamCountBase { get; set; }
  2779. /// <summary>
  2780. /// 互动次数
  2781. /// </summary>
  2782. public int interactNormalCountBase { get; set; }
  2783. /// <summary>
  2784. /// 协作次数
  2785. /// </summary>
  2786. public int coworkCountBase { get; set; }
  2787. /// <summary>
  2788. /// 智能评分次数
  2789. /// </summary>
  2790. public int smartRatingCountBase { get; set; }
  2791. /// <summary>
  2792. /// 作品上传数
  2793. /// </summary>
  2794. public List<List<double>> upload { get; set; } = new List<List<double>>();
  2795. /// <summary>
  2796. /// 学生协作成果数
  2797. /// </summary>
  2798. public List<double> stuCowork { get; set; } = new List<double>();
  2799. /// <summary>
  2800. /// 小组协作成果数
  2801. /// </summary>
  2802. public List<double> groupCowork { get; set; } = new List<double>();
  2803. public List<string> pickup { get; set; } = new List<string>();
  2804. }
  2805. }