th commited on
Commit
e6024b0
·
1 Parent(s): 2078e6b

feat: 紫微分析引擎全面升级 v3.0

Browse files

Batch 1 - 修复准确度:
- 修复'事业宫'→'官禄宫'命名错误 (sihua_data)
- 补全破军星数据到MAIN_STAR_TRAITS (14主星完整)
- 重写流月命宫计算:斗君定位法替代硬编码base_idx=0

Batch 2 - 提升分析深度:
- 三方四正加入星曜亮度权重(庙旺陷影响评分)
- 修复三方四正宫名不匹配bug('官禄宫'→'官禄',适配iztro命名)
- 四化升级v3.0:大限四化分析 + 叠加检测 + 对宫冲警告

Batch 3 - 补全功能:
- 化解建议补全巨门/擎羊/陀罗医疗建议
- Summary生成使用全部十步分析数据(身宫/六亲/健康/婚姻/福德)
- 实现_analyze_core_traits():从命宫主星查特质字典

🦊 Generated with Sisi

knowledge_enhanced_interpreter.py CHANGED
@@ -168,8 +168,14 @@ class KnowledgeEnhancedInterpreter:
168
  '特质': '将星,果断勇敢,独立性强',
169
  '事业': '适合军警、竞争性行业、开创',
170
  '性格': '刚毅果断,独来独往,不服输'
 
 
 
 
 
 
 
171
  }
172
- # 破军待补充
173
  }
174
 
175
  # 十二宫位核心意义(从02-twelve-palaces.md提取)
@@ -493,18 +499,64 @@ class KnowledgeEnhancedInterpreter:
493
  """
494
  分析命盘核心特质(基于主星知识库)
495
 
 
 
496
  Returns:
497
  dict: 核心特质分析
498
  """
499
- # TODO: 从chart_data提取命宫主星
500
- # 这里需要根据实际chart_data结构调整
501
- ming_palace_stars = [] # 待实现:从chart_data提取
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
 
503
  traits = {
504
  'primary_stars': ming_palace_stars,
505
- 'personality_summary': '待实现:基于主星生成性格总结',
506
- 'career_tendency': '待实现:基于主星生成事业倾向',
507
- 'fortune_level': '待实现:基于主星组合判断格局'
 
508
  }
509
 
510
  return traits
 
168
  '特质': '将星,果断勇敢,独立性强',
169
  '事业': '适合军警、竞争性行业、开创',
170
  '性格': '刚毅果断,独来独往,不服输'
171
+ },
172
+ '破军': {
173
+ '五行': '癸阴水',
174
+ '化气': '耗',
175
+ '特质': '先锋星,开创变革,破旧立新,不畏艰难',
176
+ '事业': '适合军警、探险、改革、投机、开拓性行业',
177
+ '性格': '冲劲十足,不惧挑战,敢于推翻重来,喜新厌旧'
178
  }
 
179
  }
180
 
181
  # 十二宫位核心意义(从02-twelve-palaces.md提取)
 
499
  """
500
  分析命盘核心特质(基于主星知识库)
501
 
502
+ 从 chart_data 中提取命宫主星,查 MAIN_STAR_TRAITS 字典生成分析
503
+
504
  Returns:
505
  dict: 核心特质分析
506
  """
507
+ # 从 palaces 中找到命宫
508
+ palaces = chart_data.get('palaces', [])
509
+ ming_palace_stars = []
510
+ ming_palace = None
511
+
512
+ for palace in palaces:
513
+ if palace.get('name') == '命宫':
514
+ ming_palace = palace
515
+ break
516
+
517
+ # 提取命宫主星名称
518
+ if ming_palace:
519
+ for star in ming_palace.get('major_stars', []):
520
+ star_name = star.get('name', '') if isinstance(star, dict) else star
521
+ if star_name:
522
+ ming_palace_stars.append(star_name)
523
+
524
+ # 从 MAIN_STAR_TRAITS 查询特质
525
+ personalities = []
526
+ careers = []
527
+ traits_detail = []
528
+
529
+ for star_name in ming_palace_stars:
530
+ star_traits = self.MAIN_STAR_TRAITS.get(star_name, {})
531
+ if star_traits:
532
+ personalities.append(star_traits.get('性格', ''))
533
+ careers.append(star_traits.get('事业', ''))
534
+ traits_detail.append({
535
+ 'star': star_name,
536
+ 'wuxing': star_traits.get('五行', ''),
537
+ 'huaqi': star_traits.get('化气', ''),
538
+ 'trait': star_traits.get('特质', ''),
539
+ 'career': star_traits.get('事业', ''),
540
+ 'personality': star_traits.get('性格', '')
541
+ })
542
+
543
+ # 组合输出
544
+ personality_summary = '、'.join(filter(None, personalities)) if personalities else '命宫无主星,性格受辅星和三方四正影响'
545
+ career_tendency = '、'.join(filter(None, careers)) if careers else '事业方向受三方四正主星影响'
546
+
547
+ # 判断格局等级
548
+ fortune_level = 'medium'
549
+ if len(ming_palace_stars) >= 2:
550
+ fortune_level = 'high' # 双星坐命格局较高
551
+ elif len(ming_palace_stars) == 0:
552
+ fortune_level = 'special' # 空宫需看三方四正借星
553
 
