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
+22 -2
View File
@@ -88,9 +88,21 @@ def build_flexible_birth_time_profile_from_window(
ayanamsa: str = "raman",
node_mode: str = "mean",
source_reference: Mapping[str, Any],
candidate_intervals: list[dict[str, str]] | None = None,
) -> dict[str, Any]:
start, end = _parse_window(birth_date, start_time, end_time)
normalized_times = _normalize_candidate_times(birth_date, start, end, candidate_times)
if candidate_intervals is None:
start, end = _parse_window(birth_date, start_time, end_time)
normalized_times = _normalize_candidate_times(birth_date, start, end, candidate_times)
else:
from scripts.rectification.candidate_window import enumerate_candidate_window
moments = enumerate_candidate_window({"birth_date": birth_date, "start_time": start_time,
"end_time": end_time, "candidate_intervals": candidate_intervals})
by_clock = {value.strftime("%H:%M"): value for value in moments}
if not 2 <= len(candidate_times) <= MAX_CANDIDATE_MINUTES or len(set(candidate_times)) != len(candidate_times):
raise FlexibleBirthTimeProfileError("candidate_count_must_be_two_to_thirty_one")
if any(value not in by_clock for value in candidate_times):
raise FlexibleBirthTimeProfileError("candidate_time_outside_window")
normalized_times = sorted(by_clock[value] for value in candidate_times)
candidates = [
_candidate_from_recast(
candidate_at=value,
@@ -103,6 +115,14 @@ def build_flexible_birth_time_profile_from_window(
for value in normalized_times
]
profile = build_flexible_birth_time_profile(candidates, source_reference=source_reference)
if candidate_intervals is not None:
# Keep civil chronology and declared gaps instead of the legacy clock-sorted envelope.
clocks = [value.strftime("%H:%M") for value in normalized_times]
profile["birth_time_window"].update({"start_time": start_time, "end_time": end_time,
"candidate_times": clocks, "candidate_intervals": deepcopy(candidate_intervals),
"candidate_datetimes": [value.isoformat(timespec="minutes") for value in normalized_times]})
refs = {row["candidate_time"]: row for row in profile["candidate_references"]}
profile["candidate_references"] = [{**refs[value.strftime("%H:%M")], "candidate_date": value.date().isoformat()} for value in normalized_times]
profile["calculation_profile"] = {
"ayanamsa": ayanamsa,
"node_mode": node_mode,