feat: add health consultation report pipeline

This commit is contained in:
Jesse_Chen
2026-09-03 23:55:14 +08:00
parent 0be6fcfb4c
commit c2f23131f7
17 changed files with 277 additions and 25 deletions
@@ -87,6 +87,24 @@ def compact_timing_narrative(modules: dict | None) -> dict[str, Any]:
}
def compact_health_narrative(modules: dict | None, chart: dict | None = None) -> dict[str, Any]:
"""Length-capped non-medical health seed for the commercial prompt pack."""
try:
engine = _load_engine()
payload = engine._build_health_narrative_payload(modules, chart)
except Exception as exc:
return {"status": "blocked", "reason": f"health_narrative_unavailable:{exc.__class__.__name__}"}
if not isinstance(payload, dict):
return {"status": "blocked", "reason": "health_narrative_unavailable"}
return {
"status": "parameter_sensitive",
"headline": _clip_text(payload.get("headline"), _HEADLINE_MAX),
"strengths": _clip_lines(payload.get("strengths")),
"risks": _clip_lines(payload.get("risks")),
"boundaries": _clip_lines(payload.get("boundaries")),
}
def compact_planetary_friendship(snapshot: dict | None) -> dict[str, Any]:
if not isinstance(snapshot, dict):
return {"status": "blocked", "reason": "planetary_friendship_unavailable", "rows": []}
+6
View File
@@ -1275,6 +1275,11 @@ def _consultation_timing_narrative(modules):
return compact_timing_narrative(modules)
def _consultation_health_narrative(modules, chart):
from scripts.consultation_engine_field_bridge import compact_health_narrative
return compact_health_narrative(modules, chart)
def _consultation_technique_audit_table(
*,
chart: dict,
@@ -7307,6 +7312,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
},
'oracle_progress': oracle_progress,
'timing_narrative': _consultation_timing_narrative(modules),
'health_narrative': _consultation_health_narrative(modules, chart),
},
'retrieval_plan': {
'local_reference_docs': [
+111
View File
@@ -10518,6 +10518,115 @@ def _build_finance_narrative_payload(finance_strict):
)
def _build_health_narrative_payload(modules, chart=None):
"""Build a non-medical health-pressure seed from allowlisted chart evidence."""
modules = modules if isinstance(modules, dict) else {}
chart = chart if isinstance(chart, dict) else {}
varga_full = modules.get('varga_full') if isinstance(modules.get('varga_full'), dict) else {}
def sign_name(row):
if not isinstance(row, dict):
return None
sign = row.get('sign')
if sign in SIGNS:
return sign
raw_idx = row.get('sign_index', row.get('sign_idx'))
try:
return SIGNS[int(raw_idx) % 12]
except (TypeError, ValueError):
return None
def division(number):
prefix = f'D{number}'
for key, value in varga_full.items():
if str(key).upper() == prefix or str(key).upper().startswith(prefix + '_'):
return value if isinstance(value, dict) else {}
return {}
ascendant = chart.get('ascendant') if isinstance(chart.get('ascendant'), dict) else {}
asc_sign = sign_name(ascendant)
if not asc_sign:
module_chart = modules.get('chart') if isinstance(modules.get('chart'), dict) else {}
ascendant = module_chart.get('ascendant') if isinstance(module_chart.get('ascendant'), dict) else {}
asc_sign = sign_name(ascendant)
health_lords = {}
if asc_sign in SIGNS:
asc_idx = SIGNS.index(asc_sign)
for house in (6, 8, 12):
health_lords[house] = SIGN_LORDS[SIGNS[(asc_idx + house - 1) % 12]]
strengths = []
risks = []
for number in (6, 8, 30):
varga = division(number)
varga_asc = sign_name(varga.get('ascendant') if isinstance(varga.get('ascendant'), dict) else varga.get('Ascendant'))
if not varga or not varga_asc:
risks.append(f'D{number} 分盘证据缺失,相关压力与恢复节奏只能保持 blocked。')
continue
planets = varga.get('planets') if isinstance(varga.get('planets'), dict) else varga
placements = []
varga_asc_idx = SIGNS.index(varga_asc)
for house, lord in health_lords.items():
row = planets.get(lord) if isinstance(planets, dict) and isinstance(planets.get(lord), dict) else {}
lord_sign = sign_name(row)
if lord_sign:
lord_house = ((SIGNS.index(lord_sign) - varga_asc_idx) % 12) + 1
placements.append(f'{house}宫主{lord}{lord_house}')
detail = ''.join(placements) if placements else '相关宫主落点待补'
strengths.append(f'D{number} 上升为 {varga_asc}{detail}')
dasha = modules.get('dasha') if isinstance(modules.get('dasha'), dict) else {}
current = dasha.get('current_dasha') if isinstance(dasha.get('current_dasha'), dict) else {}
sub_periods = modules.get('dasha_sub_periods') if isinstance(modules.get('dasha_sub_periods'), dict) else {}
running = sub_periods.get('current') if isinstance(sub_periods.get('current'), dict) else {}
def dasha_lord(*values):
for value in values:
if isinstance(value, dict):
value = value.get('lord') or value.get('planet') or value.get('name')
if isinstance(value, str) and value:
return value
return None
maha = dasha_lord(current.get('mahadasha'), current.get('lord'), running.get('mahadasha'), chart.get('dasha', {}).get('current_md') if isinstance(chart.get('dasha'), dict) else None)
antar = dasha_lord(current.get('antardasha'), running.get('antardasha'))
active = [lord for lord in (maha, antar) if lord]
activated_houses = [str(house) for house, lord in health_lords.items() if lord in active]
if active:
activation = f"当前 Dasha 激活 {'/'.join(active)}"
if activated_houses:
activation += f",其中对应本命 {'/'.join(activated_houses)} 宫主"
strengths.append(activation + '')
else:
risks.append('当前 Vimshottari 主副运未提供,6/8/12 宫主激活只能保持 blocked。')
shadbala = modules.get('shadbala') if isinstance(modules.get('shadbala'), dict) else {}
shadbala_planets = shadbala.get('planets') if isinstance(shadbala.get('planets'), dict) else {}
for lord in dict.fromkeys(health_lords.values()):
row = shadbala_planets.get(lord) if isinstance(shadbala_planets.get(lord), dict) else None
if not row:
risks.append(f'{lord} 的 Shadbala 证据缺失。')
continue
total = row.get('total_rupas', row.get('total_rupa'))
minimum = row.get('min_required')
level = row.get('strength_level') or row.get('level')
strengths.append(f'{lord} Shadbalatotal={total if total is not None else "-"}minimum={minimum if minimum is not None else "-"}level={level or "-"}')
if isinstance(total, (int, float)) and isinstance(minimum, (int, float)) and total < minimum:
risks.append(f'{lord} 的 Shadbala 低于本地最低参考值,只能作为压力负荷提示。')
available = [f'D{number}' for number in (6, 8, 30) if division(number)]
headline = f"非医疗边界:健康压力种子已接入 {('/'.join(available) if available else '分盘待补')}、6/8/12 宫主 Dasha 激活与相关 Shadbala。"
return {
'headline': headline,
'strengths': strengths[:8],
'risks': risks[:8],
'boundaries': [
'非医疗边界:本段只讨论压力、负荷与恢复节奏,不构成诊断、治疗或健康事件预测。',
'Transit 只作可选交叉层;缺失时必须显示 blocked,但不阻断 D1、D6、D8、D30 与 Vimshottari 已闭合的报告主题。',
],
}
def _build_timing_narrative_payload(modules):
modules = modules if isinstance(modules, dict) else {}
dasha = modules.get('dasha') if isinstance(modules.get('dasha'), dict) else {}
@@ -10904,6 +11013,7 @@ def _build_ai_prompt_pack(report):
career_narrative = _build_career_narrative_payload(modules.get('career_strict_evidence'))
finance_narrative = _build_finance_narrative_payload(modules.get('finance_strict_evidence'))
timing_narrative = _build_timing_narrative_payload(modules)
health_narrative = _build_health_narrative_payload(modules, chart)
vimsopaka_semantic_summary = _build_vimsopaka_semantic_summary(modules.get('vimsopaka'))
vedastro_overview = _build_vedastro_overview_payload(modules)
vedastro_official_full_snapshot = _build_vedastro_official_full_snapshot_payload(modules)
@@ -11092,6 +11202,7 @@ def _build_ai_prompt_pack(report):
'relationship_narrative': relationship_narrative,
'finance_narrative': finance_narrative,
'timing_narrative': timing_narrative,
'health_narrative': health_narrative,
'vimsopaka_semantic_summary': vimsopaka_semantic_summary,
}
+1 -2
View File
@@ -195,7 +195,6 @@ class UnifiedConsultationOrchestrator:
"Shadbala",
"Transit",
"Functional Benefic/Malefic",
"non-medical boundary",
],
display_label="health",
),
@@ -281,7 +280,7 @@ class UnifiedConsultationOrchestrator:
"marriage": ["core_partner_profile", "temperament_and_compatibility", "timing_windows", "red_flags", "verification_questions"],
"career": ["career_direction", "role_and_responsibility", "income_and_recognition", "opportunity_windows", "risks_and_verification_questions"],
"wealth": ["wealth_path", "income_structure", "asset_and_cashflow_pattern", "opportunity_windows", "verification_questions"],
"health": ["non_medical_pattern", "pressure_factors", "protective_factors", "verification_questions"],
"health": ["pressure_windows", "event_risk_windows", "recovery_support_windows", "non_medical_boundary", "verification_questions"],
"migration": ["relocation_pattern", "foreign_link", "candidate_windows", "verification_questions"],
"family": ["family_structure", "home_and_care", "children_boundary", "verification_questions"],
"education": ["study_vs_work_fit", "exam_and_degree_path", "candidate_windows", "verification_questions"],