feat(ECR-011): 昵称、首页贴士与档案 Self 唯一/合盘交叉校验
每账号仅一条 self(migration 40902);合盘只选 TA;账号昵称可改;首页穿衣/颜色/养生贴士。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
// Package yijing provides a deterministic hexagram seed from birth + local time.
|
||||
// It is a cultural rhythm seed for lifestyle tips — not fortune-telling.
|
||||
package yijing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Context is the seed passed to LLM prompts.
|
||||
type Context struct {
|
||||
GuaIndex int // 1..64
|
||||
GuaName string // short label
|
||||
Line int // 1..6 changing line hint
|
||||
DayPart string // morning|noon|afternoon|evening|night
|
||||
NowLocal string
|
||||
BirthDate string
|
||||
BirthTime string
|
||||
}
|
||||
|
||||
var guaNames = []string{
|
||||
"乾", "坤", "屯", "蒙", "需", "讼", "师", "比",
|
||||
"小畜", "履", "泰", "否", "同人", "大有", "谦", "豫",
|
||||
"随", "蛊", "临", "观", "噬嗑", "贲", "剥", "复",
|
||||
"无妄", "大畜", "颐", "大过", "坎", "离", "咸", "恒",
|
||||
"遁", "大壮", "晋", "明夷", "家人", "睽", "蹇", "解",
|
||||
"损", "益", "夬", "姤", "萃", "升", "困", "井",
|
||||
"革", "鼎", "震", "艮", "渐", "归妹", "丰", "旅",
|
||||
"巽", "兑", "涣", "节", "中孚", "小过", "既济", "未济",
|
||||
}
|
||||
|
||||
// Seed builds hexagram context from birth date/time and now (Asia/Shanghai preferred).
|
||||
func Seed(birthDate string, birthTime *string, now time.Time) Context {
|
||||
loc, err := time.LoadLocation("Asia/Shanghai")
|
||||
if err != nil {
|
||||
loc = time.FixedZone("CST", 8*3600)
|
||||
}
|
||||
now = now.In(loc)
|
||||
|
||||
bt := "12:00"
|
||||
if birthTime != nil && *birthTime != "" {
|
||||
bt = *birthTime
|
||||
}
|
||||
|
||||
sum := hash(birthDate) + hash(bt) + hash(now.Format("2006-01-02")) + now.Hour()*17 + now.Minute()/10
|
||||
gua := (sum % 64) + 1
|
||||
line := (sum/7)%6 + 1
|
||||
name := guaNames[gua-1]
|
||||
|
||||
return Context{
|
||||
GuaIndex: gua,
|
||||
GuaName: name,
|
||||
Line: line,
|
||||
DayPart: dayPart(now.Hour()),
|
||||
NowLocal: now.Format("2006-01-02 15:04"),
|
||||
BirthDate: birthDate,
|
||||
BirthTime: bt,
|
||||
}
|
||||
}
|
||||
|
||||
// PromptLine is a compact Chinese summary for the model.
|
||||
func (c Context) PromptLine() string {
|
||||
return fmt.Sprintf(
|
||||
"出生 %s %s;此刻 %s(时段 %s);卦象种子:%s(第%d卦)· 动爻%d",
|
||||
c.BirthDate, c.BirthTime, c.NowLocal, c.DayPart, c.GuaName, c.GuaIndex, c.Line,
|
||||
)
|
||||
}
|
||||
|
||||
func dayPart(h int) string {
|
||||
switch {
|
||||
case h < 6:
|
||||
return "night"
|
||||
case h < 11:
|
||||
return "morning"
|
||||
case h < 14:
|
||||
return "noon"
|
||||
case h < 18:
|
||||
return "afternoon"
|
||||
default:
|
||||
return "evening"
|
||||
}
|
||||
}
|
||||
|
||||
func hash(s string) int {
|
||||
n := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
n = n*131 + int(s[i])
|
||||
}
|
||||
if n < 0 {
|
||||
n = -n
|
||||
}
|
||||
return n
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package yijing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSeedStableSameInputs(t *testing.T) {
|
||||
bt := "10:30"
|
||||
now := time.Date(2026, 8, 11, 19, 0, 0, 0, time.FixedZone("CST", 8*3600))
|
||||
a := Seed("1990-05-12", &bt, now)
|
||||
b := Seed("1990-05-12", &bt, now)
|
||||
if a.GuaIndex != b.GuaIndex || a.GuaName != b.GuaName || a.Line != b.Line {
|
||||
t.Fatalf("expected stable seed, got %+v vs %+v", a, b)
|
||||
}
|
||||
if a.GuaIndex < 1 || a.GuaIndex > 64 {
|
||||
t.Fatalf("gua out of range: %d", a.GuaIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedChangesWithHour(t *testing.T) {
|
||||
now1 := time.Date(2026, 8, 11, 9, 0, 0, 0, time.FixedZone("CST", 8*3600))
|
||||
now2 := time.Date(2026, 8, 11, 18, 0, 0, 0, time.FixedZone("CST", 8*3600))
|
||||
a := Seed("1990-05-12", nil, now1)
|
||||
b := Seed("1990-05-12", nil, now2)
|
||||
if a.DayPart == b.DayPart {
|
||||
t.Fatalf("expected different day parts, both %s", a.DayPart)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user