feat(rectification): pass ayanamsa per request
This commit is contained in:
@@ -289,7 +289,9 @@ def _controlled_transit_rules(
|
||||
transit_chart = domain_calculation_service.compute_chart({
|
||||
"year": event_at.year, "month": event_at.month, "day": event_at.day,
|
||||
"hour": 12, "minute": 0, "lat": request["lat"], "lon": request["lon"],
|
||||
"tz": request["tz"], "ayanamsa": AYANAMSA, "node_mode": NODE_MODE,
|
||||
"tz": request["tz"],
|
||||
"ayanamsa": request.get("ayanamsa", AYANAMSA),
|
||||
"node_mode": request.get("node_mode", NODE_MODE),
|
||||
})
|
||||
rules: list[str] = []
|
||||
for planet in ("Jupiter", "Saturn"):
|
||||
@@ -425,8 +427,8 @@ def build_candidate_static_context(
|
||||
"lat": request["lat"],
|
||||
"lon": request["lon"],
|
||||
"tz": request["tz"],
|
||||
"ayanamsa": AYANAMSA,
|
||||
"node_mode": NODE_MODE,
|
||||
"ayanamsa": request.get("ayanamsa", AYANAMSA),
|
||||
"node_mode": request.get("node_mode", NODE_MODE),
|
||||
})
|
||||
planet_longitudes = {
|
||||
name: float(data["lon"])
|
||||
@@ -642,8 +644,8 @@ def _canonical_input_contract(request: RectificationEventRequest) -> tuple[dict,
|
||||
"summary": event.get("summary", ""),
|
||||
} for event in request["events"]],
|
||||
"calculation": {
|
||||
"ayanamsa": AYANAMSA,
|
||||
"node_mode": NODE_MODE,
|
||||
"ayanamsa": request.get("ayanamsa", AYANAMSA),
|
||||
"node_mode": request.get("node_mode", NODE_MODE),
|
||||
"ephemeris_source": "swisseph_calc_ut",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -73,6 +73,8 @@ class RectificationEventRequest(TypedDict):
|
||||
lat: float
|
||||
lon: float
|
||||
tz: float
|
||||
ayanamsa: NotRequired[str]
|
||||
node_mode: NotRequired[str]
|
||||
events: list[LifeEvent]
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ if str(SCRIPTS_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(SCRIPTS_DIR))
|
||||
|
||||
from active_rectification_selector import select_next_questions
|
||||
from ayanamsa_utils import DEFAULT_AYANAMSA_NAME
|
||||
|
||||
DEFAULT_NODE_MODE = "mean"
|
||||
|
||||
OPTIONS = [
|
||||
{"key": "A", "label": "明确有,且时间大致吻合", "score": 2},
|
||||
@@ -60,7 +63,8 @@ def _candidate_scan(
|
||||
lat: float | None = None,
|
||||
lon: float | None = None,
|
||||
tz: float | None = None,
|
||||
ayanamsa: str = "raman",
|
||||
ayanamsa: str = DEFAULT_AYANAMSA_NAME,
|
||||
node_mode: str = DEFAULT_NODE_MODE,
|
||||
) -> dict[str, Any]:
|
||||
start = center - timedelta(minutes=uncertainty_minutes)
|
||||
end = center + timedelta(minutes=uncertainty_minutes)
|
||||
@@ -84,7 +88,14 @@ def _candidate_scan(
|
||||
"cluster": cluster,
|
||||
"sensitivity_flags": _sensitivity_flags(abs(offset)),
|
||||
}
|
||||
recast = _candidate_recast(candidate, lat=lat, lon=lon, tz=tz, ayanamsa=ayanamsa)
|
||||
recast = _candidate_recast(
|
||||
candidate,
|
||||
lat=lat,
|
||||
lon=lon,
|
||||
tz=tz,
|
||||
ayanamsa=ayanamsa,
|
||||
node_mode=node_mode,
|
||||
)
|
||||
if recast:
|
||||
sample.update(recast)
|
||||
samples.append(sample)
|
||||
@@ -106,6 +117,8 @@ def _candidate_scan(
|
||||
"lat": lat,
|
||||
"lon": lon,
|
||||
"tz": tz,
|
||||
"ayanamsa": ayanamsa,
|
||||
"node_mode": node_mode,
|
||||
},
|
||||
uncertainty_minutes=uncertainty_minutes,
|
||||
step_minutes=step_minutes,
|
||||
@@ -126,6 +139,10 @@ def _candidate_scan(
|
||||
"cluster_labels": ["early_candidate_cluster", "middle_candidate_cluster", "late_candidate_cluster"],
|
||||
"samples": samples,
|
||||
"minute_scan": minute_scan,
|
||||
"calculation": {
|
||||
"ayanamsa": ayanamsa,
|
||||
"node_mode": node_mode,
|
||||
},
|
||||
"sensitivity_summary": {
|
||||
"method": "minute_feature_scan_v2",
|
||||
"high_value_layers": ["D2", "D4", "D9", "D10", "D24", "D30", "D60", "UL", "A7", "A10", "KP_cusp"],
|
||||
@@ -152,6 +169,7 @@ def _candidate_recast(
|
||||
lon: float | None,
|
||||
tz: float | None,
|
||||
ayanamsa: str,
|
||||
node_mode: str = DEFAULT_NODE_MODE,
|
||||
) -> dict[str, Any] | None:
|
||||
if lat is None or lon is None or tz is None:
|
||||
return None
|
||||
@@ -171,6 +189,7 @@ def _candidate_recast(
|
||||
"lon": lon,
|
||||
"tz": tz,
|
||||
"ayanamsa": ayanamsa,
|
||||
"node_mode": node_mode,
|
||||
})
|
||||
planet_lons = {
|
||||
name: data["lon"]
|
||||
@@ -231,7 +250,8 @@ def build_questionnaire(
|
||||
lat: float | None = None,
|
||||
lon: float | None = None,
|
||||
tz: float | None = None,
|
||||
ayanamsa: str = "raman",
|
||||
ayanamsa: str = DEFAULT_AYANAMSA_NAME,
|
||||
node_mode: str = DEFAULT_NODE_MODE,
|
||||
) -> dict[str, Any]:
|
||||
questions = []
|
||||
for qid, round_id, domain, sensitivity, window, prompt, yes_bias, no_bias in QUESTION_TEMPLATES:
|
||||
@@ -270,6 +290,7 @@ def build_questionnaire(
|
||||
lon=lon,
|
||||
tz=tz,
|
||||
ayanamsa=ayanamsa,
|
||||
node_mode=node_mode,
|
||||
)
|
||||
selection = select_next_questions(
|
||||
{"questions": questions, "candidate_scan": candidate_scan}, {}, limit=1
|
||||
@@ -279,6 +300,10 @@ def build_questionnaire(
|
||||
"schema_version": 1,
|
||||
"candidate_scan": candidate_scan,
|
||||
"selection": selection,
|
||||
"calculation": {
|
||||
"ayanamsa": ayanamsa,
|
||||
"node_mode": node_mode,
|
||||
},
|
||||
"workflow": [
|
||||
"candidate_time_scan",
|
||||
"varga_arudha_kp_sensitivity_diff",
|
||||
@@ -433,6 +458,10 @@ def score_answers(
|
||||
"truth_status": "not_birth_time_truth",
|
||||
"formula_unit_parity_status": "partial",
|
||||
"timing_claim_status": "exploratory_unvalidated",
|
||||
"calculation": questionnaire.get("calculation", {
|
||||
"ayanamsa": DEFAULT_AYANAMSA_NAME,
|
||||
"node_mode": DEFAULT_NODE_MODE,
|
||||
}),
|
||||
"answered_count": len(applied),
|
||||
"candidate_cluster_rankings": rankings,
|
||||
"technique_audit_table": [
|
||||
@@ -494,10 +523,18 @@ def main() -> int:
|
||||
parser.add_argument("--birth-time", required=True, help="Approximate local birth time, YYYY-MM-DD HH:MM")
|
||||
parser.add_argument("--uncertainty-minutes", type=int, default=30)
|
||||
parser.add_argument("--step-minutes", type=int, default=1)
|
||||
parser.add_argument("--ayanamsa", default=DEFAULT_AYANAMSA_NAME)
|
||||
parser.add_argument("--node-mode", choices=("mean", "true"), default=DEFAULT_NODE_MODE)
|
||||
parser.add_argument("--answers-json", default="", help="Optional JSON object mapping question id to A/B/C/D")
|
||||
parser.add_argument("--pretty", action="store_true")
|
||||
args = parser.parse_args()
|
||||
questionnaire = build_questionnaire(args.birth_time, args.uncertainty_minutes, args.step_minutes)
|
||||
questionnaire = build_questionnaire(
|
||||
args.birth_time,
|
||||
args.uncertainty_minutes,
|
||||
args.step_minutes,
|
||||
ayanamsa=args.ayanamsa,
|
||||
node_mode=args.node_mode,
|
||||
)
|
||||
report = score_answers(questionnaire, json.loads(args.answers_json)) if args.answers_json else questionnaire
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2 if args.pretty else None))
|
||||
return 0
|
||||
|
||||
@@ -34,6 +34,12 @@ def _engine_json(command: str, payload: dict[str, Any], *, timeout: int = 20) ->
|
||||
args = [sys.executable, str(ENGINE), command]
|
||||
for key in ("year", "month", "day", "hour", "minute", "lat", "lon", "tz"):
|
||||
args.extend([f"--{key}", str(payload[key])])
|
||||
args.extend([
|
||||
"--ayanamsa",
|
||||
str(payload.get("ayanamsa") or DEFAULT_AYANAMSA_NAME).strip().lower(),
|
||||
"--node-mode",
|
||||
str(payload.get("node_mode", payload.get("nodeMode", "mean"))).strip().lower(),
|
||||
])
|
||||
if command == "varga-full":
|
||||
args.extend(["--divisions", ",".join(_VARGAS)])
|
||||
completed = subprocess.run(args, cwd=ROOT, capture_output=True, text=True, timeout=timeout, check=True)
|
||||
@@ -148,6 +154,8 @@ def main() -> int:
|
||||
parser.add_argument(f"--{field}", required=True, type=cast)
|
||||
parser.add_argument("--uncertainty-minutes", type=int, default=30)
|
||||
parser.add_argument("--step-minutes", type=int, default=1)
|
||||
parser.add_argument("--ayanamsa", default=DEFAULT_AYANAMSA_NAME)
|
||||
parser.add_argument("--node-mode", choices=("mean", "true"), default="mean")
|
||||
args = parser.parse_args()
|
||||
print(json.dumps(scan_candidate_times(vars(args), uncertainty_minutes=args.uncertainty_minutes, step_minutes=args.step_minutes), ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
@@ -818,6 +818,19 @@ def _request_ayanamsa(*sources) -> str:
|
||||
return DEFAULT_AYANAMSA_NAME
|
||||
|
||||
|
||||
def _request_node_mode(*sources) -> str:
|
||||
for source in sources:
|
||||
if isinstance(source, dict):
|
||||
raw = source.get('node_mode', source.get('nodeMode'))
|
||||
else:
|
||||
raw = source
|
||||
if raw not in (None, ''):
|
||||
if not isinstance(raw, str) or raw.strip().lower() not in {'mean', 'true'}:
|
||||
raise BadRequest('node_mode must be mean or true')
|
||||
return raw.strip().lower()
|
||||
return 'mean'
|
||||
|
||||
|
||||
def _consultation_declared_routes(body: dict) -> set[str]:
|
||||
values: list[str] = []
|
||||
for key in ('strict_workflow_route', 'theme'):
|
||||
@@ -8792,8 +8805,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
lon = self._get_float(body, 'lon', 0, -180, 180)
|
||||
tz = self._get_float(body, 'tz', 0, -14, 14)
|
||||
ayanamsa = _request_ayanamsa(body)
|
||||
if not isinstance(ayanamsa, str):
|
||||
raise BadRequest('ayanamsa must be a string')
|
||||
node_mode = _request_node_mode(body)
|
||||
try:
|
||||
module = _load_local_module('active_rectification_questions')
|
||||
result = module.build_questionnaire(
|
||||
@@ -8804,6 +8816,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
lon=lon,
|
||||
tz=tz,
|
||||
ayanamsa=ayanamsa,
|
||||
node_mode=node_mode,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise BadRequest('birth_time must be YYYY-MM-DD HH:MM') from e
|
||||
@@ -8839,6 +8852,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
def _compute_active_rectification_events(self, body):
|
||||
allowed_fields = {
|
||||
'birth_date', 'start_time', 'end_time', 'lat', 'lon', 'tz', 'events', 'high_rigor',
|
||||
'ayanamsa', 'node_mode',
|
||||
}
|
||||
unsupported_fields = sorted(set(body) - allowed_fields)
|
||||
if unsupported_fields:
|
||||
@@ -8864,6 +8878,8 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
lat = self._get_float(body, 'lat', 0, -90, 90)
|
||||
lon = self._get_float(body, 'lon', 0, -180, 180)
|
||||
tz = self._get_float(body, 'tz', 0, -14, 14)
|
||||
ayanamsa = _request_ayanamsa(body)
|
||||
node_mode = _request_node_mode(body)
|
||||
events = body.get('events')
|
||||
if not isinstance(events, list) or len(events) < MIN_SCORING_EVENTS:
|
||||
raise BadRequest(f'events must contain at least {MIN_SCORING_EVENTS} item')
|
||||
@@ -8911,6 +8927,8 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
'tz': tz,
|
||||
'ayanamsa': ayanamsa,
|
||||
'node_mode': node_mode,
|
||||
'events': normalized_events,
|
||||
})
|
||||
external_validation = {
|
||||
@@ -8935,6 +8953,8 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
'tz': tz,
|
||||
'ayanamsa': ayanamsa,
|
||||
'node_mode': node_mode,
|
||||
}
|
||||
three_engine_packet = build_packet(engine_case)
|
||||
vedastro_events = [{
|
||||
|
||||
@@ -6,6 +6,8 @@ import uuid
|
||||
from datetime import date
|
||||
from typing import Any, Literal, NotRequired, TypedDict, cast
|
||||
|
||||
from scripts.ayanamsa_utils import UnsupportedAyanamsaError, normalize_ayanamsa_name
|
||||
|
||||
DatePrecision = Literal["day", "month", "quarter", "year", "range"]
|
||||
|
||||
EVENT_CONTRACT_VERSION = "rectification-event-contract-v2"
|
||||
@@ -48,7 +50,10 @@ _EVENT_PROVENANCE_FIELDS = frozenset({
|
||||
"date_source", "date_reliability", "date_corroboration", "date_conflict_status",
|
||||
"source_turn_id", "subject",
|
||||
})
|
||||
_REQUEST_FIELDS = frozenset({"birth_date", "start_time", "end_time", "lat", "lon", "tz", "events"}) | _REQUEST_PROVENANCE_FIELDS
|
||||
_REQUEST_FIELDS = frozenset({
|
||||
"birth_date", "start_time", "end_time", "lat", "lon", "tz", "events",
|
||||
"ayanamsa", "node_mode",
|
||||
}) | _REQUEST_PROVENANCE_FIELDS
|
||||
_EVENT_FIELDS = frozenset({"id", "domain", "event_kind", "date_start", "date_end", "precision", "summary"}) | _EVENT_PROVENANCE_FIELDS
|
||||
_CLOCK = re.compile(r"(?:[01]\d|2[0-3]):[0-5]\d\Z")
|
||||
|
||||
@@ -77,6 +82,8 @@ class RectificationRequest(TypedDict):
|
||||
lon: float
|
||||
tz: float
|
||||
events: list[LifeEvent]
|
||||
ayanamsa: NotRequired[str]
|
||||
node_mode: NotRequired[str]
|
||||
birth_time_source: NotRequired[str | None]
|
||||
timezone_id: NotRequired[str | None]
|
||||
timezone_source: NotRequired[str | None]
|
||||
@@ -227,6 +234,16 @@ def normalize_rectification_request(body: Any, *, today: date | None = None) ->
|
||||
"tz": _bounded_number(body, "tz", -14, 14),
|
||||
"events": cleaned_events,
|
||||
}
|
||||
if "ayanamsa" in body:
|
||||
try:
|
||||
cleaned_request["ayanamsa"] = normalize_ayanamsa_name(body.get("ayanamsa"))
|
||||
except UnsupportedAyanamsaError as exc:
|
||||
raise ValueError(str(exc)) from exc
|
||||
if "node_mode" in body:
|
||||
node_mode = body.get("node_mode")
|
||||
if not isinstance(node_mode, str) or node_mode.strip().lower() not in {"mean", "true"}:
|
||||
raise ValueError("node_mode must be mean or true")
|
||||
cleaned_request["node_mode"] = node_mode.strip().lower()
|
||||
_copy_nullable_text(body, cleaned_request, "birth_time_source", "birth_time_source", 120, _BIRTH_TIME_SOURCES)
|
||||
_copy_nullable_text(body, cleaned_request, "timezone_id", "timezone_id", 120)
|
||||
_copy_nullable_text(body, cleaned_request, "timezone_source", "timezone_source", 80)
|
||||
|
||||
@@ -103,7 +103,7 @@ def sample_event_dates(event: LifeEvent) -> list[str]:
|
||||
|
||||
def _legacy_request(request: RectificationRequest, event: LifeEvent, sampled_date: str) -> dict[str, Any]:
|
||||
engine_domain, _ = _ENGINE_KIND_BY_NATIVE_KIND[event["event_kind"]]
|
||||
return {
|
||||
legacy_request = {
|
||||
"birth_date": request["birth_date"],
|
||||
"start_time": request["start_time"],
|
||||
"end_time": request["end_time"],
|
||||
@@ -116,6 +116,10 @@ def _legacy_request(request: RectificationRequest, event: LifeEvent, sampled_dat
|
||||
"date": sampled_date, "precision": "day", "summary": event.get("summary", ""),
|
||||
}],
|
||||
}
|
||||
for key in ("ayanamsa", "node_mode"):
|
||||
if key in request:
|
||||
legacy_request[key] = request[key]
|
||||
return legacy_request
|
||||
|
||||
|
||||
def _canonical(value: Any) -> str:
|
||||
@@ -349,7 +353,9 @@ def calculation_spec(request: RectificationRequest) -> dict[str, Any]:
|
||||
"latitude": json_number(request["lat"]),
|
||||
"longitude": json_number(request["lon"]),
|
||||
"timezoneOffsetHours": json_number(request["tz"]),
|
||||
"ayanamsa": "raman", "nodeMode": "mean", "minuteStep": 1,
|
||||
"ayanamsa": request.get("ayanamsa", "raman"),
|
||||
"nodeMode": request.get("node_mode", "mean"),
|
||||
"minuteStep": 1,
|
||||
}
|
||||
for source, target in (
|
||||
("birth_time_source", "birthTimeSource"),
|
||||
|
||||
Reference in New Issue
Block a user