feat(product): present consult and rectification in local skill form
Independent Staging Quality Gate / validate (push) Failing after 6m42s
Independent Staging Quality Gate / publish (push) Has been skipped

Default ayanamsa to Raman with true_pushya support, attach governed Raman packets, restore Path C questionnaires and eight-method verification copy, and keep unique-minute confirmation blocked.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-20 21:42:47 +08:00
parent f8569c5c65
commit 8010245981
83 changed files with 2881 additions and 270 deletions
+3 -2
View File
@@ -56,8 +56,8 @@ _AUDIT_LABELS = {
"d5-panchamsha": ("D5 成就分盘", "本轮已对照学业或被委以责任的变化。"),
"d3-drekkana": ("D3 兄弟分盘", "本轮已对照兄弟姐妹主题。"),
"d7-saptamsha": ("D7 子女分盘", "本轮已对照子女或伴侣细节。"),
"d9-navamsa": ("D9 婚姻分盘", "本轮已对照关系主题,未给类型标签"),
"d10-dashamsa": ("D10 事业分盘", "本轮已对照事业主题,未给类型标签"),
"d9-navamsa": ("D9 婚姻分盘", "本轮已对照关系主题,可用 D9 上升类型表作校时方法,不是命运承诺"),
"d10-dashamsa": ("D10 事业分盘", "本轮已对照事业主题,可用 D10 上升类型表作校时方法,不是命运承诺"),
"d11-labhamsha": ("D11 收益分盘", "本轮已对照收益主题。"),
"d12-dwadashamsha": ("D12 父母分盘", "本轮已对照家人主题。"),
"d24-chaturvimshamsha": ("D24 教育分盘", "本轮已对照学业主题。"),
@@ -596,6 +596,7 @@ def build_decision_receipt(
receipt.update({
"window_scan": packet["window_scan"],
"event_dasha_ledger": packet["event_dasha_ledger"],
"event_fit_rate": packet["event_fit_rate"],
"dasha_agreement": packet["dasha_agreement"],
"lagna_contrast": packet["lagna_contrast"],
"nakshatra_boundary": packet["nakshatra_boundary"],
+1 -1
View File
@@ -61,7 +61,7 @@ def build_horary_observation(request: dict[str, Any]) -> dict[str, Any]:
"lat": float(request["lat"]),
"lon": float(request["lon"]),
"tz": float(request["tz"]),
"ayanamsa": "lahiri",
"ayanamsa": "raman",
"node_mode": "mean",
})
asc = chart.get("ascendant") or {}
+2 -2
View File
@@ -9,7 +9,7 @@ from __future__ import annotations
from typing import Any
from ayanamsa_utils import apply_ayanamsa, current_ayanamsa_name
from ayanamsa_utils import DEFAULT_AYANAMSA_NAME, apply_ayanamsa, current_ayanamsa_name
from kp_system import KP_LORDS, get_kp_lords
from scripts.domain_calculation_service import swiss_ephemeris_lock
@@ -84,4 +84,4 @@ def observe_kp_cusps(jd: float | None, lat: float | None, lon: float | None) ->
except (TypeError, ValueError, OverflowError, OSError, RuntimeError, ArithmeticError):
return dict(_BLOCKED)
finally:
apply_ayanamsa(previous or "lahiri", swe)
apply_ayanamsa(previous or DEFAULT_AYANAMSA_NAME, swe)
+68 -7
View File
@@ -1,7 +1,8 @@
"""Server-owned P0/P1 refinement packet for birth-time rectification.
Produces candidate-narrowing structure only. Never grants a unique minute,
never emits D9/D10 type labels, and never copies raw scores into public copy.
Produces candidate-narrowing structure only. Never grants a unique minute
and never copies raw scores into public copy. D9/D10 sign names are method
contrast for the skill verification report, not unique-minute proof.
"""
from __future__ import annotations
@@ -76,6 +77,18 @@ def _features(built: dict[str, Any]) -> list[dict[str, Any]]:
return rows
def _sign_names(indices: set[int]) -> list[str]:
names: list[str] = []
for idx in sorted(indices):
if isinstance(idx, int) and 0 <= idx <= 11:
names.append(SIGNS_CN[SIGNS[idx]])
return names
def _has_gochara(rule_ids: Sequence[str]) -> bool:
return any("controlled_transit" in str(item) or str(item).startswith("gochara") for item in rule_ids)
def match_level(rule_ids: Sequence[str]) -> str:
ids = [str(item) for item in rule_ids]
if not ids or ids == ["no_domain_activation"]:
@@ -92,6 +105,8 @@ def match_level(rule_ids: Sequence[str]) -> str:
return "medium"
if any(item.startswith("vim_") or item.startswith("narayana_") for item in ids):
return "weak"
if _has_gochara(ids):
return "medium"
return "none"
@@ -101,6 +116,8 @@ def _tracks(rule_ids: Sequence[str]) -> list[str]:
tracks.append("vimshottari")
if any(str(item).startswith("narayana_") for item in rule_ids):
tracks.append("narayana")
if _has_gochara(rule_ids):
tracks.append("gochara")
return tracks
@@ -196,6 +213,8 @@ def window_scan(built: dict[str, Any]) -> dict[str, Any]:
for layer in _LAYER_LABEL:
payload[f"{layer}_lagna_count" if layer.startswith("d") else f"{layer}_count"] = len(counts[layer])
payload[f"{layer}_candidates_differ"] = len(counts[layer]) > 1
payload["d9_sign_names"] = _sign_names(counts["d9"])
payload["d10_sign_names"] = _sign_names(counts["d10"])
return payload
@@ -219,18 +238,58 @@ def event_dasha_ledger(
summary = str(event.get("summary") or "").strip() or "这条经历"
tracks = _tracks(rule_ids)
track_text = "".join(
"主限" if track == "vimshottari" else "分盘大运" for track in tracks
"主限" if track == "vimshottari" else "分盘大运" if track == "narayana" else "受控行运"
for track in tracks
) or "现有大运层"
gochara_hit = _has_gochara(rule_ids)
rows.append({
"summary": summary[:80],
"match": level,
"match_label": MATCH_LABELS[level],
"tracks": tracks,
"user_meaning": f"{summary[:40]}{MATCH_LABELS[level]}{track_text}",
"gochara": "activated" if gochara_hit else "not_seen",
"user_meaning": (
f"{summary[:40]}{MATCH_LABELS[level]}{track_text}"
f"{'Gochara 激活相关宫' if gochara_hit else 'Gochara 未见对应'}"
),
})
return rows
def event_fit_rate(rows: Sequence[dict[str, Any]]) -> dict[str, Any]:
total = len(rows)
matched = sum(1 for row in rows if row.get("match") in {"strong", "medium"})
if total == 0:
return {
"matched": 0,
"total": 0,
"percent": None,
"band": "insufficient",
"label": "事件不足,无法计算吻合率",
"unique_minute_claim": False,
"user_meaning": "事件–Dasha–Gochara 表还没有可评分行。这不是唯一分钟确认。",
}
percent = round(100 * matched / total)
band = "high" if percent >= 80 else "medium" if percent >= 60 else "low"
label = (
"高度吻合(事件吻合率 ≥80%" if band == "high"
else "中度吻合(事件吻合率 6080%" if band == "medium"
else "低度吻合(事件吻合率 <60%"
)
return {
"matched": matched,
"total": total,
"percent": percent,
"band": band,
"label": label,
"unique_minute_claim": False,
"user_meaning": (
f"当前窗 {matched}/{total} 件已确认事件与 Dasha/Gochara 吻合,{label}"
"这是相对拟合,不是已确认唯一出生分钟。"
),
}
def dasha_agreement(built: dict[str, Any], candidate_times: Sequence[str]) -> dict[str, Any]:
times = [str(item)[:5] for item in candidate_times if isinstance(item, str) and len(str(item)) >= 5]
if not times:
@@ -324,7 +383,7 @@ def lagna_contrast(built: dict[str, Any]) -> dict[str, Any] | None:
"user_meaning": (
f"窗口里出现两段本命上升:{left['start']}-{left['end']}{left['lagna']}"
f"{right['start']}-{right['end']}{right['lagna']}"
"只比较宫主结构,不给性格或类型标签"
"可并列 D9/D10 类型表作校时方法,不是命运承诺,也不能确认唯一分钟"
),
"unique_minute_claim": False,
}
@@ -399,7 +458,7 @@ def precision_stage(scan: dict[str, Any], event_count: int) -> dict[str, Any]:
meaning = "事业盘已较稳,居所盘仍会换升。可再补一件记得时间的搬家或住处变化。"
elif scan.get("d5_candidates_differ") or scan.get("d24_candidates_differ"):
current = "d5_refine"
meaning = "居所盘已较稳,成就盘或学业盘仍会换升。可再补一件记得时间的学业、考试或被委以责任的变化;不要贴类型标签"
meaning = "居所盘已较稳,成就盘或学业盘仍会换升。可再补一件记得时间的学业、考试或被委以责任的变化。"
else:
current = "ready_to_adopt"
meaning = "核心分盘已不再换升。可以采用代表性时间看盘,也可以再补主题经历。"
@@ -441,10 +500,12 @@ def build_refinement_packet(
candidate_times: Sequence[str],
) -> dict[str, Any]:
scan = window_scan(built)
ledger = event_dasha_ledger(request, built, representative_time)
agreement = dasha_agreement(built, candidate_times)
return {
"window_scan": scan,
"event_dasha_ledger": event_dasha_ledger(request, built, representative_time),
"event_dasha_ledger": ledger,
"event_fit_rate": event_fit_rate(ledger),
"dasha_agreement": agreement,
"lagna_contrast": lagna_contrast(built),
"nakshatra_boundary": nakshatra_boundary(built, representative_time),
+1 -1
View File
@@ -328,7 +328,7 @@ def calculation_spec(request: RectificationRequest) -> dict[str, Any]:
"latitude": json_number(request["lat"]),
"longitude": json_number(request["lon"]),
"timezoneOffsetHours": json_number(request["tz"]),
"ayanamsa": "lahiri", "nodeMode": "mean", "minuteStep": 1,
"ayanamsa": "raman", "nodeMode": "mean", "minuteStep": 1,
}
for source, target in (
("birth_time_source", "birthTimeSource"),