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:
@@ -1,5 +1,9 @@
|
|||||||
# 印度占星 Skill 更新日志
|
# 印度占星 Skill 更新日志
|
||||||
|
|
||||||
|
## 2026-09-06 — 专业长报告补上逐月 KP 标题和校时分钟表
|
||||||
|
|
||||||
|
专业长报告里,事业、财务、关系三条逐月 MD/AD 支持只要有月份数据就会带标题;时间轨道来源改成可读标签,不再被脱敏成空。未确认出生分钟时,校时附录会列出窗口内候选分钟,不会写成已经采用的时间。Skill 版本未变。
|
||||||
|
|
||||||
## 2026-09-06 — 首轮咨询标题在刷新前就会落库
|
## 2026-09-06 — 首轮咨询标题在刷新前就会落库
|
||||||
|
|
||||||
新开的普通咨询,模型起的主题标题会写进服务器。生成中途刷新或换设备,侧栏仍是那句总结,而不是问题前几个字加省略号。你中途自己改名的,仍以你改的为准。Skill 版本未变。
|
新开的普通咨询,模型起的主题标题会写进服务器。生成中途刷新或换设备,侧栏仍是那句总结,而不是问题前几个字加省略号。你中途自己改名的,仍以你改的为准。Skill 版本未变。
|
||||||
|
|||||||
@@ -8677,4 +8677,21 @@
|
|||||||
- 复发自:无
|
- 复发自:无
|
||||||
- 修复版本:`ebc8380f`
|
- 修复版本:`ebc8380f`
|
||||||
|
|
||||||
|
## BUG-564 | 长报告 heading-ensure 之后仍丢逐月 KP 标题、时间来源与分钟排行
|
||||||
|
|
||||||
|
- 状态:resolved
|
||||||
|
- 首次发现:2026-09-06
|
||||||
|
- 最近更新:2026-09-06
|
||||||
|
- 影响面:`scripts/jyotish_engine.py` pl9 长报告、`scripts/flexible_birth_time_report_section.py`、`scripts/run_quality_gate.py` 快速门
|
||||||
|
- 用户现象:有 KP 月份数据时,事业/财务/关系三条「逐月 MD/AD 支持」标题仍不见;时间节点归因表的来源列被脱敏成空;校时附录没有分钟排行。快速门也不跑 gaps2 合同。
|
||||||
|
- 触发条件:`kp_monthly_report.months` 非空;professional 路径 sanitize 掉 `source_path`;候选窗存在但渲染层不读 `candidate_times`。
|
||||||
|
- 根因:`_kp_monthly_theme_brief` 成功路径不写标题,空包才靠文末 heading-ensure 补 blocked 标题;parity_gates 用 `source_path`,sanitize 会剥掉任何 `_path` 键;CLI provenance 只读显式 `uncertainty_minutes`;分钟排行未装配。顺带 `d1_d60_ledger` / `support_topics.kp` 为 None 时 `.get` 会在有月份数据的渲染里炸掉。
|
||||||
|
- 修复:逐月 brief 始终输出标题,空数据走 blocked;parity_gates 改 `source_label`;窗口宽度回填 uncertainty;校时附录列出候选分钟;None 守卫;快速门列入 gaps2 / parity / timing / flexible 合同。
|
||||||
|
- 验证:`tests/test_report_longform_gaps2.py`、`tests/test_flexible_birth_time_report_contract.py`、`tests/test_timing_precision_contract.py`。
|
||||||
|
- 防复发:有月份数据时三条逐月标题必须各出现一次,且不得靠文末 blocked 标题顶替;sanitize 后 Markdown 仍须保留可读 source_label;快速门必须跑上述四个测试文件。
|
||||||
|
- 相关记录:BUG-561、BUG-562
|
||||||
|
- 复发自:BUG-561(`cfcd369d` 的 heading-ensure 只覆盖空包缺席,未覆盖成功路径漏标题)
|
||||||
|
- 修复版本:待本提交 SHA
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,3 +39,18 @@
|
|||||||
本 worktree 无独立 `.venv`,实测用父仓解释器。实现已随 `809bdf13` 快进到 `origin/staging`;门禁因 MD 页 lint(BUG-563)未部署,与 gaps2 装配无关。
|
本 worktree 无独立 `.venv`,实测用父仓解释器。实现已随 `809bdf13` 快进到 `origin/staging`;门禁因 MD 页 lint(BUG-563)未部署,与 gaps2 装配无关。
|
||||||
|
|
||||||
未改 `.gitea/workflows/**`,不提升 main。
|
未改 `.gitea/workflows/**`,不提升 main。
|
||||||
|
|
||||||
|
## 补洞(相对 `cfcd369d` / `5aa05bf0`)
|
||||||
|
|
||||||
|
并行 worktree 在旧基线上多做的实现没有整棵 rebase(`jyotish_engine.py` 与 MD 页提交重叠太大)。在当前 staging HEAD 上只移植仍缺的洞:
|
||||||
|
|
||||||
|
| 洞 | 处理 |
|
||||||
|
| --- | --- |
|
||||||
|
| 有月份时逐月 MD/AD 标题仍缺 | `_kp_monthly_theme_brief` 始终写 `#### KP {事业\|财务\|关系}逐月 MD/AD 支持`;空数据 blocked |
|
||||||
|
| sanitize 抹掉 `source_path` | parity_gates 改 `source_label` |
|
||||||
|
| 快速门未跑 gaps2 | `CORE_PYTEST_TARGETS` 列入 gaps2 / parity / timing / flexible |
|
||||||
|
| 校时附录无分钟排行 | `flexible_birth_time_report_section` 列出 `candidate_times`,禁止 winner/approved |
|
||||||
|
| CLI provenance 只认显式分钟 | 从 `candidate_range` / `declared_window_*` 回填 11:50–12:10 → 20 |
|
||||||
|
| Vargas I / KP 三年节 None.get | `d1_d60_ledger` 与 `support_topics.kp` 做 dict 守卫 |
|
||||||
|
|
||||||
|
Muntha `conflict`、`pyjhora_behavior_only`、事件回放 `user_history_not_supplied` 保持原样。未 vendor `professional_parity_closure`。
|
||||||
|
|||||||
@@ -92,6 +92,8 @@
|
|||||||
| `TASK-report-candidate-range-read-20260904.md`(仓库根) | `PROGRESS-report-candidate-range-20260904.md` | accepted 生时报告直读已收权校正表 | 待验收 | `e27d5dc5`(BUG-526/534;终稿见 BUG-535) |
|
| `TASK-report-candidate-range-read-20260904.md`(仓库根) | `PROGRESS-report-candidate-range-20260904.md` | accepted 生时报告直读已收权校正表 | 待验收 | `e27d5dc5`(BUG-526/534;终稿见 BUG-535) |
|
||||||
| `TASK-report-chart-cap-20260904.md`(仓库根) | `PROGRESS-report-chart-cap-20260904.md` | 文档 charts 上限绑定 CHART_IDS | 待验收 | `4ef4c406`(BUG-535) |
|
| `TASK-report-chart-cap-20260904.md`(仓库根) | `PROGRESS-report-chart-cap-20260904.md` | 文档 charts 上限绑定 CHART_IDS | 待验收 | `4ef4c406`(BUG-535) |
|
||||||
| `TASK-report-longform-parity-20260905.md`(仓库根) | `PROGRESS-report-longform-parity-20260905.md` | 长报告对齐全量版并挂入产品附录 | 待验收 | `codex/report-longform-parity-20260905`(任务 6 待部署后核对) |
|
| `TASK-report-longform-parity-20260905.md`(仓库根) | `PROGRESS-report-longform-parity-20260905.md` | 长报告对齐全量版并挂入产品附录 | 待验收 | `codex/report-longform-parity-20260905`(任务 6 待部署后核对) |
|
||||||
|
| `TASK-report-longform-gaps2-20260906.md`(仓库根) | `PROGRESS-report-longform-gaps2-20260906.md` | 长报告真实参数组合下的装配缺口 | 待验收 | `codex/report-longform-gaps2-20260906`(BUG-561/562;补洞 BUG-564) |
|
||||||
|
| `TASK-report-md-page-20260906.md` | `PROGRESS-report-md-page-20260906.md` | 长报告 Markdown 直接作为报告页 | 已合入 | `cfcd369d` / `809bdf13`(BUG-563 lint 随后修) |
|
||||||
|
|
||||||
### 前端基础与工程
|
### 前端基础与工程
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,21 @@ def render_flexible_birth_time_report_section(projection: Mapping[str, Any]) ->
|
|||||||
lines.extend(["", "#### 窗口内敏感层"])
|
lines.extend(["", "#### 窗口内敏感层"])
|
||||||
for key in sensitive.get("evidence_keys") or []:
|
for key in sensitive.get("evidence_keys") or []:
|
||||||
lines.append(f"- `{key}`:{_render_value((sensitive.get('layers') or {}).get(key))}")
|
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([
|
lines.extend([
|
||||||
"",
|
"",
|
||||||
"#### 使用边界",
|
"#### 使用边界",
|
||||||
|
|||||||
+65
-19
@@ -2021,6 +2021,36 @@ def _attach_annual_tajika_pack(packet: dict, args) -> dict:
|
|||||||
return packet
|
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:
|
def _birth_provenance_from_args(args) -> dict:
|
||||||
return {
|
return {
|
||||||
'schema': 'jyotish.birth_provenance.v1',
|
'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_precision': getattr(args, 'coordinate_precision', None) or 'unverified_user_coordinates',
|
||||||
'coordinate_source': getattr(args, 'coordinate_source', None) or 'not_recorded',
|
'coordinate_source': getattr(args, 'coordinate_source', None) or 'not_recorded',
|
||||||
'time_source': getattr(args, 'time_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.',
|
'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',
|
'lane': 'Vimshottari boundary series',
|
||||||
'status': 'parameter_sensitive',
|
'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.',
|
'reason': 'Local boundary series is present; broader formal external boundary closure remains incomplete.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'lane': 'Narayana Dasha',
|
'lane': 'Narayana Dasha',
|
||||||
'status': 'parameter_sensitive',
|
'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.',
|
'reason': 'Local sequence is present, but parity and interpretation closure remain incomplete.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'lane': 'Patyayini / Tajika annual',
|
'lane': 'Patyayini / Tajika annual',
|
||||||
'status': 'parameter_sensitive',
|
'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.',
|
'reason': 'Independent annual packs exist, but annual timing remains limited by parity and replay gates.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'lane': 'KP exact cusp',
|
'lane': 'KP exact cusp',
|
||||||
'status': kp_status,
|
'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.',
|
'reason': kp_reason or 'Exact-cusp promotion gate remains closed.',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -2605,6 +2635,9 @@ PLANNED_LONGFORM_SECTION_HEADINGS = (
|
|||||||
'#### KP 财务宫位核对(2 / 5 / 9 / 11 宫)',
|
'#### KP 财务宫位核对(2 / 5 / 9 / 11 宫)',
|
||||||
'#### KP 关系结构小表',
|
'#### KP 关系结构小表',
|
||||||
'#### KP 关系宫位核对(2 / 7 / 11 宫)',
|
'#### KP 关系宫位核对(2 / 7 / 11 宫)',
|
||||||
|
'#### KP 事业逐月 MD/AD 支持',
|
||||||
|
'#### KP 财务逐月 MD/AD 支持',
|
||||||
|
'#### KP 关系逐月 MD/AD 支持',
|
||||||
'## 逐模块执行与结论追踪审计',
|
'## 逐模块执行与结论追踪审计',
|
||||||
'## 完整 Technique Audit Table',
|
'## 完整 Technique Audit Table',
|
||||||
'#### Bhava Bala(本地三分量评分)',
|
'#### Bhava Bala(本地三分量评分)',
|
||||||
@@ -2615,6 +2648,7 @@ PLANNED_LONGFORM_SECTION_HEADINGS = (
|
|||||||
'#### Kala Chakra 参考对齐',
|
'#### Kala Chakra 参考对齐',
|
||||||
'## 原始模块索引与未下沉字段附录',
|
'## 原始模块索引与未下沉字段附录',
|
||||||
'### Profile-aware Benchmark Boundary',
|
'### 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(f"| {label} | {' | '.join(_md_cell(value) for value in values)} | parameter_sensitive |")
|
||||||
rendered.append('')
|
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_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 {}
|
d1_d60_summary = d1_d60_section.get('summary') if isinstance(d1_d60_section.get('summary'), dict) else {}
|
||||||
if d1_d60_ledger:
|
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(f"| {_md_cell(row.get('month'))} | {_md_cell(row.get('reason'))} | parameter_sensitive |")
|
||||||
out.append('')
|
out.append('')
|
||||||
kp_support_topic = support_topics.get('kp') if isinstance(support_topics, dict) else {}
|
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 []
|
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:
|
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 []
|
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]:
|
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 {}
|
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 'result' in monthly_pack and 'response_envelope' in monthly_pack:
|
||||||
if not months:
|
nested = monthly_pack.get('result')
|
||||||
return []
|
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_map = {'career': '事业', 'wealth': '财务', 'relationship': '关系'}
|
||||||
label = label_map.get(domain, domain)
|
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 = []
|
selected_rows = []
|
||||||
for row in months[:3]:
|
for row in months[:3]:
|
||||||
if not isinstance(row, dict):
|
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')
|
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)})
|
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:
|
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'))
|
month_names = '、'.join(str(row.get('month')) for row in selected_rows if row.get('month'))
|
||||||
dasha_brief = _dasha_domain_brief(domain)
|
dasha_brief = _dasha_domain_brief(domain)
|
||||||
focus_values = []
|
focus_values = []
|
||||||
@@ -5403,7 +5450,11 @@ def render_pl9_markdown(packet: dict) -> str:
|
|||||||
if focus and focus != '-' and focus not in focus_values:
|
if focus and focus != '-' and focus not in focus_values:
|
||||||
focus_values.append(focus)
|
focus_values.append(focus)
|
||||||
focus_text = '、'.join(focus_values)
|
focus_text = '、'.join(focus_values)
|
||||||
brief = [f"对应到月度层,{label}线优先先看 {month_names};这里仍只保留 parameter_sensitive 的结构提示,不把它写成具体事件预测。"]
|
brief = [
|
||||||
|
heading,
|
||||||
|
'',
|
||||||
|
f"对应到月度层,{label}线优先先看 {month_names};这里仍只保留 parameter_sensitive 的结构提示,不把它写成具体事件预测。",
|
||||||
|
]
|
||||||
if focus_text:
|
if focus_text:
|
||||||
brief.extend(['', f'焦点宫位:{focus_text}'])
|
brief.extend(['', f'焦点宫位:{focus_text}'])
|
||||||
if dasha_brief:
|
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.extend(['', '## 校时证据领域合同', ''])
|
||||||
lines.append(_md_cell(rectification_evidence_contract.get('claim_boundary', 'rectification_evidence_contract_missing')))
|
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 | 状态 | 来源路径 | 仍未闭环原因 |', '|-------------|------|----------|----------------|'])
|
lines.extend(['', '| Parity lane | 状态 | 来源路径 | 仍未闭环原因 |', '|-------------|------|----------|----------------|'])
|
||||||
for item in timing_boundary_attribution.get('parity_gates', []) if isinstance(timing_boundary_attribution.get('parity_gates'), list) else []:
|
for item in timing_boundary_attribution.get('parity_gates', []) if isinstance(timing_boundary_attribution.get('parity_gates'), list) else []:
|
||||||
if isinstance(item, dict):
|
if isinstance(item, dict):
|
||||||
source_path = str(item.get('source_path') or '')
|
source_label = str(item.get('source_label') or item.get('source') 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 '-')
|
|
||||||
lines.append(f"| {_md_cell(item.get('lane'))} | {_md_cell(item.get('status'))} | {_md_cell(source_label)} | {_md_cell(item.get('reason'))} |")
|
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', '', '| 技法 | 状态 | 来源 | 审计说明 |', '|------|------|------|----------|'])
|
lines.extend(['', '## 完整 Technique Audit Table', '', '| 技法 | 状态 | 来源 | 审计说明 |', '|------|------|------|----------|'])
|
||||||
|
|||||||
@@ -81,6 +81,10 @@ CORE_PYTEST_TARGETS = [
|
|||||||
"tests/test_consultation_workflow_domains.py",
|
"tests/test_consultation_workflow_domains.py",
|
||||||
# Float hour/minute from the API payload must not 500 consultation_workflow (BUG-524).
|
# Float hour/minute from the API payload must not 500 consultation_workflow (BUG-524).
|
||||||
"tests/test_consultation_workflow_birth_time_sensitivity.py",
|
"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_mcp_strict_workflow_finance.py",
|
||||||
"tests/test_interpretation_template_registry.py",
|
"tests/test_interpretation_template_registry.py",
|
||||||
"tests/test_vedastro_external_technique_evidence.py",
|
"tests/test_vedastro_external_technique_evidence.py",
|
||||||
|
|||||||
@@ -62,7 +62,12 @@ def test_report_projection_is_hash_stable_and_renders_only_candidate_window_evid
|
|||||||
|
|
||||||
assert build_flexible_birth_time_full_report_projection(reordered)["projection_id"] == projection["projection_id"]
|
assert build_flexible_birth_time_full_report_projection(reordered)["projection_id"] == projection["projection_id"]
|
||||||
assert projection["status"] == "candidate_window_only"
|
assert projection["status"] == "candidate_window_only"
|
||||||
assert "出生时间敏感度" in render_flexible_birth_time_report_section(projection)
|
rendered = render_flexible_birth_time_report_section(projection)
|
||||||
|
assert "出生时间敏感度" in rendered
|
||||||
|
assert "#### 校时附录·分钟排行" in rendered
|
||||||
|
assert "08:00" in rendered
|
||||||
|
assert "winner" not in rendered.lower()
|
||||||
|
assert "approved" not in rendered.lower()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("field", ["approved_birth_time", "final_birth_time", "winner", "approval"])
|
@pytest.mark.parametrize("field", ["approved_birth_time", "final_birth_time", "winner", "approval"])
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from scripts.jyotish_engine import (
|
|||||||
PLANNED_LONGFORM_SECTION_HEADINGS,
|
PLANNED_LONGFORM_SECTION_HEADINGS,
|
||||||
_annual_series_surface,
|
_annual_series_surface,
|
||||||
_attach_report_governance_contracts,
|
_attach_report_governance_contracts,
|
||||||
|
_birth_provenance_from_args,
|
||||||
_native_dasha_master_families,
|
_native_dasha_master_families,
|
||||||
render_pl9_markdown,
|
render_pl9_markdown,
|
||||||
sanitize_professional_report_reference,
|
sanitize_professional_report_reference,
|
||||||
@@ -168,3 +169,81 @@ def test_auxiliary_dasha_headings_render_from_native_modules() -> None:
|
|||||||
assert "#### Narayana Rashi Dasha" in markdown
|
assert "#### Narayana Rashi Dasha" in markdown
|
||||||
assert "#### Kala Chakra Dasha(本地原始周期,冲突保留)" in markdown
|
assert "#### Kala Chakra Dasha(本地原始周期,冲突保留)" in markdown
|
||||||
assert "#### Bhava Bala(本地三分量评分)" in markdown
|
assert "#### Bhava Bala(本地三分量评分)" in markdown
|
||||||
|
|
||||||
|
|
||||||
|
def test_quality_gate_runs_this_file() -> None:
|
||||||
|
from scripts.run_quality_gate import CORE_PYTEST_TARGETS
|
||||||
|
|
||||||
|
assert "tests/test_report_longform_gaps2.py" in CORE_PYTEST_TARGETS
|
||||||
|
assert "tests/test_report_longform_parity.py" in CORE_PYTEST_TARGETS
|
||||||
|
assert "tests/test_timing_precision_contract.py" in CORE_PYTEST_TARGETS
|
||||||
|
assert "tests/test_flexible_birth_time_report_contract.py" in CORE_PYTEST_TARGETS
|
||||||
|
|
||||||
|
|
||||||
|
def test_kp_monthly_theme_brief_keeps_heading_when_months_exist() -> None:
|
||||||
|
markdown = render_pl9_markdown(_producer_packet({
|
||||||
|
"advanced_systems": {
|
||||||
|
"kp_monthly_report": {
|
||||||
|
"months": [
|
||||||
|
{
|
||||||
|
"month": "2026-01",
|
||||||
|
"theme_support": {
|
||||||
|
"career": {"brief_summary": "career-support", "active_houses": [10]},
|
||||||
|
"wealth": {"brief_summary": "wealth-support", "active_houses": [2]},
|
||||||
|
"relationship": {"brief_summary": "relationship-support", "active_houses": [7]},
|
||||||
|
},
|
||||||
|
"vimshottari_five_levels": {
|
||||||
|
"levels": {
|
||||||
|
"mahadasha": {"lord": "Saturn"},
|
||||||
|
"antardasha": {"lord": "Mercury"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
assert "#### KP 事业逐月 MD/AD 支持" in markdown
|
||||||
|
assert "#### KP 财务逐月 MD/AD 支持" in markdown
|
||||||
|
assert "#### KP 关系逐月 MD/AD 支持" in markdown
|
||||||
|
assert "career-support" in markdown
|
||||||
|
assert markdown.count("#### KP 事业逐月 MD/AD 支持") == 1
|
||||||
|
assert "kp_monthly_payload_empty" not in markdown
|
||||||
|
assert "planned_section_unavailable" not in markdown.split("#### KP 事业逐月 MD/AD 支持", 1)[-1].split("#### KP 财务逐月 MD/AD 支持", 1)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanitized_packet_keeps_timing_source_labels() -> None:
|
||||||
|
packet = _attach_report_governance_contracts(
|
||||||
|
_producer_packet({}),
|
||||||
|
SimpleNamespace(**BEIJING, birth_time_accuracy="confirmed"),
|
||||||
|
)
|
||||||
|
sanitized = sanitize_professional_report_reference(packet)
|
||||||
|
markdown = render_pl9_markdown(sanitized)
|
||||||
|
assert "full-reading Dasha module" in markdown
|
||||||
|
assert "full-reading Narayana module" in markdown
|
||||||
|
assert "annual Tajika series" in markdown
|
||||||
|
assert "KP exact cusp oracle status" in markdown
|
||||||
|
assert "raw_full_reading.modules" not in markdown
|
||||||
|
assert "references/oracle" not in markdown
|
||||||
|
|
||||||
|
|
||||||
|
def test_birth_provenance_uses_candidate_window_minutes() -> None:
|
||||||
|
provenance = _birth_provenance_from_args(SimpleNamespace(
|
||||||
|
birthplace_label="测试市",
|
||||||
|
coordinate_source="user_reported",
|
||||||
|
coordinate_precision="unverified_user_coordinates",
|
||||||
|
time_source="user_reported",
|
||||||
|
candidate_range={"start_time": "11:50", "end_time": "12:10"},
|
||||||
|
))
|
||||||
|
assert provenance["uncertainty_minutes"] == 20
|
||||||
|
assert provenance["coordinate_source"] == "user_reported"
|
||||||
|
|
||||||
|
|
||||||
|
def test_birth_provenance_uses_declared_window_minutes() -> None:
|
||||||
|
provenance = _birth_provenance_from_args(SimpleNamespace(
|
||||||
|
declared_window_start="11:50",
|
||||||
|
declared_window_end="12:10",
|
||||||
|
coordinate_source="user_reported",
|
||||||
|
))
|
||||||
|
assert provenance["uncertainty_minutes"] == 20
|
||||||
|
assert provenance["coordinate_source"] == "user_reported"
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ def test_timing_contract_preserves_dates_but_caps_claim_status() -> None:
|
|||||||
assert contract["promotion_gate"]["status"] == "blocked"
|
assert contract["promotion_gate"]["status"] == "blocked"
|
||||||
|
|
||||||
|
|
||||||
|
def test_quality_gate_runs_this_file() -> None:
|
||||||
|
from scripts.run_quality_gate import CORE_PYTEST_TARGETS
|
||||||
|
|
||||||
|
assert "tests/test_timing_precision_contract.py" in CORE_PYTEST_TARGETS
|
||||||
|
|
||||||
|
|
||||||
def test_empty_timing_contract_still_exposes_boundary() -> None:
|
def test_empty_timing_contract_still_exposes_boundary() -> None:
|
||||||
contract = build_timing_precision_contract()
|
contract = build_timing_precision_contract()
|
||||||
assert contract["timing_precision"] == "broad_window_only"
|
assert contract["timing_precision"] == "broad_window_only"
|
||||||
|
|||||||
Reference in New Issue
Block a user