英文漏译改在渲染侧查表,测试不再放行 PyJHora 子进程。

冻结的 jaimini.py 恢复成 staging 原样。缺译单元格写成固定标记,不再留空。

Co-Authored-By: Grok (agent loop)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jesse-ux
2026-10-22 09:58:09 +08:00
co-authored by Grok (agent loop) Cursor
parent 7876c0b4ac
commit adfa98bf97
11 changed files with 378 additions and 89 deletions
-12
View File
@@ -75,16 +75,6 @@ KARAKA_DOMAINS = {
'Darakaraka': '配偶特质、伴侣关系、婚姻质量',
'Pitrukaraka': '父亲、祖先业力、传统传承',
}
KARAKA_DOMAINS_EN = {
'Atmakaraka': 'soul aim, core self, highest aim',
'Amatyakaraka': 'career direction, main advisor, delegated authority',
'Bhratrikaraka': 'siblings, courage, appetite for risk',
'Matrikaraka': 'mother, home base, emotional security',
'Putrakaraka': 'children, creativity, students, crafted results',
'Gnatikaraka': 'rivals, illness, obstacles, capacity for change',
'Darakaraka': 'spouse traits, partnership, marriage quality',
'Pitrukaraka': 'father, ancestral pattern, inherited tradition',
}
ARUDHA_NAMES = {
1: 'Arudha Lagna (AL)', 2: 'Dhana Pada (A2)', 3: 'Vikrama Pada (A3)',
@@ -129,7 +119,6 @@ def calc_chara_karaka_7(planet_degrees: Dict[str, float]) -> Dict:
'degree_in_sign': round(deg, 4),
'rank': rank,
'domain': KARAKA_DOMAINS.get(karaka, ''),
'domain_en': KARAKA_DOMAINS_EN.get(karaka, ''),
'cn_name': KARAKA_CN.get(karaka, karaka),
}
ak = results.get('Atmakaraka', {})
@@ -171,7 +160,6 @@ def calc_chara_karaka_8(planet_degrees: Dict[str, float]) -> Dict:
'rahu_reverse_applied': pname == 'Rahu',
'rank': rank,
'domain': KARAKA_DOMAINS.get(karaka, ''),
'domain_en': KARAKA_DOMAINS_EN.get(karaka, ''),
'cn_name': KARAKA_CN.get(karaka, karaka),
}
return {'karaka_table_8': results}
+23 -15
View File
@@ -2072,22 +2072,20 @@ def _render_pl9_ai_density_raw_data_markdown_en(packet: dict) -> str:
english_report = _pl9_report_language(packet) == "en"
strength_en_map: dict[str, str] = {}
han_leaks = None
if english_report:
try:
from yoga_engine import STRENGTH_EN as strength_en_map
except ImportError:
from scripts.yoga_engine import STRENGTH_EN as strength_en_map
try:
from pl9_reader_english import HAN as han_leaks
from report_render.english_gaps import english_effects, first_clean_english
except ImportError:
from scripts.pl9_reader_english import HAN as han_leaks
from scripts.report_render.english_gaps import english_effects, first_clean_english
def _english_plain(value: Any) -> str:
text = str(value or "").strip()
if not text or (han_leaks is not None and han_leaks.search(text)):
return ""
return text
def _english_plain(value: Any, field: str, chinese: Any = None, fallback: Any = None) -> str:
if not english_report:
return str(value or "").strip()
return first_clean_english(value, fallback, field=field, chinese=chinese)
yoga = dict_get(("advanced_systems", "yoga", "detected_yogas"), [])
if isinstance(yoga, list):
@@ -2095,12 +2093,18 @@ def _render_pl9_ai_density_raw_data_markdown_en(packet: dict) -> str:
for item in yoga:
if isinstance(item, dict):
if english_report:
effects_src = item.get("effects_en") if isinstance(item.get("effects_en"), list) else []
effects = [text for text in (_english_plain(effect) for effect in effects_src) if text]
combination = _english_plain(item.get("combination_en")) or "-"
strength = _english_plain(item.get("strength_en"))
if not strength:
strength = _english_plain(strength_en_map.get(str(item.get("strength") or ""), ""))
effects = english_effects(item.get("effects_en"), item.get("effects"))
combination = _english_plain(
item.get("combination_en"),
"combination",
item.get("combination"),
) or "-"
strength = _english_plain(
item.get("strength_en"),
"strength",
item.get("strength"),
strength_en_map.get(str(item.get("strength") or ""), ""),
) or "-"
else:
effects = [effect for effect in (clean_effect(e) for e in item.get("effects", [])) if effect]
combination = item.get("combination")
@@ -2122,7 +2126,11 @@ def _render_pl9_ai_density_raw_data_markdown_en(packet: dict) -> str:
item = dosha.get(key)
if isinstance(item, dict):
if english_report:
summary = _english_plain(item.get("summary_en")) or "-"
summary = _english_plain(
item.get("summary_en"),
"summary",
item.get("summary"),
) or "-"
else:
summary = item.get("summary") or item.get("phase") or item.get("type")
rows.append([
+42 -17
View File
@@ -26,6 +26,12 @@ try: # scripts/ on sys.path
from narayana_legacy_label import LEGACY_NOTE_EN, LEGACY_NOTE_ZH
except ImportError: # pragma: no cover - package import
from scripts.narayana_legacy_label import LEGACY_NOTE_EN, LEGACY_NOTE_ZH
try: # scripts/ on sys.path
from report_render.english_gaps import english_effects, first_clean_english
from report_render.karaka_domains import KARAKA_DOMAIN_EN
except ImportError: # pragma: no cover - package import
from scripts.report_render.english_gaps import english_effects, first_clean_english
from scripts.report_render.karaka_domains import KARAKA_DOMAIN_EN
import re
SIGNS: list[str] = []
@@ -1573,9 +1579,14 @@ def render_pl9_parity_markdown(packet: dict) -> str:
if not isinstance(row, dict):
continue
karaka_name = name if _lang == 'en' else (row.get('cn_name') or name)
domain = row.get('domain_en') if _lang == 'en' else row.get('domain')
if _lang == 'en' and domain and re.search(r'[\u4e00-\u9fff]', str(domain)):
domain = ''
if _lang == 'en':
domain = first_clean_english(
KARAKA_DOMAIN_EN.get(str(name), ''),
field='domain',
chinese=row.get('domain'),
)
else:
domain = row.get('domain')
lines.append(
f"| {_cell(karaka_name)} | {_cell(_token(row.get('planet')))} | "
f"{_cell(_degree(row.get('degree_in_sign') or row.get('sign_degree')))} | "
@@ -2120,9 +2131,14 @@ def render_pl9_parity_markdown(packet: dict) -> str:
# Customer tables retain computed boundaries and reference points;
# print-layout rows deliberately blank repeated values.
kantaka_rows = _list(timeline.get('kantaka_rows')) or _list(display_rows.get('kantaka'))
sade_summary = sade_sati.get('summary_en') if _lang == 'en' else sade_sati.get('summary')
if _lang == 'en' and sade_summary and re.search(r'[\u4e00-\u9fff]', str(sade_summary)):
sade_summary = ''
if _lang == 'en':
sade_summary = first_clean_english(
sade_sati.get('summary_en'),
field='summary',
chinese=sade_sati.get('summary'),
)
else:
sade_summary = sade_sati.get('summary')
lines.extend([
'### p121-p122 Sade Sati / Dhayya / Kantaka Saturn',
'',
@@ -3029,9 +3045,14 @@ def render_pl9_parity_markdown(packet: dict) -> str:
for fact_key in ('mars_house', 'mars_sign', 'sun_house', 'sun_sign', 'rahu_house', 'ketu_house', 'moon_sign', 'severity', 'type'):
if row.get(fact_key) not in (None, ''):
fact_bits.append(f"{fact_key}={_token(row.get(fact_key))}")
summary = row.get('summary_en') if _lang == 'en' else row.get('summary')
if _lang == 'en' and summary and re.search(r'[\u4e00-\u9fff]', str(summary)):
summary = ''
if _lang == 'en':
summary = first_clean_english(
row.get('summary_en'),
field='summary',
chinese=row.get('summary'),
)
else:
summary = row.get('summary')
joiner = '; ' if _lang == 'en' else ';'
lines.append(
f"| {_cell(label)} | {_cell(detected)} | {_cell(joiner.join(map(str, fact_bits)) or '-')} | {_cell(summary)} |"
@@ -3118,15 +3139,19 @@ def render_pl9_parity_markdown(packet: dict) -> str:
])
for row in group_rows:
if _lang == 'en':
effects = row.get('effects_en') if isinstance(row.get('effects_en'), list) else []
effects = [item for item in effects if not re.search(r'[\u4e00-\u9fff]', str(item))]
effects = english_effects(row.get('effects_en'), row.get('effects'))
yoga_name = row.get('name') or row.get('type')
combination = row.get('combination_en')
if not combination or re.search(r'[\u4e00-\u9fff]', str(combination)):
combination = ''
strength = row.get('strength_en') or strength_en_map.get(str(row.get('strength') or ''))
if not strength or re.search(r'[\u4e00-\u9fff]', str(strength)):
strength = ''
combination = first_clean_english(
row.get('combination_en'),
field='combination',
chinese=row.get('combination'),
)
strength = first_clean_english(
row.get('strength_en'),
strength_en_map.get(str(row.get('strength') or ''), ''),
field='strength',
chinese=row.get('strength'),
)
else:
effects = row.get('effects') if isinstance(row.get('effects'), list) else []
yoga_name = row.get('name_cn') or row.get('name') or row.get('type')
+62
View File
@@ -0,0 +1,62 @@
"""Marker for an English report cell that is still Chinese or has no English twin."""
from __future__ import annotations
import re
from typing import Any
HAN = re.compile(r"[\u4e00-\u9fff]")
MISSING_MARK = "[translation missing:"
def translation_missing(field: str) -> str:
return f"[translation missing: {field}]"
def _as_text(value: Any) -> str:
if value is None:
return ""
if isinstance(value, (list, tuple)):
return " ".join(part for part in (_as_text(item) for item in value) if part)
return str(value).strip()
def first_clean_english(*candidates: Any, field: str, chinese: Any = None) -> str:
"""First candidate with no Han, or a visible marker.
Empty English beside an empty Chinese source stays empty. A Han string, or
a Chinese source with no clean English candidate, is not blanked out.
"""
saw_text = False
for candidate in candidates:
text = _as_text(candidate)
if not text:
continue
saw_text = True
if HAN.search(text) is None:
return text
if saw_text or _as_text(chinese):
return translation_missing(field)
return ""
def english_effects(effects_en: Any, effects_zh: Any, field: str = "effects") -> list[str]:
"""English effect lines. Han lines become one missing-translation marker."""
source = effects_en if isinstance(effects_en, list) else []
cleaned: list[str] = []
dropped_han = False
for item in source:
text = _as_text(item)
if not text:
continue
if HAN.search(text) is not None:
dropped_han = True
continue
cleaned.append(text)
if dropped_han:
cleaned.append(translation_missing(field))
if cleaned:
return cleaned
if _as_text(effects_zh) or _as_text(effects_en):
return [translation_missing(field)]
return []
+16
View File
@@ -0,0 +1,16 @@
"""English Chara Karaka duties, keyed by karaka name.
``scripts/jaimini.py`` is a sealed rectification scoring file. English wording
for the report is resolved here, at render time, and that file stays unchanged.
"""
KARAKA_DOMAIN_EN = {
"Atmakaraka": "soul aim, core self, highest aim",
"Amatyakaraka": "career direction, main advisor, delegated authority",
"Bhratrikaraka": "siblings, courage, appetite for risk",
"Matrikaraka": "mother, home base, emotional security",
"Putrakaraka": "children, creativity, students, crafted results",
"Gnatikaraka": "rivals, illness, obstacles, capacity for change",
"Darakaraka": "spouse traits, partnership, marriage quality",
"Pitrukaraka": "father, ancestral pattern, inherited tradition",
}
+1 -1
View File
@@ -135,7 +135,7 @@ CORE_PYTEST_TARGETS = [
"tests/test_serif_font_slices.py",
# Chart, report, and consult entries must not import PyJHora (BUG-1273).
"tests/test_runtime_jhora_import_scan.py",
# English edition han leak, 12-chart slice (issue #89 / BUG-1410). The 200-chart file stays off this list.
# English edition han leak, 12-chart slice (issue #89 / BUG-1413). The 200-chart file stays off this list.
"tests/test_report_english_sweep_smoke.py",
]