grownxt-server / scripts /generate_report.py
StavanShah01's picture
perf(reporting, rag): optimize end-to-end latency and concurrent execution
2ac7dee
Raw History Blame Contribute Delete
3.6 kB
"""Command-line entry point for GrowNXT institutional PDF reports.
Usage:
venv/Scripts/python.exe scripts/generate_report.py WIPRO
venv/Scripts/python.exe scripts/generate_report.py WIPRO INFY --refresh
"""
import argparse
import logging
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from core.config import safe_ticker # noqa: E402
from reporting.engine import ReportError, generate_report # noqa: E402
from scripts import cli # noqa: E402
def resolve_symbol(ticker_or_name: str) -> str:
"""Resolves an exchange symbol or company name to the canonical ticker symbol."""
raw = (ticker_or_name or "").strip()
if not raw:
return ""
upper = safe_ticker(raw)
# 1. Check if local directory cache already exists
try:
from core.config import OUTPUT_DIR
if (OUTPUT_DIR / upper / "api" / "summary.json").exists() or (OUTPUT_DIR / upper / "summary.json").exists():
return upper
except Exception:
pass
# 2. Lookup via stock discovery search API
try:
from api.search import find
hits = find(raw)
if hits:
resolved = str(hits[0].get("ticker") or hits[0].get("sid") or upper).upper()
if resolved != upper:
logging.getLogger("generate_report").info("Resolved company '%s' -> ticker '%s'", raw, resolved)
return safe_ticker(resolved)
except Exception:
pass
return upper
def _parser() -> argparse.ArgumentParser:
"""Builds the argument parser."""
p = argparse.ArgumentParser(description="Generate an institutional PDF equity report.")
p.add_argument("tickers", nargs="+", help="Ticker symbols or company names, e.g. WIPRO, Infosys")
p.add_argument("--output-dir", type=Path,
help="Root of the per-ticker build workspace (default: output/).")
p.add_argument("--keep-build", action="store_true",
help="Keep the chart SVGs and Typst source beside the PDF.")
p.add_argument("--refresh", action="store_true",
help="Re-request collector data instead of using the local cache.")
p.add_argument("--as-of", help="Display date for the header (default: today).")
p.add_argument("--quiet", action="store_true", help="Log warnings and errors only.")
return p
def main() -> int:
"""Generates one report per ticker.
Returns:
0 when every report was written, 1 otherwise.
"""
args = _parser().parse_args()
cli.setup(logging.WARNING if args.quiet else logging.INFO,
cli.PLAIN, stream=sys.stderr)
failed = []
for ticker_input in args.tickers:
sym = resolve_symbol(ticker_input)
try:
path = generate_report(sym, output_dir=args.output_dir,
refresh=args.refresh, as_of=args.as_of,
keep_build=args.keep_build)
except Exception as exc: # noqa: BLE001 - report and continue the batch
# A ReportError is about this company's data; anything else is a
# defect, and the label says which so a batch log stays readable.
label = "FAILED" if isinstance(exc, ReportError) else "ERROR"
print("%-12s %s: %s" % (sym, label, exc), file=sys.stderr)
failed.append(sym)
else:
print("%-12s %s" % (sym, path))
if failed:
print("failed: %s" % ", ".join(failed), file=sys.stderr)
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main())