فهرست منبع

feat: fuse Western astrology with Chinese zodiac for annual energy calculation

- Add sun sign detection (birthMonth/birthDay) and element/modality bias
- Add Jupiter 12yr and Saturn 29.5yr cycle phase calculation
- Blend 60% Western + 40% Chinese zodiac with Tai Sui relation modifiers
- Update controller to accept birthMonth/birthDay, return full fusion result
Xiaogang Liao 2 ماه پیش
والد
کامیت
8a86c4a495

+ 58 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/ZodiacEnergyController.java

@@ -0,0 +1,58 @@
+package com.etotem.cfc.controller;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.service.ZodiacAnnualEnergyService;
+import com.etotem.cfc.service.ZodiacAnnualEnergyService.AnnualEnergyResult;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * 星座运势能量接口(融合东西方星座体系)
+ */
+@RestController
+@RequestMapping("/api/zodiac")
+public class ZodiacEnergyController {
+
+    @Resource
+    private ZodiacAnnualEnergyService zodiacEnergyService;
+
+    /**
+     * 获取某成员的年度五维能量(融合东西方星座体系)
+     *
+     * @param birthYear  出生年份(必填)
+     * @param birthMonth 出生月份(必填,用于西方星座计算)
+     * @param birthDay   出生日期(必填,用于西方星座计算)
+     * @param zodiac     生肖代码(选填,不填则根据出生年份自动推算)
+     * @param year       目标年份(选填,默认今年)
+     */
+    @Operation(summary = "获取年度五维能量")
+    @PostMapping("/energy")
+    public Result<Map<String, Object>> getAnnualEnergy(
+            @RequestParam int birthYear,
+            @RequestParam int birthMonth,
+            @RequestParam int birthDay,
+            @Parameter(description = "生肖代码: rat/ox/tiger/rabbit/dragon/snake/horse/sheep/monkey/rooster/dog/pig")
+            @RequestParam(required = false) String zodiac,
+            @RequestParam(required = false) Integer year) {
+
+        AnnualEnergyResult result = zodiacEnergyService.calculate(birthYear, birthMonth, birthDay, zodiac, year);
+        Map<String, Object> data = new LinkedHashMap<>();
+        data.put("westernSign", result.getWesternSign());
+        data.put("westernSignCn", result.getWesternSignCn());
+        data.put("westernElement", result.getWesternElement());
+        data.put("westernModality", result.getWesternModality());
+        data.put("zodiacCode", result.getZodiacCode());
+        data.put("zodiacName", result.getZodiacName());
+        data.put("yearBranch", result.getYearBranch());
+        data.put("yearRelation", result.getYearRelation());
+        data.put("jupiterPhase", result.getJupiterPhase());
+        data.put("saturnPhase", result.getSaturnPhase());
+        data.put("dimensions", result.toMap());
+        return Result.success(data);
+    }
+}

+ 536 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/ZodiacAnnualEnergyService.java

