Files

190 lines
6.5 KiB
Python

"""Command line interface for the mileage logging tool."""
from __future__ import annotations
import argparse
import os
from datetime import date, datetime, timedelta
from typing import Optional, Tuple
import pytz
from .ingest.semantic_reader import load_place_visits
from .logic.detect_itinerary import SiteConfig, detect_itinerary
from .distance.resolve import DistanceResolver
from .export.excel_writer import build_monthly_rows, write_monthly_workbook
TZ = pytz.timezone("Europe/London")
def _today_local() -> date:
return datetime.now(TZ).date()
def _prev_month_bounds(today: Optional[date] = None) -> Tuple[date, date]:
"""Return (start_date, end_date) for the previous calendar month in Europe/London."""
if today is None:
today = _today_local()
first_this_month = today.replace(day=1)
last_prev_month = first_this_month - timedelta(days=1)
start_prev_month = last_prev_month.replace(day=1)
return start_prev_month, last_prev_month
def _month_bounds(ym: str) -> Tuple[date, date]:
"""Return (start_date, end_date) for the given YYYY-MM."""
year, month = map(int, ym.split("-"))
start = date(year, month, 1)
if month == 12:
end = date(year + 1, 1, 1) - timedelta(days=1)
else:
end = date(year, month + 1, 1) - timedelta(days=1)
return start, end
def _parse_date(s: str) -> date:
y, m, d = map(int, s.split("-"))
return date(y, m, d)
def import_file(
json_path: str,
site_config_path: str,
route_csv_path: str,
output_dir: str,
assume_home_start: bool,
weekdays_only: bool,
month: Optional[str],
last_month: bool,
since: Optional[str],
until: Optional[str],
days: Optional[int],
) -> None:
"""Import a single JSON file and write Excel workbooks (one per month)."""
visits = load_place_visits(json_path)
if not visits:
print(f"No place visits found in {json_path}")
return
# 1) Determine date range filter
start_date: Optional[date] = None
end_date: Optional[date] = None
if month:
start_date, end_date = _month_bounds(month)
elif last_month:
start_date, end_date = _prev_month_bounds()
elif since or until:
if since:
start_date = _parse_date(since)
if until:
end_date = _parse_date(until)
elif days:
end_date = _today_local()
start_date = end_date - timedelta(days=days - 1)
# 2) Apply date filtering to visits (by visit.start_time local date)
if start_date or end_date:
def in_range(v):
d = v.start_time.date()
if start_date and d < start_date:
return False
if end_date and d > end_date:
return False
return True
visits = [v for v in visits if in_range(v)]
if not visits:
label = f"{start_date or ''}..{end_date or ''}"
print(f"No place visits in requested range {label}")
return
site_config = SiteConfig.from_yaml(site_config_path)
hops = detect_itinerary(visits, site_config, assume_home_start=assume_home_start)
if not hops:
print("No recognised hops detected after filtering.")
return
# 3) Weekday filter (Sat=5, Sun=6)
if weekdays_only:
hops = [h for h in hops if h.date.weekday() < 5]
if not hops:
print("All hops fell on weekends; nothing to write.")
return
resolver = DistanceResolver(route_csv_path)
rows_by_month = build_monthly_rows(hops, site_config, resolver)
# 4) Write one workbook per month present
os.makedirs(output_dir, exist_ok=True)
for month_key, rows in sorted(rows_by_month.items()):
# If a specific month/range was requested, rows_by_month will already reflect it.
output_path = os.path.join(output_dir, f"mileage_{month_key}.xlsx")
write_monthly_workbook({month_key: rows}, output_path)
print(f"Wrote {output_path} ({len(rows)} rows)")
def main(argv: list[str] | None = None) -> None:
parser = argparse.ArgumentParser(description="Mileage logging tool")
subparsers = parser.add_subparsers(dest="command", required=True)
import_parser = subparsers.add_parser("import", help="Import a single JSON export")
import_parser.add_argument("json_path", help="Path to the JSON file to import")
import_parser.add_argument(
"--sites", dest="site_config_path",
default=os.path.join(os.path.dirname(__file__), "../config/sites.yml"),
help="Path to the sites.yml configuration",
)
import_parser.add_argument(
"--routes", dest="route_csv_path",
default=os.path.join(os.path.dirname(__file__), "../tests/data/routes_golden.csv"),
help="Path to the routes CSV catalogue",
)
import_parser.add_argument(
"--output", dest="output_dir", default=os.getcwd(),
help="Directory to write the Excel workbook(s)",
)
# Behavior toggles
import_parser.add_argument(
"--no-assume-home-start", action="store_true",
help="Do not inject a Home→first-site hop when a day doesn't start at Home.",
)
import_parser.add_argument(
"--weekdays-only", action="store_true",
help="Exclude Saturday/Sunday hops.",
)
# Date filters (choose one style)
import_parser.add_argument("--last-month", action="store_true",
help="Process the previous calendar month.")
import_parser.add_argument("--month", metavar="YYYY-MM",
help="Process a specific calendar month, e.g. 2025-08.")
import_parser.add_argument("--since", metavar="YYYY-MM-DD",
help="Lower bound (inclusive) for visits to process.")
import_parser.add_argument("--until", metavar="YYYY-MM-DD",
help="Upper bound (inclusive) for visits to process.")
import_parser.add_argument("--days", type=int,
help="Process the last N days (relative to today).")
args = parser.parse_args(argv)
if args.command == "import":
import_file(
args.json_path,
args.site_config_path,
args.route_csv_path,
args.output_dir,
assume_home_start=(not args.no_assume_home_start),
weekdays_only=args.weekdays_only,
month=args.month,
last_month=args.last_month,
since=args.since,
until=args.until,
days=args.days,
)
if __name__ == "__main__":
main()