Spaces:
Running
Running
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
- app.py +9 -0
- evidence.py +127 -0
- terms.py +67 -0
- 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,
|