ArtAnalysisController.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. using Azure.Cosmos;
  2. using ClouDASLibx;
  3. using DocumentFormat.OpenXml.Drawing.Charts;
  4. using DocumentFormat.OpenXml.Office2010.Excel;
  5. using DocumentFormat.OpenXml.Spreadsheet;
  6. using DocumentFormat.OpenXml.VariantTypes;
  7. using Google.Protobuf.WellKnownTypes;
  8. using Microsoft.AspNetCore.Authorization;
  9. using Microsoft.AspNetCore.Mvc;
  10. using Microsoft.Extensions.Options;
  11. using OpenXmlPowerTools;
  12. using System;
  13. using System.Collections.Generic;
  14. using System.Linq;
  15. using System.Text;
  16. using System.Text.Json;
  17. using System.Threading.Tasks;
  18. using TEAMModelOS.Filter;
  19. using TEAMModelOS.SDK;
  20. using TEAMModelOS.SDK.DI;
  21. using TEAMModelOS.SDK.Extension;
  22. using TEAMModelOS.SDK.Helper.Common.StringHelper;
  23. using TEAMModelOS.SDK.Models;
  24. using TEAMModelOS.SDK.Models.Cosmos.Common;
  25. using TEAMModelOS.SDK.Models.Service;
  26. using Top.Api;
  27. using static Microsoft.Azure.Amqp.Serialization.SerializableType;
  28. using WebSocketSharp.Frame;
  29. using static OpenXmlPowerTools.RevisionProcessor;
  30. using Period = TEAMModelOS.SDK.Models.Period;
  31. namespace TEAMModelOS.Controllers.Analysis
  32. {
  33. [Route("analysis/art")]
  34. [ApiController]
  35. public class ArtAnalysisController : ControllerBase
  36. {
  37. private readonly AzureCosmosFactory _azureCosmos;
  38. private readonly DingDing _dingDing;
  39. private readonly Option _option;
  40. private readonly CoreAPIHttpService _coreAPIHttpService;
  41. public ArtAnalysisController(AzureCosmosFactory azureCosmos, DingDing dingDing, IOptionsSnapshot<Option> option, CoreAPIHttpService coreAPIHttpService)
  42. {
  43. _azureCosmos = azureCosmos;
  44. _dingDing = dingDing;
  45. _option = option?.Value;
  46. _coreAPIHttpService = coreAPIHttpService;
  47. }
  48. /* [ProducesDefaultResponseType]
  49. [HttpPost("find")]
  50. //[Authorize(Roles = "IES")]
  51. //[AuthToken(Roles = "teacher,admin")]
  52. public async Task<IActionResult> find(JsonElement request)
  53. {
  54. if (!request.TryGetProperty("code", out JsonElement code)) return BadRequest();
  55. var client = _azureCosmos.GetCosmosClient();
  56. List<ArtEvaluation> arts = new();
  57. try {
  58. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIterator(queryText: $"SELECT top 1 * FROM c where c.period.id ='{period}' order by c.createTime ",
  59. requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Art-{code}") }))
  60. {
  61. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  62. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  63. {
  64. arts.Add(obj.ToObject<ArtEvaluation>());
  65. }
  66. }
  67. } catch (Exception ex) {
  68. await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-analysis/art,find()\n{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
  69. }
  70. }*/
  71. [ProducesDefaultResponseType]
  72. [HttpPost("statistics")]
  73. [Authorize(Roles = "IES")]
  74. [AuthToken(Roles = "teacher,admin")]
  75. public async Task<IActionResult> getAnalysis(JsonElement request)
  76. {
  77. var client = _azureCosmos.GetCosmosClient();
  78. if (!request.TryGetProperty("code", out JsonElement code)) return BadRequest();
  79. if (!request.TryGetProperty("periodId", out JsonElement periodId)) return BadRequest();
  80. if (!request.TryGetProperty("subjectId", out JsonElement subjectId)) return BadRequest();
  81. if (!request.TryGetProperty("id", out JsonElement id)) return BadRequest();
  82. if (!request.TryGetProperty("classIds", out JsonElement cIds)) return BadRequest();
  83. if (!request.TryGetProperty("examId", out JsonElement examId)) return BadRequest();
  84. try
  85. {
  86. //List<double> scores = new();
  87. List<ArtSubjectScore> As = new();
  88. List<(List<ArtSubjectScore> scs, string sIds, string cd)> stus = new();
  89. //List<ArtEvaluation> arts = new();
  90. string artId = id.GetString();
  91. List<string> classIds = cIds.ToObject<List<string>>();
  92. if (!request.TryGetProperty("areaId", out JsonElement _areaId)) return BadRequest();
  93. ArtSetting setting = await client.GetContainer(Constant.TEAMModelOS, "Normal").ReadItemAsync<ArtSetting>($"{_areaId}", partitionKey: new PartitionKey("ArtSetting"));
  94. (List<RMember> tchList, List<RGroupList> classLists) = await GroupListService.GetMemberByListids(_coreAPIHttpService, client, _dingDing, classIds, code.GetString(), null);
  95. //学校基本信息
  96. School scInfo = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>($"{code}", partitionKey: new PartitionKey("Base"));
  97. var perMore = scInfo.period.Where(c => c.id.Equals(periodId.GetString())).FirstOrDefault()?.grades;
  98. string queryScore = $" select c.studentId,c.classIds,c.totalScore,c.subjectScores,c.results from c ";
  99. //List<(string stu,List<string> cId)> clads = new();
  100. List<List<ArtQuotaResult>> results = new();
  101. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Student).GetItemQueryStreamIterator
  102. (queryText: queryScore, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"ArtResult-{artId}") }))
  103. {
  104. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  105. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  106. {
  107. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  108. {
  109. if (obj.TryGetProperty("subjectScores", out JsonElement subScore))
  110. {
  111. string sId = obj.GetProperty("studentId").GetString();
  112. List<ArtSubjectScore> sc = subScore.ToObject<List<ArtSubjectScore>>();
  113. string cd = obj.GetProperty("classIds").ToObject<List<string>>()[0];
  114. //clads.Add((sId,obj.GetProperty("classIds").ToObject<List<string>>()));
  115. As.AddRange(sc);
  116. stus.Add((sc, sId, cd));
  117. }
  118. else
  119. {
  120. As.AddRange(new List<ArtSubjectScore>());
  121. }
  122. results.Add(obj.GetProperty("results").ToObject<List<ArtQuotaResult>>());
  123. }
  124. }
  125. }
  126. var subjectScore = new
  127. {
  128. name = As.Where(a => a.subjectId.Equals(subjectId.GetString())).Select(x => x.score).Where(c => c > 0)
  129. };
  130. double max = subjectScore.name.Max(s => Math.Abs(s));
  131. double min = subjectScore.name.Min(s => Math.Abs(s));
  132. double total = subjectScore.name.Sum();
  133. double average = Math.Round(total / stus.Count, 2);
  134. double excellent = Math.Round(subjectScore.name.Where(s => s >= 80).Count() * 1.0 / stus.Count, 2);
  135. double pass = Math.Round(subjectScore.name.Where(s => s >= 60).Count() * 1.0 / stus.Count, 2);
  136. double powSum = 0;
  137. foreach (var sc in subjectScore.name)
  138. {
  139. powSum += Math.Pow(sc - average, 2);
  140. }
  141. var pow = Math.Round(stus.Count > 0 ? Math.Pow(powSum / stus.Count, 0.5) : 0, 2);
  142. //获取本次评测所有科目结算结果
  143. List<ExamResult> examResults = new();
  144. ExamInfo info = await client.GetContainer(Constant.TEAMModelOS, "Common").ReadItemAsync<ExamInfo>(examId.ToString(), new PartitionKey($"Exam-{code}"));
  145. //获取评测ID
  146. //var examId = arts[0].settings.SelectMany(s => s.task).Where(a => a.type == 1 && a.subject.Equals(subjectId.GetString())).FirstOrDefault().acId;
  147. //根据科目标识获取科目ID以及知识块和知识点关系TODO 引用不同试卷时 获取知识点得差异
  148. int index = 0;
  149. foreach (var pr in info.subjects)
  150. {
  151. if (pr.id.Equals(subjectId.GetString()))
  152. {
  153. break;
  154. }
  155. index++;
  156. }
  157. List<(string name, List<string> kno)> knos = new();
  158. if (string.IsNullOrEmpty(info.papers[index].periodId))
  159. {
  160. (string subId, List<(string name, List<string> kno)> values) = await getKnowledge("hbcn", client, subjectId.GetString(), "be32942d-97a9-52ba-45d6-2e5b722583f5");
  161. knos = values;
  162. }
  163. else {
  164. (string subId, List<(string name, List<string> kno)> values) = await getKnowledge(info.papers[index].code, client, subjectId.GetString(), info.papers[index].periodId);
  165. knos = values;
  166. }
  167. var query = $"select c.id,c.name,c.subjectId,c.studentScores,c.studentIds,c.paper,c.classes,c.sRate,c.average,c.standard,c.lostStus,c.record,c.phc,c.plc from c where c.examId = '{examId}' and c.subjectId = '{subjectId}' ";
  168. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryIterator<ExamResult>(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"ExamResult-{examId}") }))
  169. {
  170. examResults.Add(item);
  171. }
  172. List<KeyValuePair<string, List<(string name, double score)>>> pointPersent = new();
  173. (KeyValuePair<string, List<string>> key1, KeyValuePair<string, List<string>> key2, KeyValuePair<string, List<(string name, double score)>> key3, KeyValuePair<string, List<(string name, double score)>> key4) = DoKnowledgePoint(examResults[0], info);
  174. KeyValuePair<string, List<(string id, double sta, double pass, string stu)>> key = DoSubjectScatter(examResults[0]);
  175. pointPersent.Add(key3);
  176. List<(string name, double score)> blockScore = new();
  177. foreach (var block in knos)
  178. {
  179. double sc = 0;
  180. foreach (var no in pointPersent)
  181. {
  182. foreach (var (name, score) in no.Value)
  183. {
  184. if (null != block.kno && block.kno.Contains(name))
  185. {
  186. sc += score;
  187. }
  188. }
  189. }
  190. blockScore.Add((block.name, sc));
  191. }
  192. var blk = blockScore.Select(x => new
  193. {
  194. x.name,
  195. x.score,
  196. dimension = setting.dimensions.Where(s => s.blocks.Contains(x.name)).Select(x => x.dimension)
  197. });
  198. var kno = key4.Value.Select(x => new
  199. {
  200. x.name,
  201. x.score,
  202. block = knos.Where(v => null != v.kno && v.kno.Contains(x.name)).Select(x => x.name)
  203. });
  204. //学生信息
  205. var students = stus.Select(s => new
  206. {
  207. id = s.sIds,
  208. s.scs.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.subjectId) && x.subjectId.Equals(subjectId.GetString()))?.score,
  209. tchList.Where(t => t.id.Equals(s.sIds)).FirstOrDefault()?.name,
  210. classId = s.cd,
  211. className = examResults[0].classes.Where(c => c.id.Equals(s.cd)).FirstOrDefault()?.name,
  212. examResults[0].classes.Where(c => c.id.Equals(s.cd)).FirstOrDefault()?.gradeId,
  213. key.Value.Where(c => c.id.Equals(s.sIds)).FirstOrDefault().sta,
  214. key.Value.Where(c => c.id.Equals(s.sIds)).FirstOrDefault().pass,
  215. key.Value.Where(c => c.id.Equals(s.sIds)).FirstOrDefault().stu,
  216. });
  217. List<(string cId, double sc, double max, double min, double excellent, double pass)> clsInfo = new();
  218. foreach (var cls in classLists)
  219. {
  220. double classTotal = 0;
  221. List<double> scores = new();
  222. foreach (var member in cls.members)
  223. {
  224. double sc = (double)students.FirstOrDefault(s => s.id.Equals(member.id)).score;
  225. scores.Add(sc);
  226. classTotal += sc;
  227. }
  228. scores = scores.Where(x => x > 0).ToList();
  229. double maxc = scores.Max(s => Math.Abs(s));
  230. double minc = scores.Min(s => Math.Abs(s));
  231. double excellentc = scores.Where(s => s >= 80).Count();
  232. double ex = Math.Round(excellentc / scores.Count,2);
  233. double passc = scores.Where(s => s >= 60).Count();
  234. double pa = Math.Round(passc / scores.Count, 2);
  235. clsInfo.Add((cls.id, classTotal / scores.Count, maxc, minc, ex, pa));
  236. }
  237. //班级信息
  238. var cInfo = clsInfo.Select(x => new
  239. {
  240. id = x.cId,
  241. classLists.Where(c => c.id.Equals(x.cId)).FirstOrDefault().name,
  242. score = Math.Round(x.sc, 2),
  243. x.max,
  244. x.min,
  245. x.excellent,
  246. x.pass,
  247. examResults[0].classes.Where(c => c.id.Equals(x.cId)).FirstOrDefault().gradeId
  248. });
  249. //年级信息
  250. var grades = students.GroupBy(c => c.gradeId).Select(x => new { gradeId = x.Key, list = x.ToList().Select(v => v.score).Where(c => c > 0) });
  251. var gscore = grades.Select(x => new
  252. {
  253. id = x.gradeId,
  254. name = perMore[int.Parse(x.gradeId)],
  255. score = Math.Round((double)(x.list.Sum() / x.list.Count()), 2),
  256. max = x.list.Max(s => Math.Abs((double)s)),
  257. min = x.list.Min(s => Math.Abs((double)s)),
  258. excellent = Math.Round( x.list.Where(s => s >= 80).Count() * 1.0 / x.list.Count(),2),
  259. pass = Math.Round(x.list.Where(s => s >= 60).Count() * 1.0 / x.list.Count(), 2)
  260. });
  261. //获奖次数
  262. List<List<string>> tag = new();
  263. results.ForEach(r =>
  264. {
  265. tag.AddRange(r.Where(x => null != x.files).SelectMany(f => f.files).Select(c => c.tag).ToList());
  266. });
  267. List<string> newTag = new();
  268. tag.ForEach(t =>
  269. {
  270. if (t.Count > 0)
  271. {
  272. newTag.AddRange(t);
  273. }
  274. });
  275. //List<Dictionary<string, int>> recorde = new List<Dictionary<string, int>>();
  276. Dictionary<string, int> optCount = new Dictionary<string, int>();
  277. foreach (var s in newTag)
  278. {
  279. if (optCount.ContainsKey(s))
  280. {
  281. optCount[s] = optCount[s] + 1;
  282. }
  283. else
  284. {
  285. optCount[s] = 1;
  286. }
  287. }
  288. /*var prizeCount = optCount.Select(x => new {
  289. x.Key,
  290. x.Value
  291. });*/
  292. return Ok(new { count = tchList.Count, scount = stus.Count - info.lostStu.Count, max, min, average, excellent, pass, pow, students, cInfo, blk, kno, optCount, gscore });
  293. }
  294. catch (Exception e)
  295. {
  296. return Ok( new { code = 500});
  297. }
  298. }
  299. private static async Task<(string subId, List<(string name, List<string> kno)>)> getKnowledge(string school, CosmosClient client, string subjectBid, string pId)
  300. {
  301. try
  302. {
  303. var response = await client.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync(school, new PartitionKey($"Base"));
  304. string subjectId = string.Empty;
  305. List<Knowledge> knowledges = new();
  306. List<(string name, List<string> kno)> blocks = new();
  307. if (response.Status == 200)
  308. {
  309. using var json = await JsonDocument.ParseAsync(response.ContentStream);
  310. School sc = json.ToObject<School>();
  311. var subjects = sc.period.Where(p => p.id.Equals(pId)).Select(x => x.subjects);
  312. foreach (var sj in subjects)
  313. {
  314. foreach (var s in sj)
  315. {
  316. if (!string.IsNullOrWhiteSpace(s.bindId) && s.bindId.Equals(subjectBid))
  317. {
  318. subjectId = s.id;
  319. }
  320. }
  321. }
  322. string code = $"Knowledge-{school}-{subjectId}";
  323. StringBuilder sql = new StringBuilder($"select value(c) from c");
  324. if (string.IsNullOrWhiteSpace(pId))
  325. {
  326. sql.Append($" where c.periodId = '{pId}'");
  327. }
  328. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<Knowledge>(queryText: sql.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{code}") }))
  329. {
  330. knowledges.Add(item);
  331. }
  332. }
  333. foreach (var know in knowledges)
  334. {
  335. foreach (var block in know.blocks)
  336. {
  337. blocks.Add((block.name, block.points));
  338. }
  339. }
  340. return (subjectId, blocks);
  341. }
  342. catch (Exception e)
  343. {
  344. return (null, null);
  345. }
  346. }
  347. //获取知识点得分率
  348. private static (KeyValuePair<string, List<string>>, KeyValuePair<string, List<string>>, KeyValuePair<string, List<(string name, double score)>>, KeyValuePair<string, List<(string name, double score)>>) DoKnowledgePoint(ExamResult exam, ExamInfo info)
  349. {
  350. HashSet<string> knowledge = new HashSet<string>();
  351. List<double> point = new List<double>();
  352. List<List<double>> result = new List<List<double>>();
  353. List<ClassRange> classes = new List<ClassRange>();
  354. //求单个知识点所占分数
  355. List<string> per = new List<string>();
  356. //定位试卷信息
  357. int index = 0;
  358. foreach (ExamSubject subject in info.subjects)
  359. {
  360. if (subject.id.Equals(exam.subjectId))
  361. {
  362. break;
  363. }
  364. else
  365. {
  366. index++;
  367. }
  368. }
  369. if (info.papers[index].knowledge != null && info.papers[index].knowledge.Count > 0)
  370. {
  371. info.papers[index].knowledge.ForEach(k =>
  372. {
  373. k.ForEach(e =>
  374. {
  375. knowledge.Add(e);
  376. });
  377. });
  378. }
  379. else
  380. {
  381. return (default, default, default, default);
  382. }
  383. point = info.papers[index].point;
  384. result = exam.studentScores;
  385. classes = exam.classes;
  386. List<string> knowledgeName = new List<string>();
  387. foreach (string cla in knowledge)
  388. {
  389. knowledgeName.Add(cla);
  390. }
  391. for (int k = 0; k < knowledgeName.Count; k++)
  392. {
  393. if (null == knowledgeName[k])
  394. {
  395. knowledgeName.Remove(knowledgeName[k]);
  396. }
  397. }
  398. //初始化年级总分
  399. double total = 0;
  400. //处理年级单个知识点得分率
  401. foreach (List<double> grade in result)
  402. {
  403. total += grade.Sum();
  404. }
  405. //试卷总分
  406. double TotalPoint = point.Sum();
  407. List<double> knowScore = new List<double>();
  408. //学生得分情况
  409. List<double> Score = new List<double>();
  410. List<(string name, double score)> pointScore = new();
  411. List<(string name, double score)> pointTScore = new();
  412. for (int k = 0; k < knowledgeName.Count; k++)
  413. {
  414. double OnePoint = 0;
  415. List<string> itemNo = new List<string>();
  416. int n = 0;
  417. double scores = 0;
  418. info.papers[index].knowledge.ForEach(kno =>
  419. {
  420. if (kno.Contains(knowledgeName[k]))
  421. {
  422. var itemPersent = kno.Count > 0 ? 1 / Convert.ToDouble(kno.Count) : 0;
  423. OnePoint += point[n] * itemPersent;
  424. foreach (string id in exam.studentIds)
  425. {
  426. int index = exam.studentIds.IndexOf(id);
  427. if (exam.studentScores[index][n] > 0)
  428. {
  429. scores += exam.studentScores[index][n] * itemPersent;
  430. }
  431. }
  432. }
  433. n++;
  434. });
  435. Score.Add(scores);
  436. //单个知识点的配分
  437. pointScore.Add((knowledgeName[k], OnePoint));
  438. //该知识点平均得分
  439. double sc = exam.studentIds.Count > 0 ? Math.Round(scores * 1.0 / exam.studentIds.Count, 2) : 0;
  440. //知识点占比
  441. double average = sc * 1.5;
  442. if (average > OnePoint)
  443. {
  444. average = sc;
  445. }
  446. double persent = Math.Round(OnePoint > 0 ? average / OnePoint : 0, 2);
  447. per.Add(persent.ToString("0.00"));
  448. //单个知识点所有学生得分率
  449. pointTScore.Add((knowledgeName[k], persent));
  450. }
  451. KeyValuePair<string, List<string>> key1 = new(exam.subjectId, knowledgeName);
  452. KeyValuePair<string, List<string>> key2 = new(exam.subjectId, per);
  453. KeyValuePair<string, List<(string name, double score)>> key3 = new(exam.subjectId, pointScore);
  454. KeyValuePair<string, List<(string name, double score)>> key4 = new(exam.subjectId, pointTScore);
  455. //KeyValuePair<string, List<double>> key3 = new KeyValuePair<string, List<double>>(exam.subjectId, allPer);
  456. return (key1, key2, key3, key4);
  457. }
  458. private KeyValuePair<string, List<(string id, double sta, double pass, string stu)>> DoSubjectScatter(ExamResult e)
  459. {
  460. double[] point = StringHelper.ListTodouble(e.paper.point);
  461. double[,] result = StringHelper.ListToDouble(e.studentScores);
  462. try
  463. {
  464. var cdm = new ClouDASMatrix(result, point);
  465. //学生通过率
  466. List<double> pass = cdm.ScoringRate;
  467. //学生稳定度
  468. List<double> sta = cdm.StabilityRate;
  469. //落点区域
  470. List<string> stu = cdm.StuFallArea;
  471. int i = 0;
  472. List<(string id, double sta, double pass, string stu)> stus = new();
  473. e.studentIds.ForEach(s =>
  474. {
  475. var stuSta = sta[i] > 1 ? 1 : sta[i];
  476. stus.Add((s, stuSta, pass[i] * 0.01, stu[i]));
  477. i++;
  478. });
  479. return new KeyValuePair<string, List<(string id, double sta, double pass, string stu)>>(e.subjectId, stus);
  480. }
  481. catch (Exception ex)
  482. {
  483. BadRequest(ex.Message + ex.StackTrace);
  484. }
  485. return default;
  486. }
  487. }
  488. }