Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

672 lines
39KB

  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. var stopHighFreqSampleCount = (int)watchConfig["stopHighFreqSampleCount"]!;
  138. // 高频心率采集时长 0 为持续采集,非零为高频心率的采集时长
  139. var highFreqSampleTimes = (int)watchConfig["highFreqSampleTimes"]!;
  140. // 告警上限阀值
  141. var upperAlarmThreshold = (int)watchConfig["upperAlarmThreshold"]!;
  142. // 告警下限阀值
  143. var lowerAlarmThreshold = (int)watchConfig["lowerAlarmThreshold"]!;
  144. // EDOC
  145. var edoc = DateTimeUtil.ToDateTime(watchConfig!["EDOC"]!.ToString());
  146. // interval (分钟) 固定15分钟
  147. //var intervalFHR = 15;//int)watchConfig["interval"]!;
  148. var daysPhr = await _serviceTDengine.GetBySerialNoAsync<PregnancyHeartRateModel>(heartRate.Serialno, 7);
  149. var phr = daysPhr
  150. .Where(p => p.LastUpdate <= heartRate.LastUpdate)
  151. .ToList();
  152. if (phr.Count >= 30)
  153. {
  154. #region 定时计算胎动数据触发器两小时间隔开始
  155. var fetalMovementKey = $"health_monitor/schedule_push/cal_fetal_movement/imei/{heartRate.Serialno}";
  156. ///// 计算 0 点秒数
  157. var fetalMovementLastUpdate = (DateTime)heartRate.LastUpdate!;
  158. DateTime fmScheduleNow = DateTime.Now;
  159. // 小于两小时
  160. if (fmScheduleNow > fetalMovementLastUpdate && (fmScheduleNow - fetalMovementLastUpdate).TotalHours <= 2)
  161. {
  162. var rand = new Random();
  163. var pushSec = rand.Next(59);
  164. int pushMin = int.TryParse(heartRate.Serialno.AsSpan(heartRate.Serialno.Length - 1), out pushMin) ? pushMin : 10;
  165. var scheduleHourDiff = SCHEDULE_HOUR
  166. .Where(h => h > fetalMovementLastUpdate.Hour)
  167. .OrderBy(h => h - fetalMovementLastUpdate.Hour)
  168. .FirstOrDefault() - fetalMovementLastUpdate.Hour;
  169. var scheduleTime = fetalMovementLastUpdate.AddHours(scheduleHourDiff);
  170. DateTime nextRunTime = new(scheduleTime.Year, scheduleTime.Month, scheduleTime.Day, scheduleTime.Hour, pushMin, pushSec);
  171. TimeSpan timeUntilNextRun = nextRunTime - fmScheduleNow;
  172. var ttl = (long)timeUntilNextRun.TotalSeconds;
  173. await SetIntervalTriggerAsync(fetalMovementKey, heartRate.Serialno, ttl,heartRate);
  174. }
  175. #endregion
  176. #region 计算胎心数据(按心率时间LastUpdate)
  177. var commonPHR = await _serviceTDengine.GetLastAsync<PregnancyCommonHeartRateModel>(heartRate.Serialno);
  178. if (commonPHR == null)
  179. {
  180. // 处理孕妇业务,计算一般心率并下发
  181. commonPHR = await _serviceTDengine.InitPregnancyCommonHeartRateModeAsync(heartRate.Serialno, highFreqSampleInterval: highFreqSampleInterval);
  182. // 建模完成
  183. var flag = await _serviceIotApi.SetFetalConfig(heartRate.Serialno, 1, commonPHR!.MaxValue, commonPHR!.MinValue);
  184. _logger.LogInformation($"{heartRate.Serialno} 记录数量足够,建模完成");
  185. // 保存到TDengine数据库
  186. await _serviceTDengine.InsertAsync<PregnancyCommonHeartRateModel>("hm_pchr", commonPHR!);
  187. _logger.LogInformation($"保存TDengine完成");
  188. }
  189. // 获取最近的两个记录,并计算它们的 LastUpdate 时间差
  190. var firstTwoPhr = phr.OrderByDescending(i => i.LastUpdate).Take(2).Select(i => i.LastUpdate).ToList();
  191. var timeDiff = firstTwoPhr[0] - firstTwoPhr[1];
  192. // 如果需要,将时间差转换为秒
  193. var timeDiffInSeconds = timeDiff.TotalSeconds;
  194. // 高频统计结束时间
  195. var FreqStatsEnd = DateTime.Now;
  196. // 高频心率启动
  197. if (timeDiffInSeconds <= highFreqSampleInterval)
  198. {
  199. var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  200. if (phrFreqstatus == null)
  201. {
  202. /// 设置高频状态
  203. _logger.LogInformation($"{heartRate.Serialno} 进入高频心率启动状态 timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval}");
  204. // 设置高频状态
  205. _logger.LogInformation($"{heartRate.Serialno} phr.Count {phr.Count}");
  206. var freqFirstPhr = phr.OrderByDescending(i => i.LastUpdate).First();
  207. await _deviceCacheMgr.SetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno, freqFirstPhr);
  208. _logger.LogInformation($"{heartRate.Serialno} 设置高频状态");
  209. phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  210. }
  211. /// phr PregnancyHeartRate 连续连续正常次数个值都是正常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig),
  212. /// 取连续正常次数正常值的平均值,推送到api/v1/open/OpenIot/SetFetalHeartRateConfig
  213. #region 检查高频状态是否连续12个心率值都是正常的
  214. // 获取最近连续正常次数个心率记录
  215. //_logger.LogInformation($"{heartRate.Serialno} 设置 stopHighFreqSampleCount {stopHighFreqSampleCount}");
  216. //var lastPhr = phr.OrderByDescending(i => i.LastUpdate).Take(stopHighFreqSampleCount).ToList();
  217. var lastPhr = phr.Where(i=>i.LastUpdate>= phrFreqstatus!.LastUpdate)
  218. .OrderByDescending(i => i.LastUpdate).Take(stopHighFreqSampleCount).ToList();
  219. _logger.LogInformation($"{heartRate.Serialno} lastPhr.Count {lastPhr.Count},stopHighFreqSampleCount {stopHighFreqSampleCount}");
  220. _logger.LogInformation($"{heartRate.Serialno} count :{lastPhr.Count >= stopHighFreqSampleCount}");
  221. _logger.LogInformation($"{heartRate.Serialno} All {lastPhr.All(i => i.PregnancyHeartRate >= triggerHighFreqLow && i.PregnancyHeartRate <= triggerHighFreqHigh)}");
  222. // 检查是否连续12个值都是正常的
  223. if ((lastPhr.Count >= stopHighFreqSampleCount) &&
  224. lastPhr.All(i => i.PregnancyHeartRate >= triggerHighFreqLow && i.PregnancyHeartRate <= triggerHighFreqHigh)
  225. )
  226. {
  227. var avgPhr = lastPhr.Select(i => i.PregnancyHeartRate).Average();
  228. // 计算一般心率得到胎心系数
  229. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(DateTime.Now).ToString());
  230. // 高频数据不建模
  231. FreqStatsEnd = (DateTime)heartRate.LastUpdate!;
  232. _logger.LogInformation($"{heartRate.Serialno} 高频状态已经持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,连续 {stopHighFreqSampleCount} 次采样心率正常,将下发指令");
  233. var freqSaveAndPushFetalHeartRate = await _deviceCacheMgr.GetBizIntervalAsync(heartRate.Serialno, "SaveAndPushFetalHeartRate");
  234. // 高频不停,15分钟内只下发一条
  235. if (string.IsNullOrEmpty(freqSaveAndPushFetalHeartRate))
  236. {
  237. await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd);
  238. // 删除高频状态的首条记录
  239. await _deviceCacheMgr.DelPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  240. // 设置15分的SaveAndPushFetalHeartRate业务间隔
  241. await _deviceCacheMgr.SetBizIntervalAsync(heartRate.Serialno, "SaveAndPushFetalHeartRate");
  242. _logger.LogInformation($"{heartRate.Serialno} 连续 {stopHighFreqSampleCount} 次采样心率正常,结束高频心率状态, timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval},高频状态持续{((DateTime)heartRate.LastUpdate - phrFreqstatus!.LastUpdate).TotalSeconds} 秒");
  243. }
  244. else
  245. {
  246. _logger.LogWarning($"{heartRate.Serialno} 连续 {stopHighFreqSampleCount} 次采样心率正常,设备端应该结束高频状态,但设备端没有结束高频,平台高频15分钟内已经下发过指令");
  247. }
  248. }
  249. else
  250. {
  251. _logger.LogInformation($"{heartRate.Serialno} 处于高频状态...");
  252. }
  253. #endregion
  254. }
  255. // 高频心率结束或常规心率
  256. else
  257. {
  258. var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  259. // 高频心率结束
  260. if (phrFreqstatus != null)
  261. {
  262. /// 在highFreqSampleTimes=0一直异常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig),
  263. /// 取所有值的平均值,推送胎心数据到api/v1/open/OpenIot/SetFetalHeartRateConfig
  264. if (highFreqSampleTimes == 0)
  265. {
  266. var avgPhr = phr.OrderByDescending(i => i.LastUpdate)
  267. .Where(i => i.LastUpdate >= phrFreqstatus?.LastUpdate)
  268. .Skip(1) // 去除首条
  269. .Where(i => i.PregnancyHeartRate < triggerHighFreqLow || i.PregnancyHeartRate > triggerHighFreqHigh)
  270. .Select(i => i.PregnancyHeartRate).Average();
  271. // 推送胎心数据到 api/v1/open/OpenIot/SetFetalHeartRateConfig
  272. // 计算一般心率得到胎心系数
  273. // 高频数据不建模
  274. FreqStatsEnd = firstTwoPhr[1];
  275. _logger.LogInformation($"{heartRate.Serialno} 高频状态已经持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,highFreqSampleTimes={highFreqSampleTimes}秒,即将结束高频状态,将下发指令");
  276. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString());
  277. await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd);
  278. }
  279. /// 在highFreqSampleTimes>0一直异常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig),
  280. /// 取所有值的平均值,推送胎心数据到api/v1/open/OpenIot/SetFetalHeartRateConfig
  281. if (highFreqSampleTimes > 0 && heartRate.LastUpdate >= (phrFreqstatus?.LastUpdate.AddSeconds(highFreqSampleTimes)))
  282. {
  283. // 获取高频心率数据个数
  284. var filterPhr = phr
  285. .Where(i => i.LastUpdate >= phrFreqstatus?.LastUpdate)
  286. .Skip(1)
  287. .ToList();
  288. //var freqCollection = new List<PregnancyHeartRateModel>();
  289. //PregnancyHeartRateModel? previousItem = null;
  290. //foreach (var item in filterPhr)
  291. //{
  292. // if (previousItem != null)
  293. // {
  294. // var timeNextDiff = (previousItem!.LastUpdate - item.LastUpdate).TotalSeconds;
  295. // if (timeNextDiff <= highFreqSampleInterval)
  296. // {
  297. // freqCollection.Add(item);
  298. // }
  299. // }
  300. // previousItem = item;
  301. //}
  302. _logger.LogInformation($"{heartRate.Serialno} {phrFreqstatus?.LastUpdate.ToString("yyyy-MM-dd HH:mm:ss")}--{firstTwoPhr[1].ToString("yyyy-MM-dd HH:mm:ss")} 产生高频心率数量 {filterPhr.Count} 条");
  303. // 高频心率数据大于等于stopHighFreqSampleCount/12个才计算胎心数据
  304. if (filterPhr.Count >= stopHighFreqSampleCount)
  305. {
  306. var avgPhr = filterPhr
  307. .OrderByDescending(i => i.LastUpdate)
  308. .Take(stopHighFreqSampleCount) // 计算最后12条
  309. //.Where(i => i.PregnancyHeartRate < triggerHighFreqLow || i.PregnancyHeartRate > triggerHighFreqHigh)
  310. .Select(i => i.PregnancyHeartRate).Average();
  311. // 高频数据不建模
  312. FreqStatsEnd = firstTwoPhr[1];
  313. _logger.LogInformation($"{heartRate.Serialno} 高频状态已经持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,超过约定的 {highFreqSampleTimes} 秒,即将结束高频状态,将下发指令");
  314. await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd);
  315. }
  316. else
  317. {
  318. _logger.LogInformation($"{heartRate.Serialno} 高频心率的数据不足{stopHighFreqSampleCount}条,不进行胎心计算");
  319. }
  320. }
  321. //不满足持续10分钟highFreqSampleTimes或出现时间倒叙
  322. else
  323. {
  324. // 高频结束后与周期性的心率时间倒叙
  325. if ((firstTwoPhr[1] - phrFreqstatus!.LastUpdate).TotalSeconds<0)
  326. {
  327. _logger.LogInformation($"{heartRate.Serialno} 高频结束出现时间倒叙,计算当条心率创建之前的高频心率");
  328. #region 计算当条心率创建之前的高频心率
  329. // 取得高频之后的所有数据
  330. var phrFlashBack = daysPhr.Where(p => p.LastUpdate >= phrFreqstatus!.LastUpdate)
  331. .OrderByDescending(i => i.LastUpdate);
  332. // 取得高频数据
  333. var freqCollection = new List<PregnancyHeartRateModel>();
  334. PregnancyHeartRateModel? previousItem = null;
  335. foreach (var item in phrFlashBack)
  336. {
  337. if (previousItem != null)
  338. {
  339. var timeNextDiff = (previousItem!.LastUpdate - item.LastUpdate).TotalSeconds;
  340. if (timeNextDiff <= highFreqSampleInterval)
  341. {
  342. freqCollection.Add(item);
  343. }
  344. }
  345. previousItem = item;
  346. }
  347. _logger.LogInformation($"{heartRate.Serialno} 高频数据个数{freqCollection.Count},触发高频开始时间{phrFreqstatus!.LastUpdate}");
  348. if (freqCollection.Count>=stopHighFreqSampleCount)
  349. {
  350. // 计算高频产生的胎心
  351. var avgPhr = freqCollection
  352. .OrderByDescending(i => i.LastUpdate)
  353. .Take(stopHighFreqSampleCount) // 计算最后12条
  354. .Select(i => i.PregnancyHeartRate).Average();
  355. await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), freqCollection.Last().LastUpdate, freqCollection.First().LastUpdate);
  356. }
  357. else
  358. {
  359. _logger.LogInformation($"{heartRate.Serialno} 时间倒叙触发计算高频心率的数据不足{stopHighFreqSampleCount}条,不进行胎心计算");
  360. }
  361. #endregion
  362. }
  363. else
  364. {
  365. _logger.LogInformation($"{heartRate.Serialno} 高频持续时间不足{highFreqSampleTimes},只持续{(firstTwoPhr[1] - phrFreqstatus!.LastUpdate).TotalSeconds} 秒");
  366. }
  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 = 60;
  386. await SetIntervalTriggerAsync(fhrScheduleKey, heartRate.Serialno, fhrScheduleTTL, heartRate);
  387. }
  388. }
  389. #endregion
  390. }
  391. else
  392. {
  393. _logger.LogInformation($"{heartRate.Serialno} 记录不足30条,建模中");
  394. }
  395. }
  396. }
  397. catch (Exception ex)
  398. {
  399. _logger.LogError($"{heartRate.Serialno} 处理孕妇心率数据异常 \n{ex.Message}\n{ex.StackTrace}");
  400. }
  401. }
  402. /// <summary>
  403. /// 常规心率计算胎心数据
  404. /// </summary>
  405. /// <param name="heartRate"></param>
  406. /// <param name="upperAlarmThreshold"></param>
  407. /// <param name="lowerAlarmThreshold"></param>
  408. /// <param name="intervalFHR"></param>
  409. /// <param name="commonPHR"></param>
  410. /// <returns></returns>
  411. private async Task CalculateNormalFetalHeartRateAsync(HisGpsHeartRate heartRate, int upperAlarmThreshold, int lowerAlarmThreshold, int intervalFHR, PregnancyCommonHeartRateModel? commonPHR)
  412. {
  413. // 上15分钟的数据
  414. // 获取当前时间
  415. DateTime nowInterval = (DateTime)heartRate.LastUpdate!;
  416. // 计算last_update到上一间隔的分钟数
  417. int minutesToSubtract = nowInterval.Minute % intervalFHR;
  418. // 计算上一间隔的时间
  419. DateTime previousInterval = nowInterval.AddMinutes(-minutesToSubtract).AddSeconds(-nowInterval.Second).AddMilliseconds(-nowInterval.Millisecond);
  420. // 使用 last_update 上一刻
  421. var sampleTimeFHR = DateTimeUtil.ConvertToTimeStamp(previousInterval).ToString();
  422. // 计算last_update到下一间隔的分钟数
  423. int minutesToAdd = intervalFHR - (nowInterval.Minute % intervalFHR);
  424. if (minutesToAdd == intervalFHR)
  425. {
  426. minutesToAdd = 0; // 如果已经是间隔,则不需要增加分钟
  427. }
  428. // 计算下一间隔的时间
  429. DateTime nextInterval = nowInterval.AddMinutes(minutesToAdd)
  430. .AddSeconds(-nowInterval.Second)
  431. .AddMilliseconds(-nowInterval.Millisecond);
  432. var daysPhr = await _serviceTDengine.GetBySerialNoAsync<PregnancyHeartRateModel>(heartRate.Serialno, 7);
  433. var normalPhrStatStartTime = nextInterval.AddMinutes(-intervalFHR) ;
  434. var normalPhrStatEndTime = nextInterval;
  435. _logger.LogInformation($"{heartRate.Serialno} 计算胎心数据, 周期:{normalPhrStatStartTime}-{normalPhrStatEndTime} ");
  436. var filteredPhr = daysPhr
  437. // 使用 last_update 下一刻
  438. .Where(i => i.LastUpdate <= normalPhrStatEndTime && i.LastUpdate >= normalPhrStatStartTime)
  439. .ToList();
  440. if (filteredPhr.Count == 0)
  441. {
  442. _logger.LogWarning($"{heartRate.Serialno} 周期:{normalPhrStatStartTime}-{normalPhrStatEndTime} 孕妇心率数据不足,{filteredPhr.Count}条记录");
  443. return;
  444. }
  445. var phrValue = filteredPhr.Count == 1
  446. ? filteredPhr.First().PregnancyHeartRate
  447. : filteredPhr.Average(i => i.PregnancyHeartRate);
  448. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR!, upperAlarmThreshold, lowerAlarmThreshold, phrValue, sampleTimeFHR);
  449. await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR!, upperAlarmThreshold, lowerAlarmThreshold, phrValue, sampleTimeFHR, normalPhrStatStartTime, normalPhrStatEndTime);
  450. }
  451. /// <summary>
  452. ///
  453. /// </summary>
  454. /// <param name="heartRate"></param>
  455. /// <param name="commonPHR"></param>
  456. /// <param name="upperAlarmThreshold"></param>
  457. /// <param name="lowerAlarmThreshold"></param>
  458. /// <param name="phrValue"></param>
  459. /// <param name="sampleTime"></param>
  460. /// <param name="statStartTime"></param>
  461. /// <param name="statEndTime"></param>
  462. /// <returns></returns>
  463. private async Task SaveAndPushFetalHeartRateAsync(HisGpsHeartRate heartRate, PregnancyCommonHeartRateModel commonPHR, int upperAlarmThreshold, int lowerAlarmThreshold, double phrValue, string sampleTime,DateTime statStartTime, DateTime statEndTime)
  464. {
  465. // 计算胎心=孕妇心率*系数
  466. var fetalHeartRate = SafeType.SafeInt(phrValue * commonPHR?.StatModeAvgFprCoefficient!);
  467. //fetalHeartRate = fetalHeartRate > 220 ? 220 : fetalHeartRate; // 胎心的最大值调整为220,超过都按该值220输出
  468. if (fetalHeartRate >= 220)
  469. {
  470. // 先使用最小系数计算
  471. var statMaxValueFprCoefficient = commonPHR?.StatMaxValueFprCoefficient!;
  472. var statMinValueFprCoefficient = commonPHR?.StatMinValueFprCoefficient!;
  473. var coefficient = statMaxValueFprCoefficient < statMinValueFprCoefficient ? statMaxValueFprCoefficient : statMinValueFprCoefficient;
  474. fetalHeartRate = SafeType.SafeInt(phrValue * coefficient);
  475. if (fetalHeartRate < 220)
  476. {
  477. _logger.LogWarning($"{heartRate.Serialno} 使用极值系数 {coefficient} ,建模数据可能出现异常,请检查");
  478. }
  479. else
  480. {
  481. fetalHeartRate = 220;
  482. _logger.LogWarning($"{heartRate.Serialno} 使用所有系数都不能放映实际,建模数据可能出现异常,请检查");
  483. }
  484. }
  485. var isAbnormal = fetalHeartRate > upperAlarmThreshold ? 1 : (fetalHeartRate < lowerAlarmThreshold ? 2 : 0);
  486. var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  487. if (phrFreqstatus == null) isAbnormal = 0;
  488. var statsusDesc = (phrFreqstatus == null) ? "常规" : "高频";
  489. _logger.LogInformation($"{heartRate.Serialno} 在 {statsusDesc} 状态,生成胎心值:{fetalHeartRate},统计周期:{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}----{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")}");
  490. //if (!isFreq)
  491. //{
  492. // statStartTime = heartRate.LastUpdate;
  493. //
  494. //}
  495. // 保存到 数据服务 MySQL 数据库
  496. HisGpsFetalHeartRate gpsFetalHeartRate = new()
  497. {
  498. FetalHeartRateId = Guid.NewGuid().ToString("D"),
  499. PersonId = commonPHR!.PersonId,
  500. Serialno = heartRate.Serialno,
  501. HeartRate = fetalHeartRate,
  502. SampleTime = sampleTime.Length > 10 ? sampleTime.Substring(0, 10) : sampleTime,
  503. IsAbnormal = isAbnormal,
  504. StatStartTime = statStartTime,
  505. StatEndTime = statEndTime,//commonPHR.StatEndTime,
  506. CreateTime = DateTime.Now,
  507. Method = 1,
  508. IsDisplay = 1,
  509. DeviceKey = commonPHR!.DeviceKey
  510. };
  511. await _hisFetalHeartApiClient.AddAsync(gpsFetalHeartRate).ConfigureAwait(false);
  512. // 推送到api/v1/open/OpenIot/SetFetalHeartRateConfig
  513. await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal);
  514. var device = await _deviceCacheMgr.GetDeviceBySerialNoAsync(heartRate.Serialno).ConfigureAwait(false);
  515. var fhrMsgId = $"{heartRate.Serialno}-{sampleTime}-{Guid.NewGuid().ToString("D")[^3..]}";
  516. var fhrMsgTime = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(long.Parse(sampleTime.Length < 13 ? sampleTime.PadRight(13, '0') : sampleTime)).ToString("yyyy-MM-dd HH:mm:ss");
  517. // 胎心数据推送到第三方
  518. var topic = "topic.push.third";
  519. var fhrThridMsg = new
  520. {
  521. messageId = fhrMsgId,
  522. topic = topic,
  523. time = fhrMsgTime,
  524. data = new
  525. {
  526. imei = heartRate.Serialno,
  527. value = fetalHeartRate,
  528. isAbnormal,
  529. type = "fetalHeart"
  530. }
  531. };
  532. await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, 31, fhrThridMsg).ConfigureAwait(false);
  533. // 胎心数据推送到微信
  534. if (isAbnormal != 0)
  535. {
  536. topic = "topic.push.wx";
  537. var fhrMsg = new
  538. {
  539. messageId = fhrMsgId,
  540. topic = topic,
  541. time = fhrMsgTime,
  542. data = new
  543. {
  544. deviceId = device?.DeviceId,
  545. imei = heartRate.Serialno,
  546. alarmTypeId = 12,
  547. alarmDeviceName = heartRate.Serialno,
  548. alarmRemarks = JsonConvert.SerializeObject(new { fetalHeartValue = fetalHeartRate, isAbnormal = isAbnormal }),
  549. address = string.Empty,
  550. deviceKey = device?.DeviceId
  551. }
  552. };
  553. await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, fhrMsg).ConfigureAwait(false);
  554. }
  555. }
  556. private async Task SetIntervalTriggerAsync(string key,string imei, long interval,HisGpsHeartRate heartRate)
  557. {
  558. // var key = $"health_monitor/schedule_push/{type}/imei/{imei}";
  559. var schedulePush = await _serviceEtcd.GetValAsync(key).ConfigureAwait(false);
  560. if (string.IsNullOrWhiteSpace(schedulePush))
  561. {
  562. var now = DateTime.Now;
  563. var timeNextRun = now.Add(TimeSpan.FromSeconds(interval));
  564. var data = new
  565. {
  566. imei,
  567. create_time = now.ToString("yyyy-MM-dd HH:mm:ss"),
  568. ttl = interval,
  569. next_run_time = timeNextRun.ToString("yyyy-MM-dd HH:mm:ss"),
  570. trigger= heartRate,
  571. };
  572. var result = JsonConvert.SerializeObject(data);
  573. await _serviceEtcd.PutValAsync(key, result, interval, false).ConfigureAwait(false);
  574. }
  575. }
  576. }
  577. }