"""Build a shareable `shares/` folder from the EDGAR research DB. Exports research-ready datasets (parquet + CSV), metadata, diagnostics, the PDF report and figures, plus a text sample — everything small enough to share without the 88 GB of raw filings. """ from pathlib import Path import shutil import json import duckdb ROOT = Path(__file__).resolve().parent.parent SHARES = ROOT / "shares" PARQUET = ROOT / "data" / "parquet" # (source parquet dir, output base name, friendly description) DATASETS = [ ("annual_final", "annual_panel_final", "Annual firm-year panel WITH engineered features (deltas, rolling, novelty)"), ("quarterly_final", "quarterly_panel_final", "Quarterly firm-quarter panel with sector labels"), ("filing_scores", "filing_scores", "One row per individual filing with 8 research-construct scores"), ("filings", "filings_index", "Master index of every filing downloaded"), ("companies", "companies", "Company reference table: S&P 500 ∪ Russell 3000 (CIK, ticker, sector, SIC, index flags)"), ] def reset_dir(p: Path): if p.exists(): shutil.rmtree(p) p.mkdir(parents=True, exist_ok=True) def main(): reset_dir(SHARES) datasets_dir = SHARES / "datasets" meta_dir = SHARES / "metadata" diag_dir = SHARES / "diagnostics" report_dir = SHARES / "report" samples_dir = SHARES / "samples" for d in (datasets_dir, meta_dir, diag_dir, report_dir, samples_dir): d.mkdir(parents=True, exist_ok=True) con = duckdb.connect() manifest = [] # 1. Research-ready datasets -> parquet + csv for src, out, desc in DATASETS: glob = f"{PARQUET / src}/**/*.parquet" df = con.execute(f"SELECT * FROM read_parquet('{glob}')").df() pq = datasets_dir / f"{out}.parquet" csv = datasets_dir / f"{out}.csv" df.to_parquet(pq, index=False, compression="zstd") df.to_csv(csv, index=False) manifest.append({"name": out, "rows": len(df), "cols": df.shape[1], "desc": desc}) print(f" [dataset] {out:24s} {len(df):>7,} rows x {df.shape[1]:>3} cols") # 2. Metadata JSON (constituents + cik mapping; skip the noisy download log) for fn in ("universe.json", "sp500_constituents.json", "russell3000_constituents.json", "cik_mapping.json"): s = ROOT / "data" / "metadata" / fn if s.exists(): shutil.copy2(s, meta_dir / fn) print(f" [metadata] {fn}") # 3. Diagnostics (CSV summaries + coverage report) for f in sorted((ROOT / "outputs" / "diagnostics").glob("*")): if f.suffix in (".csv", ".json", ".txt"): shutil.copy2(f, diag_dir / f.name) print(f" [diagnostics] {len(list(diag_dir.iterdir()))} files") # 4. Report PDFs + figures latex = ROOT / "outputs" / "latex" for pdf in ("edgar_research_report.pdf", "research_questions.pdf"): if (latex / pdf).exists(): shutil.copy2(latex / pdf, report_dir / pdf) fig_src = latex / "figures" if fig_src.exists(): shutil.copytree(fig_src, report_dir / "figures", dirs_exist_ok=True) print(f" [report] 2 PDFs + figures") # 5. Text sample so reviewers see the underlying parsed content (500 rows) sec_glob = f"{PARQUET / 'filing_sections'}/**/*.parquet" sample = con.execute( f"SELECT accession_number, section_name, item_number, word_count, " f"LEFT(text, 1500) AS text_preview " f"FROM read_parquet('{sec_glob}') USING SAMPLE 500 ROWS" ).df() sample.to_csv(samples_dir / "filing_sections_sample.csv", index=False) print(f" [sample] filing_sections_sample.csv ({len(sample)} rows)") # write a machine-readable manifest (SHARES / "manifest.json").write_text(json.dumps(manifest, indent=2)) print("\nDone -> shares/") if __name__ == "__main__": main()