fix(rectification): keep hour-window tail clusters and lock the search window (BUG-623, BUG-624, BUG-625)
Independent Staging Quality Gate / validate (push) Successful in 17m15s
Independent Staging Quality Gate / publish (push) Successful in 2m15s

Hour windows no longer drop later signature clusters. Credible range uses cluster coverage, and a mid-session spoken birth window gets a fixed reply without calling the model.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-09 20:45:11 +08:00
co-authored by Cursor
parent a31a5e2426
commit 6008c07c81
41 changed files with 1164 additions and 44 deletions
+127 -2
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
import unittest
from datetime import date
from datetime import date, datetime
from unittest.mock import patch
from uuid import UUID
@@ -13,7 +13,12 @@ from scripts.jyotish_api_server import (
JyotishAPIHandler,
)
from scripts.rectification.api_service import diagnostics, score_candidates
from scripts.rectification.candidate_contrast import (
MAX_PUBLIC_CLUSTERS,
select_signature_representatives,
)
from scripts.rectification.contracts import normalize_rectification_request
from scripts.rectification.decision_policy import build_candidate_decisions
from scripts.rectification.scoring_service import (
build_event_contribution_matrix,
calculation_spec,
@@ -472,7 +477,16 @@ class RectificationV5ServicesTest(unittest.TestCase):
self.assertEqual(first["candidate_decisions"], second["candidate_decisions"])
self.assertEqual(sum(item["relative_support"] for item in first["candidate_decisions"]), 100)
for rank, candidate in enumerate(first["candidate_decisions"], start=1):
self.assertEqual(set(candidate), {"candidate_id", "rank", "time", "relative_support", "tied_minute_count"})
self.assertEqual(set(candidate), {
"candidate_id",
"rank",
"time",
"relative_support",
"tied_minute_count",
"cluster_times",
"cluster_start",
"cluster_end",
})
self.assertEqual(candidate["rank"], rank)
UUID(candidate["candidate_id"])
@@ -1102,6 +1116,117 @@ class RectificationV5ServicesTest(unittest.TestCase):
self.assertEqual(set(windows), set(times))
self.assertLess(packet["column_compare_ms"], 1000)
def test_hour_window_keeps_all_seventeen_signature_clusters_including_late_tail(self):
rows, contexts = _hour_window_signature_fixture()
public = select_signature_representatives(rows, contexts)
times = {row["time"] for row in public}
self.assertEqual(MAX_PUBLIC_CLUSTERS, 64)
self.assertEqual(len(public), 17)
self.assertTrue(times & {"14:46", "14:47", "14:48", "14:49", "14:50", "14:51"})
self.assertIn("15:00", {time for row in rows for time in [row["time"]]})
decisions = build_candidate_decisions(
rows,
result_id="00000000-0000-4000-8000-000000000623",
static_contexts=contexts,
)
self.assertEqual(len(decisions), 17)
late = next(item for item in decisions if item["time"] in {"14:46", "14:47", "14:48", "14:49", "14:50", "14:51"})
self.assertEqual(late["cluster_start"], "14:46")
self.assertEqual(late["cluster_end"], "14:51")
self.assertEqual(late["cluster_times"][0], "14:46")
self.assertEqual(late["cluster_times"][-1], "14:51")
def test_cluster_coverage_not_representative_minute_sets_credible_span(self):
times = _clock_span("14:40", "14:45")
rows = [
{"time": time, "score": 20 if time == "14:40" else 11, "evidence": [], "missing_layers": []}
for time in times
]
contexts = [_signature_context(time, 3) for time in times]
decisions = build_candidate_decisions(
rows,
result_id="00000000-0000-4000-8000-000000000624",
static_contexts=contexts,
)
self.assertEqual(len(decisions), 1)
self.assertEqual(decisions[0]["time"], "14:40")
self.assertEqual(decisions[0]["cluster_start"], "14:40")
self.assertEqual(decisions[0]["cluster_end"], "14:45")
self.assertEqual(decisions[0]["cluster_times"], times)
def test_adjacent_low_score_merge_keeps_the_window_tail(self):
rows = []
contexts = []
for index in range(65):
time = f"{index // 60:02d}:{index % 60:02d}"
score = 1 if index >= 63 else 20
rows.append({"time": time, "score": score, "evidence": [], "missing_layers": []})
contexts.append(_signature_context(time, index))
public = select_signature_representatives(rows, contexts)
self.assertEqual(len(public), 64)
covered = {time for row in public for time in row.get("cluster_times", [row["time"]])}
self.assertIn("01:04", covered)
def _clock_span(start: str, end: str) -> list[str]:
start_minutes = int(start[:2]) * 60 + int(start[3:5])
end_minutes = int(end[:2]) * 60 + int(end[3:5])
return [f"{minute // 60:02d}:{minute % 60:02d}" for minute in range(start_minutes, end_minutes + 1)]
def _signature_context(time: str, signature_index: int) -> dict:
hour, minute = (int(part) for part in time.split(":"))
return {
"candidate_at": datetime(1997, 8, 8, hour, minute),
"ascendant_index": signature_index % 12,
"planet_longitudes": {"Moon": 13.0 * (signature_index % 27)},
"feature": {
"time": time,
"ascendant_sign_index": signature_index % 12,
"moon_degree": 13.0 * (signature_index % 27),
"varga_ascendants": {
"D4": (signature_index // 12) % 12,
"D9": (signature_index // 3) % 12,
"D10": (signature_index // 5) % 12,
"D24": (signature_index // 7) % 12,
"D12": (signature_index // 11) % 12,
},
},
}
def _hour_window_signature_fixture() -> tuple[list[dict], list[dict]]:
ranges = [
("14:00", "14:03"),
("14:04", "14:06"),
("14:07", "14:09"),
("14:10", "14:12"),
("14:13", "14:15"),
("14:16", "14:18"),
("14:19", "14:21"),
("14:22", "14:24"),
("14:25", "14:28"),
("14:29", "14:32"),
("14:33", "14:36"),
("14:37", "14:39"),
("14:40", "14:45"),
("14:46", "14:51"),
("14:52", "14:53"),
("14:54", "14:57"),
("14:58", "15:00"),
]
rows: list[dict] = []
contexts: list[dict] = []
for index, (start, end) in enumerate(ranges):
times = _clock_span(start, end)
for offset, time in enumerate(times):
score = 30 + index if offset == 0 else 10 + index
if start == "14:46":
score = 90 if offset == 0 else 80
rows.append({"time": time, "score": score, "evidence": [], "missing_layers": []})
contexts.append(_signature_context(time, index))
return rows, contexts
if __name__ == "__main__":
unittest.main()