選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

TDengineService.cs 55KB

6ヶ月前
7ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
7ヶ月前
7ヶ月前
6ヶ月前
6ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271
  1. using HealthMonitor.Common;
  2. using HealthMonitor.Common.helper;
  3. using HealthMonitor.Model.Config;
  4. using HealthMonitor.Model.Service.Mapper;
  5. using HealthMonitor.Service.Biz.db.Dto;
  6. using HealthMonitor.Util.Models;
  7. using Microsoft.EntityFrameworkCore.Metadata.Internal;
  8. using Microsoft.Extensions.Logging;
  9. using Microsoft.Extensions.Options;
  10. using Newtonsoft.Json;
  11. using Newtonsoft.Json.Linq;
  12. using SqlSugar;
  13. using SqlSugar.DbConvert;
  14. using SqlSugar.TDengine;
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Data;
  18. using System.Linq;
  19. using System.Linq.Expressions;
  20. using System.Reflection;
  21. using System.Text;
  22. using System.Threading.Tasks;
  23. using System.Xml.Linq;
  24. using TDengineDriver;
  25. using TDengineDriver.Impl;
  26. using TDengineTMQ;
  27. using HealthMonitor.Service.Cache;
  28. using System.Text.RegularExpressions;
  29. using Etcdserverpb;
  30. namespace HealthMonitor.Service.Biz.db
  31. {
  32. public class TDengineService
  33. {
  34. private readonly ILogger<TDengineService> _logger;
  35. private readonly HttpHelper _httpHelper=default!;
  36. private readonly TDengineServiceConfig _configTDengineService;
  37. private readonly SqlSugarClient _clientSqlSugar;
  38. private readonly FhrPhrMapCacheManager _mgrFhrPhrMapCache;
  39. private readonly DeviceCacheManager _deviceCacheMgr;
  40. public TDengineService(ILogger<TDengineService> logger,
  41. IOptions<TDengineServiceConfig> configTDengineService,
  42. HttpHelper httpHelper,
  43. FhrPhrMapCacheManager fhrPhrMapCacheManager, DeviceCacheManager deviceCacheMgr
  44. )
  45. {
  46. _logger = logger;
  47. _configTDengineService = configTDengineService.Value;
  48. _httpHelper = httpHelper;
  49. _mgrFhrPhrMapCache = fhrPhrMapCacheManager;
  50. _deviceCacheMgr = deviceCacheMgr;
  51. _clientSqlSugar = new SqlSugarClient(new ConnectionConfig()
  52. {
  53. DbType = SqlSugar.DbType.TDengine,
  54. ConnectionString = $"Host={_configTDengineService.Host};Port={_configTDengineService.Port};Username={_configTDengineService.UserName};Password={_configTDengineService.Password};Database={_configTDengineService.DB};TsType=config_ns",
  55. IsAutoCloseConnection = true,
  56. AopEvents = new AopEvents
  57. {
  58. OnLogExecuting = (sql, p) =>
  59. {
  60. Console.WriteLine(SqlSugar.UtilMethods.GetNativeSql(sql, p));
  61. }
  62. },
  63. ConfigureExternalServices = new ConfigureExternalServices()
  64. {
  65. EntityService = (property, column) =>
  66. {
  67. if (column.SqlParameterDbType == null)
  68. {
  69. column.SqlParameterDbType = typeof(CommonPropertyConvert);
  70. }
  71. }
  72. },
  73. MoreSettings=new ConnMoreSettings()
  74. {
  75. PgSqlIsAutoToLower = false,
  76. PgSqlIsAutoToLowerCodeFirst = false,
  77. }
  78. });
  79. }
  80. public IntPtr Connection()
  81. {
  82. string host = _configTDengineService.Host;
  83. string user = _configTDengineService.UserName;
  84. string db = _configTDengineService.DB;
  85. short port = _configTDengineService.Port;
  86. string password = _configTDengineService.Password;
  87. //#if DEBUG
  88. // //string configDir = "C:/TDengine/cfg";
  89. // //TDengine.Options((int)TDengineInitOption.TSDB_OPTION_CONFIGDIR, configDir);
  90. // TDengine.Options((int)TDengineInitOption.TSDB_OPTION_TIMEZONE, "Asia/Beijing");
  91. //#endif
  92. IntPtr conn = TDengine.Connect(host, user, password, db, port);
  93. if (conn == IntPtr.Zero)
  94. {
  95. _logger.LogError($"连接 TDengine 失败....");
  96. }
  97. else
  98. {
  99. _logger.LogInformation($"连接 TDengine 成功....");
  100. }
  101. return conn;
  102. }
  103. public void ExecuteSQL(IntPtr conn, string sql)
  104. {
  105. IntPtr res = TDengine.Query(conn, sql);
  106. // Check if query success
  107. if ((res == IntPtr.Zero) || (TDengine.ErrorNo(res) != 0))
  108. {
  109. Console.Write(sql + " failure, ");
  110. // Get error message while Res is a not null pointer.
  111. if (res != IntPtr.Zero)
  112. {
  113. Console.Write("reason:" + TDengine.Error(res));
  114. }
  115. }
  116. else
  117. {
  118. Console.Write(sql + " success, {0} rows affected", TDengine.AffectRows(res));
  119. //... do something with res ...
  120. // Important: need to free result to avoid memory leak.
  121. TDengine.FreeResult(res);
  122. }
  123. }
  124. public void ExecuteQuerySQL(IntPtr conn, string sql)
  125. {
  126. IntPtr res = TDengine.Query(conn, sql);
  127. // Check if query success
  128. if ((res == IntPtr.Zero) || (TDengine.ErrorNo(res) != 0))
  129. {
  130. Console.Write(sql + " failure, ");
  131. // Get error message while Res is a not null pointer.
  132. if (res != IntPtr.Zero)
  133. {
  134. Console.Write("reason:" + TDengine.Error(res));
  135. }
  136. }
  137. else
  138. {
  139. Console.Write(sql + " success, {0} rows affected", TDengine.AffectRows(res));
  140. //... do something with res ...
  141. List<TDengineDriver.TDengineMeta> resMeta = LibTaos.GetMeta(res);
  142. List<object> resData = LibTaos.GetData(res);
  143. foreach (var meta in resMeta)
  144. {
  145. _logger.LogInformation("\t|{meta.name} {meta.TypeName()} ({meta.size})\t|", meta.name, meta.TypeName(), meta.size);
  146. }
  147. for (int i = 0; i < resData.Count; i++)
  148. {
  149. _logger.LogInformation($"|{resData[i].ToString()} \t");
  150. if (((i + 1) % resMeta.Count == 0))
  151. {
  152. _logger.LogInformation("");
  153. }
  154. }
  155. // Important: need to free result to avoid memory leak.
  156. TDengine.FreeResult(res);
  157. }
  158. }
  159. public void CheckRes(IntPtr conn, IntPtr res, String errorMsg)
  160. {
  161. if (TDengine.ErrorNo(res) != 0)
  162. {
  163. throw new Exception($"{errorMsg} since: {TDengine.Error(res)}");
  164. }
  165. }
  166. public void ExecuteInsertSQL(string sql)
  167. {
  168. var conn = Connection();
  169. try
  170. {
  171. //sql = "INSERT INTO d1001 USING meters TAGS('California.SanFrancisco', 2) VALUES ('2018-10-03 14:38:05.000', 10.30000, 219, 0.31000) ('2018-10-03 14:38:15.000', 12.60000, 218, 0.33000) ('2018-10-03 14:38:16.800', 12.30000, 221, 0.31000) " +
  172. // "d1002 USING power.meters TAGS('California.SanFrancisco', 3) VALUES('2018-10-03 14:38:16.650', 10.30000, 218, 0.25000) " +
  173. // "d1003 USING power.meters TAGS('California.LosAngeles', 2) VALUES('2018-10-03 14:38:05.500', 11.80000, 221, 0.28000)('2018-10-03 14:38:16.600', 13.40000, 223, 0.29000) " +
  174. // "d1004 USING power.meters TAGS('California.LosAngeles', 3) VALUES('2018-10-03 14:38:05.000', 10.80000, 223, 0.29000)('2018-10-03 14:38:06.500', 11.50000, 221, 0.35000)";
  175. //#if DEBUG
  176. // //string configDir = "C:/TDengine/cfg";
  177. // //TDengine.Options((int)TDengineInitOption.TSDB_OPTION_CONFIGDIR, configDir);
  178. // TDengine.Options((int)TDengineInitOption.TSDB_OPTION_TIMEZONE, "Asia/Beijing");
  179. //#endif
  180. _logger.LogInformation($"Insert SQL: {sql}");
  181. IntPtr res = TDengine.Query(conn, sql);
  182. CheckRes(conn, res, "failed to insert data");
  183. int affectedRows = TDengine.AffectRows(res);
  184. _logger.LogInformation("affectedRows {affectedRows}" , affectedRows);
  185. TDengine.FreeResult(res);
  186. }
  187. finally
  188. {
  189. TDengine.Close(conn);
  190. }
  191. }
  192. #region TDengine.Connector async query
  193. public void QueryCallback(IntPtr param, IntPtr taosRes, int code)
  194. {
  195. if (code == 0 && taosRes != IntPtr.Zero)
  196. {
  197. FetchRawBlockAsyncCallback fetchRowAsyncCallback = new FetchRawBlockAsyncCallback(FetchRawBlockCallback);
  198. TDengine.FetchRawBlockAsync(taosRes, fetchRowAsyncCallback, param);
  199. }
  200. else
  201. {
  202. _logger.LogInformation("async query data failed, failed code {code}",code);
  203. }
  204. }
  205. // Iteratively call this interface until "numOfRows" is no greater than 0.
  206. public void FetchRawBlockCallback(IntPtr param, IntPtr taosRes, int numOfRows)
  207. {
  208. if (numOfRows > 0)
  209. {
  210. _logger.LogInformation("{numOfRows} rows async retrieved", numOfRows);
  211. IntPtr pdata = TDengine.GetRawBlock(taosRes);
  212. List<TDengineMeta> metaList = TDengine.FetchFields(taosRes);
  213. List<object> dataList = LibTaos.ReadRawBlock(pdata, metaList, numOfRows);
  214. for (int i = 0; i < metaList.Count; i++)
  215. {
  216. _logger.LogInformation("{0} {1}({2}) \t|", metaList[i].name, metaList[i].type, metaList[i].size);
  217. }
  218. _logger.LogInformation("");
  219. for (int i = 0; i < dataList.Count; i++)
  220. {
  221. if (i != 0 && i % metaList.Count == 0)
  222. {
  223. _logger.LogInformation("{dataList[i]}\t|", dataList[i]);
  224. }
  225. _logger.LogInformation("{dataList[i]}\t|", dataList[i]);
  226. }
  227. TDengine.FetchRawBlockAsync(taosRes, FetchRawBlockCallback, param);
  228. }
  229. else
  230. {
  231. if (numOfRows == 0)
  232. {
  233. _logger.LogInformation("async retrieve complete.");
  234. }
  235. else
  236. {
  237. _logger.LogInformation("FetchRawBlockCallback callback error, error code {numOfRows}", numOfRows);
  238. }
  239. TDengine.FreeResult(taosRes);
  240. }
  241. }
  242. public void ExecuteQueryAsync(string sql)
  243. {
  244. var conn = Connection();
  245. QueryAsyncCallback queryAsyncCallback = new QueryAsyncCallback(QueryCallback);
  246. TDengine.QueryAsync(conn, sql, queryAsyncCallback, IntPtr.Zero);
  247. }
  248. //public void ExecuteQuery(string sql)
  249. //{
  250. // var conn = Connection();
  251. // QueryAsyncCallback queryAsyncCallback = new QueryAsyncCallback(QueryCallback);
  252. // TDengine.QueryAsync(conn, sql, queryAsyncCallback, IntPtr.Zero);
  253. //}
  254. public Aggregate GetAggregateValue(string field, string tbName, string? condition)
  255. {
  256. List<int> data = new();
  257. var sql = $"SELECT MAX({field}), MIN({field}) FROM {_configTDengineService.DB}.{tbName} WHERE {condition}";
  258. var conn = Connection();
  259. try
  260. {
  261. IntPtr res = TDengine.Query(conn, sql);
  262. // Check if query success
  263. if ((res == IntPtr.Zero) || (TDengine.ErrorNo(res) != 0))
  264. {
  265. Console.Write(sql + " failure, ");
  266. // Get error message while Res is a not null pointer.
  267. if (res != IntPtr.Zero)
  268. {
  269. Console.Write("reason:" + TDengine.Error(res));
  270. }
  271. }
  272. else
  273. {
  274. Console.Write(sql + " success, {0} rows affected", TDengine.AffectRows(res));
  275. //... do something with res ...
  276. List<TDengineMeta> resMeta = LibTaos.GetMeta(res);
  277. List<object> resData = LibTaos.GetData(res);
  278. foreach (var meta in resMeta)
  279. {
  280. Console.Write($"\t|{meta.name} {meta.TypeName()} ({meta.size})\t|");
  281. }
  282. resData.ForEach(x => data.Add(SafeType.SafeInt(x)));
  283. // Important: need to free result to avoid memory leak.
  284. TDengine.FreeResult(res);
  285. }
  286. }
  287. finally
  288. {
  289. TDengine.Close(conn);
  290. }
  291. return new Aggregate
  292. {
  293. Max = data.Count.Equals(0) ? 0 : data[0],
  294. Min = data.Count.Equals(0) ? 0 : data[1],
  295. };
  296. }
  297. public int GetAvgExceptMaxMinValue(string field, string tbName, string? condition)
  298. {
  299. List<int> data = new();
  300. var sql = $"SELECT MAX({field}), MIN({field}) FROM {_configTDengineService.DB}.{tbName} WHERE {condition}";
  301. var aggregate= GetAggregateValue(field, tbName, condition);
  302. var sqlAvg = $"SELECT AVG({field}) FROM {_configTDengineService.DB}.{tbName} WHERE {condition} AND {field} < {aggregate.Max} and {field} > {aggregate.Min}";
  303. var conn = Connection();
  304. try
  305. {
  306. IntPtr res = TDengine.Query(conn, sqlAvg);
  307. // Check if query success
  308. if ((res == IntPtr.Zero) || (TDengine.ErrorNo(res) != 0))
  309. {
  310. Console.Write(sqlAvg + " failure, ");
  311. // Get error message while Res is a not null pointer.
  312. if (res != IntPtr.Zero)
  313. {
  314. Console.Write("reason:" + TDengine.Error(res));
  315. }
  316. }
  317. else
  318. {
  319. Console.Write(sqlAvg + " success, {0} rows affected", TDengine.AffectRows(res));
  320. //... do something with res ...
  321. List<TDengineMeta> resMeta = LibTaos.GetMeta(res);
  322. List<object> resData = LibTaos.GetData(res);
  323. foreach (var meta in resMeta)
  324. {
  325. Console.Write($"\t|{meta.name} {meta.TypeName()} ({meta.size})\t|");
  326. }
  327. resData.ForEach(x => data.Add(SafeType.SafeInt(x)));
  328. // Important: need to free result to avoid memory leak.
  329. TDengine.FreeResult(res);
  330. }
  331. }
  332. finally
  333. {
  334. TDengine.Close(conn);
  335. }
  336. return data.Count.Equals(0) ? 0 : data[0];
  337. }
  338. #endregion
  339. #region RestAPI
  340. public async Task<string?> ExecuteQuerySQLRestResponse(string sql)
  341. {
  342. var url = $"http://{_configTDengineService.Host}:{_configTDengineService.RestPort}/rest/sql/{_configTDengineService.DB}";
  343. List<KeyValuePair<string, string>> headers = new()
  344. {
  345. new KeyValuePair<string, string>("Authorization", "Basic " + _configTDengineService.Token)
  346. };
  347. var result = await _httpHelper.HttpToPostAsync(url, sql, headers).ConfigureAwait(false);
  348. return result;
  349. }
  350. public async Task<string?> ExecuteSelectRestResponseAsync( string tbName, string condition="1", string field = "*")
  351. {
  352. var url = $"http://{_configTDengineService.Host}:{_configTDengineService.RestPort}/rest/sql/{_configTDengineService.DB}";
  353. var sql = $"SELECT {field} FROM {_configTDengineService.DB}.{tbName} WHERE {condition}";
  354. List<KeyValuePair<string, string>> headers = new()
  355. {
  356. new KeyValuePair<string, string>("Authorization", "Basic " + _configTDengineService.Token)
  357. };
  358. _logger.LogInformation($"{nameof(ExecuteSelectRestResponseAsync)} --- SQL 语句执行 {sql}");
  359. var result = await _httpHelper.HttpToPostAsync(url, sql, headers).ConfigureAwait(false);
  360. return result;
  361. }
  362. public async Task<bool> GernalRestSql(string sql)
  363. {
  364. //"http://{server}:{port}/rest/sql/{db}"
  365. var url = $"http://{_configTDengineService.Host}:{_configTDengineService.RestPort}/rest/sql/{_configTDengineService.DB}";
  366. List<KeyValuePair<string, string>> headers = new()
  367. {
  368. new KeyValuePair<string, string>("Authorization", "Basic " + _configTDengineService.Token)
  369. };
  370. var result = await _httpHelper.HttpToPostAsync(url, sql, headers).ConfigureAwait(false);
  371. var res = JsonConvert.DeserializeObject<TDengineRestResBase>(result!);
  372. if (result != null)
  373. {
  374. if (res?.Code == 0)
  375. {
  376. _logger.LogInformation($"{nameof(GernalRestSql)},SQL 语句执行成功|{sql}");
  377. return true;
  378. }
  379. else
  380. {
  381. _logger.LogWarning($"{nameof(GernalRestSql)},SQL 语句执行失败||{sql}");
  382. return false;
  383. }
  384. }
  385. else
  386. {
  387. _logger.LogError($"{nameof(GernalRestSql)},TDengine 服务器IP:{_configTDengineService.Host} 错误,请联系运维人员");
  388. return false;
  389. }
  390. //return res.Code==0;
  391. }
  392. public async Task<string?> GernalRestSqlResTextAsync(string sql)
  393. {
  394. _logger.LogInformation($"执行 SQL: {nameof(GernalRestSqlResTextAsync)}--{sql}");
  395. var url = $"http://{_configTDengineService.Host}:{_configTDengineService.RestPort}/rest/sql/{_configTDengineService.DB}";
  396. List<KeyValuePair<string, string>> headers = new()
  397. {
  398. new KeyValuePair<string, string>("Authorization", "Basic " + _configTDengineService.Token)
  399. };
  400. var result = await _httpHelper.HttpToPostAsync(url, sql, headers).ConfigureAwait(false);
  401. return result;
  402. }
  403. /** 取消
  404. /// <summary>
  405. /// 最大值,最小值聚合
  406. /// </summary>
  407. /// <param name="field"></param>
  408. /// <param name="tbName"></param>
  409. /// <param name="condition"></param>
  410. /// <returns></returns>
  411. public async Task<Aggregate> GetAggregateValueAsync(string field,string tbName,string? condition)
  412. {
  413. var sql = $"SELECT MAX({field}), MIN({field}) FROM {_configTDengineService.DB}.{tbName} WHERE {condition}";
  414. var result = await GernalRestSqlResTextAsync(sql);
  415. var res = JsonConvert.DeserializeObject<Aggregate>(result!);
  416. List<dynamic> data = res?.Data!;
  417. return new Aggregate
  418. {
  419. Max = data.Count.Equals(0) ? 0 : data[0][0],
  420. Min = data.Count.Equals(0) ? 0 : data[0][1],
  421. };
  422. }
  423. /// <summary>
  424. /// 去除最大值和最小值后的平均值
  425. /// </summary>
  426. /// <param name="field"></param>
  427. /// <param name="tbName"></param>
  428. /// <param name="condition"></param>
  429. /// <returns></returns>
  430. public async Task<int> GetAvgExceptMaxMinValueAsync(string field, string tbName, string? condition)
  431. {
  432. var sql = $"SELECT MAX({field}), MIN({field}) FROM {_configTDengineService.DB}.{tbName} WHERE {condition}";
  433. var result = await GernalRestSqlResTextAsync(sql);
  434. var res = JsonConvert.DeserializeObject<TDengineRestResBase>(result!);
  435. _logger.LogInformation($"最大小值:{sql}");
  436. List<dynamic> data = res?.Data!;
  437. var sqlAvg = $"SELECT AVG({field}) FROM {_configTDengineService.DB}.{tbName} WHERE {condition} AND {field} < { (data.Count.Equals(0)? 0: data[0][0]) } and {field} > {(data.Count.Equals(0) ? 0 : data[0][1])}";
  438. result = await GernalRestSqlResTextAsync(sqlAvg);
  439. _logger.LogInformation($"sqlAvg:{sqlAvg}");
  440. res = JsonConvert.DeserializeObject<TDengineRestResBase>(result!);
  441. data = res?.Data!;
  442. return data.Count.Equals(0)?0:(int)data[0][0];
  443. }
  444. /// <summary>
  445. /// 获取最后的记录
  446. /// </summary>
  447. /// <param name="tbName"></param>
  448. /// <param name="condition"></param>
  449. /// <returns></returns>
  450. public async Task<JArray?> GetLastAsync(string tbName, string? condition)
  451. {
  452. var sql = $"SELECT last_row(*) FROM {_configTDengineService.DB}.{tbName} WHERE {condition}";
  453. var result = await GernalRestSqlResTextAsync(sql);
  454. var res = JsonConvert.DeserializeObject<TDengineRestResBase>(result!);
  455. if(res?.Data?.Count==0) return null;
  456. List<dynamic> data = res?.Data!;
  457. return data[0] as JArray;
  458. }
  459. public async Task<int> GetCount(string tbName, string? condition)
  460. {
  461. var sql = $"SELECT count(ts) FROM {_configTDengineService.DB}.{tbName} WHERE {condition}";
  462. var result = await GernalRestSqlResTextAsync(sql);
  463. var res = JsonConvert.DeserializeObject<TDengineRestResBase>(result!);
  464. List<dynamic> data = res?.Data!;
  465. return SafeType.SafeInt(data[0][0]);
  466. }
  467. */
  468. #endregion
  469. /// <summary>
  470. /// 平均值算法(去除最大值,最小值和大于标定值的平均值)
  471. /// </summary>
  472. /// <param name="numToRemove"></param>
  473. /// <param name="collection"></param>
  474. /// <param name="max"></param>
  475. /// <param name="min"></param>
  476. /// <returns></returns>
  477. public static decimal AverageAfterRemovingOneMinMaxRef(List<int> collection, int max, int min,int refValue)
  478. {
  479. collection.Remove(max);
  480. collection.Remove(min);
  481. collection.RemoveAll(_ => _ > refValue);
  482. if (collection.Count < 2)
  483. {
  484. throw new ArgumentException($"数据集{collection.ToArray()},去掉一个最大值 {max}和一个最小值{min},异常值(大于标定值{refValue}),后数据值不足");
  485. }
  486. return (decimal)collection.Average(x => x);
  487. //var values = ParseData.Select(valueSelector).ToList();
  488. //collection = values.Select(i => (int)i).ToArray();
  489. //if (values.Count <= 2)
  490. //{
  491. // throw new ArgumentException("Not enough elements to remove.");
  492. //}
  493. //// Remove the specified number of minimum and maximum values
  494. ////values.RemoveAll(_ => _ == values.Min());
  495. ////values.RemoveAll(_ => _ == values.Max());
  496. //max = (int)values.Max();
  497. //min = (int)values.Min();
  498. //values.Remove(max);
  499. //values.Remove(min);
  500. //// Remove values less than the specified threshold
  501. //values.RemoveAll(_ => _ > numToRemove);
  502. //// Calculate and return the average of the remaining values
  503. //return values.Average();
  504. }
  505. /// <summary>
  506. /// 去除最大值和最小值各一个(列表的头和尾),再去除异常值
  507. /// </summary>
  508. /// <param name="systolicRefValue"></param>
  509. /// <param name="hmBpParser"></param>
  510. /// <returns></returns>
  511. public decimal[] AverageAfterRemovingOneMinMaxRef(int systolicRefValue, ParseTDengineRestResponse<BloodPressureModel>? hmBpParser)
  512. {
  513. var sortedList = hmBpParser?.Select(i => i)
  514. .Where(i => i.IsDisplay.Equals(true))
  515. .OrderByDescending(i => i.SystolicValue)
  516. .ThenByDescending(i => i.DiastolicValue)
  517. .ToList();
  518. _logger.LogInformation($"计算时间段排列数据集:{JsonConvert.SerializeObject(sortedList)}");
  519. // 去除最大值和最小值各一个(列表的头和尾)
  520. var trimmedList = sortedList?
  521. .Skip(1)
  522. .Take(sortedList.Count - 2)
  523. .ToList();
  524. _logger.LogInformation($"计算去除最大值和最小值各一个数据集:{JsonConvert.SerializeObject(trimmedList)}");
  525. // 去除异常值
  526. var filteredList = trimmedList?.Where(bp => bp.SystolicValue < SafeType.SafeInt(systolicRefValue!)).ToList();
  527. _logger.LogInformation($"计算除异常值个数据集:{JsonConvert.SerializeObject(filteredList)}");
  528. if (filteredList?.Count < 2)
  529. {
  530. // throw new ArgumentException("数据不够不能计算");
  531. // 平均值为0,说明数据不足,不能计算增量值
  532. return new decimal[] { 0M, 0M };
  533. }
  534. var systolicAvg = filteredList?.Select(bp => bp.SystolicValue).Average();
  535. var diastolicAvg = filteredList?.Select(bp => bp.DiastolicValue).Average();
  536. return new decimal[] { (decimal)systolicAvg!, (decimal)diastolicAvg! };
  537. }
  538. /// <summary>
  539. /// 去除最大值和最小值各一个(列表的头和尾)
  540. /// </summary>
  541. /// <param name="systolicRefValue"></param>
  542. /// <param name="hmBpParser"></param>
  543. /// <returns></returns>
  544. public decimal[] AverageAfterRemovingOneMinMaxRef(ParseTDengineRestResponse<BloodPressureModel>? hmBpParser)
  545. {
  546. var sortedList = hmBpParser?.Select(i => i)
  547. .Where(i => i.IsDisplay.Equals(true))
  548. .OrderByDescending(i => i.SystolicValue)
  549. .ThenByDescending(i => i.DiastolicValue)
  550. .ToList();
  551. _logger.LogInformation($"计算时间段排列数据集:{JsonConvert.SerializeObject(sortedList)}");
  552. // 去除最大值和最小值各一个(列表的头和尾)
  553. var trimmedList = sortedList?
  554. .Skip(1)
  555. .Take(sortedList.Count - 2)
  556. .ToList();
  557. _logger.LogInformation($"计算去除最大值和最小值各一个数据集:{JsonConvert.SerializeObject(trimmedList)}");
  558. var filteredList = trimmedList?.ToList();
  559. _logger.LogInformation($"计算除异常值个数据集:{JsonConvert.SerializeObject(filteredList)}");
  560. if (filteredList?.Count < 2)
  561. {
  562. // throw new ArgumentException("数据不够不能计算");
  563. // 平均值为0,说明数据不足,不能计算增量值
  564. return new decimal[] { 0M, 0M };
  565. }
  566. var systolicAvg = filteredList?.Select(bp => bp.SystolicValue).Average();
  567. var diastolicAvg = filteredList?.Select(bp => bp.DiastolicValue).Average();
  568. return new decimal[] { (decimal)systolicAvg!, (decimal)diastolicAvg! };
  569. }
  570. #region SqlSugarClient
  571. /**
  572. public async Task InsertFetalHeartRateAsync()
  573. {
  574. var tableName = typeof(FetalHeartRateModel)
  575. .GetCustomAttribute<STableAttribute>()?
  576. .STableName;
  577. if (tableName == null)
  578. {
  579. throw new InvalidOperationException("STableAttribute not found on FetalHeartRateModel class.");
  580. }
  581. await _clientSqlSugar.Ado.ExecuteCommandAsync($"create table IF NOT EXISTS hm_fhr_00 using {tableName} tags('00')");
  582. _clientSqlSugar.Insertable(new FetalHeartRateModel()
  583. {
  584. Timestamp = DateTime.Now,
  585. CreateTime = DateTime.Now,
  586. FetalHeartRate = 90,
  587. FetalHeartRateId = Guid.NewGuid().ToString("D"),
  588. IsDisplay = false,
  589. Method = 1,
  590. PersonId = Guid.NewGuid().ToString("D"),
  591. MessageId = Guid.NewGuid().ToString("D"),
  592. SerialNumber = Guid.NewGuid().ToString("D"),
  593. DeviceKey = Guid.NewGuid().ToString("D"),
  594. SerialTailNumber = "00",
  595. LastUpdate = DateTime.Now,
  596. }).AS("hm_fhr_00").ExecuteCommand();
  597. }
  598. public Task<FetalHeartRateModel> GetFirst()
  599. {
  600. var tableName = typeof(FetalHeartRateModel)
  601. .GetCustomAttribute<STableAttribute>()?
  602. .STableName;
  603. var first = _clientSqlSugar
  604. .Queryable<FetalHeartRateModel>()
  605. .AS(tableName)
  606. .OrderByDescending(x => x.Timestamp).FirstAsync();
  607. return first;
  608. }
  609. */
  610. /// <summary>
  611. /// 插入记录
  612. /// </summary>
  613. /// <typeparam name="T"></typeparam>
  614. /// <param name="model"></param>
  615. /// <param name="tbName">子表名称</param>
  616. /// <returns></returns>
  617. /// <exception cref="InvalidOperationException"></exception>
  618. public async Task InsertAsync<T>(string tbName, T model)
  619. {
  620. var stbName = typeof(T)
  621. .GetCustomAttribute<STableAttribute>()?
  622. .STableName;
  623. if (stbName == null)
  624. {
  625. throw new InvalidOperationException($"STableAttribute not found on {nameof(T)} class.");
  626. }
  627. var tailNo = typeof(T).GetProperty("SerialTailNumber")?.GetValue(model)?.ToString();
  628. if (string.IsNullOrEmpty(tailNo))
  629. {
  630. throw new InvalidOperationException($"SerialNumberAttribute not found on {nameof(T)} class.");
  631. }
  632. var tbFullName = $"{tbName}_{tailNo}";
  633. await _clientSqlSugar.Ado.ExecuteCommandAsync($"create table IF NOT EXISTS {tbFullName} using {stbName} tags('{tailNo}')");
  634. //_clientSqlSugar.InsertableByDynamic(model).AS(tbFullName).ExecuteCommand();
  635. _clientSqlSugar.InsertableByObject(model).AS(tbFullName).ExecuteCommand();
  636. }
  637. public Task<T> GetLastAsync<T>() where T : class
  638. {
  639. var tableName = typeof(T)
  640. .GetCustomAttribute<STableAttribute>()?
  641. .STableName;
  642. // 创建一个表示 Timestamp 属性的表达式
  643. var parameter = Expression.Parameter(typeof(T), "x");
  644. var property = Expression.Property(parameter, "Timestamp");
  645. var lambda = Expression.Lambda<Func<T, object>>(Expression.Convert(property, typeof(object)), parameter);
  646. var first = _clientSqlSugar
  647. .Queryable<T>()
  648. .AS(tableName)
  649. .OrderByDescending(lambda).FirstAsync();
  650. return first;
  651. }
  652. public Task<T> GetLastAsync<T>(string serialNo) where T : class
  653. {
  654. var tableName = typeof(T)
  655. .GetCustomAttribute<STableAttribute>()?
  656. .STableName;
  657. // 创建表示 Timestamp 属性的表达式
  658. var parameter = Expression.Parameter(typeof(T), "x");
  659. var timestampProperty = Expression.Property(parameter, "Timestamp");
  660. var timestampLambda = Expression.Lambda<Func<T, object>>(Expression.Convert(timestampProperty, typeof(object)), parameter);
  661. // 创建表示 SerialNo 属性的表达式
  662. var serialNoProperty = Expression.Property(parameter, "SerialNumber");
  663. var serialNoConstant = Expression.Constant(serialNo);
  664. var equalExpression = Expression.Equal(serialNoProperty, serialNoConstant);
  665. var serialNoLambda = Expression.Lambda<Func<T, bool>>(equalExpression, parameter);
  666. var first = _clientSqlSugar
  667. .Queryable<T>()
  668. .AS(tableName)
  669. .Where(serialNoLambda)
  670. .OrderByDescending(timestampLambda).FirstAsync();
  671. return first;
  672. }
  673. public async Task<List<T>> GetBySerialNoAsync<T>(string serialNo) where T : class
  674. {
  675. var tableName = typeof(T)
  676. .GetCustomAttribute<STableAttribute>()?
  677. .STableName;
  678. // 创建表示 Timestamp 属性的表达式
  679. var parameter = Expression.Parameter(typeof(T), "x");
  680. var timestampProperty = Expression.Property(parameter, "Timestamp");
  681. var timestampLambda = Expression.Lambda<Func<T, object>>(Expression.Convert(timestampProperty, typeof(object)), parameter);
  682. // 创建表示 SerialNo 属性的表达式
  683. var serialNoProperty = Expression.Property(parameter, "SerialNumber");
  684. var serialNoConstant = Expression.Constant(serialNo);
  685. var equalExpression = Expression.Equal(serialNoProperty, serialNoConstant);
  686. var serialNoLambda = Expression.Lambda<Func<T, bool>>(equalExpression, parameter);
  687. var res = await _clientSqlSugar
  688. .Queryable<T>()
  689. .AS(tableName)
  690. .Where(serialNoLambda)
  691. .OrderByDescending(timestampLambda).ToListAsync();
  692. return res;
  693. }
  694. public async Task<List<T>> GetBySerialNoAsync<T>(string serialNo, int days) where T : class
  695. {
  696. var tableName = typeof(T)
  697. .GetCustomAttribute<STableAttribute>()?
  698. .STableName;
  699. // 创建表示 Timestamp 属性的表达式
  700. var parameter = Expression.Parameter(typeof(T), "x");
  701. var timestampProperty = Expression.Property(parameter, "Timestamp");
  702. var timestampLambda = Expression.Lambda<Func<T, object>>(Expression.Convert(timestampProperty, typeof(object)), parameter);
  703. // 创建表示 SerialNo 属性的表达式
  704. var serialNoProperty = Expression.Property(parameter, "SerialNumber");
  705. var serialNoConstant = Expression.Constant(serialNo);
  706. var equalExpression = Expression.Equal(serialNoProperty, serialNoConstant);
  707. // 创建表示 Timestamp 大于指定天数的表达式
  708. var daysAgo = DateTime.Now.AddDays(-days);
  709. var daysAgoConstant = Expression.Constant(daysAgo, typeof(DateTime));
  710. var greaterThanExpression = Expression.GreaterThan(timestampProperty, daysAgoConstant);
  711. // 合并 SerialNo 和 Timestamp 条件
  712. var combinedExpression = Expression.AndAlso(equalExpression, greaterThanExpression);
  713. var combinedLambda = Expression.Lambda<Func<T, bool>>(combinedExpression, parameter);
  714. var res = await _clientSqlSugar
  715. .Queryable<T>()
  716. .AS(tableName)
  717. .Where(combinedLambda)
  718. .OrderByDescending(timestampLambda).ToListAsync();
  719. return res;
  720. }
  721. public async Task<int> DeleteAllBySerialNoCMDAsync<T>(string serialNo) where T : class
  722. {
  723. var stbName = typeof(T)
  724. .GetCustomAttribute<STableAttribute>()?
  725. .STableName;
  726. var endTime=DateTime.Now;
  727. var startTime = DateTime.Now.AddYears(-1);
  728. var sql = $"DELETE FROM {stbName} WHERE ts >= '{startTime:yyyy-MM-dd HH:mm:ss}' AND ts <= '{endTime:yyyy-MM-dd HH:mm:ss}' AND serialno='{serialNo}'";
  729. return await _clientSqlSugar.Ado.ExecuteCommandAsync(sql);
  730. }
  731. #endregion
  732. #region 胎心算法
  733. ///// <summary>
  734. ///// 计算个人一般心率(最大值,最大值,最小值)
  735. ///// </summary>
  736. ///// <param name="serialNo"></param>
  737. ///// <param name="days"></param>
  738. ///// <param name="percentage"></param>
  739. ///// <returns></returns>
  740. //public async Task<PregnancyCommonHeartRateModel?> InitPregnancyCommonHeartRateModeAsync(string serialNo, int days = 7, int percentage = 90)
  741. //{
  742. // var tableName = typeof(PregnancyHeartRateModel)
  743. // .GetCustomAttribute<STableAttribute>()?
  744. // .STableName;
  745. // var daysAgo = DateTime.Now.AddDays(-days);
  746. // var collection = await _clientSqlSugar
  747. // .Queryable<PregnancyHeartRateModel>()
  748. // .AS(tableName)
  749. // .Where(i => i.SerialNumber.Equals(serialNo))
  750. // .Where(i => i.Timestamp > daysAgo)
  751. // .OrderByDescending(i => i.Timestamp)
  752. // .ToArrayAsync();
  753. // var res = collection
  754. // .Select(i => i.PregnancyHeartRate).ToList();
  755. // // 心率数据量必须30个以上才进行计算
  756. // if (res.Count < 30)
  757. // {
  758. // _logger.LogInformation($"{serialNo} 心率数据不足,无法计算其众数");
  759. // return null;
  760. // }
  761. // #region 计算众数
  762. // var mode = res.GroupBy(n => n)
  763. // .OrderByDescending(g => g.Count())
  764. // .First()
  765. // .Key;
  766. // Console.WriteLine("众数是: " + mode);
  767. // // 如果有多个众数的情况
  768. // var maxCount = res.GroupBy(n => n)
  769. // .Max(g => g.Count());
  770. // var modes = res.GroupBy(n => n)
  771. // .Where(g => g.Count() == maxCount)
  772. // .Select(g => g.Key)
  773. // .ToList();
  774. // // 多个众数,选择最接近平均数或中位数的众数
  775. // if (modes.Count > 1)
  776. // {
  777. // // 计算平均值
  778. // double average = res.Average();
  779. // Console.WriteLine("平均值是: " + average);
  780. // // 计算中位数
  781. // double median;
  782. // int count = res.Count;
  783. // var sortedRes = res.OrderBy(n => n).ToList();
  784. // if (count % 2 == 0)
  785. // {
  786. // // 偶数个元素,取中间两个数的平均值
  787. // median = (sortedRes[count / 2 - 1] + sortedRes[count / 2]) / 2.0;
  788. // }
  789. // else
  790. // {
  791. // // 奇数个元素,取中间的数
  792. // median = sortedRes[count / 2];
  793. // }
  794. // //Console.WriteLine("中位数是: " + median);
  795. // _logger.LogInformation($"{serialNo} 中位数是: " + median);
  796. // // 找出最接近平均值的众数
  797. // //var closestToAverage = modes.OrderBy(m => Math.Abs(m - average)).First();
  798. // //Console.WriteLine("最接近平均值的众数是: " + closestToAverage);
  799. // // 找出最接近中位数的众数
  800. // var closestToMedian = modes.OrderBy(m => Math.Abs(m - median)).First();
  801. // _logger.LogInformation($"{serialNo} 最接近中位数的众数是: " + closestToMedian);
  802. // mode = closestToMedian;
  803. // }
  804. // #endregion
  805. // // 计算需要的数量
  806. // int requiredCount = (int)(res.Count * 0.9);
  807. // // 从原始数据集中获取最接近众数的元素
  808. // var closestToModeData = res.OrderBy(n => Math.Abs(n - mode))
  809. // .Take(requiredCount)
  810. // .ToList();
  811. // // 输出新数据集
  812. // _logger.LogInformation($"{serialNo} 新数据集: " + string.Join(", ", closestToModeData));
  813. // _logger.LogInformation($"{serialNo} 新数据集的数量: " + closestToModeData.Count);
  814. // var fhrMap = _mgrFhrPhrMapCache.GetHeartRatesMap();
  815. // var watchConfig = await _deviceCacheMgr.GetGpsDeviceWatchConfigCacheObjectBySerialNoAsync(serialNo, "0067");
  816. // if (watchConfig == null)
  817. // {
  818. // return null;
  819. // }
  820. // // long.TryParse(watchConfig["EDOC"]!.ToString(), out long edoc);
  821. // // "EDOC": "1720860180652",当前时间 - (EDOC - 280) days =怀孕时间
  822. // //edoc = edoc.ToString().Length == 10 ? edoc * 1000 : edoc;
  823. // var edoc = DateTimeUtil.ToDateTime(watchConfig["EDOC"]!.ToString());
  824. // int pregnancyWeek = (DateTime.Now - edoc.AddDays(-280)).Days / 7;
  825. // _logger.LogInformation($"IMEI {serialNo},EDOC:{edoc},NOW:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")},SinceNOW:{edoc.AddDays(-280).ToString("yyyy-MM-dd HH:mm:ss")},怀孕周数 {pregnancyWeek}");
  826. // float statMaxValueFprCoefficient = 0f;
  827. // float statMinValueFprCoefficient = 0f;
  828. // float StatModeAvgFprCoefficient = 0f;
  829. // // 20-45周之间
  830. // if (pregnancyWeek >= 12 && pregnancyWeek <= 45)
  831. // {
  832. // var map = fhrMap
  833. // .Where(i =>
  834. // i.PregnancyPeriod![0] <= pregnancyWeek &&
  835. // i.PregnancyPeriod[1] >= pregnancyWeek &&
  836. // i.PregnancyHeartRateRange![0] <= mode &&
  837. // i.PregnancyHeartRateRange[1] >= mode)
  838. // .FirstOrDefault();
  839. // if (map != null)
  840. // {
  841. // statMaxValueFprCoefficient = (float)Math.Round((decimal)map.FetalHeartRateRange![1] / res.Max(), 3);
  842. // statMinValueFprCoefficient = (float)Math.Round((decimal)map.FetalHeartRateRange[0] / res.Min(), 3);
  843. // StatModeAvgFprCoefficient = (float)Math.Round((decimal)map.FetalHeartRateAverage / mode, 3);
  844. // }
  845. // }
  846. // return new PregnancyCommonHeartRateModel()
  847. // {
  848. // Timestamp = DateTime.Now,
  849. // PersonId = collection.First().DeviceKey,
  850. // DeviceKey = collection.First().DeviceKey,
  851. // SerialNumber = collection.First().SerialNumber,
  852. // Mode = mode,
  853. // Percentage = percentage,
  854. // MaxValue = closestToModeData.Max(),
  855. // MinValue = closestToModeData.Min(),
  856. // OriginalMaxValue = res.Max(),
  857. // OriginalMinValue = res.Min(),
  858. // CreateTime = DateTime.Now,
  859. // StatStartTime = collection.OrderBy(i => i.Timestamp).Select(i => i.Timestamp).First(),
  860. // StatEndTime = collection.OrderBy(i => i.Timestamp).Select(i => i.Timestamp).Last(),
  861. // StatMaxValueFprCoefficient = statMaxValueFprCoefficient,
  862. // StatMinValueFprCoefficient = statMinValueFprCoefficient,
  863. // StatModeAvgFprCoefficient = StatModeAvgFprCoefficient,
  864. // Remark = string.Empty,
  865. // SerialTailNumber = serialNo.Substring(serialNo.Length - 2)
  866. // };
  867. //}
  868. public async Task<PregnancyCommonHeartRateModel?> InitPregnancyCommonHeartRateModeAsync(string serialNo, int days = 7, int percentage = 90, int highFreqSampleInterval=0)
  869. {
  870. var tableName = typeof(PregnancyHeartRateModel)
  871. .GetCustomAttribute<STableAttribute>()?
  872. .STableName;
  873. var daysAgo = DateTime.Now.AddDays(-days);
  874. var collection = await _clientSqlSugar
  875. .Queryable<PregnancyHeartRateModel>()
  876. .AS(tableName)
  877. .Where(i => i.SerialNumber.Equals(serialNo))
  878. .Where(i => i.LastUpdate > daysAgo)
  879. .OrderByDescending(i => i.LastUpdate)
  880. .ToArrayAsync();
  881. // 去除高频数据
  882. var filteredCollection = new List<PregnancyHeartRateModel>();
  883. PregnancyHeartRateModel? previousItem = null;
  884. //foreach (var item in collection)
  885. //{
  886. // if (previousItem == null || Math.Abs((previousItem.LastUpdate - item.LastUpdate).TotalSeconds) >= highFreqSampleInterval)
  887. // {
  888. // filteredCollection.Add(item);
  889. // previousItem = item;
  890. // }
  891. //}
  892. foreach (var item in collection)
  893. {
  894. if (previousItem!=null)
  895. {
  896. var timeDiff = (previousItem.LastUpdate - item.LastUpdate).TotalSeconds;
  897. Console.WriteLine($"previousItem:{previousItem.PregnancyHeartRate} -- item:{previousItem.PregnancyHeartRate} timeDiff:{timeDiff}--highFreqSampleInterval:{highFreqSampleInterval}");
  898. if (timeDiff >= highFreqSampleInterval)
  899. {
  900. filteredCollection.Add(item);
  901. }
  902. }
  903. previousItem = item;
  904. }
  905. // 心率数据量必须30个以上才进行计算
  906. if (filteredCollection.Count < 30)
  907. {
  908. _logger.LogInformation($"{serialNo} 心率数据不足,无法计算其众数");
  909. return null;
  910. }
  911. var res = filteredCollection
  912. .Select(i => i.PregnancyHeartRate).ToList();
  913. //// 心率数据量必须30个以上才进行计算
  914. //if (res.Count < 30)
  915. //{
  916. // _logger.LogInformation($"{serialNo} 心率数据不足,无法计算其众数");
  917. // return null;
  918. //}
  919. var listRes = filteredCollection.Select(i => new { last_update=i.LastUpdate.ToString("yyyy-MM-dd HH:mm:ss"),heart_rate=i.PregnancyHeartRate }).ToList();
  920. _logger.LogInformation($"highFreqSampleInterval:{highFreqSampleInterval},{serialNo} 去除高频数据后列表: {JsonConvert.SerializeObject(listRes)}");
  921. _logger.LogInformation($"{serialNo} 去除高频数据后的数据集: " + string.Join(", ", res));
  922. #region 计算众数
  923. var mode = res.GroupBy(n => n)
  924. .OrderByDescending(g => g.Count())
  925. .First()
  926. .Key;
  927. Console.WriteLine("众数是: " + mode);
  928. // 如果有多个众数的情况
  929. var maxCount = res.GroupBy(n => n)
  930. .Max(g => g.Count());
  931. var modes = res.GroupBy(n => n)
  932. .Where(g => g.Count() == maxCount)
  933. .Select(g => g.Key)
  934. .ToList();
  935. // 多个众数,选择最接近平均数或中位数的众数
  936. if (modes.Count > 1)
  937. {
  938. // 计算平均值
  939. double average = res.Average();
  940. Console.WriteLine("平均值是: " + average);
  941. // 计算中位数
  942. double median;
  943. int count = res.Count;
  944. var sortedRes = res.OrderBy(n => n).ToList();
  945. if (count % 2 == 0)
  946. {
  947. // 偶数个元素,取中间两个数的平均值
  948. median = (sortedRes[count / 2 - 1] + sortedRes[count / 2]) / 2.0;
  949. }
  950. else
  951. {
  952. // 奇数个元素,取中间的数
  953. median = sortedRes[count / 2];
  954. }
  955. //Console.WriteLine("中位数是: " + median);
  956. _logger.LogInformation($"{serialNo} 中位数是: " + median);
  957. // 找出最接近平均值的众数
  958. //var closestToAverage = modes.OrderBy(m => Math.Abs(m - average)).First();
  959. //Console.WriteLine("最接近平均值的众数是: " + closestToAverage);
  960. // 找出最接近中位数的众数
  961. var closestToMedian = modes.OrderBy(m => Math.Abs(m - median)).First();
  962. _logger.LogInformation($"{serialNo} 最接近中位数的众数是: " + closestToMedian);
  963. mode = closestToMedian;
  964. }
  965. #endregion
  966. // 计算需要的数量
  967. int requiredCount = (int)(res.Count * 0.9);
  968. // 从原始数据集中获取最接近众数的元素
  969. var closestToModeData = res.OrderBy(n => Math.Abs(n - mode))
  970. .Take(requiredCount)
  971. .ToList();
  972. // 输出新数据集
  973. _logger.LogInformation($"{serialNo} 新数据集: " + string.Join(", ", closestToModeData));
  974. _logger.LogInformation($"{serialNo} 新数据集的数量: {closestToModeData.Count},最大值: {closestToModeData.Max()},最小值 {closestToModeData.Min()},原始最大值: {res.Max()},原始最小值 {res.Min()}" );
  975. var fhrMap = _mgrFhrPhrMapCache.GetHeartRatesMap();
  976. var watchConfig = await _deviceCacheMgr.GetGpsDeviceWatchConfigCacheObjectBySerialNoAsync(serialNo, "0067");
  977. if (watchConfig == null)
  978. {
  979. return null;
  980. }
  981. // long.TryParse(watchConfig["EDOC"]!.ToString(), out long edoc);
  982. // "EDOC": "1720860180652",当前时间 - (EDOC - 280) days =怀孕时间
  983. //edoc = edoc.ToString().Length == 10 ? edoc * 1000 : edoc;
  984. var edoc = DateTimeUtil.ToDateTime(watchConfig["EDOC"]!.ToString());
  985. int pregnancyWeek = (DateTime.Now - edoc.AddDays(-280)).Days / 7;
  986. _logger.LogInformation($"IMEI {serialNo},EDOC:{edoc},NOW:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")},SinceNOW:{edoc.AddDays(-280).ToString("yyyy-MM-dd HH:mm:ss")},怀孕周数 {pregnancyWeek}");
  987. float statMaxValueFprCoefficient = 0f;
  988. float statMinValueFprCoefficient = 0f;
  989. float StatModeAvgFprCoefficient = 0f;
  990. // 20-45周之间
  991. if (pregnancyWeek >= 12 && pregnancyWeek <= 45)
  992. {
  993. var map = fhrMap
  994. .Where(i =>
  995. i.PregnancyPeriod![0] <= pregnancyWeek &&
  996. i.PregnancyPeriod[1] >= pregnancyWeek &&
  997. i.PregnancyHeartRateRange![0] <= mode &&
  998. i.PregnancyHeartRateRange[1] >= mode)
  999. .FirstOrDefault();
  1000. if (map != null)
  1001. {
  1002. statMaxValueFprCoefficient = (float)Math.Round((decimal)map.FetalHeartRateRange![1] / res.Max(), 3);
  1003. statMinValueFprCoefficient = (float)Math.Round((decimal)map.FetalHeartRateRange[0] / res.Min(), 3);
  1004. StatModeAvgFprCoefficient = (float)Math.Round((decimal)map.FetalHeartRateAverage / mode, 3);
  1005. }
  1006. }
  1007. // 如果最大值与最小值在60~100范围内,都按个固定值下发高频阈值。
  1008. var maxValue = closestToModeData.Max();
  1009. var minValue = closestToModeData.Min();
  1010. if ((maxValue >= 60 && maxValue <= 100) && (minValue >= 60 && minValue <= 100))
  1011. {
  1012. minValue = 60;
  1013. maxValue = 100;
  1014. }
  1015. return new PregnancyCommonHeartRateModel()
  1016. {
  1017. Timestamp = DateTime.Now,
  1018. PersonId = collection.First().DeviceKey,
  1019. DeviceKey = collection.First().DeviceKey,
  1020. SerialNumber = collection.First().SerialNumber,
  1021. Mode = mode,
  1022. Percentage = percentage,
  1023. MaxValue = maxValue,
  1024. MinValue = minValue,
  1025. OriginalMaxValue = res.Max(),
  1026. OriginalMinValue = res.Min(),
  1027. CreateTime = DateTime.Now,
  1028. StatStartTime = collection.OrderBy(i => i.Timestamp).Select(i => i.Timestamp).First(),
  1029. StatEndTime = collection.OrderBy(i => i.Timestamp).Select(i => i.Timestamp).Last(),
  1030. StatMaxValueFprCoefficient = statMaxValueFprCoefficient,
  1031. StatMinValueFprCoefficient = statMinValueFprCoefficient,
  1032. StatModeAvgFprCoefficient = StatModeAvgFprCoefficient,
  1033. Remark = string.Empty,
  1034. SerialTailNumber = serialNo.Substring(serialNo.Length - 2)
  1035. };
  1036. }
  1037. /// <summary>
  1038. /// 获取孕妇心率众数
  1039. /// </summary>
  1040. /// <param name="serialNo"></param>
  1041. /// <param name="days"></param>
  1042. /// <returns></returns>
  1043. //public async Task<int> GetPregnancyHeartRateModeAsync(string serialNo,int days=7)
  1044. //{
  1045. // var tableName = typeof(PregnancyHeartRateModel)
  1046. // .GetCustomAttribute<STableAttribute>()?
  1047. // .STableName;
  1048. // var daysAgo = DateTime.Now.AddDays(-days);
  1049. // var res = await _clientSqlSugar
  1050. // .Queryable<PregnancyHeartRateModel>()
  1051. // .AS(tableName)
  1052. // .Where(i=>i.SerialNumber.Equals(serialNo))
  1053. // .Where(i => i.Timestamp > daysAgo)
  1054. // //.OrderByDescending(i => i.PregnancyHeartRate)
  1055. // .Select(i =>i.PregnancyHeartRate)
  1056. // .ToListAsync();
  1057. // // 心率数据量必须30个以上才进行计算
  1058. // if (res.Count < 30) return 0;
  1059. // // 计算众数
  1060. // var mode = res.GroupBy(n => n)
  1061. // .OrderByDescending(g => g.Count())
  1062. // .First()
  1063. // .Key;
  1064. // Console.WriteLine("众数是: " + mode);
  1065. // // 如果有多个众数的情况
  1066. // var maxCount = res.GroupBy(n => n)
  1067. // .Max(g => g.Count());
  1068. // var modes = res.GroupBy(n => n)
  1069. // .Where(g => g.Count() == maxCount)
  1070. // .Select(g => g.Key)
  1071. // .ToList();
  1072. // // 多个众数,选择最接近平均数或中位数的众数
  1073. // if (modes.Count>1)
  1074. // {
  1075. // // 计算平均值
  1076. // double average = res.Average();
  1077. // Console.WriteLine("平均值是: " + average);
  1078. // // 计算中位数
  1079. // double median;
  1080. // int count = res.Count;
  1081. // var sortedRes = res.OrderBy(n => n).ToList();
  1082. // if (count % 2 == 0)
  1083. // {
  1084. // // 偶数个元素,取中间两个数的平均值
  1085. // median = (sortedRes[count / 2 - 1] + sortedRes[count / 2]) / 2.0;
  1086. // }
  1087. // else
  1088. // {
  1089. // // 奇数个元素,取中间的数
  1090. // median = sortedRes[count / 2];
  1091. // }
  1092. // Console.WriteLine("中位数是: " + median);
  1093. // // 找出最接近平均值的众数
  1094. // //var closestToAverage = modes.OrderBy(m => Math.Abs(m - average)).First();
  1095. // //Console.WriteLine("最接近平均值的众数是: " + closestToAverage);
  1096. // // 找出最接近中位数的众数
  1097. // var closestToMedian = modes.OrderBy(m => Math.Abs(m - median)).First();
  1098. // Console.WriteLine("最接近中位数的众数是: " + closestToMedian);
  1099. // mode = closestToMedian;
  1100. // }
  1101. // return mode;
  1102. //}
  1103. #endregion
  1104. }
  1105. }