Fed-Sentiment / src /cli.py
github-actions[bot]
Deploy from MaciSekander/fed-sentiment-analyzer@f9d4abf19279af4ef78eea9d53844dd022994fbb
d0622ef
Raw
History Blame Contribute Delete
8.59 kB
"""Command-line entrypoint.
python -m src.cli ingest-local --archives-dir data/archives
python -m src.cli scrape-minutes --start 2015-01-01 --end 2024-12-31
python -m src.cli scrape-speeches --start 2015-01-01 --end 2024-12-31
python -m src.cli analyze --input-dir data/raw/minutes --out data/processed/minutes_scores.csv
python -m src.cli analyze --input-dir data/raw/minutes --out data/processed/minutes_scores.csv --no-model
python -m src.cli trend --input data/processed/minutes_scores.csv --plot data/processed/minutes_trend.png
python -m src.cli history --input data/processed/minutes_scores.csv --out backend/app/static/history.json
python -m src.cli documents --scores data/processed/minutes_scores.csv --out backend/app/static/documents/
python -m src.cli fetch-fedfunds --out backend/app/static/fedfunds.json
"""
from __future__ import annotations
import argparse
from datetime import datetime
from pathlib import Path
from src.analysis.documents import build_all_document_details
from src.analysis.history import build_history
from src.analysis.trends import plot_trend, scores_to_dataframe
from src.ingestion.fed_funds import build_fedfunds_payload, fetch_fedfunds
from src.ingestion.local_archives import ingest_all
from src.scraper.fed_speeches import scrape_speeches
from src.scraper.fomc_minutes import scrape_minutes
from src.sentiment.pipeline import analyze_corpus
def _cmd_ingest_local(args: argparse.Namespace) -> None:
results = ingest_all(
archives_dir=Path(args.archives_dir),
minutes_out=Path(args.minutes_out),
statements_out=Path(args.statements_out),
)
print(f"Ingested {len(results['minutes'])} minutes documents to {args.minutes_out}")
print(f"Ingested {len(results['statements'])} statement documents to {args.statements_out}")
def _cmd_scrape_minutes(args: argparse.Namespace) -> None:
start = datetime.strptime(args.start, "%Y-%m-%d").date()
end = datetime.strptime(args.end, "%Y-%m-%d").date()
saved = scrape_minutes(start, end, Path(args.out))
print(f"Saved {len(saved)} minutes documents to {args.out}")
def _cmd_scrape_speeches(args: argparse.Namespace) -> None:
start = datetime.strptime(args.start, "%Y-%m-%d").date()
end = datetime.strptime(args.end, "%Y-%m-%d").date()
saved = scrape_speeches(start, end, Path(args.out))
print(f"Saved {len(saved)} speech documents to {args.out}")
def _cmd_analyze(args: argparse.Namespace) -> None:
scores = analyze_corpus(
Path(args.input_dir),
use_model=not args.no_model,
model_name=args.model_name,
model_weight=args.model_weight,
)
if not scores:
print(f"No .txt documents found in {args.input_dir}")
return
df = scores_to_dataframe(scores)
out_path = Path(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
df.to_csv(out_path, index=False)
print(f"Wrote {len(df)} document scores to {out_path}")
print(df[["doc_id", "date", "combined_score", "combined_label"]].to_string(index=False))
def _cmd_trend(args: argparse.Namespace) -> None:
import pandas as pd
df = pd.read_csv(args.input, parse_dates=["date"])
out = plot_trend(df, Path(args.plot), window=args.window)
print(f"Saved trend plot to {out}")
def _cmd_history(args: argparse.Namespace) -> None:
import json
import pandas as pd
df = pd.read_csv(args.input, parse_dates=["date"])
payload = build_history(df, window=args.window, gap_threshold_days=args.gap_threshold_days)
out_path = Path(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(payload, indent=2))
print(f"Wrote {len(payload['points'])} points, {len(payload['regimes'])} regimes to {out_path}")
def _cmd_documents(args: argparse.Namespace) -> None:
import json
details = build_all_document_details(Path(args.input_dir), Path(args.scores))
out_dir = Path(args.out)
out_dir.mkdir(parents=True, exist_ok=True)
for detail in details:
(out_dir / f"{detail['doc_id']}.json").write_text(json.dumps(detail))
print(f"Wrote {len(details)} document detail files to {out_dir}")
def _cmd_fetch_fedfunds(args: argparse.Namespace) -> None:
import json
df = fetch_fedfunds()
payload = build_fedfunds_payload(df)
out_path = Path(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(payload, indent=2))
print(f"Wrote {len(payload['points'])} monthly Fed funds rate points to {out_path}")
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="fed-sentiment-analyzer")
subparsers = parser.add_subparsers(dest="command", required=True)
p_ingest = subparsers.add_parser(
"ingest-local", help="Ingest pre-collected archives from data/archives/ (no network required)"
)
p_ingest.add_argument("--archives-dir", default="data/archives")
p_ingest.add_argument("--minutes-out", default="data/raw/minutes")
p_ingest.add_argument("--statements-out", default="data/raw/statements")
p_ingest.set_defaults(func=_cmd_ingest_local)
p_minutes = subparsers.add_parser("scrape-minutes", help="Scrape historical FOMC minutes")
p_minutes.add_argument("--start", required=True, help="Start date, YYYY-MM-DD")
p_minutes.add_argument("--end", required=True, help="End date, YYYY-MM-DD")
p_minutes.add_argument("--out", default="data/raw/minutes")
p_minutes.set_defaults(func=_cmd_scrape_minutes)
p_speeches = subparsers.add_parser("scrape-speeches", help="Scrape Fed speeches")
p_speeches.add_argument("--start", required=True, help="Start date, YYYY-MM-DD")
p_speeches.add_argument("--end", required=True, help="End date, YYYY-MM-DD")
p_speeches.add_argument("--out", default="data/raw/speeches")
p_speeches.set_defaults(func=_cmd_scrape_speeches)
p_analyze = subparsers.add_parser("analyze", help="Score a directory of .txt documents")
p_analyze.add_argument("--input-dir", required=True)
p_analyze.add_argument("--out", required=True, help="Output CSV path")
p_analyze.add_argument("--no-model", action="store_true", help="Lexicon-only, skip the transformer model")
p_analyze.add_argument("--model-name", default=None, help="Override the HuggingFace model to use")
p_analyze.add_argument("--model-weight", type=float, default=0.5, help="Weight of model score vs lexicon score, 0-1")
p_analyze.set_defaults(func=_cmd_analyze)
p_trend = subparsers.add_parser("trend", help="Plot hawkish/dovish trend over time from a scores CSV")
p_trend.add_argument("--input", required=True, help="CSV produced by `analyze`")
p_trend.add_argument("--plot", required=True, help="Output PNG path")
p_trend.add_argument("--window", type=int, default=3, help="Rolling average window, in documents")
p_trend.set_defaults(func=_cmd_trend)
p_history = subparsers.add_parser(
"history", help="Build the JSON payload served by GET /api/history from a scores CSV"
)
p_history.add_argument("--input", required=True, help="CSV produced by `analyze`")
p_history.add_argument("--out", required=True, help="Output JSON path, e.g. backend/app/static/history.json")
p_history.add_argument("--window", type=int, default=3, help="Rolling average window, in documents")
p_history.add_argument("--gap-threshold-days", type=int, default=180, help="Gap size that breaks the line/adds a gap annotation")
p_history.set_defaults(func=_cmd_history)
p_documents = subparsers.add_parser(
"documents", help="Build per-document JSON detail (text + highlighted phrase spans) served by GET /api/documents/{doc_id}"
)
p_documents.add_argument("--input-dir", default="data/raw/minutes")
p_documents.add_argument("--scores", required=True, help="CSV produced by `analyze`")
p_documents.add_argument("--out", required=True, help="Output directory, e.g. backend/app/static/documents/")
p_documents.set_defaults(func=_cmd_documents)
p_fedfunds = subparsers.add_parser(
"fetch-fedfunds", help="Fetch the historical effective Fed funds rate from FRED and build the GET /api/fedfunds payload"
)
p_fedfunds.add_argument("--out", required=True, help="Output JSON path, e.g. backend/app/static/fedfunds.json")
p_fedfunds.set_defaults(func=_cmd_fetch_fedfunds)
return parser
def main() -> None:
parser = build_parser()
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()