fix(rectification): split D4/D5 stages and recast after adopt
Keep family on D12+D7 instead of mixing it into D4, score D5 on education, and show a natal recast plus technique audit after adopt without unique-minute claims. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -47,14 +47,14 @@ INPUT_CONTRACT_VERSION: Final = "rectification-candidate-input-v2"
|
||||
|
||||
DomainConfig = tuple[tuple[str, ...], tuple[int, ...]]
|
||||
DOMAIN_CONFIG: Final[dict[EventDomain, DomainConfig]] = {
|
||||
"education": (("D24",), (4, 5, 9)),
|
||||
"education": (("D24", "D5"), (4, 5, 9)),
|
||||
"relocation": (("D4",), (4, 12)),
|
||||
"relationship": (("D9",), (7,)),
|
||||
"career": (("D10",), (10,)),
|
||||
"finance": (("D2", "D11"), (2, 11)),
|
||||
"health_pressure": (("D30",), (6, 8, 12)),
|
||||
# 六亲: D12 parents plus D1 houses 3/4/5/9 (siblings, mother/home, children, father).
|
||||
"family": (("D12",), (3, 4, 5, 9)),
|
||||
# 六亲: D12 parents, D7 children/spouse detail, plus D1 houses 3/4/5/9.
|
||||
"family": (("D12", "D7"), (3, 4, 5, 9)),
|
||||
# Dated appearance/marks: D1 lagna / 1st house only. Not a primary formula.
|
||||
"appearance": ((), (1,)),
|
||||
}
|
||||
@@ -369,12 +369,12 @@ def build_candidate_static_context(
|
||||
charts = varga.calc_all_vargas(
|
||||
planet_longitudes,
|
||||
ascendant_longitude,
|
||||
divisions=[2, 4, 9, 10, 12, 24, 30],
|
||||
divisions=[2, 4, 5, 7, 9, 10, 12, 24, 30],
|
||||
)
|
||||
d11_chart = _d11_chart(planet_longitudes, ascendant_longitude)
|
||||
varga_charts = {
|
||||
prefix: d11_chart if prefix == "D11" else _varga_chart(charts, prefix)
|
||||
for prefix in ("D2", "D4", "D9", "D10", "D11", "D12", "D24", "D30")
|
||||
for prefix in ("D2", "D4", "D5", "D7", "D9", "D10", "D11", "D12", "D24", "D30")
|
||||
}
|
||||
available_layers = ["D1"]
|
||||
blocked_layers = ["KP_cusps"]
|
||||
|
||||
@@ -40,6 +40,105 @@ def _decimal(value: Any, default: str = "0") -> Decimal:
|
||||
return Decimal(default)
|
||||
|
||||
|
||||
_AUDIT_LABELS = {
|
||||
"d1-rashi": ("D1 本命盘", "本轮已按该分钟重算本命宫位。"),
|
||||
"d2-hora": ("D2 财帛分盘", "本轮已对照财帛主题。"),
|
||||
"d4-chaturthamsha": ("D4 迁移分盘", "本轮已对照居所或迁移。"),
|
||||
"d5-panchamsha": ("D5 成就分盘", "本轮已对照学业或被委以责任的变化。"),
|
||||
"d7-saptamsha": ("D7 子女分盘", "本轮已对照子女或伴侣细节。"),
|
||||
"d9-navamsa": ("D9 婚姻分盘", "本轮已对照关系主题,未给类型标签。"),
|
||||
"d10-dashamsa": ("D10 事业分盘", "本轮已对照事业主题,未给类型标签。"),
|
||||
"d11-labhamsha": ("D11 收益分盘", "本轮已对照收益主题。"),
|
||||
"d12-dwadashamsha": ("D12 父母分盘", "本轮已对照家人主题。"),
|
||||
"d24-chaturvimshamsha": ("D24 教育分盘", "本轮已对照学业主题。"),
|
||||
"d30-trimshamsha": ("D30 健康压力分盘", "本轮已对照健康压力主题。"),
|
||||
"vimshottari-dasha": ("Vimshottari", "本轮已对照主限。"),
|
||||
"narayana-dasha": ("Narayana", "本轮已对照分盘大运。"),
|
||||
"gochara": ("Gochara", "本轮已做受控行运辅助对照。"),
|
||||
"ashtakavarga": ("Ashtakavarga", "本轮已做 Ashtakavarga 辅助对照。"),
|
||||
"shadbala": ("Shadbala", "本轮已做已核验的 Shadbala 分量辅助对照。"),
|
||||
"arudha-pada": ("Arudha Pada", "本轮已做 Arudha 辅助对照。"),
|
||||
"functional-benefic-malefic": ("功能吉凶星", "本轮已叠加本命功能吉凶星。"),
|
||||
}
|
||||
|
||||
|
||||
def natal_recast_copy(time: str, lagna: str) -> dict[str, Any]:
|
||||
return {
|
||||
"time": time[:5],
|
||||
"lagna": lagna,
|
||||
"user_meaning": (
|
||||
f"本命宫位已按 {time[:5]} 重算(上升 {lagna})。"
|
||||
"下面是本轮实际执行的技法,不能当作唯一分钟确认。"
|
||||
),
|
||||
"unique_minute_claim": False,
|
||||
"confirmation_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def _executed_public_methods(built: dict[str, Any]) -> list[str]:
|
||||
methods: set[str] = set()
|
||||
for contributions in (built.get("matrix") or {}).values():
|
||||
if not isinstance(contributions, dict):
|
||||
continue
|
||||
for cell in contributions.values():
|
||||
if not isinstance(cell, dict):
|
||||
continue
|
||||
for layer in cell.get("technique_layers") or []:
|
||||
if layer in _AUDIT_LABELS:
|
||||
methods.add(str(layer))
|
||||
for rule in cell.get("rule_ids") or []:
|
||||
text = str(rule)
|
||||
if text.startswith("vim_"):
|
||||
methods.add("vimshottari-dasha")
|
||||
elif text.startswith("narayana_"):
|
||||
methods.add("narayana-dasha")
|
||||
elif "functional_benefic" in text or "functional_malefic" in text:
|
||||
methods.add("functional-benefic-malefic")
|
||||
elif text.startswith("gochara") or "controlled_transit" in text:
|
||||
methods.add("gochara")
|
||||
elif "ashtakavarga" in text:
|
||||
methods.add("ashtakavarga")
|
||||
elif "shadbala" in text:
|
||||
methods.add("shadbala")
|
||||
elif "arudha" in text:
|
||||
methods.add("arudha-pada")
|
||||
return [key for key in _AUDIT_LABELS if key in methods]
|
||||
|
||||
|
||||
def build_technique_audit(
|
||||
built: dict[str, Any],
|
||||
*,
|
||||
house_table: dict[str, Any] | None,
|
||||
) -> list[dict[str, str]]:
|
||||
executed = set(_executed_public_methods(built))
|
||||
if house_table:
|
||||
executed.add("d1-rashi")
|
||||
rows: list[dict[str, str]] = []
|
||||
for method in _AUDIT_LABELS:
|
||||
if method not in executed:
|
||||
continue
|
||||
label, note = _AUDIT_LABELS[method]
|
||||
rows.append({"technique": label, "status": "executed", "note": note})
|
||||
rows.append({
|
||||
"technique": "KP 宫头",
|
||||
"status": "blocked",
|
||||
"note": "KP 宫头本轮未计算。",
|
||||
})
|
||||
rows.extend((
|
||||
{
|
||||
"technique": "VedAstro 分钟级校验",
|
||||
"status": "blocked",
|
||||
"note": "官方分钟级校验尚未评估。",
|
||||
},
|
||||
{
|
||||
"technique": "唯一分钟确认",
|
||||
"status": "blocked",
|
||||
"note": "采用不等于确认唯一分钟。",
|
||||
},
|
||||
))
|
||||
return rows
|
||||
|
||||
|
||||
def _quantized_score(row: CandidateScoreRow) -> Decimal:
|
||||
return _decimal(row.get("score")).quantize(SCORE_QUANTUM, rounding=ROUND_HALF_UP)
|
||||
|
||||
@@ -263,12 +362,22 @@ def build_decision_receipt(
|
||||
"exact_confirmation": exact_confirmation,
|
||||
},
|
||||
}
|
||||
house_table = compact_house_table_from_contexts(
|
||||
house_tables_by_time: dict[str, dict[str, Any]] = {}
|
||||
for decision in candidate_decisions:
|
||||
table = compact_house_table_from_contexts(built.get("static_contexts"), decision.get("time"))
|
||||
if table:
|
||||
house_tables_by_time[table["time"]] = table
|
||||
house_table = house_tables_by_time.get(representative["time"] if representative else "") or compact_house_table_from_contexts(
|
||||
built.get("static_contexts"),
|
||||
representative["time"] if representative else None,
|
||||
)
|
||||
if house_table:
|
||||
receipt["house_table"] = house_table
|
||||
recast = natal_recast_copy(house_table["time"], house_table["lagna"])
|
||||
receipt["natal_recast"] = recast
|
||||
if house_tables_by_time:
|
||||
receipt["house_tables_by_time"] = house_tables_by_time
|
||||
receipt["technique_audit_table"] = build_technique_audit(built, house_table=house_table)
|
||||
receipt.update({
|
||||
"window_scan": packet["window_scan"],
|
||||
"event_dasha_ledger": packet["event_dasha_ledger"],
|
||||
|
||||
@@ -113,11 +113,19 @@ def _split_track_points(rule_ids: Sequence[str], points: float) -> tuple[float,
|
||||
return points * vim / total, points * narayana / total
|
||||
|
||||
|
||||
_LAYER_LABEL = {"d1": "本命上升", "d9": "D9", "d10": "D10", "d4": "D4", "d12": "D12"}
|
||||
_LAYER_LABEL = {
|
||||
"d1": "本命上升",
|
||||
"d9": "D9",
|
||||
"d10": "D10",
|
||||
"d4": "D4",
|
||||
"d5": "D5",
|
||||
"d7": "D7",
|
||||
"d12": "D12",
|
||||
}
|
||||
|
||||
|
||||
def window_scan(built: dict[str, Any]) -> dict[str, Any]:
|
||||
"""D1/D9/D10/D4 diversity plus change minutes. Indices only; never sign names."""
|
||||
"""D1/D9/D10/D4/D5/D7/D12 diversity plus change minutes. Indices only; never sign names."""
|
||||
counts: dict[str, set[int]] = {layer: set() for layer in _LAYER_LABEL}
|
||||
transitions: list[dict[str, Any]] = []
|
||||
previous: dict[str, int | None] | None = None
|
||||
@@ -128,6 +136,8 @@ def window_scan(built: dict[str, Any]) -> dict[str, Any]:
|
||||
"d9": vargas.get("D9") if isinstance(vargas.get("D9"), int) else None,
|
||||
"d10": vargas.get("D10") if isinstance(vargas.get("D10"), int) else None,
|
||||
"d4": vargas.get("D4") if isinstance(vargas.get("D4"), int) else None,
|
||||
"d5": vargas.get("D5") if isinstance(vargas.get("D5"), int) else None,
|
||||
"d7": vargas.get("D7") if isinstance(vargas.get("D7"), int) else None,
|
||||
"d12": vargas.get("D12") if isinstance(vargas.get("D12"), int) else None,
|
||||
}
|
||||
for layer, bucket in counts.items():
|
||||
@@ -154,11 +164,15 @@ def window_scan(built: dict[str, Any]) -> dict[str, Any]:
|
||||
"d9_lagna_count": len(counts["d9"]),
|
||||
"d10_lagna_count": len(counts["d10"]),
|
||||
"d4_lagna_count": len(counts["d4"]),
|
||||
"d5_lagna_count": len(counts["d5"]),
|
||||
"d7_lagna_count": len(counts["d7"]),
|
||||
"d12_lagna_count": len(counts["d12"]),
|
||||
"d1_candidates_differ": len(counts["d1"]) > 1,
|
||||
"d9_candidates_differ": len(counts["d9"]) > 1,
|
||||
"d10_candidates_differ": len(counts["d10"]) > 1,
|
||||
"d4_candidates_differ": len(counts["d4"]) > 1,
|
||||
"d5_candidates_differ": len(counts["d5"]) > 1,
|
||||
"d7_candidates_differ": len(counts["d7"]) > 1,
|
||||
"d12_candidates_differ": len(counts["d12"]) > 1,
|
||||
"transitions": transitions,
|
||||
}
|
||||
@@ -360,14 +374,17 @@ def precision_stage(scan: dict[str, Any], event_count: int) -> dict[str, Any]:
|
||||
current = "d10_refine"
|
||||
meaning = "关系盘已较稳,事业盘仍会换升。可再补一件记得时间的工作变化。"
|
||||
elif scan.get("d4_candidates_differ"):
|
||||
current = "theme_refine"
|
||||
meaning = "核心分盘已较稳。若还想收窄,可再补一件记得时间的家人或住处变化;也可以先采用代表性时间。"
|
||||
current = "d4_refine"
|
||||
meaning = "事业盘已较稳,居所盘仍会换升。可再补一件记得时间的搬家或住处变化。"
|
||||
elif scan.get("d5_candidates_differ"):
|
||||
current = "d5_refine"
|
||||
meaning = "居所盘已较稳,成就盘仍会换升。可再补一件记得时间的学业、考试或被委以责任的变化;不要贴类型标签。"
|
||||
else:
|
||||
current = "ready_to_adopt"
|
||||
meaning = "核心分盘已不再换升。可以采用代表性时间看盘,也可以再补主题经历。"
|
||||
return {
|
||||
"current": current,
|
||||
"can_stop": current in {"d9_refine", "d10_refine", "theme_refine", "ready_to_adopt"},
|
||||
"can_stop": current in {"d9_refine", "d10_refine", "d4_refine", "d5_refine", "theme_refine", "ready_to_adopt"},
|
||||
"user_meaning": meaning,
|
||||
"unique_minute_claim": False,
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ from scripts.active_rectification_event_engine import compute_candidate_static_c
|
||||
from scripts.active_rectification_events import CandidateScoreRow
|
||||
from scripts.rectification.contracts import LifeEvent, RectificationRequest, is_scoreable_event
|
||||
|
||||
ALGORITHM_VERSION = "rectification-v5-matrix-scoring-3"
|
||||
ALGORITHM_VERSION = "rectification-v5-matrix-scoring-4"
|
||||
INPUT_CONTRACT_VERSION = "rectification-calculation-spec-v4"
|
||||
PRECISION_WEIGHTS = {
|
||||
"day": 1.0,
|
||||
@@ -184,7 +184,11 @@ def public_technique_layers(domain: str, rule_ids: Sequence[str]) -> list[str]:
|
||||
if domain == "career":
|
||||
layers.update({"d1-rashi", "d10-dashamsa"})
|
||||
elif domain == "family":
|
||||
layers.update({"d1-rashi", "d12-dwadashamsha"})
|
||||
layers.update({"d1-rashi", "d12-dwadashamsha", "d7-saptamsha"})
|
||||
elif domain == "education":
|
||||
layers.update({"d1-rashi", "d24-chaturvimshamsha", "d5-panchamsha"})
|
||||
elif domain == "relocation":
|
||||
layers.update({"d1-rashi", "d4-chaturthamsha"})
|
||||
elif domain in {"appearance", "marks"}:
|
||||
layers.add("d1-rashi")
|
||||
return sorted(layers)
|
||||
|
||||
@@ -80,7 +80,7 @@ def build_rectification_technique_contract(
|
||||
return {
|
||||
"schema_version": 2,
|
||||
"calculation_status": "not_started" if event_count == 0 else "evaluated",
|
||||
"used_divisional_charts": ["D2", "D4", "D9", "D10", "D11", "D12", "D24", "D30"],
|
||||
"used_divisional_charts": ["D2", "D4", "D5", "D7", "D9", "D10", "D11", "D12", "D24", "D30"],
|
||||
"used_arudha": ["A7", "UL", "A10"],
|
||||
"dasha_tracks": ["vimshottari_md_ad_pd", "narayana_md_ad"],
|
||||
"missing_layers": reported_missing_layers,
|
||||
|
||||
Reference in New Issue
Block a user