英文漏译改在渲染侧查表,测试不再放行 PyJHora 子进程。
冻结的 jaimini.py 恢复成 staging 原样。缺译单元格写成固定标记,不再留空。 Co-Authored-By: Grok (agent loop) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Grok (agent loop)
Cursor
parent
7876c0b4ac
commit
adfa98bf97
@@ -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}
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 []
|
||||
@@ -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",
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -83,8 +83,27 @@ def fictional_case(index: int) -> dict:
|
||||
return chosen
|
||||
|
||||
|
||||
_JHORA_BLOCKED = False
|
||||
_GUARD = {
|
||||
"pyjhora_subprocess_runs": 0,
|
||||
"subprocess_runs": 0,
|
||||
"tajaka": None,
|
||||
"annual": None,
|
||||
"installed": False,
|
||||
}
|
||||
_PYJHORA_COMMAND_MARKERS = (
|
||||
"_isolated_tajaka_worker",
|
||||
"_isolated_annual_worker",
|
||||
"from jhora",
|
||||
"import jhora",
|
||||
)
|
||||
|
||||
|
||||
def block_jhora() -> None:
|
||||
"""Fail closed if this process imports PyJHora. Call once per process."""
|
||||
global _JHORA_BLOCKED
|
||||
if _JHORA_BLOCKED:
|
||||
return
|
||||
real_import = builtins.__import__
|
||||
|
||||
def guarded(name, globals=None, locals=None, fromlist=(), level=0):
|
||||
@@ -104,6 +123,116 @@ def block_jhora() -> None:
|
||||
for key in list(sys.modules):
|
||||
if key == "jhora" or key.startswith("jhora."):
|
||||
del sys.modules[key]
|
||||
_JHORA_BLOCKED = True
|
||||
|
||||
|
||||
def _command_blob(args, kwargs) -> str:
|
||||
command = args[0] if args else kwargs.get("args", ())
|
||||
if isinstance(command, str):
|
||||
return command
|
||||
if isinstance(command, (list, tuple)):
|
||||
return " ".join(str(part) for part in command)
|
||||
return str(command)
|
||||
|
||||
|
||||
def _install_replay_stubs() -> None:
|
||||
"""Return the no-PyJHora blocked shape without starting the isolated children.
|
||||
|
||||
Production still calls the real ``_run_isolated_*`` functions. Only this
|
||||
test process replaces them, including workers started by the process pool.
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
import scripts.annual_pyjhora_replay as replay
|
||||
try:
|
||||
import annual_pyjhora_replay as replay_bare
|
||||
except ImportError:
|
||||
replay_bare = replay
|
||||
|
||||
if not getattr(subprocess.run, "_english_sweep_counter", False):
|
||||
real_run = subprocess.run
|
||||
|
||||
def counting_run(*args, **kwargs):
|
||||
blob = _command_blob(args, kwargs)
|
||||
_GUARD["subprocess_runs"] += 1
|
||||
if any(marker in blob for marker in _PYJHORA_COMMAND_MARKERS):
|
||||
_GUARD["pyjhora_subprocess_runs"] += 1
|
||||
return real_run(*args, **kwargs)
|
||||
|
||||
counting_run._english_sweep_counter = True
|
||||
subprocess.run = counting_run
|
||||
|
||||
if _GUARD["installed"]:
|
||||
return
|
||||
|
||||
def blocked_tajaka(profile, birth_julian_day, place, age):
|
||||
settings = replay._replay_settings(profile)
|
||||
request = {
|
||||
"birth_julian_day": birth_julian_day,
|
||||
"place": replay._place_payload(place),
|
||||
"age": age,
|
||||
"settings": settings,
|
||||
}
|
||||
result = replay._no_jhora_tajaka_replay(settings)
|
||||
result["request_hash"] = replay._request_hash(request)
|
||||
_GUARD["tajaka"] = result
|
||||
return result
|
||||
|
||||
def blocked_annual(profile, birth_julian_day, place, age):
|
||||
settings = replay._replay_settings(profile)
|
||||
request = {
|
||||
"birth_julian_day": birth_julian_day,
|
||||
"place": replay._place_payload(place),
|
||||
"age": age,
|
||||
"settings": settings,
|
||||
}
|
||||
result = replay._no_jhora_annual_replay(settings)
|
||||
result["request_hash"] = replay._request_hash(request)
|
||||
_GUARD["annual"] = result
|
||||
return result
|
||||
|
||||
for module in {replay, replay_bare}:
|
||||
module._run_isolated_tajaka_year_lord = blocked_tajaka
|
||||
module._run_isolated_annual_replay = blocked_annual
|
||||
_GUARD["installed"] = True
|
||||
|
||||
|
||||
def _reset_guard_counts() -> None:
|
||||
_GUARD["pyjhora_subprocess_runs"] = 0
|
||||
_GUARD["subprocess_runs"] = 0
|
||||
_GUARD["tajaka"] = None
|
||||
_GUARD["annual"] = None
|
||||
|
||||
|
||||
def _guard_snapshot() -> dict:
|
||||
from scripts.annual_pyjhora_replay import _ANNUAL_SURFACE_KEYS
|
||||
|
||||
tajaka = _GUARD["tajaka"]
|
||||
annual = _GUARD["annual"]
|
||||
annual_reason = "pyjhora_annual_adapter_setup_failed:ModuleNotFoundError"
|
||||
annual_blocked = (
|
||||
isinstance(annual, dict)
|
||||
and isinstance(annual.get("request_hash"), str)
|
||||
and bool(annual.get("request_hash"))
|
||||
and all(
|
||||
isinstance(annual.get(key), dict)
|
||||
and annual[key].get("status") == "blocked"
|
||||
and annual[key].get("reason") == annual_reason
|
||||
for key in _ANNUAL_SURFACE_KEYS
|
||||
)
|
||||
)
|
||||
tajaka_blocked = (
|
||||
isinstance(tajaka, dict)
|
||||
and tajaka.get("status") == "blocked"
|
||||
and tajaka.get("reason") == "pyjhora_ayanamsa_configuration_failed:ModuleNotFoundError"
|
||||
and isinstance(tajaka.get("request_hash"), str)
|
||||
and bool(tajaka.get("request_hash"))
|
||||
)
|
||||
return {
|
||||
"pyjhora_subprocess_runs": _GUARD["pyjhora_subprocess_runs"],
|
||||
"tajaka_blocked": tajaka_blocked,
|
||||
"annual_blocked": annual_blocked,
|
||||
}
|
||||
|
||||
|
||||
def _prepare_process() -> None:
|
||||
@@ -112,6 +241,12 @@ def _prepare_process() -> None:
|
||||
if entry not in sys.path:
|
||||
sys.path.insert(0, entry)
|
||||
block_jhora()
|
||||
_install_replay_stubs()
|
||||
|
||||
|
||||
def init_english_worker() -> None:
|
||||
"""Process-pool startup. ``render_english`` installs the same guards again."""
|
||||
_prepare_process()
|
||||
|
||||
|
||||
def _residue(text: str) -> dict:
|
||||
@@ -120,16 +255,24 @@ def _residue(text: str) -> dict:
|
||||
return {"han": len(found), "han_chars": "".join(sorted(set(found)))[:40], "glue": glue}
|
||||
|
||||
|
||||
def render_english(index: int) -> dict:
|
||||
"""Build one fictional packet and run ``_english_edition``. No birth data in the result."""
|
||||
def _load_packet(index: int) -> dict:
|
||||
_prepare_process()
|
||||
_reset_guard_counts()
|
||||
from scripts.jyotish_engine import build_professional_report_reference_packet, cmd_full_reading
|
||||
from scripts.professional_report_reference import _english_edition, _export_args
|
||||
from scripts.professional_report_reference import _export_args
|
||||
|
||||
birth = fictional_case(index)
|
||||
reading = cmd_full_reading(type("Args", (), dict(birth))())
|
||||
packet = dict(build_professional_report_reference_packet(reading, _export_args(dict(birth)), []))
|
||||
packet["report_version"] = "pl9_personal_long_report.v3"
|
||||
return packet
|
||||
|
||||
|
||||
def render_english(index: int) -> dict:
|
||||
"""Build one fictional packet and run ``_english_edition``. No birth data in the result."""
|
||||
from scripts.professional_report_reference import _english_edition
|
||||
|
||||
packet = _load_packet(index)
|
||||
edition = _english_edition(packet, True)
|
||||
markdown = edition.get("markdown_en") or ""
|
||||
flags = _flags_from_packet(packet)
|
||||
@@ -141,12 +284,15 @@ def render_english(index: int) -> dict:
|
||||
path = Path(dump_dir)
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
(path / f"chart-{index:03d}.md").write_text(markdown, encoding="utf-8")
|
||||
rendered = f"{markdown}\n{applicability}"
|
||||
return {
|
||||
"i": index,
|
||||
"status": "ok" if markdown and not edition.get("english_unavailable") else edition.get("english_unavailable") or "empty",
|
||||
"han": body["han"] + extra["han"],
|
||||
"han_chars": (body["han_chars"] + extra["han_chars"])[:40],
|
||||
"glue": sorted(set(body["glue"] + extra["glue"])),
|
||||
"missing": rendered.count("[translation missing:"),
|
||||
**_guard_snapshot(),
|
||||
**flags,
|
||||
}
|
||||
|
||||
@@ -173,33 +319,27 @@ def _flags_from_packet(packet: dict) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def row_problem(row: dict) -> str:
|
||||
"""Empty when this chart's English edition matches the no-PyJHora production report."""
|
||||
clean = (
|
||||
row["status"] == "ok"
|
||||
and not row["han"]
|
||||
and not row["glue"]
|
||||
and not row["missing"]
|
||||
and row["pyjhora_subprocess_runs"] == 0
|
||||
and row["tajaka_blocked"]
|
||||
and row["annual_blocked"]
|
||||
)
|
||||
if clean:
|
||||
return ""
|
||||
return (
|
||||
f"#{row['i']} status={row['status']} han={row['han']} chars={row['han_chars']} "
|
||||
f"glue={row['glue']} missing={row['missing']} "
|
||||
f"pyjhora_subprocess_runs={row['pyjhora_subprocess_runs']} "
|
||||
f"tajaka_blocked={row['tajaka_blocked']} annual_blocked={row['annual_blocked']}"
|
||||
)
|
||||
|
||||
|
||||
def chart_flags(index: int) -> dict:
|
||||
"""Class flags used to prove the smoke set covers L1–L6. No birth data."""
|
||||
_prepare_process()
|
||||
from scripts.jyotish_engine import build_professional_report_reference_packet, cmd_full_reading
|
||||
from scripts.professional_report_reference import _export_args
|
||||
|
||||
birth = fictional_case(index)
|
||||
reading = cmd_full_reading(type("Args", (), dict(birth))())
|
||||
packet = dict(build_professional_report_reference_packet(reading, _export_args(dict(birth)), []))
|
||||
rath = (((packet.get("worksheets") or {}).get("timing_and_predictive_systems") or {}).get("narayana_dasha") or {}).get("rath") or {}
|
||||
rule = rath.get("current_md_antardasha_rule") or {}
|
||||
direction = rule.get("direction") if isinstance(rule, dict) else {}
|
||||
advanced = (packet.get("worksheets") or {}).get("advanced_systems") or {}
|
||||
doshas = advanced.get("yogas_doshas") or {}
|
||||
kaal = doshas.get("kaal_sarp_dosha") or {}
|
||||
birth_sheet = (packet.get("worksheets") or {}).get("birth_and_parameters") or {}
|
||||
pack = birth_sheet.get("panchanga") or {}
|
||||
panchanga = pack.get("raw") if isinstance(pack, dict) and isinstance(pack.get("raw"), dict) else pack
|
||||
yoga = advanced.get("yoga") if isinstance(advanced.get("yoga"), dict) else {}
|
||||
names = [str(row.get("name") or "") for row in (yoga.get("detected_yogas") or []) if isinstance(row, dict)]
|
||||
strengths = [str(row.get("strength") or "") for row in (yoga.get("detected_yogas") or []) if isinstance(row, dict)]
|
||||
return {
|
||||
"i": index,
|
||||
"level": rath.get("strength_level_decided"),
|
||||
"direction": rath.get("direction"),
|
||||
"ad_rule": direction.get("rule") if isinstance(direction, dict) else None,
|
||||
"overall": panchanga.get("overall_quality") if isinstance(panchanga, dict) else None,
|
||||
"kaal": kaal.get("type") if isinstance(kaal, dict) else None,
|
||||
"punarphoo": "观察" in strengths or any("Punarphoo" in name for name in names),
|
||||
}
|
||||
return {"i": index, **_flags_from_packet(_load_packet(index))}
|
||||
|
||||
@@ -94,6 +94,9 @@ def test_reference_edition_shows_both_and_english_terms_cover_the_rath_lines() -
|
||||
from scripts.narayana_legacy_label import rath_report_lines_en
|
||||
|
||||
packet = _packet(CASES["day"])
|
||||
# 原值:reference 版同时有旧算法句和 Rath 表
|
||||
# 新值:full_data 版同时有这两段
|
||||
# 原因:用户看到的是完整数据版;Rath 并列表必须跟着走(BUG-1218)
|
||||
# 原值:Rath 中文行经 PHRASES_EN 可译干净
|
||||
# 新值:完整数据版英文渲染的 Rath 段无汉字且用英文函数
|
||||
# 原因:完整数据版不走 PHRASES_EN,旧断言测的不是生产路径
|
||||
|
||||
@@ -3,6 +3,8 @@ from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.muhurta import calc_panchanga
|
||||
from scripts.narayana_legacy_label import (
|
||||
DIRECTION_EN,
|
||||
@@ -146,3 +148,50 @@ def test_punarphoo_strength_column_uses_english() -> None:
|
||||
assert "| Punarphoo Yoga | relationship_observation | observational | Moon conjunct Saturn |" in text
|
||||
assert "观察" not in text
|
||||
assert STRENGTH_EN["观察"] == "Watch"
|
||||
assert "[translation missing:" not in text
|
||||
|
||||
|
||||
def test_missing_english_twin_is_marked_instead_of_blank() -> None:
|
||||
packet = {
|
||||
"report_language": "en",
|
||||
"worksheets": {"advanced_systems": {"yoga": {"detected_yogas": [{
|
||||
"name": "Example Yoga",
|
||||
"category": "special",
|
||||
"strength": "强",
|
||||
"combination": "月亮与土星合相",
|
||||
"effects": ["中文作用"],
|
||||
}]}}},
|
||||
}
|
||||
text = render_pl9_parity_markdown(packet)
|
||||
assert "[translation missing: combination]" in text
|
||||
assert "[translation missing: effects]" in text
|
||||
assert "月亮" not in text
|
||||
assert "中文" not in text
|
||||
assert "| Strong |" in text or "| strong |" in text or "Strong" in text
|
||||
|
||||
|
||||
def test_jaimini_domain_is_looked_up_by_karaka_name() -> None:
|
||||
source = (Path(__file__).resolve().parents[1] / "scripts" / "jaimini.py").read_text(encoding="utf-8")
|
||||
assert "domain_en" not in source
|
||||
assert "KARAKA_DOMAINS_EN" not in source
|
||||
packet = {
|
||||
"report_language": "en",
|
||||
"worksheets": {
|
||||
"strengths_and_scores": {"scope": "jaimini-domain-fixture"},
|
||||
"advanced_systems": {"jaimini": {"chara_karaka_7": {"karaka_table": {
|
||||
"Amatyakaraka": {
|
||||
"planet": "Saturn",
|
||||
"degree_in_sign": 1.25,
|
||||
"effective_degree_in_sign": 1.25,
|
||||
"domain": "事业方向、主要谋士、权力代理",
|
||||
"cn_name": "事业星AmK",
|
||||
},
|
||||
}}}},
|
||||
},
|
||||
}
|
||||
text = render_pl9_parity_markdown(packet)
|
||||
assert "career direction, main advisor, delegated authority" in text
|
||||
assert "事业" not in text
|
||||
assert "careerdirection" not in text
|
||||
assert "careerplanetAmK" not in text
|
||||
assert "[translation missing:" not in text
|
||||
|
||||
@@ -10,18 +10,17 @@ from __future__ import annotations
|
||||
import os
|
||||
from concurrent.futures import ProcessPoolExecutor, as_completed
|
||||
|
||||
from tests.report_english_fictional import CHART_COUNT, render_english
|
||||
from tests.report_english_fictional import CHART_COUNT, init_english_worker, render_english, row_problem
|
||||
|
||||
|
||||
def test_two_hundred_fictional_charts_have_clean_english() -> None:
|
||||
workers = min(4, os.cpu_count() or 1, CHART_COUNT)
|
||||
failures = []
|
||||
with ProcessPoolExecutor(max_workers=workers) as pool:
|
||||
with ProcessPoolExecutor(max_workers=workers, initializer=init_english_worker) as pool:
|
||||
futures = [pool.submit(render_english, index) for index in range(CHART_COUNT)]
|
||||
for future in as_completed(futures):
|
||||
row = future.result()
|
||||
if row["status"] != "ok" or row["han"] or row["glue"]:
|
||||
failures.append(
|
||||
f"#{row['i']} status={row['status']} han={row['han']} chars={row['han_chars']} glue={row['glue']}"
|
||||
)
|
||||
problem = row_problem(row)
|
||||
if problem:
|
||||
failures.append(problem)
|
||||
assert failures == []
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Smoke slice of the English sweep, on the quick gate (issue #89 / BUG-1410).
|
||||
"""Smoke slice of the English sweep, on the quick gate (issue #89 / BUG-1413).
|
||||
|
||||
Indices are from tests.report_english_fictional.fictional_case. Together they
|
||||
hit Rath level 5, an inauspicious Panchanga, Ketu in the start sign, a Table 14
|
||||
@@ -9,7 +9,7 @@ from __future__ import annotations
|
||||
|
||||
from concurrent.futures import ProcessPoolExecutor, as_completed
|
||||
|
||||
from tests.report_english_fictional import render_english
|
||||
from tests.report_english_fictional import init_english_worker, render_english, row_problem
|
||||
|
||||
# Filled once the flag scan names a chart for every class. The test checks the
|
||||
# flags, so a stale index fails instead of silently dropping a class.
|
||||
@@ -23,15 +23,14 @@ SMOKE_INDEXES = (1, 4, 5, 6, 9, 10, 11, 12, 14, 15, 32, 34)
|
||||
def test_smoke_charts_cover_every_leak_class_and_render_clean_english() -> None:
|
||||
rows = []
|
||||
failures = []
|
||||
with ProcessPoolExecutor(max_workers=min(4, len(SMOKE_INDEXES))) as pool:
|
||||
with ProcessPoolExecutor(max_workers=min(4, len(SMOKE_INDEXES)), initializer=init_english_worker) as pool:
|
||||
futures = [pool.submit(render_english, index) for index in SMOKE_INDEXES]
|
||||
for future in as_completed(futures):
|
||||
row = future.result()
|
||||
rows.append(row)
|
||||
if row["status"] != "ok" or row["han"] or row["glue"]:
|
||||
failures.append(
|
||||
f"#{row['i']} status={row['status']} han={row['han']} chars={row['han_chars']} glue={row['glue']}"
|
||||
)
|
||||
problem = row_problem(row)
|
||||
if problem:
|
||||
failures.append(problem)
|
||||
levels = {row["level"] for row in rows}
|
||||
directions = {row["direction"] for row in rows}
|
||||
ad_rules = {row["ad_rule"] for row in rows}
|
||||
|
||||
Reference in New Issue
Block a user