Spaces:
Running
Running
File size: 8,593 Bytes
d0622ef | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | """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()
|