From 8f24187d03622764b844aee0a46fce07a2884862 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 14 Jul 2026 09:22:09 +0000 Subject: [PATCH] =?UTF-8?q?Brand=20the=20export=20header=20band=20and=20fr?= =?UTF-8?q?eeze=20the=20header=20row=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- infrastructure/xlsx/branding.py | 27 +++++++++++++++++++ .../xlsx/test_scenario_export_workbook.py | 20 ++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 infrastructure/xlsx/branding.py diff --git a/infrastructure/xlsx/branding.py b/infrastructure/xlsx/branding.py new file mode 100644 index 000000000..b53195fad --- /dev/null +++ b/infrastructure/xlsx/branding.py @@ -0,0 +1,27 @@ +"""Monday.com-style branding for the Scenario Export workbook (ADR-0065). + +A programmatic openpyxl styling pass (no template): a bold indigo header band +with white text, a frozen header row, subtle alternating row bands, and roomy +column widths. The palette lives here as the single home for brand colour — the +"no brand-colour constants anywhere" gap the review flagged. + +TODO(brand): swap the placeholder Monday-style hex for the real Domna brand +palette once design supplies it. +""" + +from __future__ import annotations + +from openpyxl.worksheet.worksheet import Worksheet + +# openpyxl colours are 8-char ARGB. Placeholder Monday-style palette. +HEADER_FILL: str = "FF6C6CFF" # indigo header band +HEADER_FONT: str = "FFFFFFFF" # white header text +ROW_BAND_FILL: str = "FFF1F1FF" # subtle alternating-row tint + +_COLUMN_WIDTH: int = 18 + + +def style_sheet(worksheet: Worksheet) -> None: + """Paint the Monday-style branding onto a populated worksheet: header band + + font, frozen header row, banded rows, and column widths.""" + raise NotImplementedError diff --git a/tests/infrastructure/xlsx/test_scenario_export_workbook.py b/tests/infrastructure/xlsx/test_scenario_export_workbook.py index 4bf13859e..81e8a3cf8 100644 --- a/tests/infrastructure/xlsx/test_scenario_export_workbook.py +++ b/tests/infrastructure/xlsx/test_scenario_export_workbook.py @@ -8,6 +8,7 @@ from io import BytesIO from openpyxl import load_workbook from domain.scenario_export.scenario_sheet import ExportSheet +from infrastructure.xlsx.branding import HEADER_FILL from infrastructure.xlsx.scenario_export_workbook import render_workbook @@ -48,3 +49,22 @@ def test_sanitises_and_deduplicates_scenario_sheet_names() -> None: assert len(set(names)) == 3 assert all(len(name) <= 31 for name in names) assert not any(ch in name for name in names for ch in r":/\?*[]") + + +def test_brands_the_header_row_and_freezes_it() -> None: + # arrange + sheet = ExportSheet( + columns=("property_id", "loft_insulation"), + rows=({"property_id": 1, "loft_insulation": 1200.0},), + ) + + # act + data = render_workbook([("Fabric first", sheet)]) + + # assert — the header is a bold, brand-filled band and the header row is + # frozen so it stays visible while scrolling. + worksheet = load_workbook(BytesIO(data))["Fabric first"] + header_cell = worksheet["A1"] + assert header_cell.font.bold is True + assert header_cell.fill.fgColor.rgb == HEADER_FILL + assert worksheet.freeze_panes == "A2"