Source code for aeat.application.storage.calc_sheets._styling

"""Compute the presentation facets (styled ranges, widths, freezes, filters).

Pure functions that derive the workbook's visual layout from the resolved
:class:`SheetLayout` plus the already-computed structural facets (section
headers, anchors, provenance / evidence extents). The output is consumed
verbatim by both transports through the shared ``_theme`` palette, so the
offline xls and online Sheets present the same official-modelo look. Keeping
this computation in one place is what makes the design uniform across every
modelo and every transport.

Both data tabs use the column layout ``A=Sección B=Casilla C=Concepto D=Valor``
with an anchor column ``F``; the Procedencia / Evidencia / Detalle / Guía tabs
have their own widths declared below.
"""

from __future__ import annotations

from ._layout import SheetLayout
from ._records import (
    SheetAnchor,
    SheetAutoFilter,
    SheetColumnWidth,
    SheetFrozenView,
    SheetProvenanceRow,
    SheetSectionHeader,
    SheetStyledRange,
    TabName,
)
from ._theme import StyleRole

# Column indices shared by the two data tabs.
_COL_SECTION = 1
_COL_CASILLA = 2
_COL_CONCEPTO = 3
_COL_VALUE = 4
_COL_ANCHOR = 6

# Approximate character widths per column, per tab, sized so labels and legal
# references read without clipping.
_DATA_WIDTHS: tuple[tuple[int, int], ...] = (
    (_COL_SECTION, 30),
    (_COL_CASILLA, 9),
    (_COL_CONCEPTO, 52),
    (_COL_VALUE, 16),
    (5, 4),
    (_COL_ANCHOR, 22),
)
_PROVENANCE_WIDTHS: tuple[tuple[int, int], ...] = (
    (1, 14),
    (2, 6),
    (3, 44),
    (4, 18),
    (5, 11),
    (6, 26),
    (7, 26),
    (8, 18),
)
_TARIFFS_WIDTHS: tuple[tuple[int, int], ...] = (
    (1, 30),
    (2, 4),
    (3, 16),
    (4, 16),
    (5, 16),
    (6, 16),
)
_EVIDENCIA_WIDTHS: tuple[tuple[int, int], ...] = (
    (1, 10),
    (2, 10),
    (3, 18),
    (4, 14),
    (5, 8),
    (6, 14),
    (7, 10),
    (8, 12),
    (9, 22),
    (10, 16),
    (11, 12),
    (12, 30),
    (13, 18),
    (14, 18),
    (15, 24),
    (16, 24),
)
_GUIDE_WIDTHS: tuple[tuple[int, int], ...] = (
    (1, 90),
    (2, 40),
)

_EVIDENCIA_HEADER_ROW = 3
_EVIDENCIA_COLUMNS = 16
# Evidence is often attached to the plan after build time (the verify / export
# action bundles it), so the body styling covers a generous fixed window rather
# than gating on a body count the build pass does not yet know. Wrapping blank
# cells is harmless; the window keeps note / ref columns readable once rows land.
_EVIDENCIA_BODY_WINDOW = 250


def _widths(tab: TabName, pairs: tuple[tuple[int, int], ...]) -> list[SheetColumnWidth]:
    return [SheetColumnWidth(tab=tab, column=column, width=width) for column, width in pairs]


def _data_tab_ranges(
    *,
    tab: TabName,
    last_row: int,
    value_role: StyleRole,
    section_headers: tuple[SheetSectionHeader, ...],
) -> list[SheetStyledRange]:
    """Styled ranges shared by the Entradas / Cálculos tabs.

    Header band on row 1, a section banner per section change, the value column
    tinted by ``value_role`` (input vs computed), and a wrapped concepto column.
    """
    ranges: list[SheetStyledRange] = [
        SheetStyledRange(
            tab=tab,
            start_row=1,
            end_row=1,
            start_column=_COL_SECTION,
            end_column=_COL_VALUE,
            role=StyleRole.HEADER,
        ),
        SheetStyledRange(
            tab=tab,
            start_row=2,
            end_row=last_row,
            start_column=_COL_CONCEPTO,
            end_column=_COL_CONCEPTO,
            role=StyleRole.BODY,
            wrap=True,
        ),
        SheetStyledRange(
            tab=tab,
            start_row=2,
            end_row=last_row,
            start_column=_COL_VALUE,
            end_column=_COL_VALUE,
            role=value_role,
        ),
    ]
    for header in section_headers:
        if header.address.tab is not tab:
            continue
        ranges.append(
            SheetStyledRange(
                tab=tab,
                start_row=header.address.row,
                end_row=header.address.row,
                start_column=_COL_SECTION,
                end_column=_COL_SECTION,
                role=StyleRole.SECTION_BANNER,
                wrap=True,
            ),
        )
    return ranges


def _anchor_ranges(anchors: tuple[SheetAnchor, ...]) -> list[SheetStyledRange]:
    """Tint the start anchor as a banner and the final anchor as the result."""
    ranges: list[SheetStyledRange] = []
    for anchor in anchors:
        role = StyleRole.RESULT if anchor.kind == "final" else StyleRole.SECTION_BANNER
        ranges.append(
            SheetStyledRange(
                tab=anchor.address.tab,
                start_row=anchor.address.row,
                end_row=anchor.address.row,
                start_column=anchor.address.column,
                end_column=anchor.address.column,
                role=role,
            ),
        )
    return ranges


