fix(rectification): honor declared birth-time uncertainty and split windows over two hours (BUG-571–573)

Intake stores how sure the user is; rectification now searches that range, offers a one-click widen when event fit is low at the edge, and trisects windows longer than two hours before the minute grid.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-07 12:17:04 +08:00
parent b2a8d3a005
commit 8e31680b45
49 changed files with 3047 additions and 142 deletions
+49 -7
View File
@@ -65,6 +65,38 @@ def _report_candidate_range(
}
def _window_span_minutes(start_time: str, end_time: str) -> int:
start = _clock_minutes(start_time)
end = _clock_minutes(end_time)
return end - start if end >= start else 1440 - start + end
def _adaptive_minute_step(start_time: str, end_time: str) -> int:
width = _window_span_minutes(start_time, end_time)
if width > 360:
return 10
if width > 180:
return 5
return 2
def _block_scan_periods(request: RectificationRequest) -> list[tuple[str, str, str]]:
custom = request.get("blocks")
if isinstance(custom, list) and custom:
periods: list[tuple[str, str, str]] = []
for item in custom:
if not isinstance(item, dict):
continue
label = str(item.get("period") or item.get("label") or "")
start_time = str(item.get("start_time") or "")[:5]
end_time = str(item.get("end_time") or "")[:5]
if label and start_time and end_time:
periods.append((label, start_time, end_time))
if periods:
return periods
return list(BLOCK_SCAN_PERIODS)
def _report_evidence(
request: RectificationRequest,
built: dict[str, Any],
@@ -402,9 +434,12 @@ def _clock_in_declared_period(clock: str, start_time: str, end_time: str) -> boo
def _normalize_relative_support(raw: Sequence[float]) -> list[float]:
floored = [max(0.0, float(value)) for value in raw]
total = sum(floored)
if not floored:
return []
if total <= 0:
return [20.0 for _ in floored]
shares = [round(100.0 * value / total, 1) for value in floored]
shares = [round(100.0 / len(floored), 1) for _ in floored]
else:
shares = [round(100.0 * value / total, 1) for value in floored]
delta = round(100.0 - sum(shares), 1)
if shares:
shares[shares.index(max(shares))] = round(shares[shares.index(max(shares))] + delta, 1)
@@ -412,10 +447,16 @@ def _normalize_relative_support(raw: Sequence[float]) -> list[float]:
def block_scan(request: RectificationRequest) -> dict[str, Any]:
"""Aggregate 24h event scores into the five declared birth-time periods."""
step = int(request.get("minute_step") or 10)
if step <= 1:
step = 10
"""Aggregate event scores into declared periods or caller-supplied sub-blocks."""
requested_step = request.get("minute_step")
if isinstance(requested_step, int) and requested_step > 1:
step = requested_step
else:
step = _adaptive_minute_step(request["start_time"], request["end_time"])
if not request.get("blocks"):
step = int(request.get("minute_step") or 10)
if step <= 1:
step = 10
scoring_request = {**request, "minute_step": step}
scored = score_candidates(scoring_request)
events_by_id = {
@@ -429,9 +470,10 @@ def block_scan(request: RectificationRequest) -> dict[str, Any]:
]
day_scores = [float(row.get("score") or 0) for row in rows]
min_day = min(day_scores) if day_scores else 0.0
periods = _block_scan_periods(request)
raw_support: list[float] = []
blocks: list[dict[str, Any]] = []
for period, start_time, end_time in BLOCK_SCAN_PERIODS:
for period, start_time, end_time in periods:
members = [
row for row in rows
if _clock_in_declared_period(str(row.get("time") or "")[:5], start_time, end_time)