updates v2
This commit is contained in:
@@ -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__":
|
||||||
|
|||||||
Reference in New Issue
Block a user