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

TDengineService.cs 54KB

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