feat: P1 合盘/星座/问答与测测完整设计包及模拟器取证工具

落地 synastry/star/ask API 与 H5 页面,补齐 cece-frontend-re complete-design 证据文档,并加入 Android 模拟器截图抓取脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-03 11:37:53 +08:00
co-authored by Cursor
parent 15a9db374a
commit bd22d9dddd
248 changed files with 26309 additions and 842 deletions
+179 -112
View File
@@ -1,5 +1,4 @@
// Package portrait builds deterministic personal-portrait content from birth date.
// Copy follows .ai/product/lexicon.md — exploration / analysis, not fortune-telling.
// Package portrait builds 愈心解码 content from birth date (classic decode engine).
package portrait
import (
@@ -13,134 +12,202 @@ type Output struct {
Detail map[string]any `json:"detail"`
}
// Build generates 个人画像 content from a birth date (deterministic).
// Build generates 愈心解码 content (deterministic for a given birth date).
func Build(birth time.Time, displayName string) Output {
y, m, d := birth.Date()
num := reduce(y) + reduce(int(m)) + reduce(d)
for num > 9 {
num = reduce(num)
}
trait := traits[num%len(traits)]
tri := Calc(y, int(m), d)
main := tri.O
person := numberPeople[main]
name := displayName
if name == "" {
name = "你"
}
oneLiner := firstSentence(person.Desc)
if oneLiner == "" {
oneLiner = person.Title
}
bz := getBazi(y, int(m), d, 12)
pct := calc5E(bz)
strong := strongestEl(pct)
weak := weakestEl(pct)
cst := constitutionByElement[strong]
emo := emotionByElement[strong]
dims := []map[string]any{
{"key": "personality", "title": "性格特点", "teaser": oneLiner, "score": dimScore(main, 0)},
{"key": "communication", "title": "沟通方式", "teaser": teaserCut(oneLiner, 28), "score": dimScore(main, 1)},
{"key": "relation", "title": "关系模式", "teaser": "深度关系模式见完整分析", "score": dimScore(main, 2)},
{"key": "career", "title": "事业节奏", "teaser": "适合方向见深度版", "score": dimScore(main, 3)},
{"key": "emotion", "title": "情绪调节", "teaser": joinCN(emo.Strength, 3), "score": dimScore(main, 4)},
{"key": "lifestyle", "title": "生活节奏", "teaser": "核心养护:养" + cst.Strong + " · 需呵护" + cst.Weak, "score": dimScore(main, 5)},
}
keywords := []string{fmt.Sprintf("%d号人", main), person.Elem + "行倾向", cst.Primary}
if len(emo.Strength) > 0 {
keywords = append(keywords, emo.Strength[0])
}
tip := todayTip(time.Now())
missing, over := missAndOver(tri)
summary := map[string]any{
"title": "基础画像",
"headline": fmt.Sprintf("%s更偏「%s」的互动风格", name, trait.Label),
"keywords": trait.Keywords,
"one_liner": trait.OneLiner,
"life_tip": trait.LifeTip,
"pattern_key": num,
"title": "愈心解码·基础",
"main_number": main,
"person_title": person.Title,
"style_label": person.Title,
"style_badge": fmt.Sprintf("%d号人", main),
"headline": fmt.Sprintf("%s是%d号人 · %s", name, main, person.Title),
"keywords": keywords,
"one_liner": oneLiner,
"overview": oneLiner,
"life_tip": emo.Advice,
"today_practice": emo.Advice,
// 模糊锁占位(非完整深文案,完整内容仅在 detail)
"lock_teaser_number": "天赋优势与人生课题、适合方向与能量长文,解锁后查看。联合码按生命阶段分组解读。",
"lock_teaser_wuxing": "体质特征、调养要点、易感倾向、食疗与作息节律,解锁后查看完整方案。",
"pattern_key": main,
"pyramid": tri.PyramidMap(),
"wuxing": map[string]any{
"bars": wuxingBars(pct),
"primary": cst.Primary,
"strong": cst.Strong,
"weak": cst.Weak,
"strong_el": strong,
"weak_el": weak,
"emotion": emo.Strength,
"care_dir": "养" + cst.Strong + " · 需呵护" + cst.Weak,
},
"today_tip": tip,
"dimensions": dims,
"strengths_preview": []string{
fmt.Sprintf("%d号人·%s", main, person.Title),
cst.Primary,
},
"blind_spots_preview": []string{"完整天赋优势、人生课题、联合码与体质深析见深度版。"},
"disclaimer_constitution": "体质与调养内容为生活方式参考,非医疗建议。",
}
numberDeep := map[string]any{
"talent": person.Pos,
"topic": person.Neg,
"direction": person.Job,
"elem": person.Elem,
"desc": person.Desc,
"missing": missing,
"over": over,
"joints": jointGroups(tri),
}
constitutionDeep := map[string]any{
"desc": cst.Desc,
"body": cst.Body,
"tips": cst.Tips,
"risk": cst.Diseases,
"season_risk": cst.SeasonRisk,
"food": cst.FoodTherapy,
"rhythm": cst.DailyRhythm,
"primary": cst.Primary,
"secondary": cst.Secondary,
"emotion_risk": emo.Risk,
"emotion_advice": emo.Advice,
}
detail := map[string]any{
"title": "完整分析",
"behavior_pattern": trait.Behavior,
"relation_style": trait.Relation,
"growth_direction": trait.Growth,
"daily_suggestions": []string{
trait.LifeTip,
"遇到分歧时,先复述对方观点再表达自己的需要。",
"用一周记录情绪与精力高峰,找到更适合自己的节奏。",
"title": "完整分析",
"number_deep": numberDeep,
"constitution_deep": constitutionDeep,
"sections": []map[string]any{
section("天赋与课题", person.Desc, []string{"天赋优势:" + person.Pos, "人生课题:" + person.Neg, "适合方向:" + person.Job}),
section("体质特征", cst.Body, []string{cst.Tips}),
section("易感与季节", cst.Diseases, []string{cst.SeasonRisk}),
section("食疗方向", cst.FoodTherapy, nil),
section("作息节律", cst.DailyRhythm, nil),
section("情绪调养", emo.Advice, emo.Risk),
},
"behavior_pattern": person.Desc,
"relation_style": person.Neg,
"growth_direction": person.Pos,
"strengths": splitComma(person.Pos),
"blind_spots": splitComma(person.Neg),
"growth_plan": []map[string]any{
{"phase": "本周", "focus": emo.Advice},
{"phase": "本月", "focus": cst.Tips},
{"phase": "长期", "focus": "结合数字课题「" + person.Neg + "」与体质养护「养" + cst.Strong + "」,形成可持续的生活节奏。"},
},
"conversation_scripts": []string{
"我想聊聊自己的节奏,不用急着给建议。",
"当我压力大时,需要一点安静空间再继续聊。",
"帮我一起看看,哪件事最值得这周聚焦。",
},
"daily_suggestions": []string{strAny(tip["yi"]), emo.Advice, "核心养护:养" + cst.Strong},
"faq": []map[string]string{
{"q": "数字性格是固定的吗?", "a": "主数提供稳定的自我探索框架,具体表现会随阶段与环境变化,可当作镜子而非标签。"},
{"q": "五行体质能当看病依据吗?", "a": "不能。这里的体质与调养只是生活方式参考,不适请就医,勿自行当作治疗方案。"},
},
}
return Output{Summary: summary, Detail: detail}
}
type trait struct {
Label string
Keywords []string
OneLiner string
LifeTip string
Behavior string
Relation string
Growth string
func section(title, body string, bullets []string) map[string]any {
return map[string]any{"title": title, "body": body, "bullets": bullets}
}
var traits = []trait{
{
Label: "稳健探索", Keywords: []string{"条理", "耐心", "观察"},
OneLiner: "你习惯先理解再行动,适合把复杂事情拆成小步。",
LifeTip: "今天给自己一段不被打断的专注时间。",
Behavior: "决策偏审慎,信息充分时执行力更强;压力下可能拖延。",
Relation: "更愿意用行动表达关心,需要对方给予明确反馈。",
Growth: "练习在信息不完整时做小步试验,积累行动信心。",
},
{
Label: "热情连接", Keywords: []string{"表达", "共鸣", "主动"},
OneLiner: "你容易带动气氛,也需要被真诚回应。",
LifeTip: "把想说的话写下来,再选择合适的时机分享。",
Behavior: "行动快、反馈敏感;情绪起伏会影响专注时长。",
Relation: "重视即时沟通,冷处理容易让你感到不安。",
Growth: "学会区分「被回应」与「被认同」,减少过度解读。",
},
{
Label: "理性澄清", Keywords: []string{"分析", "边界", "清晰"},
OneLiner: "你擅长把模糊感受变成可讨论的问题。",
LifeTip: "睡前做一次简短复盘:今天最有价值的一件事是什么。",
Behavior: "偏好逻辑框架;在情绪场域可能显得抽离。",
Relation: "沟通时需要结构和具体例子,忌空泛安慰。",
Growth: "在分析之外,练习先接纳情绪再谈方案。",
},
{
Label: "柔韧调节", Keywords: []string{"适应", "体察", "平衡"},
OneLiner: "你善于照顾氛围,也别忘了照顾自己的节奏。",
LifeTip: "安排一次轻度活动,帮助身心回到平稳状态。",
Behavior: "弹性强,容易迁就;长期可能积压需求。",
Relation: "更在意关系和谐,冲突时倾向先安抚场面。",
Growth: "练习用「我需要…」表达边界,而不是只做协调者。",
},
{
Label: "目标推进", Keywords: []string{"决断", "效率", "成果"},
OneLiner: "你推动事情落地的能力突出,记得留白给休息。",
LifeTip: "把今日目标收束到一件最重要的事。",
Behavior: "结果导向,节奏偏快;对低效协作耐心有限。",
Relation: "欣赏直接沟通的人,含糊表态会消耗信任。",
Growth: "把「效率」与「关系维护」列为同等优先级的周目标。",
},
{
Label: "内观沉淀", Keywords: []string{"深度", "独立", "洞察"},
OneLiner: "你习惯向内理解世界,适合深度思考类任务。",
LifeTip: "留出安静独处时间,整理近期的想法与感受。",
Behavior: "思考深入,对外表达可能滞后于内心结论。",
Relation: "需要安全感和节奏感,突然施压会让你退回内在。",
Growth: "把洞察翻译成可分享的语言,让重要的人跟上你。",
},
{
Label: "创意发散", Keywords: []string{"想象", "灵感", "可能"},
OneLiner: "你容易看到多种可能性,适合用原型验证想法。",
LifeTip: "用纸笔画出今天最想尝试的一个小实验。",
Behavior: "点子多、切换快;收尾与复盘是短板。",
Relation: "喜欢有趣的对话,重复与僵化会让你疏离。",
Growth: "为每个灵感设定「最小完成定义」,提高闭环率。",
},
{
Label: "责任担当", Keywords: []string{"可靠", "承诺", "稳定"},
OneLiner: "你重视承诺与秩序,是团队里让人安心的存在。",
LifeTip: "检查一下是否把别人的期待误当成自己的必须。",
Behavior: "可靠且自律;过度负责时容易耗竭。",
Relation: "用持续在场表达在乎,需要被看见付出。",
Growth: "练习委托与求助,让支持系统真正运转起来。",
},
{
Label: "敏锐觉察", Keywords: []string{"细腻", "直觉", "体贴"},
OneLiner: "你对情绪与细节敏感,适合需要同理的场景。",
LifeTip: "觉察身体信号:紧张时先放慢呼吸再回应。",
Behavior: "感知力强;信息过载时容易内耗。",
Relation: "能很快读到对方状态,也易被情绪感染。",
Growth: "建立「感受—事实—选择」三步,减少被情绪牵着走。",
},
func dimScore(main, salt int) int {
// Stable pseudo-scores 6292 for relation dimension compare.
return 62 + (main*7+salt*11)%31
}
func reduce(n int) int {
if n < 0 {
n = -n
func teaserCut(s string, n int) string {
r := []rune(s)
if len(r) <= n {
return s
}
sum := 0
for n > 0 {
sum += n % 10
n /= 10
}
return sum
return string(r[:n]) + "…"
}
func joinCN(parts []string, n int) string {
if len(parts) == 0 {
return ""
}
if len(parts) < n {
n = len(parts)
}
out := parts[0]
for i := 1; i < n; i++ {
out += " · " + parts[i]
}
return out
}
func splitComma(s string) []string {
if s == "" {
return nil
}
// pos/neg use顿号
parts := []string{}
cur := ""
for _, r := range s {
if r == '、' || r == '' || r == ',' {
if cur != "" {
parts = append(parts, cur)
cur = ""
}
continue
}
cur += string(r)
}
if cur != "" {
parts = append(parts, cur)
}
if len(parts) > 3 {
return parts[:3]
}
return parts
}
func strAny(v any) string {
if s, ok := v.(string); ok {
return s
}
return ""
}