554
  traits = {
555
  'primary_stars': ming_palace_stars,
556
+ 'personality_summary': personality_summary,
557
+ 'career_tendency': career_tendency,
558
+ 'fortune_level': fortune_level,
559
+ 'traits_detail': traits_detail
560
  }
561
 
562
  return traits
liuyue_analyzer.py CHANGED
@@ -277,6 +277,10 @@ class LiuyueAnalyzer:
277
  # 获取命宫地支
278
  self.ming_gong_zhi = self._get_ming_gong_zhi()
279
 
 
 
 
 
280
  def _get_ming_gong_zhi(self) -> str:
281
  """获取命宫地支"""
282
  for palace in self.palaces:
@@ -284,6 +288,37 @@ class LiuyueAnalyzer:
284
  return palace.get('earthly_branch', '子')
285
  return '子'
286
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  def _get_palace_by_name(self, name: str) -> Optional[dict]:
288
  """根据宫位名称获取宫位信息"""
289
  for palace in self.palaces:
@@ -455,20 +490,40 @@ class LiuyueAnalyzer:
455
 
456
  def _get_liu_yue_palace(self, year: int, month: int) -> str:
457
  """
458
- 获取流月命宫位置(简化版)
459
 
460
- 基于斗君定位法的简化实现
 
 
 
 
461
  """
462
- # 宫位顺序
463
- palace_order = ['命宫', '兄弟', '夫妻', '子女', '财帛', '疾厄',
464
- '迁移', '奴仆', '官禄', '田宅', '福德', '父母']
465
-
466
- # 简化:以命宫为基准,每月移动一宫
467
- # 正月命宫 = 本命命宫位置(简化处理)
468
- base_idx = 0 # 从命宫开始
469
- target_idx = (base_idx + month - 1) % 12
470
-
471
- return palace_order[target_idx]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
472
 
473
  def _get_palace_star_type(self, palace_info: dict) -> Optional[str]:
474
  """获取宫位主星类型"""
 
277
  # 获取命宫地支
278
  self.ming_gong_zhi = self._get_ming_gong_zhi()
279
 
280
+ # 获取出生农历月份和时辰地支(斗君定位法需要)
281
+ self.birth_month = self._get_birth_month()
282
+ self.birth_hour_zhi = self._get_birth_hour_branch()
283
+
284
  def _get_ming_gong_zhi(self) -> str:
285
  """获取命宫地支"""
286
  for palace in self.palaces:
 
288
  return palace.get('earthly_branch', '子')
289
  return '子'
290
 
291
+ def _get_birth_month(self) -> int:
292
+ """从八字月柱地支提取出生农历月份"""
293
+ chart_info = self.chart.get('chart', {})
294
+ chinese_date = chart_info.get('chinese_date', '')
295
+ if chinese_date:
296
+ parts = chinese_date.split()
297
+ if len(parts) >= 2:
298
+ month_pillar = parts[1]
299
+ month_branch = month_pillar[-1] if month_pillar else ''
300
+ branch_to_month = {
301
+ '寅': 1, '卯': 2, '辰': 3, '巳': 4, '午': 5, '未': 6,
302
+ '申': 7, '酉': 8, '戌': 9, '亥': 10, '子': 11, '丑': 12
303
+ }
304
+ return branch_to_month.get(month_branch, 1)
305
+ return 1
306
+
307
+ def _get_birth_hour_branch(self) -> str:
308
+ """从八字时柱地支提取出生时辰"""
309
+ chart_info = self.chart.get('chart', {})
310
+ chinese_date = chart_info.get('chinese_date', '')
311
+ if chinese_date:
312
+ parts = chinese_date.split()
313
+ if len(parts) >= 4:
314
+ hour_pillar = parts[3]
315
+ return hour_pillar[-1] if hour_pillar else '子'
316
+ # 备用:从earthly_branch_index转换
317
+ ebi = chart_info.get('earthly_branch_index', 0)
318
+ if 0 <= ebi < 12:
319
+ return EARTHLY_BRANCHES[ebi]
320
+ return '子'
321
+
322
  def _get_palace_by_name(self, name: str) -> Optional[dict]:
323
  """根据宫位名称获取宫位信息"""
324
  for palace in self.palaces:
 
490
 
491
  def _get_liu_yue_palace(self, year: int, month: int) -> str:
492
  """
493
+ 斗君定位法 — 正确计算流月命宫
494
 
495
+ 算法:
496
+ 1. 找到流年太岁所在宫位(流年地支对应的宫位)
497
+ 2. 从太岁位置逆数生月 → 中间位置
498
+ 3. 从中间位置顺数生时 → 斗君(正月流月命宫)
499
+ 4. 从斗君顺行,每月移一宫
500
  """
