91 lines
4.1 KiB
Python
91 lines
4.1 KiB
Python
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() |