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

PregnancyHeartRateResolver.cs 46KB

3ヶ月前
3ヶ月前
4ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
4ヶ月前
3ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
4ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
4ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
4ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
4ヶ月前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  1. using Etcdserverpb;
  2. using Google.Protobuf.WellKnownTypes;
  3. using HealthMonitor.Common;
  4. using HealthMonitor.Common.helper;
  5. using HealthMonitor.Model.Service.Mapper;
  6. using HealthMonitor.Service.Biz;
  7. using HealthMonitor.Service.Biz.db;
  8. using HealthMonitor.Service.Cache;
  9. using HealthMonitor.Service.Etcd;
  10. using HealthMonitor.Service.MessageQueue;
  11. using HealthMonitor.Service.Resolver.Interface;
  12. using HealthMonitor.Service.Sub;
  13. using HealthMonitor.Service.Sub.Topic.Model;
  14. using Microsoft.EntityFrameworkCore.Metadata;
  15. using Microsoft.Extensions.Logging;
  16. using Newtonsoft.Json;
  17. using Newtonsoft.Json.Linq;
  18. using SqlSugar;
  19. using System;
  20. using System.Collections.Generic;
  21. using System.Linq;
  22. using System.Net;
  23. using System.Text;
  24. using System.Threading.Tasks;
  25. using TelpoDataService.Util.Clients;
  26. using TelpoDataService.Util.Entities.GpsCard;
  27. using TelpoDataService.Util.Entities.GpsLocationHistory;
  28. using TelpoDataService.Util.Models;
  29. using TelpoDataService.Util.QueryObjects;
  30. namespace HealthMonitor.Service.Resolver
  31. {
  32. public class PregnancyHeartRateResolver : IResolver
  33. {
  34. private readonly ILogger<PregnancyHeartRateResolver> _logger;
  35. private readonly TDengineService _serviceTDengine;
  36. private readonly DeviceCacheManager _deviceCacheMgr;
  37. private readonly IotApiService _serviceIotApi;
  38. private readonly AsyncLocal<string> _messageId = new();
  39. private readonly AsyncLocal<HisGpsHeartRate> _msgData = new();
  40. private readonly HttpHelper _httpHelper = default!;
  41. private readonly EtcdService _serviceEtcd;
  42. private readonly GpsLocationHistoryAccessorClient<HisGpsFetalHeartRate> _hisFetalHeartApiClient;
  43. private readonly GpsLocationHistoryAccessorClient<HisGpsFetalMovement> _hisFetalMovementApiClient;
  44. private readonly FetalMovementNormalValueRangeCacheManager _mgrFetalMovementNormalValueRangeCache;
  45. private readonly MqProcessLogic _serviceMqProcess;
  46. private static int[] SCHEDULE_HOUR = new int[] { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24 };
  47. public PregnancyHeartRateResolver(ILogger<PregnancyHeartRateResolver> logger,
  48. HttpHelper httpHelper, EtcdService serviceEtcd, DeviceCacheManager deviceCacheMgr,
  49. MqProcessLogic serviceMqProcess,
  50. IotApiService iotApiService, TDengineService serviceDengine, FetalMovementNormalValueRangeCacheManager fetalMovementNormalValueRangeCacheMgr,
  51. GpsLocationHistoryAccessorClient<HisGpsFetalHeartRate> hisFetalHeartApiClient,
  52. GpsLocationHistoryAccessorClient<HisGpsFetalMovement> hisFetalMovementApiClient
  53. )
  54. {
  55. _logger = logger;
  56. _httpHelper = httpHelper;
  57. _serviceEtcd = serviceEtcd;
  58. _serviceTDengine = serviceDengine;
  59. _deviceCacheMgr = deviceCacheMgr;
  60. _serviceIotApi = iotApiService;
  61. _serviceMqProcess = serviceMqProcess;
  62. _hisFetalHeartApiClient = hisFetalHeartApiClient;
  63. _hisFetalMovementApiClient = hisFetalMovementApiClient;
  64. _mgrFetalMovementNormalValueRangeCache = fetalMovementNormalValueRangeCacheMgr;
  65. }
  66. public void SetResolveInfo(PackageMsgModel msg)
  67. {
  68. var topicHmPregnancyHeartRate = JsonConvert.DeserializeObject<TopicHmPregnancyHeartRate>(msg.DetailData.ToString()!);
  69. _messageId.Value = msg.MessageId;
  70. _msgData.Value = new HisGpsHeartRate()
  71. {
  72. HeartRateId = topicHmPregnancyHeartRate!.PregnancyHeartRateId,
  73. MessageId = topicHmPregnancyHeartRate!.MessageId,
  74. Serialno = topicHmPregnancyHeartRate!.Serialno,
  75. HeartRate = topicHmPregnancyHeartRate.PregnancyHeartRate,
  76. LastUpdate = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(SafeType.SafeInt64(topicHmPregnancyHeartRate.LastUpdate) / 1000000),
  77. CreateTime = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(SafeType.SafeInt64(topicHmPregnancyHeartRate.CreateTime) / 1000000),
  78. Method = topicHmPregnancyHeartRate!.Method,
  79. IsDisplay = topicHmPregnancyHeartRate!.IsDisplay ? 1 : 0
  80. };
  81. }
  82. public override string ToString()
  83. {
  84. return $"{nameof(PregnancyHeartRateResolver)}[{_messageId.Value}]";
  85. }
  86. public async Task ExecuteMessageAsync()
  87. {
  88. var messageId = _messageId.Value;
  89. var heartRate = _msgData.Value!;
  90. try
  91. {
  92. var watchConfig = await _deviceCacheMgr.GetGpsDeviceWatchConfigCacheObjectBySerialNoAsync(heartRate.Serialno, "0067");
  93. var isFetalHeartEnable = watchConfig != null && (int)watchConfig["enabled"]! == 1;
  94. if (isFetalHeartEnable)
  95. {
  96. //_logger.LogInformation($"{heartRate.Serialno} 计算胎心胎动启动");
  97. #region 定时下发触发器(定时建模)
  98. var key = $"health_monitor/schedule_push/pregnancy_heart_rate/imei/{heartRate.Serialno}";
  99. var schedule_push = await _serviceEtcd.GetValAsync(key).ConfigureAwait(false);
  100. if (string.IsNullOrWhiteSpace(schedule_push))
  101. {
  102. // 注册首次下推
  103. var interval = 0;
  104. // 获取当前时间
  105. DateTime now = DateTime.Now;
  106. var rand = new Random();
  107. var pushSec = rand.Next(59);
  108. int pushMin = int.TryParse(heartRate.Serialno.AsSpan(heartRate.Serialno.Length - 1), out pushMin) ? pushMin : 10;
  109. // 计算距离下一个$interval天后的8点的时间间隔
  110. DateTime nextRunTime = new DateTime(now.Year, now.Month, now.Day, 6, pushMin, pushSec).AddDays(interval);
  111. TimeSpan timeUntilNextRun = nextRunTime - now;
  112. if (timeUntilNextRun < TimeSpan.Zero)
  113. {
  114. timeUntilNextRun = timeUntilNextRun.Add(TimeSpan.FromDays(1));
  115. nextRunTime += TimeSpan.FromDays(1);
  116. }
  117. var ttl = (long)timeUntilNextRun.TotalSeconds;
  118. var data = new
  119. {
  120. imei = heartRate.Serialno,
  121. create_time = now.ToString("yyyy-MM-dd HH:mm:ss"),
  122. ttl,
  123. next_run_time = nextRunTime.ToString("yyyy-MM-dd HH:mm:ss")
  124. };
  125. var result = JsonConvert.SerializeObject(data);
  126. await _serviceEtcd.PutValAsync(key, result, ttl, false).ConfigureAwait(false);
  127. }
  128. #endregion
  129. //_logger.LogInformation($"{heartRate.Serialno} 触发定时建模");
  130. // 高频心率采样间隔 highFreqSampleInterval = highFreqSampleInterval+5,增加5秒兼容(最小highFreqSampleInterval=60)
  131. var highFreqSampleInterval = (int)watchConfig!["highFreqSampleInterval"]! >= 60 ? (int)watchConfig!["highFreqSampleInterval"]! + 5 : 60;
  132. // 触发高频监测的心率上限值
  133. var triggerHighFreqHigh = (int)watchConfig["triggerHighFreqHigh"]!;
  134. // 触发高频监测的心率下限值
  135. var triggerHighFreqLow = (int)watchConfig["triggerHighFreqLow"]!;
  136. // 暂时处理
  137. triggerHighFreqLow = triggerHighFreqLow > 60 && triggerHighFreqLow < 67 ? 60 : triggerHighFreqLow;
  138. //停止高频心率采样心率连续正常次数
  139. var stopHighFreqSampleCount = (int)watchConfig["stopHighFreqSampleCount"]!;
  140. // 高频心率采集时长 0 为持续采集,非零为高频心率的采集时长
  141. var highFreqSampleTimes = (int)watchConfig["highFreqSampleTimes"]!;
  142. // 告警上限阀值
  143. var upperAlarmThreshold = (int)watchConfig["upperAlarmThreshold"]!;
  144. // 告警下限阀值
  145. var lowerAlarmThreshold = (int)watchConfig["lowerAlarmThreshold"]!;
  146. // EDOC
  147. var edoc = DateTimeUtil.ToDateTime(watchConfig!["EDOC"]!.ToString());
  148. // interval (分钟) 固定15分钟
  149. var intervalFHR = 15;//int)watchConfig["interval"]!;
  150. var daysPhr = await _serviceTDengine.GetBySerialNoAsync<PregnancyHeartRateModel>(heartRate.Serialno, 7);
  151. var phr = daysPhr
  152. .Where(p => p.LastUpdate <= heartRate.LastUpdate)
  153. .ToList();
  154. if (phr.Count >= 30)
  155. {
  156. #region 定时计算胎动数据触发器两小时间隔开始
  157. var fetalMovementKey = $"health_monitor/schedule_push/cal_fetal_movement/imei/{heartRate.Serialno}";
  158. ///// 计算 0 点秒数
  159. var fetalMovementLastUpdate = (DateTime)heartRate.LastUpdate!;
  160. DateTime fmScheduleNow = DateTime.Now;
  161. // 小于两小时
  162. if (fmScheduleNow > fetalMovementLastUpdate && (fmScheduleNow - fetalMovementLastUpdate).TotalHours <= 2)
  163. {
  164. var rand = new Random();
  165. var pushSec = rand.Next(59);
  166. int pushMin = int.TryParse(heartRate.Serialno.AsSpan(heartRate.Serialno.Length - 1), out pushMin) ? pushMin : 10;
  167. var scheduleHourDiff = SCHEDULE_HOUR
  168. .Where(h => h > fetalMovementLastUpdate.Hour)
  169. .OrderBy(h => h - fetalMovementLastUpdate.Hour)
  170. .FirstOrDefault() - fetalMovementLastUpdate.Hour;
  171. var scheduleTime = fetalMovementLastUpdate.AddHours(scheduleHourDiff);
  172. DateTime nextRunTime = new(scheduleTime.Year, scheduleTime.Month, scheduleTime.Day, scheduleTime.Hour, pushMin, pushSec);
  173. TimeSpan timeUntilNextRun = nextRunTime - fmScheduleNow;
  174. var ttl = (long)timeUntilNextRun.TotalSeconds;
  175. await SetIntervalTriggerAsync(fetalMovementKey, heartRate.Serialno, ttl, heartRate);
  176. }
  177. #endregion
  178. #region 计算胎心数据(按心率时间LastUpdate)
  179. var commonPHR = await _serviceTDengine.GetLastAsync<PregnancyCommonHeartRateModel>(heartRate.Serialno);
  180. if (commonPHR == null)
  181. {
  182. // 处理孕妇业务,计算一般心率并下发
  183. commonPHR = await _serviceTDengine.InitPregnancyCommonHeartRateModeAsync(heartRate.Serialno, highFreqSampleInterval: highFreqSampleInterval);
  184. // 建模完成
  185. var flag = await _serviceIotApi.SetFetalConfig(heartRate.Serialno, 1, commonPHR!.MaxValue, commonPHR!.MinValue);
  186. _logger.LogInformation($"{heartRate.Serialno} 记录数量足够,建模完成");
  187. // 保存到TDengine数据库
  188. await _serviceTDengine.InsertAsync<PregnancyCommonHeartRateModel>("hm_pchr", commonPHR!);
  189. _logger.LogInformation($"保存TDengine完成");
  190. }
  191. // 获取最近的两个记录,并计算它们的 LastUpdate 时间差
  192. var firstTwoPhr = phr.OrderByDescending(i => i.LastUpdate).Take(2).Select(i => i.LastUpdate).ToList();
  193. var timeDiff = firstTwoPhr[0] - firstTwoPhr[1];
  194. // 如果需要,将时间差转换为秒
  195. var timeDiffInSeconds = timeDiff.TotalSeconds;
  196. // 高频统计结束时间
  197. var FreqStatsEnd = DateTime.Now;
  198. // 高频心率启动(在高频第二条才能判断)
  199. if (timeDiffInSeconds <= highFreqSampleInterval)
  200. {
  201. var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  202. if (phrFreqstatus == null)
  203. {
  204. /// 设置高频状态
  205. _logger.LogInformation($"{heartRate.Serialno} 进入高频心率启动状态 timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval}");
  206. // 设置高频状态
  207. _logger.LogInformation($"{heartRate.Serialno} phr.Count {phr.Count}");
  208. var freqFirstPhr = phr.OrderByDescending(i => i.LastUpdate)
  209. .Skip(1) //在高频第二条才能判断,所以要去除本条记录
  210. .First();
  211. await _deviceCacheMgr.SetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno, freqFirstPhr);
  212. _logger.LogInformation($"{heartRate.Serialno} 设置高频状态");
  213. phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  214. }
  215. /// phr PregnancyHeartRate 连续连续正常次数个值都是正常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig),
  216. /// 取连续正常次数正常值的平均值,推送到api/v1/open/OpenIot/SetFetalHeartRateConfig
  217. #region 检查高频状态是否连续12个心率值都是正常的
  218. // 获取最近连续正常次数个心率记录
  219. //_logger.LogInformation($"{heartRate.Serialno} 设置 stopHighFreqSampleCount {stopHighFreqSampleCount}");
  220. //var lastPhr = phr.OrderByDescending(i => i.LastUpdate).Take(stopHighFreqSampleCount).ToList();
  221. var lastPhr = phr.Where(i => i.LastUpdate >= phrFreqstatus!.LastUpdate)
  222. .OrderByDescending(i => i.LastUpdate).Take(stopHighFreqSampleCount).ToList();
  223. _logger.LogInformation($"{heartRate.Serialno} lastPhr.Count {lastPhr.Count},stopHighFreqSampleCount {stopHighFreqSampleCount}");
  224. _logger.LogInformation($"{heartRate.Serialno} count :{lastPhr.Count >= stopHighFreqSampleCount}");
  225. _logger.LogInformation($"{heartRate.Serialno} All {lastPhr.All(i => i.PregnancyHeartRate >= triggerHighFreqLow && i.PregnancyHeartRate <= triggerHighFreqHigh)}");
  226. // 检查是否连续12个值都是正常的
  227. if ((lastPhr.Count >= stopHighFreqSampleCount) &&
  228. lastPhr.All(i => i.PregnancyHeartRate >= triggerHighFreqLow && i.PregnancyHeartRate <= triggerHighFreqHigh)
  229. )
  230. {
  231. var avgPhr = lastPhr.Select(i => i.PregnancyHeartRate).Average();
  232. // 计算一般心率得到胎心系数
  233. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(DateTime.Now).ToString());
  234. // 高频数据不建模
  235. FreqStatsEnd = (DateTime)heartRate.LastUpdate!;
  236. _logger.LogInformation($"{heartRate.Serialno} 高频状态已经持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,连续 {stopHighFreqSampleCount} 次采样心率正常,将下发指令");
  237. var freqSaveAndPushFetalHeartRate = await _deviceCacheMgr.GetBizIntervalAsync(heartRate.Serialno, "SaveAndPushFetalHeartRate");
  238. // 高频不停,15分钟内只下发一条
  239. if (string.IsNullOrEmpty(freqSaveAndPushFetalHeartRate))
  240. {
  241. await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd);
  242. // 删除高频状态的首条记录
  243. await _deviceCacheMgr.DelPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  244. // 设置15分的SaveAndPushFetalHeartRate业务间隔
  245. await _deviceCacheMgr.SetBizIntervalAsync(heartRate.Serialno, "SaveAndPushFetalHeartRate");
  246. _logger.LogInformation($"{heartRate.Serialno} 连续 {stopHighFreqSampleCount} 次采样心率正常,结束高频心率状态, timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval},高频状态持续{((DateTime)heartRate.LastUpdate - phrFreqstatus!.LastUpdate).TotalSeconds} 秒");
  247. }
  248. else
  249. {
  250. _logger.LogWarning($"{heartRate.Serialno} 连续 {stopHighFreqSampleCount} 次采样心率正常,设备端应该结束高频状态,但设备端没有结束高频,平台高频15分钟内已经下发过指令");
  251. }
  252. }
  253. else
  254. {
  255. _logger.LogInformation($"{heartRate.Serialno} 处于高频状态...");
  256. }
  257. #endregion
  258. }
  259. // 高频心率结束或常规心率
  260. else
  261. {
  262. var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  263. // 高频心率结束
  264. if (phrFreqstatus != null)
  265. {
  266. /// 在highFreqSampleTimes=0一直异常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig),
  267. /// 取所有值的平均值,推送胎心数据到api/v1/open/OpenIot/SetFetalHeartRateConfig
  268. if (highFreqSampleTimes == 0)
  269. {
  270. var avgPhr = phr.OrderByDescending(i => i.LastUpdate)
  271. .Where(i => i.LastUpdate >= phrFreqstatus?.LastUpdate)
  272. .Skip(1) // 去除首条
  273. .Where(i => i.PregnancyHeartRate < triggerHighFreqLow || i.PregnancyHeartRate > triggerHighFreqHigh)
  274. .Select(i => i.PregnancyHeartRate).Average();
  275. // 推送胎心数据到 api/v1/open/OpenIot/SetFetalHeartRateConfig
  276. // 计算一般心率得到胎心系数
  277. // 高频数据不建模
  278. FreqStatsEnd = firstTwoPhr[1];
  279. _logger.LogInformation($"{heartRate.Serialno} 高频状态已经持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,highFreqSampleTimes={highFreqSampleTimes}秒,即将结束高频状态,将下发指令");
  280. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString());
  281. await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd);
  282. }
  283. /// 在highFreqSampleTimes>0一直异常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig),
  284. /// 取所有值的平均值,推送胎心数据到api/v1/open/OpenIot/SetFetalHeartRateConfig
  285. if (highFreqSampleTimes > 0 && heartRate.LastUpdate >= (phrFreqstatus?.LastUpdate.AddSeconds(highFreqSampleTimes)))
  286. {
  287. // 获取高频心率数据个数
  288. var filterPhr = phr
  289. .Where(i => i.LastUpdate >= phrFreqstatus?.LastUpdate)
  290. .Skip(1)
  291. .ToList();
  292. _logger.LogInformation($"{heartRate.Serialno} 高频周期 {phrFreqstatus?.LastUpdate.ToString("yyyy-MM-dd HH:mm:ss")}--{firstTwoPhr[1].ToString("yyyy-MM-dd HH:mm:ss")} 产生高频心率数量 {filterPhr.Count} 条");
  293. // 高频心率数据大于stopHighFreqSampleCount/12个才计算胎心数据
  294. //
  295. if (filterPhr.Count > stopHighFreqSampleCount)
  296. {
  297. FreqStatsEnd = firstTwoPhr[1];
  298. var avgPhr = filterPhr
  299. .OrderByDescending(i => i.LastUpdate)
  300. .Take(stopHighFreqSampleCount) // 计算最后12条
  301. .Select(i => i.PregnancyHeartRate).Average();
  302. _logger.LogInformation($"{heartRate.Serialno} 高频状态已经持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,超过约定的 {highFreqSampleTimes} 秒,即将结束高频状态,将下发指令");
  303. //计算高频
  304. await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd);
  305. }
  306. else
  307. {
  308. _logger.LogInformation($"{heartRate.Serialno} 高频心率的数据不大于{stopHighFreqSampleCount}条,不进行高频数据的胎心计算");
  309. }
  310. // 删除高频状态的首条记录
  311. await _deviceCacheMgr.DelPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  312. // 计算本次常规心率的胎心数据(高频结束后,实时处理)
  313. await CalculateNormalFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, intervalFHR, commonPHR);
  314. }
  315. ///不满足持续10分钟highFreqSampleTimes或出现时间倒叙
  316. ///
  317. else
  318. {
  319. // 高频结束后与常规的心率时间倒叙
  320. if ((firstTwoPhr[1] - phrFreqstatus!.LastUpdate).TotalSeconds < 0)
  321. {
  322. _logger.LogInformation($"{heartRate.Serialno} 高频结束出现时间倒叙,计算当条心率创建之前的高频心率");
  323. #region 计算当条心率创建之前的高频心率
  324. // 取得高频之后的所有数据
  325. var phrFlashBack = daysPhr.Where(p => p.LastUpdate >= phrFreqstatus!.LastUpdate)
  326. .OrderByDescending(i => i.LastUpdate);
  327. // 取得高频数据
  328. var freqCollection = phrFlashBack.ToList();
  329. //var freqCollection = new List<PregnancyHeartRateModel>();
  330. //PregnancyHeartRateModel? previousItem = null;
  331. //foreach (var item in phrFlashBack)
  332. //{
  333. // if (previousItem != null)
  334. // {
  335. // var timeNextDiff = (previousItem!.LastUpdate - item.LastUpdate).TotalSeconds;
  336. // if (timeNextDiff <= highFreqSampleInterval)
  337. // {
  338. // freqCollection.Add(item);
  339. // }
  340. // }
  341. // previousItem = item;
  342. //}
  343. _logger.LogInformation($"{heartRate.Serialno} 高频数据个数{freqCollection.Count},触发高频开始时间{phrFreqstatus!.LastUpdate}");
  344. if (freqCollection.Count > stopHighFreqSampleCount)
  345. {
  346. // 计算高频产生的胎心
  347. var avgPhr = freqCollection
  348. .OrderByDescending(i => i.LastUpdate)
  349. .Take(stopHighFreqSampleCount) // 计算最后12条
  350. .Select(i => i.PregnancyHeartRate).Average();
  351. await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), freqCollection.Last().LastUpdate, freqCollection.First().LastUpdate);
  352. }
  353. else
  354. {
  355. _logger.LogInformation($"{heartRate.Serialno} 时间倒叙触发计算高频心率的数据不足{stopHighFreqSampleCount}条,不进行胎心计算");
  356. }
  357. #endregion
  358. }
  359. else
  360. {
  361. _logger.LogInformation($"{heartRate.Serialno} 高频持续时间不足{highFreqSampleTimes},只持续{(firstTwoPhr[1] - phrFreqstatus!.LastUpdate).TotalSeconds} 秒");
  362. }
  363. // 删除高频状态的首条记录
  364. await _deviceCacheMgr.DelPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  365. // 计算本次常规心率的胎心数据(高频结束后,实时处理)
  366. await CalculateNormalFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, intervalFHR, commonPHR);
  367. }
  368. // 删除高频状态的首条记录
  369. await _deviceCacheMgr.DelPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  370. _logger.LogInformation($"{heartRate.Serialno} 超时结束高频心率状态 timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval},高频状态持续{(firstTwoPhr[1] - phrFreqstatus!.LastUpdate).TotalSeconds} 秒");
  371. // 计算本次常规心率的胎心数据(高频结束后,实时处理)
  372. //await CalculateNormalFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, intervalFHR, commonPHR);
  373. //// 使用延后计算
  374. //var fhrScheduleKey = $"health_monitor/schedule_push/cal_fetal_heart_rate/imei/{heartRate.Serialno}";
  375. //var fhrScheduleTTL = 60;
  376. //await SetIntervalTriggerAsync(fhrScheduleKey, heartRate.Serialno, fhrScheduleTTL, heartRate);
  377. }
  378. // 常规心率(本次心率可能是高频心率的首条,所以要使用延后计算胎心率)
  379. else
  380. {
  381. // 计算本次常规心率的胎心数据
  382. //await CalculateNormalFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, intervalFHR, commonPHR);
  383. // 本次心率可能是高频心率的首条,所以要使用本次常规心率延后计算胎心率
  384. //var fhrScheduleKey = $"health_monitor/schedule_push/cal_fetal_heart_rate/imei/{heartRate.Serialno}";
  385. //var fhrScheduleTTL = 30;
  386. //await SetIntervalTriggerAsync(fhrScheduleKey, heartRate.Serialno, fhrScheduleTTL, heartRate);
  387. //_logger.LogInformation($"{heartRate.Serialno} 延时50秒,判断当前数据是否为高频首条");
  388. // 本次心率可能是高频心率的首条,所以要使用本次常规心率延后计算胎心率 查询30秒后是否有高频缓存
  389. Thread thread = new(async () =>
  390. {
  391. try
  392. {
  393. #region 休眠2秒
  394. var startTime = DateTime.Now;
  395. //var highFreqSampleInterval2 = (int)watchConfig!["highFreqSampleInterval"]!+5;
  396. var highFreqSampleInterval2 = highFreqSampleInterval;
  397. var during = TimeSpan.FromSeconds(highFreqSampleInterval2);
  398. while (true)
  399. {
  400. if (DateTime.Now - startTime > during)
  401. {
  402. break;
  403. }
  404. await Task.Delay(TimeSpan.FromSeconds(1));
  405. }
  406. #endregion
  407. var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  408. _logger.LogInformation($"phrFreqstatus==null:{phrFreqstatus == null}");
  409. _logger.LogInformation($"phrFreqstatus.LastUpdate < heartRate.LastUpdate:{phrFreqstatus?.LastUpdate < heartRate.LastUpdate}");
  410. if (phrFreqstatus == null || phrFreqstatus.LastUpdate < heartRate.LastUpdate)
  411. {
  412. await CalculateNormalFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, intervalFHR, commonPHR);
  413. _logger.LogInformation($"{heartRate.Serialno} 计算常规心率");
  414. }
  415. }
  416. catch (Exception ex)
  417. {
  418. _logger.LogError($"处理延时计算异常:{ex.Message}, {ex.StackTrace}");
  419. }
  420. });
  421. thread.Start();
  422. }
  423. }
  424. #endregion
  425. }
  426. else
  427. {
  428. _logger.LogInformation($"{heartRate.Serialno} 记录不足30条,建模中");
  429. }
  430. }
  431. }
  432. catch (Exception ex)
  433. {
  434. _logger.LogError($"{heartRate.Serialno} 处理孕妇心率数据异常 \n{ex.Message}\n{ex.StackTrace}");
  435. }
  436. }
  437. /// <summary>
  438. /// 常规心率计算胎心数据
  439. /// </summary>
  440. /// <param name="heartRate"></param>
  441. /// <param name="upperAlarmThreshold"></param>
  442. /// <param name="lowerAlarmThreshold"></param>
  443. /// <param name="intervalFHR"></param>
  444. /// <param name="commonPHR"></param>
  445. /// <returns></returns>
  446. private async Task CalculateNormalFetalHeartRateAsync(HisGpsHeartRate heartRate, int upperAlarmThreshold, int lowerAlarmThreshold, int intervalFHR, PregnancyCommonHeartRateModel? commonPHR)
  447. {
  448. // 上15分钟的数据
  449. // 获取当前时间
  450. DateTime nowInterval = (DateTime)heartRate.LastUpdate!;
  451. if (nowInterval.Second > 0)
  452. {
  453. nowInterval = nowInterval.AddMinutes(1);
  454. }
  455. // 计算last_update到上一间隔的分钟数
  456. int minutesToSubtract = nowInterval.Minute % intervalFHR;
  457. // 计算上一间隔的时间
  458. DateTime previousInterval = nowInterval.AddMinutes(-minutesToSubtract).AddSeconds(-nowInterval.Second).AddMilliseconds(-nowInterval.Millisecond);
  459. // 使用 last_update 上一刻
  460. var sampleTimeFHR = DateTimeUtil.ConvertToTimeStamp(previousInterval).ToString();
  461. // 计算last_update到下一间隔的分钟数
  462. int minutesToAdd = intervalFHR - (nowInterval.Minute % intervalFHR);
  463. if (minutesToAdd == intervalFHR)
  464. {
  465. minutesToAdd = 0; // 如果已经是间隔,则不需要增加分钟
  466. }
  467. // 计算下一间隔的时间
  468. DateTime nextInterval = nowInterval.AddMinutes(minutesToAdd)
  469. .AddSeconds(-nowInterval.Second)
  470. .AddMilliseconds(-nowInterval.Millisecond);
  471. var daysPhr = await _serviceTDengine.GetBySerialNoAsync<PregnancyHeartRateModel>(heartRate.Serialno, 7);
  472. var normalPhrStatStartTime = nextInterval.AddMinutes(-intervalFHR);
  473. var normalPhrStatEndTime = nextInterval;
  474. _logger.LogInformation($"{heartRate.Serialno} 计算胎心数据, 周期:{normalPhrStatStartTime}-{normalPhrStatEndTime} ");
  475. var filteredPhr = daysPhr
  476. // 使用 last_update 下一刻
  477. .Where(i => i.LastUpdate <= normalPhrStatEndTime && i.LastUpdate >= normalPhrStatStartTime)
  478. .ToList();
  479. if (filteredPhr.Count == 0)
  480. {
  481. _logger.LogWarning($"{heartRate.Serialno} 周期:{normalPhrStatStartTime}-{normalPhrStatEndTime} 孕妇心率数据不足,{filteredPhr.Count}条记录");
  482. return;
  483. }
  484. var phrValue = filteredPhr.Count == 1
  485. ? filteredPhr.First().PregnancyHeartRate
  486. : filteredPhr.Average(i => i.PregnancyHeartRate);
  487. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR!, upperAlarmThreshold, lowerAlarmThreshold, phrValue, sampleTimeFHR);
  488. await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR!, upperAlarmThreshold, lowerAlarmThreshold, phrValue, sampleTimeFHR, normalPhrStatStartTime, normalPhrStatEndTime);
  489. }
  490. /// <summary>
  491. ///
  492. /// </summary>
  493. /// <param name="heartRate"></param>
  494. /// <param name="commonPHR"></param>
  495. /// <param name="upperAlarmThreshold"></param>
  496. /// <param name="lowerAlarmThreshold"></param>
  497. /// <param name="phrValue"></param>
  498. /// <param name="sampleTime"></param>
  499. /// <param name="statStartTime"></param>
  500. /// <param name="statEndTime"></param>
  501. /// <returns></returns>
  502. private async Task SaveAndPushFetalHeartRateAsync(HisGpsHeartRate heartRate, PregnancyCommonHeartRateModel commonPHR, int upperAlarmThreshold, int lowerAlarmThreshold, double phrValue, string sampleTime, DateTime statStartTime, DateTime statEndTime)
  503. {
  504. // 计算胎心=孕妇心率*系数
  505. /**
  506. var fetalHeartRate = SafeType.SafeInt(phrValue * commonPHR?.StatModeAvgFprCoefficient!);
  507. fetalHeartRate = fetalHeartRate > 220 ? 220 : fetalHeartRate; // 胎心的最大值调整为220,超过都按该值220输出
  508. if (fetalHeartRate >= 220)
  509. {
  510. // 先使用最小系数计算
  511. var statMaxValueFprCoefficient = commonPHR?.StatMaxValueFprCoefficient!;
  512. var statMinValueFprCoefficient = commonPHR?.StatMinValueFprCoefficient!;
  513. var coefficient = statMaxValueFprCoefficient < statMinValueFprCoefficient ? statMaxValueFprCoefficient : statMinValueFprCoefficient;
  514. fetalHeartRate = SafeType.SafeInt(phrValue * coefficient);
  515. if (fetalHeartRate < 220)
  516. {
  517. _logger.LogWarning($"{heartRate.Serialno} 使用极值系数 {coefficient} ,建模数据可能出现异常,请检查");
  518. }
  519. else
  520. {
  521. fetalHeartRate = 220;
  522. _logger.LogWarning($"{heartRate.Serialno} 使用所有系数都不能放映实际,建模数据可能出现异常,请检查");
  523. }
  524. }
  525. */
  526. #region 胎心系数使用基于心率与中位数对比
  527. var coefficient = 0f;
  528. // 孕妇心率少于中位数,取StatMinValueFprCoefficient
  529. if (heartRate.HeartRate < commonPHR!.Mode)
  530. {
  531. coefficient = commonPHR.StatMinValueFprCoefficient!;
  532. _logger.LogInformation($"{heartRate.Serialno} 孕妇心率少于中位数,使用最小值系数 {coefficient}");
  533. }
  534. // 孕妇心率大于中位数,取StatMaxValueFprCoefficient与StatModeAvgFprCoefficient中少的那个
  535. else if (heartRate.HeartRate > commonPHR.Mode)
  536. {
  537. if (commonPHR.StatModeAvgFprCoefficient > commonPHR.StatMaxValueFprCoefficient)
  538. {
  539. coefficient = commonPHR.StatMaxValueFprCoefficient!;
  540. _logger.LogInformation($"{heartRate.Serialno} 孕妇心率大于中位数,使用最大值系数 {coefficient}");
  541. }
  542. else
  543. {
  544. coefficient = commonPHR.StatModeAvgFprCoefficient!;
  545. _logger.LogInformation($"{heartRate.Serialno} 孕妇心率大于中位数,使用均值系数 {coefficient}");
  546. }
  547. }
  548. else
  549. {
  550. coefficient = commonPHR.StatModeAvgFprCoefficient;
  551. _logger.LogInformation($"{heartRate.Serialno} 孕妇心率等于中位数,使用均值系数 {coefficient}");
  552. }
  553. #endregion
  554. var fetalHeartRate = SafeType.SafeInt(phrValue * coefficient);
  555. var isAbnormal = fetalHeartRate > upperAlarmThreshold ? 1 : (fetalHeartRate < lowerAlarmThreshold ? 2 : 0);
  556. var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  557. if (phrFreqstatus == null) isAbnormal = 0;
  558. var statsusDesc = (phrFreqstatus == null) ? "常规" : "高频";
  559. _logger.LogInformation($"{heartRate.Serialno} 在 {statsusDesc} 状态,生成胎心值:{fetalHeartRate},统计周期:{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}----{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")}");
  560. //if (!isFreq)
  561. //{
  562. // statStartTime = heartRate.LastUpdate;
  563. //
  564. //}
  565. // 保存到 数据服务 MySQL 数据库
  566. HisGpsFetalHeartRate gpsFetalHeartRate = new()
  567. {
  568. FetalHeartRateId = Guid.NewGuid().ToString("D"),
  569. PersonId = commonPHR!.PersonId,
  570. Serialno = heartRate.Serialno,
  571. HeartRate = fetalHeartRate,
  572. SampleTime = sampleTime.Length > 10 ? sampleTime.Substring(0, 10) : sampleTime,
  573. IsAbnormal = isAbnormal,
  574. StatStartTime = statStartTime,
  575. StatEndTime = statEndTime,//commonPHR.StatEndTime,
  576. CreateTime = DateTime.Now,
  577. Method = 1,
  578. IsDisplay = 1,
  579. DeviceKey = commonPHR!.DeviceKey
  580. };
  581. await _hisFetalHeartApiClient.AddAsync(gpsFetalHeartRate).ConfigureAwait(false);
  582. // 推送到api/v1/open/OpenIot/SetFetalHeartRateConfig
  583. // 推送最后一条常规心率计算的胎心数据到iot设备
  584. #region 推送最后一条常规心率计算的胎心数据到iot设备
  585. // 高频(<=12)-常规
  586. var lastPhr = await _serviceTDengine.GetLastAsync<PregnancyHeartRateModel>(heartRate.Serialno);
  587. if (lastPhr.MessageId == heartRate.MessageId && phrFreqstatus == null)
  588. {
  589. await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal);
  590. _logger.LogInformation($"{heartRate.Serialno} 推送最后一条常规心率计算的胎心数据到iot设备,高频(<=12)-常规");
  591. }
  592. // 高频(13)-常规-高频(13)
  593. if (phrFreqstatus != null)
  594. {
  595. var phr = await _serviceTDengine.GetBySerialNoAsync<PregnancyHeartRateModel>(heartRate.Serialno, 1);
  596. phr = phr.OrderByDescending(i => i.LastUpdate).ToList();
  597. // 获取高频数据
  598. var freqCollection = new List<PregnancyHeartRateModel>();
  599. PregnancyHeartRateModel? previousItem = null;
  600. foreach (var item in phr)
  601. {
  602. if (previousItem != null)
  603. {
  604. var timeNextDiff = (previousItem!.LastUpdate - item.LastUpdate).TotalSeconds;
  605. if (timeNextDiff <= 60)
  606. {
  607. freqCollection.Add(item);
  608. }
  609. }
  610. // 高频最后一条
  611. if (lastPhr.MessageId == item.MessageId)
  612. {
  613. freqCollection.Add(item);
  614. }
  615. previousItem = item;
  616. }
  617. //去除高频
  618. foreach (var item in freqCollection)
  619. {
  620. phr.Remove(item);
  621. }
  622. lastPhr = phr.FirstOrDefault();
  623. if (lastPhr?.MessageId == heartRate.MessageId)
  624. {
  625. await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal);
  626. _logger.LogInformation($"{heartRate.Serialno} 推送最后一条常规心率计算的胎心数据到iot设备,高频(13)-常规-高频(13)");
  627. }
  628. }
  629. #endregion
  630. var device = await _deviceCacheMgr.GetDeviceBySerialNoAsync(heartRate.Serialno).ConfigureAwait(false);
  631. var fhrMsgId = $"{heartRate.Serialno}-{sampleTime}-{Guid.NewGuid().ToString("D")[^3..]}";
  632. var fhrMsgTime = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(long.Parse(sampleTime.Length < 13 ? sampleTime.PadRight(13, '0') : sampleTime)).ToString("yyyy-MM-dd HH:mm:ss");
  633. // 胎心数据推送到第三方
  634. var topic = "topic.push.third";
  635. var fhrThridMsg = new
  636. {
  637. messageId = fhrMsgId,
  638. topic = topic,
  639. time = fhrMsgTime,
  640. data = new
  641. {
  642. imei = heartRate.Serialno,
  643. value = fetalHeartRate,
  644. isAbnormal,
  645. type = "fetalHeart"
  646. }
  647. };
  648. await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, 31, fhrThridMsg).ConfigureAwait(false);
  649. // 胎心数据推送到微信
  650. if (isAbnormal != 0)
  651. {
  652. topic = "topic.push.wx";
  653. var fhrMsg = new
  654. {
  655. messageId = fhrMsgId,
  656. topic = topic,
  657. time = fhrMsgTime,
  658. data = new
  659. {
  660. deviceId = device?.DeviceId,
  661. imei = heartRate.Serialno,
  662. alarmTypeId = 12,
  663. alarmDeviceName = heartRate.Serialno,
  664. alarmRemarks = JsonConvert.SerializeObject(new { fetalHeartValue = fetalHeartRate, isAbnormal = isAbnormal }),
  665. address = string.Empty,
  666. deviceKey = device?.DeviceId
  667. }
  668. };
  669. await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, fhrMsg).ConfigureAwait(false);
  670. }
  671. }
  672. private async Task SetIntervalTriggerAsync(string key, string imei, long interval, HisGpsHeartRate heartRate)
  673. {
  674. // var key = $"health_monitor/schedule_push/{type}/imei/{imei}";
  675. var schedulePush = await _serviceEtcd.GetValAsync(key).ConfigureAwait(false);
  676. if (string.IsNullOrWhiteSpace(schedulePush))
  677. {
  678. var now = DateTime.Now;
  679. var timeNextRun = now.Add(TimeSpan.FromSeconds(interval));
  680. var data = new
  681. {
  682. imei,
  683. create_time = now.ToString("yyyy-MM-dd HH:mm:ss"),
  684. ttl = interval,
  685. next_run_time = timeNextRun.ToString("yyyy-MM-dd HH:mm:ss"),
  686. trigger = heartRate,
  687. };
  688. var result = JsonConvert.SerializeObject(data);
  689. await _serviceEtcd.PutValAsync(key, result, interval, false).ConfigureAwait(false);
  690. }
  691. }
  692. }
  693. }