501
+ # Step 1: 获取流年地支,找到太岁所在宫位
502
+ liu_nian_zhi = self._get_liu_nian_zhi(year)
503
+ taisui_idx = None
504
+ for i, palace in enumerate(self.palaces):
505
+ if palace.get('earthly_branch') == liu_nian_zhi:
506
+ taisui_idx = i
507
+ break
508
+ if taisui_idx is None:
509
+ taisui_idx = 0
510
+
511
+ # Step 2: 从太岁逆数生月(逆=索引减小)
512
+ # 正月不动,二月退一位,以此类推
513
+ intermediate_idx = (taisui_idx - (self.birth_month - 1)) % 12
514
+
515
+ # Step 3: 从中间位置顺数生时(顺=索引增大)
516
+ # 子时不动,丑时进一位,以此类推
517
+ hour_offset = EARTHLY_BRANCHES.index(self.birth_hour_zhi) if self.birth_hour_zhi in EARTHLY_BRANCHES else 0
518
+ doujun_idx = (intermediate_idx + hour_offset) % 12
519
+
520
+ # Step 4: 从斗君(正月)开始,每月顺行一宫
521
+ target_idx = (doujun_idx + month - 1) % 12
522
+
523
+ # 返回该位置的宫名
524
+ if target_idx < len(self.palaces):
525
+ return self.palaces[target_idx].get('name', '命宫')
526
+ return '命宫'
527
 
528
  def _get_palace_star_type(self, palace_info: dict) -> Optional[str]:
529
  """获取宫位主星类型"""
reading_methodology.py CHANGED
@@ -1151,35 +1151,49 @@ class ReadingMethodologyEngine:
1151
  return 'senior'
1152
 
1153
  def _generate_summary(self, result: Dict) -> Dict:
1154
- """生成综合评估摘要"""
 
1155
  summary = {
1156
  'overall_pattern': '',
1157
  'key_strengths': [],
1158
  'key_challenges': [],
1159
  'life_advice': [],
 
 
 
 
 
1160
  'formulas_count': len(result.get('formulas_matched', []))
1161
  }
1162
 
1163
- # 从第二步获取总格评价
1164
- step2 = result['steps'][1] if len(result['steps']) > 1 else {}
1165
  analysis = step2.get('analysis', {})
1166
-
1167
  summary['overall_pattern'] = analysis.get('overall_comment', '')
1168
 
1169
- # 收集吉格作为优势
1170
  for ji_ge in analysis.get('ji_ge', []):
1171
  summary['key_strengths'].append(ji_ge.get('meaning', ''))
1172
-
1173
- # 收集凶格作为挑战
1174
  for xiong_ge in analysis.get('xiong_ge', []):
1175
  summary['key_challenges'].append(xiong_ge.get('meaning', ''))
1176
 
1177
- # 生成人生建议
1178
- if summary['key_challenges']:
1179
- summary['life_advice'].append('注意命盘中的挑战,提前做好准备')
1180
-
1181
- # 从第五步获取事业建议
1182
- step5 = result['steps'][4] if len(result['steps']) > 4 else {}
 
 
 
 
 
 
 
 
 
 
 
 
1183
  career_analysis = step5.get('analysis', {}).get('career', {})
1184
  if career_analysis.get('career_direction'):
