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

991 line
60KB

  1. using Etcdserverpb;
  2. using HealthMonitor.Common;
  3. using HealthMonitor.Common.helper;
  4. using HealthMonitor.Model.Service.Mapper;
  5. using HealthMonitor.Service.Biz;
  6. using HealthMonitor.Service.Biz.db;
  7. using HealthMonitor.Service.Cache;
  8. using HealthMonitor.Service.Etcd;
  9. using HealthMonitor.Service.MessageQueue;
  10. using HealthMonitor.Service.Resolver.Interface;
  11. using HealthMonitor.Service.Sub;
  12. using HealthMonitor.Service.Sub.Topic.Model;
  13. using Microsoft.EntityFrameworkCore.Metadata;
  14. using Microsoft.Extensions.Logging;
  15. using Newtonsoft.Json;
  16. using Newtonsoft.Json.Linq;
  17. using SqlSugar;
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Linq;
  21. using System.Net;
  22. using System.Text;
  23. using System.Threading.Tasks;
  24. using TelpoDataService.Util.Clients;
  25. using TelpoDataService.Util.Entities.GpsCard;
  26. using TelpoDataService.Util.Entities.GpsLocationHistory;
  27. using TelpoDataService.Util.Models;
  28. using TelpoDataService.Util.QueryObjects;
  29. namespace HealthMonitor.Service.Resolver
  30. {
  31. public class PregnancyHeartRateResolver : IResolver
  32. {
  33. private readonly ILogger<PregnancyHeartRateResolver> _logger;
  34. private readonly TDengineService _serviceTDengine;
  35. private readonly DeviceCacheManager _deviceCacheMgr;
  36. private readonly IotApiService _serviceIotApi;
  37. private readonly AsyncLocal<string> _messageId = new();
  38. private readonly AsyncLocal<HisGpsHeartRate> _msgData = new();
  39. private readonly HttpHelper _httpHelper = default!;
  40. private readonly EtcdService _serviceEtcd;
  41. private readonly GpsLocationHistoryAccessorClient<HisGpsFetalHeartRate> _hisFetalHeartApiClient;
  42. private readonly GpsLocationHistoryAccessorClient<HisGpsFetalMovement> _hisFetalMovementApiClient;
  43. private readonly FetalMovementNormalValueRangeCacheManager _mgrFetalMovementNormalValueRangeCache;
  44. private readonly MqProcessLogic _serviceMqProcess;
  45. private static int[] SCHEDULE_HOUR = new int[] { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24 };
  46. public PregnancyHeartRateResolver(ILogger<PregnancyHeartRateResolver> logger,
  47. HttpHelper httpHelper, EtcdService serviceEtcd, DeviceCacheManager deviceCacheMgr,
  48. MqProcessLogic serviceMqProcess,
  49. IotApiService iotApiService, TDengineService serviceDengine, FetalMovementNormalValueRangeCacheManager fetalMovementNormalValueRangeCacheMgr,
  50. GpsLocationHistoryAccessorClient<HisGpsFetalHeartRate> hisFetalHeartApiClient,
  51. GpsLocationHistoryAccessorClient<HisGpsFetalMovement> hisFetalMovementApiClient
  52. )
  53. {
  54. _logger = logger;
  55. _httpHelper = httpHelper;
  56. _serviceEtcd = serviceEtcd;
  57. _serviceTDengine = serviceDengine;
  58. _deviceCacheMgr = deviceCacheMgr;
  59. _serviceIotApi = iotApiService;
  60. _serviceMqProcess= serviceMqProcess;
  61. _hisFetalHeartApiClient = hisFetalHeartApiClient;
  62. _hisFetalMovementApiClient = hisFetalMovementApiClient;
  63. _mgrFetalMovementNormalValueRangeCache = fetalMovementNormalValueRangeCacheMgr;
  64. }
  65. public void SetResolveInfo(PackageMsgModel msg)
  66. {
  67. var topicHmPregnancyHeartRate = JsonConvert.DeserializeObject<TopicHmPregnancyHeartRate>(msg.DetailData.ToString()!);
  68. _messageId.Value = msg.MessageId;
  69. _msgData.Value = new HisGpsHeartRate()
  70. {
  71. HeartRateId = topicHmPregnancyHeartRate!.PregnancyHeartRateId,
  72. MessageId = topicHmPregnancyHeartRate!.MessageId,
  73. Serialno = topicHmPregnancyHeartRate!.Serialno,
  74. HeartRate= topicHmPregnancyHeartRate.PregnancyHeartRate,
  75. LastUpdate = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(SafeType.SafeInt64(topicHmPregnancyHeartRate.LastUpdate) / 1000000),
  76. CreateTime = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(SafeType.SafeInt64(topicHmPregnancyHeartRate.CreateTime) / 1000000),
  77. Method = topicHmPregnancyHeartRate!.Method,
  78. IsDisplay = topicHmPregnancyHeartRate!.IsDisplay ? 1 : 0
  79. };
  80. }
  81. public override string ToString()
  82. {
  83. return $"{nameof(PregnancyHeartRateResolver)}[{_messageId.Value}]";
  84. }
  85. public async Task ExecuteMessageAsync()
  86. {
  87. var messageId = _messageId.Value;
  88. var heartRate = _msgData.Value!;
  89. try
  90. {
  91. var watchConfig = await _deviceCacheMgr.GetGpsDeviceWatchConfigCacheObjectBySerialNoAsync(heartRate.Serialno, "0067");
  92. var isFetalHeartEnable = watchConfig != null && (int)watchConfig["enabled"]! == 1;
  93. if (isFetalHeartEnable)
  94. {
  95. // 高频心率采样间隔 highFreqSampleInterval = highFreqSampleInterval+5,增加5秒兼容
  96. var highFreqSampleInterval = (int)watchConfig!["highFreqSampleInterval"]! + 5;
  97. // 触发高频监测的心率上限值
  98. var triggerHighFreqHigh = (int)watchConfig["triggerHighFreqHigh"]!;
  99. // 触发高频监测的心率下限值
  100. var triggerHighFreqLow = (int)watchConfig["triggerHighFreqLow"]!;
  101. //停止高频心率采样心率连续正常次数
  102. var stopHighFreqSampleCount = (int)watchConfig["stopHighFreqSampleCount"]!;
  103. // 高频心率采集时长 0 为持续采集,非零为高频心率的采集时长
  104. var highFreqSampleTimes = (int)watchConfig["highFreqSampleTimes"]!;
  105. // 告警上限阀值
  106. var upperAlarmThreshold = (int)watchConfig["upperAlarmThreshold"]!;
  107. // 告警下限阀值
  108. var lowerAlarmThreshold = (int)watchConfig["lowerAlarmThreshold"]!;
  109. // EDOC
  110. var edoc = DateTimeUtil.ToDateTime(watchConfig!["EDOC"]!.ToString());
  111. // interval (分钟)
  112. var intervalFHR = (int)watchConfig["interval"]!;
  113. var fetalHeartRateIsAbnormal = 0;
  114. var phr = await _serviceTDengine.GetBySerialNoAsync<PregnancyHeartRateModel>(heartRate.Serialno, 7);
  115. if (phr.Count >= 30)
  116. {
  117. #region 计算胎心和胎动数据
  118. var commonPHR = await _serviceTDengine.GetLastAsync<PregnancyCommonHeartRateModel>(heartRate.Serialno);
  119. if (commonPHR == null)
  120. {
  121. // 处理孕妇业务,计算一般心率并下发
  122. commonPHR = await _serviceTDengine.InitPregnancyCommonHeartRateModeAsync(heartRate.Serialno, highFreqSampleInterval: highFreqSampleInterval);
  123. // 建模完成
  124. var flag = await _serviceIotApi.SetFetalConfig(heartRate.Serialno, 1, commonPHR!.MaxValue, commonPHR!.MinValue);
  125. _logger.LogInformation($"{heartRate.Serialno} 记录数量足够,建模完成");
  126. // 保存到TDengine数据库
  127. await _serviceTDengine.InsertAsync<PregnancyCommonHeartRateModel>("hm_pchr", commonPHR!);
  128. _logger.LogInformation($"保存TDengine完成");
  129. }
  130. #region 计算胎心数据(按心率时间LastUpdate)
  131. // 获取最近的两个记录,并计算它们的 LastUpdate 时间差
  132. var firstTwoPhr = phr.OrderByDescending(i => i.LastUpdate).Take(2).Select(i => i.LastUpdate).ToList();
  133. var timeDiff = firstTwoPhr[0] - firstTwoPhr[1];
  134. // 如果需要,将时间差转换为秒
  135. var timeDiffInSeconds = timeDiff.TotalSeconds;
  136. // 高频统计结束时间
  137. var FreqStatsEnd = DateTime.Now;
  138. // 高频心率启动
  139. if (timeDiffInSeconds <= highFreqSampleInterval)
  140. {
  141. var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  142. if (phrFreqstatus == null)
  143. {
  144. /// 设置高频状态
  145. _logger.LogInformation($"{heartRate.Serialno} 进入高频心率启动状态 timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval}");
  146. // 设置高频状态
  147. var freqFirstPhr = phr.OrderByDescending(i => i.LastUpdate).First();
  148. await _deviceCacheMgr.SetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno, freqFirstPhr);
  149. //phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  150. }
  151. /// phr PregnancyHeartRate 连续连续正常次数个值都是正常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig),
  152. /// 取连续正常次数正常值的平均值,推送到api/v1/open/OpenIot/SetFetalHeartRateConfig
  153. #region 检查是否连续12个值都是正常的
  154. // 获取最近连续正常次数个心率记录
  155. var lastPhr = phr.OrderByDescending(i => i.LastUpdate).Take(stopHighFreqSampleCount).ToList();
  156. // 检查是否连续12个值都是正常的
  157. if (lastPhr.All(i => i.PregnancyHeartRate >= triggerHighFreqLow && i.PregnancyHeartRate <= triggerHighFreqHigh))
  158. {
  159. var avgPhr = lastPhr.Select(i => i.PregnancyHeartRate).Average();
  160. // 计算一般心率得到胎心系数
  161. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(DateTime.Now).ToString());
  162. // 高频数据不建模
  163. FreqStatsEnd = (DateTime)heartRate.LastUpdate!;
  164. _logger.LogInformation($"{heartRate.Serialno} 高频状态已经持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,连续 {stopHighFreqSampleCount} 次采样心率正常,将下发指令");
  165. await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString());
  166. // 删除高频状态的首条记录
  167. await _deviceCacheMgr.DelPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  168. _logger.LogInformation($"{heartRate.Serialno} 连续正常结束高频心率状态, timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval},高频状态持续{((DateTime)heartRate.LastUpdate - phrFreqstatus!.LastUpdate).TotalSeconds} 秒");
  169. }
  170. #endregion
  171. }
  172. // 高频心率结束或正常心率
  173. else
  174. {
  175. var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  176. if (phrFreqstatus != null)
  177. {
  178. /// 在highFreqSampleTimes=0一直异常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig),
  179. /// 取所有值的平均值,推送胎心数据到api/v1/open/OpenIot/SetFetalHeartRateConfig
  180. if (highFreqSampleTimes == 0)
  181. {
  182. var avgPhr = phr.OrderByDescending(i => i.LastUpdate)
  183. .Where(i => i.LastUpdate >= phrFreqstatus?.LastUpdate)
  184. .Skip(1) // 去除首条
  185. .Where(i => i.PregnancyHeartRate < triggerHighFreqLow || i.PregnancyHeartRate > triggerHighFreqHigh)
  186. .Select(i => i.PregnancyHeartRate).Average();
  187. // 推送胎心数据到 api/v1/open/OpenIot/SetFetalHeartRateConfig
  188. // 计算一般心率得到胎心系数
  189. //await SaveAndPushFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, avgPhr);
  190. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(DateTime.Now).ToString());
  191. // 高频数据不建模
  192. FreqStatsEnd = firstTwoPhr[1];
  193. _logger.LogInformation($"{heartRate.Serialno} 高频状态已经持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,highFreqSampleTimes={highFreqSampleTimes}秒,即将结束高频状态,将下发指令");
  194. await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString());
  195. }
  196. /// 在highFreqSampleTimes>0一直异常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig),
  197. /// 取所有值的平均值,推送胎心数据到api/v1/open/OpenIot/SetFetalHeartRateConfig
  198. if (highFreqSampleTimes > 0 && heartRate.LastUpdate >= (phrFreqstatus?.LastUpdate.AddSeconds(highFreqSampleTimes)))
  199. {
  200. var filterPhr = phr
  201. .Where(i => i.LastUpdate >= phrFreqstatus?.LastUpdate);
  202. var avgPhr = filterPhr
  203. .OrderByDescending(i => i.LastUpdate)
  204. .Skip(1) // 去除首条
  205. .Take(12) // 计算最后12条
  206. //.Where(i => i.PregnancyHeartRate < triggerHighFreqLow || i.PregnancyHeartRate > triggerHighFreqHigh)
  207. .Select(i => i.PregnancyHeartRate).Average();
  208. // 推送胎心数据到 api/v1/open/OpenIot/SetFetalHeartRateConfig
  209. // 计算一般心率得到胎心系数
  210. //await SaveAndPushFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, avgPhr);
  211. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(DateTime.Now).ToString());
  212. //await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(DateTime.Now).ToString());
  213. // 高频数据不建模
  214. FreqStatsEnd = firstTwoPhr[1];
  215. _logger.LogInformation($"{heartRate.Serialno} 高频状态已经持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,超过约定的 {highFreqSampleTimes} 秒,即将结束高频状态,将下发指令");
  216. await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString());
  217. }
  218. // 删除高频状态的首条记录
  219. await _deviceCacheMgr.DelPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  220. _logger.LogInformation($"{heartRate.Serialno} 超时结束高频心率状态 timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval},高频状态持续{(firstTwoPhr[1] - phrFreqstatus!.LastUpdate).TotalSeconds} 秒");
  221. }
  222. // 正常心率
  223. else
  224. {
  225. // 上15分钟的数据
  226. // 获取当前时间
  227. DateTime nowInterval = (DateTime)heartRate.LastUpdate!;
  228. // 计算last_update到上一间隔的分钟数
  229. int minutesToSubtract = nowInterval.Minute % intervalFHR;
  230. // 计算上一间隔的时间
  231. DateTime previousInterval = nowInterval.AddMinutes(-minutesToSubtract).AddSeconds(-nowInterval.Second).AddMilliseconds(-nowInterval.Millisecond);
  232. // 使用 last_update 上一刻
  233. var sampleTimeFHR = DateTimeUtil.ConvertToTimeStamp(previousInterval).ToString();
  234. // 计算last_update到下一间隔的分钟数
  235. int minutesToAdd = intervalFHR - (nowInterval.Minute % intervalFHR);
  236. if (minutesToAdd == intervalFHR)
  237. {
  238. minutesToAdd = 0; // 如果已经是间隔,则不需要增加分钟
  239. }
  240. // 计算下一间隔的时间
  241. DateTime nextInterval = nowInterval.AddMinutes(minutesToAdd)
  242. .AddSeconds(-nowInterval.Second)
  243. .AddMilliseconds(-nowInterval.Millisecond);
  244. var daysPhr = await _serviceTDengine.GetBySerialNoAsync<PregnancyHeartRateModel>(heartRate.Serialno, 7);
  245. var normalPhrStatStartTime = nextInterval;
  246. var normalPhrStatEndTime = nextInterval.AddMinutes(-intervalFHR);
  247. _logger.LogInformation($"{heartRate.Serialno} 计算胎心数据, 周期:{normalPhrStatStartTime}-{normalPhrStatEndTime} ");
  248. var filteredPhr = daysPhr
  249. // 使用 last_update 下一刻
  250. .Where(i => i.LastUpdate <= normalPhrStatStartTime && i.LastUpdate >= normalPhrStatEndTime)
  251. .ToList();
  252. if (filteredPhr.Count == 0)
  253. {
  254. _logger.LogWarning($"{heartRate.Serialno} 周期:{normalPhrStatStartTime}-{normalPhrStatEndTime} 孕妇心率数据不足,{filteredPhr.Count}条记录");
  255. return;
  256. }
  257. var phrValue = filteredPhr.Count == 1
  258. ? filteredPhr.First().PregnancyHeartRate
  259. : filteredPhr.Average(i => i.PregnancyHeartRate);
  260. var fetalHeartRate = SafeType.SafeInt(phrValue * commonPHR?.StatModeAvgFprCoefficient!);
  261. fetalHeartRateIsAbnormal = fetalHeartRate > upperAlarmThreshold ? 1 : (fetalHeartRate < lowerAlarmThreshold ? 2 : 0);
  262. fetalHeartRateIsAbnormal = 0;// 不是高频数据,按正常值
  263. /* HisGpsFetalHeartRate gpsFetalHeartRate = new()
  264. {
  265. FetalHeartRateId = Guid.NewGuid().ToString("D"),
  266. PersonId = commonPHR!.PersonId,
  267. Serialno = heartRate.Serialno,
  268. HeartRate = fetalHeartRate,
  269. SampleTime = sampleTimeFHR.Length > 10 ? sampleTimeFHR.Substring(0, 10) : sampleTimeFHR,
  270. IsAbnormal = fetalHeartRateIsAbnormal,
  271. StatStartTime = filteredPhr.OrderBy(i => i.LastUpdate).First().LastUpdate,
  272. StatEndTime = filteredPhr.OrderBy(i => i.LastUpdate).Last().LastUpdate,
  273. CreateTime = DateTime.Now,
  274. Method = 1,
  275. IsDisplay = 1,
  276. DeviceKey = commonPHR!.DeviceKey
  277. };
  278. // 保存到 数据服务 MySQL 数据库
  279. await _hisFetalHeartApiClient.AddAsync(gpsFetalHeartRate).ConfigureAwait(false);
  280. // 推送到api/v1/open/OpenIot/SetFetalHeartRateConfig
  281. await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTimeFHR, fetalHeartRateIsAbnormal);
  282. // 推送送微信
  283. if (fetalHeartRateIsAbnormal != 0)
  284. {
  285. var device = await _deviceCacheMgr.GetDeviceBySerialNoAsync(heartRate.Serialno).ConfigureAwait(false);
  286. var fhrMsgId = $"{heartRate.Serialno}-{sampleTimeFHR}-{Guid.NewGuid().ToString("D")[^3..]}";
  287. var topic = "topic.push.wx";
  288. var fhrMsg = new
  289. {
  290. messageId = fhrMsgId,
  291. topic = topic,
  292. time = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(long.Parse(sampleTimeFHR.Length < 13 ? sampleTimeFHR.PadRight(13, '0') : sampleTimeFHR)).ToString("yyyy-MM-dd HH:mm:ss"),
  293. data = new
  294. {
  295. deviceId = device?.DeviceId,
  296. imei = heartRate.Serialno,
  297. alarmTypeId = 12,
  298. alarmDeviceName = heartRate.Serialno,
  299. alarmRemarks = JsonConvert.SerializeObject(new { fetalHeartValue = fetalHeartRate, isAbnormal = fetalHeartRateIsAbnormal }),
  300. address = string.Empty,
  301. deviceKey = device?.DeviceId
  302. }
  303. };
  304. await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, fhrMsg).ConfigureAwait(false);
  305. }
  306. */
  307. await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR!, upperAlarmThreshold, lowerAlarmThreshold, phrValue, sampleTimeFHR, false);
  308. }
  309. }
  310. #endregion
  311. #region 计算胎动数据(按心率时间LastUpdate)
  312. /**
  313. _logger.LogInformation($"{heartRate.Serialno} 计算胎动数据 ");
  314. var fetalMovementNow = (DateTime)heartRate.LastUpdate!;
  315. var midNight = new DateTime(fetalMovementNow.Year, fetalMovementNow.Month, fetalMovementNow.Day, 0, 0, 0);
  316. TimeSpan fetalMovementTS = fetalMovementNow - midNight;
  317. // 当天每隔2小时的段数,取整数部分
  318. int segmentCountFM = (int)(fetalMovementTS.TotalHours / 2);
  319. for (int i = 0; i < segmentCountFM; i++)
  320. {
  321. // 每两小时
  322. var fetalMovementSampleTime = DateTimeUtil.ConvertToTimeStamp(midNight.AddHours(2 * i)).ToString()[..10];
  323. // 统计开始时间
  324. var statStartTime = midNight.AddHours(2 * i);
  325. // 统计结束时间
  326. var statEndTime = midNight.AddHours(2 * (i+1));
  327. var isFetalMovementExisted = await _deviceCacheMgr.FetalMovementIsExistedAsync(heartRate.Serialno, fetalMovementSampleTime);
  328. _logger.LogInformation($"{heartRate.Serialno} 胎动记录{isFetalMovementExisted},数据采样时间:{fetalMovementSampleTime}|{midNight.AddHours(2 * i).ToString("yyyy-MM-dd HH:mm:ss")}, 周期:{statStartTime}-{statEndTime} 开始");
  329. if (!isFetalMovementExisted)
  330. {
  331. /// 开始计算
  332. var phrRange = phr.Where(i => i.LastUpdate >= statStartTime && i.LastUpdate<= statEndTime)
  333. .OrderByDescending(i => i.LastUpdate)
  334. .Select(i => i.LastUpdate)
  335. .ToList();
  336. // 判断是否有持续佩戴
  337. if (phrRange.Count >= 2)
  338. {
  339. var duringMins = Math.Abs((phrRange.First()- statStartTime).TotalMinutes);
  340. //在餐后时间段(8:00~10:00,12:00~14:00,18:00~20:00,22:00~24:00)取中间值。其他时间段取正常起始值
  341. bool isInTimeRanges = IsLastUpdateInTimeRanges(fetalMovementNow);
  342. int pregnancyWeeks = (DateTime.Now - edoc.AddDays(-280)).Days / 7;
  343. if (pregnancyWeeks >= 12 && pregnancyWeeks <= 50)
  344. {
  345. var fetalMovementMap = _mgrFetalMovementNormalValueRangeCache.GetFetalMovements();
  346. var fetalMovementMapValue = isInTimeRanges ? fetalMovementMap
  347. .Where(i =>
  348. i.PregnancyPeriod![0] <= pregnancyWeeks &&
  349. i.PregnancyPeriod[1] >= pregnancyWeeks)
  350. .Select(i => i.MedianMovement)
  351. .FirstOrDefault()
  352. :
  353. fetalMovementMap
  354. .Where(i =>
  355. i.PregnancyPeriod![0] <= pregnancyWeeks &&
  356. i.PregnancyPeriod[1] >= pregnancyWeeks)
  357. .Select(i => i.InitialMovement)
  358. .FirstOrDefault()
  359. ;
  360. var fetalMovementValue = (fetalMovementMapValue * duringMins * 2) / 120;
  361. // 四舍五入
  362. var fetalMovement = (int)Math.Round(fetalMovementValue, 0, MidpointRounding.AwayFromZero);
  363. // _logger.LogInformation($"{heartRate.Serialno} segmentCountFMIndex: {i} -- fetalMovementSampleTime:{fetalMovementSampleTime}|{midNight.AddHours(2 * i).ToString("yyyy-MM-dd HH:mm:ss")} -- statStartTime: {statStartTime} -- statEndTime: {statEndTime}-- isFetalMovementExisted: {isFetalMovementExisted} ");
  364. _logger.LogInformation($"{heartRate.Serialno} 胎动数据采样时间:{fetalMovementSampleTime}|{midNight.AddHours(2 * i).ToString("yyyy-MM-dd HH:mm:ss")}, 采样周期:{statStartTime}-{statEndTime}, 原始胎动值:{fetalMovementMapValue}, 佩戴时间 :{duringMins}|{statStartTime}-{phrRange.First()}, 胎动计算值:{fetalMovementValue}, 胎动最终值:{fetalMovement} 已完成.");
  365. // 获取胎心数据状态与胎动数据状态一致
  366. var feltalMovementIsAbnormal = fetalHeartRateIsAbnormal;
  367. //var feltalMovementIsAbnormal = 0;
  368. // 推送到api/v1/open/OpenIot/SetFetalMovementConfig
  369. await _serviceIotApi.SetFetalMovementConfig(heartRate.Serialno, fetalMovement, fetalMovementSampleTime, feltalMovementIsAbnormal);
  370. // 保存到MySQL数据库
  371. HisGpsFetalMovement fm = new()
  372. {
  373. FetalMovementId = Guid.NewGuid().ToString("D"),
  374. PersonId = commonPHR!.PersonId,
  375. Serialno = heartRate.Serialno,
  376. CreateTime = DateTime.Now,
  377. IsAbnormal = feltalMovementIsAbnormal,
  378. FetalMovementValue = fetalMovement,
  379. SampleTime = fetalMovementSampleTime,
  380. Method = 1,
  381. IsDisplay = 1,
  382. DeviceKey = commonPHR!.DeviceKey
  383. };
  384. await _hisFetalMovementApiClient.AddAsync(fm).ConfigureAwait(false);
  385. // 发送到微信
  386. if (feltalMovementIsAbnormal != 0)
  387. {
  388. var device = await _deviceCacheMgr.GetDeviceBySerialNoAsync(heartRate.Serialno).ConfigureAwait(false);
  389. var fmMsgId = $"{heartRate.Serialno}-{fetalMovementSampleTime}-{Guid.NewGuid().ToString("D")[^3..]}";
  390. var topic = "topic.push.wx";
  391. var fmMsg = new
  392. {
  393. messageId = Guid.NewGuid().ToString("D"),
  394. topic = topic,
  395. time = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(long.Parse(fetalMovementSampleTime.Length < 13 ? fetalMovementSampleTime.PadRight(13, '0') : fetalMovementSampleTime)).ToString("yyyy-MM-dd HH:mm:ss"),
  396. data = new
  397. {
  398. deviceId = device?.DeviceId,
  399. imei = heartRate.Serialno,
  400. alarmTypeId = 12,
  401. alarmDeviceName = heartRate.Serialno,
  402. alarmRemarks = JsonConvert.SerializeObject(new { fetalMovementValue = fetalMovement, isAbnormal = feltalMovementIsAbnormal }),
  403. address = string.Empty,
  404. deviceKey = device?.DeviceId
  405. }
  406. };
  407. await _serviceMqProcess.ProcessIMEIEventMessageAsync(fmMsgId, topic, fmMsg).ConfigureAwait(false);
  408. }
  409. // 设置入库缓存记录
  410. await _deviceCacheMgr.SetFetalMovementAsync(heartRate.Serialno, fetalMovementSampleTime,fm);
  411. }
  412. else
  413. {
  414. _logger.LogWarning($"{heartRate.Serialno} 孕周 {pregnancyWeeks},超出胎动计算范围");
  415. }
  416. }
  417. else
  418. {
  419. _logger.LogInformation($"{heartRate.Serialno} 胎动记录{isFetalMovementExisted},数据采样时间:{fetalMovementSampleTime}|{midNight.AddHours(2 * i).ToString("yyyy-MM-dd HH:mm:ss")}, 周期:{statStartTime}-{statEndTime} 不足两条,不能判断是否持续佩戴");
  420. }
  421. }
  422. else
  423. {
  424. _logger.LogInformation($"{heartRate.Serialno} 胎动记录{isFetalMovementExisted},数据采样时间:{fetalMovementSampleTime}|{midNight.AddHours(2 * i).ToString("yyyy-MM-dd HH:mm:ss")}, 周期:{statStartTime}-{statEndTime} 已处理");
  425. }
  426. }
  427. */
  428. #endregion
  429. #endregion
  430. #region 定时计算胎动数据触发器两小时间隔开始
  431. var fetalMovementKey = $"health_monitor/schedule_push/cal_fetal_movement/imei/{heartRate.Serialno}";
  432. ///// 计算 0 点秒数
  433. var fetalMovementLastUpdate = (DateTime)heartRate.LastUpdate!;
  434. DateTime fmScheduleNow = DateTime.Now;
  435. // 小于两小时
  436. if (fmScheduleNow > fetalMovementLastUpdate && (fmScheduleNow - fetalMovementLastUpdate).TotalHours <= 2)
  437. {
  438. var rand = new Random();
  439. var pushSec = rand.Next(59);
  440. int pushMin = int.TryParse(heartRate.Serialno.AsSpan(heartRate.Serialno.Length - 1), out pushMin) ? pushMin : 10;
  441. var scheduleHourDiff = SCHEDULE_HOUR
  442. .Where(h => h > fetalMovementLastUpdate.Hour)
  443. .OrderBy(h => h - fetalMovementLastUpdate.Hour)
  444. .FirstOrDefault() - fetalMovementLastUpdate.Hour;
  445. var scheduleTime = fetalMovementLastUpdate.AddHours(scheduleHourDiff);
  446. DateTime nextRunTime = new(scheduleTime.Year, scheduleTime.Month, scheduleTime.Day, scheduleTime.Hour, pushMin, pushSec);
  447. TimeSpan timeUntilNextRun = nextRunTime - fmScheduleNow;
  448. var ttl = (long)timeUntilNextRun.TotalSeconds;
  449. await SetIntervalTriggerAsync(fetalMovementKey, heartRate.Serialno, ttl);
  450. }
  451. #endregion
  452. #region 定时计算胎心数据触发器 {interval} 秒后
  453. //var fetalKey = $"health_monitor/schedule_push/cal_fetal_heart_rate/imei/{heartRate.Serialno}";
  454. //await SetIntervalTriggerAsync(fetalKey, heartRate.Serialno, 60 * 15);
  455. #endregion
  456. #region 定时计算胎心数据触发器下一间隔后
  457. //// 获取当前时间
  458. //DateTime nowInterval = DateTime.Now;
  459. //// 计算下一个15分钟的间隔
  460. //int minutesToAdd = 15 - (nowInterval.Minute % 15);
  461. //if (minutesToAdd == 15)
  462. //{
  463. // minutesToAdd = 0; // 如果已经是间隔,则不需要增加分钟
  464. //}
  465. //// 计算下一间隔的时间
  466. //DateTime nextInterval = nowInterval.AddMinutes(minutesToAdd)
  467. // .AddSeconds(-nowInterval.Second)
  468. // .AddMilliseconds(-nowInterval.Millisecond);
  469. //// 计算时间差
  470. //TimeSpan timeDifference = nextInterval - nowInterval;
  471. //var fetalKey = $"health_monitor/schedule_push/cal_fetal_heart_rate/imei/{heartRate.Serialno}";
  472. //await SetIntervalTriggerAsync(fetalKey, heartRate.Serialno, (long)timeDifference.TotalSeconds);
  473. #endregion
  474. #region 高频心率计算
  475. //// 获取最近的两个记录,并计算它们的 LastUpdate 时间差
  476. //var firstTwoPhr = phr.OrderByDescending(i => i.LastUpdate).Take(2).Select(i => i.LastUpdate).ToList();
  477. //var timeDiff = firstTwoPhr[0] - firstTwoPhr[1];
  478. //// 如果需要,将时间差转换为秒
  479. //var timeDiffInSeconds = timeDiff.TotalSeconds;
  480. //// 高频心率启动
  481. //if (timeDiffInSeconds<=highFreqSampleInterval)
  482. //{
  483. // var phrFreqstatus =await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  484. // if (phrFreqstatus == null)
  485. // {
  486. // /// 设置高频状态
  487. // _logger.LogInformation($"进入高频心率启动状态 timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval}");
  488. // // 设置高频状态
  489. // var freqFirstPhr= phr.OrderByDescending(i => i.Timestamp).First();
  490. // await _deviceCacheMgr.SetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno, freqFirstPhr);
  491. // phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  492. // }
  493. // /// phr PregnancyHeartRate 连续连续正常次数个值都是正常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig),
  494. // /// 取连续正常次数正常值的平均值,推送到api/v1/open/OpenIot/SetFetalHeartRateConfig
  495. // #region 检查是否连续12个值都是正常的
  496. // // 获取最近连续正常次数个心率记录
  497. // var lastPhr = phr.OrderByDescending(i => i.Timestamp).Take(stopHighFreqSampleCount).ToList();
  498. // // 检查是否连续12个值都是正常的
  499. // if (lastPhr.All(i => i.PregnancyHeartRate >= triggerHighFreqLow && i.PregnancyHeartRate <= triggerHighFreqHigh))
  500. // {
  501. // var avgPhr = lastPhr.Select(i => i.PregnancyHeartRate).Average();
  502. // // 计算一般心率得到胎心系数
  503. // await SaveAndPushFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, avgPhr);
  504. // }
  505. // #endregion
  506. //}
  507. //// 高频心率结束
  508. //else
  509. //{
  510. // var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  511. // if (phrFreqstatus != null)
  512. // {
  513. // /// 在highFreqSampleTimes=0一直异常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig),
  514. // /// 取所有值的平均值,推送胎心数据到api/v1/open/OpenIot/SetFetalHeartRateConfig
  515. // if (highFreqSampleTimes==0)
  516. // {
  517. // // if (phr.OrderByDescending(i => i.Timestamp)
  518. // //.Where(i => i.Timestamp >= phrFreqstatus?.Timestamp)
  519. // //.Skip(1) // 去除首条
  520. // //.All(i => i.PregnancyHeartRate < triggerHighFreqLow || i.PregnancyHeartRate > triggerHighFreqHig))
  521. // // {
  522. // // var avgPhr = phr.Select(i => i.PregnancyHeartRate).Average();
  523. // // // 推送胎心数据到 api/v1/open/OpenIot/SetFetalHeartRateConfig
  524. // // // 计算一般心率得到胎心系数
  525. // // }
  526. // var avgPhr = phr.OrderByDescending(i => i.Timestamp)
  527. // .Where(i => i.Timestamp >= phrFreqstatus?.Timestamp)
  528. // .Skip(1) // 去除首条
  529. // .Where(i => i.PregnancyHeartRate < triggerHighFreqLow || i.PregnancyHeartRate > triggerHighFreqHigh)
  530. // .Select(i => i.PregnancyHeartRate).Average();
  531. // // 推送胎心数据到 api/v1/open/OpenIot/SetFetalHeartRateConfig
  532. // // 计算一般心率得到胎心系数
  533. // await SaveAndPushFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, avgPhr);
  534. // }
  535. // /// 在highFreqSampleTimes>0一直异常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig),
  536. // /// 取所有值的平均值,推送胎心数据到api/v1/open/OpenIot/SetFetalHeartRateConfig
  537. // if (highFreqSampleTimes > 0 && heartRate.LastUpdate >= (phrFreqstatus?.LastUpdate + TimeSpan.FromSeconds(highFreqSampleTimes)))
  538. // {
  539. // var avgPhr = phr
  540. // .Where(i => i.Timestamp >= phrFreqstatus?.Timestamp)
  541. // .Skip(1) // 去除首条
  542. // .Where(i => i.PregnancyHeartRate < triggerHighFreqLow || i.PregnancyHeartRate > triggerHighFreqHigh)
  543. // .Select(i => i.PregnancyHeartRate).Average();
  544. // // 推送胎心数据到 api/v1/open/OpenIot/SetFetalHeartRateConfig
  545. // // 计算一般心率得到胎心系数
  546. // await SaveAndPushFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, avgPhr);
  547. // }
  548. // // 删除高频状态的首条记录
  549. // await _deviceCacheMgr.DelPregnancyHeartRateFreqStatusAsync(heartRate.Serialno);
  550. // /// 设置高频状态
  551. // _logger.LogInformation($"结束高频心率状态 timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval}");
  552. // }
  553. //}
  554. #endregion
  555. }
  556. else
  557. {
  558. // 建模中
  559. //var flag = await _serviceIotApi.SetFetalConfig(heartRate.Serialno);
  560. _logger.LogInformation($"{heartRate.Serialno} 记录不足30条,建模中");
  561. }
  562. #region 定时下发触发器(定时建模)
  563. var key = $"health_monitor/schedule_push/pregnancy_heart_rate/imei/{heartRate.Serialno}";
  564. var schedule_push = await _serviceEtcd.GetValAsync(key).ConfigureAwait(false);
  565. if (string.IsNullOrWhiteSpace(schedule_push))
  566. {
  567. // 注册首次下推
  568. var interval = 0;
  569. // 获取当前时间
  570. DateTime now = DateTime.Now;
  571. var rand = new Random();
  572. var pushSec = rand.Next(59);
  573. int pushMin = int.TryParse(heartRate.Serialno.AsSpan(heartRate.Serialno.Length - 1), out pushMin) ? pushMin : 10;
  574. // 计算距离下一个$interval天后的8点的时间间隔
  575. DateTime nextRunTime = new DateTime(now.Year, now.Month, now.Day, 6, pushMin, pushSec).AddDays(interval);
  576. TimeSpan timeUntilNextRun = nextRunTime - now;
  577. if (timeUntilNextRun < TimeSpan.Zero)
  578. {
  579. timeUntilNextRun = timeUntilNextRun.Add(TimeSpan.FromDays(1));
  580. nextRunTime += TimeSpan.FromDays(1);
  581. }
  582. var ttl = (long)timeUntilNextRun.TotalSeconds;
  583. var data = new
  584. {
  585. imei = heartRate.Serialno,
  586. create_time = now.ToString("yyyy-MM-dd HH:mm:ss"),
  587. ttl,
  588. next_run_time = nextRunTime.ToString("yyyy-MM-dd HH:mm:ss")
  589. };
  590. var result = JsonConvert.SerializeObject(data);
  591. await _serviceEtcd.PutValAsync(key, result, ttl, false).ConfigureAwait(false);
  592. }
  593. #endregion
  594. }
  595. }
  596. catch (Exception ex)
  597. {
  598. _logger.LogError($"{heartRate.Serialno} 处理孕妇心率数据异常 \n{ex.Message}\n{ex.StackTrace}");
  599. }
  600. }
  601. //private async Task SaveAndPushFetalHeartRateAsync(HisGpsHeartRate heartRate, int upperAlarmThreshold, int lowerAlarmThreshold, double avgPhr)
  602. //{
  603. // var commonPHR = await _serviceTDengine.InitPregnancyCommonHeartRateModeAsync(heartRate.Serialno);
  604. // if (commonPHR != null)
  605. // {
  606. // // 保存到TDengine数据库
  607. // await _serviceTDengine.InsertAsync<PregnancyCommonHeartRateModel>("hm_pchr", commonPHR);
  608. // // 计算胎心=孕妇心率*系数
  609. // var fetalHeartRate = SafeType.SafeInt(avgPhr * commonPHR?.StatModeAvgFprCoefficient!);
  610. // var sampleTime = DateTimeUtil.ConvertToTimeStamp(DateTime.Now).ToString();
  611. // var isAbnormal = fetalHeartRate > upperAlarmThreshold ? 1 : (fetalHeartRate < lowerAlarmThreshold ? 2 : 0);
  612. // // 保存到 数据服务 MySQL 数据库
  613. // HisGpsFetalHeartRate gpsFetalHeartRate = new ()
  614. // {
  615. // FetalHeartRateId = Guid.NewGuid().ToString("D"),
  616. // PersonId = commonPHR!.PersonId,
  617. // Serialno = heartRate.Serialno,
  618. // HeartRate = fetalHeartRate,
  619. // SampleTime = sampleTime.Length > 10 ? sampleTime.Substring(0, 10) : sampleTime,
  620. // IsAbnormal = isAbnormal,
  621. // StatStartTime = commonPHR.StatStartTime,
  622. // StatEndTime = commonPHR.StatEndTime,
  623. // CreateTime = DateTime.Now,
  624. // Method = 1,
  625. // IsDisplay = 1,
  626. // DeviceKey = commonPHR!.DeviceKey
  627. // };
  628. // await _hisFetalHeartApiClient.AddAsync(gpsFetalHeartRate).ConfigureAwait(false);
  629. // // 推送到api/v1/open/OpenIot/SetFetalHeartRateConfig
  630. // await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal);
  631. // // 推送到微信
  632. // var device = await _deviceCacheMgr.GetDeviceBySerialNoAsync(heartRate.Serialno).ConfigureAwait(false);
  633. // var deviceId = device?.DeviceId;
  634. // var msg = new
  635. // {
  636. // messageId = "",
  637. // topic = "topic.push.wx",
  638. // time=""
  639. // };
  640. // }
  641. //}
  642. /// <summary>
  643. ///
  644. /// </summary>
  645. /// <param name="heartRate"></param>
  646. /// <param name="commonPHR"></param>
  647. /// <param name="upperAlarmThreshold"></param>
  648. /// <param name="lowerAlarmThreshold"></param>
  649. /// <param name="phr"></param>
  650. /// <param name="sampleTime">使用高频心率的首条last_update</param>
  651. /// <param name="isFreq"></param>
  652. /// <returns></returns>
  653. private async Task SaveAndPushFreqFetalHeartRateAsync(HisGpsHeartRate heartRate, PregnancyCommonHeartRateModel commonPHR, int upperAlarmThreshold, int lowerAlarmThreshold, double phr,string sampleTime,bool isFreq = true)
  654. {
  655. // 计算胎心=孕妇心率*系数
  656. var fetalHeartRate = SafeType.SafeInt(phr * commonPHR?.StatModeAvgFprCoefficient!);
  657. var isAbnormal = fetalHeartRate > upperAlarmThreshold ? 1 : (fetalHeartRate < lowerAlarmThreshold ? 2 : 0);
  658. if (!isFreq) isAbnormal = 0;
  659. // 保存到 数据服务 MySQL 数据库
  660. HisGpsFetalHeartRate gpsFetalHeartRate = new()
  661. {
  662. FetalHeartRateId = Guid.NewGuid().ToString("D"),
  663. PersonId = commonPHR!.PersonId,
  664. Serialno = heartRate.Serialno,
  665. HeartRate = fetalHeartRate,
  666. SampleTime = sampleTime.Length > 10 ? sampleTime.Substring(0, 10) : sampleTime,
  667. IsAbnormal = isAbnormal,
  668. StatStartTime = commonPHR.StatStartTime,
  669. StatEndTime = commonPHR.StatEndTime,
  670. CreateTime = DateTime.Now,
  671. Method = 1,
  672. IsDisplay = 1,
  673. DeviceKey = commonPHR!.DeviceKey
  674. };
  675. await _hisFetalHeartApiClient.AddAsync(gpsFetalHeartRate).ConfigureAwait(false);
  676. // 推送到api/v1/open/OpenIot/SetFetalHeartRateConfig
  677. await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal);
  678. // 推送到微信
  679. if (isAbnormal != 0)
  680. {
  681. var device = await _deviceCacheMgr.GetDeviceBySerialNoAsync(heartRate.Serialno).ConfigureAwait(false);
  682. var fhrMsgId = $"{heartRate.Serialno}-{sampleTime}-{Guid.NewGuid().ToString("D")[^3..]}";
  683. var topic = "topic.push.wx";
  684. var fhrMsg = new
  685. {
  686. messageId = fhrMsgId,
  687. topic = topic,
  688. time = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(long.Parse(sampleTime.Length < 13 ? sampleTime.PadRight(13, '0') : sampleTime)).ToString("yyyy-MM-dd HH:mm:ss"),
  689. data = new
  690. {
  691. deviceId = device?.DeviceId,
  692. imei = heartRate.Serialno,
  693. alarmTypeId = 12,
  694. alarmDeviceName = heartRate.Serialno,
  695. alarmRemarks = JsonConvert.SerializeObject(new { fetalHeartValue = fetalHeartRate, isAbnormal = isAbnormal }),
  696. address = string.Empty,
  697. deviceKey = device?.DeviceId
  698. }
  699. };
  700. await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, fhrMsg).ConfigureAwait(false);
  701. }
  702. }
  703. private async Task SaveAndPushFreqFetalHeartRateAsync(HisGpsHeartRate heartRate, int upperAlarmThreshold, int lowerAlarmThreshold, double avgPhr, string sampleTime)
  704. {
  705. var commonPHR = await _serviceTDengine.InitPregnancyCommonHeartRateModeAsync(heartRate.Serialno);
  706. if (commonPHR != null)
  707. {
  708. // 保存到TDengine数据库
  709. await _serviceTDengine.InsertAsync<PregnancyCommonHeartRateModel>("hm_pchr", commonPHR);
  710. // 计算胎心=孕妇心率*系数
  711. var fetalHeartRate = SafeType.SafeInt(avgPhr * commonPHR?.StatModeAvgFprCoefficient!);
  712. var isAbnormal = fetalHeartRate > upperAlarmThreshold ? 1 : (fetalHeartRate < lowerAlarmThreshold ? 2 : 0);
  713. // 保存到 数据服务 MySQL 数据库
  714. HisGpsFetalHeartRate gpsFetalHeartRate = new()
  715. {
  716. FetalHeartRateId = Guid.NewGuid().ToString("D"),
  717. PersonId = commonPHR!.PersonId,
  718. Serialno = heartRate.Serialno,
  719. HeartRate = fetalHeartRate,
  720. SampleTime = sampleTime.Length > 10 ? sampleTime.Substring(0, 10) : sampleTime,
  721. IsAbnormal = isAbnormal,
  722. StatStartTime = commonPHR.StatStartTime,
  723. StatEndTime = commonPHR.StatEndTime,
  724. CreateTime = DateTime.Now,
  725. Method = 1,
  726. IsDisplay = 1,
  727. DeviceKey = commonPHR!.DeviceKey
  728. };
  729. await _hisFetalHeartApiClient.AddAsync(gpsFetalHeartRate).ConfigureAwait(false);
  730. // 推送到api/v1/open/OpenIot/SetFetalHeartRateConfig
  731. await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal);
  732. // 推送到微信
  733. if (isAbnormal != 0)
  734. {
  735. var device = await _deviceCacheMgr.GetDeviceBySerialNoAsync(heartRate.Serialno).ConfigureAwait(false);
  736. var fhrMsgId = $"{heartRate.Serialno}-{sampleTime}-{Guid.NewGuid().ToString("D")[^3..]}";
  737. var topic = "topic.push.wx";
  738. var fhrMsg = new
  739. {
  740. messageId = fhrMsgId,
  741. topic = topic,
  742. time = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(long.Parse(sampleTime.Length < 13 ? sampleTime.PadRight(13, '0') : sampleTime)).ToString("yyyy-MM-dd HH:mm:ss"),
  743. data = new
  744. {
  745. deviceId = device?.DeviceId,
  746. imei = heartRate.Serialno,
  747. alarmTypeId = 12,
  748. alarmDeviceName = heartRate.Serialno,
  749. alarmRemarks = JsonConvert.SerializeObject(new { fetalHeartValue = fetalHeartRate, isAbnormal = isAbnormal }),
  750. address = string.Empty,
  751. deviceKey = device?.DeviceId
  752. }
  753. };
  754. await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, fhrMsg).ConfigureAwait(false);
  755. }
  756. }
  757. }
  758. /// <summary>
  759. ///
  760. /// </summary>
  761. /// <param name="heartRate"></param>
  762. /// <param name="upperAlarmThreshold"></param>
  763. /// <param name="lowerAlarmThreshold"></param>
  764. /// <param name="avgPhr"></param>
  765. /// <param name="sampleTime"></param>
  766. /// <returns></returns>
  767. private async Task SaveAndPushFreqFetalHeartRate2Async(HisGpsHeartRate heartRate, int upperAlarmThreshold, int lowerAlarmThreshold, double avgPhr, string sampleTime)
  768. {
  769. var commonPHR = await _serviceTDengine.GetLastAsync<PregnancyCommonHeartRateModel>(heartRate.Serialno);
  770. if (commonPHR != null)
  771. {
  772. // 保存到TDengine数据库
  773. //await _serviceTDengine.InsertAsync<PregnancyCommonHeartRateModel>("hm_pchr", commonPHR);
  774. // 计算胎心=孕妇心率*系数
  775. var fetalHeartRate = SafeType.SafeInt(avgPhr * commonPHR?.StatModeAvgFprCoefficient!);
  776. var isAbnormal = fetalHeartRate > upperAlarmThreshold ? 1 : (fetalHeartRate < lowerAlarmThreshold ? 2 : 0);
  777. // 保存到 数据服务 MySQL 数据库
  778. HisGpsFetalHeartRate gpsFetalHeartRate = new()
  779. {
  780. FetalHeartRateId = Guid.NewGuid().ToString("D"),
  781. PersonId = commonPHR!.PersonId,
  782. Serialno = heartRate.Serialno,
  783. HeartRate = fetalHeartRate,
  784. SampleTime = sampleTime.Length > 10 ? sampleTime.Substring(0, 10) : sampleTime,
  785. IsAbnormal = isAbnormal,
  786. StatStartTime = commonPHR.StatStartTime,
  787. StatEndTime = commonPHR.StatEndTime,
  788. CreateTime = DateTime.Now,
  789. Method = 1,
  790. IsDisplay = 1,
  791. DeviceKey = commonPHR!.DeviceKey
  792. };
  793. await _hisFetalHeartApiClient.AddAsync(gpsFetalHeartRate).ConfigureAwait(false);
  794. // 推送到api/v1/open/OpenIot/SetFetalHeartRateConfig
  795. await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal);
  796. // 推送到微信
  797. if (isAbnormal != 0)
  798. {
  799. var device = await _deviceCacheMgr.GetDeviceBySerialNoAsync(heartRate.Serialno).ConfigureAwait(false);
  800. var fhrMsgId = $"{heartRate.Serialno}-{sampleTime}-{Guid.NewGuid().ToString("D")[^3..]}";
  801. var topic = "topic.push.wx";
  802. var fhrMsg = new
  803. {
  804. messageId = fhrMsgId,
  805. topic = topic,
  806. time = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(long.Parse(sampleTime.Length < 13 ? sampleTime.PadRight(13, '0') : sampleTime)).ToString("yyyy-MM-dd HH:mm:ss"),
  807. data = new
  808. {
  809. deviceId = device?.DeviceId,
  810. imei = heartRate.Serialno,
  811. alarmTypeId = 12,
  812. alarmDeviceName = heartRate.Serialno,
  813. alarmRemarks = JsonConvert.SerializeObject(new { fetalHeartValue = fetalHeartRate, isAbnormal = isAbnormal }),
  814. address = string.Empty,
  815. deviceKey = device?.DeviceId
  816. }
  817. };
  818. await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, fhrMsg).ConfigureAwait(false);
  819. }
  820. }
  821. }
  822. private async Task SetIntervalTriggerAsync(string key,string imei, long interval)
  823. {
  824. // var key = $"health_monitor/schedule_push/{type}/imei/{imei}";
  825. var schedulePush = await _serviceEtcd.GetValAsync(key).ConfigureAwait(false);
  826. if (string.IsNullOrWhiteSpace(schedulePush))
  827. {
  828. var now = DateTime.Now;
  829. var timeNextRun = now.Add(TimeSpan.FromSeconds(interval));
  830. var data = new
  831. {
  832. imei,
  833. create_time = now.ToString("yyyy-MM-dd HH:mm:ss"),
  834. ttl = interval,
  835. next_run_time = timeNextRun.ToString("yyyy-MM-dd HH:mm:ss")
  836. };
  837. var result = JsonConvert.SerializeObject(data);
  838. await _serviceEtcd.PutValAsync(key, result, interval, false).ConfigureAwait(false);
  839. }
  840. }
  841. public static bool IsLastUpdateInTimeRanges(DateTime lastUpdate)
  842. {
  843. var now = lastUpdate.TimeOfDay;
  844. var timeRanges = new List<(TimeSpan Start, TimeSpan End)>
  845. {
  846. // 8:00~10:00,12:00~14:00,18:00~20:00,22:00~24:00
  847. (new TimeSpan(8, 0, 0), new TimeSpan(10, 0, 0)),
  848. (new TimeSpan(12, 0, 0), new TimeSpan(14, 0, 0)),
  849. (new TimeSpan(18, 0, 0), new TimeSpan(20, 0, 0)),
  850. (new TimeSpan(22, 0, 0), new TimeSpan(24, 0, 0))
  851. };
  852. return timeRanges.Any(range => now >= range.Start && now <= range.End);
  853. }
  854. }
  855. }