Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

PregnancyHeartRateResolver.cs 32KB

pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 4 mēnešiem
pirms 3 mēnešiem
pirms 4 mēnešiem
pirms 3 mēnešiem
pirms 4 mēnešiem
pirms 3 mēnešiem
pirms 4 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 4 mēnešiem
pirms 3 mēnešiem
pirms 4 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 4 mēnešiem
pirms 3 mēnešiem
pirms 4 mēnešiem
pirms 3 mēnešiem
pirms 4 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 4 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 4 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 4 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 4 mēnešiem
pirms 4 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 3 mēnešiem
pirms 4 mēnešiem
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  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. var watchConfig = await _deviceCacheMgr.GetGpsDeviceWatchConfigCacheObjectBySerialNoAsync(heartRate.Serialno, "0067");
  91. var isFetalHeartEnable = watchConfig != null && (int)watchConfig["enabled"]! == 1;
  92. if (isFetalHeartEnable)
  93. {
  94. #region 定时下发触发器(定时建模)
  95. var key = $"health_monitor/schedule_push/pregnancy_heart_rate/imei/{heartRate.Serialno}";
  96. var schedule_push = await _serviceEtcd.GetValAsync(key).ConfigureAwait(false);
  97. if (string.IsNullOrWhiteSpace(schedule_push))
  98. {
  99. // 注册首次下推
  100. var interval = 0;
  101. // 获取当前时间
  102. DateTime now = DateTime.Now;
  103. var rand = new Random();
  104. var pushSec = rand.Next(59);
  105. int pushMin = int.TryParse(heartRate.Serialno.AsSpan(heartRate.Serialno.Length - 1), out pushMin) ? pushMin : 10;
  106. // 计算距离下一个$interval天后的8点的时间间隔
  107. DateTime nextRunTime = new DateTime(now.Year, now.Month, now.Day, 6, pushMin, pushSec).AddDays(interval);
  108. TimeSpan timeUntilNextRun = nextRunTime - now;
  109. if (timeUntilNextRun < TimeSpan.Zero)
  110. {
  111. timeUntilNextRun = timeUntilNextRun.Add(TimeSpan.FromDays(1));
  112. nextRunTime += TimeSpan.FromDays(1);
  113. }
  114. var ttl = (long)timeUntilNextRun.TotalSeconds;
  115. var data = new
  116. {
  117. imei = heartRate.Serialno,
  118. create_time = now.ToString("yyyy-MM-dd HH:mm:ss"),
  119. ttl,
  120. next_run_time = nextRunTime.ToString("yyyy-MM-dd HH:mm:ss")
  121. };
  122. var result = JsonConvert.SerializeObject(data);
  123. await _serviceEtcd.PutValAsync(key, result, ttl, false).ConfigureAwait(false);
  124. }
  125. #endregion
  126. // 高频心率采样间隔 highFreqSampleInterval = highFreqSampleInterval+5,增加5秒兼容
  127. var highFreqSampleInterval = (int)watchConfig!["highFreqSampleInterval"]! + 5;
  128. // 触发高频监测的心率上限值
  129. var triggerHighFreqHigh = (int)watchConfig["triggerHighFreqHigh"]!;
  130. // 触发高频监测的心率下限值
  131. var triggerHighFreqLow = (int)watchConfig["triggerHighFreqLow"]!;
  132. //停止高频心率采样心率连续正常次数
  133. var stopHighFreqSampleCount = (int)watchConfig["stopHighFreqSampleCount"]!;
  134. // 高频心率采集时长 0 为持续采集,非零为高频心率的采集时长
  135. var highFreqSampleTimes = (int)watchConfig["highFreqSampleTimes"]!;
  136. // 告警上限阀值
  137. var upperAlarmThreshold = (int)watchConfig["upperAlarmThreshold"]!;
  138. // 告警下限阀值
  139. var lowerAlarmThreshold = (int)watchConfig["lowerAlarmThreshold"]!;
  140. // EDOC
  141. var edoc = DateTimeUtil.ToDateTime(watchConfig!["EDOC"]!.ToString());
  142. // interval (分钟)
  143. var intervalFHR = (int)watchConfig["interval"]!;
  144. var phr = await _serviceTDengine.GetBySerialNoAsync<PregnancyHeartRateModel>(heartRate.Serialno, 7);
  145. if (phr.Count >= 30)
  146. {
  147. #region 定时计算胎动数据触发器两小时间隔开始
  148. var fetalMovementKey = $"health_monitor/schedule_push/cal_fetal_movement/imei/{heartRate.Serialno}";
  149. ///// 计算 0 点秒数
  150. var fetalMovementLastUpdate = (DateTime)heartRate.LastUpdate!;
  151. DateTime fmScheduleNow = DateTime.Now;
  152. // 小于两小时
  153. if (fmScheduleNow > fetalMovementLastUpdate && (fmScheduleNow - fetalMovementLastUpdate).TotalHours <= 2)
  154. {
  155. var rand = new Random();
  156. var pushSec = rand.Next(59);
  157. int pushMin = int.TryParse(heartRate.Serialno.AsSpan(heartRate.Serialno.Length - 1), out pushMin) ? pushMin : 10;
  158. var scheduleHourDiff = SCHEDULE_HOUR
  159. .Where(h => h > fetalMovementLastUpdate.Hour)
  160. .OrderBy(h => h - fetalMovementLastUpdate.Hour)
  161. .FirstOrDefault() - fetalMovementLastUpdate.Hour;
  162. var scheduleTime = fetalMovementLastUpdate.AddHours(scheduleHourDiff);
  163. DateTime nextRunTime = new(scheduleTime.Year, scheduleTime.Month, scheduleTime.Day, scheduleTime.Hour, pushMin, pushSec);
  164. TimeSpan timeUntilNextRun = nextRunTime - fmScheduleNow;
  165. var ttl = (long)timeUntilNextRun.TotalSeconds;
  166. await SetIntervalTriggerAsync(fetalMovementKey, heartRate.Serialno, ttl);
  167. }
  168. #endregion
  169. #region 计算胎心数据(按心率时间LastUpdate)
  170. var commonPHR = await _serviceTDengine.GetLastAsync<PregnancyCommonHeartRateModel>(heartRate.Serialno);
  171. if (commonPHR == null)
  172. {
  173. // 处理孕妇业务,计算一般心率并下发
  174. commonPHR = await _serviceTDengine.InitPregnancyCommonHeartRateModeAsync(heartRate.Serialno, highFreqSampleInterval: highFreqSampleInterval);
  175. // 建模完成
  176. var flag = await _serviceIotApi.SetFetalConfig(heartRate.Serialno, 1, commonPHR!.MaxValue, commonPHR!.MinValue);
  177. _logger.LogInformation($"{heartRate.Serialno} 记录数量足够,建模完成");
  178. // 保存到TDengine数据库
  179. await _serviceTDengine.InsertAsync<PregnancyCommonHeartRateModel>("hm_pchr", commonPHR!);
  180. _logger.LogInformation($"保存TDengine完成");
  181. }
  182. // 获取最近的两个记录,并计算它们的 LastUpdate 时间差
  183. var firstTwoPhr = phr.OrderByDescending(i => i.LastUpdate).Take(2).Select(i => i.LastUpdate).ToList();
  184. var timeDiff = firstTwoPhr[0] - firstTwoPhr[1];
  185. // 如果需要,将时间差转换为秒
  186. var timeDiffInSeconds = timeDiff.TotalSeconds;
  187. // 高频统计结束时间
  188. var FreqStatsEnd = DateTime.Now;
  189. // 高频心率启动
  190. if (timeDiffInSeconds <= highFreqSampleInterval)
  191. {
  192. var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  193. if (phrFreqstatus == null)
  194. {
  195. /// 设置高频状态
  196. _logger.LogInformation($"{heartRate.Serialno} 进入高频心率启动状态 timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval}");
  197. // 设置高频状态
  198. var freqFirstPhr = phr.OrderByDescending(i => i.LastUpdate).First();
  199. await _deviceCacheMgr.SetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno, freqFirstPhr);
  200. //phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  201. }
  202. /// phr PregnancyHeartRate 连续连续正常次数个值都是正常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig),
  203. /// 取连续正常次数正常值的平均值,推送到api/v1/open/OpenIot/SetFetalHeartRateConfig
  204. #region 检查是否连续12个值都是正常的
  205. // 获取最近连续正常次数个心率记录
  206. var lastPhr = phr.OrderByDescending(i => i.LastUpdate).Take(stopHighFreqSampleCount).ToList();
  207. // 检查是否连续12个值都是正常的
  208. if (lastPhr.All(i => i.PregnancyHeartRate >= triggerHighFreqLow && i.PregnancyHeartRate <= triggerHighFreqHigh))
  209. {
  210. var avgPhr = lastPhr.Select(i => i.PregnancyHeartRate).Average();
  211. // 计算一般心率得到胎心系数
  212. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(DateTime.Now).ToString());
  213. // 高频数据不建模
  214. FreqStatsEnd = (DateTime)heartRate.LastUpdate!;
  215. _logger.LogInformation($"{heartRate.Serialno} 高频状态已经持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,连续 {stopHighFreqSampleCount} 次采样心率正常,将下发指令");
  216. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString());
  217. await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd);
  218. // 删除高频状态的首条记录
  219. await _deviceCacheMgr.DelPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  220. _logger.LogInformation($"{heartRate.Serialno} 连续正常结束高频心率状态, timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval},高频状态持续{((DateTime)heartRate.LastUpdate - phrFreqstatus!.LastUpdate).TotalSeconds} 秒");
  221. }
  222. #endregion
  223. }
  224. // 高频心率结束或平常心率
  225. else
  226. {
  227. var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  228. // 高频心率结束
  229. if (phrFreqstatus != null)
  230. {
  231. /// 在highFreqSampleTimes=0一直异常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig),
  232. /// 取所有值的平均值,推送胎心数据到api/v1/open/OpenIot/SetFetalHeartRateConfig
  233. if (highFreqSampleTimes == 0)
  234. {
  235. var avgPhr = phr.OrderByDescending(i => i.LastUpdate)
  236. .Where(i => i.LastUpdate >= phrFreqstatus?.LastUpdate)
  237. .Skip(1) // 去除首条
  238. .Where(i => i.PregnancyHeartRate < triggerHighFreqLow || i.PregnancyHeartRate > triggerHighFreqHigh)
  239. .Select(i => i.PregnancyHeartRate).Average();
  240. // 推送胎心数据到 api/v1/open/OpenIot/SetFetalHeartRateConfig
  241. // 计算一般心率得到胎心系数
  242. //await SaveAndPushFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, avgPhr);
  243. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(DateTime.Now).ToString());
  244. // 高频数据不建模
  245. FreqStatsEnd = firstTwoPhr[1];
  246. _logger.LogInformation($"{heartRate.Serialno} 高频状态已经持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,highFreqSampleTimes={highFreqSampleTimes}秒,即将结束高频状态,将下发指令");
  247. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString());
  248. await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd);
  249. }
  250. /// 在highFreqSampleTimes>0一直异常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig),
  251. /// 取所有值的平均值,推送胎心数据到api/v1/open/OpenIot/SetFetalHeartRateConfig
  252. if (highFreqSampleTimes > 0 && heartRate.LastUpdate >= (phrFreqstatus?.LastUpdate.AddSeconds(highFreqSampleTimes)))
  253. {
  254. // 获取高频心率数据个数
  255. var filterPhr = phr
  256. .Where(i => i.LastUpdate >= phrFreqstatus?.LastUpdate)
  257. .Skip(1)
  258. .ToList();
  259. //var freqCollection = new List<PregnancyHeartRateModel>();
  260. //PregnancyHeartRateModel? previousItem = null;
  261. //foreach (var item in filterPhr)
  262. //{
  263. // if (previousItem != null)
  264. // {
  265. // var timeNextDiff = (previousItem!.LastUpdate - item.LastUpdate).TotalSeconds;
  266. // if (timeNextDiff <= highFreqSampleInterval)
  267. // {
  268. // freqCollection.Add(item);
  269. // }
  270. // }
  271. // previousItem = item;
  272. //}
  273. _logger.LogInformation($"{heartRate.Serialno} {phrFreqstatus?.LastUpdate.ToString("yyyy-MM-dd HH:mm:ss")}--{firstTwoPhr[1].ToString("yyyy-MM-dd HH:mm:ss")} 产生高频心率数量 {filterPhr.Count} 条");
  274. // 高频心率数据大于等于stopHighFreqSampleCount/12个才计算胎心数据
  275. if (filterPhr.Count >= stopHighFreqSampleCount)
  276. {
  277. var avgPhr = filterPhr
  278. .OrderByDescending(i => i.LastUpdate)
  279. .Take(stopHighFreqSampleCount) // 计算最后12条
  280. //.Where(i => i.PregnancyHeartRate < triggerHighFreqLow || i.PregnancyHeartRate > triggerHighFreqHigh)
  281. .Select(i => i.PregnancyHeartRate).Average();
  282. // 高频数据不建模
  283. FreqStatsEnd = firstTwoPhr[1];
  284. _logger.LogInformation($"{heartRate.Serialno} 高频状态已经持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,超过约定的 {highFreqSampleTimes} 秒,即将结束高频状态,将下发指令");
  285. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString());
  286. await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd);
  287. }
  288. else
  289. {
  290. _logger.LogInformation($"{heartRate.Serialno} 高频心率的数据不足{stopHighFreqSampleCount}条,不进行胎心计算");
  291. }
  292. }
  293. //不满足持续10分钟highFreqSampleTimes
  294. else
  295. {
  296. _logger.LogInformation($"{heartRate.Serialno} 高频持续时间不足{highFreqSampleTimes},只持续{(firstTwoPhr[1] - phrFreqstatus!.LastUpdate).TotalSeconds} 秒");
  297. }
  298. // 删除高频状态的首条记录
  299. await _deviceCacheMgr.DelPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  300. _logger.LogInformation($"{heartRate.Serialno} 超时结束高频心率状态 timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval},高频状态持续{(firstTwoPhr[1] - phrFreqstatus!.LastUpdate).TotalSeconds} 秒");
  301. // 计算本次平常心率的胎心数据
  302. await CalculateNormalFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, intervalFHR, commonPHR);
  303. }
  304. // 平常心率
  305. else
  306. {
  307. // 计算本次平常心率的胎心数据
  308. await CalculateNormalFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, intervalFHR, commonPHR);
  309. }
  310. }
  311. #endregion
  312. }
  313. else
  314. {
  315. _logger.LogInformation($"{heartRate.Serialno} 记录不足30条,建模中");
  316. }
  317. }
  318. //try
  319. //{
  320. //}
  321. //catch (Exception ex)
  322. //{
  323. // _logger.LogError($"{heartRate.Serialno} 处理孕妇心率数据异常 \n{ex.Message}\n{ex.StackTrace}");
  324. //}
  325. }
  326. /// <summary>
  327. /// 平常心率计算胎心数据
  328. /// </summary>
  329. /// <param name="heartRate"></param>
  330. /// <param name="upperAlarmThreshold"></param>
  331. /// <param name="lowerAlarmThreshold"></param>
  332. /// <param name="intervalFHR"></param>
  333. /// <param name="commonPHR"></param>
  334. /// <returns></returns>
  335. private async Task CalculateNormalFetalHeartRateAsync(HisGpsHeartRate heartRate, int upperAlarmThreshold, int lowerAlarmThreshold, int intervalFHR, PregnancyCommonHeartRateModel? commonPHR)
  336. {
  337. // 上15分钟的数据
  338. // 获取当前时间
  339. DateTime nowInterval = (DateTime)heartRate.LastUpdate!;
  340. // 计算last_update到上一间隔的分钟数
  341. int minutesToSubtract = nowInterval.Minute % intervalFHR;
  342. // 计算上一间隔的时间
  343. DateTime previousInterval = nowInterval.AddMinutes(-minutesToSubtract).AddSeconds(-nowInterval.Second).AddMilliseconds(-nowInterval.Millisecond);
  344. // 使用 last_update 上一刻
  345. var sampleTimeFHR = DateTimeUtil.ConvertToTimeStamp(previousInterval).ToString();
  346. // 计算last_update到下一间隔的分钟数
  347. int minutesToAdd = intervalFHR - (nowInterval.Minute % intervalFHR);
  348. if (minutesToAdd == intervalFHR)
  349. {
  350. minutesToAdd = 0; // 如果已经是间隔,则不需要增加分钟
  351. }
  352. // 计算下一间隔的时间
  353. DateTime nextInterval = nowInterval.AddMinutes(minutesToAdd)
  354. .AddSeconds(-nowInterval.Second)
  355. .AddMilliseconds(-nowInterval.Millisecond);
  356. var daysPhr = await _serviceTDengine.GetBySerialNoAsync<PregnancyHeartRateModel>(heartRate.Serialno, 7);
  357. var normalPhrStatStartTime = nextInterval;
  358. var normalPhrStatEndTime = nextInterval.AddMinutes(-intervalFHR);
  359. _logger.LogInformation($"{heartRate.Serialno} 计算胎心数据, 周期:{normalPhrStatStartTime}-{normalPhrStatEndTime} ");
  360. var filteredPhr = daysPhr
  361. // 使用 last_update 下一刻
  362. .Where(i => i.LastUpdate <= normalPhrStatStartTime && i.LastUpdate >= normalPhrStatEndTime)
  363. .ToList();
  364. if (filteredPhr.Count == 0)
  365. {
  366. _logger.LogWarning($"{heartRate.Serialno} 周期:{normalPhrStatStartTime}-{normalPhrStatEndTime} 孕妇心率数据不足,{filteredPhr.Count}条记录");
  367. return;
  368. }
  369. var phrValue = filteredPhr.Count == 1
  370. ? filteredPhr.First().PregnancyHeartRate
  371. : filteredPhr.Average(i => i.PregnancyHeartRate);
  372. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR!, upperAlarmThreshold, lowerAlarmThreshold, phrValue, sampleTimeFHR);
  373. await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR!, upperAlarmThreshold, lowerAlarmThreshold, phrValue, sampleTimeFHR, normalPhrStatStartTime, normalPhrStatEndTime);
  374. }
  375. /// <summary>
  376. ///
  377. /// </summary>
  378. /// <param name="heartRate"></param>
  379. /// <param name="commonPHR"></param>
  380. /// <param name="upperAlarmThreshold"></param>
  381. /// <param name="lowerAlarmThreshold"></param>
  382. /// <param name="phrValue"></param>
  383. /// <param name="sampleTime"></param>
  384. /// <param name="statStartTime"></param>
  385. /// <param name="statEndTime"></param>
  386. /// <returns></returns>
  387. private async Task SaveAndPushFetalHeartRateAsync(HisGpsHeartRate heartRate, PregnancyCommonHeartRateModel commonPHR, int upperAlarmThreshold, int lowerAlarmThreshold, double phrValue, string sampleTime,DateTime statStartTime, DateTime statEndTime)
  388. {
  389. // 计算胎心=孕妇心率*系数
  390. var fetalHeartRate = SafeType.SafeInt(phrValue * commonPHR?.StatModeAvgFprCoefficient!);
  391. //fetalHeartRate = fetalHeartRate > 220 ? 220 : fetalHeartRate; // 胎心的最大值调整为220,超过都按该值220输出
  392. if (fetalHeartRate >= 220)
  393. {
  394. // 先使用最小系数计算
  395. var statMaxValueFprCoefficient = commonPHR?.StatMaxValueFprCoefficient!;
  396. var statMinValueFprCoefficient = commonPHR?.StatMinValueFprCoefficient!;
  397. var coefficient = statMaxValueFprCoefficient < statMinValueFprCoefficient ? statMaxValueFprCoefficient : statMinValueFprCoefficient;
  398. fetalHeartRate = SafeType.SafeInt(phrValue * coefficient);
  399. if (fetalHeartRate < 220)
  400. {
  401. _logger.LogWarning($"{heartRate.Serialno} 使用极值系数 {coefficient} ,建模数据可能出现异常,请检查");
  402. }
  403. else
  404. {
  405. fetalHeartRate = 220;
  406. _logger.LogWarning($"{heartRate.Serialno} 使用所有系数都不能放映实际,建模数据可能出现异常,请检查");
  407. }
  408. }
  409. var isAbnormal = fetalHeartRate > upperAlarmThreshold ? 1 : (fetalHeartRate < lowerAlarmThreshold ? 2 : 0);
  410. var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  411. if (phrFreqstatus == null) isAbnormal = 0;
  412. //if (!isFreq)
  413. //{
  414. // statStartTime = heartRate.LastUpdate;
  415. //
  416. //}
  417. // 保存到 数据服务 MySQL 数据库
  418. HisGpsFetalHeartRate gpsFetalHeartRate = new()
  419. {
  420. FetalHeartRateId = Guid.NewGuid().ToString("D"),
  421. PersonId = commonPHR!.PersonId,
  422. Serialno = heartRate.Serialno,
  423. HeartRate = fetalHeartRate,
  424. SampleTime = sampleTime.Length > 10 ? sampleTime.Substring(0, 10) : sampleTime,
  425. IsAbnormal = isAbnormal,
  426. StatStartTime = statStartTime,
  427. StatEndTime = statEndTime,//commonPHR.StatEndTime,
  428. CreateTime = DateTime.Now,
  429. Method = 1,
  430. IsDisplay = 1,
  431. DeviceKey = commonPHR!.DeviceKey
  432. };
  433. await _hisFetalHeartApiClient.AddAsync(gpsFetalHeartRate).ConfigureAwait(false);
  434. // 推送到api/v1/open/OpenIot/SetFetalHeartRateConfig
  435. await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal);
  436. var device = await _deviceCacheMgr.GetDeviceBySerialNoAsync(heartRate.Serialno).ConfigureAwait(false);
  437. var fhrMsgId = $"{heartRate.Serialno}-{sampleTime}-{Guid.NewGuid().ToString("D")[^3..]}";
  438. var fhrMsgTime = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(long.Parse(sampleTime.Length < 13 ? sampleTime.PadRight(13, '0') : sampleTime)).ToString("yyyy-MM-dd HH:mm:ss");
  439. // 胎心数据推送到第三方
  440. var topic = "topic.push.third";
  441. var fhrThridMsg = new
  442. {
  443. messageId = fhrMsgId,
  444. topic = topic,
  445. time = fhrMsgTime,
  446. data = new
  447. {
  448. imei = heartRate.Serialno,
  449. value = fetalHeartRate,
  450. isAbnormal,
  451. type = "fetalHeart"
  452. }
  453. };
  454. await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, 31, fhrThridMsg).ConfigureAwait(false);
  455. // 胎心数据推送到微信
  456. if (isAbnormal != 0)
  457. {
  458. topic = "topic.push.wx";
  459. var fhrMsg = new
  460. {
  461. messageId = fhrMsgId,
  462. topic = topic,
  463. time = fhrMsgTime,
  464. data = new
  465. {
  466. deviceId = device?.DeviceId,
  467. imei = heartRate.Serialno,
  468. alarmTypeId = 12,
  469. alarmDeviceName = heartRate.Serialno,
  470. alarmRemarks = JsonConvert.SerializeObject(new { fetalHeartValue = fetalHeartRate, isAbnormal = isAbnormal }),
  471. address = string.Empty,
  472. deviceKey = device?.DeviceId
  473. }
  474. };
  475. await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, fhrMsg).ConfigureAwait(false);
  476. }
  477. }
  478. private async Task SetIntervalTriggerAsync(string key,string imei, long interval)
  479. {
  480. // var key = $"health_monitor/schedule_push/{type}/imei/{imei}";
  481. var schedulePush = await _serviceEtcd.GetValAsync(key).ConfigureAwait(false);
  482. if (string.IsNullOrWhiteSpace(schedulePush))
  483. {
  484. var now = DateTime.Now;
  485. var timeNextRun = now.Add(TimeSpan.FromSeconds(interval));
  486. var data = new
  487. {
  488. imei,
  489. create_time = now.ToString("yyyy-MM-dd HH:mm:ss"),
  490. ttl = interval,
  491. next_run_time = timeNextRun.ToString("yyyy-MM-dd HH:mm:ss")
  492. };
  493. var result = JsonConvert.SerializeObject(data);
  494. await _serviceEtcd.PutValAsync(key, result, interval, false).ConfigureAwait(false);
  495. }
  496. }
  497. }
  498. }