merge origin/staging into rect scoring perf (issue #57)
Docs-only conflicts (CHANGELOG, BUG_HISTORY 1318-1322 vs 1323-1325/1345) resolved by keeping both; agent-loop rule A. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LutgzyJqNhPpZy1nFsJtN3
This commit is contained in:
+23
-2
@@ -272,6 +272,20 @@ class BhavaChalitCalculator:
|
||||
# fallback: 最近的宫
|
||||
return 1
|
||||
|
||||
def _planet_bhava_from_cusps(self, planet_lon: float, cusps: List[float]) -> int:
|
||||
"""KP 宫头到宫头:宫 i(0 起)的范围是 cusp[i] → cusp[(i+1)%12]。
|
||||
|
||||
落在下一宫头上的点归下一宫。本命 Bhava Chalit 表不用这条。
|
||||
"""
|
||||
for i in range(12):
|
||||
start = cusps[i]
|
||||
end = cusps[(i + 1) % 12]
|
||||
arc = _angular_dist(start, end)
|
||||
pos = _angular_dist(start, planet_lon)
|
||||
if arc > 0 and pos < arc:
|
||||
return i + 1
|
||||
return 1
|
||||
|
||||
def _planet_rashi_house(self, planet_lon: float, asc_lon: float) -> int:
|
||||
"""整宫制宫位 (Rashi house)。"""
|
||||
p_si = _sign_idx(planet_lon)
|
||||
@@ -330,7 +344,8 @@ class BhavaChalitCalculator:
|
||||
asc_lon: float, mc_lon: float,
|
||||
house_system: str = 'sripati',
|
||||
jd: float = None, lat: float = None,
|
||||
lon: float = None) -> Dict:
|
||||
lon: float = None,
|
||||
house_assignment: str = 'sandhi') -> Dict:
|
||||
"""根据 Bhava 边界重新分配行星宫位。
|
||||
|
||||
Parameters
|
||||
@@ -346,13 +361,18 @@ class BhavaChalitCalculator:
|
||||
dict with keys:
|
||||
house_system, boundaries, planets
|
||||
"""
|
||||
if house_assignment not in ('sandhi', 'cusp_to_cusp'):
|
||||
raise ValueError("house_assignment must be 'sandhi' or 'cusp_to_cusp'")
|
||||
cusps = self.calculate_cusps(asc_lon, mc_lon, house_system,
|
||||
jd, lat, lon)
|
||||
sandhis = self.calculate_sandhis(cusps)
|
||||
|
||||
planets = {}
|
||||
for pname, plon in planet_lons.items():
|
||||
bhava = self._planet_bhava(plon, sandhis)
|
||||
if house_assignment == 'cusp_to_cusp':
|
||||
bhava = self._planet_bhava_from_cusps(plon, cusps)
|
||||
else:
|
||||
bhava = self._planet_bhava(plon, sandhis)
|
||||
rashi = self._planet_rashi_house(plon, asc_lon)
|
||||
si = _sign_idx(plon)
|
||||
deg_in_sign = plon - si * 30
|
||||
@@ -380,6 +400,7 @@ class BhavaChalitCalculator:
|
||||
return {
|
||||
'house_system': house_system,
|
||||
'house_system_desc': self.HOUSE_SYSTEMS.get(house_system, ''),
|
||||
'house_assignment': house_assignment,
|
||||
'ascendant_lon': round(asc_lon, 4),
|
||||
'mc_lon': round(mc_lon, 4),
|
||||
'planets': planets,
|
||||
|
||||
@@ -6378,7 +6378,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM
|
||||
periods = []
|
||||
current = birth_dt
|
||||
for lord in DASHA_ORDER:
|
||||
end_date = current + timedelta(days=period_years * 365.25636)
|
||||
end_date = current + timedelta(days=period_years * 365.25)
|
||||
periods.append({
|
||||
'lord': lord,
|
||||
'years': period_years,
|
||||
@@ -7398,7 +7398,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM
|
||||
current_idx, current_md = analyzer.find_current(timeline, today)
|
||||
antardashas = analyzer.build_antardasha(current_md)
|
||||
current_ad = analyzer.find_current_sub(antardashas, today)
|
||||
years_into_md = max(0.0, (today - current_md['start']).days / 365.25636)
|
||||
years_into_md = max(0.0, (today - current_md['start']).days / 365.25)
|
||||
five_levels = enhanced.calculate_five_level_dasha(current_md['lord'], years_into_md)
|
||||
formatted_timeline = [self._format_datetime_period(period) for period in timeline]
|
||||
formatted_antardashas = [self._format_datetime_period(period) for period in antardashas]
|
||||
@@ -7551,7 +7551,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM
|
||||
evidence.append({
|
||||
'label': 'Darakaraka (DK)',
|
||||
'value': f"{dk_planet} · H{darakaraka.get('dk_house', '-')}",
|
||||
'note': f"{darakaraka.get('core_profile') or '配偶象征星'};婚姻质量 {darakaraka.get('marriage_quality_score', '-')}/100",
|
||||
'note': f"{darakaraka.get('core_profile') or '配偶象征星'};婚姻质量指数 {darakaraka.get('marriage_quality_score', '-')}(0到1)",
|
||||
})
|
||||
except Exception as exc:
|
||||
darakaraka = {'error': str(exc), 'dk_planet': dk_planet}
|
||||
|
||||
@@ -13963,6 +13963,7 @@ def cmd_kp(args):
|
||||
)
|
||||
bhava_chart = kp_house_calculator.get_bhava_chalit_chart(
|
||||
planet_lons, float(asc_lon), 0.0, 'placidus', jd, args.lat, args.lon,
|
||||
house_assignment='cusp_to_cusp',
|
||||
)
|
||||
bhava_planets = bhava_chart.get('planets') if isinstance(bhava_chart.get('planets'), dict) else {}
|
||||
|
||||
@@ -13977,7 +13978,12 @@ def cmd_kp(args):
|
||||
'house': bhava_row.get('bhava_house', pdata.get('house', 1)),
|
||||
}
|
||||
|
||||
result = calc_kp_analysis(planet_positions, asc_sign, house_cusps=boundaries.get('cusps'))
|
||||
result = calc_kp_analysis(
|
||||
planet_positions,
|
||||
asc_sign,
|
||||
house_cusps=boundaries.get('cusps'),
|
||||
house_occupancy_basis='cusp_to_cusp',
|
||||
)
|
||||
moon_lon = planets.get('Moon', {}).get('degree_raw', planets.get('Moon', {}).get('degree', 0.0)) if isinstance(planets.get('Moon'), dict) else 0.0
|
||||
try:
|
||||
birth_moment = datetime(int(args.year), int(args.month), int(args.day), int(args.hour), int(getattr(args, 'minute', 0) or 0))
|
||||
|
||||
@@ -371,6 +371,7 @@ def calc_kp_analysis(
|
||||
planet_positions: Dict,
|
||||
asc_sign: str = 'Aries',
|
||||
house_cusps: Optional[List[float]] = None,
|
||||
house_occupancy_basis: Optional[str] = None,
|
||||
) -> Dict:
|
||||
"""
|
||||
完整KP分析(基于 diliprk/VedicAstro MIT 算法)。
|
||||
@@ -379,6 +380,7 @@ def calc_kp_analysis(
|
||||
planet_positions: 行星位置 {planet: {'sign': str, 'degree': float, 'house': int}}
|
||||
asc_sign: 上升星座名称(无显式宫头时用于 whole-sign 代理)
|
||||
house_cusps: 可选的 12 个实际宫头黄经。提供时优先用于 KP 宫头与显著星。
|
||||
house_occupancy_basis: 传了宫头就必须声明判宫口径;没传宫头时记 whole_sign_proxy。
|
||||
|
||||
Returns:
|
||||
完整KP分析结果
|
||||
@@ -405,8 +407,11 @@ def calc_kp_analysis(
|
||||
}
|
||||
|
||||
explicit_cusps = house_cusps is not None
|
||||
if explicit_cusps and not house_occupancy_basis:
|
||||
raise ValueError('house_occupancy_basis is required when house_cusps are provided')
|
||||
if explicit_cusps and len(house_cusps) != 12:
|
||||
raise ValueError('house_cusps must contain exactly 12 longitudes')
|
||||
occupancy_basis = house_occupancy_basis if explicit_cusps else 'whole_sign_proxy'
|
||||
|
||||
# 2. 构建宫位信息(含 KP lords)。无实际宫头时保留历史 whole-sign 中点代理。
|
||||
houses = []
|
||||
@@ -438,6 +443,7 @@ def calc_kp_analysis(
|
||||
'source': 'diliprk/VedicAstro MIT License',
|
||||
'maturity_profile': kp_maturity_profile(),
|
||||
'house_basis': 'explicit_cusps' if explicit_cusps else 'whole_sign_proxy',
|
||||
'house_occupancy_basis': occupancy_basis,
|
||||
'planets': {pname: {'kp_lords': data['kp_lords'], 'significators': planet_sig.get(pname, {})}
|
||||
for pname, data in kp_planets.items()},
|
||||
'houses': {h['house']: {'sign': h['sign'], 'cusp_longitude': h['cusp_longitude'], 'kp_lords': h['kp_lords'], 'significators': house_sig.get(h['house'], {})}
|
||||
@@ -494,6 +500,7 @@ def build_kp_report_pack(
|
||||
f"- claim_status: `{maturity.get('claim_status')}`",
|
||||
f"- truth_matrix_allowed: `{maturity.get('truth_matrix_allowed')}`",
|
||||
f"- closed_oracle_parity_assets: `{', '.join(closed_parity_assets.keys())}`",
|
||||
f"- House occupancy basis: `{kp_analysis.get('house_occupancy_basis') or 'whole_sign_proxy'}`",
|
||||
f"- planet_count: `{len(planets)}`",
|
||||
f"- house_count: `{len(houses)}`",
|
||||
f"- dba_period_count: `{len(periods)}`",
|
||||
|
||||
@@ -21,6 +21,23 @@ from typing import Dict, Optional, List
|
||||
SIGN_CN = ['白羊座','金牛座','双子座','巨蟹座','狮子座','处女座',
|
||||
'天秤座','天蝎座','射手座','摩羯座','水瓶座','双鱼座']
|
||||
|
||||
# D9 结构证据档位(BUG-1318)。中文给报告正文,英文键与英文短句给英文版。
|
||||
# 不写婚姻稳定、离婚、再婚、段数或婚前咨询。
|
||||
D9_CLAIM_BOUNDARY = (
|
||||
'D9 结构证据指数,不预测婚姻次数、稳定、离婚、伴侣好坏,也不提示需要婚前咨询'
|
||||
)
|
||||
D9_CLAIM_BOUNDARY_EN = (
|
||||
'D9 structural evidence index. It does not predict marriage count, stability, '
|
||||
'divorce, or partner quality, and it does not suggest premarital counseling.'
|
||||
)
|
||||
D9_EVIDENCE_BANDS = (
|
||||
('supportive_evidence_stronger', '支持证据较强', 'Stronger supportive evidence'),
|
||||
('supportive_evidence_mixed_positive', '支持证据偏正', 'Mixed supportive evidence'),
|
||||
('mixed_evidence', '证据混杂', 'Mixed evidence'),
|
||||
('pressure_evidence_mixed', '压力证据偏杂', 'Mixed pressure evidence'),
|
||||
('pressure_evidence_stronger', '压力证据较强', 'Stronger pressure evidence'),
|
||||
)
|
||||
|
||||
|
||||
def sign_of(lon: float) -> int:
|
||||
"""从黄道经度计算星座序号 (0-11)"""
|
||||
@@ -348,25 +365,42 @@ def _assess_d9_marriage_quality(
|
||||
quality_points -= 1
|
||||
factors.append("D9 Ketu在7宫 → 关系中的分离倾向")
|
||||
|
||||
# 综合评级
|
||||
# 证据档位,不是婚姻结局(BUG-1318,上游 f891a2c1)。
|
||||
if quality_points >= 4:
|
||||
rating = "高(关系质量好,伴侣支持强,婚姻稳定)"
|
||||
band = D9_EVIDENCE_BANDS[0]
|
||||
elif quality_points >= 1:
|
||||
rating = "中上(关系质量尚可,需经营但基础好)"
|
||||
band = D9_EVIDENCE_BANDS[1]
|
||||
elif quality_points >= 0:
|
||||
rating = "中(关系质量一般,需用心经营)"
|
||||
band = D9_EVIDENCE_BANDS[2]
|
||||
elif quality_points >= -2:
|
||||
rating = "中下(关系有挑战,需提前沟通和调整期望)"
|
||||
band = D9_EVIDENCE_BANDS[3]
|
||||
else:
|
||||
rating = "低(关系质量有重大挑战,强烈建议婚前咨询)"
|
||||
band = D9_EVIDENCE_BANDS[4]
|
||||
|
||||
return {
|
||||
'quality_rating': rating,
|
||||
'quality_band': band[0],
|
||||
'quality_rating': band[1],
|
||||
'quality_rating_en': band[2],
|
||||
'quality_points': quality_points,
|
||||
'factors': factors,
|
||||
'claim_boundary': D9_CLAIM_BOUNDARY,
|
||||
'claim_boundary_en': D9_CLAIM_BOUNDARY_EN,
|
||||
}
|
||||
|
||||
|
||||
def render_d9_evidence_report(quality: Dict, language: str = 'zh') -> str:
|
||||
"""中文报告用证据档位短句;英文版用英文短句。两边都带同一条边界。"""
|
||||
if not isinstance(quality, dict):
|
||||
quality = {}
|
||||
if language == 'en':
|
||||
band = quality.get('quality_rating_en') or quality.get('quality_band') or ''
|
||||
boundary = quality.get('claim_boundary_en') or D9_CLAIM_BOUNDARY_EN
|
||||
return f"D9 evidence band: {band}\n{boundary}"
|
||||
band = quality.get('quality_rating') or ''
|
||||
boundary = quality.get('claim_boundary') or D9_CLAIM_BOUNDARY
|
||||
return f"D9 证据档位:{band}\n{boundary}"
|
||||
|
||||
|
||||
def _marriage_recommendations(base: Dict, d9_quality: Dict) -> List[str]:
|
||||
"""几何指数与伴侣数量、事件断语分开(BUG-1183);不随指数大小给不同结论。"""
|
||||
return [
|
||||
|
||||
@@ -2140,6 +2140,7 @@ def render_pl9_parity_markdown(packet: dict) -> str:
|
||||
'### p123 KP Chart / Cusps',
|
||||
'',
|
||||
f"- House basis:{_cell(kp.get('house_basis'))}",
|
||||
f"- House occupancy basis:{_cell(kp.get('house_occupancy_basis'))}",
|
||||
'',
|
||||
'| House | Sign | Cusp longitude | KP lord chain |',
|
||||
'|-------|------|----------------|---------------|',
|
||||
|
||||
@@ -1687,11 +1687,13 @@ class YogaEngine:
|
||||
# 辅助:获取角宫主星列表
|
||||
def _kendra_lords_list():
|
||||
ai = SIGNS.index(ctx.ascendant) if ctx.ascendant in SIGNS else 0
|
||||
return list(set([SIGN_LORDS[SIGNS[(ai + h - 1) % 12]] for h in [1, 4, 7, 10]]))
|
||||
# set 顺序随进程哈希变化;排序后同一张盘的角宫主参与者顺序才稳定。
|
||||
return sorted(set([SIGN_LORDS[SIGNS[(ai + h - 1) % 12]] for h in [1, 4, 7, 10]]))
|
||||
# 辅助:获取三方宫主星列表
|
||||
def _trikona_lords_list():
|
||||
ai = SIGNS.index(ctx.ascendant) if ctx.ascendant in SIGNS else 0
|
||||
return list(set([SIGN_LORDS[SIGNS[(ai + h - 1) % 12]] for h in [1, 5, 9]]))
|
||||
# set 顺序随进程哈希变化;排序后同一张盘的三方主参与者顺序才稳定。
|
||||
return sorted(set([SIGN_LORDS[SIGNS[(ai + h - 1) % 12]] for h in [1, 5, 9]]))
|
||||
|
||||
safe_globals = {
|
||||
# 安全内置函数:只开放规则表达式需要的纯函数,避免 custom 规则因 all/any/len/set 缺失而静默失效。
|
||||
|
||||
Reference in New Issue
Block a user