th commited on
Commit
98fa8d0
·
1 Parent(s): ad0d793

feat: 四层schema追加(meta/strength/timing/evidence) — /analyze-ziwei-v4

Browse files

只追加新字段,旧字段一个不改(已验证old_keys全部保留)。

- terms.py:14主星+辅星/十二宫/亮度/四化双语术语单一真相源
- evidence.py:build_meta/build_strength/build_timing/build_evidence
- strength.weak_mutagen_stars专门标注"带四化但亮度弱"的星——这是今天实测
挖出的真实发现(太阳化权却陷落、巨门化禄却亮度"不"),此前完全没有结构化
标注,容易被AI报告当纯利好写。evidence块会自动生成CAUTION提示,要求
正反双审(结构层vs执行层)
- timing数组统一current+next大限,next大限原始数据缺end_age字段,
已按10年一步的规则兜底补上,跟evidence文本口径一致
- 已用小鱼盘验证:命宫紫微(平)+左辅+文昌(得,化忌)、身宫财帛、生年四化、
现行大限官禄(45-54)全部正确;test_golden_charts.py新增1条,7项全过

🦊 Generated with Sisi

Files changed (4) hide show
  1. app.py +9 -0
  2. evidence.py +127 -0
  3. terms.py +67 -0
  4. test_golden_charts.py +22 -0
app.py CHANGED
@@ -20,6 +20,7 @@ from ai_interpreter import AIInterpreter
20
  from knowledge_enhanced_interpreter import KnowledgeEnhancedInterpreter
21
  from daxun_analyzer import DaxunAnalyzer
22
  from resolution_advisor import ResolutionAdvisor
 
23
  from liuyue_analyzer import LiuyueAnalyzer
24
 
