SPB Git

spb/findata

Public

cli: worker --role, plan-all, backfill-all, options, fundamentals --all-active

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Nov 2, 2026) parent 2baf463 Browse files

1 changed file +51 −3

Unified Split
modified services/ingest/src/findata_ingest/cli.py +51 −3
@@ -44,10 +44,58 @@ def discover(source: str = "all") -> None:
44 44
45 45
46 46 @app.command()
47 −def worker(once: bool = False, no_scheduler: bool = False, kinds: str | None = None) -> None:
48 − """Run the single ingest worker (+ scheduler)."""
47 +def worker(once: bool = False, no_scheduler: bool = False, kinds: str | None = None, role: str = "primary") -> None:
48 + """Run an ingest worker: role=primary (lake lock + scheduler) or role=bulk (parallel backfill/options/fundamentals)."""
49 49 from findata_ingest.worker import main
50 − main(once=once, kinds=kinds.split(",") if kinds else None, with_scheduler=not no_scheduler)
50 + main(once=once, kinds=kinds.split(",") if kinds else None, with_scheduler=not no_scheduler, role=role)
51 +
52 +
53 +@ingest_app.command("plan-all")
54 +def ingest_plan_all(types: str | None = None, no_options: bool = False, no_delisted: bool = False) -> None:
55 + """Show the complete vendor download plan (counts per dataset), without enqueuing anything."""
56 + from collections import Counter
57 +
58 + from findata_market_firstrate import build_full_plan
59 + plan = build_full_plan(types.split(",") if types else None, include_options=not no_options, include_delisted=not no_delisted)
60 + typer.echo(f"{len(plan)} jobs")
61 + for ds, n in sorted(Counter(j["dataset_id"] for j in plan).items()):
62 + typer.echo(f" {ds:<48} {n}")
63 +
64 +
65 +@ingest_app.command("backfill-all")
66 +def ingest_backfill_all(types: str | None = None, no_options: bool = False, no_delisted: bool = False, yes: bool = False) -> None:
67 + """Enqueue the complete vendor download (every licensed archive). Needs the cold lake root (external SSD)."""
68 + from findata_core.config import get_settings
69 + from findata_market_firstrate import build_full_plan
70 +
71 + from findata_ingest import queue
72 + from findata_ingest.lake import Lake
73 + lake = Lake()
74 + s = get_settings()
75 + free_hot = lake.free_gb()
76 + free_cold = lake.free_gb(lake.cold) if lake.cold else None
77 + typer.echo(f"hot root {lake.root}: {free_hot:.0f} GB free · cold root {lake.cold}: {free_cold if free_cold is None else round(free_cold)} GB free")
78 + if not lake.cold:
79 + typer.echo("refusing: FINDATA_LAKE_COLD is not configured (the full catalog needs the external SSD)")
80 + raise typer.Exit(2)
81 + if free_cold < 1200 or free_hot < 300:
82 + typer.echo("refusing: need >= 1200 GB free on the cold root and >= 300 GB on the hot root for the full catalog")
83 + raise typer.Exit(2)
84 + plan = build_full_plan(types.split(",") if types else None, include_options=not no_options, include_delisted=not no_delisted)
85 + if not yes and not typer.confirm(f"enqueue {len(plan)} jobs?"):
86 + raise typer.Exit(1)
87 + n = 0
88 + for j in plan:
89 + if queue.enqueue(j["kind"], j["dataset_id"], j["params"], j["priority"], dedupe_key=j["dedupe_key"], created_by="cli:backfill-all", max_attempts=4):
90 + n += 1
91 + typer.echo(f"enqueued {n} new jobs ({len(plan) - n} already queued/running); bulk workers configured: {s.bulk_workers}")
92 +
93 +
94 +@ingest_app.command("options")
95 +def ingest_options(year: int | None = None, quarter: str | None = None, month: bool = False) -> None:
96 + from findata_ingest import queue
97 + params = {"period": "month"} if month else {"year": year, "quarter": quarter}
98 + typer.echo(queue.enqueue("options", "options.eod", params, priority=55, created_by="cli"))
51 99
52 100
53 101 @ingest_app.command("metadata")
54 102
Menu

Theme