ProductAnalysisController.cs 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. using Microsoft.Azure.Cosmos;
  2. using DingTalk.Api.Request;
  3. using Microsoft.AspNetCore.Authorization;
  4. using Microsoft.AspNetCore.Http;
  5. using Microsoft.AspNetCore.Mvc;
  6. using Microsoft.Extensions.Configuration;
  7. using Microsoft.Extensions.Options;
  8. using StackExchange.Redis;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Dynamic;
  12. using System.Linq;
  13. using System.Reflection;
  14. using System.Text;
  15. using System.Text.Json;
  16. using System.Threading.Tasks;
  17. using TEAMModelBI.Filter;
  18. using TEAMModelBI.Tool;
  19. using TEAMModelBI.Tool.Extension;
  20. using TEAMModelOS.Models;
  21. using TEAMModelOS.SDK.Context.BI;
  22. using TEAMModelOS.SDK.DI;
  23. using TEAMModelOS.SDK.Extension;
  24. using TEAMModelOS.SDK.Models;
  25. using TEAMModelOS.SDK.Models.Service.BI;
  26. using PartitionKey = Microsoft.Azure.Cosmos.PartitionKey;
  27. using QueryRequestOptions = Microsoft.Azure.Cosmos.QueryRequestOptions;
  28. using static TEAMModelOS.SDK.Models.Cosmos.IotTeachingData;
  29. using System.Text.RegularExpressions;
  30. namespace TEAMModelBI.Controllers.ProductAnalysis
  31. {
  32. [Route("prodanalysis")]
  33. [ApiController]
  34. public class ProductAnalysisController : ControllerBase
  35. {
  36. //数据容器
  37. private readonly AzureCosmosFactory _azureCosmos;
  38. private readonly AzureStorageFactory _azureStorage;
  39. private readonly AzureRedisFactory _azureRedis;
  40. //钉钉提示信息
  41. private readonly DingDing _dingDing;
  42. private readonly Option _option;
  43. private readonly IConfiguration _configuration;
  44. public ProductAnalysisController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis, DingDing dingDing, IOptionsSnapshot<Option> option, IConfiguration configuration)
  45. {
  46. _azureCosmos= azureCosmos;
  47. _azureStorage= azureStorage;
  48. _azureRedis = azureRedis;
  49. _dingDing = dingDing;
  50. _option= option?.Value;
  51. _configuration= configuration;
  52. }
  53. /// <summary>
  54. /// 取得產品分析IOT統計資料(redis)
  55. /// </summary>
  56. /// <param name="jsonElement"></param>
  57. /// <returns></returns>
  58. [ProducesDefaultResponseType]
  59. [Authorize(Roles = "IES,Sokrates")]
  60. [HttpPost("get-iotstics")]
  61. public async Task<IActionResult> GetIotStics(JsonElement jsonElement)
  62. {
  63. try
  64. {
  65. var cosmosClient = _azureCosmos.GetCosmosClient();
  66. var cosmosClientCsv2 = _azureCosmos.GetCosmosClient(name: "CoreServiceV2"); //CosmosDB CSV2
  67. var redisClinet8 = _azureRedis.GetRedisClient(8);
  68. if (!jsonElement.TryGetProperty("dateFrom", out JsonElement dateFromJobj)) return BadRequest();//查詢日期:起始(string)[例]2023-03-05
  69. if (!jsonElement.TryGetProperty("dateTo", out JsonElement dateToJobj)) return BadRequest();//查詢日期:結束(string)[例]2023-03-27
  70. if (!jsonElement.TryGetProperty("prod", out JsonElement prodJobj)) return BadRequest();//產品 HiTeach, HiTeachCC, HiTA
  71. string prod = (prodJobj.ToString().Equals("HiTeach") || prodJobj.ToString().Equals("HiTeachCC") || prodJobj.ToString().Equals("HiTA")) ? prodJobj.ToString() : string.Empty;
  72. if(string.IsNullOrWhiteSpace(prod)) return BadRequest();
  73. string dateUnit = (jsonElement.TryGetProperty("dateUnit", out JsonElement dateUnitJobj)) ? (!string.IsNullOrWhiteSpace(Convert.ToString(dateUnitJobj))) ? Convert.ToString(dateUnitJobj).ToLower() : "day" : "day"; //時間統計單位 ※以每年(Year)、每月(Month)、每日(Day) 為統計單位 預設值:每日
  74. string geoUnit = (jsonElement.TryGetProperty("geoUnit", out JsonElement geoUnitJobj)) ? (!string.IsNullOrWhiteSpace(Convert.ToString(geoUnitJobj))) ? Convert.ToString(geoUnitJobj).ToLower() : "city" : "city"; //地理統計單位 region:國 province:省 city:市 dist:區 預設值:市
  75. Geo geo = (jsonElement.TryGetProperty("geo", out JsonElement geoJobj)) ? geoJobj.ToObject<Geo>() : null;
  76. string target = (jsonElement.TryGetProperty("target", out JsonElement targetJobj)) ? (!string.IsNullOrWhiteSpace(Convert.ToString(targetJobj))) ? Convert.ToString(targetJobj).ToLower() : "school" : "school"; //統計對象 school:學校 tmid:TMID
  77. string extra = (jsonElement.TryGetProperty("extra", out JsonElement extraJobj)) ? (!string.IsNullOrWhiteSpace(Convert.ToString(extraJobj))) ? Convert.ToString(extraJobj).ToLower() : string.Empty : string.Empty; //額外參數 rmvNoAll:不取 noschoolid、allschool
  78. List<string> schoolIds = new List<string>();
  79. if (target.Equals("school"))
  80. {
  81. if (!jsonElement.TryGetProperty("schoolIds", out JsonElement schoolIdsJobj)) return BadRequest();//學校ID(array)
  82. schoolIds = schoolIdsJobj.ToObject<List<string>>();
  83. }
  84. List<string> tmids = (jsonElement.TryGetProperty("tmids", out JsonElement tmidsJobj)) ? tmidsJobj.ToObject<List<string>>() : new List<string>(); //TMID(array) ※target = tmid 時縮小搜尋範圍
  85. //起始終止日期換算
  86. List<string> dateFromList = dateFromJobj.ToString().Split('-').ToList();
  87. int dateFromYear = Convert.ToInt32(dateFromList[0], 10);
  88. int dateFromMonth = (dateUnit.Equals("day") || dateUnit.Equals("month")) ? Convert.ToInt32(dateFromList[1], 10) : 1;
  89. int dateFromDay = (dateUnit.Equals("day")) ? Convert.ToInt32(dateFromList[2], 10) : 1;
  90. List<string> dateToList = dateToJobj.ToString().Split('-').ToList();
  91. int dateToYear = Convert.ToInt32(dateToList[0], 10);
  92. int dateToMonth = (dateUnit.Equals("day") || dateUnit.Equals("month")) ? Convert.ToInt32(dateToList[1], 10) : 1;
  93. int dateToDay = (dateUnit.Equals("day")) ? Convert.ToInt32(dateToList[2], 10) : 1;
  94. DateTimeOffset dateTimeFrom = new DateTimeOffset(dateFromYear, dateFromMonth, dateFromDay, 0, 0, 0, TimeSpan.Zero);
  95. DateTimeOffset dateTimeTo = new DateTimeOffset(dateToYear, dateToMonth, dateToDay, 23, 59, 59, TimeSpan.Zero);
  96. long dateTimeFromSec = dateTimeFrom.ToUnixTimeSeconds();
  97. long dateTimeToSec = dateTimeTo.ToUnixTimeSeconds();
  98. ///輸出項定義
  99. //統計資料
  100. List<ProdAnalysisApiResult> result = new List<ProdAnalysisApiResult>();
  101. //地理位置別資訊
  102. List<ProdAnalysisApiResultGeoExtend> geoResult = new List<ProdAnalysisApiResultGeoExtend>();
  103. //產品授權資料整理
  104. List<SchAuthData> auth = new List<SchAuthData>();
  105. //CosmosDB資料取得
  106. ///TMID
  107. if (target.Equals("tmid"))
  108. {
  109. List<string> tmidList = new List<string>();
  110. string Sql = $"SELECT * FROM c WHERE c.toolType = '{prod}' AND c.dateUnit = '{dateUnit}' AND c.dateTime >= {dateTimeFromSec} AND c.dateTime <= {dateTimeToSec}";
  111. if(tmids.Count > 0)
  112. {
  113. string tmidsSer = JsonSerializer.Serialize(tmids);
  114. Sql += $" AND ARRAY_CONTAINS({tmidsSer}, c.tmid) ";
  115. }
  116. await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "Teacher").GetItemQueryStreamIteratorSql(queryText: Sql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"TmidAnalysis") }))
  117. {
  118. var json = await JsonDocument.ParseAsync(item.Content);
  119. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  120. {
  121. ProdAnalysisApiResult resultRow = obj.ToObject<ProdAnalysisApiResult>();
  122. resultRow.lTypeNone = ((resultRow.lTypeNone - resultRow.lessonLeng0) < 0) ? 0 : resultRow.lTypeNone - resultRow.lessonLeng0; //無學習型態數值須扣除未上課
  123. //各TMID IOT統計
  124. result.Add(resultRow);
  125. string tmid = obj.GetProperty("tmid").GetString();
  126. if (!tmidList.Contains(tmid)) tmidList.Add(tmid);
  127. }
  128. }
  129. ////取得TMID基本資訊 => 記入Dictionary
  130. Dictionary<string, string> tmidDic = new();
  131. string tmidsStr = JsonSerializer.Serialize(tmidList);
  132. string SqlTmid = $"SELECT c.id, c.name FROM c WHERE ARRAY_CONTAINS({tmidsStr}, c.id) ";
  133. await foreach (var item in cosmosClientCsv2.GetContainer("Core", "ID2").GetItemQueryStreamIteratorSql(SqlTmid, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("base") }))
  134. {
  135. using var json = await JsonDocument.ParseAsync(item.Content);
  136. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  137. {
  138. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  139. {
  140. string tmid = obj.GetProperty("id").GetString();
  141. string name = obj.GetProperty("name").GetString();
  142. tmidDic.Add(tmid, name);
  143. }
  144. }
  145. }
  146. ////取得TMID歸戶學校ID => 記入Dictionary
  147. Dictionary<string, string> tmidSchidDic = new();
  148. List<string> sqlSchIds = new List<string>(); //取得學校資訊用ID列表
  149. SqlTmid = $"SELECT c.id, c.schoolCode, c.schoolCodeW FROM c WHERE ARRAY_CONTAINS({tmidsStr}, c.id) ";
  150. await foreach (var item in cosmosClientCsv2.GetContainer("Core", "ID2").GetItemQueryStreamIteratorSql(SqlTmid, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("base-ex") }))
  151. {
  152. using var json = await JsonDocument.ParseAsync(item.Content);
  153. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  154. {
  155. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  156. {
  157. string tmid = obj.GetProperty("id").ToString();
  158. string schoolId = string.Empty;
  159. var schoolCode = (obj.TryGetProperty("schoolCode", out JsonElement _schoolCode)) ? Convert.ToString(_schoolCode) : string.Empty;
  160. var schoolCodeW = (obj.TryGetProperty("schoolCodeW", out JsonElement _schoolCodeW)) ? Convert.ToString(_schoolCodeW) : string.Empty;
  161. if (!string.IsNullOrWhiteSpace(schoolCode)) schoolId = schoolCode;
  162. else if (!string.IsNullOrWhiteSpace(schoolCodeW)) schoolId = schoolCodeW;
  163. if (!string.IsNullOrWhiteSpace(schoolId))
  164. {
  165. tmidSchidDic.Add(tmid, schoolId);
  166. if (!sqlSchIds.Contains(schoolId))
  167. {
  168. sqlSchIds.Add(schoolId);
  169. }
  170. }
  171. }
  172. }
  173. }
  174. ////取得TMID歸戶學校資料 => 記入Dictionary
  175. Dictionary<string, Dictionary<string, string>> schDic = new();
  176. string schIdListStr = JsonSerializer.Serialize(sqlSchIds);
  177. string SqlSch = $"SELECT c.id, c.name, c.code, c.region, c.province, c.city, c.areaId, c.dist FROM c WHERE (c.code = 'Base' OR c.code = 'VirtualBase') AND ARRAY_CONTAINS({schIdListStr}, c.id, true)";
  178. await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIteratorSql(queryText: SqlSch, requestOptions: null))
  179. {
  180. var json = await JsonDocument.ParseAsync(item.Content);
  181. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  182. {
  183. string schId = obj.GetProperty("id").GetString();
  184. string name = Convert.ToString(obj.GetProperty("name"));
  185. string region = Convert.ToString(obj.GetProperty("region"));
  186. string province = Convert.ToString(obj.GetProperty("province"));
  187. string city = Convert.ToString(obj.GetProperty("city"));
  188. string dist = (obj.TryGetProperty("dist", out JsonElement distJ)) ? Convert.ToString(distJ) : string.Empty;
  189. string areaId = (obj.TryGetProperty("areaId", out JsonElement areaIdJ)) ? Convert.ToString(areaIdJ) : string.Empty;
  190. string type = Convert.ToString(obj.GetProperty("code"));
  191. if (!schDic.ContainsKey(schId))
  192. {
  193. Dictionary<string, string> schDicRow = new() { { "name", name }, { "region", region }, { "province", province }, { "city", city }, { "dist", dist }, { "areaId", areaId }, { "type", type } };
  194. schDic.Add(schId, schDicRow);
  195. }
  196. }
  197. }
  198. //資料整理
  199. foreach (ProdAnalysisApiResult resultRow in result)
  200. {
  201. //記入TMID資料
  202. if(tmidDic.ContainsKey(resultRow.tmid))
  203. {
  204. resultRow.tmidInfo.name = tmidDic[resultRow.tmid];
  205. }
  206. //記入學校資料
  207. if(tmidSchidDic.ContainsKey(resultRow.tmid))
  208. {
  209. resultRow.schoolId = tmidSchidDic[resultRow.tmid];
  210. if (!string.IsNullOrWhiteSpace(resultRow.schoolId) && schDic.ContainsKey(resultRow.schoolId))
  211. {
  212. resultRow.school.name = schDic[resultRow.schoolId]["name"];
  213. resultRow.school.region = schDic[resultRow.schoolId]["region"];
  214. resultRow.school.province = schDic[resultRow.schoolId]["province"];
  215. resultRow.school.city = schDic[resultRow.schoolId]["city"];
  216. resultRow.school.dist = schDic[resultRow.schoolId]["dist"];
  217. resultRow.school.areaId = schDic[resultRow.schoolId]["areaId"];
  218. resultRow.school.type = schDic[resultRow.schoolId]["type"];
  219. }
  220. }
  221. }
  222. }
  223. ///學校
  224. else if(target.Equals("school"))
  225. {
  226. ////取得學校基本資訊 => 記入Dictionary
  227. Dictionary<string, Dictionary<string, string>> schDic = new();
  228. string SqlSch = $"SELECT c.id, c.name, c.code, c.region, c.province, c.city, c.areaId, c.dist FROM c WHERE (c.code = 'Base' OR c.code = 'VirtualBase')";
  229. //ARRAY_CONTAINS({schIdListStr}, c.id, true) AND
  230. string schIdListStr = JsonSerializer.Serialize(schoolIds);
  231. SqlSch += $" AND ARRAY_CONTAINS({schIdListStr}, c.id, true)";
  232. await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIteratorSql(queryText: SqlSch, requestOptions: null))
  233. {
  234. var json = await JsonDocument.ParseAsync(item.Content);
  235. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  236. {
  237. string schId = obj.GetProperty("id").GetString();
  238. string name = Convert.ToString(obj.GetProperty("name"));
  239. string region = Convert.ToString(obj.GetProperty("region"));
  240. string province = Convert.ToString(obj.GetProperty("province"));
  241. string city = Convert.ToString(obj.GetProperty("city"));
  242. string dist = (obj.TryGetProperty("dist", out JsonElement distJ)) ? Convert.ToString(distJ) : string.Empty;
  243. string areaId = (obj.TryGetProperty("areaId", out JsonElement areaIdJ)) ? Convert.ToString(areaIdJ) : string.Empty;
  244. string type = Convert.ToString(obj.GetProperty("code"));
  245. if (!schDic.ContainsKey(schId))
  246. {
  247. Dictionary<string, string> schDicRow = new() { { "name", name }, { "region", region }, { "province", province }, { "city", city }, { "dist", dist }, { "areaId", areaId }, { "type", type } };
  248. schDic.Add(schId, schDicRow);
  249. }
  250. }
  251. }
  252. //取得學校產品授權資訊
  253. Dictionary<string, List<SchoolProductSumDataService>> serviceResultDic = new Dictionary<string, List<SchoolProductSumDataService>>(); //key:學校ID
  254. Dictionary<string, List<SchProdSerial>> serialResultDic = new Dictionary<string, List<SchProdSerial>>();
  255. try
  256. {
  257. //服務
  258. string schoolIdsStr = JsonSerializer.Serialize(schoolIds);
  259. string SqlSchProdSoervice = $"SELECT * FROM c WHERE ARRAY_CONTAINS({schoolIdsStr}, c.id)";
  260. await foreach (SchoolProductSum schProductSum in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryIteratorSql<SchoolProductSum>(queryText: SqlSchProdSoervice, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"ProductSum") }))
  261. {
  262. if (schProductSum != null && schProductSum.service != null && schProductSum.service.Count > 0)
  263. {
  264. string schId = schProductSum.id;
  265. if (!serviceResultDic.ContainsKey(schId)) serviceResultDic.Add(schId, new List<SchoolProductSumDataService>());
  266. serviceResultDic[schId] = schProductSum.service;
  267. }
  268. }
  269. }
  270. catch (Exception ex)
  271. {
  272. }
  273. //軟體
  274. List<string> schoolCodes = new List<string>();
  275. foreach (string schId in schoolIds)
  276. {
  277. schoolCodes.Add($"Product-{schId}");
  278. }
  279. string schoolCodesStr = JsonSerializer.Serialize(schoolCodes);
  280. string SqlSchProdSoft = $"SELECT * FROM c WHERE c.dataType = 'serial' AND c.expireStatus = 'A' AND ARRAY_CONTAINS({schoolCodesStr}, c.code, true)";
  281. try
  282. {
  283. await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIteratorSql(queryText: SqlSchProdSoft, requestOptions: null))
  284. {
  285. var json = await JsonDocument.ParseAsync(item.Content);
  286. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  287. {
  288. SchoolProductSerial serialRow = obj.ToObject<SchoolProductSerial>();
  289. SchAuthDataSerial schAuthDataSerial = new SchAuthDataSerial();
  290. schAuthDataSerial.prodCode = serialRow.prodCode;
  291. schAuthDataSerial.serial = serialRow.serial;
  292. schAuthDataSerial.deviceBoundCnt = (serialRow.deviceBound != null) ? serialRow.deviceBound.Count : 0;
  293. schAuthDataSerial.clientQty = serialRow.clientQty;
  294. schAuthDataSerial.regDate = serialRow.regDate;
  295. schAuthDataSerial.startDate = serialRow.startDate;
  296. schAuthDataSerial.endDate = serialRow.endDate;
  297. schAuthDataSerial.deviceMax = serialRow.deviceMax;
  298. schAuthDataSerial.aprule = serialRow.aprule;
  299. string schId = serialRow.code.Replace("Product-", "");
  300. if (!serialResultDic.ContainsKey(schId)) serialResultDic.Add(schId, new List<SchProdSerial>());
  301. SchProdSerial schProdSerialNow = serialResultDic[schId].Where(s => s.prodCode.Equals(serialRow.prodCode) && s.endDate.Equals(serialRow.endDate) && s.deviceMax.Equals(serialRow.deviceMax)).FirstOrDefault();
  302. if (schProdSerialNow == null)
  303. {
  304. SchProdSerial schProdSerialNew = new SchProdSerial();
  305. schProdSerialNew.prodCode = serialRow.prodCode;
  306. schProdSerialNew.endDate = serialRow.endDate;
  307. schProdSerialNew.deviceMax = serialRow.deviceMax;
  308. schProdSerialNew.serials.Add(schAuthDataSerial);
  309. serialResultDic[schId].Add(schProdSerialNew);
  310. }
  311. else
  312. {
  313. schProdSerialNow.serials.Add(schAuthDataSerial);
  314. }
  315. }
  316. }
  317. }
  318. catch (Exception ex)
  319. {
  320. }
  321. ////取得IOT產品分析資訊、回傳結果製作
  322. List<string> calProperty = new List<string>() { "lessonRecord", "useIES", "useIES5Resource", "useWebIrs", "useDeviceIrs", "useHaboard", "useHita", "lessonLengMin", "lessonLeng0", "stuShow", "stuLessonLengMin", "tGreen", "tLesson", "lTypeCoop", "lTypeIact", "lTypeMis", "lTypeTst", "lTypeDif", "lTypeNone", "lessonCnt928", "lessonCntId", "lessonCntDevice", "lessonCntIdDevice", "mission", "missionFin", "item", "interact", "sendSok", "learnPeer", "learnCoop", "useWordCloud", "useClouDAS", "useGPT", "useIes5Test", "usePaperTest", "useExcelTest", "useScoreBoard", "learnParticipationCnt", "learnParticipationT", "coopMission", "coopWork", "coopContributionT", "peerAct", "peerStuParticipationT", "useTransMode", "useMiniMode" }; //要加算統計的欄位名
  323. Dictionary<string, ProdAnalysisApiResult> geoDic = new Dictionary<string, ProdAnalysisApiResult>(); //各地理資訊統計資料
  324. Dictionary<string, List<string>> geoSchDic = new Dictionary<string, List<string>>(); //地理ID->學校ID 字典
  325. string Sql = $"SELECT * FROM c WHERE c.toolType = '{prod}' AND c.dateUnit = '{dateUnit}' AND c.dateTime >= {dateTimeFromSec} AND c.dateTime <= {dateTimeToSec}";
  326. if(!extra.Equals("rmvnoall"))
  327. {
  328. schoolIds.Add("noschoolid");
  329. schoolIds.Add("allschool");
  330. }
  331. schIdListStr = JsonSerializer.Serialize(schoolIds);
  332. Sql += $" AND ARRAY_CONTAINS({schIdListStr}, c.schoolId, true)";
  333. await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIteratorSql(queryText: Sql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"ProdAnalysis") }))
  334. {
  335. var json = await JsonDocument.ParseAsync(item.Content);
  336. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  337. {
  338. ProdAnalysisApiResult resultRow = obj.ToObject<ProdAnalysisApiResult>();
  339. resultRow.lTypeNone = ((resultRow.lTypeNone - resultRow.lessonLeng0) < 0) ? 0 : resultRow.lTypeNone - resultRow.lessonLeng0; //無學習型態數值須扣除未上課
  340. //各校IOT統計
  341. if (schDic.ContainsKey(resultRow.schoolId))
  342. {
  343. resultRow.school.name = schDic[resultRow.schoolId]["name"];
  344. resultRow.school.region = schDic[resultRow.schoolId]["region"];
  345. resultRow.school.province = schDic[resultRow.schoolId]["province"];
  346. resultRow.school.city = schDic[resultRow.schoolId]["city"];
  347. resultRow.school.dist = schDic[resultRow.schoolId]["dist"];
  348. resultRow.school.areaId = schDic[resultRow.schoolId]["areaId"];
  349. resultRow.school.type = schDic[resultRow.schoolId]["type"];
  350. }
  351. //各校產品授權資訊
  352. result.Add(resultRow);
  353. //地理位置統計
  354. string geoId = string.Empty;
  355. switch (geoUnit)
  356. {
  357. case "region":
  358. geoId = resultRow.school.region;
  359. break;
  360. case "province":
  361. geoId = resultRow.school.region + resultRow.school.province;
  362. break;
  363. case "city":
  364. geoId = resultRow.school.region + resultRow.school.province + resultRow.school.city;
  365. break;
  366. case "dist":
  367. geoId = resultRow.school.region + resultRow.school.province + resultRow.school.city + resultRow.school.dist;
  368. break;
  369. }
  370. if (!string.IsNullOrWhiteSpace(geoId))
  371. {
  372. //統計資料
  373. string geoKey = $"{geoId}-{resultRow.date}";
  374. if (!geoDic.ContainsKey(geoKey)) geoDic.Add(geoKey, new ProdAnalysisApiResult());
  375. foreach (PropertyInfo propertyInfo in resultRow.GetType().GetProperties())
  376. {
  377. if (calProperty.Contains(propertyInfo.Name))
  378. {
  379. var propType = propertyInfo.PropertyType;
  380. var valNow = propertyInfo.GetValue(geoDic[geoKey]);
  381. var valTodo = resultRow.GetType().GetProperty(propertyInfo.Name).GetValue(resultRow);
  382. if (propType.Equals(typeof(long))) propertyInfo.SetValue(geoDic[geoKey], Convert.ToInt64(valNow.ToString(), 10) + Convert.ToInt64(valTodo.ToString(), 10));
  383. else propertyInfo.SetValue(geoDic[geoKey], Convert.ToInt32(valNow.ToString(), 10) + Convert.ToInt32(valTodo.ToString(), 10));
  384. }
  385. }
  386. decimal learnParticipationTmp = (resultRow.learnParticipationCnt > 0) ? (decimal)resultRow.learnParticipationT / (decimal)resultRow.learnParticipationCnt : 0;
  387. geoDic[geoKey].date = resultRow.date;
  388. geoDic[geoKey].learnParticipation = Math.Round(learnParticipationTmp, 2); //學習參與度指數(平均)
  389. geoDic[geoKey].deviceList = geoDic[geoKey].deviceList.Union(resultRow.deviceList).ToList();
  390. geoDic[geoKey].deviceCnt = geoDic[geoKey].deviceList.Count;
  391. geoDic[geoKey].deviceAuthList = geoDic[geoKey].deviceAuthList.Union(resultRow.deviceAuthList).ToList();
  392. geoDic[geoKey].deviceAuth = geoDic[geoKey].deviceAuthList.Count;
  393. geoDic[geoKey].deviceNoAuthList = geoDic[geoKey].deviceNoAuthList.Union(resultRow.deviceNoAuthList).ToList();
  394. geoDic[geoKey].deviceNoAuth = geoDic[geoKey].deviceNoAuthList.Count;
  395. geoDic[geoKey].tmidList = geoDic[geoKey].tmidList.Union(resultRow.tmidList).ToList();
  396. geoDic[geoKey].tmidCnt = geoDic[geoKey].tmidList.Count;
  397. //地理ID->學校ID 字典
  398. if (!geoSchDic.ContainsKey(geoId)) geoSchDic.Add(geoId, new List<string>());
  399. if (!geoSchDic[geoId].Contains(resultRow.schoolId)) geoSchDic[geoId].Add(resultRow.schoolId);
  400. }
  401. }
  402. }
  403. //地理資訊統計資料整理
  404. foreach (KeyValuePair<string, ProdAnalysisApiResult> geoDicRow in geoDic)
  405. {
  406. ProdAnalysisApiResultGeoExtend geoResultRow = new ProdAnalysisApiResultGeoExtend();
  407. foreach (PropertyInfo propertyInfo in geoDicRow.Value.GetType().GetProperties())
  408. {
  409. var valNow = propertyInfo.GetValue(geoDicRow.Value);
  410. propertyInfo.SetValue(geoResultRow, valNow);
  411. }
  412. geoResultRow.geoInfo = geoDicRow.Key.Split("-").First();
  413. if (geoSchDic.ContainsKey(geoResultRow.geoInfo)) geoResultRow.schoolIds = geoSchDic[geoResultRow.geoInfo];
  414. geoResult.Add(geoResultRow);
  415. }
  416. ////服務
  417. foreach (KeyValuePair<string, List<SchoolProductSumDataService>> op in serviceResultDic)
  418. {
  419. string schIdNow = op.Key;
  420. SchAuthData schAuthDataNow = auth.Where(a => a.schId.Equals(schIdNow)).FirstOrDefault();
  421. if (schAuthDataNow != null)
  422. {
  423. schAuthDataNow.authService = op.Value;
  424. }
  425. else
  426. {
  427. SchAuthData schAuthDataNew = new SchAuthData();
  428. schAuthDataNew.schId = schIdNow;
  429. schAuthDataNew.authService = op.Value;
  430. auth.Add(schAuthDataNew);
  431. }
  432. }
  433. ////序號
  434. foreach (KeyValuePair<string, List<SchProdSerial>> op in serialResultDic)
  435. {
  436. string schIdNow = op.Key;
  437. SchAuthData schAuthDataNow = auth.Where(a => a.schId.Equals(schIdNow)).FirstOrDefault();
  438. if (schAuthDataNow != null)
  439. {
  440. schAuthDataNow.authSerial = op.Value;
  441. }
  442. else
  443. {
  444. SchAuthData schAuthDataNew = new SchAuthData();
  445. schAuthDataNew.schId = schIdNow;
  446. schAuthDataNew.authSerial = op.Value;
  447. auth.Add(schAuthDataNew);
  448. }
  449. }
  450. }
  451. ///地理資訊
  452. else if (target.Equals("geo"))
  453. {
  454. ////取得IOT產品分析資訊、回傳結果製作
  455. List<string> calProperty = new List<string>() { "lessonRecord", "useIES", "useIES5Resource", "useWebIrs", "useDeviceIrs", "useHaboard", "useHita", "lessonLengMin", "lessonLeng0", "stuShow", "stuLessonLengMin", "tGreen", "tLesson", "lTypeCoop", "lTypeIact", "lTypeMis", "lTypeTst", "lTypeDif", "lTypeNone", "lessonCnt928", "lessonCntId", "lessonCntDevice", "lessonCntIdDevice", "mission", "missionFin", "item", "interact", "sendSok", "learnPeer", "learnCoop", "useWordCloud", "useClouDAS", "useGPT", "useIes5Test", "usePaperTest", "useExcelTest", "useScoreBoard", "learnParticipationCnt", "learnParticipationT", "coopMission", "coopWork", "coopContributionT", "peerAct", "peerStuParticipationT", "useTransMode", "useMiniMode" }; //要加算統計的欄位名
  456. Dictionary<string, ProdAnalysisApiResult> geoDic = new Dictionary<string, ProdAnalysisApiResult>(); //各地理資訊統計資料
  457. string Sql = $"SELECT * FROM c WHERE c.toolType = '{prod}' AND c.dateUnit = '{dateUnit}' AND c.dateTime >= {dateTimeFromSec} AND c.dateTime <= {dateTimeToSec}";
  458. if(!string.IsNullOrWhiteSpace(geo.countryId))
  459. {
  460. Sql += $" AND c.geo.countryId = '{geo.countryId}' ";
  461. }
  462. if (!string.IsNullOrWhiteSpace(geo.provinceId))
  463. {
  464. string provinceId = (Regex.IsMatch(geo.provinceId, "^\\d{6}$")) ? geo.provinceId.Substring(0, 2) : geo.provinceId;
  465. Sql += $" AND c.geo.provinceId = '{provinceId}' ";
  466. }
  467. if (!string.IsNullOrWhiteSpace(geo.cityId))
  468. {
  469. Sql += $" AND c.geo.cityId = '{geo.cityId}' ";
  470. }
  471. if (!string.IsNullOrWhiteSpace(geo.distId))
  472. {
  473. Sql += $" AND c.geo.distId = '{geo.distId}' ";
  474. }
  475. await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "Common").GetItemQueryStreamIteratorSql(queryText: Sql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"GeoAnalysis") }))
  476. {
  477. var json = await JsonDocument.ParseAsync(item.Content);
  478. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  479. {
  480. GeoAnalysisCosmos resultRow = obj.ToObject<GeoAnalysisCosmos>();
  481. resultRow.lTypeNone = ((resultRow.lTypeNone - resultRow.lessonLeng0) < 0) ? 0 : resultRow.lTypeNone - resultRow.lessonLeng0; //無學習型態數值須扣除未上課
  482. ProdAnalysisApiResultGeoExtend geoResultRow = new ProdAnalysisApiResultGeoExtend();
  483. geoResultRow.geoId = resultRow.geoId;
  484. geoResultRow.geo = resultRow.geo;
  485. geoResultRow.geoInfo = $"{resultRow.geo.countryName}" + $"{resultRow.geo.provinceName}" + $"{resultRow.geo.cityName}";
  486. geoResultRow.dateUnit = resultRow.dateUnit;
  487. geoResultRow.date = resultRow.date;
  488. geoResultRow.year = resultRow.year;
  489. geoResultRow.month = resultRow.month;
  490. geoResultRow.day = resultRow.day;
  491. geoResultRow.interact = resultRow.interact;
  492. geoResultRow.item = resultRow.item;
  493. geoResultRow.lTypeCoop = resultRow.lTypeCoop;
  494. geoResultRow.lTypeDif = resultRow.lTypeDif;
  495. geoResultRow.lTypeIact = resultRow.lTypeIact;
  496. geoResultRow.lTypeMis = resultRow.lTypeMis;
  497. geoResultRow.lTypeNone = resultRow.lTypeNone;
  498. geoResultRow.lTypeTst = resultRow.lTypeTst;
  499. geoResultRow.learnCoop = resultRow.learnCoop;
  500. geoResultRow.learnParticipation = resultRow.learnParticipation;
  501. geoResultRow.learnParticipationCnt = resultRow.learnParticipationCnt;
  502. geoResultRow.learnParticipationT = resultRow.learnParticipationT;
  503. geoResultRow.learnPeer = resultRow.learnPeer;
  504. geoResultRow.lessonCnt928 = resultRow.lessonCnt928;
  505. geoResultRow.lessonCntDevice = resultRow.lessonCntDevice;
  506. geoResultRow.lessonCntId = resultRow.lessonCntId;
  507. geoResultRow.lessonCntIdDevice = resultRow.lessonCntIdDevice;
  508. geoResultRow.lessonLeng0 = resultRow.lessonLeng0;
  509. geoResultRow.lessonLengMin = resultRow.lessonLengMin;
  510. geoResultRow.lessonRecord = resultRow.lessonRecord;
  511. geoResultRow.mission = resultRow.mission;
  512. geoResultRow.missionFin = resultRow.missionFin;
  513. geoResultRow.month = resultRow.month;
  514. geoResultRow.peerAct = resultRow.peerAct;
  515. geoResultRow.peerStuParticipationT = resultRow.peerStuParticipationT;
  516. geoResultRow.sendSok = resultRow.sendSok;
  517. geoResultRow.stuLessonLengMin = resultRow.stuLessonLengMin;
  518. geoResultRow.stuShow = resultRow.stuShow;
  519. geoResultRow.tGreen = resultRow.tGreen;
  520. geoResultRow.tLesson = resultRow.tLesson;
  521. geoResultRow.tmidCnt = resultRow.tmidCnt;
  522. geoResultRow.tmidList = resultRow.tmidList;
  523. geoResultRow.toolType = resultRow.toolType;
  524. geoResultRow.useClouDAS = resultRow.useClouDAS;
  525. geoResultRow.useDeviceIrs = resultRow.useDeviceIrs;
  526. geoResultRow.useExcelTest = resultRow.useExcelTest;
  527. geoResultRow.useGPT = resultRow.useGPT;
  528. geoResultRow.useHaboard = resultRow.useHaboard;
  529. geoResultRow.useHita = resultRow.useHita;
  530. geoResultRow.useIES = resultRow.useIES;
  531. geoResultRow.useIES5Resource = resultRow.useIES5Resource;
  532. geoResultRow.useIes5Test = resultRow.useIes5Test;
  533. geoResultRow.useMiniMode = resultRow.useMiniMode;
  534. geoResultRow.usePaperTest = resultRow.usePaperTest;
  535. geoResultRow.useScoreBoard = resultRow.useScoreBoard;
  536. geoResultRow.useTransMode = resultRow.useTransMode;
  537. geoResultRow.useWebIrs = resultRow.useWebIrs;
  538. geoResultRow.useWordCloud = resultRow.useWordCloud;
  539. geoResultRow.verList = resultRow.verList;
  540. geoResult.Add(geoResultRow);
  541. }
  542. }
  543. }
  544. return Ok(new { state = 200, data = result, geo = geoResult, auth });
  545. }
  546. catch (Exception ex)
  547. {
  548. await _dingDing.SendBotMsg($"BI,{_option.Location} /prodanalysis/get-iotstics \n {ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
  549. return BadRequest();
  550. }
  551. }
  552. /// <summary>
  553. /// 取得學校資訊
  554. /// </summary>
  555. /// <param name="jsonElement"></param>
  556. /// <returns></returns>
  557. [ProducesDefaultResponseType]
  558. [Authorize(Roles = "IES")]
  559. [HttpPost("get-school")]
  560. public async Task<IActionResult> GetSchoolInfo(JsonElement jsonElement)
  561. {
  562. try
  563. {
  564. var cosmosClient = _azureCosmos.GetCosmosClient();
  565. var cosmosClientCsv2 = _azureCosmos.GetCosmosClient(name: "CoreServiceV2"); //CosmosDB CSV2
  566. List<string> schoolIds = (jsonElement.TryGetProperty("schoolIds", out JsonElement schoolIdsJobj)) ? schoolIdsJobj.ToObject<List<string>>() : new List<string>(); //學校ID(array)
  567. string region = (jsonElement.TryGetProperty("region", out JsonElement regionJobj)) ? regionJobj.GetString() : string.Empty; //地區
  568. string province = (jsonElement.TryGetProperty("province", out JsonElement provinceJobj)) ? provinceJobj.GetString() : string.Empty; //省
  569. string city = (jsonElement.TryGetProperty("city", out JsonElement cityJobj)) ? cityJobj.GetString() : string.Empty; //市
  570. string dist = (jsonElement.TryGetProperty("dist", out JsonElement distJobj)) ? distJobj.GetString() : string.Empty; //區
  571. List<string> areaIds = (jsonElement.TryGetProperty("areaIds", out JsonElement areaIdsJobj)) ? areaIdsJobj.ToObject<List<string>>() : new List<string>(); //學區ID(array)
  572. bool hasVirtual = (jsonElement.TryGetProperty("virtual", out JsonElement virtualJobj)) ? virtualJobj.GetBoolean() : true; //是否取得虛擬學校
  573. //國際站欄位調整
  574. if(_option.Location.Contains("Global"))
  575. {
  576. if(string.IsNullOrWhiteSpace(region) && !string.IsNullOrWhiteSpace(province))
  577. {
  578. region = province;
  579. province = string.Empty;
  580. dist = string.Empty;
  581. }
  582. }
  583. //地區特殊字去除
  584. comeRemoveStr.ForEach(c => { region = region.Replace(c, ""); });
  585. comeRemoveStr.ForEach(c => { province = province.Replace(c, ""); });
  586. if (!_option.Location.Contains("Global")) comeRemoveStr.ForEach(c => { city = city.Replace(c, ""); }); //國際站不去除「市」的特殊字元,以區分縣市
  587. comeRemoveStr.ForEach(c => { dist = dist.Replace(c, ""); });
  588. //CosmosDB資料取得
  589. List<SimpleSchoolInfo> result = new List<SimpleSchoolInfo>();
  590. ///檢索條件全無對策
  591. if(schoolIds.Count.Equals(0) && string.IsNullOrWhiteSpace(region) && string.IsNullOrWhiteSpace(province) && string.IsNullOrWhiteSpace(city) && string.IsNullOrWhiteSpace(dist) && areaIds.Count.Equals(0))
  592. {
  593. return Ok(new { state = 200, data = result });
  594. }
  595. string schIdListStr = JsonSerializer.Serialize(schoolIds);
  596. string areaIdsListStr = JsonSerializer.Serialize(areaIds);
  597. string Sql = $"SELECT c.id, c.name, c.region, c.province, c.city, c.dist, c.areaId FROM c WHERE (c.pk = 'Base' OR c.pk = 'School')";
  598. if (schoolIds.Count > 0) Sql += $" AND ARRAY_CONTAINS({schIdListStr}, c.id, true)";
  599. if (!string.IsNullOrWhiteSpace(region)) Sql += $" AND CONTAINS(c.region, '{region}')";
  600. if (!string.IsNullOrWhiteSpace(province)) Sql += $" AND CONTAINS(c.province, '{province}')";
  601. if (!string.IsNullOrWhiteSpace(city)) Sql += $" AND CONTAINS(c.city, '{city}')";
  602. if (!string.IsNullOrWhiteSpace(dist)) Sql += $" AND CONTAINS(c.dist, '{dist}')";
  603. if (areaIds.Count > 0) Sql += $" AND ARRAY_CONTAINS({areaIdsListStr}, c.areaId, true)";
  604. //實體學校
  605. await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIteratorSql(queryText: Sql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base") }))
  606. {
  607. using var json = await JsonDocument.ParseAsync(item.Content);
  608. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  609. {
  610. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  611. {
  612. SimpleSchoolInfo resultRow = obj.ToObject<SimpleSchoolInfo>();
  613. result.Add(resultRow);
  614. }
  615. }
  616. }
  617. //虛擬學校
  618. if (hasVirtual)
  619. {
  620. await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIteratorSql(queryText: Sql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"VirtualBase") }))
  621. {
  622. using var json = await JsonDocument.ParseAsync(item.Content);
  623. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  624. {
  625. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  626. {
  627. SimpleSchoolInfo resultRow = obj.ToObject<SimpleSchoolInfo>();
  628. resultRow.isvirtual = true;
  629. result.Add(resultRow);
  630. }
  631. }
  632. }
  633. }
  634. return Ok(new { state = 200, data = result });
  635. }
  636. catch (Exception ex)
  637. {
  638. await _dingDing.SendBotMsg($"BI,{_option.Location} /prodanalysis/get-school \n {ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
  639. return BadRequest();
  640. }
  641. }
  642. public List<string> comeRemoveStr = new List<string>() { "省", "市", "区", "州", "县", "旗", "盟", "自治", "地區", "區", "縣" };
  643. private class SimpleSchoolInfo
  644. {
  645. public string id { get; set; }
  646. public string name { get; set; }
  647. public string region { get; set; }
  648. public string province { get; set; }
  649. public string city { get; set; }
  650. public string dist { get; set; }
  651. public string areaId { get; set; }
  652. public bool isvirtual { get; set; }
  653. }
  654. private class ProdAnalysisApiResultGeoExtend : ProdAnalysisApiResult
  655. {
  656. public string geoInfo { get; set; }
  657. public string geoId { get; set; }
  658. public Geo geo { get; set; }
  659. public List<string> schoolIds { get; set; } = new();
  660. }
  661. private class SchProdSerial
  662. {
  663. public string prodCode { get; set; }
  664. public long endDate { get; set; }
  665. public int deviceMax { get; set; }
  666. public List<SchAuthDataSerial> serials { get; set; } = new List<SchAuthDataSerial>();
  667. }
  668. private class SchAuthData {
  669. public string schId { get; set; }
  670. public List<SchProdSerial> authSerial { get; set; } = new();
  671. public List<SchoolProductSumDataService> authService { get; set; } = new();
  672. }
  673. private class SchAuthDataSerial
  674. {
  675. public string prodCode { get; set; }
  676. public string serial { get; set; }
  677. public int deviceBoundCnt { get; set; }
  678. public int clientQty { get; set; }
  679. public long regDate { get; set; }
  680. public long startDate { get; set; }
  681. public long endDate { get; set; }
  682. public int deviceMax { get; set; }
  683. public object aprule { get; set; }
  684. }
  685. }
  686. }