@@ -0,0 +1,536 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.entity.ZodiacConfig;
+import com.etotem.cfc.mapper.ZodiacConfigMapper;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+/**
+ * 星座运势年度五维能量计算服务
+ *
+ * 融合东西方星座体系的年度能量计算:
+ * - 西方星座(60%权重):太阳星座元素倾向 + 模态倾向 + 木星/土星年度周期修正
+ * - 中国生肖(40%权重):太岁关系(本命年/六合/三合/冲/害/平)+ 干支五行修正
+ *
+ * 五维能量值范围 0-100:身、心、智、行、富
+ */
+@Service
+public class ZodiacAnnualEnergyService {
+
+    @Resource
+    private ZodiacConfigMapper zodiacConfigMapper;
+
+    // ==================== 西方星座体系 ====================
+
+    /** 黄道十二星座代码 */
+    private static final String[] WESTERN_SIGNS = {
+            "capricorn", "aquarius", "pisces", "aries", "taurus", "gemini",
+            "cancer", "leo", "virgo", "libra", "scorpio", "sagittarius"
+    };
+
+    /** 星座中文名 */
+    private static final String[] WESTERN_NAMES_CN = {
+            "摩羯座", "水瓶座", "双鱼座", "白羊座", "金牛座", "双子座",
+            "巨蟹座", "狮子座", "处女座", "天秤座", "天蝎座", "射手座"
+    };
+
+    /** 星座元素:fire/earth/air/water */
+    private static final String[] WESTERN_ELEMENTS = {
+            "earth", "air", "water",
+            "fire", "earth", "air",
+            "water", "fire", "earth",
+            "air", "water", "fire"
+    };
+
+    /** 星座模态:cardinal/fixed/mutable */
+    private static final String[] WESTERN_MODALITIES = {
+            "fixed", "cardinal", "mutable",
+            "cardinal", "fixed", "mutable",
+            "fixed", "cardinal", "mutable",
+            "cardinal", "fixed", "mutable"
+    };
+
+    /** 星座日期范围 [startMonth, startDay, endMonth, endDay],跨年星座(摩羯)end 设为 12/31 */
+    private static final int[][] WESTERN_RANGES = {
+            {1, 20, 2, 18},    // capricorn  (cross-year: Jan-Feb)
+            {1, 20, 2, 18},    // aquarius   (Feb)
+            {2, 19, 3, 20},    // pisces     (Mar)
+            {3, 21, 4, 19},    // aries      (Apr)
+            {4, 20, 5, 20},    // taurus     (May)
+            {5, 21, 6, 20},    // gemini     (Jun)
+            {6, 21, 7, 22},    // cancer     (Jul)
+            {7, 23, 8, 22},    // leo        (Aug)
+            {8, 23, 9, 22},    // virgo      (Sep)
+            {9, 23, 10, 22},   // libra      (Oct)
+            {10, 23, 11, 21},  // scorpio    (Nov)
+            {11, 22, 12, 21},  // sagittarius (Dec)
+    };
+
+    /** 元素 → 五维映射(先天倾向)*/
+    private static final Map<String, DimensionBias> ELEMENT_BIASES = new LinkedHashMap<String, DimensionBias>() {{
+        put("fire",  new DimensionBias(55, 72, 50, 78, 48));  // 火:行强心高
+        put("earth", new DimensionBias(78, 50, 55, 50, 72));  // 土:身富强智中
+        put("air",   new DimensionBias(50, 55, 78, 50, 55));  // 风:智强心身中
+        put("water", new DimensionBias(50, 78, 55, 55, 50));  // 水:心强身智中
+    }};
+
+    /** 模态 → 五维修正 */
+    private static final Map<String, DimensionBias> MODALITY_BIASES = new LinkedHashMap<String, DimensionBias>() {{
+        put("cardinal", new DimensionBias(60, 55, 55, 75, 55));  // 开创:行动强
+        put("fixed",    new DimensionBias(70, 65, 60, 55, 70));  // 固定:身富稳
+        put("mutable",  new DimensionBias(55, 75, 70, 60, 55));  // 变动:心智活
+    }};
+
+    // ==================== 中国生肖体系 ====================
+
+    /** 十二生肖代码(与 FamilyMemberAttributeService 一致)*/
+    static final String[] ZODIAC_CODES = {"rat", "ox", "tiger", "rabbit", "dragon", "snake",
+            "horse", "sheep", "monkey", "rooster", "dog", "pig"};
+
+    /** 生肖中文名 */
+    private static final String[] ZODIAC_CN = {"鼠", "牛", "虎", "兔", "龙", "蛇",
+            "马", "羊", "猴", "鸡", "狗", "猪"};
+
+    /** 生肖五行 */
+    private static final String[] ZODIAC_ELEMENTS = {"水", "土", "木", "木", "土", "火",
+            "火", "土", "金", "金", "土", "水"};
+
+    /** 地支六冲偏移 */
+    private static final int[] CHONG_OFFSET = {6, 7, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5};
+
+    /** 地支六合 */
+    private static final Map<String, String> LIU_HE = new LinkedHashMap<String, String>() {{
+        put("rat", "ox"); put("ox", "rat");
+        put("tiger", "pig"); put("pig", "tiger");
+        put("rabbit", "dog"); put("dog", "rabbit");
+        put("dragon", "monkey"); put("monkey", "dragon");
+        put("snake", "rooster"); put("rooster", "snake");
+        put("horse", "sheep"); put("sheep", "horse");
+    }};
+
+    /** 地支三合 */
+    private static final Map<String, List<String>> SAN_HE = new LinkedHashMap<String, List<String>>() {{
+        put("rat", Arrays.asList("dragon", "monkey"));
+        put("dragon", Arrays.asList("monkey", "rat"));
+        put("monkey", Arrays.asList("rat", "dragon"));
+        put("ox", Arrays.asList("snake", "rooster"));
+        put("snake", Arrays.asList("rooster", "ox"));
+        put("rooster", Arrays.asList("ox", "snake"));
+        put("tiger", Arrays.asList("horse", "dog"));
+        put("horse", Arrays.asList("dog", "tiger"));
+        put("dog", Arrays.asList("tiger", "horse"));
+        put("rabbit", Arrays.asList("sheep", "pig"));
+        put("sheep", Arrays.asList("rabbit", "pig"));
+        put("pig", Arrays.asList("rabbit", "sheep"));
+    }};
+
+    /** 五行 → 五维基础映射 */
+    private static final Map<String, DimensionBias> WUXING_BIASES = new LinkedHashMap<String, DimensionBias>() {{
+        put("金", new DimensionBias(50, 60, 78, 50, 60));   // 金:智心偏强
+        put("木", new DimensionBias(55, 55, 55, 78, 50));   // 木:行偏强
+        put("水", new DimensionBias(50, 78, 60, 50, 55));   // 水:心偏强
+        put("火", new DimensionBias(60, 72, 50, 65, 50));   // 火:身心偏强
+        put("土", new DimensionBias(78, 50, 55, 50, 72));   // 土:身富偏强
+    }};
+
+    /**
+     * 五维能量结果
+     */
+    public static class AnnualEnergyResult {
+        private String westernSign;
+        private String westernSignCn;
+        private String westernElement;
+        private String westernModality;
+        private String zodiacCode;
+        private String zodiacName;
+        private String yearBranch;
+        private String yearRelation;
+        private String jupiterPhase;
+        private String saturnPhase;
+        private int body;
+        private int mind;
+        private int wisdom;
+        private int action;
+        private int wealth;
+
+        public String getWesternSign() { return westernSign; }
+        public void setWesternSign(String westernSign) { this.westernSign = westernSign; }
+        public String getWesternSignCn() { return westernSignCn; }
+        public void setWesternSignCn(String westernSignCn) { this.westernSignCn = westernSignCn; }
+        public String getWesternElement() { return westernElement; }
+        public void setWesternElement(String westernElement) { this.westernElement = westernElement; }
+        public String getWesternModality() { return westernModality; }
+        public void setWesternModality(String westernModality) { this.westernModality = westernModality; }
+        public String getZodiacCode() { return zodiacCode; }
+        public void setZodiacCode(String zodiacCode) { this.zodiacCode = zodiacCode; }
+        public String getZodiacName() { return zodiacName; }
+        public void setZodiacName(String zodiacName) { this.zodiacName = zodiacName; }
+        public String getYearBranch() { return yearBranch; }
+        public void setYearBranch(String yearBranch) { this.yearBranch = yearBranch; }
+        public String getYearRelation() { return yearRelation; }
+        public void setYearRelation(String yearRelation) { this.yearRelation = yearRelation; }
+        public String getJupiterPhase() { return jupiterPhase; }
+        public void setJupiterPhase(String jupiterPhase) { this.jupiterPhase = jupiterPhase; }
+        public String getSaturnPhase() { return saturnPhase; }
+        public void setSaturnPhase(String saturnPhase) { this.saturnPhase = saturnPhase; }
+        public int getBody() { return body; }
+        public void setBody(int body) { this.body = body; }
+        public int getMind() { return mind; }
+        public void setMind(int mind) { this.mind = mind; }
+        public int getWisdom() { return wisdom; }
+        public void setWisdom(int wisdom) { this.wisdom = wisdom; }
+        public int getAction() { return action; }
+        public void setAction(int action) { this.action = action; }
+        public int getWealth() { return wealth; }
+        public void setWealth(int wealth) { this.wealth = wealth; }
+
+        /** 五维能量 Map */
+        public Map<String, Integer> toMap() {
+            Map<String, Integer> m = new LinkedHashMap<>();
+            m.put("body", body);
+            m.put("mind", mind);
+            m.put("wisdom", wisdom);
+            m.put("action", action);
+            m.put("wealth", wealth);
+            return m;
+        }
+    }
+
+    /** 五维偏差载体 */
+    static class DimensionBias {
+        final int body, mind, wisdom, action, wealth;
+        DimensionBias(int body, int mind, int wisdom, int action, int wealth) {
+            this.body = body; this.mind = mind; this.wisdom = wisdom;
+            this.action = action; this.wealth = wealth;
+        }
+    }
+
+    // ==================== 主入口 ====================
+
+    /**
+     * 计算某成员当年的五维能量值(融合东西方体系)
+     *
+     * @param birthYear  出生年份
+     * @param birthMonth 出生月份(西方星座用)
+     * @param birthDay   出生日期(西方星座用)
+     * @param zodiacCode 生肖代码(选填,null 则自动推算)
+     * @param targetYear 目标年份(默认今年)
+     * @return 五维能量结果
+     */
+    public AnnualEnergyResult calculate(int birthYear, int birthMonth, int birthDay,
+                                        String zodiacCode, Integer targetYear) {
+        if (birthYear < 1900 || birthYear > targetYearOrDefault(targetYear) - 1) {
+            return emptyResult();
+        }
+
+        int year = targetYear != null ? targetYear : Calendar.getInstance().get(Calendar.YEAR);
+
+        // 1. 西方星座
+        String westernSign = findWesternSign(birthMonth, birthDay);
+        int westernIdx = indexOfWestern(westernSign);
+        String element = westernIdx >= 0 ? WESTERN_ELEMENTS[westernIdx] : "earth";
+        String modality = westernIdx >= 0 ? WESTERN_MODALITIES[westernIdx] : "fixed";
+
+        // 2. 中国生肖
+        if (zodiacCode == null) {
+            zodiacCode = ZODIAC_CODES[(birthYear - 4) % 12];
+            int idx = (birthYear - 4) % 12;
+            if (idx < 0) idx += 12;
+            zodiacCode = ZODIAC_CODES[idx];
+        }
+        int zodiacIdx = indexOf(zodiacCode);
+        if (zodiacIdx < 0) zodiacIdx = 0;
+
+        // 3. 当年地支
+        int yearIdx = (year - 4) % 12;
+        if (yearIdx < 0) yearIdx += 12;
+        String yearBranch = ZODIAC_CODES[yearIdx];
+        String yearBranchCn = ZODIAC_CN[yearIdx];
+
+        // 4. 太岁关系
+        String relation = calcRelation(zodiacIdx, yearIdx);
+
+        // 5. 行星周期(木星12年回归 / 土星29.5年回归)
+        String jupiterPhase = calcJupiterPhase(birthYear, year);
+        String saturnPhase = calcSaturnPhase(birthYear, year);
+
+        // 6. 融合计算
+        AnnualEnergyResult result = new AnnualEnergyResult();
+        result.setWesternSign(westernSign);
+        result.setWesternSignCn(westernIdx >= 0 ? WESTERN_NAMES_CN[westernIdx] : "");
+        result.setWesternElement(element);
+        result.setWesternModality(modality);
+        result.setZodiacCode(zodiacCode);
+        result.setZodiacName(findZodiacName(zodiacCode));
+        result.setYearBranch(yearBranchCn + "年");
+        result.setYearRelation(relation);
+        result.setJupiterPhase(jupiterPhase);
+        result.setSaturnPhase(saturnPhase);
+
+        // 西方部分(60%)
+        DimensionBias eastBias = ELEMENT_BIASES.getOrDefault(element, new DimensionBias(55, 55, 55, 55, 55));
+        DimensionBias modBias = MODALITY_BIASES.getOrDefault(modality, new DimensionBias(55, 55, 55, 55, 55));
+        DimensionBias wuxingBias = WUXING_BIASES.getOrDefault(ZODIAC_ELEMENTS[zodiacIdx], new DimensionBias(55, 55, 55, 55, 55));
+
+        int wBody = round60(eastBias.body, modBias.body, wuxingBias.body, relation, zodiacIdx, yearIdx);
+        int wMind = round60(eastBias.mind, modBias.mind, wuxingBias.mind, relation, zodiacIdx, yearIdx);
+        int wWisdom = round60(eastBias.wisdom, modBias.wisdom, wuxingBias.wisdom, relation, zodiacIdx, yearIdx);
+        int wAction = round60(eastBias.action, modBias.action, wuxingBias.action, relation, zodiacIdx, yearIdx);
+        int wWealth = round60(eastBias.wealth, modBias.wealth, wuxingBias.wealth, relation, zodiacIdx, yearIdx);
+
+        // 查 zodiac_config 覆盖(如果有配置)
+        ZodiacConfig config = findByCode(zodiacCode);
+        if (config != null) {
+            // 用配置的 mindBase/wisdomBase 覆盖心和智
+            if (config.getMindBase() != null && config.getMindBase() > 0) {
+                wMind = clamp((int) Math.round(wMind * 0.6 + config.getMindBase() * 0.4), relation);
+            }
+            if (config.getWisdomBase() != null && config.getWisdomBase() > 0) {
+                wWisdom = clamp((int) Math.round(wWisdom * 0.6 + config.getWisdomBase() * 0.4), relation);
+            }
+            // 用配置的修正系数覆盖身/行/富
+            wBody = applyModifier(wBody, config.getBodyModifier(), relation);
+            wAction = applyModifier(wAction, config.getActionModifier(), relation);
+            wWealth = applyModifier(wWealth, config.getWealthModifier(), relation);
+        }
+
+        result.setBody(wBody);
+        result.setMind(wMind);
+        result.setWisdom(wWisdom);
+        result.setAction(wAction);
+        result.setWealth(wWealth);
+
+        return result;
+    }
+
+    /**
+     * 简化的快捷方法(仅传出生年份 + 生肖)
+     */
+    public AnnualEnergyResult calculate(Integer birthYear, String zodiacCode, Integer targetYear) {
+        // 无法计算西方星座,退化为纯中国生肖模式
+        if (birthYear == null || birthYear < 1900) {
+            return emptyResult();
+        }
+        int year = targetYear != null ? targetYear : Calendar.getInstance().get(Calendar.YEAR);
+        if (zodiacCode == null) {
+            int idx = (birthYear - 4) % 12;
+            if (idx < 0) idx += 12;
+            zodiacCode = ZODIAC_CODES[idx];
+        }
+        int zodiacIdx = indexOf(zodiacCode);
+        if (zodiacIdx < 0) return emptyResult();
+
+        int yearIdx = (year - 4) % 12;
+        if (yearIdx < 0) yearIdx += 12;
+        String yearBranchCn = ZODIAC_CN[yearIdx];
+        String relation = calcRelation(zodiacIdx, yearIdx);
+
+        AnnualEnergyResult result = new AnnualEnergyResult();
+        result.setWesternSign("");
+        result.setWesternSignCn("");
+        result.setWesternElement("");
+        result.setWesternModality("");
+        result.setZodiacCode(zodiacCode);
+        result.setZodiacName(findZodiacName(zodiacCode));
+        result.setYearBranch(yearBranchCn + "年");
+        result.setYearRelation(relation);
+        result.setJupiterPhase("");
+        result.setSaturnPhase("");
+
+        ZodiacConfig config = findByCode(zodiacCode);
+        if (config != null) {
+            result.setBody(clamp(60 + (config.getBodyModifier() != null ? config.getBodyModifier() / 10 : 0), relation));
+            result.setMind(clamp(config.getMindBase() != null ? config.getMindBase() : 50, relation));
+            result.setWisdom(clamp(config.getWisdomBase() != null ? config.getWisdomBase() : 50, relation));
+            result.setAction(clamp(55 + (config.getActionModifier() != null ? config.getActionModifier() / 10 : 0), relation));
+            result.setWealth(clamp(50 + (config.getWealthModifier() != null ? config.getWealthModifier() / 10 : 0), relation));
+        } else {
+            result.setBody(50); result.setMind(50); result.setWisdom(50);
+            result.setAction(50); result.setWealth(50);
+        }
+        return result;
+    }
+
+    /**
+     * 根据生肖代码计算当年五维能量(快捷方法)
+     */
+    public AnnualEnergyResult calculateByZodiac(String zodiacCode, Integer targetYear) {
+        return calculate(null, zodiacCode, targetYear);
+    }
+
+    /**
+     * 根据出生年份自动推算生肖并计算
+     */
+    public AnnualEnergyResult calculateByBirthYear(int birthYear, Integer targetYear) {
+        return calculate(birthYear, 0, 0, null, targetYear);
+    }
+
+    // ==================== 内部计算逻辑 ====================
+
+    /** 60/40 加权融合 + 关系钳制 */
+    private int round60(int eastern, int chinese, int wuxing, String relation, int zIdx, int yIdx) {
+        // 西方星座 60%
+        int west = (eastern + chinese) / 2;
+        // 中国生肖 40%
+        int cn = wuxing;
+        // 太岁关系修正
+        int combined = (int) Math.round(west * 0.6 + cn * 0.4);
+        return clamp(combined, relation);
+    }
+
+    /** 应用 zodiac_config 修正系数(千分比)*/
+    private int applyModifier(int base, Integer modifier, String relation) {
+        if (modifier == null) return clamp(base, relation);
+        double mult = getRelationMultiplier(relation);
+        int adj = (int) Math.round(modifier * mult / 1000.0);
+        return clamp(base + adj, relation);
+    }
+
+    /** 判定太岁关系 */
+    private String calcRelation(int zodiacIdx, int yearIdx) {
+        if (zodiacIdx == yearIdx) return "benming";
+        if (CHONG_OFFSET[zodiacIdx] == yearIdx) return "chong";
+        String code = ZODIAC_CODES[zodiacIdx];
+        if (LIU_HE.get(code).equals(ZODIAC_CODES[yearIdx])) return "liuhe";
+        List<String> sanHe = SAN_HE.get(code);
+        if (sanHe != null && sanHe.contains(ZODIAC_CODES[yearIdx])) return "sanhe";
+        if ((zodiacIdx + 7) % 12 == yearIdx) return "hai";
+        return "ping";
+    }
+
+    /** 钳制分数 + 关系微调 */
+    private int clamp(int score, String relation) {
+        switch (relation) {
+            case "sanhe":    score = (int) Math.round(score * 1.08); break;
+            case "liuhe":    score = (int) Math.round(score * 1.12); break;
+            case "benming":  score = (int) Math.round(score * 0.92); break;
+            case "chong":    score = (int) Math.round(score * 0.85); break;
+            case "hai":      score = (int) Math.round(score * 0.90); break;
+            default:         break;
+        }
+        return Math.max(0, Math.min(100, score));
+    }
+
+    /** 关系乘数 */
+    private double getRelationMultiplier(String relation) {
+        switch (relation) {
+            case "sanhe":     return 1.15;
+            case "liuhe":     return 1.25;
+            case "benming":   return 0.80;
+            case "chong":     return 0.65;
+            case "hai":       return 0.75;
+            default:          return 1.0;
+        }
+    }
+
+    // ==================== 西方星座 ====================
+
+    /** 根据月日查找太阳星座 */
+    private String findWesternSign(int month, int day) {
+        if (month < 1 || month > 12 || day < 1 || day > 31) {
+            return "capricorn"; // fallback
+        }
+        // 按月份排序遍历(避免跨年摩羯的边界问题)
+        int[][] monthOrder = {
+                {1, 20, 2, 18}, {2, 19, 3, 20}, {3, 21, 4, 19},
+                {4, 20, 5, 20}, {5, 21, 6, 20}, {6, 21, 7, 22},
+                {7, 23, 8, 22}, {8, 23, 9, 22}, {9, 23, 10, 22},
+                {10, 23, 11, 21}, {11, 22, 12, 21}, {12, 22, 1, 19}
+        };
+        String[] signs = {
+                "aquarius", "pisces", "aries", "taurus", "gemini", "cancer",
+                "leo", "virgo", "libra", "scorpio", "sagittarius", "capricorn"
+        };
+        for (int i = 0; i < monthOrder.length; i++) {
+            int sm = monthOrder[i][0], sd = monthOrder[i][1];
+            int em = monthOrder[i][2], ed = monthOrder[i][3];
+            if (dateInRange(month, day, sm, sd, em, ed)) {
+                return signs[i];
+            }
+        }
+        return "capricorn";
+    }
+
+    /** 日期是否在范围内(支持跨年)*/
+    private boolean dateInRange(int m, int d, int sm, int sd, int em, int ed) {
+        int input = m * 100 + d;
+        if (sm <= em) {
+            // 同年内
+            return input >= sm * 100 + sd && input <= em * 100 + ed;
+        } else {
+            // 跨年(如摩羯 1/20 - 2/18 实际是上一年的1/20到2/18,这里简化处理)
+            return input >= sm * 100 + sd || input <= em * 100 + ed;
+        }
+    }
+
+    private int indexOfWestern(String code) {
+        for (int i = 0; i < WESTERN_SIGNS.length; i++) {
+            if (WESTERN_SIGNS[i].equals(code)) return i;
+        }
+        return -1;
+    }
+
+    // ==================== 行星周期 ====================
+
+    /** 木星周期(约12年),基于出生年判断当前相位 */
+    private String calcJupiterPhase(int birthYear, int targetYear) {
+        int diff = targetYear - birthYear;
+        int phase = diff % 12;
+        if (phase == 0) return "conjunction";     // 木星合相(本命回归)
+        if (phase == 6) return "opposition";       // 木星对冲
+        if (phase <= 3) return "waxing";           // 渐盈
+        if (phase <= 9) return "waning";           // 渐亏
+        return "neutral";
+    }
+
+    /** 土星周期(约29.5年),基于出生年判断当前相位 */
+    private String calcSaturnPhase(int birthYear, int targetYear) {
+        int diff = targetYear - birthYear;
+        // 取整到最近整数周期
+        int cycles = Math.round(diff / 29.5f);
+        int phase = diff - cycles * 29;
+        if (phase <= 2) return "saturn-return";    // 土星回归
+        if (phase >= 27) return "saturn-return";
+        if (phase == 14 || phase == 15) return "opposition";
+        return "transit";
+    }
+
+    private int targetYearOrDefault(Integer targetYear) {
+        return targetYear != null ? targetYear : Calendar.getInstance().get(Calendar.YEAR);
+    }
+
+    // ==================== 辅助方法 ====================
+
+    private ZodiacConfig findByCode(String zodiacCode) {
+        return zodiacConfigMapper.selectOne(
+                new LambdaQueryWrapper<ZodiacConfig>()
+                        .eq(ZodiacConfig::getZodiacCode, zodiacCode));
+    }
+
+    private int indexOf(String code) {
+        for (int i = 0; i < ZODIAC_CODES.length; i++) {
+            if (ZODIAC_CODES[i].equals(code)) return i;
+        }
+        return -1;
+    }
+
+    private String findZodiacName(String code) {
+        int idx = indexOf(code);
+        return idx >= 0 ? ZODIAC_CN[idx] : code;
+    }
+
+    private AnnualEnergyResult emptyResult() {
+        AnnualEnergyResult r = new AnnualEnergyResult();
+        r.setWesternSign(""); r.setWesternSignCn("");
+        r.setWesternElement(""); r.setWesternModality("");
+        r.setZodiacCode(""); r.setZodiacName("");
+        r.setYearBranch(""); r.setYearRelation("unknown");
+        r.setJupiterPhase(""); r.setSaturnPhase("");
+        r.setBody(0); r.setMind(0); r.setWisdom(0);
+        r.setAction(0); r.setWealth(0);
+        return r;
+    }
+}