交付卡、旁白和遥测在有簇区间时按段书写。打分、出题和进区间门槛未改;对半出题和门槛 6 因为没有真值保留回放而没有交付。 Co-Authored-By: Grok (agent loop)
80 lines
2.7 KiB
Python
80 lines
2.7 KiB
Python
"""Display-only merge of living candidate intervals.
|
||
|
||
Decision width stays on ``interval_union_width`` / ``unionWindowIntervals``.
|
||
This module is not part of the sealed scoring identity: scoring must not import it.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
from typing import Any
|
||
|
||
LIVING_DISPLAY_MERGE_GAP_MINUTES = 4
|
||
LIVING_DISPLAY_MAX_SEGMENTS = 3
|
||
|
||
|
||
def _stamp(value: str) -> datetime:
|
||
return datetime.fromisoformat(value)
|
||
|
||
|
||
def merge_living_display_intervals(
|
||
intervals: list[dict[str, Any]],
|
||
gap_minutes: int = LIVING_DISPLAY_MERGE_GAP_MINUTES,
|
||
) -> list[dict[str, str]]:
|
||
"""Merge intervals whose gap is <= gap_minutes. Gap is the open minutes between them."""
|
||
rows = []
|
||
for row in intervals:
|
||
start, end = str(row.get("start_at") or ""), str(row.get("end_at") or "")
|
||
if len(start) < 16 or len(end) < 16 or start > end:
|
||
continue
|
||
try:
|
||
_stamp(start)
|
||
_stamp(end)
|
||
except ValueError:
|
||
continue
|
||
rows.append({"start_at": start[:16], "end_at": end[:16]})
|
||
rows.sort(key=lambda item: (item["start_at"], item["end_at"]))
|
||
merged: list[dict[str, str]] = []
|
||
for row in rows:
|
||
if not merged:
|
||
merged.append(row)
|
||
continue
|
||
previous = merged[-1]
|
||
gap = int((_stamp(row["start_at"]) - _stamp(previous["end_at"])).total_seconds() // 60) - 1
|
||
if gap <= gap_minutes:
|
||
if row["end_at"] > previous["end_at"]:
|
||
previous["end_at"] = row["end_at"]
|
||
else:
|
||
merged.append(row)
|
||
return merged
|
||
|
||
|
||
def inclusive_interval_minutes(intervals: list[dict[str, str]]) -> int:
|
||
total = 0
|
||
for row in intervals:
|
||
width = int((_stamp(row["end_at"]) - _stamp(row["start_at"])).total_seconds() // 60) + 1
|
||
if width > 0:
|
||
total += width
|
||
return total
|
||
|
||
|
||
def living_display_label(
|
||
intervals: list[dict[str, str]],
|
||
birth_date: str | None = None,
|
||
max_segments: int = LIVING_DISPLAY_MAX_SEGMENTS,
|
||
) -> str:
|
||
def label(value: str) -> str:
|
||
date, clock = value[:10], value[11:16]
|
||
if not birth_date:
|
||
return f"{date} {clock}"
|
||
if date == birth_date:
|
||
return clock
|
||
return f"{'前一天' if date < birth_date else '后一天'} {clock}"
|
||
|
||
def text(row: dict[str, str]) -> str:
|
||
return f"{label(row['start_at'])}–{label(row['end_at'])}"
|
||
|
||
if len(intervals) <= max_segments:
|
||
return "、".join(text(row) for row in intervals)
|
||
shown = "、".join(text(row) for row in intervals[:max_segments])
|
||
return f"{shown},等 {len(intervals)} 段,共约 {inclusive_interval_minutes(intervals)} 分钟"
|