using Etcdserverpb; using Google.Protobuf.WellKnownTypes; using HealthMonitor.Common; using HealthMonitor.Common.helper; using HealthMonitor.Model.Service.Mapper; using HealthMonitor.Service.Biz; using HealthMonitor.Service.Biz.db; using HealthMonitor.Service.Cache; using HealthMonitor.Service.Etcd; using HealthMonitor.Service.MessageQueue; using HealthMonitor.Service.Resolver.Interface; using HealthMonitor.Service.Sub; using HealthMonitor.Service.Sub.Topic.Model; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SqlSugar; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; using TelpoDataService.Util.Clients; using TelpoDataService.Util.Entities.GpsCard; using TelpoDataService.Util.Entities.GpsLocationHistory; using TelpoDataService.Util.Models; using TelpoDataService.Util.QueryObjects; namespace HealthMonitor.Service.Resolver { public class PregnancyHeartRateResolver : IResolver { private readonly ILogger _logger; private readonly TDengineService _serviceTDengine; private readonly DeviceCacheManager _deviceCacheMgr; private readonly IotApiService _serviceIotApi; private readonly AsyncLocal _messageId = new(); private readonly AsyncLocal _msgData = new(); private readonly HttpHelper _httpHelper = default!; private readonly EtcdService _serviceEtcd; private readonly GpsLocationHistoryAccessorClient _hisFetalHeartApiClient; private readonly GpsLocationHistoryAccessorClient _hisFetalMovementApiClient; private readonly FetalMovementNormalValueRangeCacheManager _mgrFetalMovementNormalValueRangeCache; private readonly MqProcessLogic _serviceMqProcess; private static int[] SCHEDULE_HOUR = new int[] {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22}; private static int[] ODD_SCHEDULE_HOUR = new int[] { 1, 3, 5, 7, 9, 11,13, 15, 17, 19, 21, 23 }; // 延时胎心计算间隔周期 private static int INTERVAL_FHR=15; public PregnancyHeartRateResolver(ILogger logger, HttpHelper httpHelper, EtcdService serviceEtcd, DeviceCacheManager deviceCacheMgr, MqProcessLogic serviceMqProcess, IotApiService iotApiService, TDengineService serviceDengine, FetalMovementNormalValueRangeCacheManager fetalMovementNormalValueRangeCacheMgr, GpsLocationHistoryAccessorClient hisFetalHeartApiClient, GpsLocationHistoryAccessorClient hisFetalMovementApiClient ) { _logger = logger; _httpHelper = httpHelper; _serviceEtcd = serviceEtcd; _serviceTDengine = serviceDengine; _deviceCacheMgr = deviceCacheMgr; _serviceIotApi = iotApiService; _serviceMqProcess = serviceMqProcess; _hisFetalHeartApiClient = hisFetalHeartApiClient; _hisFetalMovementApiClient = hisFetalMovementApiClient; _mgrFetalMovementNormalValueRangeCache = fetalMovementNormalValueRangeCacheMgr; } public void SetResolveInfo(PackageMsgModel msg) { var topicHmPregnancyHeartRate = JsonConvert.DeserializeObject(msg.DetailData.ToString()!); _messageId.Value = msg.MessageId; _msgData.Value = new HisGpsHeartRate() { HeartRateId = topicHmPregnancyHeartRate!.PregnancyHeartRateId, MessageId = topicHmPregnancyHeartRate!.MessageId, Serialno = topicHmPregnancyHeartRate!.Serialno, HeartRate = topicHmPregnancyHeartRate.PregnancyHeartRate, LastUpdate = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(SafeType.SafeInt64(topicHmPregnancyHeartRate.LastUpdate) / 1000000), CreateTime = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(SafeType.SafeInt64(topicHmPregnancyHeartRate.CreateTime) / 1000000), Method = topicHmPregnancyHeartRate!.Method, IsDisplay = topicHmPregnancyHeartRate!.IsDisplay ? 1 : 0 }; } public override string ToString() { return $"{nameof(PregnancyHeartRateResolver)}[{_messageId.Value}]"; } public async Task ExecuteMessageAsync() { var messageId = _messageId.Value; var heartRate = _msgData.Value!; try { var watchConfig = await _deviceCacheMgr.GetGpsDeviceWatchConfigCacheObjectBySerialNoAsync(heartRate.Serialno, "0067"); var isFetalHeartEnable = watchConfig != null && (int)watchConfig["enabled"]! == 1; if (isFetalHeartEnable) { //_logger.LogInformation($"{heartRate.Serialno} 计算胎心胎动启动"); #region 定时下发触发器(定时建模) var key = $"health_monitor/schedule_push/pregnancy_heart_rate/imei/{heartRate.Serialno}"; var schedule_push = await _serviceEtcd.GetValAsync(key).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(schedule_push)) { // 注册首次下推 var interval = 0; // 获取当前时间 DateTime now = DateTime.Now; var rand = new Random(); var pushSec = rand.Next(59); int pushMin = int.TryParse(heartRate.Serialno.AsSpan(heartRate.Serialno.Length - 1), out pushMin) ? pushMin : 10; // 计算距离下一个$interval天后的8点的时间间隔 DateTime nextRunTime = new DateTime(now.Year, now.Month, now.Day, 6, pushMin, pushSec).AddDays(interval); TimeSpan timeUntilNextRun = nextRunTime - now; if (timeUntilNextRun < TimeSpan.Zero) { timeUntilNextRun = timeUntilNextRun.Add(TimeSpan.FromDays(1)); nextRunTime += TimeSpan.FromDays(1); } var ttl = (long)timeUntilNextRun.TotalSeconds; var data = new { imei = heartRate.Serialno, create_time = now.ToString("yyyy-MM-dd HH:mm:ss"), ttl, next_run_time = nextRunTime.ToString("yyyy-MM-dd HH:mm:ss") }; var result = JsonConvert.SerializeObject(data); await _serviceEtcd.PutValAsync(key, result, ttl, false).ConfigureAwait(false); } #endregion //_logger.LogInformation($"{heartRate.Serialno} 触发定时建模"); // 高频心率采样间隔 highFreqSampleInterval = highFreqSampleInterval 最小highFreqSampleInterval=60) var highFreqSampleInterval = (int)watchConfig!["highFreqSampleInterval"]! >= 60 ? (int)watchConfig!["highFreqSampleInterval"]!:60; // 触发高频监测的心率上限值 var triggerHighFreqHigh = (int)watchConfig["triggerHighFreqHigh"]!; // 触发高频监测的心率下限值 var triggerHighFreqLow = (int)watchConfig["triggerHighFreqLow"]!; // 暂时处理 triggerHighFreqLow = triggerHighFreqLow > 60 && triggerHighFreqLow < 67 ? 60 : triggerHighFreqLow; //停止高频心率采样心率连续正常次数 var stopHighFreqSampleCount = (int)watchConfig["stopHighFreqSampleCount"]!; // 高频心率采集时长 0 为持续采集,非零为高频心率的采集时长 //var highFreqSampleTimes = (int)watchConfig["highFreqSampleTimes"]!; var highFreqSampleTimes = 540; // 告警上限阀值 var upperAlarmThreshold = (int)watchConfig["upperAlarmThreshold"]!; // 告警下限阀值 var lowerAlarmThreshold = (int)watchConfig["lowerAlarmThreshold"]!; // EDOC var edoc = DateTimeUtil.ToDateTime(watchConfig!["EDOC"]!.ToString()); // 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) .ToList(); _logger.LogInformation($"{heartRate.Serialno} 当前数据在TD数据库:{phr.Select(i=>i.MessageId).Contains(heartRate.MessageId)}"); var nonFreqPhr = GetNonFreqPregnancyHeartRate(phr, highFreqSampleInterval); if (nonFreqPhr.Count >= 30) { #region 定时计算胎动数据触发器两小时间隔开始(奇数小时计算) /** var fetalMovementKey = $"health_monitor/schedule_push/cal_fetal_movement/imei/{heartRate.Serialno}"; var rand = new Random(); var pushSec = rand.Next(59); int pushMin = int.TryParse(heartRate.Serialno.AsSpan(heartRate.Serialno.Length - 1), out pushMin) ? pushMin : 10; DateTime fmScheduleNow = DateTime.Now; int hour = fmScheduleNow.Hour; int selectedScheduleHour; // 使用模运算来获取相应的ODD_SCHEDULE_HOUR if (hour % 2 == 0) { selectedScheduleHour = ODD_SCHEDULE_HOUR[hour / 2 % ODD_SCHEDULE_HOUR.Length]; } else { selectedScheduleHour = ODD_SCHEDULE_HOUR[(hour + 1) / 2 % ODD_SCHEDULE_HOUR.Length]; } DateTime scheduledDateTime = new DateTime( fmScheduleNow.Year, fmScheduleNow.Month, fmScheduleNow.Day, selectedScheduleHour, pushMin, pushSec ); // 如果生成的时间在当前时间之前 // 或者 fmScheduleNow 在 23 点之后并且 selectedScheduleHour 在 1 点(即次日) if (scheduledDateTime < fmScheduleNow || (fmScheduleNow.Hour >= 23 && selectedScheduleHour <= 1)) { scheduledDateTime = scheduledDateTime.AddDays(1); } TimeSpan timeUntilNextRun = scheduledDateTime - fmScheduleNow; var ttl = (long)timeUntilNextRun.TotalSeconds; await SetIntervalTriggerAsync(fetalMovementKey, heartRate.Serialno, ttl, heartRate); _logger.LogInformation($"{heartRate.Serialno}-{heartRate.MessageId} 创建计划统计胎动时间{scheduledDateTime.ToString("yyyy-MM-dd HH:mm:ss")}"); */ #endregion #region 定时计算胎动数据触发器每小时间隔开始,延时一个小时计算 /* ///当前时间是的0~1小时返回2小时 , ///当前时间是的1~2小时返回3小时 ,如此类推, ///当前时间是的21~22小时返回23小时 ///当前时间是的23~0小时返回次日1小时 var fetalMovementKey = $"health_monitor/schedule_push/cal_fetal_movement/imei/{heartRate.Serialno}"; var rand = new Random(); var pushSec = rand.Next(59); int pushMin = int.TryParse(heartRate.Serialno.AsSpan(heartRate.Serialno.Length - 1), out pushMin) ? pushMin : 10; DateTime fmScheduleNow = DateTime.Now; int hour = fmScheduleNow.Hour; int selectedScheduleHour=DateTimeUtil.GetNextHour(fmScheduleNow.Hour); DateTime scheduledDateTime = new DateTime( fmScheduleNow.Year, fmScheduleNow.Month, fmScheduleNow.Day, selectedScheduleHour, pushMin, pushSec ); // 跨天 if (selectedScheduleHour == 1 || selectedScheduleHour == 0) { scheduledDateTime = scheduledDateTime.AddDays(1); } TimeSpan timeUntilNextRun = scheduledDateTime - fmScheduleNow; var ttl = (long)timeUntilNextRun.TotalSeconds; await SetIntervalTriggerAsync(fetalMovementKey, heartRate.Serialno, ttl, heartRate); _logger.LogInformation($"{heartRate.Serialno}-{heartRate.MessageId} 创建计划统计胎动时间{scheduledDateTime.ToString("yyyy-MM-dd HH:mm:ss")}"); */ #endregion #region 定时计算胎动数据触发器每小时间隔开始,延时一个小时计算 ///当前时间是的0~1小时返回2小时 , ///当前时间是的1~2小时返回3小时 ,如此类推, ///当前时间是的21~22小时返回23小时 ///当前时间是的23~0小时返回次日1小时 // lastUpdate默认奇数 // lastUpdate偶数小时 var fetalMovementKey = $"health_monitor/schedule_push/cal_fetal_movement/imei/{heartRate.Serialno}"; var rand = new Random(); var pushSec = rand.Next(59); int pushMin = int.TryParse(heartRate.Serialno.AsSpan(heartRate.Serialno.Length - 1), out pushMin) ? pushMin : 10; DateTime fmScheduleNow = DateTime.Now; int hour = fmScheduleNow.Hour; int selectedScheduleHour = DateTimeUtil.GetNextHour(fmScheduleNow.Hour); DateTime scheduledDateTime = DateTime.Now.Date .AddHours(selectedScheduleHour) .AddMinutes(pushMin) .AddSeconds(pushSec); // 当前时间与lastUpdate的时间差小于2小时,0~1->2,0~1的数据是1点的sample_time,2点计算 if (DateTime.Now.Subtract((DateTime)heartRate.LastUpdate!).TotalHours <= 2) { selectedScheduleHour = DateTimeUtil.GetNextHour(((DateTime)heartRate.LastUpdate).Hour); scheduledDateTime = DateTime.Now.Date .AddHours(selectedScheduleHour) .AddMinutes(pushMin) .AddSeconds(pushSec); if (((DateTime)heartRate.LastUpdate).Hour % 2 == 0) { // lastUpdate偶数小时 fetalMovementKey = $"health_monitor/schedule_push/cal_fetal_movement/imei/{heartRate.Serialno}/even_hour"; } else { // lastUpdate奇数小时 fetalMovementKey = $"health_monitor/schedule_push/cal_fetal_movement/imei/{heartRate.Serialno}/odd_hour"; } } // sample_time采集时间与计划计算时间最长是2小时,次日上报引起 else { _logger.LogWarning($"iot 上报延迟,last_update与计划计算时间超过2小时"); } // 跨天 if (selectedScheduleHour == 1 || selectedScheduleHour == 0) { scheduledDateTime = scheduledDateTime.AddDays(1); } TimeSpan timeUntilNextRun = scheduledDateTime - fmScheduleNow; var ttl = (long)timeUntilNextRun.TotalSeconds; await SetIntervalTriggerAsync(fetalMovementKey, heartRate.Serialno, ttl, heartRate); _logger.LogInformation($"{heartRate.Serialno}-{heartRate.MessageId} 创建计划统计胎动时间{scheduledDateTime.ToString("yyyy-MM-dd HH:mm:ss")}"); #endregion #region 计算胎心数据(按心率时间LastUpdate) var commonPHR = await _serviceTDengine.GetLastAsync(heartRate.Serialno); // 如果commonPHR为空或已建模已过期,进行建模和保存 if (commonPHR == null || !commonPHR.CreateTime.Date.Equals(DateTime.Now.Date)) // 获取当天 6:11 的时间点, 6:11 所有数据都已经全部建模 //if (commonPHR == null || commonPHR.CreateTime < DateTime.Today.AddHours(6).AddMinutes(11)) { // 处理孕妇业务,计算一般心率并下发 commonPHR = await _serviceTDengine.InitPregnancyCommonHeartRateModeAsync(heartRate.Serialno, highFreqSampleInterval: highFreqSampleInterval); // 建模完成 var flag = await _serviceIotApi.SetFetalConfig(heartRate.Serialno, 1, commonPHR!.MaxValue, commonPHR!.MinValue); _logger.LogInformation($"{heartRate.Serialno} 记录数量足够,建模完成"); // 保存到TDengine数据库 await _serviceTDengine.InsertAsync("hm_pchr", commonPHR!); _logger.LogInformation($"保存TDengine完成"); } // 获取最近的两个记录,并计算它们的 LastUpdate 时间差 var firstTwoPhr = phr.OrderByDescending(i => i.LastUpdate).Take(2).Select(i => i.LastUpdate).ToList(); var timeDiff = firstTwoPhr[0] - firstTwoPhr[1]; // 如果需要,将时间差转换为秒 var timeDiffInSeconds = timeDiff.TotalSeconds; // 高频统计结束时间 var FreqStatsEnd = DateTime.Now; // 高频心率启动(在高频第二条才能判断) 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) { /// 设置高频状态 _logger.LogInformation($"{heartRate.Serialno} 进入高频心率启动状态,时间差 timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval}"); // 设置高频状态 _logger.LogInformation($"{heartRate.Serialno} 前7天到当前记录数 {phr.Count},当前 {phr.First().MessageId}"); var freqFirstPhr = phr.OrderByDescending(i => i.LastUpdate) .Skip(1) //在高频第二条才能判断,所以要去除本条记录 .First(); _logger.LogInformation($"首条高频附近的3条:{string.Join(',', phr.OrderByDescending(i => i.LastUpdate).Take(3).Select(i=>i.MessageId))}"); await _deviceCacheMgr.SetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno, freqFirstPhr); _logger.LogInformation($"{heartRate.Serialno} 设置高频状态,首条高频心率ID:{freqFirstPhr.MessageId}"); 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 #region 检查高频状态是否连续12个心率值都是正常的 // 获取最近连续正常次数个心率记录 //_logger.LogInformation($"{heartRate.Serialno} 设置 stopHighFreqSampleCount {stopHighFreqSampleCount}"); //var lastPhr = phr.OrderByDescending(i => i.LastUpdate).Take(stopHighFreqSampleCount).ToList(); var lastPhr = phr.Where(i => i.LastUpdate >= phrFreqstatus!.LastUpdate) .OrderByDescending(i => i.LastUpdate).Take(stopHighFreqSampleCount).ToList(); _logger.LogInformation($"{heartRate.Serialno} 最后段记录数量:lastPhr.Count {lastPhr.Count},stopHighFreqSampleCount {stopHighFreqSampleCount}"); _logger.LogInformation($"{heartRate.Serialno} 条件1-数量 Count:{lastPhr.Count >= stopHighFreqSampleCount}"); _logger.LogInformation($"{heartRate.Serialno} 条件2-是否正常值 ALL{lastPhr.All(i => i.PregnancyHeartRate >= triggerHighFreqLow && i.PregnancyHeartRate <= triggerHighFreqHigh)}"); // 检查是否连续12个值都是正常的 if ((lastPhr.Count >= stopHighFreqSampleCount) && lastPhr.All(i => i.PregnancyHeartRate >= triggerHighFreqLow && i.PregnancyHeartRate <= triggerHighFreqHigh) ) { var avgPhr = lastPhr.Select(i => i.PregnancyHeartRate).Average(); // 计算一般心率得到胎心系数 //await SaveAndPushFreqFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(DateTime.Now).ToString()); // 高频数据不建模 FreqStatsEnd = (DateTime)heartRate.LastUpdate!; _logger.LogInformation($"{heartRate.Serialno} 高频状态已经持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,连续 {stopHighFreqSampleCount} 次采样心率正常,将下发指令"); var freqSaveAndPushFetalHeartRate = await _deviceCacheMgr.GetBizIntervalAsync(heartRate.Serialno, "SaveAndPushFetalHeartRate"); // 高频不停,15分钟内只下发一条 if (string.IsNullOrEmpty(freqSaveAndPushFetalHeartRate)) { //await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd); heartRate.HeartRate = (int)avgPhr; //await SaveAndPushFetalHeartRateEndFreqHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd); // 最后一条高频心率 var lastFreqHr = lastPhr.First(); var ts = DateTimeUtil.GetTimeDifferenceInSeconds((DateTime)lastFreqHr.LastUpdate!, phrFreqstatus!.LastUpdate); // 判断是否够highFreqSampleTimes,540s if (ts < highFreqSampleTimes) { /// 不够10分钟最近12个数据的最小值生成胎心值 _logger.LogInformation($"{heartRate.Serialno} 不够10分钟最近12个数据的最小值生成胎心值"); heartRate.HeartRate = lastPhr.Select(i=>i.PregnancyHeartRate).Min(); heartRate.LastUpdate = lastFreqHr.LastUpdate; } _logger.LogInformation($"{heartRate.Serialno} 高频数据触发连续12个值都是正常的的高频心率处理"); await SaveAndPushFetalHeartRateEndFreqHeartRateAsync(heartRate, commonPHR,highFreqSampleTimes ,upperAlarmThreshold, lowerAlarmThreshold, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd); // 删除高频状态的首条记录 await _deviceCacheMgr.DelPregnancyHeartRateFreqStatusAsync(heartRate.Serialno); // 设置15分的SaveAndPushFetalHeartRate业务间隔 await _deviceCacheMgr.SetBizIntervalAsync(heartRate.Serialno, "SaveAndPushFetalHeartRate"); _logger.LogInformation($"{heartRate.Serialno} 连续 {stopHighFreqSampleCount} 次采样心率正常,结束高频心率状态, timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval},高频状态持续{((DateTime)heartRate.LastUpdate - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,高频心率平均值:{(int)avgPhr}"); } else { _logger.LogWarning($"{heartRate.Serialno} 连续 {stopHighFreqSampleCount} 次采样心率正常,设备端应该结束高频状态,但设备端没有结束高频,平台高频15分钟内已经下发过指令"); } } else { _logger.LogInformation($"{heartRate.Serialno} 处于高频状态..."); } #endregion } // 高频心率结束或常规心率 else { var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno); // 高频心率结束 if (phrFreqstatus != null) { /// 在highFreqSampleTimes=0一直异常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig), /// 取所有值的平均值,推送胎心数据到api/v1/open/OpenIot/SetFetalHeartRateConfig if (highFreqSampleTimes == 0) { var lastPhr = phr.OrderByDescending(i => i.LastUpdate) .Where(i => i.LastUpdate >= phrFreqstatus?.LastUpdate) .Skip(1) // 去除首条 .Where(i => i.PregnancyHeartRate < triggerHighFreqLow || i.PregnancyHeartRate > triggerHighFreqHigh); //.Select(i => i.PregnancyHeartRate); //.Average(); var avgPhr = lastPhr.Select(i => i.PregnancyHeartRate).Average(); // 推送胎心数据到 api/v1/open/OpenIot/SetFetalHeartRateConfig // 计算一般心率得到胎心系数 // 高频数据不建模 FreqStatsEnd = firstTwoPhr[1]; _logger.LogInformation($"{heartRate.Serialno} 高频状态已经持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,highFreqSampleTimes={highFreqSampleTimes}秒,即将结束高频状态,高频心率平均值:{(int)avgPhr},将下发指令"); //await SaveAndPushFreqFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString()); //await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd); heartRate.HeartRate = (int)avgPhr; //await SaveAndPushFetalHeartRateEndFreqHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd); // 最后一条高频心率 var lastFreqHr = lastPhr.First(); var ts = DateTimeUtil.GetTimeDifferenceInSeconds((DateTime)lastFreqHr.LastUpdate!, phrFreqstatus!.LastUpdate); // 判断是否够highFreqSampleTimes,540s if (ts < highFreqSampleTimes) { /// 不够10分钟最近12个数据的最小值生成胎心值 _logger.LogInformation($"{heartRate.Serialno} 不够10分钟最近12个数据的最小值生成胎心值"); heartRate.HeartRate = lastPhr.Select(i => i.PregnancyHeartRate).Min(); heartRate.LastUpdate = lastFreqHr.LastUpdate; } _logger.LogInformation($"{heartRate.Serialno} 高频结束后的highFreqSampleTimes=0的高频心率处理"); await SaveAndPushFetalHeartRateEndFreqHeartRateAsync(heartRate, commonPHR, highFreqSampleTimes, upperAlarmThreshold, lowerAlarmThreshold, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd); } /// 在highFreqSampleTimes>0一直异常(大于等于triggerHighFreqLow,少于等于triggerHighFreqHig), /// 取所有值的平均值,推送胎心数据到api/v1/open/OpenIot/SetFetalHeartRateConfig if (highFreqSampleTimes > 0 && heartRate.LastUpdate >= (phrFreqstatus?.LastUpdate.AddSeconds(highFreqSampleTimes))) { // 获取高频心率数据个数 var filterPhr = phr .Where(i => i.LastUpdate >= phrFreqstatus?.LastUpdate) .Skip(1) .ToList(); _logger.LogInformation($"{heartRate.Serialno} 高频周期 {phrFreqstatus?.LastUpdate.ToString("yyyy-MM-dd HH:mm:ss")}--{firstTwoPhr[1].ToString("yyyy-MM-dd HH:mm:ss")} 产生高频心率数量 {filterPhr.Count} 条"); // 高频心率数据大于stopHighFreqSampleCount/12个才计算胎心数据 // if (filterPhr.Count > stopHighFreqSampleCount) { FreqStatsEnd = firstTwoPhr[1]; var avgPhr = filterPhr .OrderByDescending(i => i.LastUpdate) .Take(stopHighFreqSampleCount) // 计算最后12条 .Select(i => i.PregnancyHeartRate).Average(); //_logger.LogInformation($"{heartRate.Serialno} 高频状态已经持续{(FreqStatsEnd - phrFreqstatus!.LastUpdate).TotalSeconds} 秒,约定的 {highFreqSampleTimes} 秒,即将结束高频状态,高频心率平均值:{(int)avgPhr},将下发指令"); //计算高频 //await SaveAndPushFetalHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, avgPhr, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd); heartRate.HeartRate = (int)avgPhr; //await SaveAndPushFetalHeartRateEndFreqHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd); // 判断是否够highFreqSampleTimes,540s var lastPhr = filterPhr .OrderByDescending(i => i.LastUpdate) .Take(stopHighFreqSampleCount); // 最后一条高频心率 var lastFreqHr = lastPhr.First(); var ts = DateTimeUtil.GetTimeDifferenceInSeconds((DateTime)lastFreqHr.LastUpdate!, phrFreqstatus!.LastUpdate); if (ts < highFreqSampleTimes) { /// 不够10分钟最近12个数据的最小值生成胎心值 _logger.LogInformation($"{heartRate.Serialno} 不够10分钟最近12个数据的最小值生成胎心值"); heartRate.HeartRate = lastPhr.Select(i => i.PregnancyHeartRate).Min(); heartRate.LastUpdate = lastFreqHr.LastUpdate; } _logger.LogInformation($"{heartRate.Serialno} 高频结束后的在highFreqSampleTimes>0 正常心率(通常情况)触发的高频心率处理"); await SaveAndPushFetalHeartRateEndFreqHeartRateAsync(heartRate, commonPHR, highFreqSampleTimes, upperAlarmThreshold, lowerAlarmThreshold, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd); } else { _logger.LogWarning($"{heartRate.Serialno} 高频心率的数据不大于{stopHighFreqSampleCount}条,不进行高频数据的胎心计算"); } // 删除高频状态的首条记录 await _deviceCacheMgr.DelPregnancyHeartRateFreqStatusAsync(heartRate.Serialno); // (逐条计算)计算本次常规心率的胎心数据(高频结束后,实时处理) // await CalculateNormalFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, intervalFHR, commonPHR); await CalculateNormalFetalHeartRateScheduleAsync(heartRate); } ///不满足持续10分钟highFreqSampleTimes或出现时间倒叙 /// else { // 高频结束后与常规的心率时间倒叙 if ((firstTwoPhr[1] - phrFreqstatus!.LastUpdate).TotalSeconds < 0) { _logger.LogInformation($"{heartRate.Serialno} 高频结束出现时间倒叙,计算当条心率创建之前的高频心率"); #region 计算当条心率创建之前的高频心率 // 取得高频之后的所有数据 var phrFlashBack = daysPhr.Where(p => p.LastUpdate >= phrFreqstatus!.LastUpdate) .OrderByDescending(i => i.LastUpdate); // 取得高频数据 var freqCollection = phrFlashBack.ToList(); //var freqCollection = new List(); //PregnancyHeartRateModel? previousItem = null; //foreach (var item in phrFlashBack) //{ // if (previousItem != null) // { // var timeNextDiff = (previousItem!.LastUpdate - item.LastUpdate).TotalSeconds; // if (timeNextDiff <= highFreqSampleInterval) // { // freqCollection.Add(item); // } // } // previousItem = item; //} _logger.LogInformation($"{heartRate.Serialno} 高频数据个数{freqCollection.Count},触发高频开始时间{phrFreqstatus!.LastUpdate}"); if (freqCollection.Count > stopHighFreqSampleCount) { // 计算高频产生的胎心 var avgPhr = freqCollection .OrderByDescending(i => i.LastUpdate) .Take(stopHighFreqSampleCount) // 计算最后12条 .Select(i => i.PregnancyHeartRate).Average(); heartRate.HeartRate = (int)avgPhr; // await SaveAndPushFetalHeartRateEndFreqHeartRateAsync(heartRate, commonPHR, upperAlarmThreshold, lowerAlarmThreshold, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd); //_logger.LogInformation($"{heartRate.Serialno} 高频数据个数{freqCollection.Count},触发高频开始时间{phrFreqstatus!.LastUpdate},高频心率平均值:{(int)avgPhr}"); // 判断是否够highFreqSampleTimes,540s var lastPhr = freqCollection .OrderByDescending(i => i.LastUpdate) .Take(stopHighFreqSampleCount); // 最后一条高频心率 var lastFreqHr = lastPhr.First(); var ts = DateTimeUtil.GetTimeDifferenceInSeconds((DateTime)lastFreqHr.LastUpdate!, phrFreqstatus!.LastUpdate); if (ts < highFreqSampleTimes) { /// 不够10分钟最近12个数据的最小值生成胎心值 _logger.LogInformation($"{heartRate.Serialno} 不够10分钟最近12个数据的最小值生成胎心值"); heartRate.HeartRate = lastPhr.Select(i => i.PregnancyHeartRate).Min(); heartRate.LastUpdate = lastFreqHr.LastUpdate; } _logger.LogInformation($"{heartRate.Serialno} 高频结束后的时间倒序的正常心率触发的高频心率处理"); await SaveAndPushFetalHeartRateEndFreqHeartRateAsync(heartRate, commonPHR, highFreqSampleTimes, upperAlarmThreshold, lowerAlarmThreshold, DateTimeUtil.ConvertToTimeStamp(phrFreqstatus!.LastUpdate).ToString(), phrFreqstatus!.LastUpdate, FreqStatsEnd); _logger.LogInformation($"{heartRate.Serialno} 高频数据个数{freqCollection.Count},触发高频开始时间{phrFreqstatus!.LastUpdate},时间倒叙"); } else { _logger.LogInformation($"{heartRate.Serialno} 时间倒叙触发计算高频心率的数据不足{stopHighFreqSampleCount}条,不进行胎心计算"); } #endregion } else { _logger.LogInformation($"{heartRate.Serialno} 高频持续时间不足{highFreqSampleTimes},只持续{(firstTwoPhr[1] - phrFreqstatus!.LastUpdate).TotalSeconds} 秒"); } // 删除高频状态的首条记录 await _deviceCacheMgr.DelPregnancyHeartRateFreqStatusAsync(heartRate.Serialno); // (逐条计算)计算本次常规心率的胎心数据(高频结束后,实时处理) //await CalculateNormalFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, intervalFHR, commonPHR); await CalculateNormalFetalHeartRateScheduleAsync(heartRate); } // 删除高频状态的首条记录 await _deviceCacheMgr.DelPregnancyHeartRateFreqStatusAsync(heartRate.Serialno); _logger.LogInformation($"{heartRate.Serialno} 超时结束高频心率状态 timeDiffInSeconds {timeDiffInSeconds},highFreqSampleInterval:{highFreqSampleInterval},高频状态持续{(firstTwoPhr[1] - phrFreqstatus!.LastUpdate).TotalSeconds} 秒"); //// 使用延后计算 //var fhrScheduleKey = $"health_monitor/schedule_push/cal_fetal_heart_rate/imei/{heartRate.Serialno}"; //var fhrScheduleTTL = 60; //await SetIntervalTriggerAsync(fhrScheduleKey, heartRate.Serialno, fhrScheduleTTL, heartRate); } // 常规心率(本次心率可能是高频心率的首条,所以要使用延后计算胎心率) else { // 本次心率可能是高频心率的首条,所以要使用本次常规心率延后计算胎心率 //var fhrScheduleKey = $"health_monitor/schedule_push/cal_fetal_heart_rate/imei/{heartRate.Serialno}"; //var fhrScheduleTTL = 30; //await SetIntervalTriggerAsync(fhrScheduleKey, heartRate.Serialno, fhrScheduleTTL, heartRate); //_logger.LogInformation($"{heartRate.Serialno} 延时50秒,判断当前数据是否为高频首条"); // 本次心率可能是高频心率的首条,所以要使用本次常规心率延后计算胎心率 查询30秒后是否有高频缓存 Thread thread = new(async () => { try { #region 休眠highFreqSampleInterval2秒 var startTime = DateTime.Now; //var highFreqSampleInterval2 = (int)watchConfig!["highFreqSampleInterval"]!+5; var highFreqSampleInterval2 = highFreqSampleInterval; var during = TimeSpan.FromSeconds(highFreqSampleInterval2); while (true) { if (DateTime.Now - startTime > during) { break; } await Task.Delay(TimeSpan.FromSeconds(1)); } #endregion var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno); _logger.LogInformation($"phrFreqstatus==null:{phrFreqstatus == null}"); _logger.LogInformation($"phrFreqstatus.LastUpdate < heartRate.LastUpdate:{phrFreqstatus?.LastUpdate < heartRate.LastUpdate}"); if (phrFreqstatus == null || phrFreqstatus.LastUpdate < heartRate.LastUpdate) { // (逐条计算)常规心率 //await CalculateNormalFetalHeartRateAsync(heartRate, upperAlarmThreshold, lowerAlarmThreshold, intervalFHR, commonPHR); //_logger.LogInformation($"{heartRate.Serialno} 计算常规心率"); // 延时计算常规心率 await CalculateNormalFetalHeartRateScheduleAsync(heartRate); } } catch (Exception ex) { _logger.LogError($"处理延时计算异常:{ex.Message}, {ex.StackTrace}"); } }); thread.Start(); } } #endregion } else { _logger.LogInformation($"{heartRate.Serialno} 记录不足30条,建模中"); } } } catch (Exception ex) { _logger.LogError($"{heartRate.Serialno} 处理孕妇心率数据异常 \n{ex.Message}\n{ex.StackTrace}"); } } /// /// (逐条计算)常规心率计算胎心数据 /// /// /// /// /// /// /// /** 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.Serialno} 计算胎心数据, 周期:{normalPhrStatStartTime}-{normalPhrStatEndTime} "); var filteredPhr = daysPhr // 使用 last_update 下一刻 .Where(i => i.LastUpdate <= normalPhrStatEndTime && i.LastUpdate >= normalPhrStatStartTime) .ToList(); if (filteredPhr.Count == 0) { _logger.LogWarning($"{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); heartRate.HeartRate = (int)phrValue; //await SaveAndPushFetalHeartRateEndFreqHeartRateAsync(heartRate, commonPHR!, upperAlarmThreshold, lowerAlarmThreshold, sampleTimeFHR, normalPhrStatStartTime, normalPhrStatEndTime); } */ /// /// 延时计算 /// /// /// public async Task CalculateNormalFetalHeartRateScheduleAsync(HisGpsHeartRate heartRate) { var fhrScheduleKey = $"health_monitor/schedule_push/cal_normalphr_fetal_heart_rate/imei/{heartRate.Serialno}"; DateTime nowInterval = DateTime.Now; if (nowInterval.Second > 0) { nowInterval = nowInterval.AddMinutes(1); } // 需要减去的分钟 int minutesToSubtract = nowInterval.Minute % INTERVAL_FHR; var nextRunTime=nowInterval .AddMinutes(-minutesToSubtract) .AddMinutes(INTERVAL_FHR); TimeSpan timeUntilNextRun = nextRunTime - nowInterval; var ttl = (long)timeUntilNextRun.TotalSeconds; await SetIntervalTriggerAsync(fhrScheduleKey, heartRate.Serialno, ttl, heartRate); } /// /// /// /// /// /// /// /// /// /// /// /// 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输出 if (fetalHeartRate >= 220) { // 先使用最小系数计算 var statMaxValueFprCoefficient = commonPHR?.StatMaxValueFprCoefficient!; var statMinValueFprCoefficient = commonPHR?.StatMinValueFprCoefficient!; var coefficient = statMaxValueFprCoefficient < statMinValueFprCoefficient ? statMaxValueFprCoefficient : statMinValueFprCoefficient; fetalHeartRate = SafeType.SafeInt(phrValue * coefficient); if (fetalHeartRate < 220) { _logger.LogWarning($"{heartRate.Serialno} 使用极值系数 {coefficient} ,建模数据可能出现异常,请检查"); } else { fetalHeartRate = 220; _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; if (fetalHeartRate > 220) { fetalHeartRate = 220; _logger.LogWarning($"{heartRate.Serialno} 大于220,按220输出,计算因子:孕妇心率 {heartRate.HeartRate},系数 {coefficient},周期 周期:{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}----{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")}"); } // 胎心的最小值调整为90,超过都按该值90 if (fetalHeartRate < 90) { fetalHeartRate = 90; _logger.LogWarning($"{heartRate.Serialno} 小于90,按90输出,计算因子:孕妇心率 {heartRate.HeartRate},系数 {coefficient},周期 周期:{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}----{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")}"); } 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) ? "常规" : "高频"; _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; // //} // 保存到 数据服务 MySQL 数据库 HisGpsFetalHeartRate gpsFetalHeartRate = new() { FetalHeartRateId = Guid.NewGuid().ToString("D"), PersonId = commonPHR!.PersonId, Serialno = heartRate.Serialno, HeartRate = fetalHeartRate, SampleTime = sampleTime.Length > 10 ? sampleTime.Substring(0, 10) : sampleTime, IsAbnormal = isAbnormal, StatStartTime = statStartTime, StatEndTime = statEndTime,//commonPHR.StatEndTime, CreateTime = DateTime.Now, Method = 1, IsDisplay = 1, DeviceKey = commonPHR!.DeviceKey }; await _hisFetalHeartApiClient.AddAsync(gpsFetalHeartRate).ConfigureAwait(false); // 推送到api/v1/open/OpenIot/SetFetalHeartRateConfig // 推送最后一条常规心率计算的胎心数据到iot设备 #region 推送最后一条常规心率计算的胎心数据到iot设备 // 高频(<=12)-常规 var lastPhr = await _serviceTDengine.GetLastAsync(heartRate.Serialno); if (lastPhr.MessageId == heartRate.MessageId && phrFreqstatus == null) { await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal); _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"); // 胎心数据推送到第三方 var topic = "topic.push.third"; var fhrThridMsg = new { messageId = fhrMsgId, topic = topic, time = fhrMsgTime, data = new { imei = heartRate.Serialno, value = fetalHeartRate, isAbnormal, type = "fetalHeart" } }; await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, 31, fhrThridMsg).ConfigureAwait(false); // 胎心数据推送到微信 if (isAbnormal != 0) { topic = "topic.push.wx"; var fhrMsg = new { messageId = fhrMsgId, topic = topic, time = fhrMsgTime, data = new { deviceId = device?.DeviceId, imei = heartRate.Serialno, alarmTypeId = 12, alarmDeviceName = heartRate.Serialno, alarmRemarks = JsonConvert.SerializeObject(new { fetalHeartValue = fetalHeartRate, isAbnormal = isAbnormal }), address = string.Empty, deviceKey = device?.DeviceId } }; await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, fhrMsg).ConfigureAwait(false); } } private async Task SaveAndPushFetalHeartRateEndFreqHeartRateAsync(HisGpsHeartRate heartRate, PregnancyCommonHeartRateModel commonPHR, int upperAlarmThreshold, int lowerAlarmThreshold, string sampleTime, DateTime statStartTime, DateTime statEndTime) { // 计算胎心=孕妇心率*系数 /** var fetalHeartRate = SafeType.SafeInt(phrValue * commonPHR?.StatModeAvgFprCoefficient!); fetalHeartRate = fetalHeartRate > 220 ? 220 : fetalHeartRate; // 胎心的最大值调整为220,超过都按该值220输出 if (fetalHeartRate >= 220) { // 先使用最小系数计算 var statMaxValueFprCoefficient = commonPHR?.StatMaxValueFprCoefficient!; var statMinValueFprCoefficient = commonPHR?.StatMinValueFprCoefficient!; var coefficient = statMaxValueFprCoefficient < statMinValueFprCoefficient ? statMaxValueFprCoefficient : statMinValueFprCoefficient; fetalHeartRate = SafeType.SafeInt(phrValue * coefficient); if (fetalHeartRate < 220) { _logger.LogWarning($"{heartRate.Serialno} 使用极值系数 {coefficient} ,建模数据可能出现异常,请检查"); } else { fetalHeartRate = 220; _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(heartRate.HeartRate! * coefficient); // 胎心的最大值调整为220,超过都按该值220输出 // fetalHeartRate = fetalHeartRate>= 220 ? 220 : fetalHeartRate; if (fetalHeartRate > 220) { fetalHeartRate = 220; _logger.LogWarning($"{heartRate.Serialno} 大于220,按220输出,计算因子:孕妇心率 {heartRate.HeartRate},系数 {coefficient},周期 周期:{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}----{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")}"); } // 胎心的最小值调整为90,超过都按该值90 if (fetalHeartRate < 90) { fetalHeartRate = 90; _logger.LogWarning($"{heartRate.Serialno} 小于90,按90输出,计算因子:孕妇心率 {heartRate.HeartRate},系数 {coefficient},周期 周期:{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}----{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")}"); } 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) ? "常规" : "高频"; _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; // //} // 保存到 数据服务 MySQL 数据库 HisGpsFetalHeartRate gpsFetalHeartRate = new() { FetalHeartRateId = Guid.NewGuid().ToString("D"), PersonId = commonPHR!.PersonId, Serialno = heartRate.Serialno, HeartRate = fetalHeartRate, SampleTime = sampleTime.Length > 10 ? sampleTime.Substring(0, 10) : sampleTime, IsAbnormal = isAbnormal, StatStartTime = statStartTime, StatEndTime = statEndTime,//commonPHR.StatEndTime, CreateTime = DateTime.Now, Method = 1, IsDisplay = 1, DeviceKey = commonPHR!.DeviceKey }; await _hisFetalHeartApiClient.AddAsync(gpsFetalHeartRate).ConfigureAwait(false); // 推送到api/v1/open/OpenIot/SetFetalHeartRateConfig // 推送最后一条常规心率计算的胎心数据到iot设备 #region 推送最后一条常规心率计算的胎心数据到iot设备 // 高频(<=12)-常规 var lastPhr = await _serviceTDengine.GetLastAsync(heartRate.Serialno); if (lastPhr.MessageId == heartRate.MessageId && phrFreqstatus == null) { await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal); _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"); // 胎心数据推送到第三方 var topic = "topic.push.third"; var fhrThridMsg = new { messageId = fhrMsgId, topic = topic, time = fhrMsgTime, data = new { imei = heartRate.Serialno, value = fetalHeartRate, isAbnormal, type = "fetalHeart" } }; await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, 31, fhrThridMsg).ConfigureAwait(false); // 胎心数据推送到微信 if (isAbnormal != 0) { topic = "topic.push.wx"; var fhrMsg = new { messageId = fhrMsgId, topic = topic, time = fhrMsgTime, data = new { deviceId = device?.DeviceId, imei = heartRate.Serialno, alarmTypeId = 12, alarmDeviceName = heartRate.Serialno, alarmRemarks = JsonConvert.SerializeObject(new { fetalHeartValue = fetalHeartRate, isAbnormal = isAbnormal }), address = string.Empty, deviceKey = device?.DeviceId } }; await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, fhrMsg).ConfigureAwait(false); } } /// /// 高频胎心处理 /// 1. 高频数据触发连续12个值都是正常的的高频心率处理 /// 2. 高频结束后的highFreqSampleTimes=0的高频心率处理 /// 3. 高频结束后的在highFreqSampleTimes>0 正常心率触发的高频心率处理(常态) /// 4. 高频结束后的时间倒序的正常心率触发的高频心率处理 /// 5. 高频结束后计算胎心数据,防止结束后与常规心理的胎心处理过长,定时器时长highFreqSampleInterval触发的高频心率处理 /// 高频结束后超过9分钟且不在阈值内才告警 /// /// /// /// /// /// /// /// /// /// private async Task SaveAndPushFetalHeartRateEndFreqHeartRateAsync(HisGpsHeartRate heartRate, PregnancyCommonHeartRateModel commonPHR, int highFreqSampleTimes, int upperAlarmThreshold, int lowerAlarmThreshold, string sampleTime, DateTime statStartTime, DateTime statEndTime) { // 计算胎心=孕妇心率*系数 #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(heartRate.HeartRate! * coefficient); // 胎心的最大值调整为220,超过都按该值220输出 // fetalHeartRate = fetalHeartRate>= 220 ? 220 : fetalHeartRate; var phrFreqstatus = await _deviceCacheMgr.GetPregnancyHeartRateFreqStatusAsync(heartRate.Serialno); var isAbnormal = 0; #region 判断是否够highFreqSampleTimes,540s var ts = DateTimeUtil.GetTimeDifferenceInSeconds((DateTime)heartRate.LastUpdate!, phrFreqstatus!.LastUpdate); // 判断是否够highFreqSampleTimes,540s ///高频时长不足10分钟停止高频的胎心值生成说明:最近12个数据的最小值生成胎心值, ///对于小于高频下限阀值取高频下限阀值进行转换; ///对于高于高频上限阀值取高频上限阀值进行转换。并输出相关日志后续进行数据分析。 if (ts < highFreqSampleTimes) { if (fetalHeartRate > upperAlarmThreshold) { _logger.LogWarning($"{heartRate.Serialno} 高频持续不足10分钟,计算胎心值 {fetalHeartRate} 高于高频警告上限阀值{upperAlarmThreshold},最后胎心值{upperAlarmThreshold},并且不告警"); fetalHeartRate = upperAlarmThreshold; } else if (fetalHeartRate < lowerAlarmThreshold) { _logger.LogWarning($"{heartRate.Serialno} 高频持续不足10分钟,计算胎心值 {fetalHeartRate} 低于高频警告下限阀值 {lowerAlarmThreshold},最后胎心值{lowerAlarmThreshold},并且不告警"); fetalHeartRate = lowerAlarmThreshold; } else { _logger.LogWarning($"{heartRate.Serialno} 高频持续不足10分钟,在高频警告下限阀值 {lowerAlarmThreshold} 和 高频警告上限阀值:{upperAlarmThreshold}之间,最后胎心值{fetalHeartRate},并且不告警"); } } // 超过highFreqSampleTimes,540s else { if (fetalHeartRate > 220) { fetalHeartRate = 220; _logger.LogWarning($"{heartRate.Serialno} 大于220,按220输出,计算因子:孕妇心率 {heartRate.HeartRate},系数 {coefficient},周期 周期:{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}----{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")}"); } // 胎心的最小值调整为90,超过都按该值90 if (fetalHeartRate < 90) { fetalHeartRate = 90; _logger.LogWarning($"{heartRate.Serialno} 小于90,按90输出,计算因子:孕妇心率 {heartRate.HeartRate},系数 {coefficient},周期 周期:{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}----{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")}"); } isAbnormal = fetalHeartRate > upperAlarmThreshold ? 1 : (fetalHeartRate < lowerAlarmThreshold ? 2 : 0); } #endregion //if (phrFreqstatus == null) isAbnormal = 0; //var statsusDesc = (phrFreqstatus == null) ? "常规" : "高频"; var statsusDesc = "高频"; _logger.LogInformation($"{heartRate.Serialno} 在 {statsusDesc} 状态,生成胎心值:{fetalHeartRate},统计周期:{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}----{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")}"); // 保存到 数据服务 MySQL 数据库 HisGpsFetalHeartRate gpsFetalHeartRate = new() { FetalHeartRateId = Guid.NewGuid().ToString("D"), PersonId = commonPHR!.PersonId, Serialno = heartRate.Serialno, HeartRate = fetalHeartRate, SampleTime = sampleTime.Length > 10 ? sampleTime.Substring(0, 10) : sampleTime, IsAbnormal = isAbnormal, StatStartTime = statStartTime, StatEndTime = statEndTime,//commonPHR.StatEndTime, CreateTime = DateTime.Now, Method = 1, IsDisplay = 1, DeviceKey = commonPHR!.DeviceKey }; await _hisFetalHeartApiClient.AddAsync(gpsFetalHeartRate).ConfigureAwait(false); // 推送到api/v1/open/OpenIot/SetFetalHeartRateConfig // 推送最后一条常规心率计算的胎心数据到iot设备 #region 推送最后一条常规心率计算的胎心数据到iot设备 // 高频(<=12)-常规 var lastPhr = await _serviceTDengine.GetLastAsync(heartRate.Serialno); if (lastPhr.MessageId == heartRate.MessageId && phrFreqstatus == null) { await _serviceIotApi.SetFetalHeartRateConfig(heartRate.Serialno, fetalHeartRate, sampleTime, isAbnormal); _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"); // 胎心数据推送到第三方 var topic = "topic.push.third"; var fhrThridMsg = new { messageId = fhrMsgId, topic = topic, time = fhrMsgTime, data = new { imei = heartRate.Serialno, value = fetalHeartRate, isAbnormal, type = "fetalHeart" } }; await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, 31, fhrThridMsg).ConfigureAwait(false); // 胎心数据推送到微信 if (isAbnormal != 0) { topic = "topic.push.wx"; var fhrMsg = new { messageId = fhrMsgId, topic = topic, time = fhrMsgTime, data = new { deviceId = device?.DeviceId, imei = heartRate.Serialno, alarmTypeId = 12, alarmDeviceName = heartRate.Serialno, alarmRemarks = JsonConvert.SerializeObject(new { fetalHeartValue = fetalHeartRate, isAbnormal = isAbnormal }), address = string.Empty, deviceKey = device?.DeviceId } }; await _serviceMqProcess.ProcessIMEIEventMessageAsync(fhrMsgId, topic, fhrMsg).ConfigureAwait(false); } } private async Task SetIntervalTriggerAsync(string key, string imei, long interval, HisGpsHeartRate heartRate) { // var key = $"health_monitor/schedule_push/{type}/imei/{imei}"; var schedulePush = await _serviceEtcd.GetValAsync(key).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(schedulePush)) { var now = DateTime.Now; var timeNextRun = now.Add(TimeSpan.FromSeconds(interval)); var data = new { imei, create_time = now.ToString("yyyy-MM-dd HH:mm:ss"), ttl = interval, next_run_time = timeNextRun.ToString("yyyy-MM-dd HH:mm:ss"), trigger = heartRate, }; var result = JsonConvert.SerializeObject(data); await _serviceEtcd.PutValAsync(key, result, interval, false).ConfigureAwait(false); } } /// /// 高频延时计算触发器 /// /// 键 /// IMEI /// 高频采集间隔 /// 首条高频心率 /// 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); _logger.LogInformation($"{imei} 心率高频状态创建首条高频心率的触发记录,创建高频延时计算触发器"); } else { // 不断修改时长,直到最后的一条高频胎心 await _serviceEtcd.PutValAsync(key, schedulePush, interval, false).ConfigureAwait(false); _logger.LogInformation($"{imei} 心率高频状态续租"); } } /// /// 去除高频数据 /// /// /// /// 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; #region 反向 var phr1 = phr.OrderByDescending(i => i.LastUpdate).ToList(); var result = new List(); PregnancyHeartRateModel? previousItem1 = null; foreach (var item in phr1) { if (previousItem1 != null) { var timeNextDiff = (previousItem1!.LastUpdate - item.LastUpdate).TotalSeconds; if (timeNextDiff > highFreqSampleInterval) { result.Add(previousItem1); } } previousItem1 = item; } // 添加上一个 if (previousItem1 != null) { result.Add(previousItem1); } #endregion #region 正向 var phr2 = phr.OrderByDescending(i => i.LastUpdate).ToList(); ; var freqCollection = new List(); PregnancyHeartRateModel? previousItem = null; foreach (var item in phr2) { if (previousItem != null) { var timeNextDiff = (previousItem!.LastUpdate - item.LastUpdate).TotalSeconds; if (timeNextDiff <= highFreqSampleInterval) { freqCollection.Add(item); } } previousItem = item; } //去除高频 foreach (var item in freqCollection) { phr2.Remove(item); } #endregion // 交集 var commonElements = phr2.Intersect(result).ToList(); return commonElements; } /// /// 获取高频数据 /// /// /// /// 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; } } }