Spaces:
Running
Running
Download evidence.py from sisithefox/astrology-ziwei-api: direct link, hf CLI and curl.
- Browser
- Download file 10.8 kB
-
https://huggingface.co/spaces/sisithefox/astrology-ziwei-api/resolve/main/evidence.py
- Command line
-
hf download hf://spaces/sisithefox/astrology-ziwei-api/evidence.py
-
curl -L -o evidence.py https://huggingface.co/spaces/sisithefox/astrology-ziwei-api/resolve/main/evidence.py
10.8 kB
| """ | |
| 紫微斗数四层Schema组装:meta / strength / timing / evidence。 | |
| 只追加新字段,不改动任何旧字段——见 API_UNIFICATION_PLAN.md §3。 | |
| 调用方:app.py 的 v4系列endpoint (analyze_ziwei_knowledge_enhanced / analyze_ziwei_v4)。 | |
| """ | |
| from datetime import datetime | |
| from typing import Any, Dict, List, Optional | |
| from terms import star_term, palace_term, brightness_term, mutagen_term, star_with_brightness | |
| WEAK_BRIGHTNESS = {"不", "陷"} | |
| GAN = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"] | |
| ZHI = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"] | |
| # 十天干四化表(三合派通行版,禄/权/科/忌顺序)。 | |
| # 与iztro horoscope()的流年四化输出已用2026丙午/2027丁未/2028戊申/2029己酉 | |
| # 四个年份对照验证一致(见API_UNIFICATION_PLAN.md §2.1 golden基线)—— | |
| # 因此流年四化不需要动Node子进程,查表+本命盘落宫即可。 | |
| FLOW_SIHUA = { | |
| "甲": ("廉贞", "破军", "武曲", "太阳"), | |
| "乙": ("天机", "天梁", "紫微", "太阴"), | |
| "丙": ("天同", "天机", "文昌", "廉贞"), | |
| "丁": ("太阴", "天同", "天机", "巨门"), | |
| "戊": ("贪狼", "太阴", "右弼", "天机"), | |
| "己": ("武曲", "贪狼", "天梁", "文曲"), | |
| "庚": ("太阳", "武曲", "太阴", "天同"), | |
| "辛": ("巨门", "太阳", "文曲", "文昌"), | |
| "壬": ("天梁", "紫微", "左辅", "武曲"), | |
| "癸": ("破军", "巨门", "太阴", "贪狼"), | |
| } | |
| def _year_ganzhi(year: int) -> str: | |
| """公历年→干支(立春边界忽略:流年四化按整年粒度)""" | |
| return GAN[(year - 4) % 10] + ZHI[(year - 4) % 12] | |
| def build_flow_year_sihua(palaces: List[Dict[str, Any]], year: Optional[int] = None) -> Optional[Dict[str, Any]]: | |
| """ | |
| 流年四化(2026-07-13补齐Phase 3规范缺口"查询年流年四化"): | |
| 流年天干查FLOW_SIHUA表,四化星的落宫/亮度取自本命盘palaces—— | |
| 流年四化星落本命某宫 = 该宫被这一年引动,是标准判读方式。 | |
| """ | |
| if year is None: | |
| year = datetime.now().year | |
| gz = _year_ganzhi(year) | |
| stars = FLOW_SIHUA.get(gz[0]) | |
| if not stars: | |
| return None | |
| star_loc: Dict[str, Dict[str, str]] = {} | |
| for p in palaces: | |
| for group in ("major_stars", "minor_stars"): | |
| for s in p.get(group) or []: | |
| if s.get("name"): | |
| star_loc[s["name"]] = { | |
| "palace": p.get("name", ""), | |
| "brightness": s.get("brightness", ""), | |
| } | |
| transformations = {} | |
| for label, star in zip(("化禄", "化权", "化科", "化忌"), stars): | |
| loc = star_loc.get(star, {}) | |
| transformations[label] = { | |
| "star": star, | |
| "palace": loc.get("palace", ""), | |
| "brightness": loc.get("brightness", ""), | |
| } | |
| return {"year": year, "year_ganzhi": gz, "transformations": transformations} | |
| def build_meta() -> Dict[str, str]: | |
| return {"engine": "iztro", "schema_version": "1.0"} | |
| def build_strength(palaces: List[Dict[str, Any]]) -> Dict[str, Any]: | |
| """ | |
| 亮度汇总——尤其标出"带四化但亮度弱"的星(2026-07-12发现的真实案例:小鱼盘太阳 | |
| 化权却落陷、巨门化禄却亮度"不",这两条此前完全没有被结构化标注出来,只能靠 | |
| 人工核对十二宫才能发现,容易被AI报告当成纯粹的利好来写)。 | |
| """ | |
| weak_mutagen_stars = [] | |
| strong_stars = [] | |
| for p in palaces: | |
| for s in p.get("major_stars", []) or []: | |
| name, brightness, mutagen = s.get("name"), s.get("brightness"), s.get("mutagen") | |
| if not name: | |
| continue | |
| if mutagen and brightness in WEAK_BRIGHTNESS: | |
| weak_mutagen_stars.append({ | |
| "star": name, "palace": p.get("name"), "brightness": brightness, "mutagen": mutagen, | |
| }) | |
| if brightness in ("庙", "旺"): | |
| strong_stars.append({"star": name, "palace": p.get("name"), "brightness": brightness}) | |
| return { | |
| "weak_mutagen_stars": weak_mutagen_stars, | |
| "strong_stars": strong_stars, | |
| } | |
| def build_lu_ji_flow(sihua_analysis: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]: | |
| """ | |
| 禄因忌果(小鱼原创方法论,见 agent-knowledge/TH&SiSi/business-methodology/ | |
| lu-yin-ji-guo-theory.md):化禄=因,资源的入口与流动(资源从哪来); | |
| 化忌=果,仓库——资源最终沉淀、真正属于命主的位置。忌所在宫位是要经营的 | |
| 一生课题,不是要逃避的缺陷。 | |
| 这是确定性推导(禄/忌落宫直接可得,不是AI判读),放在API层让所有产品 | |
| 复用,避免每条产品线的prompt各自教AI这套读法、各教一个版本。 | |
| """ | |
| if not sihua_analysis: | |
| return None | |
| year_sihua = sihua_analysis.get("year_sihua", {}) | |
| lu, ji = year_sihua.get("化禄"), year_sihua.get("化忌") | |
| if not (lu and ji): | |
| return None | |
| return { | |
| "inlet": {"star": lu.get("star", ""), "palace": lu.get("palace", "")}, | |
| "vault": {"star": ji.get("star", ""), "palace": ji.get("palace", "")}, | |
| "reading": ( | |
| "Lu marks where resources flow IN; Ji marks the VAULT where they finally settle — " | |
| "a lifelong arena to cultivate, not a flaw. Always pair the vault's friction with its settlement." | |
| ), | |
| } | |
| def build_timing(daxun_analysis: Optional[Dict[str, Any]]) -> List[Dict[str, Any]]: | |
| if not daxun_analysis: | |
| return [] | |
| timing = [] | |
| for key, is_current in (("current", True), ("next", False)): | |
| d = daxun_analysis.get(key) | |
| if not d: | |
| continue | |
| # "next" daxun(predict_next_daxun的输出)不带end_age,只有start_age—— | |
| # 每步大限固定10年,缺失时用start_age+9兜底,跟evidence文本口径保持一致 | |
| end_age = d.get("end_age") | |
| if end_age is None and d.get("start_age") is not None: | |
| end_age = d["start_age"] + 9 | |
| timing.append({ | |
| "layer": "daxun", | |
| "label": d.get("palace_name", ""), | |
| "label_en": palace_term(d.get("palace_name", "")) if d.get("palace_name") else "", | |
| "start_age": d.get("start_age"), | |
| "end_age": end_age, | |
| "is_current": is_current, | |
| "flags": ["empty_palace"] if d.get("is_empty_palace") else [], | |
| }) | |
| return timing | |
| def build_evidence( | |
| chart: Dict[str, Any], | |
| palaces: List[Dict[str, Any]], | |
| sihua_analysis: Optional[Dict[str, Any]], | |
| daxun_analysis: Optional[Dict[str, Any]], | |
| ) -> str: | |
| lines: List[str] = [] | |
| ming_gong = next((p for p in palaces if p.get("name") == "命宫"), None) | |
| body_palace = next((p for p in palaces if p.get("is_body_palace")), None) | |
| if ming_gong: | |
| stars = ", ".join( | |
| star_with_brightness(s["name"], s.get("brightness", "")) | |
| for s in ming_gong.get("major_stars", []) if s.get("name") | |
| ) | |
| lines.append(f"- {palace_term('命宫')}: {stars or '(no major star / 借星安宫)'}.") | |
| lines.append(f"- Soul star (命主): {star_term(chart.get('soul', ''))}. Body star (身主): {star_term(chart.get('body', ''))}.") | |
| if body_palace: | |
| lines.append(f"- Body Palace (身宫) falls in: {palace_term(body_palace.get('name', ''))}.") | |
| if sihua_analysis: | |
| year_sihua = sihua_analysis.get("year_sihua", {}) | |
| parts = [] | |
| for mutagen_key, mutagen_char in (("化禄", "禄"), ("化权", "权"), ("化科", "科"), ("化忌", "忌")): | |
| entry = year_sihua.get(mutagen_key) | |
| if entry: | |
| parts.append(f"{mutagen_term(mutagen_char)} -> {star_term(entry.get('star',''))} @ {palace_term(entry.get('palace',''))}") | |
| if parts: | |
| lines.append("- Natal Four Transformations (生年四化): " + "; ".join(parts) + ".") | |
| flow = build_lu_ji_flow(sihua_analysis) | |
| if flow: | |
| lines.append( | |
| f"- Lu-Ji flow (禄因忌果): resources flow IN via {star_term(flow['inlet']['star'])} @ {palace_term(flow['inlet']['palace'])}; " | |
| f"they finally SETTLE in the VAULT at {star_term(flow['vault']['star'])} @ {palace_term(flow['vault']['palace'])}. " | |
| f"The Ji palace is the lifelong vault to cultivate — what truly belongs to this person accumulates there — " | |
| f"not a flaw to avoid; always pair its friction with its settlement." | |
| ) | |
| fy = build_flow_year_sihua(palaces) | |
| if fy: | |
| parts = [] | |
| for label in ("化禄", "化权", "化科", "化忌"): | |
| t = fy["transformations"].get(label, {}) | |
| if t.get("star"): | |
| loc = f" @ {palace_term(t['palace'])}" if t.get("palace") else "" | |
| bright = f" (brightness={brightness_term(t['brightness'])})" if t.get("brightness") in WEAK_BRIGHTNESS else "" | |
| parts.append(f"{mutagen_term(label[-1])} -> {star_term(t['star'])}{loc}{bright}") | |
| lines.append( | |
| f"- Flow-year Four Transformations (流年四化, {fy['year']} {fy['year_ganzhi']}): " | |
| + "; ".join(parts) | |
| + ". The flow-year Ji palace is the arena stress-tested THIS year (annual friction, not lifelong verdict)." | |
| ) | |
| strength = build_strength(palaces) | |
| if strength["weak_mutagen_stars"]: | |
| flagged = "; ".join( | |
| f"{star_term(w['star'])} {mutagen_term(w['mutagen'][-1] if len(w['mutagen'])>1 else w['mutagen'])} " | |
| f"but brightness={brightness_term(w['brightness'])}" | |
| for w in strength["weak_mutagen_stars"] | |
| ) | |
| lines.append( | |
| f"- CAUTION (signal present but weak execution): {flagged}. " | |
| f"The mutagen signal exists structurally but the star's own brightness is low — " | |
| f"deliver at a discount, must state both sides (structure vs. execution)." | |
| ) | |
| if daxun_analysis: | |
| current = daxun_analysis.get("current", {}) | |
| if current: | |
| major = ", ".join(current.get("palace_stars", {}).get("major", [])) | |
| lines.append( | |
| f"- Current Daxun (现行大限): {palace_term(current.get('palace_name',''))} " | |
| f"({current.get('start_age')}-{current.get('end_age')}), stars: {major or '(empty)'}." | |
| ) | |
| nxt = daxun_analysis.get("next", {}) | |
| if nxt: | |
| lines.append( | |
| f"- Next Daxun (下一大限): {palace_term(nxt.get('palace_name',''))} " | |
| f"({nxt.get('start_age')}-{nxt.get('start_age', 0) + 9})." | |
| ) | |
| return "\n".join(lines) | |