Full rewrite of METRO HMG for TKB tunnel department: - People-based grid (18 TKB + 5 IT), year-long calendar - Color-coded shift values (4/6/8/12/A/B/D/N/U/O) - Drag-and-drop cells, multi-cell selection (click/ctrl/shift/drag) - Right-click context menu with color palette - Tunnel closure + Metro + D8 info rows (toggleable) - Czech holidays highlighted with names - PDF export (2-page A4 landscape, DejaVu font for Czech chars) - Improvement proposals system - Sticky headers (vertical + horizontal scroll) - Cell value filter toggles in legend Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
109 lines
3.7 KiB
Python
109 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate Excel export from saved schedule using template."""
|
|
import json
|
|
import sys
|
|
import openpyxl
|
|
from openpyxl.styles import PatternFill
|
|
from copy import copy
|
|
|
|
TEMPLATE = sys.argv[1]
|
|
SCHEDULE = sys.argv[2]
|
|
OUTPUT = sys.argv[3]
|
|
|
|
STATION_ROWS = {
|
|
'DE': 13, 'HR': 14, 'MA': 15, 'ST': 16, 'MSA': 17, 'MUA': 18,
|
|
'NM': 19, 'FL': 20, 'JP': 21, 'ZL': 22, 'SN': 23, 'AN': 24,
|
|
'KN': 25, 'NA': 26, 'MSB': 27, 'NR': 28, 'FRB': 29, 'KR': 30,
|
|
'KC': 31, 'VY': 32, 'IP': 33, 'MUC': 34, 'HN': 35, 'FRC': 36,
|
|
'VL': 37, 'NH': 38, 'KB': 39,
|
|
}
|
|
|
|
NO_FILL = PatternFill(fill_type=None)
|
|
|
|
with open(SCHEDULE) as f:
|
|
raw = json.load(f)
|
|
|
|
data = raw['data'] if 'data' in raw and 'stations' not in raw else raw
|
|
|
|
wb = openpyxl.load_workbook(TEMPLATE)
|
|
ws = wb.active
|
|
|
|
# Identify weekend columns: DEN row (row 11) has fill on weekends/holidays
|
|
# Capture the DATA ROW fill (row 13) for those columns — it has the correct theme=0 gray
|
|
weekend_cols = set()
|
|
for col in range(7, 281):
|
|
den_cell = ws.cell(row=11, column=col)
|
|
if den_cell.fill and den_cell.fill.fill_type == 'solid':
|
|
weekend_cols.add(col)
|
|
|
|
col_fills = {}
|
|
for col in weekend_cols:
|
|
data_cell = ws.cell(row=13, column=col)
|
|
if data_cell.fill and data_cell.fill.fill_type == 'solid':
|
|
theme = data_cell.fill.fgColor.theme if hasattr(data_cell.fill.fgColor, 'theme') and isinstance(data_cell.fill.fgColor.theme, int) else None
|
|
if theme == 0: # Gray weekend fill, not teal s/v fill
|
|
col_fills[col] = copy(data_cell.fill)
|
|
|
|
for station in data['stations']:
|
|
row = STATION_ROWS.get(station['code'])
|
|
if not row:
|
|
continue
|
|
|
|
# Step 1: Clear all values
|
|
for col in range(7, 281):
|
|
ws.cell(row=row, column=col).value = None
|
|
|
|
# Step 2: Restore original column fills (weekends etc)
|
|
for col in range(7, 281):
|
|
if col in col_fills:
|
|
ws.cell(row=row, column=col).fill = copy(col_fills[col])
|
|
else:
|
|
ws.cell(row=row, column=col).fill = NO_FILL
|
|
|
|
# Step 3: Write data values (no extra fills — only weekend column fills from step 2)
|
|
for idx_str, cell_data in station['data'].items():
|
|
idx = int(idx_str)
|
|
col = idx + 7
|
|
if col < 7 or col > 280:
|
|
continue
|
|
val = cell_data.get('v')
|
|
if val is not None:
|
|
ws.cell(row=row, column=col).value = val
|
|
|
|
# Add DEN row comments
|
|
if 'dayComments' in data:
|
|
comments = data['dayComments']
|
|
# Handle both list format [{dayIdx, text}] and dict format {"idx": "text"}
|
|
if isinstance(comments, list):
|
|
for c in comments:
|
|
idx = c.get('dayIdx', 0)
|
|
text = c.get('text', '')
|
|
if text:
|
|
col = idx + 7
|
|
if 7 <= col <= 280:
|
|
from openpyxl.comments import Comment
|
|
ws.cell(row=11, column=col).comment = Comment(text, 'Metro HMG')
|
|
elif isinstance(comments, dict):
|
|
for idx_str, text in comments.items():
|
|
if text:
|
|
col = int(idx_str) + 7
|
|
if 7 <= col <= 280:
|
|
from openpyxl.comments import Comment
|
|
ws.cell(row=11, column=col).comment = Comment(text, 'Metro HMG')
|
|
|
|
# Add cell comments (per station+day)
|
|
if 'cellComments' in data:
|
|
from openpyxl.comments import Comment as XlComment
|
|
for c in data.get('cellComments', []):
|
|
code = c.get('stationCode', '')
|
|
idx = c.get('dayIdx', 0)
|
|
text = c.get('text', '')
|
|
row = STATION_ROWS.get(code)
|
|
if row and text:
|
|
col = idx + 7
|
|
if 7 <= col <= 280:
|
|
ws.cell(row=row, column=col).comment = XlComment(text, 'Metro HMG')
|
|
|
|
wb.save(OUTPUT)
|
|
print(f'Exported to {OUTPUT}', file=sys.stderr)
|