Initial commit (clean, ignores in place)
This commit is contained in:
91
tests/test_excel_writer.py
Normal file
91
tests/test_excel_writer.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from openpyxl import load_workbook
|
||||
|
||||
from mileage_logger.ingest.semantic_reader import load_place_visits
|
||||
from mileage_logger.logic.detect_itinerary import SiteConfig, detect_itinerary
|
||||
from mileage_logger.distance.resolve import DistanceResolver
|
||||
from mileage_logger.export.excel_writer import build_monthly_rows, write_monthly_workbook
|
||||
|
||||
|
||||
FIXTURES_DIR = os.path.join(os.path.dirname(__file__), "fixtures", "semantic")
|
||||
CONFIG_PATH = os.path.join(os.path.dirname(__file__), "..", "config", "sites.yml")
|
||||
ROUTES_PATH = os.path.join(os.path.dirname(__file__), "data", "routes_golden.csv")
|
||||
|
||||
|
||||
class TestExcelWriter(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.site_config = SiteConfig.from_yaml(CONFIG_PATH)
|
||||
|
||||
def _build_workbook(self, fixture_file: str) -> str:
|
||||
visits = load_place_visits(os.path.join(FIXTURES_DIR, fixture_file))
|
||||
hops = detect_itinerary(visits, self.site_config)
|
||||
resolver = DistanceResolver(route_csv_path=ROUTES_PATH)
|
||||
rows_by_month = build_monthly_rows(hops, self.site_config, resolver)
|
||||
# Write to a temporary file
|
||||
fd, path = tempfile.mkstemp(suffix=".xlsx")
|
||||
os.close(fd)
|
||||
write_monthly_workbook(rows_by_month, path)
|
||||
return path
|
||||
|
||||
def test_excel_layout_simple(self):
|
||||
# Build workbook for the simple fixture
|
||||
path = self._build_workbook("2025-08-08.one_day_simple.json")
|
||||
try:
|
||||
wb = load_workbook(path)
|
||||
# Determine expected month from the first hop date
|
||||
visits = load_place_visits(os.path.join(FIXTURES_DIR, "2025-08-08.one_day_simple.json"))
|
||||
hops = detect_itinerary(visits, self.site_config)
|
||||
if not hops:
|
||||
self.fail("No hops detected for simple fixture")
|
||||
expected_month = hops[0].date.strftime("%Y-%m")
|
||||
self.assertIn(expected_month, wb.sheetnames)
|
||||
ws = wb[expected_month]
|
||||
rows = list(ws.iter_rows(values_only=True))
|
||||
# number of hops + header
|
||||
self.assertEqual(len(rows), len(hops) + 1)
|
||||
header = rows[0]
|
||||
expected_header = ("Date", "Purpose", "Miles", "Vehicle", "Job Role", "From", "To", "Notes")
|
||||
self.assertEqual(header, expected_header)
|
||||
# Validate each hop row
|
||||
resolver = DistanceResolver(route_csv_path=ROUTES_PATH)
|
||||
for i, hop in enumerate(hops, start=1):
|
||||
row = rows[i]
|
||||
origin_site = self.site_config.by_canonical[hop.origin]
|
||||
dest_site = self.site_config.by_canonical[hop.destination]
|
||||
dist = resolver.resolve(hop.origin, hop.destination, (origin_site.lat, origin_site.lon), (dest_site.lat, dest_site.lon))
|
||||
self.assertEqual(row[0], hop.date.isoformat())
|
||||
expected_purpose = f"Travel from {origin_site.label} to {dest_site.label} {dist:.1f}mi"
|
||||
self.assertEqual(row[1], expected_purpose)
|
||||
self.assertAlmostEqual(float(row[2]), dist, places=1)
|
||||
self.assertEqual(row[3], resolver.vehicle_label)
|
||||
self.assertEqual(row[4], resolver.job_role)
|
||||
self.assertEqual(row[5], origin_site.label)
|
||||
self.assertEqual(row[6], dest_site.label)
|
||||
# Notes may be returned as None when reading from Excel
|
||||
self.assertIn(row[7] or "", ["", None])
|
||||
finally:
|
||||
os.unlink(path)
|
||||
|
||||
def test_cross_midnight_sheet_rows(self):
|
||||
path = self._build_workbook("cross_midnight.json")
|
||||
try:
|
||||
wb = load_workbook(path)
|
||||
# Should still be month 2025-08
|
||||
self.assertIn("2025-08", wb.sheetnames)
|
||||
ws = wb["2025-08"]
|
||||
rows = list(ws.iter_rows(values_only=True))
|
||||
# two hops -> 3 rows including header
|
||||
self.assertEqual(len(rows), 3)
|
||||
# Dates should span two days
|
||||
dates = [r[0] for r in rows[1:]]
|
||||
self.assertEqual(dates, ["2025-08-08", "2025-08-09"])
|
||||
finally:
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Reference in New Issue
Block a user