# SPDX-License-Identifier: Apache-2.0 # Copyright 2026 Aaron K. Clark """Command-line entry point for the applytrack poller. `applytrack poll` runs the job-discovery poller once (intended to be wired to an hourly cron / systemd timer). The web UI is served by the .NET API in this rewrite, so `serve`/`gencert` no longer live here. """ from __future__ import annotations import argparse import os from pathlib import Path def _default_dir() -> Path: return Path(os.environ.get("APPLYTRACK_DIR") or (Path.cwd() / "applications")) def _print_multi(results: dict[int, list[str]], scope: str) -> None: total = sum(len(names) for names in results.values()) print( f"applytrack poll: {total} new lead(s) added across {len(results)} {scope}." ) for tid, names in sorted(results.items()): for name in names: print(f" + [{tid}] {name}") def _poll(args: argparse.Namespace) -> int: from applytrack.db import PollRepo from applytrack.importer import connect from applytrack.poll import run_poll from applytrack.worker import _mark_polled, drain_requests, run_all_tenants with connect(args.database_url) as conn: # Each lead/seen-key stands alone: a slug collision must skip one listing, # not abort the run, so commit per-statement rather than one big txn. The # on-demand drain also relies on autocommit to claim its queue rows. conn.autocommit = True if args.drain: # Fast cron: service the on-demand /api/poll queue, polling only the # tenants whose button was pressed since the last drain. _print_multi(drain_requests(conn, limit_per_source=args.limit), "queued tenant(s)") return 0 if args.tenant is None: # Cron mode: fan out across every active tenant, fetching sources once. # --ats-only is the freshness fast lane: only the tenants' ATS boards, so it # can run far more often than the full poll without touching aggregator limits. _print_multi( run_all_tenants( conn, limit_per_source=args.limit, ats_only=args.ats_only ), "active tenant(s)", ) return 0 repo = PollRepo(conn, args.tenant) profile = repo.load_profile() added = run_poll(repo, profile, limit_per_source=args.limit) _mark_polled(repo, args.tenant) print(f"applytrack poll: {len(added)} new lead(s) added for tenant {args.tenant}.") for name in added: print(f" + {name}") return 0 def _import_md(args: argparse.Namespace) -> int: from applytrack.importer import connect, import_markdown data_dir = Path(args.dir).expanduser().resolve() with connect(args.database_url) as conn: imported = import_markdown(conn, data_dir, args.tenant) print( f"applytrack import-md: {len(imported)} application(s) " f"imported into tenant {args.tenant}.") for name in imported: print(f" + {name}") return 0 def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(prog="applytrack", description=__doc__) sub = parser.add_subparsers(dest="cmd", required=True) p_poll = sub.add_parser("poll", help="fetch new matching leads from job boards") p_poll.add_argument( "--tenant", type=int, default=None, help="tenant_id to poll for (default: every active tenant — cron mode)") p_poll.add_argument( "--drain", action="store_true", help="service the on-demand poll queue (poll_requests) — run on a fast cron") p_poll.add_argument( "--ats-only", action="store_true", help="poll only the tenants' ATS boards (Greenhouse/Lever/Ashby) — the " "freshness fast lane; run on a shorter cadence than the full poll") p_poll.add_argument("--limit", type=int, default=40, help="max results per source to scan") p_poll.add_argument( "--database-url", default=None, help="libpq connection URL; falls back to DATABASE_URL / POSTGRES_* env") p_poll.set_defaults(func=_poll) p_import = sub.add_parser( "import-md", help="one-shot: load existing Markdown apps into Postgres") p_import.add_argument("--dir", default=str(_default_dir()), help="applications data folder") p_import.add_argument("--tenant", type=int, default=1, help="tenant_id to import into") p_import.add_argument( "--database-url", default=None, help="libpq connection URL; falls back to DATABASE_URL / POSTGRES_* env") p_import.set_defaults(func=_import_md) args = parser.parse_args(argv) return int(args.func(args)) if __name__ == "__main__": raise SystemExit(main())