fix(engine): dispositor cycles are not collapsed to one planet; dasa convergence drops uncalibrated probabilities (upstream e9beae6b)
final_dispositor_from_chain and its regression test are upstream yinduzhanxing e9beae6b; probability is None with probability_basis. The Narayana convergence additions of that commit are out of scope. TASK-upstream-sync4-20261002 T7. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
08376cf0a8
commit
bc9b9f89bb
+28
-21
@@ -472,6 +472,29 @@ def calc_inter_chart_linkage(planet_name, d1_data, d9_data, d10_data, d12_data=N
|
||||
return result
|
||||
|
||||
|
||||
def final_dispositor_from_chain(chain):
|
||||
"""Return a terminal only for a self-loop; retain multi-body cycles.
|
||||
|
||||
Ported from upstream yinduzhanxing e9beae6b (TASK-upstream-sync4-20261002 T7).
|
||||
"""
|
||||
path = []
|
||||
for row in chain:
|
||||
planet, target = row.get('planet'), row.get('dispositor')
|
||||
if not planet or not target or (path and path[-1] != planet):
|
||||
return None
|
||||
if not path:
|
||||
path.append(planet)
|
||||
if target == planet:
|
||||
return planet
|
||||
if target in path:
|
||||
cycle = path[path.index(target):]
|
||||
rotations = [cycle[i:] + cycle[:i] for i in range(len(cycle))]
|
||||
cycle = min(rotations)
|
||||
return 'Cycle: ' + ' -> '.join(cycle + cycle[:1])
|
||||
path.append(target)
|
||||
return None # incomplete or depth-limited chain is not a terminal
|
||||
|
||||
|
||||
def calc_all_dispositor_chains(planets_data):
|
||||
"""为所有行星计算定位星链"""
|
||||
result = {}
|
||||
@@ -15023,21 +15046,20 @@ def _calc_dasa_convergence(dasha_result, chara_dasha_result, yogini_result, plan
|
||||
sc = info['system_count']
|
||||
if sc >= 4:
|
||||
info['convergence_level'] = 'L5'
|
||||
info['probability'] = '85-92%'
|
||||
info['interpretation'] = '四个及以上推运系统同时激活,顶级收敛信号'
|
||||
elif sc >= 3:
|
||||
info['convergence_level'] = 'L4'
|
||||
info['probability'] = '75-85%'
|
||||
info['interpretation'] = '三系统同时激活,极强信号'
|
||||
elif sc >= 2:
|
||||
info['convergence_level'] = 'L3'
|
||||
info['probability'] = '50-65%'
|
||||
info['interpretation'] = '双系统激活,强信号'
|
||||
else:
|
||||
info['convergence_level'] = 'L1'
|
||||
info['probability'] = '+15-20%'
|
||||
info['interpretation'] = '单系统激活,需Transit确认'
|
||||
|
||||
info['probability'] = None
|
||||
info['probability_basis'] = 'No empirical probability calibration; level counts activating systems only.'
|
||||
|
||||
# 收敛窗口(最高优先级的领域)
|
||||
top_domains = sorted(domain_activations.items(), key=lambda x: x[1]['system_count'], reverse=True)[:5]
|
||||
|
||||
@@ -15725,23 +15747,8 @@ def cmd_full_reading(args):
|
||||
report['modules']['inter_chart_linkage'] = ic_result
|
||||
|
||||
# 补充:每个行星的"最终定位星"(Dispositor Chain 的最后一个非循环元素)
|
||||
final_dispositors = {}
|
||||
for pname, chain in dc_result.items():
|
||||
if chain:
|
||||
# 找到最后一个不重复的元素
|
||||
seen = set()
|
||||
last_valid = None
|
||||
for item in chain:
|
||||
if item['dispositor'] in seen:
|
||||
break
|
||||
seen.add(item['dispositor'])
|
||||
last_valid = item
|
||||
if last_valid:
|
||||
final_dispositors[pname] = last_valid['dispositor']
|
||||
else:
|
||||
final_dispositors[pname] = chain[-1]['dispositor'] if chain else None
|
||||
else:
|
||||
final_dispositors[pname] = None
|
||||
# A multi-planet cycle has no unique terminal dispositor.
|
||||
final_dispositors = {p: final_dispositor_from_chain(c) for p, c in dc_result.items()}
|
||||
report['modules']['final_dispositors'] = final_dispositors
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Do not collapse a directed dispositor cycle into an arbitrary terminal."""
|
||||
import ast
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def resolver():
|
||||
# Isolate the pure function without importing the large CLI and adapters.
|
||||
source = Path(__file__).parents[1] / 'scripts/jyotish_engine.py'
|
||||
tree = ast.parse(source.read_text())
|
||||
node = next(n for n in tree.body if isinstance(n, ast.FunctionDef)
|
||||
and n.name == 'final_dispositor_from_chain')
|
||||
namespace = {}
|
||||
exec(compile(ast.Module(body=[node], type_ignores=[]), str(source), 'exec'), namespace)
|
||||
return namespace[node.name]
|
||||
|
||||
|
||||
def chain(*names):
|
||||
return [{'planet':a,'dispositor':b} for a,b in zip(names,names[1:])]
|
||||
|
||||
|
||||
def test_cycle_rotation_and_leading_planet():
|
||||
f = resolver()
|
||||
expected = 'Cycle: Jupiter -> Mercury -> Jupiter'
|
||||
assert f(chain('Mercury','Jupiter','Mercury')) == expected
|
||||
assert f(chain('Jupiter','Mercury','Jupiter')) == expected
|
||||
assert f(chain('Venus','Jupiter','Mercury','Jupiter')) == expected
|
||||
|
||||
|
||||
def test_terminal_and_incomplete_are_distinct():
|
||||
f = resolver()
|
||||
assert f(chain('Sun','Mars','Saturn','Saturn')) == 'Saturn'
|
||||
assert f(chain('Sun','Mars')) is None
|
||||
assert f([]) is None
|
||||
assert f(chain('Sun','Mars') + chain('Venus','Venus')) is None
|
||||
|
||||
|
||||
def test_three_body_cycle_preserves_direction():
|
||||
assert resolver()(chain('Sun','Mars','Moon','Sun')) == 'Cycle: Mars -> Moon -> Sun -> Mars'
|
||||
Reference in New Issue
Block a user