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; /// 组合独立的 BMI 和室温策略,不负责发送家具控制命令。 public sealed class MattressComfortService : ITransient { private readonly IHomeAssistantGateway _gateway; private readonly IndoorEnvironmentReader _environment; private readonly HomeAssistantEntityOptions _entities; public MattressComfortService(IHomeAssistantGateway gateway, IndoorEnvironmentReader environment, IOptions options) => (_gateway, _environment, _entities) = (gateway, environment, options.Value.Entities); public async Task GetAsync(HealthProfile? profile, CancellationToken cancellationToken = default) { IReadOnlyDictionary 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(); } return Get(profile, states); } public MattressComfortOutput Get(HealthProfile? profile, IReadOnlyDictionary 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 }; }