| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588 |
- package com.etotem.cfc.service;
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import com.etotem.cfc.dto.*;
- import com.etotem.cfc.entity.*;
- import com.etotem.cfc.entity.Family;
- import com.etotem.cfc.entity.FamilyMember;
- import com.etotem.cfc.entity.FamilyMemberAttributes;
- import com.etotem.cfc.entity.ZodiacPairing;
- import com.etotem.cfc.entity.BloodTypePairing;
- import com.etotem.cfc.entity.LifeNumberRelationship;
- import com.etotem.cfc.mapper.*;
- import org.springframework.stereotype.Service;
- import javax.annotation.Resource;
- import java.util.*;
- import java.util.stream.Collectors;
- import com.etotem.cfc.util.SortUtil;
- @Service
- public class CompatibilityService {
- @Resource
- private FamilyMemberAttributeService familyMemberAttributeService;
- @Resource
- private FamilyMemberMapper familyMemberMapper;
- @Resource
- private FamilyMapper familyMapper;
- @Resource
- private ZodiacPairingMapper zodiacPairingMapper;
- @Resource
- private BloodTypePairingMapper bloodTypePairingMapper;
- @Resource
- private LifeNumberRelationshipMapper lifeNumberRelationshipMapper;
- private static final String[] ZODIAC_CODES = {"rat", "ox", "tiger", "rabbit", "dragon", "snake",
- "horse", "sheep", "monkey", "rooster", "dog", "pig"};
- private static final String[] WUXING_ELEMENTS = {"wood", "fire", "earth", "metal", "water"};
- /**
- * Full duo compatibility analysis
- */
- public CompatibilityResultVO computeCompatibility(Long memberId1, String memberType1,
- Long memberId2, String memberType2) {
- FamilyMemberVO member1 = buildMemberVO(memberId1);
- FamilyMemberVO member2 = buildMemberVO(memberId2);
- FamilyMemberAttributes attrs1 = familyMemberAttributeService.getByMember(memberId1, memberType1);
- FamilyMemberAttributes attrs2 = familyMemberAttributeService.getByMember(memberId2, memberType2);
- CompatibilityResultVO result = new CompatibilityResultVO();
- result.setMember1(member1);
- result.setMember2(member2);
- // Zodiac
- ZodiacCompatibilityVO zodiac = computeZodiac(attrs1, attrs2);
- result.setZodiac(zodiac);
- // Wuxing
- WuxingComplementarityVO wuxing = computeWuxing(attrs1, attrs2);
- result.setWuxing(wuxing);
- // Blood type
- BloodTypeCompatibilityVO bloodType = computeBloodType(attrs1, attrs2);
- result.setBloodType(bloodType);
- // Life number
- LifeNumberRelationshipVO lifeNumber = computeLifeNumber(attrs1, attrs2);
- result.setLifeNumber(lifeNumber);
- // Overall: zodiac 40% + wuxing 30% + blood 15% + lifeNumber 15%
- int zodiacScore = zodiac != null ? zodiac.getCompatibilityScore() : 50;
- int wuxingScore = wuxing != null ? wuxing.getComplementarityScore() : 50;
- int bloodScore = bloodType != null ? bloodType.getCompatibilityScore() : 50;
- int lifeScore = lifeNumber != null ? 70 : 50; // life number doesn't have a score, base 70 for having one
- int overall = (zodiacScore * 40 + wuxingScore * 30 + bloodScore * 15 + lifeScore * 15) / 100;
- result.setOverallScore(overall);
- // Overall advice
- result.setOverallAdvice(generateOverallAdvice(overall, zodiac, wuxing, bloodType, lifeNumber));
- return result;
- }
- /**
- * Family dashboard: all members + aggregated wuxing + pairwise summaries
- */
- public FamilyDashboardVO getFamilyDashboard(Long familyId, Long perspectiveMemberId, String perspectiveMemberType) {
- FamilyDashboardVO dashboard = new FamilyDashboardVO();
- // Get all family members
- LambdaQueryWrapper<FamilyMember> wrapper = new LambdaQueryWrapper<>();
- wrapper.eq(FamilyMember::getFamilyId, familyId);
- SortUtil.applySort(wrapper);
- SortUtil.applySort(wrapper);
- List<FamilyMember> members = familyMemberMapper.selectList(wrapper);
- if (members.isEmpty()) return dashboard;
- Family family = familyMapper.selectById(familyId);
- dashboard.setFamilyId(familyId);
- dashboard.setFamilyName(family != null ? family.getName() : null);
- // Build member tradition data
- List<FamilyMemberTraditionVO> traditionList = new ArrayList<>();
- for (FamilyMember member : members) {
- FamilyMemberTraditionVO vo = buildMemberTradition(member);
- traditionList.add(vo);
- }
- dashboard.setMembers(traditionList);
- // Family wuxing aggregation
- dashboard.setFamilyWuxing(aggregateWuxing(traditionList));
- // Pairwise summaries
- List<RelationshipSummaryVO> relationships = new ArrayList<>();
- for (int i = 0; i < traditionList.size(); i++) {
- for (int j = i + 1; j < traditionList.size(); j++) {
- FamilyMemberTraditionVO m1 = traditionList.get(i);
- FamilyMemberTraditionVO m2 = traditionList.get(j);
- RelationshipSummaryVO summary = computeRelationshipSummary(m1, m2);
- relationships.add(summary);
- }
- }
- dashboard.setRelationships(relationships);
- // Weekly insight
- dashboard.setWeeklyInsight(generateWeeklyInsight(traditionList, relationships));
- return dashboard;
- }
- // ========== Zodiac compatibility ==========
- private ZodiacCompatibilityVO computeZodiac(FamilyMemberAttributes attrs1, FamilyMemberAttributes attrs2) {
- if (attrs1 == null || attrs2 == null) return null;
- String zodiac1 = attrs1.getZodiac();
- String zodiac2 = attrs2.getZodiac();
- if (zodiac1 == null || zodiac2 == null) return null;
- LambdaQueryWrapper<ZodiacPairing> qw = new LambdaQueryWrapper<>();
- // Look up both orderings
- qw.eq(ZodiacPairing::getZodiacCode1, zodiac1);
- qw.eq(ZodiacPairing::getZodiacCode2, zodiac2);
- SortUtil.applySort(qw);
- ZodiacPairing pairing = zodiacPairingMapper.selectOne(qw);
- if (pairing == null) {
- // Try reverse order
- LambdaQueryWrapper<ZodiacPairing> reverseQw = new LambdaQueryWrapper<>();
- reverseQw.eq(ZodiacPairing::getZodiacCode1, zodiac2);
- reverseQw.eq(ZodiacPairing::getZodiacCode2, zodiac1);
- SortUtil.applySort(reverseQw);
- pairing = zodiacPairingMapper.selectOne(reverseQw);
- }
- ZodiacCompatibilityVO vo = new ZodiacCompatibilityVO();
- vo.setZodiacCode1(zodiac1);
- vo.setZodiacCode2(zodiac2);
- if (pairing != null) {
- vo.setPairingType(pairing.getPairingType());
- vo.setCompatibilityScore(pairing.getCompatibilityScore());
- vo.setDescription(pairing.getDescription());
- vo.setAdvice(pairing.getAdvice());
- } else {
- vo.setPairingType("ping");
- vo.setCompatibilityScore(50);
- vo.setDescription("暂无配对数据");
- vo.setAdvice("互相尊重,用心相处");
- }
- return vo;
- }
- // ========== Wuxing complementarity ==========
- private WuxingComplementarityVO computeWuxing(FamilyMemberAttributes attrs1, FamilyMemberAttributes attrs2) {
- if (attrs1 == null || attrs2 == null) return null;
- Map<String, Integer> wx1 = parseWuxingMap(attrs1.getWuxingElements());
- Map<String, Integer> wx2 = parseWuxingMap(attrs2.getWuxingElements());
- if (wx1.isEmpty() || wx2.isEmpty()) return null;
- // Compute complementarity: for each element, reward shared strength AND complementary difference
- int totalComp = 0;
- StringBuilder analysis = new StringBuilder();
- List<String> complementaryElements = new ArrayList<>();
- List<String> sharedElements = new ArrayList<>();
- for (String elem : WUXING_ELEMENTS) {
- int v1 = wx1.getOrDefault(elem, 0);
- int v2 = wx2.getOrDefault(elem, 0);
- int avg = (v1 + v2) / 2;
- int diff = Math.abs(v1 - v2);
- // complementarityScore for this element: avg (shared strength) + diff (complementary gap)
- // Scale: max per element score is ~100 (when avg=50 and diff=50)
- int elemComp = Math.min(100, avg + diff);
- totalComp += elemComp;
- if (diff >= 20) {
- complementaryElements.add(elem);
- }
- if (avg >= 30 && diff < 15) {
- sharedElements.add(elem);
- }
- }
- int avgScore = totalComp / 5;
- // Build analysis text
- if (!complementaryElements.isEmpty()) {
- analysis.append("五行互补: ");
- analysis.append(String.join("、", complementaryElements));
- analysis.append("元素互补性强,彼此弥补短板。");
- }
- if (!sharedElements.isEmpty()) {
- if (analysis.length() > 0) analysis.append(" ");
- analysis.append("五行共鸣: ");
- analysis.append(String.join("、", sharedElements));
- analysis.append("元素能量相近,容易产生默契。");
- }
- if (complementaryElements.isEmpty() && sharedElements.isEmpty()) {
- analysis.append("五行分布较为独立,各有所长。");
- }
- WuxingComplementarityVO vo = new WuxingComplementarityVO();
- vo.setWuxing1(wx1);
- vo.setWuxing2(wx2);
- vo.setComplementarityScore(avgScore);
- vo.setAnalysis(analysis.toString());
- return vo;
- }
- // ========== Blood type compatibility ==========
- private BloodTypeCompatibilityVO computeBloodType(FamilyMemberAttributes attrs1, FamilyMemberAttributes attrs2) {
- if (attrs1 == null || attrs2 == null) return null;
- String bt1 = attrs1.getBloodType();
- String bt2 = attrs2.getBloodType();
- if (bt1 == null || bt2 == null) return null;
- LambdaQueryWrapper<BloodTypePairing> qw = new LambdaQueryWrapper<>();
- qw.eq(BloodTypePairing::getBloodType1, bt1);
- qw.eq(BloodTypePairing::getBloodType2, bt2);
- SortUtil.applySort(qw);
- BloodTypePairing pairing = bloodTypePairingMapper.selectOne(qw);
- if (pairing == null) {
- LambdaQueryWrapper<BloodTypePairing> reverseQw = new LambdaQueryWrapper<>();
- reverseQw.eq(BloodTypePairing::getBloodType1, bt2);
- reverseQw.eq(BloodTypePairing::getBloodType2, bt1);
- SortUtil.applySort(reverseQw);
- pairing = bloodTypePairingMapper.selectOne(reverseQw);
- }
- BloodTypeCompatibilityVO vo = new BloodTypeCompatibilityVO();
- vo.setBloodType1(bt1);
- vo.setBloodType2(bt2);
- if (pairing != null) {
- vo.setCompatibilityScore(pairing.getCompatibilityScore());
- vo.setDescription(pairing.getDescription());
- vo.setAdvice(pairing.getAdvice());
- } else {
- vo.setCompatibilityScore(50);
- vo.setDescription("暂无配对数据");
- vo.setAdvice("血型只是参考,用心相处最重要");
- }
- return vo;
- }
- // ========== Life number relationship ==========
- private LifeNumberRelationshipVO computeLifeNumber(FamilyMemberAttributes attrs1, FamilyMemberAttributes attrs2) {
- if (attrs1 == null || attrs2 == null) return null;
- Integer lifeNum1 = extractLifeNumber(attrs1.getBehaviorModifier());
- Integer lifeNum2 = extractLifeNumber(attrs2.getBehaviorModifier());
- if (lifeNum1 == null || lifeNum2 == null) return null;
- // Relationship number: digital root of sum
- int sum = lifeNum1 + lifeNum2;
- int relNum = sum;
- while (relNum >= 10) {
- int n = relNum;
- relNum = 0;
- while (n > 0) {
- relNum += n % 10;
- n /= 10;
- }
- }
- LifeNumberRelationship relationship = lifeNumberRelationshipMapper.selectById(relNum);
- LifeNumberRelationshipVO vo = new LifeNumberRelationshipVO();
- vo.setLifeNumber1(lifeNum1);
- vo.setLifeNumber2(lifeNum2);
- vo.setRelationshipNumber(relNum);
- if (relationship != null) {
- vo.setTitle(relationship.getTitle());
- vo.setDescription(relationship.getDescription());
- vo.setAdvice(relationship.getAdvice());
- vo.setColorHex(relationship.getColorHex());
- }
- return vo;
- }
- // ========== Dashboard helpers ==========
- private FamilyMemberTraditionVO buildMemberTradition(FamilyMember member) {
- FamilyMemberTraditionVO vo = new FamilyMemberTraditionVO();
- vo.setMemberId(member.getId());
- vo.setNickname(member.getNickname());
- vo.setMemberType("child");
- FamilyMemberAttributes attrs = familyMemberAttributeService.getByMember(member.getId(), "child");
- if (attrs == null) {
- attrs = familyMemberAttributeService.getByMember(member.getId(), "parent");
- if (attrs != null) vo.setMemberType("parent");
- }
- if (attrs != null) {
- vo.setZodiac(attrs.getZodiac());
- vo.setBloodType(attrs.getBloodType());
- vo.setMindBaseScore(attrs.getMindBaseScore());
- vo.setWisdomBaseScore(attrs.getWisdomBaseScore());
- vo.setEightCharacters(parseJsonToMap(attrs.getEightCharacters()));
- vo.setWuxingElements(parseWuxingMap(attrs.getWuxingElements()));
- vo.setLifeNumber(extractLifeNumber(attrs.getBehaviorModifier()));
- }
- return vo;
- }
- private FamilyWuxingVO aggregateWuxing(List<FamilyMemberTraditionVO> members) {
- FamilyWuxingVO vo = new FamilyWuxingVO();
- Map<String, Integer> totals = new LinkedHashMap<>();
- for (String elem : WUXING_ELEMENTS) {
- totals.put(elem, 0);
- }
- int count = 0;
- String maxMember = null;
- int maxBalanceScore = -1;
- for (FamilyMemberTraditionVO m : members) {
- if (m.getWuxingElements() != null) {
- count++;
- // Calculate balance score for this member
- int minVal = Integer.MAX_VALUE;
- int maxVal = 0;
- for (String elem : WUXING_ELEMENTS) {
- int val = m.getWuxingElements().getOrDefault(elem, 0);
- totals.put(elem, totals.get(elem) + val);
- if (val < minVal) minVal = val;
- if (val > maxVal) maxVal = val;
- }
- int balance = 100 - (maxVal - minVal);
- if (balance > maxBalanceScore) {
- maxBalanceScore = balance;
- maxMember = m.getNickname();
- }
- }
- }
- Map<String, Integer> pctMap = new LinkedHashMap<>();
- String strongest = null;
- String weakest = null;
- int maxPct = 0;
- int minPct = Integer.MAX_VALUE;
- int totalAll = 0;
- for (String elem : WUXING_ELEMENTS) {
- int val = count > 0 ? totals.get(elem) * 100 / (count * 100) : 0;
- pctMap.put(elem, val);
- totalAll += val;
- if (val > maxPct) { maxPct = val; strongest = elem; }
- if (val < minPct) { minPct = val; weakest = elem; }
- }
- vo.setAggregatePercentages(pctMap);
- vo.setStrongestElement(strongest);
- vo.setWeakestElement(weakest);
- vo.setBalancedMemberName(maxMember);
- StringBuilder analysis = new StringBuilder();
- if (strongest != null) {
- analysis.append("你家");
- analysis.append(elementLabel(strongest));
- analysis.append("能量最强");
- }
- if (weakest != null && minPct < 15) {
- if (analysis.length() > 0) analysis.append(",");
- analysis.append(elementLabel(weakest));
- analysis.append("能量偏弱");
- }
- if (maxMember != null) {
- if (analysis.length() > 0) analysis.append("。");
- analysis.append(maxMember);
- analysis.append("的五行最为均衡,是家庭的稳定器。");
- }
- if (analysis.length() == 0) {
- analysis.append("家庭五行分布均衡,整体和谐。");
- }
- vo.setAnalysis(analysis.toString());
- return vo;
- }
- private RelationshipSummaryVO computeRelationshipSummary(FamilyMemberTraditionVO m1, FamilyMemberTraditionVO m2) {
- RelationshipSummaryVO summary = new RelationshipSummaryVO();
- summary.setMemberId1(m1.getMemberId());
- summary.setNickname1(m1.getNickname());
- summary.setMemberId2(m2.getMemberId());
- summary.setNickname2(m2.getNickname());
- // Quick zodiac lookup
- int zodiacScore = 50;
- String pairingType = "ping";
- if (m1.getZodiac() != null && m2.getZodiac() != null) {
- LambdaQueryWrapper<ZodiacPairing> qw = new LambdaQueryWrapper<>();
- qw.eq(ZodiacPairing::getZodiacCode1, m1.getZodiac());
- qw.eq(ZodiacPairing::getZodiacCode2, m2.getZodiac());
- SortUtil.applySort(qw);
- ZodiacPairing p = zodiacPairingMapper.selectOne(qw);
- if (p == null) {
- LambdaQueryWrapper<ZodiacPairing> rqw = new LambdaQueryWrapper<>();
- rqw.eq(ZodiacPairing::getZodiacCode1, m2.getZodiac());
- rqw.eq(ZodiacPairing::getZodiacCode2, m1.getZodiac());
- SortUtil.applySort(qw);
- SortUtil.applySort(rqw);
- p = zodiacPairingMapper.selectOne(rqw);
- }
- if (p != null) {
- zodiacScore = p.getCompatibilityScore();
- pairingType = p.getPairingType();
- summary.setSummary(p.getDescription());
- }
- }
- summary.setZodiacScore(zodiacScore);
- summary.setZodiacPairingType(pairingType);
- // Overall = zodiac + wuxing (if available)
- int wuxingScore = 50;
- if (m1.getWuxingElements() != null && m2.getWuxingElements() != null) {
- int total = 0;
- for (String elem : WUXING_ELEMENTS) {
- int v1 = m1.getWuxingElements().getOrDefault(elem, 0);
- int v2 = m2.getWuxingElements().getOrDefault(elem, 0);
- total += Math.min(100, (v1 + v2) / 2 + Math.abs(v1 - v2));
- }
- wuxingScore = total / 5;
- }
- int overall = (zodiacScore * 55 + wuxingScore * 45) / 100;
- summary.setOverallScore(overall);
- if (summary.getSummary() == null) {
- if (overall >= 80) {
- summary.setSummary("非常融洽");
- } else if (overall >= 60) {
- summary.setSummary("相处和谐");
- } else if (overall >= 40) {
- summary.setSummary("需要磨合");
- } else {
- summary.setSummary("需要更多包容");
- }
- }
- return summary;
- }
- // ========== Insights ==========
- private String generateWeeklyInsight(List<FamilyMemberTraditionVO> members, List<RelationshipSummaryVO> relationships) {
- List<String> insights = new ArrayList<>();
- // Find the best relationship
- RelationshipSummaryVO best = null;
- RelationshipSummaryVO worst = null;
- for (RelationshipSummaryVO r : relationships) {
- if (best == null || r.getOverallScore() > best.getOverallScore()) best = r;
- if (worst == null || r.getOverallScore() < worst.getOverallScore()) worst = r;
- }
- if (best != null && best.getOverallScore() >= 80) {
- insights.add(best.getNickname1() + "和" + best.getNickname2() + "是天生的好搭档,本周可以一起完成一个家庭任务");
- }
- if (worst != null && worst.getOverallScore() < 50) {
- insights.add(worst.getNickname1() + "和" + worst.getNickname2() + "的命理需要磨合,本周建议多安排一些耐心沟通的时间");
- }
- if (insights.isEmpty()) {
- insights.add("本周家庭关系和谐,享受彼此的陪伴吧");
- }
- return String.join(";", insights);
- }
- private String generateOverallAdvice(int overall, ZodiacCompatibilityVO zodiac,
- WuxingComplementarityVO wuxing,
- BloodTypeCompatibilityVO bloodType,
- LifeNumberRelationshipVO lifeNumber) {
- List<String> parts = new ArrayList<>();
- if (overall >= 80) {
- parts.add("你们是天生的好搭档,命理高度契合。");
- } else if (overall >= 60) {
- parts.add("你们的关系有很好的基础,稍微注意互相包容会更完美。");
- } else if (overall >= 40) {
- parts.add("你们虽然有差异,但差异正是互补的来源。");
- } else {
- parts.add("你们需要更多耐心和理解来经营关系。");
- }
- if (zodiac != null && zodiac.getAdvice() != null) {
- parts.add("生肖方面:" + zodiac.getAdvice());
- }
- if (bloodType != null && bloodType.getAdvice() != null) {
- parts.add("血型方面:" + bloodType.getAdvice());
- }
- if (lifeNumber != null && lifeNumber.getAdvice() != null) {
- parts.add("灵数方面:" + lifeNumber.getAdvice());
- }
- return String.join("", parts);
- }
- // ========== Parsing helpers ==========
- @SuppressWarnings("unchecked")
- private Map<String, Integer> parseWuxingMap(String json) {
- Map<String, Integer> result = new LinkedHashMap<>();
- if (json == null || json.isEmpty()) return result;
- try {
- Map<String, Object> raw = familyMemberAttributeService.fromJson(json);
- for (String elem : WUXING_ELEMENTS) {
- Object val = raw.get(elem);
- if (val instanceof Number) {
- result.put(elem, ((Number) val).intValue());
- }
- }
- } catch (Exception ignored) {}
- return result;
- }
- @SuppressWarnings("unchecked")
- private Map<String, String> parseJsonToMap(String json) {
- Map<String, String> result = new LinkedHashMap<>();
- if (json == null || json.isEmpty()) return result;
- try {
- Map<String, Object> raw = familyMemberAttributeService.fromJson(json);
- for (Map.Entry<String, Object> entry : raw.entrySet()) {
- result.put(entry.getKey(), String.valueOf(entry.getValue()));
- }
- } catch (Exception ignored) {}
- return result;
- }
- private Integer extractLifeNumber(String behaviorModifier) {
- if (behaviorModifier == null || behaviorModifier.isEmpty()) return null;
- try {
- Map<String, Object> map = familyMemberAttributeService.fromJson(behaviorModifier);
- Object ln = map.get("lifeNumber");
- if (ln instanceof Number) {
- return ((Number) ln).intValue();
- }
- } catch (Exception ignored) {}
- return null;
- }
- private FamilyMemberVO buildMemberVO(Long memberId) {
- FamilyMember member = familyMemberMapper.selectById(memberId);
- if (member == null) return null;
- FamilyMemberVO vo = new FamilyMemberVO();
- vo.setId(member.getId());
- vo.setNickname(member.getNickname());
- vo.setMemberType(member.getGeneration() != null && member.getGeneration() < 0 ? "child" : "parent");
- return vo;
- }
- private String elementLabel(String elem) {
- switch (elem) {
- case "wood": return "木";
- case "fire": return "火";
- case "earth": return "土";
- case "metal": return "金";
- case "water": return "水";
- default: return elem;
- }
- }
- }
|