同一份真实盘包在屏蔽与不屏蔽 PyJHora 下新旧渲染 sha 都一致,各 24 项且退出码 0。指定 pytest 233 通过、3 跳过。 Co-Authored-By: Grok (agent loop)
128 lines
5.8 KiB
Python
128 lines
5.8 KiB
Python
"""Helpers moved unchanged out of render_pl9_markdown (phase 1)."""
|
||
|
||
try:
|
||
from scripts.report_render.text import _md_cell
|
||
except ModuleNotFoundError: # pragma: no cover - direct scripts/ execution
|
||
from report_render.text import _md_cell
|
||
|
||
def _render_boundary_packet_summary_line(packet: dict, summary_pairs: list[tuple[str, str]]) -> str:
|
||
summary = packet.get('summary') if isinstance(packet.get('summary'), dict) else {}
|
||
bits = [_md_cell(packet.get('packet_mode'))]
|
||
for key, suffix in summary_pairs:
|
||
value = summary.get(key)
|
||
if value not in (None, '', []):
|
||
bits.append(f"{_md_cell(value)}{suffix}")
|
||
return f"参考包摘要:{';'.join(bits)}。"
|
||
|
||
def _status_from_report_pack_section(section: dict) -> str:
|
||
if not isinstance(section, dict):
|
||
return 'blocked'
|
||
audit = section.get('audit') if isinstance(section.get('audit'), dict) else {}
|
||
if section.get('status'):
|
||
return str(section.get('status'))
|
||
if audit.get('status'):
|
||
return str(audit.get('status'))
|
||
if section:
|
||
return 'partial_verified'
|
||
return 'blocked'
|
||
|
||
def _section_evidence_boundary(section: dict) -> str:
|
||
if not isinstance(section, dict):
|
||
return 'section_missing'
|
||
exports = section.get('exports') if isinstance(section.get('exports'), dict) else {}
|
||
ai_bundle = exports.get('ai_evidence_bundle') if isinstance(exports.get('ai_evidence_bundle'), dict) else {}
|
||
audit = section.get('audit') if isinstance(section.get('audit'), dict) else {}
|
||
boundaries = []
|
||
if ai_bundle.get('contains_private_pl9_text') is False:
|
||
boundaries.append('no_private_pl9_text')
|
||
if ai_bundle.get('claim_policy'):
|
||
boundaries.append(str(ai_bundle.get('claim_policy')))
|
||
if audit.get('must_not_claim'):
|
||
boundaries.append('must_not_claim=' + ','.join(map(str, audit.get('must_not_claim')[:3])))
|
||
if section.get('reason'):
|
||
boundaries.append(str(section.get('reason')))
|
||
return '; '.join(boundaries) if boundaries else 'evidence_labeled'
|
||
|
||
def _domi_pl9_current_gap_appendix() -> list[str]:
|
||
return []
|
||
|
||
def _chart_planets_from_positions(positions: dict) -> dict:
|
||
chart_planets = {}
|
||
if not isinstance(positions, dict):
|
||
return chart_planets
|
||
for planet_name in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu'):
|
||
planet = positions.get(planet_name)
|
||
if not isinstance(planet, dict):
|
||
continue
|
||
sign = planet.get('sign') or planet.get('rashi') or planet.get('sign_name')
|
||
if not sign:
|
||
continue
|
||
degree = planet.get('degree_in_sign', planet.get('degree', planet.get('longitude_in_sign', 0)))
|
||
chart_planets[planet_name] = {'sign': sign, 'degree': degree or 0}
|
||
return chart_planets
|
||
|
||
def _kp_house_focus_text(value) -> str:
|
||
house_label_map = {
|
||
2: '收入与资源',
|
||
5: '机会与投入',
|
||
6: '职责与压力',
|
||
7: '合作与伴侣',
|
||
9: '运气与扩张',
|
||
10: '事业与位置',
|
||
11: '回报与支持',
|
||
}
|
||
parts = []
|
||
if isinstance(value, (list, tuple)):
|
||
raw_parts = [str(item).strip() for item in value if str(item).strip()]
|
||
else:
|
||
raw_parts = [part.strip() for part in str(value or '').replace('/', ',').split(',') if part.strip()]
|
||
for part in raw_parts:
|
||
try:
|
||
house_no = int(part)
|
||
except ValueError:
|
||
parts.append(part)
|
||
continue
|
||
parts.append(f"{house_no}宫[{house_label_map.get(house_no, '-')}]")
|
||
return '、'.join(parts) if parts else '-'
|
||
|
||
def _high_value_theme_authority_sentence(domain: str) -> str | None:
|
||
domain_label_map = {
|
||
'career': '事业',
|
||
'wealth': '财务',
|
||
'relationship': '关系',
|
||
}
|
||
action_map = {
|
||
'career': '优先排序事业推进顺序、外部舞台和月份节奏',
|
||
'wealth': '优先排序资源承接、现金流节奏和收益兑现顺序',
|
||
'relationship': '优先排序合作推进、联结温度和关系节奏',
|
||
}
|
||
return (
|
||
"主结论加厚:Narayana 第二时间轴、力量兑现结构、年度 highlights / field briefs、Patyayini 年内换挡、Graha Padas 这几层现在直接参与"
|
||
+ f"{domain_label_map.get(domain, domain)}章主结论,"
|
||
+ f"用来{action_map.get(domain, '补强主题排序')};"
|
||
+ "它们仍保留 parameter_sensitive / unclosed_divisional_chart 边界,但不再只停留在附录或登记表。"
|
||
)
|
||
|
||
def _kp_conflict_adjudication_sentence(domain: str) -> str:
|
||
domain_label_map = {
|
||
'career': '事业',
|
||
'wealth': '财务',
|
||
'relationship': '关系',
|
||
}
|
||
focus_map = {
|
||
'career': '外部舞台、职位变化和项目推进',
|
||
'wealth': '资源承接、现金流和回收节奏',
|
||
'relationship': '合作推进、联结温度和承诺节奏',
|
||
}
|
||
return (
|
||
f"如果 Vimshottari、Narayana 与年度专题同向,而 KP 月度支持也落在同一主题上,就把这个月份优先当作{domain_label_map.get(domain, domain)}线的可执行窗口。"
|
||
f"如果 KP 单独活跃、但本命结构或双大运没有接力,KP 只负责提示 {focus_map.get(domain, '主题变化')} 的月份排序,不改写主题事实。"
|
||
f"如果 KP 与年度专题或双大运出现冲突,先以本命结构 + Vimshottari + Narayana 定主轴,KP 退回到月度提醒与核对层,继续保持 parameter_sensitive。"
|
||
)
|
||
|
||
def _prashna_exact_cusp_boundary_sentence() -> str | None:
|
||
return (
|
||
"如果下一步要把月份继续缩到更细日期,仍需要补 question_time / place / timezone / question category;"
|
||
"Prashna ruling planets 与 exact cusp oracle 在这份本命个人报告里继续保持 blocked,只作为后续缩窄入口,不冒充 timing truth。"
|
||
)
|