Spaces:
Running
Running
th commited on
Commit ·
5424d2c
1
Parent(s): 0649308
feat: 四层schema追加(meta/strength/timing/evidence) — /api/calculate-vedic-chart
Browse files只追加新字段,旧字段一个不改(已验证old_keys全部保留)。
- evidence.py:build_meta/build_strength/build_timing/build_evidence
- evidence部分是sisi-astrology前端generate-vedic-report.ts里formatEvidence()的
Python移植版(已于2026-07-12在vedic-astrology skill的local_vedic_report.py里
验证过输出正确),现在下沉到API侧生成——这是Phase 3的核心动机:消费方(CF Function)
以后可以优先读API返回的evidence,不用自己每次重新拼一份
- strength统一shadbala/sav_by_house/dignity/combustion(不重复计算,只是换个
位置引用,跟bazi/ziwei的schema形状保持一致)
- timing展开mahadasha全序列+当前mahadasha内的antardasha逐条(月级颗粒度)
- 已用小鱼盘验证:土星Shadbala 132.6%、当前Moon-Venus antardasha、
Atmakaraka/DOUBLE-TRANSIT等关键证据行全部正确;
test_golden_charts.py新增1条,6项全过(需在_vedic-spike-venv里跑,
已装fastapi/kerykeion/python-dotenv补全依赖)
🦊 Generated with Sisi
- app.py +9 -0
- evidence.py +227 -0
- vedic/test_golden_charts.py +31 -0
app.py
CHANGED
|
@@ -16,6 +16,7 @@ import io
|
|
| 16 |
import re
|
| 17 |
import os
|
| 18 |
import secrets
|
|
|
|
| 19 |
|
| 20 |
# ===== Knowledge Base Extractor (Embedded) =====
|
| 21 |
# Extracts professional aspect interpretations from markdown knowledge base
|
|
@@ -1172,6 +1173,14 @@ def calculate_vedic_chart(req: BirthRequest, api_key: str = Depends(verify_api_k
|
|
| 1172 |
finally:
|
| 1173 |
_sys.stdout = _real_stdout
|
| 1174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1175 |
logger.info(f"✅ Vedic chart success for {req.name}")
|
| 1176 |
return {"success": True, "data": _vedic_jsonable(chart)}
|
| 1177 |
|
|
|
|
| 16 |
import re
|
| 17 |
import os
|
| 18 |
import secrets
|
| 19 |
+
from evidence import build_meta, build_strength, build_timing, build_evidence
|
| 20 |
|
| 21 |
# ===== Knowledge Base Extractor (Embedded) =====
|
| 22 |
# Extracts professional aspect interpretations from markdown knowledge base
|
|
|
|
| 1173 |
finally:
|
| 1174 |
_sys.stdout = _real_stdout
|
| 1175 |
|
| 1176 |
+
# 2026-07-12 Phase 3: 追加 meta/strength/timing/evidence 四层字段,旧字段一个不改。
|
| 1177 |
+
# evidence是CF Function里generate-vedic-report.ts的formatEvidence()的Python移植版,
|
| 1178 |
+
# 下沉到API侧生成,消费方(CF Function)优先读这里返回的evidence,不用自己再拼一份。
|
| 1179 |
+
chart['meta'] = build_meta()
|
| 1180 |
+
chart['strength'] = build_strength(chart)
|
| 1181 |
+
chart['timing'] = build_timing(chart)
|
| 1182 |
+
chart['evidence'] = build_evidence(chart)
|
| 1183 |
+
|
| 1184 |
logger.info(f"✅ Vedic chart success for {req.name}")
|
| 1185 |
return {"success": True, "data": _vedic_jsonable(chart)}
|
| 1186 |
|
evidence.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
印度占星四层Schema组装:meta / strength / timing / evidence。
|
| 3 |
+
|
| 4 |
+
只追加新字段,不改动任何旧字段——见 API_UNIFICATION_PLAN.md §3。
|
| 5 |
+
调用方:app.py 的 /api/calculate-vedic-chart endpoint。
|
| 6 |
+
|
| 7 |
+
evidence部分是 sisi-astrology 前端 generate-vedic-report.ts 里 formatEvidence()
|
| 8 |
+
的Python移植版(已于2026-07-12用小鱼真实生辰在 vedic-astrology skill 的
|
| 9 |
+
local_vedic_report.py 里验证过输出正确)——现在把它下沉到API侧生成,而不是
|
| 10 |
+
让每个消费方(CF Function)各自重新拼一份,这是Phase 3的核心动机。
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from typing import Any, Dict, List, Optional
|
| 14 |
+
|
| 15 |
+
GRAHA_ORDER = ["Lagna", "Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu"]
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def build_meta() -> Dict[str, str]:
|
| 19 |
+
return {"engine": "pyjhora", "schema_version": "1.0"}
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def build_strength(chart: Dict[str, Any]) -> Dict[str, Any]:
|
| 23 |
+
"""力量层:把已经算好的shadbala/SAV/dignity/combustion归拢成统一的strength字段,
|
| 24 |
+
跟bazi/ziwei的schema保持形状一致——不重复计算,只是换个位置引用。"""
|
| 25 |
+
return {
|
| 26 |
+
"shadbala": chart.get("shadbala"),
|
| 27 |
+
"sav_by_house": chart.get("sav_by_house"),
|
| 28 |
+
"dignity": chart.get("dignity"),
|
| 29 |
+
"combustion": chart.get("combustion"),
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def build_timing(chart: Dict[str, Any]) -> List[Dict[str, Any]]:
|
| 34 |
+
"""时间线:mahadasha全序列 + 当前mahadasha内的antardasha逐条展开(月级颗粒度)。"""
|
| 35 |
+
timing: List[Dict[str, Any]] = []
|
| 36 |
+
for d in chart.get("dashas") or []:
|
| 37 |
+
timing.append({
|
| 38 |
+
"layer": "mahadasha",
|
| 39 |
+
"label": d.get("planet", ""),
|
| 40 |
+
"label_en": d.get("planet", ""),
|
| 41 |
+
"start": d.get("start"),
|
| 42 |
+
"end": d.get("end"),
|
| 43 |
+
"is_current": bool(d.get("is_current", False)),
|
| 44 |
+
"flags": [],
|
| 45 |
+
})
|
| 46 |
+
if d.get("is_current"):
|
| 47 |
+
for a in d.get("antardashas") or []:
|
| 48 |
+
timing.append({
|
| 49 |
+
"layer": "antardasha",
|
| 50 |
+
"label": f"{d.get('planet','')}-{a.get('planet','')}",
|
| 51 |
+
"label_en": f"{d.get('planet','')}-{a.get('planet','')}",
|
| 52 |
+
"start": a.get("start"),
|
| 53 |
+
"end": a.get("end"),
|
| 54 |
+
"is_current": bool(a.get("is_current", False)),
|
| 55 |
+
"flags": [],
|
| 56 |
+
})
|
| 57 |
+
return timing
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def build_evidence(v: Dict[str, Any]) -> str:
|
| 61 |
+
"""结论性事实 + 括号内数据来源,AI可以直接引用出证据链。"""
|
| 62 |
+
lines: List[str] = []
|
| 63 |
+
hl = v.get("house_lords") or {}
|
| 64 |
+
|
| 65 |
+
def lord(h):
|
| 66 |
+
return hl.get(str(h)) or hl.get(h)
|
| 67 |
+
|
| 68 |
+
def dignity_of(p):
|
| 69 |
+
d = (v.get("dignity") or {}).get(p)
|
| 70 |
+
return d.get("basic") if d else "—"
|
| 71 |
+
|
| 72 |
+
l1 = lord(1)
|
| 73 |
+
if l1:
|
| 74 |
+
lines.append(
|
| 75 |
+
f"- Lagna (1st) lord: {l1['sign']} ruled by {l1['lord']}, sitting in house "
|
| 76 |
+
f"{l1['lord_house']} (dignity: {dignity_of(l1['lord'])})."
|
| 77 |
+
)
|
| 78 |
+
all_lords = []
|
| 79 |
+
for h in range(1, 13):
|
| 80 |
+
L = lord(h)
|
| 81 |
+
if L and L.get("lord_house"):
|
| 82 |
+
all_lords.append(f"H{h}:{L['lord']}→H{L['lord_house']}")
|
| 83 |
+
if all_lords:
|
| 84 |
+
lines.append(
|
| 85 |
+
f"- All house lords (H=house, →=sits in): {' '.join(all_lords)}. "
|
| 86 |
+
f"For yoga audit: kendra lords 1/4/7/10 + trikona lords 1/5/9 linked = Raja Yoga; "
|
| 87 |
+
f"2/5/9/11 lords linked = Dhana Yoga; 6/8/12 lords sitting in each other's houses = Vipareeta Raja."
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
ak = (v.get("karakas") or {}).get("7k", [None])[0]
|
| 91 |
+
if ak:
|
| 92 |
+
lines.append(f"- Atmakaraka (soul significator, highest-degree planet): {ak[1]} — the central karmic lesson of this life.")
|
| 93 |
+
|
| 94 |
+
d9 = v.get("d9")
|
| 95 |
+
if isinstance(d9, dict):
|
| 96 |
+
vargottama = v.get("vargottama") or {}
|
| 97 |
+
parts = [f"{k}→{d9[k][0]}{'★' if vargottama.get(k) else ''}" for k in GRAHA_ORDER if k in d9]
|
| 98 |
+
if parts:
|
| 99 |
+
lines.append(f"- D9 Navamsa (marriage, inner strength, latter half of life; ★=Vargottama=doubly strong): {', '.join(parts)}.")
|
| 100 |
+
d10 = v.get("d10")
|
| 101 |
+
if isinstance(d10, dict):
|
| 102 |
+
parts = [f"{k}→{d10[k][0]}" for k in GRAHA_ORDER if k in d10]
|
| 103 |
+
if parts:
|
| 104 |
+
lines.append(f"- D10 Dasamsa (career, status, profession): {', '.join(parts)}.")
|
| 105 |
+
|
| 106 |
+
sav_h = v.get("sav_by_house")
|
| 107 |
+
if isinstance(sav_h, dict):
|
| 108 |
+
entries = sorted([{"h": int(h), "val": (sav_h[h] or {}).get("value", 0)} for h in sav_h.keys()], key=lambda e: e["h"])
|
| 109 |
+
if entries:
|
| 110 |
+
sav_str = " ".join(f"H{e['h']}:{e['val']}" for e in entries)
|
| 111 |
+
by_val = sorted(entries, key=lambda e: e["val"], reverse=True)
|
| 112 |
+
strong = ", ".join(f"H{e['h']}({e['val']})" for e in by_val[:2])
|
| 113 |
+
weak = ", ".join(f"H{e['h']}({e['val']})" for e in by_val[-2:])
|
| 114 |
+
lines.append(f"- Ashtakavarga SAV per house (life-area strength, ~28 = average): {sav_str}. Strongest areas: {strong}; weakest: {weak}.")
|
| 115 |
+
|
| 116 |
+
comb = v.get("combustion") or {}
|
| 117 |
+
comb_str = ", ".join(f"{p} ({comb[p].get('distance', 0):.1f}° from Sun)" for p in comb) if comb else "none"
|
| 118 |
+
lines.append(f"- Combust planets (weakened by the Sun's glare): {comb_str}.")
|
| 119 |
+
|
| 120 |
+
dign = v.get("dignity") or {}
|
| 121 |
+
hard = [p for p in dign if dign[p].get("basic") in ("debilitated", "enemy", "great_enemy") or dign[p].get("compound") in ("debilitated", "enemy", "great_enemy")]
|
| 122 |
+
if hard:
|
| 123 |
+
lines.append(f"- Planets under strain (enemy/debilitated): {', '.join(hard)}.")
|
| 124 |
+
|
| 125 |
+
dashas = v.get("dashas") or []
|
| 126 |
+
if dashas:
|
| 127 |
+
maha = "; ".join(f"{d['planet']} {d['start']}–{d['end']}{' (CURRENT)' if d.get('is_current') else ''}" for d in dashas)
|
| 128 |
+
lines.append(f"- Vimshottari Mahadasha timeline: {maha}.")
|
| 129 |
+
cur = next((d for d in dashas if d.get("is_current")), None)
|
| 130 |
+
if cur and cur.get("antardashas"):
|
| 131 |
+
ad = "; ".join(f"{a['planet']} {a['start']}–{a['end']}{' (CURRENT)' if a.get('is_current') else ''}" for a in cur["antardashas"])
|
| 132 |
+
lines.append(f"- Antardashas inside the current {cur['planet']} Mahadasha (use these for 3–5 year windows): {ad}.")
|
| 133 |
+
|
| 134 |
+
t = v.get("transits") or {}
|
| 135 |
+
tparts = []
|
| 136 |
+
if t.get("Saturn"):
|
| 137 |
+
tparts.append(f"Saturn transiting {t['Saturn']['sign']} (your house {t['Saturn']['house']})")
|
| 138 |
+
if t.get("Jupiter"):
|
| 139 |
+
tparts.append(f"Jupiter transiting {t['Jupiter']['sign']} (your house {t['Jupiter']['house']})")
|
| 140 |
+
if t.get("sade_sati"):
|
| 141 |
+
tparts.append(f"Sade Sati status: {t['sade_sati']}")
|
| 142 |
+
if tparts:
|
| 143 |
+
lines.append(f"- Gochar (current transits): {'; '.join(tparts)}.")
|
| 144 |
+
|
| 145 |
+
sb = v.get("shadbala") or {}
|
| 146 |
+
if sb and not sb.get("error"):
|
| 147 |
+
parts = []
|
| 148 |
+
for p in sb:
|
| 149 |
+
s = sb[p]
|
| 150 |
+
if s.get("total_rupas") is None:
|
| 151 |
+
continue
|
| 152 |
+
tag = " WEAK" if s.get("classification") == "weak" else (" STRONG" if s.get("classification") == "strong" else "")
|
| 153 |
+
parts.append(f"{p} {s['total_rupas']}R/{s['strength_pct']}%{tag}")
|
| 154 |
+
if parts:
|
| 155 |
+
lines.append(
|
| 156 |
+
f"- Shadbala six-fold strength (R=rupas, %=vs BPHS required minimum; "
|
| 157 |
+
f"<100% = under-powered to DELIVER results even if well-placed): {', '.join(parts)}."
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
aspects = v.get("aspects") or []
|
| 161 |
+
tight = [f"{a['p1']}+{a['p2']} ({a['orb']}°)" for a in aspects if a.get("type") == "合相" and isinstance(a.get("orb"), (int, float)) and a["orb"] <= 3]
|
| 162 |
+
if tight:
|
| 163 |
+
lines.append(
|
| 164 |
+
f"- Tight conjunctions ≤3° (BUNDLED: the two planets' agendas are tied together — "
|
| 165 |
+
f"benefits and costs cannot be separated; always state both sides): {', '.join(tight)}."
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
lag_deg = (v.get("lagna") or {}).get("degree")
|
| 169 |
+
if isinstance(lag_deg, (int, float)) and (lag_deg < 1 or lag_deg > 29):
|
| 170 |
+
lines.append(
|
| 171 |
+
f"- Lagna at {lag_deg:.2f}° — SANDHI (junction degree): identity carries the adjacent "
|
| 172 |
+
f"sign's transitional flavor, and birth-time sensitivity is high (gently suggest time rectification)."
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
moon = (v.get("planets") or {}).get("Moon")
|
| 176 |
+
if moon and isinstance(moon.get("sign_idx"), int):
|
| 177 |
+
moon_idx = moon["sign_idx"]
|
| 178 |
+
flanked = any(
|
| 179 |
+
(pd["sign_idx"] - moon_idx + 12) % 12 in (1, 11)
|
| 180 |
+
for p, pd in (v.get("planets") or {}).items() if p not in ("Moon", "Sun", "Rahu", "Ketu")
|
| 181 |
+
)
|
| 182 |
+
if not flanked:
|
| 183 |
+
lines.append(
|
| 184 |
+
"- Kemadruma indication: no planet flanks the Moon (2nd & 12th from Moon empty) — "
|
| 185 |
+
"thin emotional buffer, processes feelings alone. MUST check cancellation (planet in "
|
| 186 |
+
"kendra from Lagna/Moon, or Jupiter aspecting Moon) before asserting it."
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
mp = v.get("moon_phase")
|
| 190 |
+
if mp:
|
| 191 |
+
lines.append(f"- Moon phase: {'waxing (Shukla — benefic strength rising)' if mp.get('waxing') else 'waning (Krishna — benefic strength reduced)'}.")
|
| 192 |
+
|
| 193 |
+
gd = v.get("graha_drishti") or {}
|
| 194 |
+
if gd:
|
| 195 |
+
parts = []
|
| 196 |
+
for p in gd:
|
| 197 |
+
d = gd[p]
|
| 198 |
+
houses = "/H".join(str(h) for h in (d.get("houses") or []))
|
| 199 |
+
planets_on = f" [on {','.join(d['planets'])}]" if d.get("planets") else ""
|
| 200 |
+
parts.append(f"{p}→H{houses}{planets_on}")
|
| 201 |
+
if parts:
|
| 202 |
+
lines.append(f"- Graha drishti (classical vedic full aspects — use for affliction/support checks, NOT western orbs): {'; '.join(parts)}.")
|
| 203 |
+
|
| 204 |
+
tt = v.get("transit_timeline")
|
| 205 |
+
if isinstance(tt, dict):
|
| 206 |
+
def segs(arr):
|
| 207 |
+
return "; ".join(f"{s['sign']}(H{s['house']}) {s['start']}→{s['end']}" for s in arr)
|
| 208 |
+
if tt.get("saturn_segments"):
|
| 209 |
+
lines.append(f"- Saturn transit timeline next 5y (retrograde dips included — a sign re-entry is a revisit, not an error): {segs(tt['saturn_segments'])}.")
|
| 210 |
+
if tt.get("jupiter_segments"):
|
| 211 |
+
lines.append(f"- Jupiter transit timeline next 5y: {segs(tt['jupiter_segments'])}.")
|
| 212 |
+
if tt.get("sade_sati_windows"):
|
| 213 |
+
ssw = "; ".join(f"{w['phase']} {w['start']}→{w['end']}" for w in tt["sade_sati_windows"])
|
| 214 |
+
lines.append(f"- Sade Sati windows within 5y: {ssw}.")
|
| 215 |
+
dtw = tt.get("double_transit_windows") or []
|
| 216 |
+
hits = [w for w in dtw if w.get("houses")]
|
| 217 |
+
if hits:
|
| 218 |
+
hit_parts = []
|
| 219 |
+
for w in hits:
|
| 220 |
+
hstr = "H" + "/H".join(str(h) for h in w["houses"])
|
| 221 |
+
hit_parts.append(f"{hstr} {w['start']}→{w['end']}")
|
| 222 |
+
lines.append(
|
| 223 |
+
f"- DOUBLE-TRANSIT windows (Saturn AND Jupiter both activating the same house — "
|
| 224 |
+
f"the classic event trigger WHEN the running dasha also supports that house): {'; '.join(hit_parts)}."
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
return "\n".join(lines)
|
vedic/test_golden_charts.py
CHANGED
|
@@ -73,3 +73,34 @@ def test_current_dasha(xiaoyu_chart):
|
|
| 73 |
assert current["planet"] == "Moon"
|
| 74 |
assert current["start"] == "2017-05"
|
| 75 |
assert current["end"] == "2027-05"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
assert current["planet"] == "Moon"
|
| 74 |
assert current["start"] == "2017-05"
|
| 75 |
assert current["end"] == "2027-05"
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def test_four_layer_schema_via_app_endpoint():
|
| 79 |
+
"""
|
| 80 |
+
2026-07-12 Phase 3新增:/api/calculate-vedic-chart 追加 meta/strength/timing/evidence,
|
| 81 |
+
evidence是CF Function里formatEvidence()的Python移植版,下沉到API侧生成。
|
| 82 |
+
这里直接调FastAPI handler(需要fastapi+kerykeion,跟pyswisseph装在同一个venv里)。
|
| 83 |
+
"""
|
| 84 |
+
import sys, os
|
| 85 |
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # sisi-astrology-api/ 根目录
|
| 86 |
+
from app import calculate_vedic_chart, BirthRequest
|
| 87 |
+
|
| 88 |
+
req = BirthRequest(
|
| 89 |
+
name="Xiaoyu", date="1981-10-04", time="20:40", location="Fuzhou",
|
| 90 |
+
latitude=26.0745, longitude=119.2965, timezone="Asia/Shanghai",
|
| 91 |
+
)
|
| 92 |
+
resp = calculate_vedic_chart(req, api_key="bypass")
|
| 93 |
+
d = resp["data"]
|
| 94 |
+
|
| 95 |
+
assert d["meta"]["schema_version"] == "1.0"
|
| 96 |
+
assert d["strength"]["shadbala"]["Saturn"]["strength_pct"] == pytest.approx(132.5, abs=1.0)
|
| 97 |
+
|
| 98 |
+
current_maha = next(t for t in d["timing"] if t["layer"] == "mahadasha" and t["is_current"])
|
| 99 |
+
assert current_maha["label"] == "Moon"
|
| 100 |
+
current_antar = next(t for t in d["timing"] if t["layer"] == "antardasha" and t["is_current"])
|
| 101 |
+
assert current_antar["label"] == "Moon-Venus"
|
| 102 |
+
|
| 103 |
+
evidence = d["evidence"]
|
| 104 |
+
assert "Atmakaraka" in evidence and "Moon" in evidence
|
| 105 |
+
assert "Shadbala" in evidence
|
| 106 |
+
assert "DOUBLE-TRANSIT" in evidence
|