fix(rectification): anchor candidate windows to civil dates across midnight
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:
@@ -156,6 +156,11 @@ def indistinguishable_width_minutes(candidates: Sequence[dict[str, Any]]) -> int
|
||||
return 0
|
||||
ranked = sorted(candidates, key=lambda row: int(row.get("rank") or 0))
|
||||
top = ranked[0]
|
||||
if all(row.get("cluster_intervals") for row in ranked):
|
||||
from scripts.rectification.candidate_window import interval_union_width
|
||||
return max(int(top.get("tied_minute_count") or 1), interval_union_width([
|
||||
interval for row in ranked for interval in row["cluster_intervals"]
|
||||
]), 1)
|
||||
starts: list[int] = []
|
||||
ends: list[int] = []
|
||||
for row in ranked:
|
||||
@@ -428,7 +433,12 @@ def build_candidate_decisions(
|
||||
for other in all_scores
|
||||
)
|
||||
cluster_times, cluster_start, cluster_end = _cluster_span(row)
|
||||
from scripts.rectification.candidate_window import intervals_from_positions
|
||||
position = {key: row[key] for key in ("candidate_date", "window_index", "window_offset_minutes", "segment_index") if key in row}
|
||||
coverage = intervals_from_positions(row.get("cluster_positions", []))
|
||||
decisions.append({
|
||||
**position,
|
||||
**({"cluster_intervals": coverage} if coverage else {}),
|
||||
"candidate_id": str(uuid5(NAMESPACE_URL, f"{POLICY_VERSION}:{result_id}:{row['time']}")),
|
||||
"rank": index + 1,
|
||||
"time": row["time"],
|
||||
@@ -455,7 +465,8 @@ def _cluster_span(row: dict[str, Any]) -> tuple[list[str], str, str]:
|
||||
if not times:
|
||||
clock = str(row.get("time") or "")[:5]
|
||||
times = [clock] if len(clock) == 5 and clock[2] == ":" else []
|
||||
times.sort(key=lambda value: int(value[:2]) * 60 + int(value[3:5]))
|
||||
positions = {item["time"]: item["window_index"] for item in row.get("cluster_positions", [])}
|
||||
times.sort(key=lambda value: positions.get(value, int(value[:2]) * 60 + int(value[3:5])))
|
||||
start = times[0] if times else str(row.get("time") or "")[:5]
|
||||
end = times[-1] if times else start
|
||||
return times, start, end
|
||||
|
||||
Reference in New Issue
Block a user