fix(report): keep monthly KP headings and readable timing sources
Heading-ensure only covered empty packets, so present KP months still dropped MD/AD titles and sanitizer blanked source_path. Also lock the quality gate onto the new contracts. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -35,6 +35,21 @@ def render_flexible_birth_time_report_section(projection: Mapping[str, Any]) ->
|
||||
lines.extend(["", "#### 窗口内敏感层"])
|
||||
for key in sensitive.get("evidence_keys") or []:
|
||||
lines.append(f"- `{key}`:{_render_value((sensitive.get('layers') or {}).get(key))}")
|
||||
lines.extend(["", "#### 校时附录·分钟排行", ""])
|
||||
candidate_times = window.get("candidate_times") if isinstance(window.get("candidate_times"), list) else []
|
||||
if candidate_times:
|
||||
lines.extend([
|
||||
"下列分钟只是窗口内候选顺序,不是已确认出生分钟。",
|
||||
"",
|
||||
"| 序号 | 候选分钟 |",
|
||||
"|------|----------|",
|
||||
])
|
||||
for idx, time in enumerate(candidate_times, start=1):
|
||||
lines.append(f"| {idx} | {time} |")
|
||||
else:
|
||||
lines.extend([
|
||||
"`blocked` / `candidate_times_missing`",
|
||||
])
|
||||
lines.extend([
|
||||
"",
|
||||
"#### 使用边界",
|
||||
|
||||
+65
-19
@@ -2021,6 +2021,36 @@ def _attach_annual_tajika_pack(packet: dict, args) -> dict:
|
||||
return packet
|
||||
|
||||
|
||||
def _uncertainty_minutes_from_args(args):
|
||||
explicit = getattr(args, 'uncertainty_minutes', None)
|
||||
if explicit not in (None, ''):
|
||||
return explicit
|
||||
before = getattr(args, 'uncertainty_before_minutes', None)
|
||||
after = getattr(args, 'uncertainty_after_minutes', None)
|
||||
if before not in (None, '') or after not in (None, ''):
|
||||
try:
|
||||
return int(before or 0) + int(after or 0) or None
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
candidate_range = getattr(args, 'candidate_range', None) or {}
|
||||
start_raw = ''
|
||||
end_raw = ''
|
||||
if isinstance(candidate_range, dict):
|
||||
start_raw = str(candidate_range.get('start_time') or '')[:5]
|
||||
end_raw = str(candidate_range.get('end_time') or '')[:5]
|
||||
if not start_raw:
|
||||
start_raw = str(getattr(args, 'declared_window_start', None) or '')[:5]
|
||||
if not end_raw:
|
||||
end_raw = str(getattr(args, 'declared_window_end', None) or '')[:5]
|
||||
try:
|
||||
start = datetime.strptime(start_raw, '%H:%M')
|
||||
end = datetime.strptime(end_raw, '%H:%M')
|
||||
minutes = int((end - start).total_seconds() // 60)
|
||||
return minutes if minutes >= 0 else None
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _birth_provenance_from_args(args) -> dict:
|
||||
return {
|
||||
'schema': 'jyotish.birth_provenance.v1',
|
||||
@@ -2028,7 +2058,7 @@ def _birth_provenance_from_args(args) -> dict:
|
||||
'coordinate_precision': getattr(args, 'coordinate_precision', None) or 'unverified_user_coordinates',
|
||||
'coordinate_source': getattr(args, 'coordinate_source', None) or 'not_recorded',
|
||||
'time_source': getattr(args, 'time_source', None) or 'not_recorded',
|
||||
'uncertainty_minutes': getattr(args, 'uncertainty_minutes', None),
|
||||
'uncertainty_minutes': _uncertainty_minutes_from_args(args),
|
||||
'boundary': 'A hospital label is not treated as hospital-grade coordinates unless coordinate_source and coordinate_precision explicitly support it.',
|
||||
}
|
||||
|
||||
@@ -2286,25 +2316,25 @@ def _build_timing_boundary_attribution(packet: dict) -> dict:
|
||||
{
|
||||
'lane': 'Vimshottari boundary series',
|
||||
'status': 'parameter_sensitive',
|
||||
'source_path': 'raw_full_reading.modules.dasha',
|
||||
'source_label': 'full-reading Dasha module',
|
||||
'reason': 'Local boundary series is present; broader formal external boundary closure remains incomplete.',
|
||||
},
|
||||
{
|
||||
'lane': 'Narayana Dasha',
|
||||
'status': 'parameter_sensitive',
|
||||
'source_path': 'raw_full_reading.modules.narayana_dasha',
|
||||
'source_label': 'full-reading Narayana module',
|
||||
'reason': 'Local sequence is present, but parity and interpretation closure remain incomplete.',
|
||||
},
|
||||
{
|
||||
'lane': 'Patyayini / Tajika annual',
|
||||
'status': 'parameter_sensitive',
|
||||
'source_path': 'worksheets.timing_and_predictive_systems.annual_tajika_series',
|
||||
'source_label': 'annual Tajika series',
|
||||
'reason': 'Independent annual packs exist, but annual timing remains limited by parity and replay gates.',
|
||||
},
|
||||
{
|
||||
'lane': 'KP exact cusp',
|
||||
'status': kp_status,
|
||||
'source_path': 'references/oracle/kp_exact_cusp_mainline_status_2026_08_22.json',
|
||||
'source_label': 'KP exact cusp oracle status',
|
||||
'reason': kp_reason or 'Exact-cusp promotion gate remains closed.',
|
||||
},
|
||||
]
|
||||
@@ -2605,6 +2635,9 @@ PLANNED_LONGFORM_SECTION_HEADINGS = (
|
||||
'#### KP 财务宫位核对(2 / 5 / 9 / 11 宫)',
|
||||
'#### KP 关系结构小表',
|
||||
'#### KP 关系宫位核对(2 / 7 / 11 宫)',
|
||||
'#### KP 事业逐月 MD/AD 支持',
|
||||
'#### KP 财务逐月 MD/AD 支持',
|
||||
'#### KP 关系逐月 MD/AD 支持',
|
||||
'## 逐模块执行与结论追踪审计',
|
||||
'## 完整 Technique Audit Table',
|
||||
'#### Bhava Bala(本地三分量评分)',
|
||||
@@ -2615,6 +2648,7 @@ PLANNED_LONGFORM_SECTION_HEADINGS = (
|
||||
'#### Kala Chakra 参考对齐',
|
||||
'## 原始模块索引与未下沉字段附录',
|
||||
'### Profile-aware Benchmark Boundary',
|
||||
'#### 校时附录·分钟排行',
|
||||
)
|
||||
|
||||
|
||||
@@ -4396,7 +4430,8 @@ def render_pl9_markdown(packet: dict) -> str:
|
||||
rendered.append(f"| {label} | {' | '.join(_md_cell(value) for value in values)} | parameter_sensitive |")
|
||||
rendered.append('')
|
||||
|
||||
d1_d60_section = full_report_pack.get('sections', {}).get('d1_d60_ledger') if isinstance(full_report_pack.get('sections'), dict) else {}
|
||||
pack_sections = full_report_pack.get('sections') if isinstance(full_report_pack.get('sections'), dict) else {}
|
||||
d1_d60_section = pack_sections.get('d1_d60_ledger') if isinstance(pack_sections.get('d1_d60_ledger'), dict) else {}
|
||||
d1_d60_ledger = d1_d60_section.get('d1_to_d60') if isinstance(d1_d60_section.get('d1_to_d60'), dict) else {}
|
||||
d1_d60_summary = d1_d60_section.get('summary') if isinstance(d1_d60_section.get('summary'), dict) else {}
|
||||
if d1_d60_ledger:
|
||||
@@ -5321,7 +5356,9 @@ def render_pl9_markdown(packet: dict) -> str:
|
||||
out.append(f"| {_md_cell(row.get('month'))} | {_md_cell(row.get('reason'))} | parameter_sensitive |")
|
||||
out.append('')
|
||||
kp_support_topic = support_topics.get('kp') if isinstance(support_topics, dict) else {}
|
||||
kp_snapshot = source_pack_contracts.get('kp_support_snapshot') if isinstance(source_pack_contracts.get('kp_support_snapshot'), dict) else {}
|
||||
if not isinstance(kp_support_topic, dict):
|
||||
kp_support_topic = {}
|
||||
kp_snapshot = source_pack_contracts.get('kp_support_snapshot') if isinstance(source_pack_contracts, dict) and isinstance(source_pack_contracts.get('kp_support_snapshot'), dict) else {}
|
||||
kp_must_not_claim = kp_support_topic.get('must_not_claim') if isinstance(kp_support_topic.get('must_not_claim'), list) else []
|
||||
if not kp_must_not_claim:
|
||||
kp_must_not_claim = kp_snapshot.get('must_not_claim') if isinstance(kp_snapshot.get('must_not_claim'), list) else []
|
||||
@@ -5376,11 +5413,21 @@ def render_pl9_markdown(packet: dict) -> str:
|
||||
|
||||
def _kp_monthly_theme_brief(domain: str) -> list[str]:
|
||||
monthly_pack = advanced_sheet.get('kp_monthly_report') if isinstance(advanced_sheet.get('kp_monthly_report'), dict) else {}
|
||||
months = monthly_pack.get('months') if isinstance(monthly_pack.get('months'), list) else []
|
||||
if not months:
|
||||
return []
|
||||
if 'result' in monthly_pack and 'response_envelope' in monthly_pack:
|
||||
nested = monthly_pack.get('result')
|
||||
monthly_pack = nested if isinstance(nested, dict) else {}
|
||||
raw_months = monthly_pack.get('months')
|
||||
if isinstance(raw_months, list):
|
||||
months = raw_months
|
||||
elif isinstance(raw_months, dict):
|
||||
months = [raw_months[key] for key in sorted(raw_months, key=str)]
|
||||
else:
|
||||
months = []
|
||||
label_map = {'career': '事业', 'wealth': '财务', 'relationship': '关系'}
|
||||
label = label_map.get(domain, domain)
|
||||
heading = f'#### KP {label}逐月 MD/AD 支持'
|
||||
if not months:
|
||||
return _blocked_planned_section(heading, 'kp_monthly_payload_empty')
|
||||
selected_rows = []
|
||||
for row in months[:3]:
|
||||
if not isinstance(row, dict):
|
||||
@@ -5394,7 +5441,7 @@ def render_pl9_markdown(packet: dict) -> str:
|
||||
focus_code = detail.get('active_houses') or detail.get('houses') or detail.get('promise_code')
|
||||
selected_rows.append({'month': row.get('month'), 'dasha_pair': f"{_humanize_reader_token(md.get('lord'))} / {_humanize_reader_token(ad.get('lord'))}", 'summary': detail.get('brief_summary') or detail.get('summary') or '-', 'focus': _kp_house_focus_text(focus_code)})
|
||||
if not selected_rows:
|
||||
return []
|
||||
return _blocked_planned_section(heading, 'kp_monthly_theme_empty')
|
||||
month_names = '、'.join(str(row.get('month')) for row in selected_rows if row.get('month'))
|
||||
dasha_brief = _dasha_domain_brief(domain)
|
||||
focus_values = []
|
||||
@@ -5403,7 +5450,11 @@ def render_pl9_markdown(packet: dict) -> str:
|
||||
if focus and focus != '-' and focus not in focus_values:
|
||||
focus_values.append(focus)
|
||||
focus_text = '、'.join(focus_values)
|
||||
brief = [f"对应到月度层,{label}线优先先看 {month_names};这里仍只保留 parameter_sensitive 的结构提示,不把它写成具体事件预测。"]
|
||||
brief = [
|
||||
heading,
|
||||
'',
|
||||
f"对应到月度层,{label}线优先先看 {month_names};这里仍只保留 parameter_sensitive 的结构提示,不把它写成具体事件预测。",
|
||||
]
|
||||
if focus_text:
|
||||
brief.extend(['', f'焦点宫位:{focus_text}'])
|
||||
if dasha_brief:
|
||||
@@ -9820,6 +9871,7 @@ def render_pl9_markdown(packet: dict) -> str:
|
||||
'未做校时。当前没有可用的候选窗,因此不输出分钟级敏感性矩阵或校时分钟排行。',
|
||||
'',
|
||||
])
|
||||
lines.extend(_blocked_planned_section('#### 校时附录·分钟排行', 'candidate_window_not_applicable'))
|
||||
|
||||
lines.extend(['', '## 校时证据领域合同', ''])
|
||||
lines.append(_md_cell(rectification_evidence_contract.get('claim_boundary', 'rectification_evidence_contract_missing')))
|
||||
@@ -9853,13 +9905,7 @@ def render_pl9_markdown(packet: dict) -> str:
|
||||
lines.extend(['', '| Parity lane | 状态 | 来源路径 | 仍未闭环原因 |', '|-------------|------|----------|----------------|'])
|
||||
for item in timing_boundary_attribution.get('parity_gates', []) if isinstance(timing_boundary_attribution.get('parity_gates'), list) else []:
|
||||
if isinstance(item, dict):
|
||||
source_path = str(item.get('source_path') or '')
|
||||
source_label = {
|
||||
'raw_full_reading.modules.dasha': 'full-reading Dasha module',
|
||||
'raw_full_reading.modules.narayana_dasha': 'full-reading Narayana module',
|
||||
'worksheets.timing_and_predictive_systems.annual_tajika_series': 'annual Tajika series',
|
||||
'references/oracle/kp_exact_cusp_mainline_status_2026_08_22.json': 'KP exact cusp oracle status',
|
||||
}.get(source_path, source_path.split('.')[-1] if source_path else '-')
|
||||
source_label = str(item.get('source_label') or item.get('source') or '-')
|
||||
lines.append(f"| {_md_cell(item.get('lane'))} | {_md_cell(item.get('status'))} | {_md_cell(source_label)} | {_md_cell(item.get('reason'))} |")
|
||||
|
||||
lines.extend(['', '## 完整 Technique Audit Table', '', '| 技法 | 状态 | 来源 | 审计说明 |', '|------|------|------|----------|'])
|
||||
|
||||
@@ -81,6 +81,10 @@ CORE_PYTEST_TARGETS = [
|
||||
"tests/test_consultation_workflow_domains.py",
|
||||
# Float hour/minute from the API payload must not 500 consultation_workflow (BUG-524).
|
||||
"tests/test_consultation_workflow_birth_time_sensitivity.py",
|
||||
"tests/test_report_longform_parity.py",
|
||||
"tests/test_report_longform_gaps2.py",
|
||||
"tests/test_timing_precision_contract.py",
|
||||
"tests/test_flexible_birth_time_report_contract.py",
|
||||
"tests/test_mcp_strict_workflow_finance.py",
|
||||
"tests/test_interpretation_template_registry.py",
|
||||
"tests/test_vedastro_external_technique_evidence.py",
|
||||
|
||||
Reference in New Issue
Block a user