#!/usr/bin/env python3 """WP44 — category-level actor and impact indicators (after classification): per primary category and year — works, share with a China affiliation (any author country CN), share with a company affiliation, international share, share with an arXiv version, mean/median c36 and Gini of c36 (complete windows only), mean NCS36, share in top 10 % FWCI. Outputs results/bibliometrics/category_actors_yearly.csv and category_impact_yearly.csv (read-only DuckDB).""" import argparse, os, duckdb def main(): ap = argparse.ArgumentParser(); ap.add_argument("--root", required=True); a = ap.parse_args(); R = a.root out = f"{R}/results/bibliometrics"; con = duckdb.connect(f"{R}/data/processed/wp44.duckdb", read_only=True); con.execute(f"SET threads TO 16; SET memory_limit='{os.environ.get('WP44_DUCKDB_MEM', '30GB')}';") P = f"read_parquet('{R}/data/processed/classification/primary/*.parquet')"; W = f"read_parquet('{R}/data/processed/corpus/work_citation_windows.parquet')" con.execute(f"""COPY (SELECT c.publication_year AS year, c.period, p.primary_category, count(*) n, round(avg(list_contains(c.countries, 'CN')::int), 4) share_cn, round(avg(list_contains(c.countries, 'US')::int), 4) share_us, round(avg(list_contains(c.institution_types, 'company')::int), 4) share_company, round(avg((c.n_countries >= 2)::int), 4) share_intl, round(avg((c.arxiv_id_linked IS NOT NULL)::int), 4) share_arxiv, round(avg(c.is_oa::int), 4) share_oa, round(avg(c.n_authors), 3) mean_authors FROM corpus_works c JOIN {P} p ON p.work_id = c.id WHERE c.eligible GROUP BY 1,2,3 ORDER BY 1,3) TO '{out}/category_actors_yearly.csv' (HEADER)""") con.execute(f"""COPY (WITH J AS (SELECT w.year, w.c36, w.c36_complete, w.ncs36, w.work_id, p.primary_category, c.is_top10 FROM {W} w JOIN {P} p ON p.work_id = w.work_id JOIN corpus_works c ON c.id = w.work_id), Rk AS (SELECT *, row_number() OVER (PARTITION BY year, primary_category ORDER BY c36, work_id) rk FROM J) SELECT year, primary_category, count(*) n, any_value(c36_complete) c36_complete, round(avg(c36), 3) mean_c36, quantile_cont(c36, 0.5) med_c36, round(sum((c36 = 0)::int) * 1.0 / count(*), 4) share_uncited_36, round(avg(ncs36), 3) mean_ncs36, round((2 * sum(c36 * rk) / (count(*) * sum(c36)) - (count(*) + 1.0) / count(*)), 4) gini_c36, round(avg(is_top10::int), 4) share_top10_fwci FROM Rk GROUP BY 1,2 HAVING count(*) >= 200 ORDER BY 1,2) TO '{out}/category_impact_yearly.csv' (HEADER)""") print(open(f"{out}/category_actors_yearly.csv").read()[:600]) if __name__ == "__main__": main()