CompatibilityService.java 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. package com.etotem.cfc.service;
  2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  3. import com.etotem.cfc.dto.*;
  4. import com.etotem.cfc.entity.*;
  5. import com.etotem.cfc.entity.Family;
  6. import com.etotem.cfc.entity.FamilyMember;
  7. import com.etotem.cfc.entity.FamilyMemberAttributes;
  8. import com.etotem.cfc.entity.ZodiacPairing;
  9. import com.etotem.cfc.entity.BloodTypePairing;
  10. import com.etotem.cfc.entity.LifeNumberRelationship;
  11. import com.etotem.cfc.mapper.*;
  12. import org.springframework.stereotype.Service;
  13. import javax.annotation.Resource;
  14. import java.util.*;
  15. import java.util.stream.Collectors;
  16. import com.etotem.cfc.util.SortUtil;
  17. @Service
  18. public class CompatibilityService {
  19. @Resource
  20. private FamilyMemberAttributeService familyMemberAttributeService;
  21. @Resource
  22. private FamilyMemberMapper familyMemberMapper;
  23. @Resource
  24. private FamilyMapper familyMapper;
  25. @Resource
  26. private ZodiacPairingMapper zodiacPairingMapper;
  27. @Resource
  28. private BloodTypePairingMapper bloodTypePairingMapper;
  29. @Resource
  30. private LifeNumberRelationshipMapper lifeNumberRelationshipMapper;
  31. private static final String[] ZODIAC_CODES = {"rat", "ox", "tiger", "rabbit", "dragon", "snake",
  32. "horse", "sheep", "monkey", "rooster", "dog", "pig"};
  33. private static final String[] WUXING_ELEMENTS = {"wood", "fire", "earth", "metal", "water"};
  34. /**
  35. * Full duo compatibility analysis
  36. */
  37. public CompatibilityResultVO computeCompatibility(Long memberId1, String memberType1,
  38. Long memberId2, String memberType2) {
  39. FamilyMemberVO member1 = buildMemberVO(memberId1);
  40. FamilyMemberVO member2 = buildMemberVO(memberId2);
  41. FamilyMemberAttributes attrs1 = familyMemberAttributeService.getByMember(memberId1, memberType1);
  42. FamilyMemberAttributes attrs2 = familyMemberAttributeService.getByMember(memberId2, memberType2);
  43. CompatibilityResultVO result = new CompatibilityResultVO();
  44. result.setMember1(member1);
  45. result.setMember2(member2);
  46. // Zodiac
  47. ZodiacCompatibilityVO zodiac = computeZodiac(attrs1, attrs2);
  48. result.setZodiac(zodiac);
  49. // Wuxing
  50. WuxingComplementarityVO wuxing = computeWuxing(attrs1, attrs2);
  51. result.setWuxing(wuxing);
  52. // Blood type
  53. BloodTypeCompatibilityVO bloodType = computeBloodType(attrs1, attrs2);
  54. result.setBloodType(bloodType);
  55. // Life number
  56. LifeNumberRelationshipVO lifeNumber = computeLifeNumber(attrs1, attrs2);
  57. result.setLifeNumber(lifeNumber);
  58. // Overall: zodiac 40% + wuxing 30% + blood 15% + lifeNumber 15%
  59. int zodiacScore = zodiac != null ? zodiac.getCompatibilityScore() : 50;
  60. int wuxingScore = wuxing != null ? wuxing.getComplementarityScore() : 50;
  61. int bloodScore = bloodType != null ? bloodType.getCompatibilityScore() : 50;
  62. int lifeScore = lifeNumber != null ? 70 : 50; // life number doesn't have a score, base 70 for having one
  63. int overall = (zodiacScore * 40 + wuxingScore * 30 + bloodScore * 15 + lifeScore * 15) / 100;
  64. result.setOverallScore(overall);
  65. // Overall advice
  66. result.setOverallAdvice(generateOverallAdvice(overall, zodiac, wuxing, bloodType, lifeNumber));
  67. return result;
  68. }
  69. /**
  70. * Family dashboard: all members + aggregated wuxing + pairwise summaries
  71. */
  72. public FamilyDashboardVO getFamilyDashboard(Long familyId, Long perspectiveMemberId, String perspectiveMemberType) {
  73. FamilyDashboardVO dashboard = new FamilyDashboardVO();
  74. // Get all family members
  75. LambdaQueryWrapper<FamilyMember> wrapper = new LambdaQueryWrapper<>();
  76. wrapper.eq(FamilyMember::getFamilyId, familyId);
  77. SortUtil.applySort(wrapper);
  78. SortUtil.applySort(wrapper);
  79. List<FamilyMember> members = familyMemberMapper.selectList(wrapper);
  80. if (members.isEmpty()) return dashboard;
  81. Family family = familyMapper.selectById(familyId);
  82. dashboard.setFamilyId(familyId);
  83. dashboard.setFamilyName(family != null ? family.getName() : null);
  84. // Build member tradition data
  85. List<FamilyMemberTraditionVO> traditionList = new ArrayList<>();
  86. for (FamilyMember member : members) {
  87. FamilyMemberTraditionVO vo = buildMemberTradition(member);
  88. traditionList.add(vo);
  89. }
  90. dashboard.setMembers(traditionList);
  91. // Family wuxing aggregation
  92. dashboard.setFamilyWuxing(aggregateWuxing(traditionList));
  93. // Pairwise summaries
  94. List<RelationshipSummaryVO> relationships = new ArrayList<>();
  95. for (int i = 0; i < traditionList.size(); i++) {
  96. for (int j = i + 1; j < traditionList.size(); j++) {
  97. FamilyMemberTraditionVO m1 = traditionList.get(i);
  98. FamilyMemberTraditionVO m2 = traditionList.get(j);
  99. RelationshipSummaryVO summary = computeRelationshipSummary(m1, m2);
  100. relationships.add(summary);
  101. }
  102. }
  103. dashboard.setRelationships(relationships);
  104. // Weekly insight
  105. dashboard.setWeeklyInsight(generateWeeklyInsight(traditionList, relationships));
  106. return dashboard;
  107. }
  108. // ========== Zodiac compatibility ==========
  109. private ZodiacCompatibilityVO computeZodiac(FamilyMemberAttributes attrs1, FamilyMemberAttributes attrs2) {
  110. if (attrs1 == null || attrs2 == null) return null;
  111. String zodiac1 = attrs1.getZodiac();
  112. String zodiac2 = attrs2.getZodiac();
  113. if (zodiac1 == null || zodiac2 == null) return null;
  114. LambdaQueryWrapper<ZodiacPairing> qw = new LambdaQueryWrapper<>();
  115. // Look up both orderings
  116. qw.eq(ZodiacPairing::getZodiacCode1, zodiac1);
  117. qw.eq(ZodiacPairing::getZodiacCode2, zodiac2);
  118. SortUtil.applySort(qw);
  119. ZodiacPairing pairing = zodiacPairingMapper.selectOne(qw);
  120. if (pairing == null) {
  121. // Try reverse order
  122. LambdaQueryWrapper<ZodiacPairing> reverseQw = new LambdaQueryWrapper<>();
  123. reverseQw.eq(ZodiacPairing::getZodiacCode1, zodiac2);
  124. reverseQw.eq(ZodiacPairing::getZodiacCode2, zodiac1);
  125. SortUtil.applySort(reverseQw);
  126. pairing = zodiacPairingMapper.selectOne(reverseQw);
  127. }
  128. ZodiacCompatibilityVO vo = new ZodiacCompatibilityVO();
  129. vo.setZodiacCode1(zodiac1);
  130. vo.setZodiacCode2(zodiac2);
  131. if (pairing != null) {
  132. vo.setPairingType(pairing.getPairingType());
  133. vo.setCompatibilityScore(pairing.getCompatibilityScore());
  134. vo.setDescription(pairing.getDescription());
  135. vo.setAdvice(pairing.getAdvice());
  136. } else {
  137. vo.setPairingType("ping");
  138. vo.setCompatibilityScore(50);
  139. vo.setDescription("暂无配对数据");
  140. vo.setAdvice("互相尊重,用心相处");
  141. }
  142. return vo;
  143. }
  144. // ========== Wuxing complementarity ==========
  145. private WuxingComplementarityVO computeWuxing(FamilyMemberAttributes attrs1, FamilyMemberAttributes attrs2) {
  146. if (attrs1 == null || attrs2 == null) return null;
  147. Map<String, Integer> wx1 = parseWuxingMap(attrs1.getWuxingElements());
  148. Map<String, Integer> wx2 = parseWuxingMap(attrs2.getWuxingElements());
  149. if (wx1.isEmpty() || wx2.isEmpty()) return null;
  150. // Compute complementarity: for each element, reward shared strength AND complementary difference
  151. int totalComp = 0;
  152. StringBuilder analysis = new StringBuilder();
  153. List<String> complementaryElements = new ArrayList<>();
  154. List<String> sharedElements = new ArrayList<>();
  155. for (String elem : WUXING_ELEMENTS) {
  156. int v1 = wx1.getOrDefault(elem, 0);
  157. int v2 = wx2.getOrDefault(elem, 0);
  158. int avg = (v1 + v2) / 2;
  159. int diff = Math.abs(v1 - v2);
  160. // complementarityScore for this element: avg (shared strength) + diff (complementary gap)
  161. // Scale: max per element score is ~100 (when avg=50 and diff=50)
  162. int elemComp = Math.min(100, avg + diff);
  163. totalComp += elemComp;
  164. if (diff >= 20) {
  165. complementaryElements.add(elem);
  166. }
  167. if (avg >= 30 && diff < 15) {
  168. sharedElements.add(elem);
  169. }
  170. }
  171. int avgScore = totalComp / 5;
  172. // Build analysis text
  173. if (!complementaryElements.isEmpty()) {
  174. analysis.append("五行互补: ");
  175. analysis.append(String.join("、", complementaryElements));
  176. analysis.append("元素互补性强,彼此弥补短板。");
  177. }
  178. if (!sharedElements.isEmpty()) {
  179. if (analysis.length() > 0) analysis.append(" ");
  180. analysis.append("五行共鸣: ");
  181. analysis.append(String.join("、", sharedElements));
  182. analysis.append("元素能量相近,容易产生默契。");
  183. }
  184. if (complementaryElements.isEmpty() && sharedElements.isEmpty()) {
  185. analysis.append("五行分布较为独立,各有所长。");
  186. }
  187. WuxingComplementarityVO vo = new WuxingComplementarityVO();
  188. vo.setWuxing1(wx1);
  189. vo.setWuxing2(wx2);
  190. vo.setComplementarityScore(avgScore);
  191. vo.setAnalysis(analysis.toString());
  192. return vo;
  193. }
  194. // ========== Blood type compatibility ==========
  195. private BloodTypeCompatibilityVO computeBloodType(FamilyMemberAttributes attrs1, FamilyMemberAttributes attrs2) {
  196. if (attrs1 == null || attrs2 == null) return null;
  197. String bt1 = attrs1.getBloodType();
  198. String bt2 = attrs2.getBloodType();
  199. if (bt1 == null || bt2 == null) return null;
  200. LambdaQueryWrapper<BloodTypePairing> qw = new LambdaQueryWrapper<>();
  201. qw.eq(BloodTypePairing::getBloodType1, bt1);
  202. qw.eq(BloodTypePairing::getBloodType2, bt2);
  203. SortUtil.applySort(qw);
  204. BloodTypePairing pairing = bloodTypePairingMapper.selectOne(qw);
  205. if (pairing == null) {
  206. LambdaQueryWrapper<BloodTypePairing> reverseQw = new LambdaQueryWrapper<>();
  207. reverseQw.eq(BloodTypePairing::getBloodType1, bt2);
  208. reverseQw.eq(BloodTypePairing::getBloodType2, bt1);
  209. SortUtil.applySort(reverseQw);
  210. pairing = bloodTypePairingMapper.selectOne(reverseQw);
  211. }
  212. BloodTypeCompatibilityVO vo = new BloodTypeCompatibilityVO();
  213. vo.setBloodType1(bt1);
  214. vo.setBloodType2(bt2);
  215. if (pairing != null) {
  216. vo.setCompatibilityScore(pairing.getCompatibilityScore());
  217. vo.setDescription(pairing.getDescription());
  218. vo.setAdvice(pairing.getAdvice());
  219. } else {
  220. vo.setCompatibilityScore(50);
  221. vo.setDescription("暂无配对数据");
  222. vo.setAdvice("血型只是参考,用心相处最重要");
  223. }
  224. return vo;
  225. }
  226. // ========== Life number relationship ==========
  227. private LifeNumberRelationshipVO computeLifeNumber(FamilyMemberAttributes attrs1, FamilyMemberAttributes attrs2) {
  228. if (attrs1 == null || attrs2 == null) return null;
  229. Integer lifeNum1 = extractLifeNumber(attrs1.getBehaviorModifier());
  230. Integer lifeNum2 = extractLifeNumber(attrs2.getBehaviorModifier());
  231. if (lifeNum1 == null || lifeNum2 == null) return null;
  232. // Relationship number: digital root of sum
  233. int sum = lifeNum1 + lifeNum2;
  234. int relNum = sum;
  235. while (relNum >= 10) {
  236. int n = relNum;
  237. relNum = 0;
  238. while (n > 0) {
  239. relNum += n % 10;
  240. n /= 10;
  241. }
  242. }
  243. LifeNumberRelationship relationship = lifeNumberRelationshipMapper.selectById(relNum);
  244. LifeNumberRelationshipVO vo = new LifeNumberRelationshipVO();
  245. vo.setLifeNumber1(lifeNum1);
  246. vo.setLifeNumber2(lifeNum2);
  247. vo.setRelationshipNumber(relNum);
  248. if (relationship != null) {
  249. vo.setTitle(relationship.getTitle());
  250. vo.setDescription(relationship.getDescription());
  251. vo.setAdvice(relationship.getAdvice());
  252. vo.setColorHex(relationship.getColorHex());
  253. }
  254. return vo;
  255. }
  256. // ========== Dashboard helpers ==========
  257. private FamilyMemberTraditionVO buildMemberTradition(FamilyMember member) {
  258. FamilyMemberTraditionVO vo = new FamilyMemberTraditionVO();
  259. vo.setMemberId(member.getId());
  260. vo.setNickname(member.getNickname());
  261. vo.setMemberType("child");
  262. FamilyMemberAttributes attrs = familyMemberAttributeService.getByMember(member.getId(), "child");
  263. if (attrs == null) {
  264. attrs = familyMemberAttributeService.getByMember(member.getId(), "parent");
  265. if (attrs != null) vo.setMemberType("parent");
  266. }
  267. if (attrs != null) {
  268. vo.setZodiac(attrs.getZodiac());
  269. vo.setBloodType(attrs.getBloodType());
  270. vo.setMindBaseScore(attrs.getMindBaseScore());
  271. vo.setWisdomBaseScore(attrs.getWisdomBaseScore());
  272. vo.setEightCharacters(parseJsonToMap(attrs.getEightCharacters()));
  273. vo.setWuxingElements(parseWuxingMap(attrs.getWuxingElements()));
  274. vo.setLifeNumber(extractLifeNumber(attrs.getBehaviorModifier()));
  275. }
  276. return vo;
  277. }
  278. private FamilyWuxingVO aggregateWuxing(List<FamilyMemberTraditionVO> members) {
  279. FamilyWuxingVO vo = new FamilyWuxingVO();
  280. Map<String, Integer> totals = new LinkedHashMap<>();
  281. for (String elem : WUXING_ELEMENTS) {
  282. totals.put(elem, 0);
  283. }
  284. int count = 0;
  285. String maxMember = null;
  286. int maxBalanceScore = -1;
  287. for (FamilyMemberTraditionVO m : members) {
  288. if (m.getWuxingElements() != null) {
  289. count++;
  290. // Calculate balance score for this member
  291. int minVal = Integer.MAX_VALUE;
  292. int maxVal = 0;
  293. for (String elem : WUXING_ELEMENTS) {
  294. int val = m.getWuxingElements().getOrDefault(elem, 0);
  295. totals.put(elem, totals.get(elem) + val);
  296. if (val < minVal) minVal = val;
  297. if (val > maxVal) maxVal = val;
  298. }
  299. int balance = 100 - (maxVal - minVal);
  300. if (balance > maxBalanceScore) {
  301. maxBalanceScore = balance;
  302. maxMember = m.getNickname();
  303. }
  304. }
  305. }
  306. Map<String, Integer> pctMap = new LinkedHashMap<>();
  307. String strongest = null;
  308. String weakest = null;
  309. int maxPct = 0;
  310. int minPct = Integer.MAX_VALUE;
  311. int totalAll = 0;
  312. for (String elem : WUXING_ELEMENTS) {
  313. int val = count > 0 ? totals.get(elem) * 100 / (count * 100) : 0;
  314. pctMap.put(elem, val);
  315. totalAll += val;
  316. if (val > maxPct) { maxPct = val; strongest = elem; }
  317. if (val < minPct) { minPct = val; weakest = elem; }
  318. }
  319. vo.setAggregatePercentages(pctMap);
  320. vo.setStrongestElement(strongest);
  321. vo.setWeakestElement(weakest);
  322. vo.setBalancedMemberName(maxMember);
  323. StringBuilder analysis = new StringBuilder();
  324. if (strongest != null) {
  325. analysis.append("你家");
  326. analysis.append(elementLabel(strongest));
  327. analysis.append("能量最强");
  328. }
  329. if (weakest != null && minPct < 15) {
  330. if (analysis.length() > 0) analysis.append(",");
  331. analysis.append(elementLabel(weakest));
  332. analysis.append("能量偏弱");
  333. }
  334. if (maxMember != null) {
  335. if (analysis.length() > 0) analysis.append("。");
  336. analysis.append(maxMember);
  337. analysis.append("的五行最为均衡,是家庭的稳定器。");
  338. }
  339. if (analysis.length() == 0) {
  340. analysis.append("家庭五行分布均衡,整体和谐。");
  341. }
  342. vo.setAnalysis(analysis.toString());
  343. return vo;
  344. }
  345. private RelationshipSummaryVO computeRelationshipSummary(FamilyMemberTraditionVO m1, FamilyMemberTraditionVO m2) {
  346. RelationshipSummaryVO summary = new RelationshipSummaryVO();
  347. summary.setMemberId1(m1.getMemberId());
  348. summary.setNickname1(m1.getNickname());
  349. summary.setMemberId2(m2.getMemberId());
  350. summary.setNickname2(m2.getNickname());
  351. // Quick zodiac lookup
  352. int zodiacScore = 50;
  353. String pairingType = "ping";
  354. if (m1.getZodiac() != null && m2.getZodiac() != null) {
  355. LambdaQueryWrapper<ZodiacPairing> qw = new LambdaQueryWrapper<>();
  356. qw.eq(ZodiacPairing::getZodiacCode1, m1.getZodiac());
  357. qw.eq(ZodiacPairing::getZodiacCode2, m2.getZodiac());
  358. SortUtil.applySort(qw);
  359. ZodiacPairing p = zodiacPairingMapper.selectOne(qw);
  360. if (p == null) {
  361. LambdaQueryWrapper<ZodiacPairing> rqw = new LambdaQueryWrapper<>();
  362. rqw.eq(ZodiacPairing::getZodiacCode1, m2.getZodiac());
  363. rqw.eq(ZodiacPairing::getZodiacCode2, m1.getZodiac());
  364. SortUtil.applySort(qw);
  365. SortUtil.applySort(rqw);
  366. p = zodiacPairingMapper.selectOne(rqw);
  367. }
  368. if (p != null) {
  369. zodiacScore = p.getCompatibilityScore();
  370. pairingType = p.getPairingType();
  371. summary.setSummary(p.getDescription());
  372. }
  373. }
  374. summary.setZodiacScore(zodiacScore);
  375. summary.setZodiacPairingType(pairingType);
  376. // Overall = zodiac + wuxing (if available)
  377. int wuxingScore = 50;
  378. if (m1.getWuxingElements() != null && m2.getWuxingElements() != null) {
  379. int total = 0;
  380. for (String elem : WUXING_ELEMENTS) {
  381. int v1 = m1.getWuxingElements().getOrDefault(elem, 0);
  382. int v2 = m2.getWuxingElements().getOrDefault(elem, 0);
  383. total += Math.min(100, (v1 + v2) / 2 + Math.abs(v1 - v2));
  384. }
  385. wuxingScore = total / 5;
  386. }
  387. int overall = (zodiacScore * 55 + wuxingScore * 45) / 100;
  388. summary.setOverallScore(overall);
  389. if (summary.getSummary() == null) {
  390. if (overall >= 80) {
  391. summary.setSummary("非常融洽");
  392. } else if (overall >= 60) {
  393. summary.setSummary("相处和谐");
  394. } else if (overall >= 40) {
  395. summary.setSummary("需要磨合");
  396. } else {
  397. summary.setSummary("需要更多包容");
  398. }
  399. }
  400. return summary;
  401. }
  402. // ========== Insights ==========
  403. private String generateWeeklyInsight(List<FamilyMemberTraditionVO> members, List<RelationshipSummaryVO> relationships) {
  404. List<String> insights = new ArrayList<>();
  405. // Find the best relationship
  406. RelationshipSummaryVO best = null;
  407. RelationshipSummaryVO worst = null;
  408. for (RelationshipSummaryVO r : relationships) {
  409. if (best == null || r.getOverallScore() > best.getOverallScore()) best = r;
  410. if (worst == null || r.getOverallScore() < worst.getOverallScore()) worst = r;
  411. }
  412. if (best != null && best.getOverallScore() >= 80) {
  413. insights.add(best.getNickname1() + "和" + best.getNickname2() + "是天生的好搭档,本周可以一起完成一个家庭任务");
  414. }
  415. if (worst != null && worst.getOverallScore() < 50) {
  416. insights.add(worst.getNickname1() + "和" + worst.getNickname2() + "的命理需要磨合,本周建议多安排一些耐心沟通的时间");
  417. }
  418. if (insights.isEmpty()) {
  419. insights.add("本周家庭关系和谐,享受彼此的陪伴吧");
  420. }
  421. return String.join(";", insights);
  422. }
  423. private String generateOverallAdvice(int overall, ZodiacCompatibilityVO zodiac,
  424. WuxingComplementarityVO wuxing,
  425. BloodTypeCompatibilityVO bloodType,
  426. LifeNumberRelationshipVO lifeNumber) {
  427. List<String> parts = new ArrayList<>();
  428. if (overall >= 80) {
  429. parts.add("你们是天生的好搭档,命理高度契合。");
  430. } else if (overall >= 60) {
  431. parts.add("你们的关系有很好的基础,稍微注意互相包容会更完美。");
  432. } else if (overall >= 40) {
  433. parts.add("你们虽然有差异,但差异正是互补的来源。");
  434. } else {
  435. parts.add("你们需要更多耐心和理解来经营关系。");
  436. }
  437. if (zodiac != null && zodiac.getAdvice() != null) {
  438. parts.add("生肖方面:" + zodiac.getAdvice());
  439. }
  440. if (bloodType != null && bloodType.getAdvice() != null) {
  441. parts.add("血型方面:" + bloodType.getAdvice());
  442. }
  443. if (lifeNumber != null && lifeNumber.getAdvice() != null) {
  444. parts.add("灵数方面:" + lifeNumber.getAdvice());
  445. }
  446. return String.join("", parts);
  447. }
  448. // ========== Parsing helpers ==========
  449. @SuppressWarnings("unchecked")
  450. private Map<String, Integer> parseWuxingMap(String json) {
  451. Map<String, Integer> result = new LinkedHashMap<>();
  452. if (json == null || json.isEmpty()) return result;
  453. try {
  454. Map<String, Object> raw = familyMemberAttributeService.fromJson(json);
  455. for (String elem : WUXING_ELEMENTS) {
  456. Object val = raw.get(elem);
  457. if (val instanceof Number) {
  458. result.put(elem, ((Number) val).intValue());
  459. }
  460. }
  461. } catch (Exception ignored) {}
  462. return result;
  463. }
  464. @SuppressWarnings("unchecked")
  465. private Map<String, String> parseJsonToMap(String json) {
  466. Map<String, String> result = new LinkedHashMap<>();
  467. if (json == null || json.isEmpty()) return result;
  468. try {
  469. Map<String, Object> raw = familyMemberAttributeService.fromJson(json);
  470. for (Map.Entry<String, Object> entry : raw.entrySet()) {
  471. result.put(entry.getKey(), String.valueOf(entry.getValue()));
  472. }
  473. } catch (Exception ignored) {}
  474. return result;
  475. }
  476. private Integer extractLifeNumber(String behaviorModifier) {
  477. if (behaviorModifier == null || behaviorModifier.isEmpty()) return null;
  478. try {
  479. Map<String, Object> map = familyMemberAttributeService.fromJson(behaviorModifier);
  480. Object ln = map.get("lifeNumber");
  481. if (ln instanceof Number) {
  482. return ((Number) ln).intValue();
  483. }
  484. } catch (Exception ignored) {}
  485. return null;
  486. }
  487. private FamilyMemberVO buildMemberVO(Long memberId) {
  488. FamilyMember member = familyMemberMapper.selectById(memberId);
  489. if (member == null) return null;
  490. FamilyMemberVO vo = new FamilyMemberVO();
  491. vo.setId(member.getId());
  492. vo.setNickname(member.getNickname());
  493. vo.setMemberType(member.getGeneration() != null && member.getGeneration() < 0 ? "child" : "parent");
  494. return vo;
  495. }
  496. private String elementLabel(String elem) {
  497. switch (elem) {
  498. case "wood": return "木";
  499. case "fire": return "火";
  500. case "earth": return "土";
  501. case "metal": return "金";
  502. case "water": return "水";
  503. default: return elem;
  504. }
  505. }
  506. }