225 lines
9.3 KiB
C#
225 lines
9.3 KiB
C#
|
|
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<string, string> NourishMapping = new(StringComparer.Ordinal)
|
|||
|
|
{
|
|||
|
|
["木"] = "水",
|
|||
|
|
["火"] = "木",
|
|||
|
|
["土"] = "火",
|
|||
|
|
["金"] = "土",
|
|||
|
|
["水"] = "金"
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
private static readonly Dictionary<string, (string[] Colors, string[] Props, string[] Directions)> 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<string> BuildActivities(string element, string weakElement, BaziProfile todayProfile)
|
|||
|
|
{
|
|||
|
|
var activities = new List<string>
|
|||
|
|
{
|
|||
|
|
$"携带{element}系小物件,稳住气场",
|
|||
|
|
$"在{todayProfile.MonthBranch}月令的主题上做一点复盘"
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (!string.Equals(element, weakElement, StringComparison.Ordinal))
|
|||
|
|
{
|
|||
|
|
activities.Add($"补足{weakElement}之气,宜接触自然或深呼吸练习");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
activities.Add("保持饮水与拉伸,照顾身体节奏");
|
|||
|
|
return activities;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static string BuildSummary(IEnumerable<FortuneDimension> 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<FortuneDimension> 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;
|
|||
|
|
}
|