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

TDengineService.cs 59KB

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