diff --git a/HealthMonitor.WebApi/Worker.cs b/HealthMonitor.WebApi/Worker.cs index 3f32d03..69191d1 100644 --- a/HealthMonitor.WebApi/Worker.cs +++ b/HealthMonitor.WebApi/Worker.cs @@ -999,6 +999,7 @@ namespace HealthMonitor.WebApi 22~23->0 23~0->1 */ + /* using (_logger.BeginScope(new Dictionary { ["RequestId"] = $"FM-{imeiDel}-{DateTime.Now.ToString("yyyyMMddHHmmss")}" })) { try @@ -1419,6 +1420,81 @@ namespace HealthMonitor.WebApi } + */ + #endregion + + + #region 相隔1小时胎动延时计算(实时now是2小时,计算 lastupdate 0~1范围的数据,) + /** + 0~1->2 + 1~2->3 + 2~3->4 + 3~4->5 + 4~5->6 + 5~6->7 + 7~8->9 + 8~9->10 + 9~10->11 + 10~11->12 + 11~12->13 + 12~13->14 + 14~15->16 + 15~16->17 + 16~17->18 + 17~18->19 + 18~19->20 + 19~20->21 + 20~21->22 + 21~22->23 + 22~23->0 + 23~0->1 + */ + + var triggerValue = (JObject)JsonConvert.DeserializeObject(e.PrevKv.Value.ToStringUtf8())!; + var trigger = triggerValue["trigger"]?.ToString(); + if (!string.IsNullOrEmpty(trigger)) + { + var triggerHeartRate = JsonConvert.DeserializeObject(trigger); + using (_logger.BeginScope(new Dictionary { ["RequestId"] = $"FM-{imeiDel}-{DateTime.Now.ToString("yyyyMMddHHmmss")}" })) + { + try + { + var watchConfig = await _deviceCacheMgr.GetGpsDeviceWatchConfigCacheObjectBySerialNoAsync(imeiDel, "0067"); + _logger.LogInformation($"触发胎动计算,设备配置{JsonConvert.SerializeObject(watchConfig)}"); + var isFetalHeartEnable = watchConfig != null && (int)watchConfig["enabled"]! == 1; + var highFreqSampleInterval = (int)watchConfig!["highFreqSampleInterval"]! >= 60 ? (int)watchConfig!["highFreqSampleInterval"]! : 60; + + if (isFetalHeartEnable) + { + var edoc = DateTimeUtil.ToDateTime(watchConfig!["EDOC"]!.ToString()); + // 已经建模 + var commonPHR = await _serviceTDengine.GetLastAsync(imeiDel); + if (commonPHR != null) + { + await CalculateFetalMovementIntervalAsync(triggerHeartRate!, commonPHR, edoc); + } + else + { + _logger.LogWarning($"{imeiDel} 没有胎心建模数据"); + } + + } + else + { + _logger.LogWarning($"{imeiDel} 胎心监测功能没有开启"); + } + } + catch (Exception ex) + { + + _logger.LogError($"{imeiDel} 计算胎动数据异常 {ex.Message}\n {ex.StackTrace}"); + } + + + } + + } + #endregion } @@ -2360,7 +2436,7 @@ namespace HealthMonitor.WebApi var boundaryStatStartTime = GetSampleTimeFromLastUpdate((DateTime)startPhr.LastUpdate!, INTERVAL_FHR); var boundaryStatEndTime = GetSampleTimeFromLastUpdate((DateTime)endPhr.LastUpdate!, INTERVAL_FHR).AddMinutes(INTERVAL_FHR); - _logger.LogInformation($"{heartRate.Serialno} 统计边界{boundaryStatStartTime}-{boundaryStatEndTime}"); + _logger.LogInformation($"{heartRate.Serialno} 常规胎心统计边界{boundaryStatStartTime}-{boundaryStatEndTime}"); try { @@ -2389,7 +2465,7 @@ namespace HealthMonitor.WebApi if (statEndTime > boundaryStatEndTime) { - _logger.LogInformation($"{heartRate.Serialno} 超过时间边界,迭代完成跳出循环 "); + _logger.LogInformation($"{heartRate.Serialno} 常规胎心统计超过时间边界,迭代完成跳出循环 "); break; } @@ -2590,7 +2666,7 @@ namespace HealthMonitor.WebApi } else { - _logger.LogInformation($"{heartRate.Serialno},统计周期:{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}----{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")} ,胎心已处理"); + _logger.LogInformation($"{heartRate.Serialno},统计常规胎心周期:{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}----{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")} ,常规胎心已处理"); } //await Task.Delay(TimeSpan.FromSeconds(1)); // 跳出循环 @@ -2611,7 +2687,7 @@ namespace HealthMonitor.WebApi } catch (Exception ex) { - _logger.LogError($"处理心率数据时发生错误: {ex.Message}"); + _logger.LogError($"处理常规胎心数据时发生错误: {ex.Message}"); } @@ -2633,6 +2709,467 @@ namespace HealthMonitor.WebApi //} } + + public async Task CalculateFetalMovementIntervalAsync(HisGpsHeartRate heartRate, PregnancyCommonHeartRateModel commonPHR,DateTime edoc) + { + + var daysPhr = await _serviceTDengine.GetBySerialNoAsync(heartRate.Serialno, 7); + + //var start = ""; + //var end = ""; + + DateTime fmNow = DateTime.Now; + int fmNowHour = fmNow.Hour; + DateTime startHour; + DateTime endHour; + + // 跨天 + if (fmNowHour == 1) + { + // last_update 23~0 + startHour = fmNow.Date.AddDays(-1).AddHours(((DateTime)heartRate.LastUpdate!).Hour); + endHour = fmNow.Date; + } + else + { + // last_update 0~1->2,1~2->3,2~3->4...21~22->23 + startHour = fmNow.Date.AddHours(((DateTime)heartRate.LastUpdate!).Hour); + endHour = fmNow.Date.AddHours(fmNowHour - 1); + } + + var filterPhr = daysPhr + .Where(i => i.LastUpdate >= startHour && i.LastUpdate<= endHour) + .OrderBy(i => i.LastUpdate).ToList(); + + var startPhr = filterPhr.First(); + var endPhr = filterPhr.Last(); + + + + // 数据统计边界 + var boundaryStatStartTime = startPhr.LastUpdate.Date.AddHours(startPhr.LastUpdate.Hour); + var boundaryStatEndTime = endPhr.LastUpdate.Date.AddHours(endPhr.LastUpdate.Hour + 1); + + _logger.LogInformation($"{heartRate.Serialno} 胎动统计边界{boundaryStatStartTime}-{boundaryStatEndTime}"); + + + + try + { + if (filterPhr.Count<2) + { + _logger.LogWarning($"{heartRate.Serialno} 胎动统计边界{boundaryStatStartTime}-{boundaryStatEndTime},数据少于2条,不统计"); + return; + } + + var c = 0; + while (true) + { + await Task.Delay(TimeSpan.FromSeconds(1)); + + var segmentStatStartTime = boundaryStatStartTime.AddHours(c * 1); + var segmentStatEndTime = segmentStatStartTime.AddHours(1); + c++; + + var statStartTime = segmentStatStartTime; + var statEndTime = segmentStatEndTime; + + _logger.LogInformation($"{heartRate.Serialno} 当前胎动统计周期{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}-{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")}"); + + + if (statEndTime > boundaryStatEndTime) + { + _logger.LogInformation($"{heartRate.Serialno} 常规胎心统计超过时间边界,迭代完成跳出循环 "); + break; + } + + var segmentPhr = filterPhr + .Where(i => i.LastUpdate <= statEndTime && i.LastUpdate >= statStartTime) + .ToList(); + + if (segmentPhr.Count == 0) + { + // 跳出当次迭代,进入下次迭代 + _logger.LogWarning($"{heartRate.Serialno} 胎动统计周期:{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}-{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")} 孕妇心率数据不足,{segmentPhr.Count}条记录,不处理"); + continue; + } + + _logger.LogInformation($"{heartRate.Serialno} 当前胎动统计周期{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}-{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")},对应的常规心率ID{string.Join(",", segmentPhr.Select(i => i.MessageId))}"); + + var fetalMovementSampleTime = DateTimeUtil.ConvertToTimeStamp(statStartTime).ToString()[..10]; + var isFetalMovementExisted = await _deviceCacheMgr.FetalMovementIsExistedAsync(heartRate.Serialno, fetalMovementSampleTime); + + if (!isFetalMovementExisted) + { + /// 开始计算 + var phrRange = segmentPhr.OrderByDescending(i => i.LastUpdate) + .Select(i => i.LastUpdate) + .ToList(); + if (phrRange.Count >= 2) + { + // 读取胎心数据 + GeneralParam param = new() + { + Filters = new List + { + new () + { + Key=nameof(HisGpsFetalHeartRate.Serialno), + Value=heartRate.Serialno, + ValueType=QueryValueTypeEnum.String, + Operator=QueryOperatorEnum.Equal + }, + //new () + //{ + // Key=nameof(HisGpsFetalHeartRate.SampleTime), + // Value=sampleTime, + // ValueType=QueryValueTypeEnum.String, + // Operator=QueryOperatorEnum.GreaterEqual + //}, + }, + OrderBys = new List + { + new (){ + IsDesc=true, + Key=nameof(HisGpsFetalHeartRate.SampleTime) + } + } + }; + var fetalHeartRateIsAbnormal = 0; + var fhr = await _hisFetalHeartApiClient.GetFirstAsync(param, heartRate.Serialno[^2..], null, new RequestHeader { RequestId = Guid.NewGuid().ToString("D") }); + + // 胎心数据时间与胎动时间一致 + var time = long.Parse(fhr.SampleTime.Length < 13 ? fhr.SampleTime.PadRight(13, '0') : fhr.SampleTime); + var fhrSampleTime = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(time); + // 胎心数据时间与胎动时间一致,最后一条胎心数据与胎动数据的小时差不大于2 + //if ((DateTime.Now.Hour - fhrSampleTime.Hour) <= 2) + if(true) + { + var duringMins = Math.Abs((phrRange.First() - phrRange.Last()).TotalMinutes); + //在餐后时间段(8:00~10:00,12:00~14:00,18:00~20:00,22:00~24:00)取中间值。其他时间段取正常起始值 + bool isInTimeRanges = IsLastUpdateInTimeRanges(phrRange.First()); + + int pregnancyWeeks = (DateTime.Now - edoc.AddDays(-280)).Days / 7; + if (pregnancyWeeks >= 12 && pregnancyWeeks <= 50) + { + var fetalMovementMap = _mgrFetalMovementNormalValueRangeCache.GetFetalMovements(); + + var fetalMovementMapValue = isInTimeRanges ? fetalMovementMap + .Where(i => + i.PregnancyPeriod![0] <= pregnancyWeeks && + i.PregnancyPeriod[1] >= pregnancyWeeks) + .Select(i => i.MedianMovement) + .FirstOrDefault() + : + fetalMovementMap + .Where(i => + i.PregnancyPeriod![0] <= pregnancyWeeks && + i.PregnancyPeriod[1] >= pregnancyWeeks) + .Select(i => i.InitialMovement) + .FirstOrDefault() + ; + + // var fetalMovementTimeVar = (fetalMovementMapValue * duringMins * 2) / 120; + var fetalMovementTimeVar = (fetalMovementMapValue * duringMins) / 60; + + if (duringMins < 59) + { + // 取12小时胎动总数最小值 + fetalMovementMapValue = fetalMovementMap + .Where(i => i.PregnancyPeriod![0] <= pregnancyWeeks && i.PregnancyPeriod[1] >= pregnancyWeeks) + .First() + .TwelveHourMovementRange[0]; + //取12小时胎动总数最小值的平均值 + fetalMovementTimeVar = fetalMovementMapValue / 12; + + _logger.LogWarning($"{heartRate.Serialno} 佩戴不足1小时,12小时胎动总数最小值{fetalMovementMapValue},平均值{fetalMovementTimeVar}"); + } + + + #region 生理健康与胎动的关系 + /// (步数)运动步数超过1500步则加1; + /// (体温)低烧则胎动减1,高烧胎动减2;低烧是37.3~38.5,38.6以上是高烧。 + /// (血压)血压收缩压大于160则加1。 + /// (心理)心理压力高加2,压力中加1; + + var fetalMovementStepVar = 0; + var fetalMovementTempVar = 0; + var fetalMovementBpVar = 0; + var fetalMovementPpVar = 0; + var fetalMovementFhrVar = 0; + // 步数 + if (false) + { + var step = await _personCacheMgr.GetStepPeriodicityAsync(heartRate.Serialno); + if (step != null) + { + if (DateTime.Now.Hour - ((DateTime)step?.LastUpdate!).Hour <= 2) + { + if (step.Steps > 1500) fetalMovementStepVar = 1; + } + else + { + _logger.LogWarning($"{heartRate.Serialno} 周期步数 时间无效"); + } + } + else + { + _logger.LogWarning($"{heartRate.Serialno} 周期步数无数据"); + } + + } + + // 体温 + if (true) + { + var temp = await _personCacheMgr.GetTemperaturePeriodicityAsync(heartRate.Serialno); + if (temp != null) + { + if (DateTime.Now.Hour - ((DateTime)temp?.LastUpdate!).Hour <= 2) + { + // 中烧 + if (temp.Temperature >= 37.3M && temp.Temperature <= 38.5M) + { + fetalMovementTempVar = -1; + } + // 高烧 + if (temp.Temperature >= 38.6M) + { + fetalMovementTempVar = -2; + } + } + else + { + _logger.LogWarning($"{heartRate.Serialno} 周期体温 时间无效"); + } + } + else + { + _logger.LogWarning($"{heartRate.Serialno} 周期体温无数据"); + } + + } + + // 血压 + if (true) + { + var bp = await _personCacheMgr.GetBloodPressPeriodicityAsync(heartRate.Serialno); + if (bp != null) + { + if (DateTime.Now.Hour - ((DateTime)bp?.LastUpdate!).Hour <= 2) + { + if (bp.SystolicValue > 160) + { + fetalMovementBpVar = 1; + } + } + else + { + _logger.LogWarning($"{heartRate.Serialno} 周期血压 时间无效"); + } + } + else + { + _logger.LogWarning($"{heartRate.Serialno} 周期血压无数据"); + } + + } + // 心理 + if (false) + { + //-1 不处理, + //0 正常, + //1 轻度, + //2 中度; + //3 重度; + GeneralParam psychResultParam = new() + { + Filters = new List + { + new () + { + Key=nameof(HisGpsPsychResult.Serialno), + Value=heartRate.Serialno, + ValueType=QueryValueTypeEnum.String, + Operator=QueryOperatorEnum.Equal + }, + //new () + //{ + // Key=nameof(HisGpsFetalHeartRate.SampleTime), + // Value=sampleTime, + // ValueType=QueryValueTypeEnum.String, + // Operator=QueryOperatorEnum.GreaterEqual + //}, + }, + OrderBys = new List + { + new (){ + IsDesc=true, + Key=nameof(HisGpsPsychResult.Serialno) + } + } + }; + var psych = await _hisPsychResultApiClient.GetFirstAsync(psychResultParam, heartRate.Serialno[^2..], null, new RequestHeader { RequestId = Guid.NewGuid().ToString("D") }); + if (psych != null) + { + if (psych?.StressScore == 2) + { + fetalMovementPpVar = 1; + } + + if (psych?.StressScore == 3) + { + fetalMovementPpVar = 2; + } + } + else + { + _logger.LogWarning($"{heartRate.Serialno} 周期心理压力无数据"); + } + + } + + #endregion + + #region 胎心与胎动的关系 + /// 胎心值过缓时,则胎动数量减1;胎心值过速时,则胎动也加1。 + /// 此值允许在上限值上继续增加,在下限值上继续减少,最小值为0。 + /// 告警上限阀值 + + //1表示偏高;2表示偏低 + if (true) + { + if (fhr.IsAbnormal == 2) + { + fetalMovementPpVar = -1; + } + + if (fhr.IsAbnormal == 1) + { + fetalMovementPpVar = 1; + } + } + + #endregion + + _logger.LogInformation($"{heartRate.Serialno} 时间比例胎动值:{fetalMovementTimeVar}, 步数参数变动值:{fetalMovementStepVar},体温参数变动值:{fetalMovementTempVar},血压参数变动值:{fetalMovementBpVar},心理压力参数变动值:{fetalMovementPpVar},胎心参数变动值:{fetalMovementFhrVar}"); + + + var fetalMovementValue = fetalMovementTimeVar + fetalMovementStepVar + fetalMovementTempVar + fetalMovementBpVar + fetalMovementPpVar + fetalMovementFhrVar; + + // 四舍五入 + var fetalMovement = (int)Math.Round(fetalMovementValue, 0, MidpointRounding.AwayFromZero); + + _logger.LogInformation($"{heartRate.Serialno} 孕周:{pregnancyWeeks},胎动数据采样时间:{fetalMovementSampleTime}|{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}, 采样周期:{statStartTime}-{statEndTime}, 时间比例胎动值:{fetalMovementMapValue}, 佩戴时间 :{duringMins}|{phrRange.Last()}-{phrRange.First()}, 胎动计算值:{fetalMovementTimeVar}, 胎动四舍五入最终值:{fetalMovement} 已完成."); + + // 获取胎心数据状态与胎动数据状态一致 + //etalHeartRateIsAbnormal= fhr.IsAbnormal; + var feltalMovementIsAbnormal = fetalHeartRateIsAbnormal; + + await _serviceIotApi.SetFetalMovementConfig(heartRate.Serialno, fetalMovement, fetalMovementSampleTime, feltalMovementIsAbnormal); + + // 保存到MySQL数据库 + HisGpsFetalMovement fm = new() + { + FetalMovementId = Guid.NewGuid().ToString("D"), + PersonId = commonPHR!.PersonId, + Serialno = heartRate.Serialno, + CreateTime = DateTime.Now, + IsAbnormal = feltalMovementIsAbnormal, + FetalMovementValue = fetalMovement, + SampleTime = fetalMovementSampleTime, + Method = 1, + IsDisplay = 1, + DeviceKey = commonPHR!.DeviceKey + }; + await _hisFetalMovementApiClient.AddAsync(fm).ConfigureAwait(false); + + var device = await _deviceCacheMgr.GetDeviceBySerialNoAsync(heartRate.Serialno).ConfigureAwait(false); + var fmMsgId = $"{heartRate.Serialno}-{fetalMovementSampleTime}-{Guid.NewGuid().ToString("D")[^3..]}"; + var fmMsgTime = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(long.Parse(fetalMovementSampleTime.Length < 13 ? fetalMovementSampleTime.PadRight(13, '0') : fetalMovementSampleTime)).ToString("yyyy-MM-dd HH:mm:ss"); + // 胎动数据推送到第三方 + var topic = "topic.push.third"; + var fmThridMsg = new + { + messageId = fmMsgId, + topic = topic, + time = fmMsgTime, + data = new + { + imei = heartRate.Serialno, + value = fetalMovement, + isAbnormal = feltalMovementIsAbnormal, + type = "fetalMovement" + } + }; + await _serviceMqProcess.ProcessIMEIEventMessageAsync(fmMsgId, topic, 31, fmThridMsg).ConfigureAwait(false); + + // 胎动数据推送到微信 + if (feltalMovementIsAbnormal != 0) + { + topic = "topic.push.wx"; + var fmMsg = new + { + messageId = Guid.NewGuid().ToString("D"), + topic = topic, + time = DateTimeUtil.GetDateTimeFromUnixTimeMilliseconds(long.Parse(fetalMovementSampleTime.Length < 13 ? fetalMovementSampleTime.PadRight(13, '0') : fetalMovementSampleTime)).ToString("yyyy-MM-dd HH:mm:ss"), + data = new + { + deviceId = device?.DeviceId, + imei = heartRate.Serialno, + alarmTypeId = 12, + alarmDeviceName = heartRate.Serialno, + alarmRemarks = JsonConvert.SerializeObject(new { fetalMovementValue = fetalMovement, isAbnormal = feltalMovementIsAbnormal }), + address = string.Empty, + deviceKey = device?.DeviceId + } + }; + await _serviceMqProcess.ProcessIMEIEventMessageAsync(fmMsgId, topic, fmMsg).ConfigureAwait(false); + } + // 设置入库缓存记录 + await _deviceCacheMgr.SetFetalMovementAsync(heartRate.Serialno, fetalMovementSampleTime, fm); + + + } + else + { + _logger.LogWarning($"{heartRate.Serialno} 孕周 {pregnancyWeeks},超出胎动计算范围"); + } + } + else + { + _logger.LogWarning($"{heartRate.Serialno} 最后一条胎心数据与胎动数据的小时差大于2,不计算胎动数据"); + } + } + else + { + _logger.LogInformation($"{heartRate.Serialno} 胎动记录{isFetalMovementExisted},数据采样时间:{fetalMovementSampleTime}|{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}, 周期:{statStartTime}-{statEndTime} 不足两条,不能判断是否持续佩戴"); + + } + } + else + { + _logger.LogInformation($"{heartRate.Serialno} 胎动记录{isFetalMovementExisted},数据采样时间:{fetalMovementSampleTime}|{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}, 周期:{statStartTime}-{statEndTime} 已处理"); + } + + // 跳出循环 + if (statEndTime.ToString("yyyyMMddHHmm") == boundaryStatEndTime.ToString("yyyyMMddHHmm")) + { + _logger.LogInformation($"{heartRate.Serialno} 胎动记录迭代完成跳出循环 "); + break; + } + else + { + _logger.LogInformation($"{heartRate.Serialno} 胎动周期{statStartTime.ToString("yyyy-MM-dd HH:mm:ss")}-{statEndTime.ToString("yyyy-MM-dd HH:mm:ss")}计算完成, "); + } + } + } + catch (Exception ex) + { + _logger.LogError($"处理胎动数据时发生错误: {ex.Message}"); + } + + } //private DateTime GetSampleTimeFromLastUpdate(DateTime lastUpdate,int interval) //{ // DateTime nowInterval = lastUpdate;