1185
  direction_map = {
@@ -1191,6 +1205,40 @@ class ReadingMethodologyEngine:
1191
  if advice:
1192
  summary['life_advice'].append(advice)
1193
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1194
  return summary
1195
 
1196
  def _enhance_summary_with_barnum(self, summary: Dict) -> Dict:
 
1151
  return 'senior'
1152
 
1153
  def _generate_summary(self, result: Dict) -> Dict:
1154
+ """生成综合评估摘要(使用全部十步分析数据)"""
1155
+ steps = result.get('steps', [])
1156
  summary = {
1157
  'overall_pattern': '',
1158
  'key_strengths': [],
1159
  'key_challenges': [],
1160
  'life_advice': [],
1161
+ 'post_heaven_focus': '',
1162
+ 'relationship_highlights': [],
1163
+ 'health_warnings': [],
1164
+ 'marriage_timing': '',
1165
+ 'fortune_blessing': '',
1166
  'formulas_count': len(result.get('formulas_matched', []))
1167
  }
1168
 
1169
+ # Step 2: 总格评价(吉格/凶格)
1170
+ step2 = steps[1] if len(steps) > 1 else {}
1171
  analysis = step2.get('analysis', {})
 
1172
  summary['overall_pattern'] = analysis.get('overall_comment', '')
1173
 
 
1174
  for ji_ge in analysis.get('ji_ge', []):
1175
  summary['key_strengths'].append(ji_ge.get('meaning', ''))
 
 
1176
  for xiong_ge in analysis.get('xiong_ge', []):
1177
  summary['key_challenges'].append(xiong_ge.get('meaning', ''))
1178
 
1179
+ # Step 3: 身宫 → 后天发展方向
1180
+ step3 = steps[2] if len(steps) > 2 else {}
1181
+ shen_analysis = step3.get('analysis', {})
1182
+ shen_meaning = shen_analysis.get('shen_palace_meaning', '')
1183
+ if shen_meaning:
1184
+ summary['post_heaven_focus'] = shen_meaning
1185
+
1186
+ # Step 4: 六亲 → 关键亲缘提醒
1187
+ step4 = steps[3] if len(steps) > 3 else {}
1188
+ six_analysis = step4.get('analysis', {})
1189
+ for relation in ['parents', 'siblings', 'spouse', 'children']:
1190
+ rel_data = six_analysis.get(relation, {})
1191
+ key_point = rel_data.get('key_point', '') or rel_data.get('status', '')
1192
+ if key_point and key_point != 'normal':
1193
+ summary['relationship_highlights'].append(f'{relation}: {key_point}')
1194
+
1195
+ # Step 5: 事业方向
1196
+ step5 = steps[4] if len(steps) > 4 else {}
1197
  career_analysis = step5.get('analysis', {}).get('career', {})
1198
  if career_analysis.get('career_direction'):
1199
  direction_map = {
 
1205
  if advice:
1206
  summary['life_advice'].append(advice)
1207
 
1208
+ # Step 6: 疾厄 → 健康注意事项
1209
+ step6 = steps[5] if len(steps) > 5 else {}
1210
+ health_analysis = step6.get('analysis', {})
1211
+ health_warning = health_analysis.get('health_warning', '') or health_analysis.get('key_point', '')
1212
+ if health_warning:
1213
+ summary['health_warnings'].append(health_warning)
1214
+ meridian = health_analysis.get('meridian', '')
1215
+ if meridian:
1216
+ summary['health_warnings'].append(f'经络关注: {meridian}')
1217
+
1218
+ # Step 8: 婚姻时机
1219
+ step8 = steps[7] if len(steps) > 7 else {}
1220
+ marriage_analysis = step8.get('analysis', {})
1221
+ marriage_note = marriage_analysis.get('timing_note', '') or marriage_analysis.get('key_point', '')
1222
+ if marriage_note:
1223
+ summary['marriage_timing'] = marriage_note
1224
+
1225
+ # Step 10: 福德宫核心判断
1226
+ step10 = steps[9] if len(steps) > 9 else {}
1227
+ fude_analysis = step10.get('analysis', {})
1228
+ fude_note = fude_analysis.get('core_meaning', '') or fude_analysis.get('key_point', '')
1229
+ if fude_note:
1230
+ summary['fortune_blessing'] = fude_note
1231
+ # 福德宫化忌重要警告
1232
+ fude_warning = fude_analysis.get('critical_warning', '')
1233
+ if fude_warning:
1234
+ summary['key_challenges'].append(fude_warning)
1235
+
1236
+ # 综合建议
1237
+ if summary['key_challenges']:
1238
+ summary['life_advice'].append('注意命盘中的挑战,提前做好准备')
1239
+ if summary['health_warnings']:
1240
+ summary['life_advice'].append('关注健康,定期体检')
1241
+
1242
  return summary
1243
 
1244
  def _enhance_summary_with_barnum(self, summary: Dict) -> Dict:
resolution_advisor.py CHANGED
@@ -341,6 +341,60 @@ class ResolutionAdvisor:
341
  }
342
  ]
343
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  return []
345
 
346
  def _get_health_monitoring_advice(
 
341
  }
342
  ]
343
 
