196 lines
6.2 KiB
Python
196 lines
6.2 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import asyncio
|
||
|
|
import json
|
||
|
|
import sys
|
||
|
|
from datetime import date
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
from services.date_parser import parse_dates_file, parse_dates_text
|
||
|
|
from services.fill_service import FillService
|
||
|
|
|
||
|
|
|
||
|
|
def _print_json(data: object) -> None:
|
||
|
|
print(json.dumps(data, ensure_ascii=False, indent=2))
|
||
|
|
|
||
|
|
|
||
|
|
def _resolve_dates(args: argparse.Namespace) -> list[date]:
|
||
|
|
if args.file:
|
||
|
|
return parse_dates_file(args.file)
|
||
|
|
if args.dates:
|
||
|
|
return parse_dates_text(args.dates)
|
||
|
|
raise ValueError("Provide --file or --dates")
|
||
|
|
|
||
|
|
|
||
|
|
async def cmd_status(_: argparse.Namespace) -> int:
|
||
|
|
service = FillService()
|
||
|
|
_print_json(await service.get_status())
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
async def cmd_teams_test(args: argparse.Namespace) -> int:
|
||
|
|
"""Standalone Teams calendar fetch test (no PTS / GitLab write)."""
|
||
|
|
from services.teams_calendar_scraper import TeamsCalendarScraper
|
||
|
|
|
||
|
|
start = date.fromisoformat(args.start) if args.start else date.today()
|
||
|
|
end = date.fromisoformat(args.end) if args.end else start
|
||
|
|
scraper = TeamsCalendarScraper()
|
||
|
|
result = await scraper.list_meetings(
|
||
|
|
start,
|
||
|
|
end,
|
||
|
|
skip_holidays=not args.include_holidays,
|
||
|
|
debug=True,
|
||
|
|
)
|
||
|
|
_print_json(result)
|
||
|
|
if result.get("error"):
|
||
|
|
return 1
|
||
|
|
if (result.get("total_count") or 0) == 0:
|
||
|
|
print(
|
||
|
|
"\nHINT: 0 meetings. Login Teams via Web UI Settings (MFA), then retry.",
|
||
|
|
file=sys.stderr,
|
||
|
|
)
|
||
|
|
return 2
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
async def cmd_preview(args: argparse.Namespace) -> int:
|
||
|
|
service = FillService()
|
||
|
|
start = date.fromisoformat(args.start)
|
||
|
|
end = date.fromisoformat(args.end)
|
||
|
|
_print_json(await service.preview_plan(start, end, use_llm=not args.no_llm))
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
async def cmd_preview_dates(args: argparse.Namespace) -> int:
|
||
|
|
service = FillService()
|
||
|
|
days = _resolve_dates(args)
|
||
|
|
_print_json(await service.preview_plan_for_days(days, use_llm=not args.no_llm))
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
async def cmd_fill_today(args: argparse.Namespace) -> int:
|
||
|
|
service = FillService()
|
||
|
|
try:
|
||
|
|
service.ensure_ready()
|
||
|
|
except ValueError as exc:
|
||
|
|
print(f"ERROR: {exc}", file=sys.stderr)
|
||
|
|
return 1
|
||
|
|
|
||
|
|
result = await service.fill_today(dry_run=args.dry_run, use_llm=not args.no_llm)
|
||
|
|
_print_json(result)
|
||
|
|
return 0 if result.get("ok") else 1
|
||
|
|
|
||
|
|
|
||
|
|
async def cmd_fill_range(args: argparse.Namespace) -> int:
|
||
|
|
service = FillService()
|
||
|
|
try:
|
||
|
|
service.ensure_ready()
|
||
|
|
except ValueError as exc:
|
||
|
|
print(f"ERROR: {exc}", file=sys.stderr)
|
||
|
|
return 1
|
||
|
|
|
||
|
|
start = date.fromisoformat(args.start)
|
||
|
|
end = date.fromisoformat(args.end)
|
||
|
|
result = await service.fill_dates(
|
||
|
|
start,
|
||
|
|
end,
|
||
|
|
dry_run=args.dry_run,
|
||
|
|
skip_existing=not args.force,
|
||
|
|
use_llm=not args.no_llm,
|
||
|
|
)
|
||
|
|
_print_json(result)
|
||
|
|
return 0 if result.get("ok") else 1
|
||
|
|
|
||
|
|
|
||
|
|
async def cmd_fill_dates(args: argparse.Namespace) -> int:
|
||
|
|
service = FillService()
|
||
|
|
try:
|
||
|
|
service.ensure_ready()
|
||
|
|
except ValueError as exc:
|
||
|
|
print(f"ERROR: {exc}", file=sys.stderr)
|
||
|
|
return 1
|
||
|
|
|
||
|
|
days = _resolve_dates(args)
|
||
|
|
result = await service.fill_date_list(
|
||
|
|
days,
|
||
|
|
dry_run=args.dry_run,
|
||
|
|
skip_existing=not args.force,
|
||
|
|
use_llm=not args.no_llm,
|
||
|
|
)
|
||
|
|
_print_json(result)
|
||
|
|
return 0 if result.get("ok") else 1
|
||
|
|
|
||
|
|
|
||
|
|
def build_parser() -> argparse.ArgumentParser:
|
||
|
|
parser = argparse.ArgumentParser(description="PTS auto-fill CLI")
|
||
|
|
sub = parser.add_subparsers(dest="command", required=True)
|
||
|
|
|
||
|
|
sub.add_parser("status", help="Show system status")
|
||
|
|
|
||
|
|
preview = sub.add_parser("preview", help="Preview fill plan for date range")
|
||
|
|
preview.add_argument("--start", required=True, help="YYYY-MM-DD")
|
||
|
|
preview.add_argument("--end", required=True, help="YYYY-MM-DD")
|
||
|
|
preview.add_argument("--no-llm", action="store_true")
|
||
|
|
|
||
|
|
preview_dates = sub.add_parser("preview-dates", help="Preview fill plan for date list")
|
||
|
|
preview_dates.add_argument("--file", help="Path to dates.txt")
|
||
|
|
preview_dates.add_argument("--dates", help="Comma/newline separated dates")
|
||
|
|
preview_dates.add_argument("--no-llm", action="store_true")
|
||
|
|
|
||
|
|
fill_today = sub.add_parser("fill-today", help="Fill today's PTS entries")
|
||
|
|
fill_today.add_argument("--dry-run", action="store_true")
|
||
|
|
fill_today.add_argument("--no-llm", action="store_true")
|
||
|
|
|
||
|
|
fill_range = sub.add_parser("fill-range", help="Fill PTS entries for date range")
|
||
|
|
fill_range.add_argument("--start", required=True, help="YYYY-MM-DD")
|
||
|
|
fill_range.add_argument("--end", required=True, help="YYYY-MM-DD")
|
||
|
|
fill_range.add_argument("--dry-run", action="store_true")
|
||
|
|
fill_range.add_argument("--force", action="store_true")
|
||
|
|
fill_range.add_argument("--no-llm", action="store_true")
|
||
|
|
|
||
|
|
fill_dates = sub.add_parser("fill-dates", help="Fill PTS entries for explicit date list")
|
||
|
|
fill_dates.add_argument("--file", help="Path to dates.txt")
|
||
|
|
fill_dates.add_argument("--dates", help="Comma/newline separated dates")
|
||
|
|
fill_dates.add_argument("--dry-run", action="store_true")
|
||
|
|
fill_dates.add_argument("--force", action="store_true")
|
||
|
|
fill_dates.add_argument("--no-llm", action="store_true")
|
||
|
|
|
||
|
|
teams_test = sub.add_parser(
|
||
|
|
"teams-test",
|
||
|
|
help="Standalone test: fetch Teams/Outlook meetings only (no PTS write)",
|
||
|
|
)
|
||
|
|
teams_test.add_argument("--start", help="YYYY-MM-DD (default: today)")
|
||
|
|
teams_test.add_argument("--end", help="YYYY-MM-DD (default: start)")
|
||
|
|
teams_test.add_argument(
|
||
|
|
"--include-holidays",
|
||
|
|
action="store_true",
|
||
|
|
help="Do not skip weekends / TW holidays",
|
||
|
|
)
|
||
|
|
|
||
|
|
return parser
|
||
|
|
|
||
|
|
|
||
|
|
async def main() -> int:
|
||
|
|
parser = build_parser()
|
||
|
|
args = parser.parse_args()
|
||
|
|
|
||
|
|
handlers = {
|
||
|
|
"status": cmd_status,
|
||
|
|
"preview": cmd_preview,
|
||
|
|
"preview-dates": cmd_preview_dates,
|
||
|
|
"fill-today": cmd_fill_today,
|
||
|
|
"fill-range": cmd_fill_range,
|
||
|
|
"fill-dates": cmd_fill_dates,
|
||
|
|
"teams-test": cmd_teams_test,
|
||
|
|
}
|
||
|
|
|
||
|
|
try:
|
||
|
|
return await handlers[args.command](args)
|
||
|
|
except ValueError as exc:
|
||
|
|
print(f"ERROR: {exc}", file=sys.stderr)
|
||
|
|
return 1
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
raise SystemExit(asyncio.run(main()))
|