去掉老床垫相关功能

This commit is contained in:
fuenmao
2026-11-09 16:10:05 +08:00
parent 2d44cc3966
commit 3ca90c4cf3
25 changed files with 227 additions and 466 deletions
@@ -0,0 +1,54 @@
using Evergreen.Health.Application.Health.Devices.Environment;
using Evergreen.Health.Application.Health.Entities;
using Evergreen.Health.Application.Health.Integrations.HomeAssistant;
using Evergreen.Health.Application.Health.Models;
using Evergreen.Health.Application.Health.Policies;
using Microsoft.Extensions.Options;
namespace Evergreen.Health.Application.Health.Services;
/// <summary>依据室温与体质提供空调建议,不负责发送家具控制命令。</summary>
public sealed class EnvironmentAdviceService : ITransient
{
private readonly IHomeAssistantGateway _gateway;
private readonly IndoorEnvironmentReader _environment;
private readonly HomeAssistantEntityOptions _entities;
public EnvironmentAdviceService(IHomeAssistantGateway gateway, IndoorEnvironmentReader environment,
IOptions<HomeAssistantOptions> options)
=> (_gateway, _environment, _entities) = (gateway, environment, options.Value.Entities);
public async Task<EnvironmentAdviceOutput> GetAsync(HealthProfile? profile, CancellationToken cancellationToken = default)
{
IReadOnlyDictionary<string, HomeAssistantState> states;
try
{
states = (await _gateway.GetStatesAsync(cancellationToken)).GroupBy(state => state.EntityId, StringComparer.OrdinalIgnoreCase)
.ToDictionary(group => group.Key, group => group.Last(), StringComparer.OrdinalIgnoreCase);
}
catch (Exception exception) when (exception is not OperationCanceledException)
{
states = new Dictionary<string, HomeAssistantState>();
}
return Get(profile, states);
}
public EnvironmentAdviceOutput Get(HealthProfile? profile, IReadOnlyDictionary<string, HomeAssistantState> states)
{
var tcm = TcmConstitutionRules.Read(profile);
var sample = _environment.Read(states);
var advice = RoomTemperaturePolicy.Evaluate(sample, RecommendedRoomRange.ForConstitution(tcm.ConstitutionCode));
advice.PendingIntegration = (advice.Mode is "heat" or "cool") && string.IsNullOrWhiteSpace(_entities.Climate);
return new()
{
IndoorTemperatureCelsius = sample.Celsius, IndoorTemperatureAvailable = sample.Available,
IndoorTemperatureObservedAt = sample.ObservedAt,
ClimateAdvice = advice
};
}
public static void ApplyTo(PersonalizedSceneConfig config, EnvironmentAdviceOutput recommendation)
{
config.ClimateAdvice = recommendation.ClimateAdvice;
}
}
@@ -24,7 +24,7 @@ public sealed class HealthAppService : IDynamicApiController, ITransient
private readonly SqlSugarRepository<HealthProfile> _profileRepository;
private readonly SqlSugarRepository<HealthModuleSelection> _moduleRepository;
private readonly SqlSugarRepository<HealthPlan> _planRepository;
private readonly MattressComfortService _mattressComfort;
private readonly EnvironmentAdviceService _environmentAdvice;
private readonly IHttpContextAccessor _httpContextAccessor;
public HealthAppService(
@@ -32,14 +32,14 @@ public sealed class HealthAppService : IDynamicApiController, ITransient
SqlSugarRepository<HealthProfile> profileRepository,
SqlSugarRepository<HealthModuleSelection> moduleRepository,
SqlSugarRepository<HealthPlan> planRepository,
MattressComfortService mattressComfort,
EnvironmentAdviceService environmentAdvice,
IHttpContextAccessor httpContextAccessor)
{
_userRepository = userRepository;
_profileRepository = profileRepository;
_moduleRepository = moduleRepository;
_planRepository = planRepository;
_mattressComfort = mattressComfort;
_environmentAdvice = environmentAdvice;
_httpContextAccessor = httpContextAccessor;
}
@@ -118,11 +118,11 @@ public sealed class HealthAppService : IDynamicApiController, ITransient
}
var tcmResult = TcmConstitutionRules.Read(profile);
MattressComfortOutput? mattressComfort = null;
EnvironmentAdviceOutput? environmentAdvice = null;
if (tcmResult.HasCompleted)
{
mattressComfort = await _mattressComfort.GetAsync(profile);
MattressComfortService.ApplyTo(tcmResult.SceneConfig, mattressComfort);
environmentAdvice = await _environmentAdvice.GetAsync(profile);
EnvironmentAdviceService.ApplyTo(tcmResult.SceneConfig, environmentAdvice);
}
return new HealthDashboardOutput
@@ -136,7 +136,7 @@ public sealed class HealthAppService : IDynamicApiController, ITransient
BmiStatus = bmiStatus,
SceneConfig = tcmResult.HasCompleted ? tcmResult.SceneConfig : null,
TcmResult = tcmResult,
MattressComfort = mattressComfort
EnvironmentAdvice = environmentAdvice
};
}
@@ -30,7 +30,7 @@ public sealed class HealthHomeAssistantService : IDynamicApiController, ITransie
private readonly HomeAssistantOptions _options;
private readonly SqlSugarRepository<HealthSceneExecution> _executionRepository;
private readonly SqlSugarRepository<HealthProfile> _profileRepository;
private readonly MattressComfortService _mattressComfort;
private readonly EnvironmentAdviceService _environmentAdvice;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly DeviceModuleRegistry _modules;
private readonly DeviceParameterController _parameters;
@@ -46,7 +46,7 @@ public sealed class HealthHomeAssistantService : IDynamicApiController, ITransie
IOptions<HomeAssistantOptions> options,
SqlSugarRepository<HealthSceneExecution> executionRepository,
SqlSugarRepository<HealthProfile> profileRepository,
MattressComfortService mattressComfort,
EnvironmentAdviceService environmentAdvice,
IHttpContextAccessor httpContextAccessor,
DeviceModuleRegistry modules,
DeviceParameterController parameters,
@@ -61,7 +61,7 @@ public sealed class HealthHomeAssistantService : IDynamicApiController, ITransie
_options = options.Value;
_executionRepository = executionRepository;
_profileRepository = profileRepository;
_mattressComfort = mattressComfort;
_environmentAdvice = environmentAdvice;
_httpContextAccessor = httpContextAccessor;
_modules = modules;
_parameters = parameters;
@@ -128,8 +128,8 @@ public sealed class HealthHomeAssistantService : IDynamicApiController, ITransie
{
constitutionCode = tcmResult.ConstitutionCode;
userConfig = tcmResult.SceneConfig;
var comfort = _mattressComfort.Get(profile, stateMap);
MattressComfortService.ApplyTo(userConfig, comfort);
var comfort = _environmentAdvice.Get(profile, stateMap);
EnvironmentAdviceService.ApplyTo(userConfig, comfort);
}
}
catch (Exception exception) when (exception is not OperationCanceledException)
@@ -340,8 +340,8 @@ public sealed class HealthHomeAssistantService : IDynamicApiController, ITransie
if (!tcmResult.HasCompleted)
throw new ScenePreparationException("请先完成中医四诊,生成体质倾向后再一键启动");
var config = tcmResult.SceneConfig;
var mattressComfort = await _mattressComfort.GetAsync(profile, token);
MattressComfortService.ApplyTo(config, mattressComfort);
var environmentAdvice = await _environmentAdvice.GetAsync(profile, token);
EnvironmentAdviceService.ApplyTo(config, environmentAdvice);
RoomTemperaturePolicy.PickTargetForSession(config.ClimateAdvice);
return new PreparedSleepScene(config, tcmResult.ConstitutionCode);
});
@@ -213,8 +213,6 @@ public static class TcmConstitutionRules
{
TargetHumidity = humidity, DehumidifierMode = "睡眠", FanLevel = "1档",
LightBrightnessPercent = 20, LightColorTemperatureKelvin = kelvin,
MattressHeatingLevel = 0, MattressLeftFirmness = 0, MattressRightFirmness = 0,
MattressMode = "智能适配",
EnableFragrance = fragrance && input.Answers["allergy"] != "often", FragranceLevel = 1,
EnableSpeakerSleepMode = true, SpeakerVolumePercent = 50
}
@@ -1,73 +0,0 @@
using Evergreen.Health.Application.Health.Devices.Environment;
using Evergreen.Health.Application.Health.Entities;
using Evergreen.Health.Application.Health.Integrations.HomeAssistant;
using Evergreen.Health.Application.Health.Models;
using Evergreen.Health.Application.Health.Policies;
using Microsoft.Extensions.Options;
namespace Evergreen.Health.Application.Health.Services;
/// <summary>组合独立的 BMI 和室温策略,不负责发送家具控制命令。</summary>
public sealed class MattressComfortService : ITransient
{
private readonly IHomeAssistantGateway _gateway;
private readonly IndoorEnvironmentReader _environment;
private readonly HomeAssistantEntityOptions _entities;
public MattressComfortService(IHomeAssistantGateway gateway, IndoorEnvironmentReader environment,
IOptions<HomeAssistantOptions> options)
=> (_gateway, _environment, _entities) = (gateway, environment, options.Value.Entities);
public async Task<MattressComfortOutput> GetAsync(HealthProfile? profile, CancellationToken cancellationToken = default)
{
IReadOnlyDictionary<string, HomeAssistantState> states;
try
{
states = (await _gateway.GetStatesAsync(cancellationToken)).GroupBy(state => state.EntityId, StringComparer.OrdinalIgnoreCase)
.ToDictionary(group => group.Key, group => group.Last(), StringComparer.OrdinalIgnoreCase);
}
catch (Exception exception) when (exception is not OperationCanceledException)
{
states = new Dictionary<string, HomeAssistantState>();
}
return Get(profile, states);
}
public MattressComfortOutput Get(HealthProfile? profile, IReadOnlyDictionary<string, HomeAssistantState> states)
{
var bmi = BmiFirmnessPolicy.Calculate(profile?.HeightCm, profile?.WeightKg);
var firmness = BmiFirmnessPolicy.Firmness(bmi);
var tcm = TcmConstitutionRules.Read(profile);
var sample = _environment.Read(states);
var advice = RoomTemperaturePolicy.Evaluate(sample, RecommendedRoomRange.ForConstitution(tcm.ConstitutionCode));
advice.PendingIntegration = (advice.Mode is "heat" or "cool") && string.IsNullOrWhiteSpace(_entities.Climate);
return new()
{
Bmi = bmi, Firmness = firmness, HeatingLevel = advice.Mode == "heat" ? 1 : 0,
IndoorTemperatureCelsius = sample.Celsius, IndoorTemperatureAvailable = sample.Available,
IndoorTemperatureObservedAt = sample.ObservedAt, HeatingReason = advice.Reason,
WeatherReason = advice.Reason, // 旧客户端兼容字段,不再读取或依赖室外天气。
ClimateAdvice = advice,
FirmnessReason = firmness is null
? "健康档案尚无有效身高和体重,暂不自动调节软硬度"
: $"按 BMI {bmi:0.0} 给出“{firmness}”演示起点,可在设备详情手动调整"
};
}
public static void ApplyTo(PersonalizedSceneConfig config, MattressComfortOutput recommendation)
{
config.MattressHeatingLevel = recommendation.HeatingLevel;
config.MattressLeftFirmness = recommendation.Firmness is null ? 0 : FirmnessLevel(recommendation.Firmness);
config.MattressRightFirmness = config.MattressLeftFirmness;
config.ClimateAdvice = recommendation.ClimateAdvice;
}
public static int FirmnessLevel(string firmness) => firmness switch
{
"最软" => 1, "很软" => 2, "稍软" => 3, "稍硬" => 4, "很硬" => 5, "最硬" => 6, _ => 0
};
public static string? FirmnessName(int level) => level switch
{
1 => "最软", 2 => "很软", 3 => "稍软", 4 => "稍硬", 5 => "很硬", 6 => "最硬", _ => null
};
}