// Package rhythm builds 身心节律 reports (lifestyle exploration, not fortune/medical claims). package rhythm import ( "fmt" "time" "github.com/yuxingu/digital-psychology/apps/api/internal/companion" "github.com/yuxingu/digital-psychology/apps/api/internal/portrait" ) // Output free summary + gated detail. type Output struct { Summary map[string]any Detail map[string]any } var elements = []string{"木", "火", "土", "金", "水"} // Build from birth date (asOf = now). func Build(birth time.Time, displayName string) Output { return BuildWith(birth, displayName, time.Time{}) } // BuildWith anchors today/week tips to asOf (zero = now CST). func BuildWith(birth time.Time, displayName string, asOf time.Time) Output { name := displayName if name == "" { name = "你" } primary := elements[(birth.Year()+int(birth.Month())+birth.Day())%5] secondary := elements[(birth.YearDay())%5] if secondary == primary { secondary = elements[(birth.YearDay()+1)%5] } tip := tips[primary] dims := []map[string]any{ {"key": "element", "title": "主导倾向", "teaser": fmt.Sprintf("更偏「%s」隐喻", primary), "score": 80}, {"key": "balance", "title": "平衡关注", "teaser": fmt.Sprintf("可留意「%s」侧滋养", secondary), "score": 68}, {"key": "sleep", "title": "作息", "teaser": tip.Sleep, "score": 72}, {"key": "diet", "title": "饮食节奏", "teaser": tip.Diet, "score": 70}, {"key": "move", "title": "活动", "teaser": tip.Move, "score": 74}, {"key": "mood", "title": "情绪调节", "teaser": tip.Mood, "score": 71}, } when := asOf if when.IsZero() { when = time.Now().In(time.FixedZone("CST", 8*3600)) } else { when = when.In(time.FixedZone("CST", 8*3600)) } todayTips := []string{tip.Sleep, tip.Diet, tip.Move, tip.Mood} todayTip := todayTips[when.YearDay()%len(todayTips)] weekday := int(when.Weekday()) weekHints := []string{tip.Mood, tip.Move, tip.Diet, tip.Sleep, tip.WeekTip, tip.Move, tip.Mood} dayKey := when.Format("2006-01-02") validUntil := time.Date(when.Year(), when.Month(), when.Day()+1, 0, 0, 0, 0, when.Location()) wx := portrait.WuxingBlock(birth) st := companion.TodaySolar(when) summary := map[string]any{ "title": "身心节律·基础", "style_label": primary + "倾向", "headline": fmt.Sprintf("%s的身心节律更偏「%s」隐喻", name, primary), "one_liner": tip.OneLiner, "overview": fmt.Sprintf("%s%s主导倾向「%s」,辅助关注「%s」。以下建议用于生活节奏探索,不构成医疗意见。", name, tip.Overview, primary, secondary), "life_tip": tip.WeekTip, "today_tip": todayTip, "week_focus": weekHints[weekday%len(weekHints)], "as_of": dayKey, "valid_until": validUntil.Format(time.RFC3339), "wuxing": wx, "solar_term": map[string]any{ "name": st.Name, "tip": st.Tip, }, "keywords": []string{primary, secondary, "生活建议", "节律"}, "dimensions": dims, "primary_element": primary, "secondary_element": secondary, "strengths_preview": tip.Strengths[:min(3, len(tip.Strengths))], "blind_spots_preview": tip.BlindSpots[:min(3, len(tip.BlindSpots))], "disclaimer_constitution": "体质与调养内容为生活方式参考,非医疗建议。", } detail := map[string]any{ "title": "身心节律·完整建议", "sections": []map[string]any{ {"title": "节律总览", "body": tip.DeepOverview, "bullets": tip.OverviewBullets}, {"title": "作息建议", "body": tip.SleepDeep, "bullets": tip.SleepBullets}, {"title": "饮食节奏", "body": tip.DietDeep, "bullets": tip.DietBullets}, {"title": "活动与放松", "body": tip.MoveDeep, "bullets": tip.MoveBullets}, {"title": "情绪与压力", "body": tip.MoodDeep, "bullets": tip.MoodBullets}, {"title": "与节气联动", "body": "可结合「节气生活」调整当季节奏;变化慢一点,观察身体反馈即可。", "bullets": []string{"查看今日节气", "一次只改一个习惯", "不适就医,勿自行当治疗"}}, {"title": "今日生活建议", "body": todayTip + " " + tip.Mood, "bullets": []string{tip.Move, tip.Diet}}, {"title": "本周节奏", "body": weekHints[weekday%len(weekHints)], "bullets": tip.OverviewBullets}, }, "strengths": tip.Strengths, "blind_spots": tip.BlindSpots, "growth_plan": []map[string]any{ {"phase": "本周", "focus": tip.WeekTip}, {"phase": "本月", "focus": tip.MonthTip}, {"phase": "长期", "focus": "形成「充电/耗电」清单,并与节气提醒一起复盘。"}, }, "conversation_scripts": tip.Scripts, "faq": []map[string]string{ {"q": "这是占卜或看病吗?", "a": "都不是。这是身心节律的自我探索与生活建议,不预测未来,也不构成医疗诊断。"}, }, "behavior_pattern": tip.DeepOverview, "relation_style": tip.MoodDeep, "growth_direction": tip.MonthTip, } return Output{Summary: summary, Detail: detail} } func min(a, b int) int { if a < b { return a } return b } type tipPack struct { OneLiner, Overview, WeekTip, MonthTip, DeepOverview string Sleep, Diet, Move, Mood string SleepDeep, DietDeep, MoveDeep, MoodDeep string OverviewBullets, SleepBullets, DietBullets, MoveBullets, MoodBullets []string Strengths, BlindSpots, Scripts []string } var tips = map[string]tipPack{ "木": { OneLiner: "你适合有伸展与目标感的节奏,压得太死容易闷。", Overview: "在节律隐喻里偏「生发」:需要空间与方向。", WeekTip: "本周安排三次户外或拉伸,每次 15 分钟。", MonthTip: "给自己一个可完成的小目标,并拆成周检查点。", DeepOverview: "木倾向的人常需要「向前」的感觉。卡住时,动一动比硬想更有效。注意肩颈与情绪积压。", Sleep: "尽量固定入睡窗口", Diet: "清淡多样,少暴饮暴食", Move: "伸展、快走、轻度有氧", Mood: "把烦闷说出来或写下来", SleepDeep: "规律睡眠能显著降低烦躁。睡前一小时减少争执性内容。", DietDeep: "少油炸重味,增加新鲜蔬菜;饥一顿饱一顿会放大情绪波动。", MoveDeep: "优先选择有伸展感的活动,避免长期久坐。", MoodDeep: "愤怒或闷气是常见信号。命名情绪后做一次短活动再对话。", OverviewBullets: []string{"需要方向感", "忌长期压抑", "动一动有助疏通"}, SleepBullets: []string{"固定入睡", "睡前减刺激"}, DietBullets: []string{"清淡多样", "规律进餐"}, MoveBullets: []string{"伸展快走", "避免久坐"}, MoodBullets: []string{"命名情绪", "先活动再谈"}, Strengths: []string{"行动欲", "目标感", "适应生长"}, BlindSpots: []string{"易急躁", "压怒", "忽视休息"}, Scripts: []string{"我有点闷,想先走走再聊。", "我需要一个小目标来找到节奏。"}, }, "火": { OneLiner: "你靠热情与连接充电,也要注意过热后的回落。", Overview: "节律隐喻偏「温煦」:表达与互动重要。", WeekTip: "保证两晚 23:30 前入睡,并安排一次轻松社交。", MonthTip: "学会在热情后安排恢复日,而不是连续硬冲。", DeepOverview: "火倾向的人能量外放。高潮后空虚很常见,回落期做轻松恢复即可。", Sleep: "避免熬夜透支", Diet: "少辛辣刺激过量", Move: "有节奏的有氧", Mood: "表达后也要倾听", SleepDeep: "睡眠差会放大冲动。把「今晚早睡」当成任务完成。", DietDeep: "辛辣咖啡适量;补水与规律三餐稳住节奏。", MoveDeep: "运动有助释放兴奋,但避免以透支换快乐。", MoodDeep: "表达是优点。记得给对方回应空间,也给自己降温时间。", OverviewBullets: []string{"热情连接", "注意回落", "表达重要"}, SleepBullets: []string{"少熬夜", "降温再睡"}, DietBullets: []string{"刺激适量", "规律进餐"}, MoveBullets: []string{"有氧释放", "勿透支"}, MoodBullets: []string{"表达+倾听", "回落期恢复"}, Strengths: []string{"感染力", "行动号召", "乐观"}, BlindSpots: []string{"过热", "承诺过多", "忽视恢复"}, Scripts: []string{"我很兴奋,我们先定本周唯一下一步。", "我需要今晚早点休息。"}, }, "土": { OneLiner: "你需要稳定与踏实,变化太快会消耗安全感。", Overview: "节律隐喻偏「承载」:规律比刺激更重要。", WeekTip: "固定三餐时间,并完成一次环境整理(桌面/房间一角)。", MonthTip: "建立一个可重复的晨间或睡前小仪式。", DeepOverview: "土倾向的人靠可预期感充电。突变多时,用清单与仪式稳住自己。", Sleep: "同一时间上床", Diet: "温热易消化、勿过饥过饱", Move: "散步、力量轻度", Mood: "少同时处理多件事", SleepDeep: "规律比时长更重要。周末也不要大幅颠倒作息。", DietDeep: "细嚼慢咽;减少生冷刺激性饮食实验。", MoveDeep: "稳定、可坚持的运动优于偶尔高强度。", MoodDeep: "焦虑常来自失控感。把任务拆小,完成一件勾一项。", OverviewBullets: []string{"需要稳定", "仪式感有用", "忌突变过多"}, SleepBullets: []string{"同时上床", "周末少颠倒"}, DietBullets: []string{"规律温热", "细嚼"}, MoveBullets: []string{"可坚持", "散步力量"}, MoodBullets: []string{"清单拆解", "单线程"}, Strengths: []string{"踏实", "耐心", "可靠"}, BlindSpots: []string{"抗拒变化", "过度担忧", "行动偏慢"}, Scripts: []string{"我想先把节奏排好,再谈变化。", "给我一点准备时间,我会更稳。"}, }, "金": { OneLiner: "你重视秩序与边界,混乱环境会让你烦躁。", Overview: "节律隐喻偏「收敛」:清晰与整理很重要。", WeekTip: "清理数字或实体杂物 20 分钟,并写下三条边界。", MonthTip: "练习两次温和而清晰的拒绝。", DeepOverview: "金倾向的人靠清晰边界获得掌控感。过度苛求完美会消耗自己。", Sleep: "睡前收工仪式", Diet: "少熬夜夜宵", Move: "呼吸、拉伸、节奏运动", Mood: "把标准调到足够好", SleepDeep: "工作与睡眠边界要划清;床上不处理未完成清单。", DietDeep: "规律饮食;减少以咖啡硬撑。", MoveDeep: "注重呼吸与肩颈;短时高质量运动即可。", MoodDeep: "挑剔有时是焦虑。用「足够好」完成一件事对抗空转。", OverviewBullets: []string{"边界清晰", "忌混乱", "完美主义留意"}, SleepBullets: []string{"收工仪式", "床上不工作"}, DietBullets: []string{"少硬撑", "规律"}, MoveBullets: []string{"呼吸拉伸", "短时有效"}, MoodBullets: []string{"足够好", "温和拒绝"}, Strengths: []string{"条理", "原则", "质量感"}, BlindSpots: []string{"过苛", "难放手", "紧绷"}, Scripts: []string{"这次我想按我的节奏来。", "这件事我接不住,我们换个方案。"}, }, "水": { OneLiner: "你感受深、需要沉淀,被催促时容易关闭。", Overview: "节律隐喻偏「滋润」:休整与感受空间重要。", WeekTip: "安排两段不被打扰的静默时间(各 20 分钟)。", MonthTip: "建立睡前放松流程,减少临睡刷信息。", DeepOverview: "水倾向的人需要内在空间。催促与过载会让你退缩;适度分享感受可减少误解。", Sleep: "保证连续睡眠", Diet: "温补平和、少冰冷刺激", Move: "游泳、散步、柔软活动", Mood: "先感受再决策", SleepDeep: "睡眠是情绪水库。缺觉时少做重大决定。", DietDeep: "规律温热;观察哪些食物让你更稳。", MoveDeep: "柔和持续的活动比爆发冲刺更适合你。", MoodDeep: "情绪来时先命名;写三句或找安全的人听你说完。", OverviewBullets: []string{"需要沉淀", "忌强催", "感受力强"}, SleepBullets: []string{"连续睡眠", "少缺觉决策"}, DietBullets: []string{"温热平和", "规律"}, MoveBullets: []string{"柔和持续", "散步游泳"}, MoodBullets: []string{"先命名", "再决策"}, Strengths: []string{"洞察", "共情", "韧性"}, BlindSpots: []string{"回避", "反刍", "边界模糊"}, Scripts: []string{"我需要一点时间感受一下,再回复你。", "我不是拖延,我在整理自己。"}, }, }