74 lines
3.7 KiB
C#
74 lines
3.7 KiB
C#
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
|
|
};
|
|
}
|