# Edit Patterns — Reusable Code for Complex Edit Operations > Load this file ON DEMAND when you encounter grouping, sorting, block detection, or other complex edit patterns. > Do NOT load upfront for simple edits. --- ## Pattern: Block Detection Data is often split into independent blocks separated by blank rows or keyword rows (e.g., TOTAL, Subtotal). ```python def detect_blocks(ws, col=1, start_row=1, end_row=None, separator='blank', keyword='TOTAL'): """ Detect data block boundaries. separator: 'blank' (empty row) or 'keyword' (row containing keyword) Returns: list of (start_row, end_row) tuples """ if end_row is None: end_row = ws.max_row blocks, block_start = [], None for row in range(start_row, end_row + 1): val = ws.cell(row=row, column=col).value is_blank = val is None or (isinstance(val, str) and val.strip() == '') is_kw = (separator == 'keyword' and isinstance(val, str) and keyword in str(val).upper()) if separator == 'blank': if not is_blank and block_start is None: block_start = row elif is_blank and block_start is not None: blocks.append((block_start, row - 1)) block_start = None elif separator == 'keyword': if is_kw: if block_start: blocks.append((block_start, row)) block_start = None elif not is_blank and block_start is None: block_start = row if block_start: blocks.append((block_start, end_row)) return blocks ``` --- ## Pattern: Pre-filter Null Rows Before any groupby/aggregation, filter out rows where key columns are empty. ```python def pre_filter_rows(ws, key_cols, start_row, end_row): """Return row numbers where ALL key columns are non-null.""" return [row for row in range(start_row, end_row + 1) if all(normalize_cell_value(ws.cell(row=row, column=c).value) is not None for c in key_cols)] ``` --- ## Pattern: Sort with Formula Rewrite When sorting rows by swapping data (not using `insert_rows`), formulas must be regenerated with new row numbers. ```python def sort_block_with_formulas(ws, block_rows, sort_col, formula_templates, descending=True): """ Sort rows within a block, regenerating formulas. formula_templates: dict {col_index: '=B{row}+C{row}'} """ # 1. Read all row data + compute sort key rows_data = [] for r in block_rows: vals = {c: ws.cell(row=r, column=c).value for c in range(1, ws.max_column + 1)} rows_data.append(vals) rows_data.sort(key=lambda x: (x.get(sort_col) or 0), reverse=descending) # 2. Write back with new row numbers for i, rd in enumerate(rows_data): target = block_rows[i] for col, val in rd.items(): if col in formula_templates: ws.cell(row=target, column=col).value = formula_templates[col].format(row=target) else: ws.cell(row=target, column=col).value = val ``` --- ## Pattern: Group-Merge (Aggregate by Key) Group rows by a key column. Take first-row values for some columns, sum for others. ```python from collections import OrderedDict def group_merge_rows(ws, key_col, start_row, end_row, first_cols, sum_cols): """ Group by key_col, merge rows. first_cols: take value from first row in group sum_cols: sum values across group """ groups = OrderedDict() for row in range(start_row, end_row + 1): key = normalize_cell_value(ws.cell(row=row, column=key_col).value) if key is None: continue if key not in groups: groups[key] = { 'first': {c: ws.cell(row=row, column=c).value for c in first_cols}, 'sums': {c: 0.0 for c in sum_cols}, } for c in sum_cols: v = normalize_cell_value(ws.cell(row=row, column=c).value) if v is not None: try: groups[key]['sums'][c] += float(v) except (ValueError, TypeError): pass return groups ``` --- ## Pattern: Group-Max-Keep-Ties Group by key, find max value per group, keep ALL rows that match the max (not just the first). ```python from collections import defaultdict def group_max_keep_ties(rows, key_func, value_func, filter_null=True): """ Keep all rows with the maximum value per group (ties preserved). rows: list of row dicts or tuples key_func: row → group key value_func: row → comparable value (e.g., date) """ groups = defaultdict(list) for row in rows: val = value_func(row) if filter_null and val is None: continue groups[key_func(row)].append(row) kept = [] for key, group in groups.items(): max_val = max(value_func(r) for r in group) kept.extend(r for r in group if value_func(r) == max_val) return kept ``` --- ## Pattern: Sequence Fill (Smart Numbering) Fill blank rows with "parent number + letter suffix" (e.g., 5 → 5a, 5b, ..., 5z, 5aa). ```python import re def get_letter_suffix(n): """0=a, 25=z, 26=aa, 27=ab...""" if n < 26: return chr(ord('a') + n) return chr(ord('a') + (n // 26) - 1) + chr(ord('a') + (n % 26)) def fill_sequential_labels(ws, col, start_row, end_row): last_base, blank_count = None, 0 for row in range(start_row, end_row + 1): val = ws.cell(row=row, column=col).value if val is not None: m = re.match(r'^(\d+)', str(val)) if m: last_base = m.group(1) blank_count = 0 else: if last_base is not None: ws.cell(row=row, column=col).value = f"{last_base}{get_letter_suffix(blank_count)}" blank_count += 1 ``` --- ## Pattern: Zero-as-Blank Output When merged/aggregated values of 0 should display as empty: ```python # Method 1: Write None (best for programmatic verification) cell.value = computed_value if computed_value != 0 else None # Method 2: Number format (best for Excel viewing) cell.value = computed_value cell.number_format = '0.00;-0.00;""' # positive;negative;zero(blank) ``` --- ## Pattern: Side-by-Side Table Detection Some sheets contain multiple independent tables arranged horizontally (separated by empty columns). ```python def detect_side_by_side_tables(ws): """Find column groups separated by all-null columns.""" tables = [] current_start = None for col in range(1, ws.max_column + 1): has_data = any(ws.cell(row=r, column=col).value is not None for r in range(1, ws.max_row + 1)) if has_data and current_start is None: current_start = col elif not has_data and current_start is not None: tables.append((current_start, col - 1)) current_start = None if current_start: tables.append((current_start, ws.max_column)) return tables # [(start_col, end_col), ...] ```