fix(yoga): Chinese combination text from a Chinese source, not 未判定 (#91 fix2 F3, BUG-1418)

Removing the English-to-Chinese back-translation left every BVR semantic
note and every yogas_doshas raj/dhana "H*_Lord(...) in H*" sentence as
未判定 in the Chinese yoga table and pages.

- yoga_engine: semantic conditions return BiText(Chinese, old note). The
  Chinese comes from references/yoga_semantic_notes_zh.json, one sentence per
  note, so dedup keeps the same equality classes. English-only custom
  templates get the same lookup. combination_en is unchanged.
- yogas_doshas: raj/dhana rows add combination_zh; combination is unchanged.
- chinese_combination prefers a clean combination_zh.

Which yogas fire, their strength and category are unchanged (fictional
charts 0-19, same rows as staging; only set-iteration order differs).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HwMNZEzdzmrbPhuRNmZe3C
This commit is contained in:
Jesse_Chen
2026-10-22 21:55:02 +08:00
co-authored by Claude Opus 5.5
parent 00c2b4d498
commit ac3dccbac8
6 changed files with 382 additions and 3 deletions
+169
View File
@@ -0,0 +1,169 @@
{
"10th house occupied by lords of 10th, 2nd and 7th": "第10宫有10宫主、2宫主与7宫主",
"10th lord conjoined with 9th lord": "10宫主与9宫主合相",
"10th lord in 3rd house with Saturn": "10宫主与土星同在第3宫",
"10th lord in 6th, 8th or 12th house": "10宫主在第6、8或12宫",
"10th lord in trine from 7th house": "10宫主在第7宫起的三方宫",
"10th lord's navamsa dispositor is exalted in 10th and lagna lord joins him": "10宫主 D9 定位星擢升落第10宫,且上升主与之相会",
"11th lord conjoins Venus": "11宫主与金星相会",
"11th lord in deep exaltation": "11宫主在深度擢升点",
"12th lord occupies the 12th house (Vipareeta Raja Yoga)": "12宫主落第12宫(逆转王瑜伽)",
"1st and 11th lords exchange": "1宫主与11宫主互换星座",
"2nd and 10th lords exchange": "2宫主与10宫主互换星座",
"2nd lord in 10th with malefics": "2宫主与凶星同在第10宫",
"2nd lord in house of Saturn or Mars": "2宫主落在土星或火星守护的星座",
"2nd lord joins Jupiter in 8th house": "2宫主与木星同在第8宫",
"3rd house is a benefic sign aspected by benefic planets": "第3宫是吉星星座并受吉星相位",
"3rd lord joins a benefic amsa (with or aspected by benefic)": "3宫主落吉星分度(与吉星同宫或受其相位)",
"3rd lord joins the Moon": "3宫主与月亮相会",
"4th and 10th lords exchange signs (Parivartana)": "4宫主与10宫主互换星座(互容)",
"4th lord aspected by malefic": "4宫主受凶星相位",
"4th lord exalted": "4宫主擢升",
"5th house occupied by malefics": "第5宫有凶星",
"5th lord aspected by another benefic or in benefic sign": "5宫主受另一吉星相位或落吉星星座",
"5th lord conjunct malefics": "5宫主与凶星合相",
"5th lord in 6th, 8th or 12th": "5宫主在第6、8或12宫",
"5th lord is a natural benefic": "5宫主是天然吉星",
"5th lord with benefic and Mars/Saturn in 5th": "5宫主与吉星同宫,且火星或土星在第5宫",
"6th house further aspected by another malefic": "第6宫另受一颗凶星相位",
"6th lord in conjunction with Venus": "6宫主与金星合相",
"6th lord is a natural malefic": "6宫主是天然凶星",
"6th lord occupies the 6th house (Vipareeta Raja Yoga)": "6宫主落第6宫(逆转王瑜伽)",
"7 visible planets occupy exactly 3 distinct signs": "七颗可见行星恰好分布在三个星座",
"7 visible planets occupy exactly 4 distinct signs": "七颗可见行星恰好分布在四个星座",
"7 visible planets occupy exactly 5 distinct signs": "七颗可见行星恰好分布在五个星座",
"7 visible planets occupy exactly 6 distinct signs": "七颗可见行星恰好分布在六个星座",
"7 visible planets occupy exactly 7 distinct signs": "七颗可见行星恰好分布在七个星座",
"7th lord conjunct Venus, aspected by Saturn": "7宫主与金星合相,并受土星相位",
"7th lord exalted in 10th": "7宫主擢升落第10宫",
"8th house is not Jupiter's own or exaltation sign": "第8宫不是木星的本座或擢升座",
"8th lord occupies the 8th house (Vipareeta Raja Yoga)": "8宫主落第8宫(逆转王瑜伽)",
"9th lord aspected by benefic": "9宫主受吉星相位",
"9th lord exalted": "9宫主擢升",
"All 7 visible planets exclusively in movable signs (Ar/Cn/Li/Cp)": "七颗可见行星只在基本星座(白羊、巨蟹、天秤、摩羯)",
"All 7 visible planets in 7 consecutive houses starting from non-Kendra, each house occupied": "七颗可见行星从非角宫起连续占据七个宫,每宫都有星",
"All planets exclusively in dual signs (Ge/Vi/Sg/Pi)": "所有行星都只在双体星座(双子、处女、射手、双鱼)",
"All planets exclusively in fixed signs (Ta/Le/Sc/Aq)": "所有行星都只在固定星座(金牛、狮子、天蝎、水瓶)",
"All planets in even houses (2,4,6,8,10,12) from Lagna": "所有行星都在上升起的偶数宫(2、4、6、8、10、12)",
"All planets in odd houses (1,3,5,7,9,11) from Lagna": "所有行星都在上升起的奇数宫(1、3、5、7、9、11)",
"Aspected by benefics": "受吉星相位",
"BVR definition: TBD": "BVR 定义待补",
"Benefic in 4th house": "吉星在第4宫",
"Benefic in 9th house": "吉星在第9宫",
"Benefics in Upachaya houses (3,6,10,11) from Lagna or Moon": "吉星在上升或月亮起的成长宫(3、6、10、11)",
"Benefics in both 2nd and 12th from Lagna, not aspected by malefics": "上升两侧第2、12宫都有吉星,且不受凶星相位",
"Born during daytime with waxing Moon": "白天出生且月亮渐盈",
"Both 11th lord and Venus in a quadrant from lagna lord": "11宫主与金星都在上升主起的角宫",
"Jupiter and Venus in kendras": "木星与金星在角宫",
"Jupiter conjunct Saturn and Moon in 6th house": "木星与土星、月亮同在第6宫",
"Jupiter in 12th house": "木星在第12宫",
"Jupiter in 2nd or 5th house, conjoined or aspected by Mercury and Venus": "木星在第2或5宫,与水星和金星合相或受其相位",
"Jupiter in 3rd house": "木星在第3宫",
"Jupiter in 5th or 9th from Moon": "木星在月亮起第5或第9宫",
"Jupiter in 5th or 9th from Venus": "木星在金星起第5或第9宫",
"Jupiter in Lagna or aspects Lagna from watery sign": "木星在上升宫,或从水象星座相位上升宫",
"Jupiter in own, friendly or exaltation sign": "木星在本座、友座或擢升座",
"Jupiter is lord of 5th house (Lagna in Leo or Scorpio)": "木星守护第5宫(狮子或天蝎上升)",
"Jupiter is weak (debilitated, combust, or enemy sign)": "木星偏弱(落陷、燃烧或在敌座)",
"Jupiter weak or afflicted": "木星弱或受克",
"Jupiter, Venus, Mercury and Moon all in kendras (1,4,7,10) from Lagna": "木星、金星、水星与月亮都在上升起的角宫(1、4、7、10)",
"Lagna has benefics and not affected by malefics": "上升宫有吉星且不受凶星影响",
"Lagna has malefics and not affected by benefics": "上升宫有凶星且不受吉星影响",
"Lagna in a fixed sign": "上升在固定星座",
"Lagna in fixed sign aspected by 6th lord": "上升在固定星座,并受6宫主相位",
"Lagna in malefic sign (Ar/Sc/Cp/Aq) or Sg/Ta aspected by malefics": "上升在凶星星座(白羊、天蝎、摩羯、水瓶),或在射手、金牛并受凶星相位",
"Lagna in watery sign with benefics OR Lagna lord is watery planet": "上升在水象星座且有吉星,或上升主是水象行星",
"Lagna lord and 2nd lord exchange their places (Parivartana)": "上升主与2宫主互换星座(互容)",
"Lagna lord and navamsa lord both in watery signs": "上升主与其 D9 所落星座的主星都在水象星座",
"Lagna lord aspected by Saturn": "上升主受土星相位",
"Lagna lord conjoined or aspected by Jupiter": "上升主与木星合相或受木星相位",
"Lagna lord conjunct/aspected by malefics": "上升主与凶星合相或受凶星相位",
"Lagna lord exalted in a quadrant with Jupiter's aspect": "上升主在角宫擢升,并受木星相位",
"Lagna lord in Mercury sign with Mercury": "上升主落在水星守护的星座且与水星同宫",
"Lagna lord in a dry sign": "上升主落在干燥星座",
"Lagna lord is exalted": "上升主擢升",
"Lagna lord joins Mars and Mercury in 4th or 12th": "上升主与火星、水星同在第4或12宫",
"Lagna lord, his dispositor, the latter's dispositor in rasi and navamsa, all in kendras/trines/exaltation": "上升主、其定位星及再下一层定位星在本命盘与 D9 中都落角宫、三方宫或擢升",
"Lagna malefic/Gulika in trine OR Gulika with Kendra/Trine lords OR L1 with Rahu/Sat/Ket": "上升宫有凶星且 Gulika 在三方宫,或 Gulika 与角宫、三方宫主同宫,或上升主与罗睺、土星、计都同宫",
"Lord of navamsa occupied by 4th lord in 12th": "4宫主所落 D9 星座的主星在第12宫",
"Lord of navamsa occupied by 4th lord in kendra from Lagna and Chandra Lagna": "4宫主所落 D9 星座的主星在上升与月亮上升起的角宫",
"Lord of navamsa occupied by Moon is exalted in rasi": "月亮所落 D9 星座的主星在本命盘擢升",
"Lord of sign occupied by lagna lord, or navamsa dispositor thereof, in kendra/trine/own/exaltation": "上升主所落星座的主星,或其 D9 定位星,在角宫、三方宫、本座或擢升座",
"Lord of sign occupied in navamsa by 2nd/5th/11th lord, exalted and joins the 9th lord": "2/5/11宫主在 D9 所落星座的主星擢升,并与9宫主相会",
"Lords of 10th and 6th in Lagna with 2nd lord": "10宫主、6宫主与2宫主同在上升宫",
"Lords of 10th and 6th in Neechamsat (debilitation)": "10宫主与6宫主在 D9 落陷",
"Lords of 4th and 10th conjoined with Saturn and Mars": "4宫主与10宫主和土星、火星合相",
"Lords of Aroodha Lagna (A1/AL) and Aroodha Dwadasa (A12/UL) in conjunction": "Arudha 上升(A1/AL)与 Arudha 第12宫(A12/UL)的主星合相",
"Lords of Lagna, 7th and 5th are weak": "上升主、7宫主与5宫主都弱",
"Malefic 6th lord in Lagna, 8th or 10th": "凶性6宫主在上升宫、第8或10宫",
"Malefics in both 2nd and 12th from Lagna, not aspected by benefics": "上升两侧第2、12宫都有凶星,且不受吉星相位",
"Malefics join Rasi Lagna": "凶星落本命上升宫",
"Malefics join kendras and trines": "凶星同时落入角宫与三方宫",
"Malefics join trines": "凶星落入三方宫",
"Malefics occupy 12th, 7th and Lagna": "凶星占据第12宫、第7宫与上升宫",
"Malika Yoga (planets in consecutive houses) starting from 11th house": "从第11宫起的 Malika 格局(行星连续占宫)",
"Mandi and Rahu in 3rd house": "Mandi 与罗睺同在第3宫",
"Mandi in 2nd house": "Mandi 在第2宫",
"Mars and Saturn in 5th house": "火星与土星同在第5宫",
"Mars conjoins 3rd lord and Rahu/Saturn/Mandi in cruel shashtiamsas": "火星与3宫主及罗睺、土星或 Mandi 同在凶性六十分",
"Mars in 2nd house": "火星在第2宫",
"Mars in 3rd in Preta Puriha sashtiamsa (cruel)": "火星在第3宫,落 Preta Puriha 六十分(凶)",
"Mars in 5/7/9, malefic moon, Saturn in 12th": "火星在第5、7或9宫,月亮受凶,土星在第12宫",
"Mars in 7th from Sun": "火星在太阳起第7宫",
"Mars is lord of 6th or 8th": "火星守护第6或第8宫",
"Mars joins Saturn": "火星与土星相会",
"Mercury 2nd from Sun": "水星在太阳起第2宫",
"Mercury conjunct or aspected by a malefic": "水星与凶星合相或受凶星相位",
"Mercury in 3rd house": "水星在第3宫",
"Mercury in a quadrant": "水星在角宫",
"Mercury is lord of both Lagna and 4th (Gemini or Virgo Lagna)": "水星同时守护上升与第4宫(双子或处女上升)",
"Mercury, Jupiter and Venus each in kendra, trine or 2nd house": "水星、木星、金星各在角宫、三方宫或第2宫",
"Method 1: 9th lord from 11th in 11th with Moon, aspected by 11th lord": "算法一:11宫起的9宫主与月亮同在第11宫,并受11宫主相位",
"Method 2: 9th lord from Lagna and Moon in 11th": "算法二:上升起的9宫主与月亮同在第11宫",
"Moon 11th from Mercury (BVR method)": "月亮在水星起第11宫(BVR 算法)",
"Moon and Mars in conjunction in any house": "月亮与火星在同一宫合相",
"Moon and Venus in 7th, malefics in 4th and 10th": "月亮与金星在第7宫,凶星在第4宫与第10宫",
"Moon aspected by Lagna lord in 6th, 8th or 12th": "月亮受落在第6、8或12宫的上升主相位",
"Moon conjunct Saturn, Mandi or Rahu": "月亮与土星、Mandi 或罗睺合相",
"Moon in 10th from Sun": "月亮在太阳起第10宫",
"Moon in 10th, Venus in 7th, malefics in 4th": "月亮在第10宫,金星在第7宫,凶星在第4宫",
"Moon in 5th from Jupiter": "月亮在木星起第5宫",
"Moon in 9th house": "月亮在第9宫",
"Moon in Lagna": "月亮在上升宫",
"Moon in conjunction with Saturn": "月亮与土星合相",
"Moon is strong": "月亮有力",
"Moon joins malefics in navamsa of Cancer or Scorpio": "月亮在巨蟹座或天蝎座 D9 与凶星同宫",
"Navamsa Lagna owned by dry planet": "D9 上升由干燥行星守护",
"No Jupiter aspect on Moon": "月亮不受木星相位",
"Powerful lords of 5th and 7th join with 6th lord": "有力的5宫主与7宫主和6宫主相会",
"PyJHora aligned source logic": "按 PyJHora 源码条件判定",
"Rahu in 2nd house": "罗睺在第2宫",
"Rahu in Lagna": "罗睺在上升宫",
"Rahu in Lagna in conjunction with Moon": "罗睺在上升宫与月亮合相",
"Raja Yoga disrupted by malefic associations or aspects": "王瑜伽因凶星同宫或相位受损",
"Saturn aspects 5th lord OR Moon in 5th house": "土星相位5宫主,或月亮在第5宫",
"Saturn exalted and in kendra": "土星擢升且在角宫",
"Saturn in 12th house": "土星在第12宫",
"Saturn in 2nd from Sun": "土星在太阳起第2宫",
"Saturn in 5th house": "土星在第5宫",
"Saturn in 9th house": "土星在第9宫",
"Strong benefic in Lagna, 3rd or 5th, simultaneously aspecting 9th": "有力吉星在第1、3或5宫,同时相位第9宫",
"Sun and Mandi in 2nd house": "太阳与 Mandi 同在第2宫",
"Sun and Mercury in the same house (Budha-Aditya conjunction)": "太阳与水星同宫(水日合相)",
"Sun and Saturn in 5th house": "太阳与土星同在第5宫",
"Sun exalted and strong": "太阳擢升且有力",
"Sun in 6th house in conjunction with a malefic": "太阳在第6宫与凶星合相",
"Sun in 7th house": "太阳在第7宫",
"Sun in kendra from Moon": "太阳在月亮起的角宫",
"Sun in own, exalted or friendly sign": "太阳在本座、擢升座或友座",
"Sun in the 10th house": "太阳在第10宫",
"Sun joins lords of navamsa signs occupied by 6th and 9th lords": "太阳与6宫主、9宫主所落 D9 星座的主星相会",
"Sun or Saturn with Rahu in cruel shashtiamsa": "太阳或土星与罗睺同在凶性六十分",
"Sun strong in Leo": "太阳在狮子座有力",
"Sun, Moon and Mars are in mutual trines (trikona) from each other": "太阳、月亮、火星彼此成三方关系",
"Two benefics join in 7th, 9th or 10th": "两颗吉星同在第7、9或10宫",
"Venus in own/exaltation sign (Ta/Li/Pi) and in kendra (1/4/7/10) from Lagna": "金星在本座或擢升座(金牛、天秤、双鱼)且在上升起的角宫(1、4、7、10)",
"Venus, Saturn and Mars join Moon in 7th house": "金星、土星、火星与月亮同在第7宫",
"Waning Moon in 5th house": "亏月在第5宫",
"Weak Moon in 7th house": "弱月在第7宫"
}
@@ -647,6 +647,10 @@ def chinese_yoga_label(item: dict) -> str:
def chinese_combination(item: dict) -> str:
"""Use the Chinese combination sentence. An English sentence is not translated word by word."""
authored = str(item.get("combination_zh") or "").strip()
if authored and re.search(r"[一-鿿]", authored) and not _has_prose_glue(authored):
# Written in Chinese by the detector itself (yogas_doshas raj / dhana rows).
return authored
combination = str(item.get("combination") or "").strip()
han = bool(re.search(r"[\u4e00-\u9fff]", combination))
function = bool(_HAN_FUNCTION_RE.search(combination)) if combination else False
+46 -3
View File
@@ -162,6 +162,48 @@ STRENGTH_EN = {
}
# Chinese wording for the English ``note`` of semantic (BVR / PyJHora batch)
# conditions. The note stays the English twin. One note maps to one Chinese
# sentence, so the dedup key keeps the same equality classes (BUG-1418).
_SEMANTIC_NOTES_ZH_PATH = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "..", "references", "yoga_semantic_notes_zh.json")
# A condition without a note used to print its type key. Keep one fixed
# sentence per type so two matches of the same type still dedupe together.
_SEMANTIC_TYPE_ZH = {
"planet_in_house": "行星落在规则指定的宫位",
"planet_in_lagna": "行星落在上升宫",
}
@functools.lru_cache(maxsize=1)
def _semantic_notes_zh() -> Dict[str, str]:
try:
with open(_SEMANTIC_NOTES_ZH_PATH, "r", encoding="utf-8") as handle:
data = json.load(handle)
except (OSError, ValueError):
return {}
return {str(key): str(value) for key, value in data.items()} if isinstance(data, dict) else {}
def _semantic_text(cond: Dict, ctype: str):
"""``cond.get("note", ctype)`` with Chinese text; the old value stays as ``.en``."""
if "note" in cond:
note = cond.get("note")
zh = _semantic_notes_zh().get(str(note)) if note else None
return BiText(zh, note) if zh else note
zh = _SEMANTIC_TYPE_ZH.get(str(ctype))
return BiText(zh, ctype) if zh else ctype
def _template_text(zh: str, en) -> BiText:
"""``BiText(zh, en)``; a rule template written only in English gets its Chinese sentence (BUG-1418)."""
if not any("\u4e00" <= char <= "\u9fff" for char in zh):
chinese = _semantic_notes_zh().get(zh)
if chinese:
return BiText(chinese, zh if en is None else en)
return BiText(zh, en)
def _combo(cond: Dict, default_zh: str, **values) -> BiText:
"""Format the rule's combination template in Chinese (unchanged) and in English."""
zh = cond.get("combo_template", default_zh).format(**values)
@@ -172,7 +214,7 @@ def _combo(cond: Dict, default_zh: str, **values) -> BiText:
en = en_template.format(**{key: (value.en if isinstance(value, BiText) else value) for key, value in values.items()})
except (KeyError, IndexError, ValueError):
en = None
return BiText(zh, en)
return _template_text(zh, en)
def _get_dignity_level(planet: str, sign: str) -> str:
@@ -1465,7 +1507,7 @@ class YogaEngine:
combo = cond.get("combo_template", "自定义条件满足")
if isinstance(combo, str):
# Custom templates were never formatted; keep that, and pair the English text.
combo = BiText(combo, cond.get("combo_template_en") if "combo_template" in cond else DEFAULT_COMBO_EN["自定义条件满足"])
combo = _template_text(combo, cond.get("combo_template_en") if "combo_template" in cond else DEFAULT_COMBO_EN["自定义条件满足"])
if callable(combo):
combo = combo(ctx, bindings)
strength = cond.get("strength", rule.get("strength", "中"))
@@ -1788,7 +1830,8 @@ class YogaEngine:
def _eval_semantic_condition(self, ctype, cond, ctx, rule, bindings):
"""兼容批量抽取规则中的语义型条件。返回保守布尔匹配。"""
def success(combo=None):
return [{"combination": combo or cond.get("note", ctype), "strength": rule.get("strength", "中")}]
text = combo or _semantic_text(cond, ctype)
return [{"combination": text, "strength": rule.get("strength", "中")}]
def house(ref):
if isinstance(ref, int):
+30
View File
@@ -29,6 +29,24 @@ NATURAL_BENEFICS = {'Jupiter', 'Venus', 'Mercury', 'Moon'}
NATURAL_MALEFICS = {'Saturn', 'Mars', 'Sun', 'Rahu', 'Ketu'}
# Chinese twins of the raj / dhana combination strings. ``combination`` keeps its
# old text; the Chinese report reads ``combination_zh`` (BUG-1418).
_PLANET_ZH = {
'Sun': '太阳', 'Moon': '月亮', 'Mars': '火星', 'Mercury': '水星', 'Jupiter': '木星',
'Venus': '金星', 'Saturn': '土星', 'Rahu': '罗睺', 'Ketu': '计都',
}
def _cn(planet) -> str:
return _PLANET_ZH.get(str(planet), str(planet))
def _houses_cn(houses) -> str:
if isinstance(houses, (list, tuple)):
return '、'.join(str(house) for house in houses)
return str(houses)
def calc_raj_yogas(planets_data: Dict, houses: Dict) -> Dict:
"""
计算 Raj Yogas(王者瑜伽)——权力、地位、社会影响力格局 v7.0
@@ -109,6 +127,7 @@ def calc_raj_yogas(planets_data: Dict, houses: Dict) -> Dict:
'type': 'Raj Yoga',
'subtype': 'dual_lordship',
'combination': f'{lord}(H{k_houses}主+H{t_houses}主)',
'combination_zh': f'{_cn(lord)}同时是第{_houses_cn(k_houses)}宫主(角宫)与第{_houses_cn(t_houses)}宫主(三方宫)',
'formation_house': lord_house,
'strength': 'strong' if lord_house in kendras else 'moderate',
'interpretation': f'Raj Yoga——{lord}同时掌管角宫{k_houses}和三方宫{t_houses},且在{lord_house}宫,双重权力格局',
@@ -134,6 +153,7 @@ def calc_raj_yogas(planets_data: Dict, houses: Dict) -> Dict:
'type': 'Raj Yoga',
'subtype': 'conjunction',
'combination': f'{kl}(角宫主) + {tl}(三方宫主)',
'combination_zh': f'角宫主{_cn(kl)}与三方宫主{_cn(tl)}同宫',
'formation_house': house_from_asc,
'strength': 'strong' if house_from_asc in kendras else 'moderate',
'interpretation': f'Raj Yoga——{kl}(角宫主)与{tl}(三方宫主)同宫在{SIGNS_LIST[kl_sign]},权力格局',
@@ -146,6 +166,7 @@ def calc_raj_yogas(planets_data: Dict, houses: Dict) -> Dict:
'type': 'Raj Yoga',
'subtype': 'mutual_aspect',
'combination': f'{kl}(角宫主) ↔ {tl}(三方宫主)',
'combination_zh': f'角宫主{_cn(kl)}与三方宫主{_cn(tl)}对宫互视',
'interpretation': f'Raj Yoga——{kl}(角宫主)与{tl}(三方宫主)互看(对宫相位),权力格局',
}
results['yogas'].append(yoga)
@@ -166,6 +187,7 @@ def calc_raj_yogas(planets_data: Dict, houses: Dict) -> Dict:
'type': 'Raj Yoga',
'subtype': 'parivartana',
'combination': f'{kl}(角宫主) ⇄ {tl}(三方宫主)',
'combination_zh': f'角宫主{_cn(kl)}与三方宫主{_cn(tl)}互换星座',
'interpretation': f'Raj Yoga——{kl}(角宫主)与{tl}(三方宫主)互容交换,权力格局强化',
}
results['yogas'].append(yoga)
@@ -185,6 +207,7 @@ def calc_raj_yogas(planets_data: Dict, houses: Dict) -> Dict:
'type': 'Viparita Raja Yoga',
'subtype': 'dusthana_in_dusthana',
'combination': f'H{h}_Lord({lord}) in H{lord_house}',
'combination_zh': f'{h}宫主{_cn(lord)}落第{lord_house}宫',
'interpretation': f'Viparita Raja Yoga——{h}宫主{lord}落入{lord_house}宫(凶中凶),先苦后甜逆转格局',
}
results['yogas'].append(yoga)
@@ -254,6 +277,7 @@ def calc_dhana_yogas(planets_data: Dict, houses: Dict) -> Dict:
'type': 'Dhana Yoga',
'subtype': '2nd_11th_lords_strong',
'combination': f'H2_Lord({lord_2}) in H{h2} + H11_Lord({lord_11}) in H{h11}',
'combination_zh': f'2宫主{_cn(lord_2)}落第{h2}宫,11宫主{_cn(lord_11)}落第{h11}宫',
'strength': 'strong' if h2 in KENDRA and h11 in KENDRA else 'moderate',
'interpretation': f'Dhana Yoga——2宫主{lord_2}(H{h2})与11宫主{lord_11}(H{h11})均落强宫,财富积累格局',
})
@@ -268,6 +292,7 @@ def calc_dhana_yogas(planets_data: Dict, houses: Dict) -> Dict:
'type': 'Dhana Yoga',
'subtype': '5th_9th_conjunction',
'combination': f'H5_Lord({lord_5}) + H9_Lord({lord_9})',
'combination_zh': f'5宫主{_cn(lord_5)}与9宫主{_cn(lord_9)}同宫',
'strength': 'strong',
'interpretation': f'Dhana Yoga——5宫主{lord_5}与9宫主{lord_9}同宫在{s},财富+幸运格局',
})
@@ -281,6 +306,7 @@ def calc_dhana_yogas(planets_data: Dict, houses: Dict) -> Dict:
'type': 'Dhana Yoga',
'subtype': '5th_9th_mutual_aspect',
'combination': f'H5_Lord({lord_5}) ↔ H9_Lord({lord_9})',
'combination_zh': f'5宫主{_cn(lord_5)}与9宫主{_cn(lord_9)}对宫互视',
'strength': 'moderate',
'interpretation': f'Dhana Yoga——5宫主{lord_5}(H{h5})与9宫主{lord_9}(H{h9})互看,财富格局',
})
@@ -290,6 +316,7 @@ def calc_dhana_yogas(planets_data: Dict, houses: Dict) -> Dict:
'type': 'Dhana Yoga',
'subtype': '5th_9th_strong_houses',
'combination': f'H5_Lord({lord_5}) in H{h5} + H9_Lord({lord_9}) in H{h9}',
'combination_zh': f'5宫主{_cn(lord_5)}落第{h5}宫,9宫主{_cn(lord_9)}落第{h9}宫',
'strength': 'moderate',
'interpretation': f'Dhana Yoga——5宫主{lord_5}与9宫主{lord_9}均落强宫,财富格局',
})
@@ -302,6 +329,7 @@ def calc_dhana_yogas(planets_data: Dict, houses: Dict) -> Dict:
'type': 'Dhana Yoga',
'subtype': '2nd_9th_conjunction',
'combination': f'H2_Lord({lord_2}) + H9_Lord({lord_9})',
'combination_zh': f'2宫主{_cn(lord_2)}与9宫主{_cn(lord_9)}同宫',
'strength': 'strong',
'interpretation': f'Dhana Yoga——2宫主{lord_2}与9宫主{lord_9}同宫在{s},财富+幸运组合',
})
@@ -314,6 +342,7 @@ def calc_dhana_yogas(planets_data: Dict, houses: Dict) -> Dict:
'type': 'Dhana Yoga',
'subtype': '11th_9th_conjunction',
'combination': f'H11_Lord({lord_11}) + H9_Lord({lord_9})',
'combination_zh': f'11宫主{_cn(lord_11)}与9宫主{_cn(lord_9)}同宫',
'strength': 'strong',
'interpretation': f'Dhana Yoga——11宫主{lord_11}与9宫主{lord_9}同宫在{s},收益+幸运组合',
})
@@ -329,6 +358,7 @@ def calc_dhana_yogas(planets_data: Dict, houses: Dict) -> Dict:
'type': 'Dhana Yoga',
'subtype': f'{w_house}th_lord_benefic_conjunction',
'combination': f'H{w_house}_Lord({wealth_lord}) + {benefic}',
'combination_zh': f'{w_house}宫主{_cn(wealth_lord)}与{_cn(benefic)}同宫',
'strength': 'moderate',
'interpretation': f'Dhana Yoga——{w_house}宫主{wealth_lord}与吉星{benefic}同宫在{s},财富助力格局',
})
+28
View File
@@ -328,6 +328,34 @@ def test_parity_chapters_go_through_local_conventions() -> None:
assert "divorce" not in en_body.lower()
def _combination_cells(markdown: str) -> list[str]:
cells: list[str] = []
column = None
for line in markdown.splitlines():
if not line.startswith("|"):
column = None
continue
parts = [cell.strip() for cell in line.strip().strip("|").split("|")]
if column is None:
column = parts.index("组合条件") if "组合条件" in parts else -1
continue
if column < 0 or all(set(cell) <= set("-: ") and cell for cell in parts):
continue
if column < len(parts):
cells.append(parts[column])
return cells
def test_chinese_yoga_combinations_are_not_blanked(fictional_reports: dict[int, tuple[str, str]]) -> None:
"""BUG-1418: staging printed Chinese text in these cells; none may fall back to 未判定."""
for index, (chinese, english) in fictional_reports.items():
cells = _combination_cells(chinese)
assert cells, index
assert [cell for cell in cells if cell == "未判定"] == [], index
assert "组合:未判定" not in chinese
assert HAN.findall(english) == []
def test_chart_nine_partial_kaal_and_friendship(fictional_reports: dict[int, tuple[str, str]]) -> None:
chinese, english = fictional_reports[9]
assert _KAAL_ZH.search(chinese)
+105
View File
@@ -0,0 +1,105 @@
"""BUG-1418: Chinese yoga combination text comes from a Chinese source.
The English note or template of a rule stays the English twin. The Chinese
report must not print 「未判定」 for a combination that has text, and it must
not translate English back word by word.
"""
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
sys.modules.setdefault("jhora", None)
from scripts.report_render.appendix_language import _has_prose_glue, chinese_combination
from scripts.yoga_engine import BiText, YogaEngine, _semantic_notes_zh, _semantic_text, _template_text
from scripts.yogas_doshas import calc_dhana_yogas, calc_raj_yogas
ROOT = Path(__file__).resolve().parents[1]
RULES = ROOT / "references" / "yoga_rules.json"
HAN = re.compile(r"[一-鿿]")
def _semantic_notes() -> dict[str, set[str]]:
engine = YogaEngine(str(RULES))
notes: dict[str, set[str]] = {}
def walk(node, rule_id):
if isinstance(node, dict):
kind = node.get("type") or node.get("op")
if kind and kind not in ("compound_conditions", "compound") and not hasattr(engine, f"_eval_{kind}"):
if node.get("note"):
notes.setdefault(node["note"], set()).add(rule_id)
for value in node.values():
walk(value, rule_id)
elif isinstance(node, list):
for value in node:
walk(value, rule_id)
for rule in json.loads(RULES.read_text(encoding="utf-8"))["rules"]:
walk(rule.get("logic", {}), rule.get("id"))
return notes
def test_every_semantic_note_has_one_chinese_sentence() -> None:
table = _semantic_notes_zh()
notes = _semantic_notes()
assert notes
missing = sorted(note for note in notes if note not in table)
assert missing == []
chinese = [table[note] for note in notes]
# One-to-one, so the detector's dedup key keeps the same equality classes.
assert len(set(chinese)) == len(chinese)
for text in table.values():
assert HAN.search(text), text
assert not _has_prose_glue(text), text
assert "; " not in text
def test_semantic_text_keeps_the_old_value_as_english() -> None:
note = next(iter(_semantic_notes()))
text = _semantic_text({"note": note}, "any_type")
assert isinstance(text, BiText)
assert HAN.search(str(text))
assert text.en == note
# No note: the old value was the type key; dedup still groups by type.
keyed = _semantic_text({}, "planet_in_house")
assert HAN.search(str(keyed)) and keyed.en == "planet_in_house"
assert _semantic_text({"note": ""}, "planet_in_house") == ""
english_template = _template_text("PyJHora aligned source logic", None)
assert HAN.search(str(english_template))
assert english_template.en == "PyJHora aligned source logic"
chinese_template = _template_text("满足自定义条件", "Custom condition met")
assert str(chinese_template) == "满足自定义条件" and chinese_template.en == "Custom condition met"
def _planets(**signs: str) -> dict:
order = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio",
"Sagittarius", "Capricorn", "Aquarius", "Pisces"]
asc = order.index("Aries")
planets = {}
for planet, sign in signs.items():
index = order.index(sign)
planets[planet] = {"sign": sign, "house": (index - asc) % 12 + 1, "degree": index * 30 + 10.0}
return planets
def test_raj_and_dhana_rows_carry_chinese_combination() -> None:
# Fictional placements, Aries ascendant: Mars with Saturn, Jupiter in the 9th.
planets = _planets(Sun="Leo", Moon="Cancer", Mars="Capricorn", Mercury="Virgo",
Jupiter="Sagittarius", Venus="Libra", Saturn="Capricorn", Rahu="Gemini", Ketu="Sagittarius")
houses = {"asc_sign": "Aries"}
rows = calc_raj_yogas(planets, houses)["yogas"] + calc_dhana_yogas(planets, houses)["yogas"]
assert rows
for row in rows:
text = row.get("combination_zh")
assert text and HAN.search(text), row
assert not _has_prose_glue(text), text
assert not re.search(r"[A-Za-z]{3,}", text), text
assert chinese_combination(row) == text
english = [row for row in rows if "_Lord(" in row["combination"]]
for row in english:
assert chinese_combination(row) != "未判定"