feat: add 24-Hour (full-day) session period type
Sessions could only be tagged day or night (weekday/weekend). 24/7 continuous
jobs had no fitting period type. Add "24-Hour" (full_24h) — a single full-day
period covering day + night.
UI (session_list.html):
- Full-width "24-Hour" button under the WD/WE x Day/Night grid; teal badge.
- Selecting it clears + disables the hour inputs (no window); reopening an
existing 24-Hour session opens with hours disabled. Badge current-period
kept in sync after save.
Backend (projects.py):
- full_24h added to VALID_PERIOD_TYPES and the session-label maps
("... - 24-Hour"). Operator-set only; never auto-derived.
- Combined report: include ALL rows for a 24-hour session (no day/night
window filter) and split them by hour into the three non-overlapping
buckets — Daytime 7-18:59, Evening 19-21:59, Nighttime 22:00-06:59. Empty
period columns are dropped downstream, so it shows whatever periods have data.
Scoped to the combined-report path; the older per-session single report still
uses the fixed Evening/Nighttime layout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+39
-12
@@ -2241,7 +2241,10 @@ async def delete_session(
|
||||
})
|
||||
|
||||
|
||||
VALID_PERIOD_TYPES = {"weekday_day", "weekday_night", "weekend_day", "weekend_night"}
|
||||
# full_24h = a single continuous 24-hour period (day + night). Operator-set
|
||||
# only; never auto-derived. In reports its rows are split across
|
||||
# Daytime/Evening/Nighttime by hour rather than filtered to one window.
|
||||
VALID_PERIOD_TYPES = {"weekday_day", "weekday_night", "weekend_day", "weekend_night", "full_24h"}
|
||||
|
||||
|
||||
def _derive_period_type(dt: datetime) -> str:
|
||||
@@ -2255,7 +2258,7 @@ def _derive_period_type(dt: datetime) -> str:
|
||||
def _build_session_label(dt: datetime, location_name: str, period_type: str) -> str:
|
||||
day_abbr = dt.strftime("%a")
|
||||
date_str = f"{dt.month}/{dt.day}"
|
||||
period_str = {"weekday_day": "Day", "weekday_night": "Night", "weekend_day": "Day", "weekend_night": "Night"}.get(period_type, "")
|
||||
period_str = {"weekday_day": "Day", "weekday_night": "Night", "weekend_day": "Day", "weekend_night": "Night", "full_24h": "24-Hour"}.get(period_type, "")
|
||||
parts = [p for p in [location_name, f"{day_abbr} {date_str}", period_str] if p]
|
||||
return " — ".join(parts)
|
||||
|
||||
@@ -4085,7 +4088,15 @@ def _build_location_data_from_sessions(project_id: str, db, selected_session_ids
|
||||
# Prefer per-session period_start/end_hour; fall back to hardcoded defaults.
|
||||
sh = entry.get("period_start_hour") # e.g. 7 for Day, 19 for Night
|
||||
eh = entry.get("period_end_hour") # e.g. 19 for Day, 7 for Night
|
||||
if sh is None or eh is None:
|
||||
|
||||
target_date = None
|
||||
if period_type == 'full_24h':
|
||||
# 24-hour continuous: keep every row (rows get split across
|
||||
# Daytime/Evening/Nighttime by hour in the sheet builder). No
|
||||
# hour-window filtering and no single target date.
|
||||
is_day_session = False
|
||||
filtered = [(dt, row) for dt, row in parsed if dt]
|
||||
elif sh is None or eh is None:
|
||||
# Legacy defaults based on period_type
|
||||
is_day_session = period_type in ('weekday_day', 'weekend_day')
|
||||
sh = 7 if is_day_session else 19
|
||||
@@ -4093,8 +4104,9 @@ def _build_location_data_from_sessions(project_id: str, db, selected_session_ids
|
||||
else:
|
||||
is_day_session = eh > sh # crosses midnight when end < start
|
||||
|
||||
target_date = None
|
||||
if is_day_session:
|
||||
if period_type == 'full_24h':
|
||||
pass # filtered already set above
|
||||
elif is_day_session:
|
||||
# Day-style: start_h <= hour < end_h, restricted to the LAST calendar date
|
||||
in_window = lambda h: sh <= h < eh
|
||||
if entry.get("report_date"):
|
||||
@@ -4152,7 +4164,8 @@ def _build_location_data_from_sessions(project_id: str, db, selected_session_ids
|
||||
# Rebuild session label using the correct label date
|
||||
if label_dt and entry["loc_name"]:
|
||||
period_str = {"weekday_day": "Day", "weekday_night": "Night",
|
||||
"weekend_day": "Day", "weekend_night": "Night"}.get(period_type, "")
|
||||
"weekend_day": "Day", "weekend_night": "Night",
|
||||
"full_24h": "24-Hour"}.get(period_type, "")
|
||||
day_abbr = label_dt.strftime("%a")
|
||||
date_label = f"{label_dt.month}/{label_dt.day}"
|
||||
session_label = " — ".join(p for p in [loc_name, f"{day_abbr} {date_label}", period_str] if p)
|
||||
@@ -4411,21 +4424,35 @@ async def generate_combined_from_preview(
|
||||
evening_rows_data = []
|
||||
night_rows_data = []
|
||||
|
||||
def _row_hour(time_v):
|
||||
if time_v and ':' in str(time_v):
|
||||
try:
|
||||
return int(str(time_v).split(':')[0])
|
||||
except ValueError:
|
||||
pass
|
||||
return 0
|
||||
|
||||
for pt, time_v, lmx, l1, l2 in parsed_rows:
|
||||
if pt in PERIOD_TYPE_IS_DAY:
|
||||
day_rows_data.append((lmx, l1, l2))
|
||||
elif pt in PERIOD_TYPE_IS_NIGHT:
|
||||
# Split by time: Evening = 19:00–21:59, Nighttime = 22:00–06:59
|
||||
hour = 0
|
||||
if time_v and ':' in str(time_v):
|
||||
try:
|
||||
hour = int(str(time_v).split(':')[0])
|
||||
except ValueError:
|
||||
pass
|
||||
hour = _row_hour(time_v)
|
||||
if 19 <= hour <= 21:
|
||||
evening_rows_data.append((lmx, l1, l2))
|
||||
else:
|
||||
night_rows_data.append((lmx, l1, l2))
|
||||
elif pt == 'full_24h':
|
||||
# 24-hour continuous: split each row by hour into the three
|
||||
# non-overlapping buckets (Daytime 7–18:59, Evening 19–21:59,
|
||||
# Nighttime 22:00–06:59). Empty buckets are dropped downstream.
|
||||
hour = _row_hour(time_v)
|
||||
if 7 <= hour < 19:
|
||||
day_rows_data.append((lmx, l1, l2))
|
||||
elif 19 <= hour <= 21:
|
||||
evening_rows_data.append((lmx, l1, l2))
|
||||
else:
|
||||
night_rows_data.append((lmx, l1, l2))
|
||||
else:
|
||||
day_rows_data.append((lmx, l1, l2))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user