fix(rectification): anchor candidate windows to civil dates across midnight
Independent Staging Quality Gate / validate (push) Successful in 13m27s
Independent Staging Quality Gate / publish (push) Failing after 1h0m1s

Carry explicit local date intervals instead of inferring the day from clock
order. Cluster width, delivery, adoption, and reports keep the actual civil
date; adopted date is stored separately from the reported birth_date.

Algorithm identity is scoring-9 / spec-v5. Scoring weights, confirmation
thresholds, and Skill version are unchanged. Isolated Linux final-3 gates
passed; four pre-existing Python failures remain. This is not a production
release.
This commit is contained in:
jesse-ux
2026-09-21 02:55:00 +08:00
parent 3be740f84d
commit b85c4a686a
115 changed files with 106484 additions and 315 deletions
+14 -14
View File
@@ -88,19 +88,12 @@ def _event_datetime(event: LifeEvent) -> datetime:
def _candidate_datetimes(request: RectificationEventRequest) -> list[datetime]:
birth_date = date.fromisoformat(request["birth_date"])
start = datetime.combine(birth_date, time.fromisoformat(request["start_time"]))
end = datetime.combine(birth_date, time.fromisoformat(request["end_time"]))
if end < start:
end += timedelta(days=1)
minute_count = int((end - start).total_seconds() // 60) + 1
if minute_count < 1 or minute_count > 1_440:
raise RectificationEventCalculationError("candidate_range_out_of_bounds")
raw_step = request.get("minute_step") if isinstance(request, dict) else None
step = int(raw_step or 1)
if step < 1 or step > 15:
raise RectificationEventCalculationError("minute_step_out_of_bounds")
return [start + timedelta(minutes=offset) for offset in range(0, minute_count, step)]
from scripts.rectification.candidate_window import enumerate_candidate_window
try:
return enumerate_candidate_window(request)
except ValueError as exc:
raise RectificationEventCalculationError(str(exc)) from exc
def _active_vimshottari(
@@ -611,7 +604,11 @@ def compute_candidate_static_contexts(
candidates: Sequence[datetime] | None = None,
) -> list[dict[str, Any]]:
candidate_datetimes = list(candidates) if candidates is not None else _candidate_datetimes(request)
return [build_candidate_static_context(request, candidate) for candidate in candidate_datetimes]
from scripts.rectification.candidate_window import candidate_positions
positions = candidate_positions(request, candidate_datetimes)
return [{**build_candidate_static_context(request, candidate), **position}
for candidate, position in zip(candidate_datetimes, positions)]
def _candidate_row(
@@ -737,6 +734,9 @@ def _canonical_input_contract(request: RectificationEventRequest) -> tuple[dict,
"ephemeris_source": "swisseph_calc_ut",
},
}
if "candidate_intervals" in request:
payload["schema_version"] = "rectification-candidate-input-v3"
payload["candidate_intervals"] = request["candidate_intervals"]
normalized = json.dumps(payload, ensure_ascii=True, sort_keys=True, separators=(",", ":"))
return payload, hashlib.sha256(normalized.encode("utf-8")).hexdigest()