[docs] def compute_styling( *, layout: SheetLayout, section_headers: tuple[SheetSectionHeader, ...], anchors: tuple[SheetAnchor, ...], provenance: tuple[SheetProvenanceRow, ...], guide_paragraphs: int, ) -> tuple[ tuple[SheetStyledRange, ...], tuple[SheetColumnWidth, ...], tuple[SheetFrozenView, ...], tuple[SheetAutoFilter, ...], ]: """Derive ``(styled_ranges, column_widths, frozen_views, auto_filters)``. Returns a four-tuple of (:class:`SheetStyledRange`, :class:`SheetColumnWidth`, :class:`SheetFrozenView`, :class:`SheetAutoFilter`) tuples. A pure function of the resolved layout and the structural facets: two runs over the same inputs produce the same facets, so the design is deterministic and the two transports stay in lock-step. """ entradas_last = max( (row.row for row in layout.entradas_rows), default=1, ) binding_last = max((row.row for row in layout.binding_rows), default=1) entradas_last = max(entradas_last, binding_last, 1) calculos_last = max((row.row for row in layout.calculos_rows), default=1) provenance_last = max(len(provenance) + 1, 1) evidencia_last = _EVIDENCIA_HEADER_ROW + _EVIDENCIA_BODY_WINDOW styled: list[SheetStyledRange] = [] styled += _data_tab_ranges( tab=TabName.ENTRADAS, last_row=entradas_last, value_role=StyleRole.INPUT, section_headers=section_headers, ) styled += _data_tab_ranges( tab=TabName.CALCULOS, last_row=calculos_last, value_role=StyleRole.COMPUTED, section_headers=section_headers, ) # Provenance header band + wrapped concepto / legal / source columns. styled.append( SheetStyledRange( tab=TabName.PROVENANCE, start_row=1, end_row=1, start_column=1, end_column=8, role=StyleRole.HEADER, ), ) for wrap_col in (3, 6, 7): styled.append( SheetStyledRange( tab=TabName.PROVENANCE, start_row=2, end_row=provenance_last, start_column=wrap_col, end_column=wrap_col, role=StyleRole.BODY, wrap=True, ), ) # Evidencia: fingerprint title on A1, header band on row 3, wrapped note / # ref columns. styled.append( SheetStyledRange( tab=TabName.EVIDENCIA, start_row=1, end_row=1, start_column=1, end_column=1, role=StyleRole.TITLE, ), ) styled.append( SheetStyledRange( tab=TabName.EVIDENCIA, start_row=_EVIDENCIA_HEADER_ROW, end_row=_EVIDENCIA_HEADER_ROW, start_column=1, end_column=_EVIDENCIA_COLUMNS, role=StyleRole.HEADER, ), ) for wrap_col in (12, 15, 16): styled.append( SheetStyledRange( tab=TabName.EVIDENCIA, start_row=_EVIDENCIA_HEADER_ROW + 1, end_row=evidencia_last, start_column=wrap_col, end_column=wrap_col, role=StyleRole.BODY, wrap=True, ), ) # Guía: title on A1, wrapped prose column. styled.append( SheetStyledRange(tab=TabName.GUIDE, start_row=1, end_row=1, start_column=1, end_column=1, role=StyleRole.TITLE), ) if guide_paragraphs: styled.append( SheetStyledRange( tab=TabName.GUIDE, start_row=3, end_row=2 + guide_paragraphs, start_column=1, end_column=1, role=StyleRole.BODY, wrap=True, ), ) # Anchors last so the result accent wins over the broad value column. styled += _anchor_ranges(anchors) widths: list[SheetColumnWidth] = [] widths += _widths(TabName.ENTRADAS, _DATA_WIDTHS) widths += _widths(TabName.CALCULOS, _DATA_WIDTHS) widths += _widths(TabName.PROVENANCE, _PROVENANCE_WIDTHS) widths += _widths(TabName.TARIFFS, _TARIFFS_WIDTHS) widths += _widths(TabName.EVIDENCIA, _EVIDENCIA_WIDTHS) widths += _widths(TabName.GUIDE, _GUIDE_WIDTHS) frozen: list[SheetFrozenView] = [ SheetFrozenView(tab=TabName.ENTRADAS, frozen_rows=1), SheetFrozenView(tab=TabName.CALCULOS, frozen_rows=1), SheetFrozenView(tab=TabName.PROVENANCE, frozen_rows=1), SheetFrozenView(tab=TabName.EVIDENCIA, frozen_rows=_EVIDENCIA_HEADER_ROW), ] filters: list[SheetAutoFilter] = [ SheetAutoFilter(tab=TabName.ENTRADAS, start_row=1, end_row=entradas_last, start_column=1, end_column=4), SheetAutoFilter(tab=TabName.CALCULOS, start_row=1, end_row=calculos_last, start_column=1, end_column=4), SheetAutoFilter(tab=TabName.PROVENANCE, start_row=1, end_row=provenance_last, start_column=1, end_column=8), ] filters.append( SheetAutoFilter( tab=TabName.EVIDENCIA, start_row=_EVIDENCIA_HEADER_ROW, end_row=evidencia_last, start_column=1, end_column=_EVIDENCIA_COLUMNS, ), ) return tuple(styled), tuple(widths), tuple(frozen), tuple(filters)
__all__ = ["compute_styling"]