25
  app = FastAPI(
@@ -410,6 +411,14 @@ def analyze_ziwei_knowledge_enhanced(request: ZiweiRequest, api_key: str = Depen
410
  'sisi_signature': '🦊 Generated with warmth & wisdom by Sisi v4.0'
411
  }
412
 
 
 
 
 
 
 
 
 
413
  return ZiweiResponse(
414
  status="success",
415
  data=enhanced_data
 
20
  from knowledge_enhanced_interpreter import KnowledgeEnhancedInterpreter
21
  from daxun_analyzer import DaxunAnalyzer
22
  from resolution_advisor import ResolutionAdvisor
23
+ from evidence import build_meta, build_strength, build_timing, build_evidence
24
  from liuyue_analyzer import LiuyueAnalyzer
25
 
26
  app = FastAPI(
 
411
  'sisi_signature': '🦊 Generated with warmth & wisdom by Sisi v4.0'
412
  }
413
 
414
+ # 2026-07-12 Phase 3: 追加 meta/strength/timing/evidence 四层字段,旧字段一个不改。
415
+ enhanced_data['meta'] = build_meta()
416
+ enhanced_data['strength'] = build_strength(ziwei_chart.get('palaces', []))
417
+ enhanced_data['timing'] = build_timing(daxun_analysis)
418
+ enhanced_data['evidence'] = build_evidence(
419
+ ziwei_chart.get('chart', {}), ziwei_chart.get('palaces', []), sihua_analysis, daxun_analysis
420
+ )
421
+
422
  return ZiweiResponse(
423
  status="success",
424
  data=enhanced_data
evidence.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 紫微斗数四层Schema组装:meta / strength / timing / evidence。
3
+
4
+ 只追加新字段,不改动任何旧字段——见 API_UNIFICATION_PLAN.md §3。
5
+ 调用方:app.py 的 v4系列endpoint (analyze_ziwei_knowledge_enhanced / analyze_ziwei_v4)。
6
+ """
7
+
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ from terms import star_term, palace_term, brightness_term, mutagen_term, star_with_brightness
11
+
12
+ WEAK_BRIGHTNESS = {"不", "陷"}
13
+
14
+
15
+ def build_meta() -> Dict[str, str]:
16
+ return {"engine": "iztro", "schema_version": "1.0"}
17
+
18
+
19
+ def build_strength(palaces: List[Dict[str, Any]]) -> Dict[str, Any]:
20
+ """
21
+ 亮度汇总——尤其标出"带四化但亮度弱"的星(2026-07-12发现的真实案例:小鱼盘太阳
22
+ 化权却落陷、巨门化禄却亮度"不",这两条此前完全没有被结构化标注出来,只能靠
23
+ 人工核对十二宫才能发现,容易被AI报告当成纯粹的利好来写)。
24
+ """
25
+ weak_mutagen_stars = []
26
+ strong_stars = []
27
+ for p in palaces:
28
+ for s in p.get("major_stars", []) or []:
29
+ name, brightness, mutagen = s.get("name"), s.get("brightness"), s.get("mutagen")
30
+ if not name:
31
+ continue
32
+ if mutagen and brightness in WEAK_BRIGHTNESS:
33
+ weak_mutagen_stars.append({
34
+ "star": name, "palace": p.get("name"), "brightness": brightness, "mutagen": mutagen,
35
+ })
36
+ if brightness in ("庙", "旺"):
37
+ strong_stars.append({"star": name, "palace": p.get("name"), "brightness": brightness})
38
+ return {
39
+ "weak_mutagen_stars": weak_mutagen_stars,
40
+ "strong_stars": strong_stars,
41
+ }
42
+
43
+
44
+ def build_timing(daxun_analysis: Optional[Dict[str, Any]]) -> List[Dict[str, Any]]:
45
+ if not daxun_analysis:
46
+ return []
47
+ timing = []
48
+ for key, is_current in (("current", True), ("next", False)):
49
+ d = daxun_analysis.get(key)
50
+ if not d:
51
+ continue
52
+ # "next" daxun(predict_next_daxun的输出)不带end_age,只有start_age——
53
+ # 每步大限固定10年,缺失时用start_age+9兜底,跟evidence文本口径保持一致
54
+ end_age = d.get("end_age")
55
+ if end_age is None and d.get("start_age") is not None:
56
+ end_age = d["start_age"] + 9
57
+ timing.append({
58
+ "layer": "daxun",
59
+ "label": d.get("palace_name", ""),
60
+ "label_en": palace_term(d.get("palace_name", "")) if d.get("palace_name") else "",
61
+ "start_age": d.get("start_age"),
62
+ "end_age": end_age,
63
+ "is_current": is_current,
64
+ "flags": ["empty_palace"] if d.get("is_empty_palace") else [],
65
+ })
66
+ return timing
67
+
68
+
69
+ def build_evidence(
70
+ chart: Dict[str, Any],
71
+ palaces: List[Dict[str, Any]],
72
+ sihua_analysis: Optional[Dict[str, Any]],
73
+ daxun_analysis: Optional[Dict[str, Any]],
74
+ ) -> str:
75
+ lines: List[str] = []
76
+
77
+ ming_gong = next((p for p in palaces if p.get("name") == "命宫"), None)
78
+ body_palace = next((p for p in palaces if p.get("is_body_palace")), None)
79
+ if ming_gong:
80
+ stars = ", ".join(
81
+ star_with_brightness(s["name"], s.get("brightness", ""))
82
+ for s in ming_gong.get("major_stars", []) if s.get("name")
83
+ )
84
+ lines.append(f"- {palace_term('命宫')}: {stars or '(no major star / 借星安宫)'}.")
85
+ lines.append(f"- Soul star (命主): {star_term(chart.get('soul', ''))}. Body star (身主): {star_term(chart.get('body', ''))}.")
86
+ if body_palace:
87
+ lines.append(f"- Body Palace (身宫) falls in: {palace_term(body_palace.get('name', ''))}.")
88
+
89
+ if sihua_analysis:
90
+ year_sihua = sihua_analysis.get("year_sihua", {})
91
+ parts = []
92
+ for mutagen_key, mutagen_char in (("化禄", "禄"), ("化权", "权"), ("化科", "科"), ("化忌", "忌")):
93
+ entry = year_sihua.get(mutagen_key)
94
+ if entry:
95
+ parts.append(f"{mutagen_term(mutagen_char)} -> {star_term(entry.get('star',''))} @ {palace_term(entry.get('palace',''))}")
96
+ if parts:
97
+ lines.append("- Natal Four Transformations (生年四化): " + "; ".join(parts) + ".")
98
+
99
+ strength = build_strength(palaces)
100
+ if strength["weak_mutagen_stars"]:
101
+ flagged = "; ".join(
102
+ f"{star_term(w['star'])} {mutagen_term(w['mutagen'][-1] if len(w['mutagen'])>1 else w['mutagen'])} "
103
+ f"but brightness={brightness_term(w['brightness'])}"
104
+ for w in strength["weak_mutagen_stars"]
105
+ )
106
+ lines.append(
107
+ f"- CAUTION (signal present but weak execution): {flagged}. "
108
+ f"The mutagen signal exists structurally but the star's own brightness is low — "
109
+ f"deliver at a discount, must state both sides (structure vs. execution)."
110
+ )
111
+
112
+ if daxun_analysis:
113
+ current = daxun_analysis.get("current", {})
114
+ if current:
115
+ major = ", ".join(current.get("palace_stars", {}).get("major", []))
116
+ lines.append(
117
+ f"- Current Daxun (现行大限): {palace_term(current.get('palace_name',''))} "
118
+ f"({current.get('start_age')}-{current.get('end_age')}), stars: {major or '(empty)'}."
119
+ )
120
+ nxt = daxun_analysis.get("next", {})
121
+ if nxt:
122
+ lines.append(
123
+ f"- Next Daxun (下一大限): {palace_term(nxt.get('palace_name',''))} "
124
+ f"({nxt.get('start_age')}-{nxt.get('start_age', 0) + 9})."
125
+ )
126
+
127
+ return "\n".join(lines)
terms.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 紫微斗数术语双语对照表 —— 单一真相源。
3
+
4
+ 跟astrologybazi-api的terms.py同一个思路:AI报告/evidence块引用命理术语时统一从这里查,
5
+ 避免同一颗星这次译一个名字下次译另一个名字。
6
+ """
7
+
8
+ STAR_TERMS = {
9
+ "紫微": "Zi Wei (Emperor Star)", "天机": "Tian Ji (Advisor Star)",
10
+ "太阳": "Tai Yang (Sun Star)", "武曲": "Wu Qu (Military Star)",
11
+ "天同": "Tian Tong (Harmony Star)", "廉贞": "Lian Zhen (Chastity Star)",
12
+ "天府": "Tian Fu (Treasury Star)", "太阴": "Tai Yin (Moon Star)",
13
+ "贪狼": "Tan Lang (Greedy Wolf Star)", "巨门": "Ju Men (Giant Gate Star)",
14
+ "天相": "Tian Xiang (Minister Star)", "天梁": "Tian Liang (Beam Star)",
15
+ "七杀": "Qi Sha (Seven Killings Star)", "破军": "Po Jun (Army Breaker Star)",
16
+ "左辅": "Zuo Fu (Left Assistant)", "右弼": "You Bi (Right Assistant)",
17
+ "文昌": "Wen Chang (Literary Star)", "文曲": "Wen Qu (Artistic Star)",
18
+ "天魁": "Tian Kui (Noble Star, Yang)", "天钺": "Tian Yue (Noble Star, Yin)",
19
+ }
20
+
21
+ PALACE_TERMS = {
22
+ "命宫": "Life Palace (Ming Gong)", "兄弟": "Siblings Palace (Xiong Di)",
23
+ "夫妻": "Spouse Palace (Fu Qi)", "子女": "Children Palace (Zi Nu)",
24
+ "财帛": "Wealth Palace (Cai Bo)", "疾厄": "Health Palace (Ji E)",
25
+ "迁移": "Travel Palace (Qian Yi)", "仆役": "Friends Palace (Pu Yi)",
26
+ "官禄": "Career Palace (Guan Lu)", "田宅": "Property Palace (Tian Zhai)",
27
+ "福德": "Fortune Palace (Fu De)", "父母": "Parents Palace (Fu Mu)",
28
+ }
29
+
30
+ BRIGHTNESS_TERMS = {
31
+ "庙": "temple-bright, strongest (Miao)",
32
+ "旺": "prosperous, strong (Wang)",
33
+ "得": "gaining, above average (De)",
34
+ "利": "favorable, average-plus (Li)",
35
+ "平": "level, average (Ping)",
36
+ "不": "weak, below average (Bu)",
37
+ "陷": "trapped, weakest (Xian)",
38
+ }
39
+
40
+ MUTAGEN_TERMS = {
41
+ "禄": "Transformation of Wealth (Hua Lu)",
42
+ "权": "Transformation of Power (Hua Quan)",
43
+ "科": "Transformation of Fame (Hua Ke)",
44
+ "忌": "Transformation of Obstruction (Hua Ji)",
45
+ }
46
+
47
+
48
+ def star_term(name: str) -> str:
49
+ return f"{name} ({STAR_TERMS[name]})" if name in STAR_TERMS else name
50
+
51
+
52
+ def palace_term(name: str) -> str:
53
+ return f"{name} ({PALACE_TERMS[name]})" if name in PALACE_TERMS else name
54
+
55
+
56
+ def brightness_term(level: str) -> str:
57
+ return f"{level} ({BRIGHTNESS_TERMS[level]})" if level in BRIGHTNESS_TERMS else level
58
+
59
+
60
+ def mutagen_term(mutagen: str) -> str:
61
+ return f"Hua {mutagen} ({MUTAGEN_TERMS[mutagen]})" if mutagen in MUTAGEN_TERMS else mutagen
62
+
63
+
64
+ def star_with_brightness(name: str, brightness: str) -> str:
65
+ if brightness:
66
+ return f"{star_term(name)} [{brightness_term(brightness)}]"
67
+ return star_term(name)
test_golden_charts.py CHANGED
@@ -76,6 +76,28 @@ def test_xiaoyu_current_daxun():
76
  assert set(current['palace_stars']['major']) == {'廉贞', '天府'}
77
 
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  def test_ziwei_v4_evidence_fields_present():
80
  """
81
  2026-07-12 P0 bug回归锁定:确认v4 endpoint真的返回sihua_analysis/daxun_analysis,
 
76
  assert set(current['palace_stars']['major']) == {'廉贞', '天府'}
77
 
78
 
79
+ def test_four_layer_schema():
80
+ """
81
+ 2026-07-12 Phase 3新增:v4 endpoint追加 meta/strength/timing/evidence 四层字段。
82
+ strength.weak_mutagen_stars专门标注"带四化但亮度弱"的星——这是2026-07-12实测时
83
+ 发现的真实案例(太阳化权却陷落、巨门化禄却亮度'不'),此前完全没有结构化标注。
84
+ """
85
+ d = _get_data()
86
+ assert d['meta']['schema_version'] == '1.0'
87
+
88
+ weak = {w['star']: w for w in d['strength']['weak_mutagen_stars']}
89
+ assert '太阳' in weak and weak['太阳']['brightness'] == '陷'
90
+ assert '巨门' in weak and weak['巨门']['brightness'] == '不'
91
+
92
+ current_timing = next(t for t in d['timing'] if t['is_current'])
93
+ assert current_timing['label'] == '官禄'
94
+ assert current_timing['start_age'] == 45 and current_timing['end_age'] == 54
95
+
96
+ evidence = d['evidence']
97
+ assert 'CAUTION' in evidence
98
+ assert '巨门' in evidence and '太阳' in evidence
99
+
100
+
101
  def test_ziwei_v4_evidence_fields_present():
102
  """
103
  2026-07-12 P0 bug回归锁定:确认v4 endpoint真的返回sihua_analysis/daxun_analysis,