From 29c4fb704957ad1e3a88287caa930aba4e936422 Mon Sep 17 00:00:00 2001 From: John Lancaster <32917998+jsl12@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:00:17 -0600 Subject: [PATCH] initial commit --- .gitignore | 10 ++++++ README.md | 1 + mileage_rate.py | 96 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 mileage_rate.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..505a3b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv diff --git a/README.md b/README.md new file mode 100644 index 0000000..8d8b884 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Mileage Report diff --git a/mileage_rate.py b/mileage_rate.py new file mode 100644 index 0000000..8edfe14 --- /dev/null +++ b/mileage_rate.py @@ -0,0 +1,96 @@ +# /// script +# requires-python = ">=3.12" +# dependencies = ["typer", "rich", "xlwings"] +# /// +from contextlib import contextmanager +from pathlib import Path +from typing import Annotated + +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"): + 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 + + +def get_grand_total(book: xw.Book): + totals = dict(get_totals(book)) + grand_total = sum(totals.values()) + return grand_total + + +@contextmanager +def switch_values(book: xw.Book): + og_vals = book.sheets['GSA Rate'].range("B2").expand("down").value + try: + book.sheets['GSA Rate'].range("B2").expand("down").value = 0.70 + yield + finally: + book.sheets['GSA Rate'].range("B2").expand("down").options(transpose=True).value = og_vals + + +def get_diff(book: xw.Book): + book.sheets['GSA Rate'].range("B2:B10").value = 0.67 + initial = get_grand_total(book) + + with switch_values(book): + fixed = get_grand_total(book) + + diff = round(fixed - initial, 2) + return diff + + +def get_all_diffs(base: Path): + reports = [f for f in base.glob('*Mileage*.xlsx') if f.is_file() and not f.stem.startswith('~$')] + console.print(f"Found {len(reports)} report(s).") + for r in reports: + with console.status(f'Processing [bold blue]{r.name}[/bold blue]'): + console.print('Opening workbook...') + book = xw.Book(r) + 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}[/]") + except Exception as e: + console.print(f"[red]Error processing {r.name}: {e}[/red]") + finally: + book.close() + + +def main( + directory: Annotated[ + Path, + typer.Argument( + exists=True, + file_okay=False, + dir_okay=True, + readable=True, + writable=True, + resolve_path=True + ) + ] = 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') + + +if __name__ == "__main__": + typer.run(main)