344
+ if star == '巨门':
345
+ return [
346
+ {
347
+ 'category': '口腔咽喉检查',
348
+ 'action': '口腔及消化系统检查',
349
+ 'detail': '巨门主口舌,化忌时注意口腔溃疡、咽喉炎、肠胃功能紊乱,建议做胃镜或消化科检查',
350
+ 'priority': '高',
351
+ 'timing': '农历一月或流年化忌月份'
352
+ },
353
+ {
354
+ 'category': '心理减压',
355
+ 'action': '管理口舌是非带来的心理压力',
356
+ 'detail': '巨门化忌易招口舌是非、被误解,长期压力影响消化和睡眠。建议定期心理疏导或冥想练习',
357
+ 'priority': '中',
358
+ 'timing': '全年持续'
359
+ }
360
+ ]
361
+
362
+ if star == '擎羊':
363
+ return [
364
+ {
365
+ 'category': '外伤血光预防',
366
+ 'action': '年度全面体检 + 避免高风险活动',
367
+ 'detail': '擎羊主刀伤血光,化忌加重。避免极限运动、注意交通安全、厨房用火用刀谨慎,年初做外科体检',
368
+ 'priority': '高',
369
+ 'timing': '农历一月体检,全年注意安全'
370
+ },
371
+ {
372
+ 'category': '牙科眼科检查',
373
+ 'action': '主动排查头面部问题',
374
+ 'detail': '擎羊与头面部相关,化忌时注意牙齿、眼睛、头部问题,主动预约检查可人工应劫',
375
+ 'priority': '中',
376
+ 'timing': '农历一月或二月'
377
+ }
378
+ ]
379
+
380
+ if star == '陀罗':
381
+ return [
382
+ {
383
+ 'category': '慢性病排查',
384
+ 'action': '针对性慢性病检查',
385
+ 'detail': '陀罗主暗疾拖延,化忌时潜伏的慢性病容易加重。重点检查:关节、皮肤、慢性炎症、甲状腺等容易被忽视的问题',
386
+ 'priority': '高',
387
+ 'timing': '农历一月全面检查'
388
+ },
389
+ {
390
+ 'category': '运动调理',
391
+ 'action': '规律低强度运动',
392
+ 'detail': '陀罗性质拖沓粘滞,通过规律运动(瑜伽、游泳、散步)促进气血流通,防止暗疾淤积',
393
+ 'priority': '中',
394
+ 'timing': '全年坚持,每周3-4次'
395
+ }
396
+ ]
397
+
398
  return []
399
 
