From dcd9bda8a927df858e208e6c68fea848f766859b Mon Sep 17 00:00:00 2001 From: John Lancaster <32917998+jsl12@users.noreply.github.com> Date: Mon, 17 Nov 2025 13:20:04 -0600 Subject: [PATCH] updates v2 --- mileage_rate.py | 95 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 64 insertions(+), 31 deletions(-) diff --git a/mileage_rate.py b/mileage_rate.py index 8edfe14..39d134e 100644 --- a/mileage_rate.py +++ b/mileage_rate.py @@ -2,6 +2,10 @@ # requires-python = ">=3.12" # dependencies = ["typer", "rich", "xlwings"] # /// + +import json +import re +from collections.abc import Generator from contextlib import contextmanager from pathlib import Path from typing import Annotated @@ -10,24 +14,51 @@ import xlwings as xw import typer from rich.console import Console + console = Console() - -def get_totals(book: xw.Book): - for sheet in book.sheets: - if sheet.name.endswith("Rate"): +def get_reports(base_dir: Path): + report_regex = re.compile(r'(?P\w+)_2025_[\w ]+') + for report in base_dir.glob("*Mileage Report.xlsx"): + if not report.is_file() or report.stem.startswith('~$'): continue - try: - total = float(sheet.range("S3").expand("down").value[-1]) - except Exception as e: - console.print(f"[red]Error reading total from sheet {sheet.name}: {e}[/red]") - console.print_exception(show_locals=False) - else: - yield sheet.name, total + if m := report_regex.match(report.stem): + yield m.group("name"), report + + +def find_in_range(rng: xw.Range, query: str) -> xw.Range | None: + for cell in rng: + match cell.value: + case str(cell_value) if cell_value.lower().startswith(query.lower()): + return cell + return None + + +def find_total_row(table_start: xw.Range) -> int: + desc_col = find_in_range(table_start.expand('right'), "Desc") + col = desc_col.get_address(column_absolute=False)[0] + return find_in_range(table_start.sheet.range(f'{col}:{col}'), 'total').row + + +def get_total_cost(sheet: xw.Sheet) -> float: + table_start = find_in_range(sheet.range("A1:A10"), "Date") + cost_col = find_in_range(table_start.expand('right'), "Cost") + total_row = find_total_row(table_start) + cost_cell = sheet.range(f'{cost_col.get_address(column_absolute=False)[0]}{total_row}') + return cost_cell.value + + +def get_all_totals(book: xw.Book) -> Generator[str, float]: + for sheet in book.sheets: + if sheet.name.endswith('Rate'): + continue + total = float(get_total_cost(sheet)) + console.print(f'Got ${total} for sheet {sheet.name}') + yield sheet.name, float(total) def get_grand_total(book: xw.Book): - totals = dict(get_totals(book)) + totals = dict(get_all_totals(book)) grand_total = sum(totals.values()) return grand_total @@ -42,32 +73,36 @@ def switch_values(book: xw.Book): book.sheets['GSA Rate'].range("B2").expand("down").options(transpose=True).value = og_vals -def get_diff(book: xw.Book): +def get_diff(book: xw.Book) -> dict[str, float]: book.sheets['GSA Rate'].range("B2:B10").value = 0.67 - initial = get_grand_total(book) + console.print('Calculating initial total...') + initial_totals = dict(get_all_totals(book)) with switch_values(book): - fixed = get_grand_total(book) + console.print('Calculating new rate total...') + new_totals = dict(get_all_totals(book)) - diff = round(fixed - initial, 2) - return diff + return { + 'old_rate': initial_totals, + 'new_rate': new_totals, + 'difference': round(sum(new_totals.values()) - sum(initial_totals.values()), 2) + } def get_all_diffs(base: Path): - reports = [f for f in base.glob('*Mileage*.xlsx') if f.is_file() and not f.stem.startswith('~$')] + reports = dict(get_reports(base)) console.print(f"Found {len(reports)} report(s).") - for r in reports: - with console.status(f'Processing [bold blue]{r.name}[/bold blue]'): + for name, report_path in reports.items(): + with console.status(f'Processing [bold blue]{report_path.name}[/bold blue]'): console.print('Opening workbook...') - book = xw.Book(r) + book = xw.Book(report_path) try: - console.print('Calculating totals...') - diff = get_diff(book) - yield r.name, diff - console.print(f"[cyan]Report:[/] {r.name}") - console.print(f" Difference after rate change: [bold green]${diff:.2f}[/]") + res = get_diff(book) + console.print(f"[cyan]Report:[/] {report_path.name}") + console.print(f" Results: ${res}") + yield name, res except Exception as e: - console.print(f"[red]Error processing {r.name}: {e}[/red]") + console.print(f"[red]Error processing {report_path.name}: {e}[/red]") finally: book.close() @@ -86,10 +121,8 @@ def main( ] = None) -> None: console.print(f"Processing files in directory: {directory}") results = dict(get_all_diffs(directory)) - with Path('results.csv').open('w', encoding='utf-8') as f: - f.write("Report,Difference\n") - for report, diff in results.items(): - f.write(f'{report},{diff}\n') + with Path('results.jsonl').open('w', encoding='utf-8') as f: + json.dump(results, f, indent=4) if __name__ == "__main__":