feat(rectification): pass ayanamsa per request
This commit is contained in:
@@ -36,5 +36,6 @@ export async function payloadFromProfile(profile: BirthRectificationProfile) {
|
||||
lon,
|
||||
tz,
|
||||
ayanamsa: resolveAyanamsa(resolved),
|
||||
node_mode: "mean",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from "./birth-time-journey-dynamic-state.ts";
|
||||
import { BirthTimeJourneyStoreError } from "./birth-time-journey-store-errors.ts";
|
||||
import { resolveMissingBirthTimezoneOffset } from "./birth-profile-timezone.ts";
|
||||
import { resolveAyanamsa } from "./ayanamsa.ts";
|
||||
|
||||
type JourneyLoadResult = { readonly data: unknown; readonly error: unknown };
|
||||
type JourneyLoadQuery = {
|
||||
@@ -141,7 +142,7 @@ export async function loadStoredRectificationCase(
|
||||
const parsed = storedCaseSchema.parse(data);
|
||||
const { data: profile, error: profileError } = await client
|
||||
.from("profiles")
|
||||
.select("birth_date,reported_birth_time,birth_time_period,latitude,longitude,timezone_id,timezone_offset")
|
||||
.select("birth_date,reported_birth_time,birth_time_period,latitude,longitude,timezone_id,timezone_offset,ayanamsa")
|
||||
.eq("id", userId)
|
||||
.maybeSingle();
|
||||
if (profileError || !profile) throw new BirthTimeJourneyStoreError("load_case");
|
||||
@@ -200,6 +201,7 @@ export async function loadStoredRectificationCase(
|
||||
lat: location.latitude,
|
||||
lon: location.longitude,
|
||||
tz: location.timezone_offset,
|
||||
ayanamsa: resolveAyanamsa(resolvedProfile as { ayanamsa?: unknown }),
|
||||
},
|
||||
lifeEvents: parsed.life_events,
|
||||
candidateResult,
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
DynamicChoiceScoreInput,
|
||||
JourneyEventScoreInput,
|
||||
} from "./birth-time-journey-service.ts";
|
||||
import { resolveAyanamsa } from "./ayanamsa.ts";
|
||||
|
||||
export class BirthTimeJourneyEngineError extends Error {
|
||||
readonly name = "BirthTimeJourneyEngineError";
|
||||
@@ -87,6 +88,8 @@ export function eventScorePayload(input: JourneyEventScoreInput) {
|
||||
lat: input.lat,
|
||||
lon: input.lon,
|
||||
tz: input.tz,
|
||||
ayanamsa: resolveAyanamsa(input),
|
||||
node_mode: "mean",
|
||||
high_rigor: true,
|
||||
events: (input.events ?? []).map((event) => ({
|
||||
id: event.id,
|
||||
@@ -163,6 +166,7 @@ export function createJourneyEngineMethods(wire: JourneyEngineWire): BirthTimeJo
|
||||
lon: input.lon,
|
||||
tz: input.tz,
|
||||
ayanamsa: input.ayanamsa,
|
||||
node_mode: "mean",
|
||||
},
|
||||
});
|
||||
return { questionnaire: parseRectificationQuestionnaire(payload) };
|
||||
|
||||
@@ -70,6 +70,7 @@ export type JourneyEventScoreInput = {
|
||||
readonly lat: number;
|
||||
readonly lon: number;
|
||||
readonly tz: number;
|
||||
readonly ayanamsa?: AyanamsaName;
|
||||
readonly events: readonly LifeEvent[];
|
||||
};
|
||||
|
||||
@@ -123,6 +124,7 @@ type StoredRectificationCaseBase = {
|
||||
readonly lat: number;
|
||||
readonly lon: number;
|
||||
readonly tz: number;
|
||||
readonly ayanamsa?: AyanamsaName;
|
||||
};
|
||||
readonly lifeEvents?: readonly LifeEvent[];
|
||||
readonly candidateResult?: CandidateResult | null;
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
AgenticRectificationCandidateResult,
|
||||
AgenticRectificationContext,
|
||||
} from "@/mastra/rectification-tools";
|
||||
import { resolveAyanamsa, type AyanamsaName } from "../ayanamsa.ts";
|
||||
import { normalizePersistedBirthDate } from "../birth-time-intake-model.ts";
|
||||
import { declaredClockRange } from "../declared-birth-window.ts";
|
||||
|
||||
@@ -26,6 +27,7 @@ export type AgenticRectificationProfile = Readonly<{
|
||||
lat: number;
|
||||
lon: number;
|
||||
tz: number;
|
||||
ayanamsa: AyanamsaName;
|
||||
declaredAccuracy: AgenticRectificationContext["declaredAccuracy"];
|
||||
timeSource: AgenticRectificationContext["timeSource"];
|
||||
baselineReportedTime: string | null;
|
||||
@@ -166,7 +168,7 @@ export async function loadAgenticRectificationProfile(
|
||||
): Promise<AgenticRectificationProfile> {
|
||||
const { data, error } = await accounting
|
||||
.from("profiles")
|
||||
.select("birth_date,reported_birth_time,active_birth_time,birth_time_source,birth_time_period,declared_window_start,declared_window_end,uncertainty_before_minutes,uncertainty_after_minutes,latitude,longitude,timezone_offset")
|
||||
.select("birth_date,reported_birth_time,active_birth_time,birth_time_source,birth_time_period,declared_window_start,declared_window_end,uncertainty_before_minutes,uncertainty_after_minutes,latitude,longitude,timezone_offset,ayanamsa")
|
||||
.eq("id", userId)
|
||||
.single();
|
||||
if (error || !data) throw new AgenticRectificationProfileError("profile_unavailable");
|
||||
@@ -199,6 +201,7 @@ export async function loadAgenticRectificationProfile(
|
||||
lat,
|
||||
lon,
|
||||
tz,
|
||||
ayanamsa: resolveAyanamsa(data),
|
||||
declaredAccuracy: declaredAccuracyFrom(uncertaintyBefore, uncertaintyAfter, rawSource),
|
||||
timeSource: timeSourceFrom(rawSource),
|
||||
baselineReportedTime: reportedTime,
|
||||
@@ -284,6 +287,7 @@ export function createAgenticRectificationContext(
|
||||
lat: profile.lat,
|
||||
lon: profile.lon,
|
||||
tz: profile.tz,
|
||||
ayanamsa: profile.ayanamsa,
|
||||
},
|
||||
candidateRange: profile.candidateRange,
|
||||
declaredAccuracy: profile.declaredAccuracy,
|
||||
|
||||
@@ -477,6 +477,7 @@ function engineRequestBody(input: {
|
||||
lon,
|
||||
tz,
|
||||
ayanamsa: resolveAyanamsa(snapshot),
|
||||
node_mode: "mean",
|
||||
events: input.events,
|
||||
birth_time_source: snapshot.birth_time_source,
|
||||
timezone_id: snapshot.timezone_id,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createTool } from "@mastra/core/tools";
|
||||
import { createHash } from "node:crypto";
|
||||
import { z } from "zod";
|
||||
import { resolveAyanamsa, type AyanamsaName } from "../lib/ayanamsa.ts";
|
||||
import { dateRangeFromDeclared } from "../lib/rectification-agentic/date-range.ts";
|
||||
|
||||
/**
|
||||
@@ -27,6 +28,7 @@ export type AgenticRectificationBirth = Readonly<{
|
||||
lat: number;
|
||||
lon: number;
|
||||
tz: number;
|
||||
ayanamsa?: AyanamsaName | string | null;
|
||||
}>;
|
||||
|
||||
export type AgenticRectificationCandidateRange = Readonly<{
|
||||
@@ -250,6 +252,8 @@ function v5Request(
|
||||
lat: ctx.birth.lat,
|
||||
lon: ctx.birth.lon,
|
||||
tz: ctx.birth.tz,
|
||||
ayanamsa: resolveAyanamsa(ctx.birth),
|
||||
node_mode: "mean",
|
||||
events: events.map(toV5Event),
|
||||
};
|
||||
}
|
||||
@@ -425,6 +429,8 @@ export function createAgenticRectificationTools(ctx: AgenticRectificationContext
|
||||
lat: ctx.birth.lat,
|
||||
lon: ctx.birth.lon,
|
||||
tz: ctx.birth.tz,
|
||||
ayanamsa: resolveAyanamsa(ctx.birth),
|
||||
node_mode: "mean",
|
||||
time_uncertainty_minutes: scanWindow.uncertaintyMinutes,
|
||||
step_minutes: input.step_minutes,
|
||||
};
|
||||
@@ -533,6 +539,8 @@ export function createAgenticRectificationTools(ctx: AgenticRectificationContext
|
||||
lat: ctx.birth.lat,
|
||||
lon: ctx.birth.lon,
|
||||
tz: ctx.birth.tz,
|
||||
ayanamsa: resolveAyanamsa(ctx.birth),
|
||||
node_mode: "mean",
|
||||
events: [],
|
||||
});
|
||||
const snapshot = (data.candidate_feature_snapshot && typeof data.candidate_feature_snapshot === "object")
|
||||
@@ -568,6 +576,8 @@ export function createAgenticRectificationTools(ctx: AgenticRectificationContext
|
||||
lat: ctx.birth.lat,
|
||||
lon: ctx.birth.lon,
|
||||
tz: ctx.birth.tz,
|
||||
ayanamsa: resolveAyanamsa(ctx.birth),
|
||||
node_mode: "mean",
|
||||
events: input.events.map(toV3Event),
|
||||
high_rigor: true,
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@ test("journey engine serializes only stored event-scoring inputs", () => {
|
||||
lat: 31.2304,
|
||||
lon: 121.4737,
|
||||
tz: 8,
|
||||
ayanamsa: "lahiri",
|
||||
events: [
|
||||
{ id: "5cb071d6-6d99-46be-85dc-a9bf59ef6ac5", domain: "career", date: "2019-07", precision: "month", summary: "晋升为团队负责人" },
|
||||
{ id: "0790866c-ad5e-4a45-b2b4-a5c73f6be6ea", domain: "education", date: "2011", precision: "year" },
|
||||
@@ -33,6 +34,8 @@ test("journey engine serializes only stored event-scoring inputs", () => {
|
||||
lat: 31.2304,
|
||||
lon: 121.4737,
|
||||
tz: 8,
|
||||
ayanamsa: "lahiri",
|
||||
node_mode: "mean",
|
||||
high_rigor: true,
|
||||
events: [
|
||||
{ id: "5cb071d6-6d99-46be-85dc-a9bf59ef6ac5", domain: "career", date: "2019-07", precision: "month", summary: "晋升为团队负责人" },
|
||||
|
||||
@@ -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