using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using DouyinApi.Model.DailyFortune; using DouyinApi.Services.Naming; namespace DouyinApi.Services.DailyFortune; internal sealed class DailyFortuneFallbackGenerator { private static readonly (string Key, string Title, string Focus)[] DimensionDefinitions = { ("career", "事业运", "职场推进"), ("wealth", "财务运", "资源调度"), ("relationship", "情感运", "情绪表达"), ("health", "健康运", "身心节奏"), ("social", "人际运", "团队协同"), ("inspiration", "灵感运", "学习洞察") }; private static readonly Dictionary NourishMapping = new(StringComparer.Ordinal) { ["木"] = "水", ["火"] = "木", ["土"] = "火", ["金"] = "土", ["水"] = "金" }; private static readonly Dictionary ElementMappings = new(StringComparer.Ordinal) { ["木"] = (new[] { "青绿", "墨绿" }, new[] { "绿松石", "木质手串" }, new[] { "正东", "东南" }), ["火"] = (new[] { "朱红", "橘色" }, new[] { "朱砂饰品", "暖色围巾" }, new[] { "正南", "东南" }), ["土"] = (new[] { "杏色", "暖棕" }, new[] { "陶土摆件", "黄玉戒" }, new[] { "中央", "西南" }), ["金"] = (new[] { "米金", "象牙白" }, new[] { "金属耳饰", "白水晶" }, new[] { "正西", "西北" }), ["水"] = (new[] { "深蓝", "青蓝" }, new[] { "黑曜石", "海蓝宝" }, new[] { "正北", "东北" }) }; private static readonly (string Label, string Period)[] TimeSlots = { ("拂晓", "05:00-07:00"), ("辰时", "07:00-09:00"), ("巳时", "09:00-11:00"), ("午时", "11:00-13:00"), ("申时", "15:00-17:00"), ("酉时", "17:00-19:00"), ("夜间", "19:00-21:00") }; public DailyFortuneInsight Generate(DailyFortuneRequest request, BaziProfile birthProfile, BaziProfile todayProfile, DateTime fortuneMoment) { var seedSource = $"{request.BirthCity}-{request.BirthDate}-{request.BirthTime}-{fortuneMoment:yyyyMMdd}"; var seed = Math.Abs(seedSource.GetHashCode()); var random = new Random(seed); var weakElement = ResolveWeakElement(birthProfile, todayProfile); var dimensions = DimensionDefinitions .Select((definition, index) => { var swing = ((seed >> (index + 1)) % 21) - 10; var baseScore = 70 + swing; if (!birthProfile.IsBalanced && string.Equals(definition.Key, "health", StringComparison.Ordinal)) { baseScore -= 2; } var score = Math.Clamp(baseScore, 48, 96); var trend = swing switch { > 3 => "up", < -3 => "down", _ => "steady" }; var insight = $"{definition.Focus}受{weakElement}气场牵引,今日呈现{DescribeTrend(trend)}走势。"; var suggestion = BuildSuggestion(definition.Key, weakElement, todayProfile, trend); return new FortuneDimension { Key = definition.Key, Title = definition.Title, Score = score, Trend = trend, Insight = insight, Suggestion = suggestion }; }) .ToList(); var luckyGuide = BuildLuckyGuide(weakElement, todayProfile, random); var summary = BuildSummary(dimensions, luckyGuide, fortuneMoment); var narrative = BuildNarrative(request, dimensions, luckyGuide, weakElement, todayProfile, fortuneMoment); return new DailyFortuneInsight { Dimensions = dimensions, LuckyGuide = luckyGuide, Summary = summary, Narrative = narrative }; } private static LuckyGuide BuildLuckyGuide(string weakElement, BaziProfile todayProfile, Random random) { var target = TargetElement(weakElement); if (!ElementMappings.TryGetValue(target, out var mapping)) { mapping = ElementMappings["木"]; } var slotIndex = random.Next(TimeSlots.Length); var slotB = (slotIndex + 3) % TimeSlots.Length; return new LuckyGuide { Element = target, Colors = mapping.Colors, Props = mapping.Props, Directions = mapping.Directions, Activities = BuildActivities(target, weakElement, todayProfile), BestTimeSlots = new[] { new LuckyTimeSlot { Label = TimeSlots[slotIndex].Label, Period = TimeSlots[slotIndex].Period, Reason = $"与日支{todayProfile.DayBranch}气息谐和" }, new LuckyTimeSlot { Label = TimeSlots[slotB].Label, Period = TimeSlots[slotB].Period, Reason = "利于沉浸式处理重点事务" } } }; } private static IReadOnlyList BuildActivities(string element, string weakElement, BaziProfile todayProfile) { var activities = new List { $"携带{element}系小物件,稳住气场", $"在{todayProfile.MonthBranch}月令的主题上做一点复盘" }; if (!string.Equals(element, weakElement, StringComparison.Ordinal)) { activities.Add($"补足{weakElement}之气,宜接触自然或深呼吸练习"); } activities.Add("保持饮水与拉伸,照顾身体节奏"); return activities; } private static string BuildSummary(IEnumerable dimensions, LuckyGuide guide, DateTime fortuneMoment) { var avg = (int)Math.Round(dimensions.Average(x => x.Score)); var strongest = dimensions.OrderByDescending(x => x.Score).First(); var weakest = dimensions.OrderBy(x => x.Score).First(); return $"{fortuneMoment:yyyy年M月d日}整体指数约为{avg}分,{strongest.Title}是今日亮点,{weakest.Title}需留意。以{guide.Element}元素入场,能更顺利地衔接节奏。"; } private static string BuildNarrative( DailyFortuneRequest request, IEnumerable dimensions, LuckyGuide guide, string weakElement, BaziProfile todayProfile, DateTime fortuneMoment) { var focus = dimensions .Where(x => x.Trend == "up") .Select(x => x.Title) .DefaultIfEmpty("核心主题") .ToArray(); return $"结合{request.BirthCity}出生的先天命盘与今日{todayProfile.DayStem}{todayProfile.DayBranch}能量,建议把握{string.Join("、", focus)}。" + $"多使用{string.Join("、", guide.Colors)}等{guide.Element}系元素,可温和弥补{weakElement}不足。" + $"若能在{string.Join("、", guide.BestTimeSlots.Select(s => s.Label))}安排重要事项,将更容易与当日天象共振。"; } private static string BuildSuggestion(string dimensionKey, string weakElement, BaziProfile todayProfile, string trend) { return dimensionKey switch { "career" => trend == "up" ? "适合推进关键节点,善用会议前的5分钟整理要点。" : "避免一次承担过多议题,可把结论拆分分批落实。", "wealth" => trend == "up" ? "梳理现金流或理财计划,集中处理应收款。" : "控制冲动型消费,预算留白以应对变量。", "relationship" => trend == "up" ? "表达时保持真诚与柔软,分享情绪而非立场。" : "先倾听再回应,避免在情绪波动时做决定。", "health" => trend == "up" ? "保持稳定作息,适合做轻量有氧或拉伸。" : "补水与热身不可省,防止因节奏紊乱带来疲惫感。", "social" => trend == "up" ? "在团队中扮演连接者的角色,协调资源会有惊喜。" : "不必事事亲自上阵,学会授权也能稳住局面。", "inspiration" => trend == "up" ? "灵感度高,可记录随手闪现的想法并快速试错。" : "转换学习方式,利用碎片化时间吸收新知。", _ => $"保持呼吸节奏,善用{weakElement}相关意象维持专注度。" }; } private static string DescribeTrend(string trend) => trend switch { "up" => "回升", "down" => "略有波动", _ => "趋于平稳" }; private static string ResolveWeakElement(BaziProfile birthProfile, BaziProfile todayProfile) => birthProfile.WeakElements.FirstOrDefault() ?? todayProfile.WeakElements.FirstOrDefault() ?? "木"; private static string TargetElement(string weakElement) => NourishMapping.TryGetValue(weakElement, out var target) ? target : weakElement; }