updates v2

This commit is contained in:
John Lancaster
2025-11-17 13:20:04 -06:00
parent 29c4fb7049
commit dcd9bda8a9

View File

@@ -2,6 +2,10 @@
# requires-python = ">=3.12" # requires-python = ">=3.12"
# dependencies = ["typer", "rich", "xlwings"] # dependencies = ["typer", "rich", "xlwings"]
# /// # ///
import json
import re
from collections.abc import Generator
from contextlib import contextmanager from contextlib import contextmanager
from pathlib import Path from pathlib import Path
from typing import Annotated from typing import Annotated
@@ -10,24 +14,51 @@ import xlwings as xw
import typer import typer
from rich.console import Console from rich.console import Console
console = Console() console = Console()
def get_reports(base_dir: Path):
def get_totals(book: xw.Book): report_regex = re.compile(r'(?P<name>\w+)_2025_[\w ]+')
for sheet in book.sheets: for report in base_dir.glob("*Mileage Report.xlsx"):
if sheet.name.endswith("Rate"): if not report.is_file() or report.stem.startswith('~$'):
continue continue
try: if m := report_regex.match(report.stem):
total = float(sheet.range("S3").expand("down").value[-1]) yield m.group("name"), report
except Exception as e:
console.print(f"[red]Error reading total from sheet {sheet.name}: {e}[/red]")
console.print_exception(show_locals=False) def find_in_range(rng: xw.Range, query: str) -> xw.Range | None:
else: for cell in rng:
yield sheet.name, total 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): def get_grand_total(book: xw.Book):
totals = dict(get_totals(book)) totals = dict(get_all_totals(book))
grand_total = sum(totals.values()) grand_total = sum(totals.values())
return grand_total 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 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 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): 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 {
return diff '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): 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).") console.print(f"Found {len(reports)} report(s).")
for r in reports: for name, report_path in reports.items():
with console.status(f'Processing [bold blue]{r.name}[/bold blue]'): with console.status(f'Processing [bold blue]{report_path.name}[/bold blue]'):
console.print('Opening workbook...') console.print('Opening workbook...')
book = xw.Book(r) book = xw.Book(report_path)
try: try:
console.print('Calculating totals...') res = get_diff(book)
diff = get_diff(book) console.print(f"[cyan]Report:[/] {report_path.name}")
yield r.name, diff console.print(f" Results: ${res}")
console.print(f"[cyan]Report:[/] {r.name}") yield name, res
console.print(f" Difference after rate change: [bold green]${diff:.2f}[/]")
except Exception as e: 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: finally:
book.close() book.close()
@@ -86,10 +121,8 @@ def main(
] = None) -> None: ] = None) -> None:
console.print(f"Processing files in directory: {directory}") console.print(f"Processing files in directory: {directory}")
results = dict(get_all_diffs(directory)) results = dict(get_all_diffs(directory))
with Path('results.csv').open('w', encoding='utf-8') as f: with Path('results.jsonl').open('w', encoding='utf-8') as f:
f.write("Report,Difference\n") json.dump(results, f, indent=4)
for report, diff in results.items():
f.write(f'{report},{diff}\n')
if __name__ == "__main__": if __name__ == "__main__":