diff --git a/HealthMonitor.Service/Resolver/PregnancyHeartRateResolver.cs b/HealthMonitor.Service/Resolver/PregnancyHeartRateResolver.cs index a2c4584..c345d88 100644 --- a/HealthMonitor.Service/Resolver/PregnancyHeartRateResolver.cs +++ b/HealthMonitor.Service/Resolver/PregnancyHeartRateResolver.cs @@ -173,8 +173,6 @@ namespace HealthMonitor.Service.Resolver // interval (分钟) 固定15分钟 var intervalFHR = 15;//int)watchConfig["interval"]!; - - var daysPhr = await _serviceTDengine.GetBySerialNoAsync(heartRate.Serialno, 7); var phr = daysPhr .Where(p => p.LastUpdate <= heartRate.LastUpdate) @@ -236,6 +234,8 @@ namespace HealthMonitor.Service.Resolver // 高频心率启动(在高频第二条才能判断) if (timeDiffInSeconds <= highFreqSampleInterval) { + // 设置延时计算高频心率的胎心 + var freqHearRateHey = $"health_monitor/schedule_push/cal_freqhr_fetal_heart_rate/imei/{heartRate.Serialno}"; var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno); if (phrFreqstatus == null) { @@ -249,8 +249,23 @@ namespace HealthMonitor.Service.Resolver await _deviceCacheMgr.SetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno, freqFirstPhr); _logger.LogInformation($"{heartRate.Serialno} 设置高频状态"); phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno); - // 通过续租 延时计算高频心率的胎心 + HisGpsHeartRate freqFirstHR = new() + { + CreateTime = freqFirstPhr.CreateTime, + DeviceKey = freqFirstPhr.DeviceKey, + HeartRate = freqFirstPhr.PregnancyHeartRate, + HeartRateId = freqFirstPhr.PregnancyHeartRateId, + IsDisplay = freqFirstPhr.IsDisplay ? 1 : 0, + LastUpdate = freqFirstPhr.LastUpdate, + MessageId = freqFirstPhr.MessageId, + Method = freqFirstPhr.Method, + PersonId = freqFirstPhr.PersonId, + Serialno = freqFirstPhr.SerialNumber, + }; + await SetFreqHeartRateTriggerAsync(freqHearRateHey, heartRate.Serialno, highFreqSampleInterval, freqFirstHR); } + // 续租延时计算高频心率的胎心 + await SetFreqHeartRateTriggerAsync(freqHearRateHey, heartRate.Serialno, highFreqSampleInterval); /// phr PregnancyHeartRate 连续连续正常次数个值都是正常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig), /// 取连续正常次数正常值的平均值,推送到api/v1/open/OpenIot/SetFetalHeartRateConfig @@ -358,7 +373,7 @@ namespace HealthMonitor.Service.Resolver } else { - _logger.LogInformation($"{heartRate.Serialno} 高频心率的数据不大于{stopHighFreqSampleCount}条,不进行高频数据的胎心计算"); + _logger.LogWarning($"{heartRate.Serialno} 高频心率的数据不大于{stopHighFreqSampleCount}条,不进行高频数据的胎心计算"); } @@ -723,6 +738,14 @@ namespace HealthMonitor.Service.Resolver } } + #endregion + + #region 高频心率计算胎心数据到iot设备 + // 高频(17) ,连续12个高频正常,也不停止且数据偏高和偏低也推送到iot + if (phrFreqstatus != null && isAbnormal!=0) + { + await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal); + } #endregion var device = await _deviceCacheMgr.GetDeviceBySerialNoAsync(heartRate.Serialno).ConfigureAwait(false); var fhrMsgId = $"{heartRate.Serialno}-{sampleTime}-{Guid.NewGuid().ToString("D")[^3..]}"; @@ -792,6 +815,36 @@ namespace HealthMonitor.Service.Resolver } } + /// + /// 高频延时计算触发器 + /// + /// + /// + /// + /// + /// + private async Task SetFreqHeartRateTriggerAsync(string key, string imei, long interval, HisGpsHeartRate? heartRate=null) + { + var schedulePush = await _serviceEtcd.GetValAsync(key).ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(schedulePush)) + { + var now = DateTime.Now; + var data = new + { + imei, + create_time = now.ToString("yyyy-MM-dd HH:mm:ss"), + lease = interval, + trigger = heartRate, + }; + var result = JsonConvert.SerializeObject(data); + await _serviceEtcd.PutValAsync(key, result, interval, false).ConfigureAwait(false); + } + else + { + await _serviceEtcd.PutValAsync(key, schedulePush, interval, false).ConfigureAwait(false); + } + + } /// /// 去除高频数据 /// diff --git a/HealthMonitor.WebApi/Worker.cs b/HealthMonitor.WebApi/Worker.cs index 7c68fb7..7219e1b 100644 --- a/HealthMonitor.WebApi/Worker.cs +++ b/HealthMonitor.WebApi/Worker.cs @@ -274,6 +274,87 @@ namespace HealthMonitor.WebApi } } // health_monitor/schedule_push/cal_fetal_heart_rate/imei/ + if (key.Contains("health_monitor/schedule_push/cal_freqhr_fetal_heart_rate/imei/")) + { + /// 延时计算高频状态的胎心数据,防止高频结束后与触发数据时间相隔过长。 + var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(imeiDel); + // 高频不停,15分钟内只下发一条 + var push =await _deviceCacheMgr.GetBizIntervalAsync(imeiDel, "SaveAndPushFetalHeartRate"); + var triggerValue = (JObject)JsonConvert.DeserializeObject(e.PrevKv.Value.ToStringUtf8())!; + var trigger = triggerValue["trigger"]?.ToString(); + // 验证高频首条是否还存在 + if (phrFreqstatus!=null && string.IsNullOrEmpty(push)) + { + if (!string.IsNullOrEmpty(trigger)) + { + var triggerHeartRate = JsonConvert.DeserializeObject(trigger); + using (_logger.BeginScope(new Dictionary { ["RequestId"] = triggerHeartRate?.MessageId! })) + { + var watchConfig = await _deviceCacheMgr.GetGpsDeviceWatchConfigCacheObjectBySerialNoAsync(imeiDel, "0067"); + _logger.LogInformation($"{imeiDel}高频结束后,延迟计算高频数据产生的胎心数据"); + var isFetalHeartEnable = watchConfig != null && (int)watchConfig["enabled"]! == 1; + if (isFetalHeartEnable) + { + // 防止超过12条连续正常,高频不停止 + if (phrFreqstatus.MessageId == triggerHeartRate?.MessageId) + { + // 告警上限阀值 + var upperAlarmThreshold = (int)watchConfig!["upperAlarmThreshold"]!; + // 告警下限阀值 + var lowerAlarmThreshold = (int)watchConfig["lowerAlarmThreshold"]!; + + // 高频心率采样间隔 highFreqSampleInterval = highFreqSampleInterval+5,增加5秒兼容(最小highFreqSampleInterval=60) + var highFreqSampleInterval = (int)watchConfig!["highFreqSampleInterval"]! >= 60 ? (int)watchConfig!["highFreqSampleInterval"]! + 5 : 60; + + //停止高频心率采样心率连续正常次数 + var stopHighFreqSampleCount = (int)watchConfig["stopHighFreqSampleCount"]!; + + var commonPHR = await _serviceTDengine.GetLastAsync(imeiDel); + + + var phr = await _serviceTDengine.GetBySerialNoAsync(imeiDel, 7); + var phrFromFreqstatus = phr.Where(i => i.LastUpdate >= phrFreqstatus.LastUpdate).ToList(); + // 高频数据 + var phrInFreqstatus = GetFreqPregnancyHeartRate(phrFromFreqstatus, highFreqSampleInterval) + .OrderByDescending(i=>i.LastUpdate).ToList(); + + if (phrInFreqstatus.Count > stopHighFreqSampleCount) + { + // 取最后 stopHighFreqSampleCount 条高频数据 + phrInFreqstatus = phrInFreqstatus + .OrderByDescending(i => i.LastUpdate) + .Take(stopHighFreqSampleCount).ToList(); // 计算最后12条 + + var avgPhr = phrInFreqstatus + .Select(i => i.PregnancyHeartRate).Average(); + + var FreqStatsEnd = phrInFreqstatus.First().LastUpdate; + + _logger.LogInformation($"延时计算,{imeiDel} 高频状态持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,统计周期:{phrFreqstatus!.LastUpdate.ToString("yyyy-MM-dd HH:mm:ss")}----{FreqStatsEnd.ToString("yyyy-MM-dd HH:mm:ss")},将下发指令"); + + await SaveAndPushFetalHeartRateAsync(triggerHeartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd); + } + else + { + _logger.LogWarning($"{imeiDel} 高频心率的数据不大于{stopHighFreqSampleCount}条,不进行高频数据的胎心计算"); + + } + // 删除高频状态的首条记录 + await _deviceCacheMgr.DelPregnancyHeartRateFreqStatusAsync(imeiDel); + } + else + { + _logger.LogWarning($"{imeiDel} 超过12条连续正常,高频不停止,暂不处理"); + } + } + else + { + _logger.LogWarning($"{imeiDel} 胎心监测功能没有启动"); + } + } + } + } + } else if (key.Contains("health_monitor/schedule_push/cal_fetal_heart_rate/imei/")) { /** @@ -1191,67 +1272,13 @@ namespace HealthMonitor.WebApi return timeRanges.Any(range => now >= range.Start && now <= range.End); } - private async Task CalculateNormalFetalHeartRateAsync(HisGpsHeartRate heartRate, int upperAlarmThreshold, int lowerAlarmThreshold, int intervalFHR, PregnancyCommonHeartRateModel? commonPHR) - { - // 上15分钟的数据 - // 获取当前时间 - DateTime nowInterval = (DateTime)heartRate.LastUpdate!; - - if (nowInterval.Second > 0) - { - nowInterval = nowInterval.AddMinutes(1); - } - // 计算last_update到上一间隔的分钟数 - int minutesToSubtract = nowInterval.Minute % intervalFHR; - - // 计算上一间隔的时间 - DateTime previousInterval = nowInterval.AddMinutes(-minutesToSubtract).AddSeconds(-nowInterval.Second).AddMilliseconds(-nowInterval.Millisecond); - - // 使用 last_update 上一刻 - var sampleTimeFHR = DateTimeUtil.ConvertToTimeStamp(previousInterval).ToString(); - - // 计算last_update到下一间隔的分钟数 - int minutesToAdd = intervalFHR - (nowInterval.Minute % intervalFHR); - if (minutesToAdd == intervalFHR) - { - minutesToAdd = 0; // 如果已经是间隔,则不需要增加分钟 - } - - // 计算下一间隔的时间 - DateTime nextInterval = nowInterval.AddMinutes(minutesToAdd) - .AddSeconds(-nowInterval.Second) - .AddMilliseconds(-nowInterval.Millisecond); - - - var daysPhr = await _serviceTDengine.GetBySerialNoAsync(heartRate.Serialno, 7); - - var normalPhrStatStartTime = nextInterval.AddMinutes(-intervalFHR); - - var normalPhrStatEndTime = nextInterval; - - _logger.LogInformation($" {heartRate.MessageId} -- {heartRate.Serialno} 计算胎心数据, 周期:{normalPhrStatStartTime}-{normalPhrStatEndTime} "); - var filteredPhr = daysPhr - // 使用 last_update 下一刻 - .Where(i => i.LastUpdate <= normalPhrStatEndTime && i.LastUpdate >= normalPhrStatStartTime) - .ToList(); - if (filteredPhr.Count == 0) - { - _logger.LogWarning($"{heartRate.MessageId} -- {heartRate.Serialno} 周期:{normalPhrStatStartTime}-{normalPhrStatEndTime} 孕妇心率数据不足,{filteredPhr.Count}条记录"); - return; - } - var phrValue = filteredPhr.Count == 1 - ? filteredPhr.First().PregnancyHeartRate - : filteredPhr.Average(i => i.PregnancyHeartRate); - - //await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR!, upperAlarmThreshold, lowerAlarmThreshold, phrValue, sampleTimeFHR); - await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR!, upperAlarmThreshold, lowerAlarmThreshold, phrValue, sampleTimeFHR, normalPhrStatStartTime, normalPhrStatEndTime); - } private async Task SaveAndPushFetalHeartRateAsync(HisGpsHeartRate heartRate, PregnancyCommonHeartRateModel commonPHR, int upperAlarmThreshold, int lowerAlarmThreshold, double phrValue, string sampleTime, DateTime statStartTime, DateTime statEndTime) { // 计算胎心=孕妇心率*系数 + /** var fetalHeartRate = SafeType.SafeInt(phrValue * commonPHR?.StatModeAvgFprCoefficient!); - //fetalHeartRate = fetalHeartRate > 220 ? 220 : fetalHeartRate; // 胎心的最大值调整为220,超过都按该值220输出 + fetalHeartRate = fetalHeartRate > 220 ? 220 : fetalHeartRate; // 胎心的最大值调整为220,超过都按该值220输出 if (fetalHeartRate >= 220) { // 先使用最小系数计算 @@ -1269,12 +1296,46 @@ namespace HealthMonitor.WebApi _logger.LogWarning($"{heartRate.Serialno} 使用所有系数都不能放映实际,建模数据可能出现异常,请检查"); } } + */ + + #region 胎心系数使用基于心率与中位数对比 + var coefficient = 0f; + // 孕妇心率少于中位数,取StatMinValueFprCoefficient + if (heartRate.HeartRate < commonPHR!.Mode) + { + coefficient = commonPHR.StatMinValueFprCoefficient!; + _logger.LogInformation($"{heartRate.Serialno} 孕妇心率少于中位数,使用最小值系数 {coefficient}"); + } + // 孕妇心率大于中位数,取StatMaxValueFprCoefficient与StatModeAvgFprCoefficient中少的那个 + else if (heartRate.HeartRate > commonPHR.Mode) + { + if (commonPHR.StatModeAvgFprCoefficient > commonPHR.StatMaxValueFprCoefficient) + { + coefficient = commonPHR.StatMaxValueFprCoefficient!; + _logger.LogInformation($"{heartRate.Serialno} 孕妇心率大于中位数,使用最大值系数 {coefficient}"); + } + else + { + coefficient = commonPHR.StatModeAvgFprCoefficient!; + _logger.LogInformation($"{heartRate.Serialno} 孕妇心率大于中位数,使用均值系数 {coefficient}"); + } + } + else + { + coefficient = commonPHR.StatModeAvgFprCoefficient; + _logger.LogInformation($"{heartRate.Serialno} 孕妇心率等于中位数,使用均值系数 {coefficient}"); + } + #endregion + + var fetalHeartRate = SafeType.SafeInt(phrValue * coefficient); + // 胎心的最大值调整为220,超过都按该值220输出 + fetalHeartRate = fetalHeartRate >= 220 ? 220 : fetalHeartRate; var isAbnormal = fetalHeartRate > upperAlarmThreshold ? 1 : (fetalHeartRate < lowerAlarmThreshold ? 2 : 0); var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno); if (phrFreqstatus == null) isAbnormal = 0; - var statsusDesc = (phrFreqstatus == null) ? $"MSG ID: {heartRate.MessageId} 常规" : "高频"; - _logger.LogInformation($"{heartRate.Serialno} 在 {statsusDesc} 状态,心率值{heartRate.HeartRate},生成胎心值:{fetalHeartRate},统计周期:{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}----{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")}"); + var statsusDesc = (phrFreqstatus == null) ? "常规" : "高频"; + _logger.LogInformation($"{heartRate.Serialno} 在 {statsusDesc} 状态,生成胎心值:{fetalHeartRate},统计周期:{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}----{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")}"); //if (!isFreq) //{ // statStartTime = heartRate.LastUpdate; @@ -1300,13 +1361,63 @@ namespace HealthMonitor.WebApi // 推送到api/v1/open/OpenIot/SetFetalHeartRateConfig // 推送最后一条常规心率计算的胎心数据到iot设备 + #region 推送最后一条常规心率计算的胎心数据到iot设备 + + // 高频(<=12)-常规 var lastPhr = await _serviceTDengine.GetLastAsync(heartRate.Serialno); - if (lastPhr.MessageId== heartRate.MessageId) + if (lastPhr.MessageId == heartRate.MessageId && phrFreqstatus == null) { await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal); - _logger.LogInformation($"{heartRate.Serialno} 推送最后一条常规心率计算的胎心数据到iot设备"); + _logger.LogInformation($"{heartRate.Serialno} 推送最后一条常规心率计算的胎心数据到iot设备,高频(<=12)-常规"); + } + // 高频(13)-常规-高频(13) + if (phrFreqstatus != null) + { + var phr = await _serviceTDengine.GetBySerialNoAsync(heartRate.Serialno, 1); + phr = phr.OrderByDescending(i => i.LastUpdate).ToList(); + // 获取高频数据 + var freqCollection = new List(); + PregnancyHeartRateModel? previousItem = null; + foreach (var item in phr) + { + if (previousItem != null) + { + var timeNextDiff = (previousItem!.LastUpdate - item.LastUpdate).TotalSeconds; + if (timeNextDiff <= 60) + { + freqCollection.Add(item); + } + } + // 高频最后一条 + if (lastPhr.MessageId == item.MessageId) + { + freqCollection.Add(item); + } + + previousItem = item; + } + //去除高频 + foreach (var item in freqCollection) + { + phr.Remove(item); + } + lastPhr = phr.FirstOrDefault(); + if (lastPhr?.MessageId == heartRate.MessageId) + { + await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal); + _logger.LogInformation($"{heartRate.Serialno} 推送最后一条常规心率计算的胎心数据到iot设备,高频(13)-常规-高频(13)"); + } } + #endregion + + #region 高频心率计算胎心数据到iot设备 + // 高频(17) ,连续12个高频正常,也不停止且数据偏高和偏低也推送到iot + if (phrFreqstatus != null && isAbnormal != 0) + { + await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal); + } + #endregion var device = await _deviceCacheMgr.GetDeviceBySerialNoAsync(heartRate.Serialno).ConfigureAwait(false); var fhrMsgId = $"{heartRate.Serialno}-{sampleTime}-{Guid.NewGuid().ToString("D")[^3..]}"; var fhrMsgTime = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(long.Parse(sampleTime.Length < 13 ? sampleTime.PadRight(13, '0') : sampleTime)).ToString("yyyy-MM-dd HH:mm:ss"); @@ -1353,5 +1464,73 @@ namespace HealthMonitor.WebApi } } + + /// + /// 去除高频数据 + /// + /// + /// + /// + private static List GetNonFreqPregnancyHeartRate(List phr, int highFreqSampleInterval) + { + phr = phr.OrderByDescending(i => i.LastUpdate).ToList(); + var result = new List(); + PregnancyHeartRateModel? previousItem = null; + + foreach (var item in phr) + { + if (previousItem != null) + { + var timeNextDiff = (previousItem!.LastUpdate - item.LastUpdate).TotalSeconds; + if (timeNextDiff > highFreqSampleInterval) + { + result.Add(previousItem); + } + } + previousItem = item; + } + + // 添加上一个 + if (previousItem != null) + { + result.Add(previousItem); + } + + return result; + } + + /// + /// 获取高频数据 + /// + /// + /// + /// + private static List GetFreqPregnancyHeartRate(List phr, int highFreqSampleInterval) + { + phr = phr.OrderByDescending(i => i.LastUpdate).ToList(); + var freqCollection = new List(); + PregnancyHeartRateModel? previousItem = null; + + foreach (var item in phr) + { + if (previousItem != null) + { + var timeNextDiff = (previousItem.LastUpdate - item.LastUpdate).TotalSeconds; + if (timeNextDiff <= highFreqSampleInterval) + { + freqCollection.Add(previousItem); + } + } + previousItem = item; + } + + // 检查最后一条是否高频 + if (previousItem != null && (phr.Last().LastUpdate - previousItem.LastUpdate).TotalSeconds <= highFreqSampleInterval) + { + freqCollection.Add(previousItem); + } + + return freqCollection; + } } }