"""`catlas` seed commands: `seed`, `import-companies`, `company-add`, `registry-stats` (docs/SEEDS.md).""" from __future__ import annotations from collections import Counter from pathlib import Path from typing import Annotated import typer from rich.table import Table def _print_counters(counters: dict[str, int]) -> None: from companyatlas.cli import out table = Table(title="seed", show_header=False) for k, v in counters.items(): table.add_row(k, str(v)) out.print(table) def register(app: typer.Typer) -> None: @app.command("seed") def seed_cmd( no_companies: Annotated[bool, typer.Option("--no-companies", help="only industries + countries")] = False, limit: Annotated[int | None, typer.Option("--limit", help="stop after N registry rows")] = None, file: Annotated[list[Path] | None, typer.Option("--file", help="NDJSON file(s) instead of registry/companies/*.ndjson")] = None, ) -> None: """Load the seed registry (idempotent): industries, countries, companies + discover queue.""" from companyatlas.cli import run_async from companyatlas.db import transaction from companyatlas.registry.seed import seed async def go() -> dict[str, int]: async with transaction() as conn: return await seed(conn, companies=not no_companies, limit=limit, files=file or None) _print_counters(run_async(go())) @app.command("import-companies") def import_cmd(path: Annotated[Path, typer.Argument(help=".ndjson / .jsonl / .json / .csv with a `website` column")], source: Annotated[str, typer.Option("--source")] = "manual") -> None: """Import companies from a manual file (`website` required; `display_name` derived from the domain when missing).""" from companyatlas.cli import run_async from companyatlas.db import transaction from companyatlas.registry.seed import import_companies, read_rows_file rows = read_rows_file(path) async def go() -> dict[str, int]: async with transaction() as conn: return await import_companies(conn, rows, source=source) _print_counters(run_async(go())) @app.command("company-add") def add_cmd(website: Annotated[str, typer.Argument()], name: Annotated[str | None, typer.Option("--name")] = None, country: Annotated[str | None, typer.Option("--country", help="ISO-2")] = None, industry: Annotated[list[str] | None, typer.Option("--industry", help="taxonomy slug (repeatable)")] = None) -> None: """Add one company by website and queue its discovery.""" from companyatlas.cli import out, run_async from companyatlas.db import transaction from companyatlas.registry.seed import add_company async def go() -> dict: async with transaction() as conn: return await add_company(conn, website, display_name=name, country=country, industries=industry or []) row = run_async(go()) counters = row.pop("counters", {}) out.print(row) _print_counters(counters) @app.command("registry-stats") def stats_cmd(top: Annotated[int, typer.Option("--top", help="rows per table")] = 30) -> None: """Counts by country / industry / tier from the registry NDJSON files (no database).""" from companyatlas.cli import out from companyatlas.registry.industries import industry_index, top_level_of from companyatlas.registry.seed import load_countries_file, load_registry_rows, registry_files rows = load_registry_rows() countries = {c["code"]: c for c in load_countries_file()} names = {slug: ind.name for slug, ind in industry_index().items()} out.print(f"[bold]{len(rows)}[/] companies in {len(registry_files())} file(s); " f"{sum(1 for r in rows if r.get('public_company'))} public; {sum(1 for r in rows if not r.get('industries'))} without industry") def table(title: str, counter: Counter, label=lambda k: k) -> None: # type: ignore[no-untyped-def] t = Table(title=title) t.add_column("key") t.add_column("companies", justify="right") t.add_column("share", justify="right") for k, v in counter.most_common(top): t.add_row(str(label(k)), str(v), f"{100 * v / max(1, len(rows)):.1f} %") out.print(t) table("tiers", Counter(r.get("tier") for r in rows)) table("regions", Counter(countries.get(r.get("country") or "", {}).get("region") or "unknown" for r in rows)) table("countries", Counter(r.get("country") or "??" for r in rows), lambda k: f"{k} {countries.get(k, {}).get('name', '')}".strip()) table("top-level industries", Counter(t for r in rows for t in {top_level_of(s) for s in r.get("industries", [])}), lambda k: names.get(k, k)) table("industries", Counter(s for r in rows for s in r.get("industries", [])), lambda k: names.get(k, k))