400
  def _get_health_monitoring_advice(
san_fang_si_zheng_analyzer.py CHANGED
@@ -15,55 +15,67 @@ Author: Sisi 🦊
15
  from typing import Dict, List, Any, Optional
16
  from star_brightness_table import get_brightness_level, get_star_brightness
17
 
18
- # 12宫位的三方四正关系表
 
 
 
 
 
 
 
 
 
 
 
 
19
  SAN_FANG_SI_ZHENG_MAP = {
20
  '命宫': {
21
- '三方': ['官禄宫', '财帛宫'], # 三合宫
22
- '四正': ['官禄宫', '财帛宫', '迁移宫'] # 三合宫 + 对宫
23
  },
24
- '兄弟宫': {
25
- '三方': ['疾厄宫', '田宅宫'],
26
- '四正': ['疾厄宫', '田宅宫', '奴仆宫']
27
  },
28
- '夫妻宫': {
29
- '三方': ['迁移宫', '福德宫'],
30
- '四正': ['迁移宫', '福德宫', '官禄宫']
31
  },
32
- '子女宫': {
33
- '三方': ['奴仆宫', '父母宫'],
34
- '四正': ['奴仆宫', '父母宫', '疾厄宫']
35
  },
36
- '财帛宫': {
37
- '三方': ['命宫', '福德宫'],
38
- '四正': ['命宫', '福德宫', '田宅宫']
39
  },
40
- '疾厄宫': {
41
- '三方': ['兄弟宫', '父母宫'],
42
- '四正': ['兄弟宫', '父母宫', '迁移宫']
43
  },
44
- '迁移宫': {
45
- '三方': ['夫妻宫', '疾厄宫'],
46
- '四正': ['夫妻宫', '疾厄宫', '命宫']
47
  },
48
- '奴仆宫': {
49
- '三方': ['子女宫', '兄弟宫'],
50
- '四正': ['子女宫', '兄弟宫', '福德宫']
51
  },
52
- '官禄宫': {
53
- '三方': ['命宫', '田宅宫'],
54
- '四正': ['命宫', '田宅宫', '夫妻宫']
55
  },
56
- '田宅宫': {
57
- '三方': ['兄弟宫', '官禄宫'],
58
- '四正': ['兄弟宫', '官禄宫', '财帛宫']
59
  },
60
- '福德宫': {
61
- '三方': ['财帛宫', '夫妻宫'],
62
- '四正': ['财帛宫', '夫妻宫', '奴仆宫']
63
  },
64
- '父母宫': {
65
- '三方': ['子女宫', '疾厄宫'],
66
- '四正': ['子女宫', '疾厄宫', '田宅宫']
67
  }
68
  }
69
 
@@ -97,6 +109,9 @@ def analyze_san_fang_si_zheng(
97
  Returns:
98
  dict: 三方四正分析结果
99
  """
 
 
 
100
  if palace_name not in SAN_FANG_SI_ZHENG_MAP:
101
  return {
102
  'error': f'无效的宫位名称: {palace_name}',
@@ -225,17 +240,32 @@ def _calculate_san_fang_strength(
225
  major_star_score = min(len(major_stars) * 8, 50)
226
  score += major_star_score
227
 
228
- # 2. 吉星统计
229
  auspicious_count = 0
230
  inauspicious_count = 0
231
-
232
- for star in major_stars + minor_stars:
233
- if star in AUSPICIOUS_STARS:
234
- auspicious_count += 1
235
- score += 5
236
- if star in INAUSPICIOUS_STARS:
237
- inauspicious_count += 1
238
- score -= 3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
  # 限制吉星加分和凶星扣分
241
  score = max(score, 0) # 不低于0
@@ -292,6 +322,7 @@ def _calculate_san_fang_strength(
292
  'balance': balance,
293
  'breakdown': {
294
  'major_star_score': major_star_score,
 
295
  'sihua_score': sihua_score
296
  }
297
  }
 
15
  from typing import Dict, List, Any, Optional
16
  from star_brightness_table import get_brightness_level, get_star_brightness
17
 
18
+ # 宫名标准化:iztro 返回的宫名不带"宫"字(除命宫外),仆役宫名为"仆役"
19
+ # 此表统一使用 iztro 命名规范
20
+ def _normalize_palace_name(name: str) -> str:
21
+ """标准化宫位名称,兼容带/不带'宫'字的写法"""
22
+ aliases = {'奴仆': '仆役', '奴仆宫': '仆役', '交友': '仆役', '交友宫': '仆役'}
23
+ if name in aliases:
24
+ return aliases[name]
25
+ # 去掉"宫"字(命宫除外)
26
+ if name != '命宫' and name.endswith('宫'):
27
+ return name[:-1]
28
+ return name
29
+
30
+ # 12宫位的三方四正关系表(使用 iztro 宫名规范)
31
  SAN_FANG_SI_ZHENG_MAP = {
32
  '命宫': {
33
+ '三方': ['官禄', '财帛'], # 三合宫
34
+ '四正': ['官禄', '财帛', '迁移'] # 三合宫 + 对宫
35
  },
36
+ '兄弟': {
37
+ '三方': ['疾厄', '田宅'],
38
+ '四正': ['疾厄', '田宅', '仆役']
39
  },
40
+ '夫妻': {
41
+ '三方': ['迁移', '福德'],
42
+ '四正': ['迁移', '福德', '官禄']
43
  },
44
+ '子女': {
45
+ '三方': ['仆役', '父母'],
46
+ '四正': ['仆役', '父母', '疾厄']
47
  },
48
+ '财帛': {
49
+ '三方': ['命宫', '福德'],
50
+ '四正': ['命宫', '福德', '田宅']
51
  },
52
+ '疾厄': {
53
+ '三方': ['兄弟', '父母'],
54
+ '四正': ['兄弟', '父母', '迁移']
55
  },
56
+ '迁移': {
57
+ '三方': ['夫妻', '疾厄'],
58
+ '四正': ['夫妻', '疾厄', '命宫']
59
  },
60
+ '仆役': {
61
+ '三方': ['子女', '兄弟'],
62
+ '四正': ['子女', '兄弟', '福德']
63
  },
64
+ '官禄': {
65
+ '三方': ['命宫', '田宅'],
66
+ '四正': ['命宫', '田宅', '夫妻']
67
  },
68
+ '田宅': {
69
+ '三方': ['兄弟', '官禄'],
70
+ '四正': ['兄弟', '官禄', '财帛']
71
  },
72
+ '福德': {
73
+ '三方': ['财帛', '夫妻'],
74
+ '四正': ['财帛', '夫妻', '仆役']
75
  },
76
+ '父母': {
77
+ '三方': ['子女', '疾厄'],
78
+ '四正': ['子女', '疾厄', '田宅']
79
  }
80
  }
81
 
 
109
  Returns:
110
  dict: 三方四正分析结果
111
  """
112
+ # 标准化宫名(兼容带/不带"宫"字、奴仆/仆役等别名)
113
+ palace_name = _normalize_palace_name(palace_name)
114
+
115
  if palace_name not in SAN_FANG_SI_ZHENG_MAP:
116
  return {
117
  'error': f'无效的宫位名称: {palace_name}',
 
240
  major_star_score = min(len(major_stars) * 8, 50)
241
  score += major_star_score
242
 
243
+ # 2. 吉星/凶星统计 + 星曜亮度加权
244
  auspicious_count = 0
245
  inauspicious_count = 0
246
+ brightness_score = 0
247
+
248
+ for pd in palace_details:
249
+ p_name = pd['palace']
250
+ for star in pd['major_stars'] + pd['minor_stars']:
251
+ if star in AUSPICIOUS_STARS:
252
+ auspicious_count += 1
253
+ score += 5
254
+ if star in INAUSPICIOUS_STARS:
255
+ inauspicious_count += 1
256
+ score -= 3
257
+
258
+ # 主星亮度评分(庙旺加分,陷减分)
259
+ for star in pd['major_stars']:
260
+ brightness = get_star_brightness(star, p_name)
261
+ if brightness:
262
+ level = get_brightness_level(brightness)
263
+ brightness_map = {5: 4, 4: 3, 3: 2, 2: 1, 1: 0, 0: -1, -1: -2}
264
+ brightness_score += brightness_map.get(level, 0)
265
+
266
+ # 亮度加分上限15分
267
+ brightness_score = max(min(brightness_score, 15), -10)
268
+ score += brightness_score
269
 
270
  # 限制吉星加分和凶星扣分
271
  score = max(score, 0) # 不低于0
 
322
  'balance': balance,
323
  'breakdown': {
324
  'major_star_score': major_star_score,
325
+ 'brightness_score': brightness_score,
326
  'sihua_score': sihua_score
327
  }
328
  }
sihua_analyzer.py CHANGED
@@ -17,6 +17,17 @@ from sihua_data import (
17
  )
18
 
19
 
 
 
 
 
 
 
 
 
 
 
 
20
  class SihuaAnalyzer:
21
  """四化飞星分析器"""
22
 
@@ -35,9 +46,20 @@ class SihuaAnalyzer:
35
  self.year_gan = chart_info.get('year_heavenly_stem', '')
36
  self.gender = chart_info.get('gender', '男')
37
 
38
- def analyze_all_sihua(self) -> dict:
 
 
 
 
 
 
 
 
39
  """
40
- 分析所有四化(年干四化为主)
 
 
 
41
 
42
  Returns:
43
  dict: 完整的四化分析结果
@@ -45,15 +67,147 @@ class SihuaAnalyzer:
45
  # 1. 年干四化(本命盘最重要的四化)
46
  year_sihua = self._analyze_year_sihua()
47
 
48
- # 2. 综合判断
 
 
 
 
 
 
 
 
 
 
 
49
  overall_judgment = self._judge_overall_sihua(year_sihua)
50
 
51
- return {
52
  'year_sihua': year_sihua,
53
  'overall_judgment': overall_judgment,
54
- 'analysis_version': 'v2.0_enhanced'
55
  }
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  def _analyze_year_sihua(self) -> dict:
58
  """
59
  分析年干四化
 
17
  )
18
 
19
 
20
+ # 对宫映射(用于化忌冲对宫分析)
21
+ OPPOSING_PALACE_MAP = {
22
+ '命宫': '迁移', '迁移': '命宫',
23
+ '兄弟': '仆役', '仆役': '兄弟',
24
+ '夫妻': '官禄', '官禄': '夫妻',
25
+ '子女': '田宅', '田宅': '子女',
26
+ '财帛': '福德', '福德': '财帛',
27
+ '疾厄': '父母', '父母': '疾厄',
28
+ }
29
+
30
+
31
  class SihuaAnalyzer:
32
  """四化飞星分析器"""
33
 
 
46
  self.year_gan = chart_info.get('year_heavenly_stem', '')
47
  self.gender = chart_info.get('gender', '男')
48
 
49
+ # 构建宫名→天干映射
50
+ self.palace_gan_map = {}
51
+ for p in self.palaces:
52
+ name = p.get('name', '')
53
+ gan = p.get('heavenly_stem', '')
54
+ if name and gan:
55
+ self.palace_gan_map[name] = gan
56
+
57
+ def analyze_all_sihua(self, daxun_palace_name: str = '') -> dict:
58
  """
59
+ 分析所有四化
60
+
61
+ Args:
62
+ daxun_palace_name: 当前大限宫位名称(如传入则分析大限四化)
63
 
64
  Returns:
65
  dict: 完整的四化分析结果
 
67
  # 1. 年干四化(本命盘最重要的四化)
68
  year_sihua = self._analyze_year_sihua()
69
 
70
+ # 2. 大限四化(如果提供了大限宫名)
71
+ daxun_sihua = {}
72
+ if daxun_palace_name:
73
+ daxun_sihua = self._analyze_daxun_sihua(daxun_palace_name)
74
+
75
+ # 3. 叠加检测(生年四化 + 大限四化同宫)
76
+ stacking = self._detect_stacking(year_sihua, daxun_sihua) if daxun_sihua else []
77
+
78
+ # 4. 对宫冲检测(化忌冲对宫)
79
+ opposition = self._detect_opposition(year_sihua, daxun_sihua)
80
+
81
+ # 5. 综合判断
82
  overall_judgment = self._judge_overall_sihua(year_sihua)
83
 
84
+ result = {
85
  'year_sihua': year_sihua,
86
  'overall_judgment': overall_judgment,
87
+ 'analysis_version': 'v3.0_multi_layer'
88
  }
89
 
90
+ if daxun_sihua:
91
+ result['daxun_sihua'] = daxun_sihua
92
+ result['daxun_palace'] = daxun_palace_name
93
+ if stacking:
94
+ result['stacking_effects'] = stacking
95
+ if opposition:
96
+ result['opposition_warnings'] = opposition
97
+
98
+ return result
99
+
100
+ def _analyze_daxun_sihua(self, daxun_palace_name: str) -> dict:
101
+ """
102
+ 分析大限四化(从大限宫位天干推算)
103
+
104
+ Args:
105
+ daxun_palace_name: 大限宫位名称
106
+
107
+ Returns:
108
+ dict: 大限四化分析结果
109
+ """
110
+ daxun_gan = self.palace_gan_map.get(daxun_palace_name, '')
111
+ if not daxun_gan:
112
+ return {}
113
+
114
+ sihua_stars = get_sihua_by_gan(daxun_gan)
115
+ result = {}
116
+
117
+ for sihua_type, star_name in sihua_stars.items():
118
+ palace_info = self._find_star_palace(star_name)
119
+ if palace_info:
120
+ display_type = '化忌' if sihua_type == '化忌2' else sihua_type
121
+ analysis = self._analyze_single_sihua(display_type, star_name, palace_info)
122
+ analysis['source'] = 'daxun'
123
+ analysis['daxun_gan'] = daxun_gan
124
+ result[sihua_type] = analysis
125
+
126
+ return result
127
+
128
+ def _detect_stacking(self, year_sihua: dict, daxun_sihua: dict) -> list:
129
+ """
130
+ 检测生年四化与大限四化的叠加效果
131
+
132
+ 当两层四化落在同一宫位时,效果显著增强
133
+ """
134
+ stacking = []
135
+
136
+ for y_type, y_data in year_sihua.items():
137
+ y_palace = y_data.get('palace', '')
138
+ if not y_palace:
139
+ continue
140
+ for d_type, d_data in daxun_sihua.items():
141
+ d_palace = d_data.get('palace', '')
142
+ if y_palace == d_palace:
143
+ # 同宫叠加
144
+ y_display = '化忌' if y_type == '化忌2' else y_type
145
+ d_display = '化忌' if d_type == '化忌2' else d_type
146
+
147
+ if y_display == d_display:
148
+ # 同类叠加(如双化禄、双化忌)—— 效果最强
149
+ severity = 'critical' if '忌' in y_display else 'very_strong'
150
+ msg = f'{y_palace}出现双重{y_display}!'
151
+ if '忌' in y_display:
152
+ msg += '此宫位压力极大,是当前大限重点课题'
153
+ else:
154
+ msg += '此宫位力量倍增,是当前大限最大优势'
155
+ else:
156
+ # 异类叠加(如化禄+化忌同宫)—— 矛盾张力
157
+ severity = 'tension'
158
+ msg = f'{y_palace}同时有{y_display}和{d_display},形成矛盾张���——有机会但也有风险'
159
+
160
+ stacking.append({
161
+ 'palace': y_palace,
162
+ 'year_sihua': y_display,
163
+ 'daxun_sihua': d_display,
164
+ 'severity': severity,
165
+ 'message': msg
166
+ })
167
+
168
+ return stacking
169
+
170
+ def _detect_opposition(self, year_sihua: dict, daxun_sihua: dict = None) -> list:
171
+ """
172
+ 检测化忌冲对宫
173
+
174
+ 化忌所在宫位的对宫也会受到影响(冲)
175
+ """
176
+ opposition = []
177
+
178
+ # 检查年干化忌的对冲
179
+ for s_type, s_data in year_sihua.items():
180
+ if '忌' not in s_type:
181
+ continue
182
+ palace = s_data.get('palace', '')
183
+ opposing = OPPOSING_PALACE_MAP.get(palace)
184
+ if opposing:
185
+ opposition.append({
186
+ 'source': 'year',
187
+ 'huaji_palace': palace,
188
+ 'huaji_star': s_data.get('star', ''),
189
+ 'opposing_palace': opposing,
190
+ 'message': f'生年{s_data.get("star", "")}化忌在{palace},冲{opposing}——{opposing}方面的事务也受波及'
191
+ })
192
+
193
+ # 检查大限化忌的对冲
194
+ if daxun_sihua:
195
+ for s_type, s_data in daxun_sihua.items():
196
+ if '忌' not in s_type:
197
+ continue
198
+ palace = s_data.get('palace', '')
199
+ opposing = OPPOSING_PALACE_MAP.get(palace)
200
+ if opposing:
201
+ opposition.append({
202
+ 'source': 'daxun',
203
+ 'huaji_palace': palace,
204
+ 'huaji_star': s_data.get('star', ''),
205
+ 'opposing_palace': opposing,
206
+ 'message': f'大限{s_data.get("star", "")}化忌在{palace},冲{opposing}——当前十年{opposing}事务需谨慎'
207
+ })
208
+
209
+ return opposition
210
+
211
  def _analyze_year_sihua(self) -> dict:
212
  """
213
  分析年干四化
sihua_data.py CHANGED
@@ -139,7 +139,7 @@ CRITICAL_PALACES_FOR_HUAJI = {
139
  'severity': 'high',
140
  'reason': '婚姻不顺、感情波折'
141
  },
142
- '事业宫': {
143
  'severity': 'high',
144
  'reason': '事业受阻、工作不稳'
145
  },
 
139
  'severity': 'high',
140
  'reason': '婚姻不顺、感情波折'
141
  },
142
+ '官禄宫': {
143
  'severity': 'high',
144
  'reason': '事业受阻、工作不稳'
145
  },