111
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
using Evergreen.Health.Application.Health.Entities;
|
||||
using Evergreen.Health.Application.Health.Models;
|
||||
using Furion.DatabaseAccessor;
|
||||
using Furion.FriendlyException;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using SqlSugar;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Evergreen.Health.Application.Health.Services;
|
||||
|
||||
// 演示用四诊倾向,不等同于标准量表或医学诊断。
|
||||
[ApiDescriptionSettings("健康管理", Name = "HealthTcmAssessment", Order = 52)]
|
||||
public sealed class HealthTcmAssessmentService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly SqlSugarRepository<HealthProfile> _profiles;
|
||||
private readonly IHttpContextAccessor _http;
|
||||
|
||||
public HealthTcmAssessmentService(SqlSugarRepository<HealthProfile> profiles, IHttpContextAccessor http)
|
||||
{
|
||||
_profiles = profiles;
|
||||
_http = http;
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet("/api/health/tcm-assessment")]
|
||||
public async Task<TcmAssessmentResult> GetAssessment()
|
||||
{
|
||||
var userId = CurrentUserId();
|
||||
var profile = await _profiles.GetFirstAsync(p => p.UserId == userId);
|
||||
return TcmConstitutionRules.Read(profile);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPost("/api/health/tcm-assessment")]
|
||||
[UnitOfWork]
|
||||
public async Task<TcmAssessmentResult> SaveAssessment(TcmAssessmentInput input)
|
||||
{
|
||||
var result = TcmConstitutionRules.Evaluate(input);
|
||||
var userId = CurrentUserId();
|
||||
var profile = await _profiles.GetFirstAsync(p => p.UserId == userId);
|
||||
if (profile == null)
|
||||
{
|
||||
profile = new HealthProfile { UserId = userId };
|
||||
profile.TcmAssessmentJson = JsonSerializer.Serialize(input);
|
||||
profile.TcmResultJson = JsonSerializer.Serialize(result);
|
||||
await _profiles.InsertAsync(profile);
|
||||
}
|
||||
else
|
||||
{
|
||||
profile.TcmAssessmentJson = JsonSerializer.Serialize(input);
|
||||
profile.TcmResultJson = JsonSerializer.Serialize(result);
|
||||
await _profiles.AsUpdateable(profile)
|
||||
.UpdateColumns(p => new { p.TcmAssessmentJson, p.TcmResultJson })
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private long CurrentUserId()
|
||||
{
|
||||
var claim = _http.HttpContext?.User.FindFirstValue("health_user_id");
|
||||
if (!long.TryParse(claim, out var userId))
|
||||
throw Oops.Oh("登录状态已失效").StatusCode(401);
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TcmConstitutionRules
|
||||
{
|
||||
private static readonly string[] Types =
|
||||
[
|
||||
"balanced", "qi_deficient", "yang_deficient", "yin_deficient",
|
||||
"phlegm_damp", "damp_heat", "blood_stasis", "qi_stagnation", "special"
|
||||
];
|
||||
private static readonly Dictionary<string, string> Names = new()
|
||||
{
|
||||
["balanced"] = "平和质", ["qi_deficient"] = "气虚质", ["yang_deficient"] = "阳虚质",
|
||||
["yin_deficient"] = "阴虚质", ["phlegm_damp"] = "痰湿质", ["damp_heat"] = "湿热质",
|
||||
["blood_stasis"] = "血瘀质", ["qi_stagnation"] = "气郁质", ["special"] = "特禀质"
|
||||
};
|
||||
private static readonly Dictionary<string, string[]> Choices = new()
|
||||
{
|
||||
["face"] = ["rosy", "pale", "sallow", "flushed", "dusky"],
|
||||
["body"] = ["balanced", "heavy", "thin", "abdominal"],
|
||||
["spirit"] = ["energetic", "low", "tired"],
|
||||
["voice"] = ["clear", "normal", "weak"],
|
||||
["coldHeat"] = ["neutral", "cold", "hot"],
|
||||
["sweating"] = ["normal", "easy", "little"],
|
||||
["mouth"] = ["normal", "bland", "bitter", "dry"],
|
||||
["stool"] = ["normal", "dry", "loose", "sticky"],
|
||||
["sleep"] = ["good", "difficult", "waking", "dreams"],
|
||||
["emotion"] = ["steady", "anxious", "depressed", "irritable"],
|
||||
["pulse"] = ["normal", "wiry", "slippery", "thin", "rough", "deep", "floating", "weak", "unknown"],
|
||||
["allergy"] = ["none", "often"]
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, (string Type, int Points)[]> Weights = new()
|
||||
{
|
||||
["face:pale"] = [("qi_deficient", 2), ("yang_deficient", 1)],
|
||||
["face:sallow"] = [("qi_deficient", 2), ("phlegm_damp", 1)],
|
||||
["face:flushed"] = [("yin_deficient", 2), ("damp_heat", 1)],
|
||||
["face:dusky"] = [("blood_stasis", 3)],
|
||||
["body:heavy"] = [("phlegm_damp", 3)],
|
||||
["body:thin"] = [("yin_deficient", 2)],
|
||||
["body:abdominal"] = [("phlegm_damp", 3), ("damp_heat", 1)],
|
||||
["spirit:low"] = [("qi_deficient", 2)],
|
||||
["spirit:tired"] = [("qi_deficient", 2), ("yang_deficient", 1)],
|
||||
["voice:weak"] = [("qi_deficient", 2)],
|
||||
["coldHeat:cold"] = [("yang_deficient", 4)],
|
||||
["coldHeat:hot"] = [("yin_deficient", 2), ("damp_heat", 2)],
|
||||
["sweating:easy"] = [("qi_deficient", 2)],
|
||||
["sweating:little"] = [("yin_deficient", 1)],
|
||||
["mouth:bland"] = [("yang_deficient", 1), ("phlegm_damp", 1)],
|
||||
["mouth:bitter"] = [("damp_heat", 3)],
|
||||
["mouth:dry"] = [("yin_deficient", 3)],
|
||||
["stool:dry"] = [("yin_deficient", 2), ("damp_heat", 1)],
|
||||
["stool:loose"] = [("yang_deficient", 2), ("qi_deficient", 1)],
|
||||
["stool:sticky"] = [("damp_heat", 3), ("phlegm_damp", 2)],
|
||||
["sleep:difficult"] = [("qi_stagnation", 2), ("yin_deficient", 1)],
|
||||
["sleep:waking"] = [("qi_deficient", 1), ("yin_deficient", 1)],
|
||||
["sleep:dreams"] = [("qi_stagnation", 2)],
|
||||
["emotion:anxious"] = [("qi_stagnation", 3)],
|
||||
["emotion:depressed"] = [("qi_stagnation", 3)],
|
||||
["emotion:irritable"] = [("qi_stagnation", 2), ("damp_heat", 1)],
|
||||
["pulse:wiry"] = [("qi_stagnation", 2)],
|
||||
["pulse:slippery"] = [("phlegm_damp", 2), ("damp_heat", 1)],
|
||||
["pulse:thin"] = [("yin_deficient", 2)],
|
||||
["pulse:rough"] = [("blood_stasis", 3)],
|
||||
["pulse:deep"] = [("yang_deficient", 1)],
|
||||
["pulse:floating"] = [("qi_deficient", 1)],
|
||||
["pulse:weak"] = [("qi_deficient", 2)],
|
||||
["allergy:often"] = [("special", 6)]
|
||||
};
|
||||
|
||||
public static TcmAssessmentResult Read(HealthProfile? profile)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(profile?.TcmResultJson)) return new TcmAssessmentResult();
|
||||
try { return JsonSerializer.Deserialize<TcmAssessmentResult>(profile.TcmResultJson) ?? new TcmAssessmentResult(); }
|
||||
catch { return new TcmAssessmentResult(); }
|
||||
}
|
||||
|
||||
public static TcmAssessmentResult Evaluate(TcmAssessmentInput input)
|
||||
{
|
||||
if (input?.Answers == null) throw Oops.Oh("请完成四诊问卷");
|
||||
foreach (var (field, choices) in Choices)
|
||||
if (!input.Answers.TryGetValue(field, out var answer) || !choices.Contains(answer))
|
||||
throw Oops.Oh($"四诊项目 {field} 未填写或选项无效");
|
||||
|
||||
var scores = Types.ToDictionary(type => type, _ => 0);
|
||||
foreach (var (field, answer) in input.Answers)
|
||||
if (Weights.TryGetValue($"{field}:{answer}", out var weights))
|
||||
foreach (var (type, points) in weights) scores[type] += points;
|
||||
|
||||
// 无明显偏颇时才判平和;同分时按固定顺序取值,结果可复现。
|
||||
var ranked = Types.Skip(1).OrderByDescending(type => scores[type]).ToList();
|
||||
var primary = scores[ranked[0]] < 3 ? "balanced" : ranked[0];
|
||||
var notes = (input.Notes ?? new Dictionary<string, string>())
|
||||
.Where(item => new[] { "tongue", "odor", "breath", "pulseNote" }.Contains(item.Key))
|
||||
.ToDictionary(item => item.Key, item => (item.Value ?? "").Trim()[..Math.Min((item.Value ?? "").Trim().Length, 300)]);
|
||||
var (aroma, music, room, humidity, kelvin, fragrance, volume) = primary switch
|
||||
{
|
||||
"qi_deficient" => ("清淡木香", "舒缓自然声", "22–25℃", 55, 3000, true, 14),
|
||||
"yang_deficient" => ("温暖木质香", "轻柔慢节奏", "24–26℃", 52, 2700, true, 14),
|
||||
"yin_deficient" => ("淡菊花香", "轻柔海浪声", "21–24℃", 48, 4000, true, 12),
|
||||
"phlegm_damp" => ("清新柑橘香", "舒缓雨声", "22–24℃", 45, 3500, true, 15),
|
||||
"damp_heat" => ("清淡薄荷香", "流水白噪音", "21–24℃", 45, 4000, true, 12),
|
||||
"blood_stasis" => ("淡花木香", "低频舒缓音乐", "23–25℃", 52, 3000, true, 14),
|
||||
"qi_stagnation" => ("清新柑橘香", "轻柔吉他曲", "22–25℃", 51, 3000, true, 14),
|
||||
"special" => ("建议不使用香氛,避免过敏原", "自然风声", "22–25℃", 50, 3500, false, 12),
|
||||
_ => ("淡雅薰衣草香", "自然雨声", "22–25℃", 50, 3000, true, 15)
|
||||
};
|
||||
return new TcmAssessmentResult
|
||||
{
|
||||
HasCompleted = true,
|
||||
ConstitutionCode = primary,
|
||||
ConstitutionName = Names[primary],
|
||||
Summary = $"根据本次望闻问切选择,演示规则提示您偏向{Names[primary]}。仅作健康生活方式参考,不作为诊断。",
|
||||
AromaRecommendation = input.Answers["allergy"] == "often"
|
||||
? "建议不使用香氛,避免过敏原" : aroma,
|
||||
MusicRecommendation = music,
|
||||
RoomTemperatureRecommendation = room,
|
||||
Scores = scores,
|
||||
Answers = Choices.Keys.ToDictionary(key => key, key => input.Answers[key]),
|
||||
Notes = notes,
|
||||
AssessedAt = DateTime.Now,
|
||||
SceneConfig = new PersonalizedSceneConfig
|
||||
{
|
||||
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 = volume
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user