You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4 月之前
3 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
1 年之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
3 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
3 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
3 月之前
4 月之前
3 月之前
3 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
1 年之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
1 年之前
4 月之前
4 月之前
4 月之前
4 月之前
4 月之前
3 月之前
3 月之前
3 月之前
3 月之前
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153
  1. using dotnet_etcd;
  2. using Etcdserverpb;
  3. using Google.Protobuf.WellKnownTypes;
  4. using HealthMonitor.Common;
  5. using HealthMonitor.Common.helper;
  6. using HealthMonitor.Core.Common.Extensions;
  7. using HealthMonitor.Core.Pipeline;
  8. using HealthMonitor.Model.Config;
  9. using HealthMonitor.Model.Service;
  10. using HealthMonitor.Model.Service.Mapper;
  11. using HealthMonitor.Service.Biz;
  12. using HealthMonitor.Service.Biz.db;
  13. using HealthMonitor.Service.Cache;
  14. using HealthMonitor.Service.Etcd;
  15. using HealthMonitor.Service.MessageQueue;
  16. using HealthMonitor.Service.Sub;
  17. using Microsoft.AspNetCore.Mvc.RazorPages;
  18. using Microsoft.EntityFrameworkCore.Metadata.Internal;
  19. using Microsoft.Extensions.Options;
  20. using NetTaste;
  21. using Newtonsoft.Json;
  22. using Newtonsoft.Json.Linq;
  23. using System;
  24. using System.Reflection;
  25. using System.Threading.Channels;
  26. using TDengineDriver;
  27. using TDengineTMQ;
  28. using TelpoDataService.Util.Clients;
  29. using TelpoDataService.Util.Entities.GpsCard;
  30. using TelpoDataService.Util.Entities.GpsLocationHistory;
  31. using TelpoDataService.Util.Models;
  32. using TelpoDataService.Util.QueryObjects;
  33. namespace HealthMonitor.WebApi
  34. {
  35. public class Worker : BackgroundService
  36. {
  37. private readonly ILogger<Worker> _logger;
  38. private readonly IServiceProvider _services;
  39. private readonly TDengineDataSubcribe _tdEngineDataSubcribe;
  40. private readonly PackageProcess _processor;
  41. private readonly TDengineService _serviceTDengine;
  42. private readonly EtcdService _serviceEtcd;
  43. private readonly HttpHelper _httpHelper = default!;
  44. private readonly IotApiService _serviceIotApi;
  45. private readonly BoodPressResolverConfig _configBoodPressResolver;
  46. private readonly BloodPressReferenceValueCacheManager _bpRefValCacheManager;
  47. private readonly PersonCacheManager _personCacheMgr;
  48. private readonly DeviceCacheManager _deviceCacheMgr;
  49. private readonly FetalMovementNormalValueRangeCacheManager _mgrFetalMovementNormalValueRangeCache;
  50. private readonly GpsLocationHistoryAccessorClient<HisGpsFetalHeartRate> _hisFetalHeartApiClient;
  51. private readonly GpsLocationHistoryAccessorClient<HisGpsFetalMovement> _hisFetalMovementApiClient;
  52. private readonly MqProcessLogic _serviceMqProcess;
  53. private CancellationTokenSource _tokenSource = default!;
  54. public Worker(ILogger<Worker> logger, IServiceProvider services, PersonCacheManager personCacheMgr,
  55. BloodPressReferenceValueCacheManager bpRefValCacheManager, IotApiService IotApiService,
  56. IOptions<BoodPressResolverConfig> optionBoodPressResolver, PackageProcess processor,
  57. TDengineDataSubcribe tdEngineDataSubcribe, TDengineService serviceDengine,
  58. GpsLocationHistoryAccessorClient<HisGpsFetalHeartRate> hisFetalHeartApiClient,
  59. GpsLocationHistoryAccessorClient<HisGpsFetalMovement> hisFetalMovementApiClient,
  60. FetalMovementNormalValueRangeCacheManager fetalMovementNormalValueRangeCacheMgr, MqProcessLogic serviceMqProcess,
  61. HttpHelper httpHelper, EtcdService serviceEtcd, DeviceCacheManager deviceCacheMgr)
  62. {
  63. _logger = logger;
  64. _tdEngineDataSubcribe = tdEngineDataSubcribe;
  65. _services = services;
  66. _serviceIotApi = IotApiService;
  67. _processor = processor;
  68. _serviceEtcd = serviceEtcd;
  69. _serviceTDengine = serviceDengine;
  70. _httpHelper = httpHelper;
  71. _configBoodPressResolver = optionBoodPressResolver.Value;
  72. _bpRefValCacheManager = bpRefValCacheManager;
  73. _personCacheMgr = personCacheMgr;
  74. _deviceCacheMgr = deviceCacheMgr;
  75. _hisFetalHeartApiClient = hisFetalHeartApiClient;
  76. _hisFetalMovementApiClient = hisFetalMovementApiClient;
  77. _serviceMqProcess = serviceMqProcess;
  78. _mgrFetalMovementNormalValueRangeCache = fetalMovementNormalValueRangeCacheMgr;
  79. }
  80. public override Task StartAsync(CancellationToken cancellationToken)
  81. {
  82. //_logger.LogInformation("------StartAsync");
  83. _tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
  84. return base.StartAsync(cancellationToken);
  85. }
  86. public override Task StopAsync(CancellationToken cancellationToken)
  87. {
  88. //_logger.LogInformation("------StopAsync");
  89. _tokenSource.Cancel(); //停止工作线程
  90. return base.StopAsync(cancellationToken);
  91. }
  92. protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  93. {
  94. var tasks = new[]
  95. {
  96. Task.Run(async () =>
  97. {
  98. _logger.LogInformation("解析器启动");
  99. while (!stoppingToken.IsCancellationRequested)
  100. {
  101. await _processor.ResolveAsync().ConfigureAwait(false);
  102. }
  103. }, stoppingToken),
  104. Task.Run(() =>
  105. {
  106. _logger.LogInformation("TDengine 订阅启动");
  107. while (!stoppingToken.IsCancellationRequested)
  108. {
  109. _tdEngineDataSubcribe.BeginListen(stoppingToken);
  110. }
  111. }, stoppingToken),
  112. Task.Run(() =>
  113. _serviceEtcd.WacthKeysWithPrefixResponseAsync("health_monitor/schedule_push", WatchEvents),
  114. stoppingToken)
  115. };
  116. await Task.WhenAll(tasks);
  117. }
  118. private void WatchEvents(WatchResponse response)
  119. {
  120. response.Events.ToList<Mvccpb.Event>().ForEach(async e =>
  121. {
  122. try
  123. {
  124. switch (e.Type.ToString())
  125. {
  126. case "Put":
  127. // 获取时间点计算TTL
  128. Console.BackgroundColor = ConsoleColor.Blue;
  129. Console.WriteLine($"--- {e.Type}--{e.Kv.Key.ToStringUtf8()}--{e.Kv.Value.ToStringUtf8()}---{DateTime.Now}");
  130. Console.BackgroundColor = ConsoleColor.Black;
  131. break;
  132. case "Delete":
  133. // TTL到了重新计算TTL,下发
  134. //Console.BackgroundColor = ConsoleColor.Green;
  135. //Console.WriteLine($"--- {e.Type}--{e.Kv.Key.ToStringUtf8()}--{e.Kv.Value.ToStringUtf8()}---{DateTime.Now}");
  136. // var key = $"health_monitor/schedule_push/imei/{bp.Serialno}";
  137. var key = e.Kv.Key.ToStringUtf8();
  138. var imeiDel = key.Split('/')[^1];
  139. var schedule_push = await _serviceEtcd.GetValAsync(key).ConfigureAwait(false);
  140. if (string.IsNullOrWhiteSpace(schedule_push))
  141. {
  142. if (key.Contains("pregnancy_heart_rate"))
  143. {
  144. var watchConfig = await _deviceCacheMgr.GetGpsDeviceWatchConfigCacheObjectBySerialNoAsync(imeiDel, "0067");
  145. var isFetalHeartEnable = watchConfig != null && (int)watchConfig["enabled"]! == 1;
  146. if (isFetalHeartEnable)
  147. {
  148. // 高频心率采样间隔 highFreqSampleInterval = highFreqSampleInterval+5,增加5秒兼容
  149. //var highFreqSampleInterval = (int)watchConfig!["highFreqSampleInterval"]! + 5;
  150. // 高频心率采样间隔 highFreqSampleInterval = highFreqSampleInterval+5,增加5秒兼容(最小highFreqSampleInterval=60)
  151. var highFreqSampleInterval = (int)watchConfig!["highFreqSampleInterval"]! >= 60 ? (int)watchConfig!["highFreqSampleInterval"]! + 5 : 60;
  152. // 处理孕妇业务,计算一般心率并下发
  153. var commonPHR = await _serviceTDengine.InitPregnancyCommonHeartRateModeAsync(imeiDel, highFreqSampleInterval: highFreqSampleInterval);
  154. if (commonPHR == null)
  155. {
  156. // 建模中
  157. var flag = await _serviceIotApi.SetFetalConfig(imeiDel);
  158. _logger.LogInformation($"{imeiDel} 建模建模中");
  159. }
  160. else
  161. {
  162. // 建模完成
  163. var flag = await _serviceIotApi.SetFetalConfig(imeiDel, 1, commonPHR.MaxValue, commonPHR.MinValue);
  164. _logger.LogInformation($"{imeiDel} 建模完成");
  165. // 保存到TDengine数据库
  166. await _serviceTDengine.InsertAsync<PregnancyCommonHeartRateModel>("hm_pchr", commonPHR);
  167. _logger.LogInformation($"保存TDengine完成");
  168. //
  169. }
  170. }
  171. #region 注册定时下发
  172. var startTime = DateTime.Now;
  173. // 注册下次下推
  174. var endTime = DateTime.Now;
  175. #if DEBUG
  176. //long ttl = (long)((60 * 1000-(endTime-startTime).TotalMilliseconds)/1000);
  177. //await _serviceEtcd.PutValAsync(key, imeiDel,ttl, false).ConfigureAwait(false);
  178. var interval = 0;
  179. // 获取当前时间
  180. DateTime now = DateTime.Now;
  181. // 计算距离下一个$interval天后的8点的时间间隔
  182. DateTime nextRunTime = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute + 2, 0).AddDays(interval);
  183. TimeSpan timeUntilNextRun = nextRunTime - now;
  184. // 如果当前时间已经超过了8点,将等待到明天后的8点
  185. if (timeUntilNextRun < TimeSpan.Zero)
  186. {
  187. timeUntilNextRun = timeUntilNextRun.Add(TimeSpan.FromMinutes(1));
  188. nextRunTime += timeUntilNextRun;
  189. }
  190. //long ttl = timeUntilNextRun.Milliseconds/1000;
  191. long ttl = (long)((timeUntilNextRun.TotalMilliseconds - (endTime - startTime).TotalMilliseconds) / 1000);
  192. var data = new
  193. {
  194. imei = imeiDel,
  195. create_time = now.ToString("yyyy-MM-dd HH:mm:ss"),
  196. ttl,
  197. next_run_time = nextRunTime.ToString("yyyy-MM-dd HH:mm:ss")
  198. };
  199. var result = JsonConvert.SerializeObject(data);
  200. await _serviceEtcd.PutValAsync(key, result, ttl, false).ConfigureAwait(false);
  201. #else
  202. // 每$interval天,晚上8点
  203. var interval = 1;
  204. // 获取当前时间
  205. DateTime now = DateTime.Now;
  206. var rand = new Random();
  207. var pushSec = rand.Next(59);
  208. int pushMin = int.TryParse(imeiDel.AsSpan(imeiDel.Length - 1), out pushMin) ? pushMin : 10;
  209. // 计算距离下一个$interval天后的0点的时间间隔
  210. DateTime nextRunTime = new DateTime(now.Year, now.Month, now.Day, 6, pushMin, pushSec).AddDays(interval);
  211. TimeSpan timeUntilNextRun = nextRunTime - now;
  212. // 如果当前时间已经超过了8点,将等待到明天后的8点
  213. if (timeUntilNextRun < TimeSpan.Zero)
  214. {
  215. timeUntilNextRun = timeUntilNextRun.Add(TimeSpan.FromDays(1));
  216. nextRunTime += timeUntilNextRun;
  217. }
  218. // var ttl = timeUntilNextRun.TotalMilliseconds;
  219. long ttl = (long)((timeUntilNextRun.TotalMilliseconds-(endTime-startTime).TotalMilliseconds)/1000);
  220. var data = new
  221. {
  222. imei = imeiDel,
  223. create_time = now.ToString("yyyy-MM-dd HH:mm:ss"),
  224. ttl,
  225. next_run_time = nextRunTime.ToString("yyyy-MM-dd HH:mm:ss")
  226. };
  227. var result = JsonConvert.SerializeObject(data);
  228. await _serviceEtcd.PutValAsync(key, result, ttl, false).ConfigureAwait(false);
  229. #endif
  230. #endregion
  231. }
  232. // health_monitor/schedule_push/cal_fetal_heart_rate/imei/
  233. else if (key.Contains("health_monitor/schedule_push/cal_fetal_heart_rate/imei/"))
  234. {
  235. var triggerValue= (JObject)JsonConvert.DeserializeObject(e.PrevKv.Value.ToStringUtf8())!;
  236. var trigger = triggerValue["trigger"]?.ToString();
  237. if (!string.IsNullOrEmpty(trigger))
  238. {
  239. var triggerHeartRate= JsonConvert.DeserializeObject<HisGpsHeartRate>(trigger);
  240. using (_logger.BeginScope(new Dictionary<string, object> { ["RequestId"] = triggerHeartRate?.MessageId! }))
  241. {
  242. var watchConfig = await _deviceCacheMgr.GetGpsDeviceWatchConfigCacheObjectBySerialNoAsync(imeiDel, "0067");
  243. _logger.LogInformation($"触发平常心率计算胎心");
  244. var isFetalHeartEnable = watchConfig != null && (int)watchConfig["enabled"]! == 1;
  245. if (isFetalHeartEnable)
  246. {
  247. // 告警上限阀值
  248. var upperAlarmThreshold = (int)watchConfig!["upperAlarmThreshold"]!;
  249. // 告警下限阀值
  250. var lowerAlarmThreshold = (int)watchConfig["lowerAlarmThreshold"]!;
  251. // 高频心率采样间隔 highFreqSampleInterval = highFreqSampleInterval+5,增加5秒兼容(最小highFreqSampleInterval=60)
  252. var highFreqSampleInterval = (int)watchConfig!["highFreqSampleInterval"]! >= 60 ? (int)watchConfig!["highFreqSampleInterval"]! + 5 : 60;
  253. var commonPHR = await _serviceTDengine.GetLastAsync<PregnancyCommonHeartRateModel>(imeiDel);
  254. // 最后一条孕妇心率
  255. var lastPhr = await _serviceTDengine.GetLastAsync<PregnancyHeartRateModel>(imeiDel);
  256. var isNormalHeartRate = triggerHeartRate?.MessageId == lastPhr.MessageId;
  257. // 判断最后一条孕妇心率与解析器的触发心率是否一致
  258. if (isNormalHeartRate)
  259. {
  260. //最后一条孕妇心率与解析器的触发心率一致
  261. HisGpsHeartRate heartRate = new()
  262. {
  263. CreateTime = lastPhr.CreateTime,
  264. DeviceKey = lastPhr.DeviceKey,
  265. HeartRate = lastPhr.PregnancyHeartRate,
  266. HeartRateId = lastPhr.PregnancyHeartRateId,
  267. IsDisplay = lastPhr.IsDisplay ? 1 : 0,
  268. MessageId = lastPhr.MessageId,
  269. LastUpdate = lastPhr.LastUpdate,
  270. Method = lastPhr.Method,
  271. PersonId = lastPhr.PersonId,
  272. Serialno = lastPhr.SerialNumber
  273. };
  274. var intervalFHR = 15;
  275. await CalculateNormalFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, intervalFHR, commonPHR);
  276. }
  277. // 不一致,触发心率是高频心率的首条
  278. else
  279. {
  280. var phr = await _serviceTDengine.GetBySerialNoAsync<PregnancyHeartRateModel>(imeiDel, 7);
  281. // 修改缓存中高频状态触发心率
  282. var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(imeiDel);
  283. if (phrFreqstatus != null)
  284. {
  285. #region 修改首条高频心率
  286. var freqFirstPhr = phr.Where(p => p.MessageId == triggerHeartRate?.MessageId).First();
  287. await _deviceCacheMgr.SetPregnancyHeartRateFreqStatusAsync(imeiDel, freqFirstPhr);
  288. _logger.LogInformation($"{imeiDel} 设置高频状态,修正触发的孕妇心率记录");
  289. #endregion
  290. }
  291. // 不在高频状态状态,但触发孕妇心率与最后一条孕妇心率不一致,
  292. // 平常心率数据是批量上报,需要计算每条心率对应的胎心数据
  293. else
  294. {
  295. _logger.LogInformation($"{imeiDel} 平常心率数据是批量上报,需要计算每条心率对应的胎心数据");
  296. #region 计算每条心率对应的胎心数据
  297. phr = phr.OrderByDescending(i => i.LastUpdate).ToList();
  298. // 获取高频数据
  299. var freqCollection = new List<PregnancyHeartRateModel>();
  300. PregnancyHeartRateModel? previousItem = null;
  301. foreach (var item in phr)
  302. {
  303. if (previousItem != null)
  304. {
  305. var timeNextDiff = (previousItem!.LastUpdate - item.LastUpdate).TotalSeconds;
  306. if (timeNextDiff <= highFreqSampleInterval)
  307. {
  308. freqCollection.Add(item);
  309. }
  310. }
  311. previousItem = item;
  312. }
  313. //去除高频
  314. foreach (var item in freqCollection)
  315. {
  316. phr.Remove(item);
  317. }
  318. // 排序并过滤高频数据
  319. //var previousItem = phr.OrderByDescending(i => i.LastUpdate).ToList().First();
  320. //phr = phr.Skip(1)
  321. // .Where(item =>
  322. // {
  323. // var timeNextDiff = (previousItem!.LastUpdate - item.LastUpdate).TotalSeconds;
  324. // previousItem = item;
  325. // return timeNextDiff > highFreqSampleInterval;
  326. // })
  327. // .Prepend(previousItem)
  328. // .OrderByDescending(i => i.LastUpdate)
  329. // .ToList();
  330. var calFhrTasks = phr.Where(p => p.LastUpdate >= triggerHeartRate!.LastUpdate)
  331. .Select( async p =>
  332. {
  333. HisGpsHeartRate heartRate = new()
  334. {
  335. CreateTime = p.CreateTime,
  336. DeviceKey = p.DeviceKey,
  337. HeartRate = p.PregnancyHeartRate,
  338. HeartRateId = p.PregnancyHeartRateId,
  339. IsDisplay = p.IsDisplay ? 1 : 0,
  340. MessageId = p.MessageId,
  341. LastUpdate = p.LastUpdate,
  342. Method = p.Method,
  343. PersonId = p.PersonId,
  344. Serialno = p.SerialNumber
  345. };
  346. var intervalFHR = 15;
  347. await CalculateNormalFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, intervalFHR, commonPHR);
  348. });
  349. await Task.WhenAll(calFhrTasks);
  350. #endregion
  351. }
  352. }
  353. }
  354. else
  355. {
  356. _logger.LogWarning($"{imeiDel} 胎心监测功能没有开启");
  357. }
  358. }
  359. }
  360. else
  361. {
  362. _logger.LogWarning($"{imeiDel} trigger is not set");
  363. }
  364. }
  365. //health_monitor/schedule_push/cal_fetal_movement/imei/
  366. else if (key.Contains("health_monitor/schedule_push/cal_fetal_movement/imei/"))
  367. {
  368. #region 胎动延时计算
  369. var watchConfig = await _deviceCacheMgr.GetGpsDeviceWatchConfigCacheObjectBySerialNoAsync(imeiDel, "0067");
  370. _logger.LogInformation($"触发胎动计算,设备配置{JsonConvert.SerializeObject(watchConfig)}");
  371. var isFetalHeartEnable = watchConfig != null && (int)watchConfig["enabled"]! == 1;
  372. if (isFetalHeartEnable)
  373. {
  374. var edoc = DateTimeUtil.ToDateTime(watchConfig!["EDOC"]!.ToString());
  375. // 已经建模
  376. var commonPHR = await _serviceTDengine.GetLastAsync<PregnancyCommonHeartRateModel>(imeiDel);
  377. if (commonPHR != null)
  378. {
  379. var phr = await _serviceTDengine.GetBySerialNoAsync<PregnancyHeartRateModel>(imeiDel, 7);
  380. _logger.LogInformation($"{imeiDel} 计算胎动数据 ");
  381. var fmNow = DateTime.Now;
  382. // 两小时前
  383. var fmNowSubtract = fmNow.AddMinutes(-fmNow.Minute).AddSeconds(-fmNow.Second).AddMilliseconds(-fmNow.Millisecond);
  384. var fetalMovementSampleTime = DateTimeUtil.ConvertToTimeStamp(fmNowSubtract).ToString()[..10];
  385. // 统计开始时间
  386. var statStartTime = fmNowSubtract.AddHours(-2);
  387. // 统计结束时间
  388. var statEndTime = fmNowSubtract;
  389. var isFetalMovementExisted = await _deviceCacheMgr.FetalMovementIsExistedAsync(imeiDel, fetalMovementSampleTime);
  390. _logger.LogInformation($"{imeiDel} 胎动记录{isFetalMovementExisted},数据采样时间:{fetalMovementSampleTime}|{fmNowSubtract.ToString("yyyy-MM-dd HH:mm:ss")}, 周期:{statStartTime}-{statEndTime} 开始");
  391. if (!isFetalMovementExisted)
  392. {
  393. /// 开始计算
  394. var phrRange = phr.Where(i => i.LastUpdate >= statStartTime && i.LastUpdate <= statEndTime)
  395. .OrderByDescending(i => i.LastUpdate)
  396. .Select(i => i.LastUpdate)
  397. .ToList();
  398. // 判断是否有持续佩戴
  399. if (phrRange.Count >= 2)
  400. {
  401. // 读取胎心数据
  402. GeneralParam param = new()
  403. {
  404. Filters = new List<QueryFilterCondition>
  405. {
  406. new ()
  407. {
  408. Key=nameof(HisGpsFetalHeartRate.Serialno),
  409. Value=imeiDel,
  410. ValueType=QueryValueTypeEnum.String,
  411. Operator=QueryOperatorEnum.Equal
  412. },
  413. //new ()
  414. //{
  415. // Key=nameof(HisGpsFetalHeartRate.SampleTime),
  416. // Value=sampleTime,
  417. // ValueType=QueryValueTypeEnum.String,
  418. // Operator=QueryOperatorEnum.GreaterEqual
  419. //},
  420. },
  421. OrderBys = new List<OrderByCondition>
  422. {
  423. new (){
  424. IsDesc=true,
  425. Key=nameof(HisGpsFetalHeartRate.SampleTime)
  426. }
  427. }
  428. };
  429. var fetalHeartRateIsAbnormal = 0;
  430. var fhr = await _hisFetalHeartApiClient.GetFirstAsync(param, imeiDel[^2..], null, new RequestHeader { RequestId = Guid.NewGuid().ToString("D") });
  431. // 胎心数据时间与胎动时间一致
  432. var time = long.Parse(fhr.SampleTime.Length < 13 ? fhr.SampleTime.PadRight(13, '0') : fhr.SampleTime);
  433. var fhrSampleTime = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(time);
  434. // 胎心数据时间与胎动时间一致,最后一条胎心数据与胎动数据的小时差不大于2
  435. if ((DateTime.Now.Hour - fhrSampleTime.Hour) <= 2)
  436. {
  437. var duringMins = Math.Abs((phrRange.First() - phrRange.Last()).TotalMinutes);
  438. //在餐后时间段(8:00~10:00,12:00~14:00,18:00~20:00,22:00~24:00)取中间值。其他时间段取正常起始值
  439. bool isInTimeRanges = IsLastUpdateInTimeRanges(phrRange.First());
  440. int pregnancyWeeks = (DateTime.Now - edoc.AddDays(-280)).Days / 7;
  441. if (pregnancyWeeks >= 12 && pregnancyWeeks <= 50)
  442. {
  443. var fetalMovementMap = _mgrFetalMovementNormalValueRangeCache.GetFetalMovements();
  444. var fetalMovementMapValue = isInTimeRanges ? fetalMovementMap
  445. .Where(i =>
  446. i.PregnancyPeriod![0] <= pregnancyWeeks &&
  447. i.PregnancyPeriod[1] >= pregnancyWeeks)
  448. .Select(i => i.MedianMovement)
  449. .FirstOrDefault()
  450. :
  451. fetalMovementMap
  452. .Where(i =>
  453. i.PregnancyPeriod![0] <= pregnancyWeeks &&
  454. i.PregnancyPeriod[1] >= pregnancyWeeks)
  455. .Select(i => i.InitialMovement)
  456. .FirstOrDefault()
  457. ;
  458. var fetalMovementValue = (fetalMovementMapValue * duringMins * 2) / 120;
  459. // 四舍五入
  460. var fetalMovement = (int)Math.Round(fetalMovementValue, 0, MidpointRounding.AwayFromZero);
  461. // _logger.LogInformation($"{imeiDel} segmentCountFMIndex: {i} -- fetalMovementSampleTime:{fetalMovementSampleTime}|{midNight.AddHours(2 * i).ToString("yyyy-MM-dd HH:mm:ss")} -- statStartTime: {statStartTime} -- statEndTime: {statEndTime}-- isFetalMovementExisted: {isFetalMovementExisted} ");
  462. _logger.LogInformation($"{imeiDel} 孕周:{pregnancyWeeks},胎动数据采样时间:{fetalMovementSampleTime}|{fmNowSubtract.ToString("yyyy-MM-dd HH:mm:ss")}, 采样周期:{statStartTime}-{statEndTime}, 原始胎动值:{fetalMovementMapValue}, 佩戴时间 :{duringMins}|{phrRange.Last()}-{phrRange.First()}, 胎动计算值:{fetalMovementValue}, 胎动最终值:{fetalMovement} 已完成.");
  463. // 获取胎心数据状态与胎动数据状态一致
  464. //etalHeartRateIsAbnormal= fhr.IsAbnormal;
  465. var feltalMovementIsAbnormal = fetalHeartRateIsAbnormal;
  466. await _serviceIotApi.SetFetalMovementConfig(imeiDel, fetalMovement, fetalMovementSampleTime, feltalMovementIsAbnormal);
  467. // 保存到MySQL数据库
  468. HisGpsFetalMovement fm = new()
  469. {
  470. FetalMovementId = Guid.NewGuid().ToString("D"),
  471. PersonId = commonPHR!.PersonId,
  472. Serialno = imeiDel,
  473. CreateTime = DateTime.Now,
  474. IsAbnormal = feltalMovementIsAbnormal,
  475. FetalMovementValue = fetalMovement,
  476. SampleTime = fetalMovementSampleTime,
  477. Method = 1,
  478. IsDisplay = 1,
  479. DeviceKey = commonPHR!.DeviceKey
  480. };
  481. await _hisFetalMovementApiClient.AddAsync(fm).ConfigureAwait(false);
  482. var device = await _deviceCacheMgr.GetDeviceBySerialNoAsync(imeiDel).ConfigureAwait(false);
  483. var fmMsgId = $"{imeiDel}-{fetalMovementSampleTime}-{Guid.NewGuid().ToString("D")[^3..]}";
  484. var fmMsgTime = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(long.Parse(fetalMovementSampleTime.Length < 13 ? fetalMovementSampleTime.PadRight(13, '0') : fetalMovementSampleTime)).ToString("yyyy-MM-dd HH:mm:ss");
  485. // 胎动数据推送到第三方
  486. var topic = "topic.push.third";
  487. var fmThridMsg = new
  488. {
  489. messageId = fmMsgId,
  490. topic = topic,
  491. time = fmMsgTime,
  492. data = new
  493. {
  494. imei = imeiDel,
  495. value = fetalMovement,
  496. isAbnormal = feltalMovementIsAbnormal,
  497. type = "fetalMovement"
  498. }
  499. };
  500. await _serviceMqProcess.ProcessIMEIEventMessageAsync(fmMsgId, topic, 31, fmThridMsg).ConfigureAwait(false);
  501. // 胎动数据推送到微信
  502. if (feltalMovementIsAbnormal != 0)
  503. {
  504. topic = "topic.push.wx";
  505. var fmMsg = new
  506. {
  507. messageId = Guid.NewGuid().ToString("D"),
  508. topic = topic,
  509. time = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(long.Parse(fetalMovementSampleTime.Length < 13 ? fetalMovementSampleTime.PadRight(13, '0') : fetalMovementSampleTime)).ToString("yyyy-MM-dd HH:mm:ss"),
  510. data = new
  511. {
  512. deviceId = device?.DeviceId,
  513. imei = imeiDel,
  514. alarmTypeId = 12,
  515. alarmDeviceName = imeiDel,
  516. alarmRemarks = JsonConvert.SerializeObject(new { fetalMovementValue = fetalMovement, isAbnormal = feltalMovementIsAbnormal }),
  517. address = string.Empty,
  518. deviceKey = device?.DeviceId
  519. }
  520. };
  521. await _serviceMqProcess.ProcessIMEIEventMessageAsync(fmMsgId, topic, fmMsg).ConfigureAwait(false);
  522. }
  523. // 设置入库缓存记录
  524. await _deviceCacheMgr.SetFetalMovementAsync(imeiDel, fetalMovementSampleTime, fm);
  525. }
  526. else
  527. {
  528. _logger.LogWarning($"{imeiDel} 孕周 {pregnancyWeeks},超出胎动计算范围");
  529. }
  530. }
  531. else
  532. {
  533. _logger.LogWarning($"{imeiDel} 最后一条胎心数据与胎动数据的小时差大于2,不计算胎动数据");
  534. }
  535. }
  536. else
  537. {
  538. _logger.LogInformation($"{imeiDel} 胎动记录{isFetalMovementExisted},数据采样时间:{fetalMovementSampleTime}|{fmNowSubtract.ToString("yyyy-MM-dd HH:mm:ss")}, 周期:{statStartTime}-{statEndTime} 不足两条,不能判断是否持续佩戴");
  539. }
  540. }
  541. else
  542. {
  543. _logger.LogInformation($"{imeiDel} 胎动记录{isFetalMovementExisted},数据采样时间:{fetalMovementSampleTime}|{fmNowSubtract.ToString("yyyy-MM-dd HH:mm:ss")}, 周期:{statStartTime}-{statEndTime} 已处理");
  544. }
  545. }
  546. }
  547. #endregion
  548. }
  549. else
  550. {
  551. // 处理血压业务
  552. int systolicInc;
  553. int diastolicInc;
  554. int systolicRefValue;
  555. int diastolicRefValue;
  556. decimal systolicAvg;
  557. decimal diastolicAvg;
  558. int systolicMax = 0;
  559. int diastolicMax = 0;
  560. // 统计时间
  561. //DateTime endTime = DateTime.Now; //测试
  562. DateTime statStartTime = DateTime.Now;
  563. // 最小值
  564. int systolicMin = 0;
  565. int diastolicMin = 0;
  566. // 偏移参数
  567. var avgOffset = 0.25M;
  568. var systolicAvgOffset = avgOffset;
  569. var diastolicAvgOffset = avgOffset;
  570. // 最后一次下发值
  571. int lastPushSystolicInc = 0;
  572. int lastPushDiastolicInc = 0;
  573. var startTime = DateTime.Now;
  574. // 下发增量值
  575. #region 统计定时下发增量值
  576. //var last = await _serviceTDengine.GetLastAsync("stb_hm_bloodpress_stats_inc", $"serialno='{imeiDel}' order by last_update desc");
  577. //var ts = last?[0];
  578. // 最后一条血压数据
  579. var condition = $"serialno='{imeiDel}' order by last_update desc";
  580. var field = "last_row(*)";
  581. var lastHmBpResponse = await _serviceTDengine.ExecuteSelectRestResponseAsync("stb_hm_bloodpress", condition, field);
  582. var lastHmBpParser = JsonConvert.DeserializeObject<ParseTDengineRestResponse<BloodPressureModel>>(lastHmBpResponse!);
  583. var lastHmBp = lastHmBpParser?.Select().FirstOrDefault();
  584. //if (lastHmBpParser?.Select()?.ToList().Count < 2)
  585. //{
  586. // _logger.LogInformation($"{imeiDel} -- {nameof(Worker)} --{nameof(WatchEvents)} -- 血压数据条目不足");
  587. // break;
  588. //}
  589. // 7 天有效数据
  590. if (lastHmBp?.Timestamp.AddDays(7) > DateTime.Now)
  591. {
  592. // 计算增量值
  593. condition = $"serialno='{imeiDel}' order by ts desc";
  594. var lastPushResponse = await _serviceTDengine.ExecuteSelectRestResponseAsync("stb_hm_bp_push_ref_inc_value", condition, field);
  595. if (lastPushResponse == null)
  596. {
  597. _logger.LogInformation($"{imeiDel}--没有下发记录");
  598. break;
  599. }
  600. var lastPushParser = JsonConvert.DeserializeObject<ParseTDengineRestResponse<BloodPressurePushRefIncModel>>(lastPushResponse);
  601. var lastPush = lastPushParser!.Select().FirstOrDefault();
  602. // 有下推记录
  603. if (lastPush != null)
  604. {
  605. systolicRefValue = lastPush!.SystolicRefValue;
  606. diastolicRefValue = lastPush!.DiastolicRefValue;
  607. lastPushSystolicInc = lastPush!.SystolicIncValue;
  608. lastPushDiastolicInc = lastPush!.DiastolicIncValue;
  609. condition = $"ts between '{lastPush?.Timestamp:yyyy-MM-dd HH:mm:ss.fff}' and '{startTime:yyyy-MM-dd HH:mm:ss.fff}' " +
  610. $"and serialno='{imeiDel}' " +
  611. $"and is_display = true";
  612. // 使用最近一次的下推时间作为统计的开始时间
  613. statStartTime = lastPush!.Timestamp;
  614. }
  615. // 没有下推记录(历史遗留数据),没有初始的测量值产生的平均值(测量值=平均值)
  616. else
  617. {
  618. #region 获取个人信息
  619. var person = await _personCacheMgr.GetDeviceGpsPersonCacheBySerialNoAsync(Guid.NewGuid().ToString(), imeiDel).ConfigureAwait(false);
  620. //验证这个信息是否存在
  621. if (person == null || person?.Person.BornDate == null)
  622. {
  623. _logger.LogWarning($"{nameof(Worker)}--{imeiDel} 验证个人信息,找不到个人信息,跳过此消息");
  624. break;
  625. }
  626. // 验证年龄是否在范围 (2 - 120)
  627. var age = SafeType.SafeInt(DateTime.Today.Year - person?.Person.BornDate!.Value.Year!);
  628. if (age < 2 || age > 120)
  629. {
  630. _logger.LogWarning($"{nameof(Worker)}--{imeiDel} 验证年龄,不在范围 (2 - 120)岁,跳过此消息");
  631. break;
  632. }
  633. var gender = person?.Person.Gender == true ? 1 : 2;
  634. var isHypertension = SafeType.SafeBool(person?.Person.Ishypertension!);
  635. var height = SafeType.SafeDouble(person?.Person.Height!);
  636. var weight = SafeType.SafeDouble(person?.Person.Weight!);
  637. #endregion
  638. #region 初始化常规血压标定值标定值
  639. var bpRef = await _bpRefValCacheManager.GetBloodPressReferenceValueAsync(age, gender, isHypertension);
  640. //systolicRefValue = bpRef!.Systolic;//?
  641. //diastolicRefValue = bpRef!.Diastolic;//?
  642. #endregion
  643. systolicRefValue = bpRef!.Systolic;
  644. diastolicRefValue = bpRef!.Diastolic;
  645. lastPushSystolicInc = 0;
  646. lastPushDiastolicInc = 0;
  647. condition = $"ts <= '{startTime:yyyy-MM-dd HH:mm:ss.fff}' " +
  648. $"and serialno='{imeiDel}' " +
  649. $"and is_display = true";
  650. }
  651. var hmBpResponse = await _serviceTDengine.ExecuteSelectRestResponseAsync("stb_hm_bloodpress", condition);
  652. var hmBpParser = JsonConvert.DeserializeObject<ParseTDengineRestResponse<BloodPressureModel>>(hmBpResponse!);
  653. var hmBp = hmBpParser?.Select();
  654. //if (hmBp?.ToList().Count < 2)
  655. // 1. 判断数据样本数量
  656. if (hmBpParser!.Rows < 5)
  657. {
  658. _logger.LogInformation($"{imeiDel} -- {nameof(Worker)} --{nameof(WatchEvents)} -- 统计定时下发,计算增量值的数据条目不足:{hmBpParser!.Rows} < 5");
  659. _logger.LogInformation($"{imeiDel} -- {nameof(Worker)} --{nameof(WatchEvents)} 没有足够的数据样本,不会定时下发");
  660. break;
  661. }
  662. // 没有下推记录重新计算统计时间
  663. if (lastPush == null)
  664. {
  665. var firstHmBp = hmBpParser?.Select(i => i).OrderBy(i => i.Timestamp).FirstOrDefault();
  666. statStartTime = firstHmBp!.Timestamp;
  667. }
  668. // NewMethod(systolicRefValue, hmBpParser);
  669. // 最大值
  670. //systolicMax = (int)hmBpParser?.Select(i => i.SystolicValue).Max()!;
  671. //diastolicMax = (int)hmBpParser?.Select(i => i.DiastolicValue).Max()!;
  672. //// 最小值
  673. //systolicMin = (int)hmBpParser?.Select(i => i.SystolicValue).Min()!;
  674. //diastolicMin = (int)hmBpParser?.Select(i => i.DiastolicValue).Min()!;
  675. //systolicAvg = (int)(hmBpParser?.AverageAfterRemovingOneMinMaxRef(i => i.SystolicValue, SafeType.SafeInt(systolicRefValue!)))!;
  676. //diastolicAvg = (int)(hmBpParser?.AverageAfterRemovingOneMinMaxRef(i => i.DiastolicValue, SafeType.SafeInt(diastolicRefValue!)))!;
  677. var avgs = _serviceTDengine.AverageAfterRemovingOneMinMaxRef(hmBpParser!);
  678. systolicAvg = avgs[0];
  679. diastolicAvg = avgs[1];
  680. // 2. 判断能否计算增量值
  681. if (systolicAvg.Equals(0))
  682. {
  683. _logger.LogInformation($"{imeiDel}--{nameof(Worker)}--计算平均值" +
  684. $"\n currentSystolicAvg:{systolicAvg} -- lastPushSystolicInc:{lastPushSystolicInc}" +
  685. $"\n currentDiastolicInc:{diastolicAvg} -- lastPushDiastolicInc:{lastPushDiastolicInc}");
  686. _logger.LogInformation($"{imeiDel}--{nameof(Worker)} 没有足够的数据样本计算平均值,不会定时下发");
  687. break;
  688. }
  689. // 除最大值和最小值后的平均值与标定值差值少于4后(当天计算出该结果则也不产生增量调整),就不再进行增量值调整了。
  690. if (systolicRefValue - systolicAvg < 4)
  691. {
  692. _logger.LogInformation($"diastolic 舒张压 {imeiDel}除最大值和最小值后的平均值:{diastolicAvg}与标定值:{diastolicRefValue}\n systolic 收缩压 {imeiDel}除最大值和最小值后的平均值:{systolicAvg}与标定值:{systolicRefValue}的差值(标定值-平均值)少于4后,systolic 收缩压 不再进行增量值调整");
  693. break;
  694. }
  695. if (diastolicRefValue - diastolicAvg < 4)
  696. {
  697. _logger.LogInformation($"systolic 收缩压 {imeiDel}除最大值和最小值后的平均值:{systolicAvg}与标定值:{systolicRefValue}\n diastolic 舒张压 {imeiDel}除最大值和最小值后的平均值:{diastolicAvg}与标定值:{diastolicRefValue}的差值(标定值-平均值)少于4后,diastolic 舒张压 不再进行增量值调整");
  698. break;
  699. }
  700. // 增量值=(标定值-平均值)* 0.25
  701. var currentSystolicInc = (int)((systolicRefValue - systolicAvg) * systolicAvgOffset)!;
  702. var currentDiastolicInc = (int)((diastolicRefValue - diastolicAvg) * diastolicAvgOffset)!;
  703. // 累计增量
  704. systolicInc = currentSystolicInc + lastPushSystolicInc;
  705. diastolicInc = currentDiastolicInc + lastPushDiastolicInc;
  706. _logger.LogInformation($"{imeiDel}--{nameof(Worker)}--计算增量值" +
  707. $"\n {imeiDel} -- systolicAvg:{systolicAvg}-- systolicInc:{systolicInc}-- currentSystolicInc:{currentSystolicInc} -- lastPushSystolicInc:{lastPushSystolicInc}" +
  708. $"\n {imeiDel} -- diastolicAvg:{diastolicAvg}-- diastolicInc:{diastolicInc} --currentDiastolicInc:{currentDiastolicInc} -- lastPushDiastolicInc:{lastPushDiastolicInc}");
  709. _logger.LogInformation($"{imeiDel}--{nameof(Worker)}-- 定时校准,发给设备的绝对增量值=(上次绝对增量值+新数据的增量值)");
  710. _logger.LogInformation($"{nameof(Worker)} 开启血压标定值下发: {_configBoodPressResolver.EnableBPRefPush}");
  711. if (_configBoodPressResolver.EnableBPRefPush)
  712. // if (false) // 临时关闭
  713. {
  714. BloodPressCalibrationConfigModel bpIncData = new()
  715. {
  716. Imei = imeiDel,
  717. SystolicRefValue = SafeType.SafeInt(((int)systolicRefValue!)), //收缩压标定值,值为0 表示不生效
  718. DiastolicRefValue = SafeType.SafeInt(((int)diastolicRefValue!)), //舒张压标定值,值为0表示不生效
  719. SystolicIncValue = SafeType.SafeInt(((int)systolicInc!)), //收缩压显示增量,值为0 表示不生效
  720. DiastolicIncValue = SafeType.SafeInt(((int)diastolicInc!)) //舒张压显示增量,值为0 表示不生效
  721. };
  722. //var pushedBP = await _serviceIotApi.SetBloodPressCalibrationConfigAsync(bpIncData).ConfigureAwait(false);
  723. var response = await _serviceIotApi.SetBloodPressCalibrationConfig2Async(bpIncData).ConfigureAwait(false);
  724. var pushedBP = response.Flag;
  725. if (pushedBP)
  726. {
  727. #region 保存下推记录 stb_hm_bp_push_ref_inc_value
  728. var sql = $"INSERT INTO health_monitor.hm_bp_push_ref_inc_value_{imeiDel.Substring(imeiDel.Length - 2)} " +
  729. $"USING health_monitor.stb_hm_bp_push_ref_inc_value " +
  730. $"TAGS ('{imeiDel.Substring(imeiDel.Length - 2)}') " +
  731. $"VALUES(" +
  732. $"'{startTime:yyyy-MM-dd HH:mm:ss.fff}'," +
  733. $"'{imeiDel}'," +
  734. $"{bpIncData.SystolicRefValue}," +
  735. $"{bpIncData.DiastolicRefValue}," +
  736. $"{bpIncData.SystolicIncValue}," +
  737. $"{bpIncData.DiastolicIncValue}," +
  738. $"{false}," +
  739. $"{systolicAvg}," +
  740. $"{diastolicAvg}," +
  741. $"{systolicAvgOffset}," +
  742. $"{diastolicAvgOffset}," +
  743. $"'{statStartTime:yyyy-MM-dd HH:mm:ss.fff}'," +
  744. $"'{startTime:yyyy-MM-dd HH:mm:ss.fff}'" +
  745. $")";
  746. _serviceTDengine.ExecuteInsertSQL(sql);
  747. #endregion
  748. #region 注册定时下发
  749. // 注册下次下推
  750. var endTime = DateTime.Now;
  751. #if DEBUG
  752. //long ttl = (long)((60 * 1000-(endTime-startTime).TotalMilliseconds)/1000);
  753. //await _serviceEtcd.PutValAsync(key, imeiDel,ttl, false).ConfigureAwait(false);
  754. var interval = 0;
  755. // 获取当前时间
  756. DateTime now = DateTime.Now;
  757. // 计算距离下一个$interval天后的8点的时间间隔
  758. DateTime nextRunTime = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute + 2, 0).AddDays(interval);
  759. TimeSpan timeUntilNextRun = nextRunTime - now;
  760. // 如果当前时间已经超过了8点,将等待到明天后的8点
  761. if (timeUntilNextRun < TimeSpan.Zero)
  762. {
  763. timeUntilNextRun = timeUntilNextRun.Add(TimeSpan.FromMinutes(1));
  764. nextRunTime += timeUntilNextRun;
  765. }
  766. // var ttl = timeUntilNextRun.TotalMilliseconds;
  767. long ttl = (long)((timeUntilNextRun.TotalMilliseconds - (endTime - startTime).TotalMilliseconds) / 1000);
  768. var data = new
  769. {
  770. imei = imeiDel,
  771. create_time = now.ToString("yyyy-MM-dd HH:mm:ss"),
  772. ttl,
  773. next_run_time = nextRunTime.ToString("yyyy-MM-dd HH:mm:ss")
  774. };
  775. var result = JsonConvert.SerializeObject(data);
  776. await _serviceEtcd.PutValAsync(key, result, ttl, false).ConfigureAwait(false);
  777. #else
  778. // 每$interval天,晚上8点
  779. var interval = 1;
  780. // 获取当前时间
  781. DateTime now = DateTime.Now;
  782. // 计算距离下一个$interval天后的8点的时间间隔
  783. DateTime nextRunTime = new DateTime(now.Year, now.Month, now.Day, 20, 0, 0).AddDays(interval);
  784. TimeSpan timeUntilNextRun = nextRunTime - now;
  785. // 如果当前时间已经超过了8点,将等待到明天后的8点
  786. if (timeUntilNextRun < TimeSpan.Zero)
  787. {
  788. timeUntilNextRun = timeUntilNextRun.Add(TimeSpan.FromDays(1));
  789. nextRunTime += timeUntilNextRun;
  790. }
  791. // var ttl = timeUntilNextRun.TotalMilliseconds;
  792. long ttl = (long)((timeUntilNextRun.TotalMilliseconds-(endTime-startTime).TotalMilliseconds)/1000);
  793. var data = new
  794. {
  795. imei = imeiDel,
  796. create_time = now.ToString("yyyy-MM-dd HH:mm:ss"),
  797. ttl,
  798. next_run_time = nextRunTime.ToString("yyyy-MM-dd HH:mm:ss")
  799. };
  800. var result = JsonConvert.SerializeObject(data);
  801. await _serviceEtcd.PutValAsync(key, result, ttl, false).ConfigureAwait(false);
  802. #endif
  803. #endregion
  804. }
  805. else
  806. {
  807. _logger.LogInformation($"错误响应,没有下推数据:{response.Message}");
  808. }
  809. }
  810. }
  811. else
  812. {
  813. _logger.LogInformation($"向{imeiDel}统计数据已经失效");
  814. }
  815. #endregion
  816. }
  817. }
  818. break;
  819. }
  820. }
  821. catch (Exception ex)
  822. {
  823. _logger.LogInformation($"{nameof(WatchEvents)},出错: |{ex.Message}|{ex.StackTrace}");
  824. }
  825. });
  826. }
  827. private async Task SetIntervalTriggerAsync(string key, string imei, long interval)
  828. {
  829. // var key = $"health_monitor/schedule_push/{type}/imei/{imei}";
  830. var schedulePush = await _serviceEtcd.GetValAsync(key).ConfigureAwait(false);
  831. if (string.IsNullOrWhiteSpace(schedulePush))
  832. {
  833. var now = DateTime.Now;
  834. var timeNextRun = now.Add(TimeSpan.FromSeconds(interval));
  835. var data = new
  836. {
  837. imei,
  838. create_time = now.ToString("yyyy-MM-dd HH:mm:ss"),
  839. ttl = interval,
  840. next_run_time = timeNextRun.ToString("yyyy-MM-dd HH:mm:ss")
  841. };
  842. var result = JsonConvert.SerializeObject(data);
  843. await _serviceEtcd.PutValAsync(key, result, interval, false).ConfigureAwait(false);
  844. }
  845. }
  846. public static bool IsNowInTimeRanges()
  847. {
  848. var now = DateTime.Now.TimeOfDay;
  849. var timeRanges = new List<(TimeSpan Start, TimeSpan End)>
  850. {
  851. // 8:00~10:00,12:00~14:00,18:00~20:00,22:00~24:00
  852. (new TimeSpan(8, 0, 0), new TimeSpan(10, 0, 0)),
  853. (new TimeSpan(12, 0, 0), new TimeSpan(14, 0, 0)),
  854. (new TimeSpan(18, 0, 0), new TimeSpan(20, 0, 0)),
  855. (new TimeSpan(22, 0, 0), new TimeSpan(24, 0, 0))
  856. };
  857. return timeRanges.Any(range => now >= range.Start && now <= range.End);
  858. }
  859. public static bool IsLastUpdateInTimeRanges(DateTime lastUpdate)
  860. {
  861. var now = lastUpdate.TimeOfDay;
  862. var timeRanges = new List<(TimeSpan Start, TimeSpan End)>
  863. {
  864. // 8:00~10:00,12:00~14:00,18:00~20:00,22:00~24:00
  865. (new TimeSpan(8, 0, 0), new TimeSpan(10, 0, 0)),
  866. (new TimeSpan(12, 0, 0), new TimeSpan(14, 0, 0)),
  867. (new TimeSpan(18, 0, 0), new TimeSpan(20, 0, 0)),
  868. (new TimeSpan(22, 0, 0), new TimeSpan(24, 0, 0))
  869. };
  870. return timeRanges.Any(range => now >= range.Start && now <= range.End);
  871. }
  872. private async Task CalculateNormalFetalHeartRateAsync(HisGpsHeartRate heartRate, int upperAlarmThreshold, int lowerAlarmThreshold, int intervalFHR, PregnancyCommonHeartRateModel? commonPHR)
  873. {
  874. // 上15分钟的数据
  875. // 获取当前时间
  876. DateTime nowInterval = (DateTime)heartRate.LastUpdate!;
  877. // 计算last_update到上一间隔的分钟数
  878. int minutesToSubtract = nowInterval.Minute % intervalFHR;
  879. // 计算上一间隔的时间
  880. DateTime previousInterval = nowInterval.AddMinutes(-minutesToSubtract).AddSeconds(-nowInterval.Second).AddMilliseconds(-nowInterval.Millisecond);
  881. // 使用 last_update 上一刻
  882. var sampleTimeFHR = DateTimeUtil.ConvertToTimeStamp(previousInterval).ToString();
  883. // 计算last_update到下一间隔的分钟数
  884. int minutesToAdd = intervalFHR - (nowInterval.Minute % intervalFHR);
  885. if (minutesToAdd == intervalFHR)
  886. {
  887. minutesToAdd = 0; // 如果已经是间隔,则不需要增加分钟
  888. }
  889. // 计算下一间隔的时间
  890. DateTime nextInterval = nowInterval.AddMinutes(minutesToAdd)
  891. .AddSeconds(-nowInterval.Second)
  892. .AddMilliseconds(-nowInterval.Millisecond);
  893. var daysPhr = await _serviceTDengine.GetBySerialNoAsync<PregnancyHeartRateModel>(heartRate.Serialno, 7);
  894. var normalPhrStatStartTime = nextInterval.AddMinutes(-intervalFHR);
  895. var normalPhrStatEndTime = nextInterval;
  896. _logger.LogInformation($"{heartRate.Serialno} 计算胎心数据, 周期:{normalPhrStatStartTime}-{normalPhrStatEndTime} ");
  897. var filteredPhr = daysPhr
  898. // 使用 last_update 下一刻
  899. .Where(i => i.LastUpdate <= normalPhrStatEndTime && i.LastUpdate >= normalPhrStatStartTime)
  900. .ToList();
  901. if (filteredPhr.Count == 0)
  902. {
  903. _logger.LogWarning($"{heartRate.Serialno} 周期:{normalPhrStatStartTime}-{normalPhrStatEndTime} 孕妇心率数据不足,{filteredPhr.Count}条记录");
  904. return;
  905. }
  906. var phrValue = filteredPhr.Count == 1
  907. ? filteredPhr.First().PregnancyHeartRate
  908. : filteredPhr.Average(i => i.PregnancyHeartRate);
  909. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR!, upperAlarmThreshold, lowerAlarmThreshold, phrValue, sampleTimeFHR);
  910. await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR!, upperAlarmThreshold, lowerAlarmThreshold, phrValue, sampleTimeFHR, normalPhrStatStartTime, normalPhrStatEndTime);
  911. }
  912. private async Task SaveAndPushFetalHeartRateAsync(HisGpsHeartRate heartRate, PregnancyCommonHeartRateModel commonPHR, int upperAlarmThreshold, int lowerAlarmThreshold, double phrValue, string sampleTime, DateTime statStartTime, DateTime statEndTime)
  913. {
  914. // 计算胎心=孕妇心率*系数
  915. var fetalHeartRate = SafeType.SafeInt(phrValue * commonPHR?.StatModeAvgFprCoefficient!);
  916. //fetalHeartRate = fetalHeartRate > 220 ? 220 : fetalHeartRate; // 胎心的最大值调整为220,超过都按该值220输出
  917. if (fetalHeartRate >= 220)
  918. {
  919. // 先使用最小系数计算
  920. var statMaxValueFprCoefficient = commonPHR?.StatMaxValueFprCoefficient!;
  921. var statMinValueFprCoefficient = commonPHR?.StatMinValueFprCoefficient!;
  922. var coefficient = statMaxValueFprCoefficient < statMinValueFprCoefficient ? statMaxValueFprCoefficient : statMinValueFprCoefficient;
  923. fetalHeartRate = SafeType.SafeInt(phrValue * coefficient);
  924. if (fetalHeartRate < 220)
  925. {
  926. _logger.LogWarning($"{heartRate.Serialno} 使用极值系数 {coefficient} ,建模数据可能出现异常,请检查");
  927. }
  928. else
  929. {
  930. fetalHeartRate = 220;
  931. _logger.LogWarning($"{heartRate.Serialno} 使用所有系数都不能放映实际,建模数据可能出现异常,请检查");
  932. }
  933. }
  934. var isAbnormal = fetalHeartRate > upperAlarmThreshold ? 1 : (fetalHeartRate < lowerAlarmThreshold ? 2 : 0);
  935. var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  936. if (phrFreqstatus == null) isAbnormal = 0;
  937. var statsusDesc = (phrFreqstatus == null) ? $"MSG ID: {heartRate.MessageId} 平常" : "高频";
  938. _logger.LogInformation($"{heartRate.Serialno} 在 {statsusDesc} 状态,生成胎心值:{fetalHeartRate},统计周期:{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}----{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")}");
  939. //if (!isFreq)
  940. //{
  941. // statStartTime = heartRate.LastUpdate;
  942. //
  943. //}
  944. // 保存到 数据服务 MySQL 数据库
  945. HisGpsFetalHeartRate gpsFetalHeartRate = new()
  946. {
  947. FetalHeartRateId = Guid.NewGuid().ToString("D"),
  948. PersonId = commonPHR!.PersonId,
  949. Serialno = heartRate.Serialno,
  950. HeartRate = fetalHeartRate,
  951. SampleTime = sampleTime.Length > 10 ? sampleTime.Substring(0, 10) : sampleTime,
  952. IsAbnormal = isAbnormal,
  953. StatStartTime = statStartTime,
  954. StatEndTime = statEndTime,//commonPHR.StatEndTime,
  955. CreateTime = DateTime.Now,
  956. Method = 1,
  957. IsDisplay = 1,
  958. DeviceKey = commonPHR!.DeviceKey
  959. };
  960. await _hisFetalHeartApiClient.AddAsync(gpsFetalHeartRate).ConfigureAwait(false);
  961. // 推送到api/v1/open/OpenIot/SetFetalHeartRateConfig
  962. await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal);
  963. var device = await _deviceCacheMgr.GetDeviceBySerialNoAsync(heartRate.Serialno).ConfigureAwait(false);
  964. var fhrMsgId = $"{heartRate.Serialno}-{sampleTime}-{Guid.NewGuid().ToString("D")[^3..]}";
  965. var fhrMsgTime = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(long.Parse(sampleTime.Length < 13 ? sampleTime.PadRight(13, '0') : sampleTime)).ToString("yyyy-MM-dd HH:mm:ss");
  966. // 胎心数据推送到第三方
  967. var topic = "topic.push.third";
  968. var fhrThridMsg = new
  969. {
  970. messageId = fhrMsgId,
  971. topic = topic,
  972. time = fhrMsgTime,
  973. data = new
  974. {
  975. imei = heartRate.Serialno,
  976. value = fetalHeartRate,
  977. isAbnormal,
  978. type = "fetalHeart"
  979. }
  980. };
  981. await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, 31, fhrThridMsg).ConfigureAwait(false);
  982. // 胎心数据推送到微信
  983. if (isAbnormal != 0)
  984. {
  985. topic = "topic.push.wx";
  986. var fhrMsg = new
  987. {
  988. messageId = fhrMsgId,
  989. topic = topic,
  990. time = fhrMsgTime,
  991. data = new
  992. {
  993. deviceId = device?.DeviceId,
  994. imei = heartRate.Serialno,
  995. alarmTypeId = 12,
  996. alarmDeviceName = heartRate.Serialno,
  997. alarmRemarks = JsonConvert.SerializeObject(new { fetalHeartValue = fetalHeartRate, isAbnormal = isAbnormal }),
  998. address = string.Empty,
  999. deviceKey = device?.DeviceId
  1000. }
  1001. };
  1002. await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, fhrMsg).ConfigureAwait(false);
  1003. }
  1004. }
  1005. }
  1006. }