puremacro 0.92.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- puremacro/__init__.py +8 -0
- puremacro/_backend.py +92 -0
- puremacro/_cache_db.py +216 -0
- puremacro/_codes.py +31 -0
- puremacro/_http.py +198 -0
- puremacro/_http_cache.py +158 -0
- puremacro/_linalg.py +131 -0
- puremacro/bartik/__init__.py +14 -0
- puremacro/bartik/county_epu.py +84 -0
- puremacro/bartik/exposure_iv.py +110 -0
- puremacro/bartik/sensitivity.py +98 -0
- puremacro/bartik/shares.py +119 -0
- puremacro/bis_neer.py +154 -0
- puremacro/build_panel.py +841 -0
- puremacro/build_subnational_panel.py +559 -0
- puremacro/cache.py +158 -0
- puremacro/climate/__init__.py +25 -0
- puremacro/climate/annual_lp.py +75 -0
- puremacro/climate/degree_days.py +73 -0
- puremacro/climate/mediation.py +120 -0
- puremacro/climate/monthly_dl.py +201 -0
- puremacro/cointegration_modern.py +355 -0
- puremacro/connectedness/__init__.py +4 -0
- puremacro/connectedness/diebold_yilmaz.py +109 -0
- puremacro/credentials.py +188 -0
- puremacro/cross_nest.py +390 -0
- puremacro/cycles.py +86 -0
- puremacro/data.py +179 -0
- puremacro/did/__init__.py +49 -0
- puremacro/did/_results.py +314 -0
- puremacro/did/borusyak_jaravel_spiess.py +192 -0
- puremacro/did/callaway_santanna.py +196 -0
- puremacro/did/cdh.py +344 -0
- puremacro/did/sdid_multi.py +269 -0
- puremacro/did/sun_abraham.py +121 -0
- puremacro/did/synthetic_did.py +200 -0
- puremacro/did/types.py +81 -0
- puremacro/dsge/__init__.py +32 -0
- puremacro/dsge/_results.py +126 -0
- puremacro/dsge/_sw07_data.csv +170 -0
- puremacro/dsge/estimate.py +267 -0
- puremacro/dsge/fertility_adj_costs.py +866 -0
- puremacro/dsge/gensys.py +160 -0
- puremacro/dsge/klein.py +367 -0
- puremacro/dsge/load_dynare.py +125 -0
- puremacro/dsge/priors.py +124 -0
- puremacro/dsge/smets_wouters.py +877 -0
- puremacro/dsge/sw07_estimate.py +86 -0
- puremacro/dsge/sw07_observation.py +119 -0
- puremacro/dsge/sw07_priors.py +108 -0
- puremacro/dynpanel/__init__.py +32 -0
- puremacro/dynpanel/_results.py +119 -0
- puremacro/dynpanel/ab_gmm.py +334 -0
- puremacro/dynpanel/bb_gmm.py +476 -0
- puremacro/dynpanel/diagnostics.py +253 -0
- puremacro/dynpanel/instruments.py +627 -0
- puremacro/examples/__init__.py +5 -0
- puremacro/examples/asset_composition_dynamics.py +168 -0
- puremacro/examples/berkowitz_demo.py +66 -0
- puremacro/examples/bloom2009.py +119 -0
- puremacro/examples/bvar_fan_chart.py +123 -0
- puremacro/examples/caldara_iacoviello.py +63 -0
- puremacro/examples/cointegration_johansen.py +67 -0
- puremacro/examples/dcc_volatility.py +90 -0
- puremacro/examples/dfm_bai_ng.py +93 -0
- puremacro/examples/dfm_nowcast_kalman.py +115 -0
- puremacro/examples/did_callaway_santanna_demo.py +95 -0
- puremacro/examples/diebold_yilmaz_spillover.py +58 -0
- puremacro/examples/dsge_ar1_demo.py +63 -0
- puremacro/examples/dsge_fertility_demo.py +39 -0
- puremacro/examples/dsge_rbc_klein.py +157 -0
- puremacro/examples/exact_diffuse_kalman.py +101 -0
- puremacro/examples/fetch_fred_demo.py +112 -0
- puremacro/examples/fm_ols_dols_demo.py +107 -0
- puremacro/examples/forecast_density_eval.py +124 -0
- puremacro/examples/gali_1999_hours.py +210 -0
- puremacro/examples/gfevd_spillover.py +85 -0
- puremacro/examples/gk_robust_from_gibbs.py +97 -0
- puremacro/examples/gk_robust_signs.py +106 -0
- puremacro/examples/global_fiscal_stance.py +111 -0
- puremacro/examples/glp_lambda_search.py +89 -0
- puremacro/examples/govt_vs_private_investment.py +134 -0
- puremacro/examples/har_realized_vol.py +122 -0
- puremacro/examples/hfi_gertler_karadi.py +72 -0
- puremacro/examples/kilian_2009_oil.py +301 -0
- puremacro/examples/la_lp_pmw_demo.py +115 -0
- puremacro/examples/labor_flows_demo.py +102 -0
- puremacro/examples/lp_asymmetric_tenreyro.py +58 -0
- puremacro/examples/lp_panel_dk.py +52 -0
- puremacro/examples/lp_smooth_demo.py +66 -0
- puremacro/examples/lwui_wage_demo.py +96 -0
- puremacro/examples/mcmc_diagnostics.py +102 -0
- puremacro/examples/midas_quarterly_monthly.py +105 -0
- puremacro/examples/mle_sandwich_demo.py +95 -0
- puremacro/examples/ms_var_business_cycle.py +117 -0
- puremacro/examples/narrative_custom_corpus.py +115 -0
- puremacro/examples/narrative_event_study.py +171 -0
- puremacro/examples/narrative_g7_panel.py +192 -0
- puremacro/examples/narrative_homogeneous_panel.py +202 -0
- puremacro/examples/narrative_indices_demo.py +75 -0
- puremacro/examples/narrative_llm_pipeline.py +93 -0
- puremacro/examples/narrative_local_llm.py +65 -0
- puremacro/examples/narrative_multilingual_live.py +145 -0
- puremacro/examples/narrative_panel_lp.py +150 -0
- puremacro/examples/narrative_ramey_2011.py +158 -0
- puremacro/examples/narrative_romer_romer_2010.py +144 -0
- puremacro/examples/narrative_sign_adrr.py +163 -0
- puremacro/examples/narrative_us_canonical_compare.py +194 -0
- puremacro/examples/non_gaussian_svar.py +94 -0
- puremacro/examples/posterior_predictive_fan.py +83 -0
- puremacro/examples/ramey_zubairy_2018_multipliers.py +392 -0
- puremacro/examples/regime_workflow.py +107 -0
- puremacro/examples/report_workflow.py +79 -0
- puremacro/examples/shock_atlas_demo.py +64 -0
- puremacro/examples/sigma_decomposition.py +113 -0
- puremacro/examples/sign_restrictions_uhlig.py +95 -0
- puremacro/examples/spectral_business_cycle.py +93 -0
- puremacro/examples/svariv_mertens_ravn.py +212 -0
- puremacro/examples/synthetic_control_demo.py +116 -0
- puremacro/examples/tvar_threshold_demo.py +89 -0
- puremacro/examples/tvecm_demo.py +78 -0
- puremacro/examples/tvp_var_demo.py +120 -0
- puremacro/examples/tvp_var_sv_demo.py +100 -0
- puremacro/examples/vintage_revisions.py +113 -0
- puremacro/examples/vulnerable_growth.py +108 -0
- puremacro/examples/wavelet_business_cycle.py +113 -0
- puremacro/experiment.py +176 -0
- puremacro/factor.py +141 -0
- puremacro/fetch/__init__.py +50 -0
- puremacro/fetch/_classic.py +143 -0
- puremacro/fetch/_http.py +79 -0
- puremacro/fetch/_oecd_sdmx.py +70 -0
- puremacro/fetch/_seasonal.py +106 -0
- puremacro/fetch/_static_tables.py +31 -0
- puremacro/fetch/bea_cainc.py +163 -0
- puremacro/fetch/bea_industry_shares.py +101 -0
- puremacro/fetch/bls_state_panel.py +151 -0
- puremacro/fetch/cdc_births_county.py +529 -0
- puremacro/fetch/cdc_births_state.py +200 -0
- puremacro/fetch/cdc_socrata_natality.py +103 -0
- puremacro/fetch/census_bfs.py +129 -0
- puremacro/fetch/census_pep_births.py +151 -0
- puremacro/fetch/ces_states.py +74 -0
- puremacro/fetch/epu.py +91 -0
- puremacro/fetch/epu_news_historical.py +75 -0
- puremacro/fetch/epu_states.py +103 -0
- puremacro/fetch/fernald.py +63 -0
- puremacro/fetch/frb_phil_coincident.py +86 -0
- puremacro/fetch/fred.py +73 -0
- puremacro/fetch/fred_states.py +152 -0
- puremacro/fetch/gpr.py +81 -0
- puremacro/fetch/hrs_mpu.py +57 -0
- puremacro/fetch/imf_ifs.py +168 -0
- puremacro/fetch/jln.py +64 -0
- puremacro/fetch/kenfrench_industry.py +56 -0
- puremacro/fetch/labor.py +488 -0
- puremacro/fetch/labor_eurostat.py +498 -0
- puremacro/fetch/labor_ilostat.py +740 -0
- puremacro/fetch/laus.py +89 -0
- puremacro/fetch/lmn.py +61 -0
- puremacro/fetch/oecd.py +309 -0
- puremacro/fetch/oecd_energy.py +108 -0
- puremacro/fetch/oecd_fx.py +63 -0
- puremacro/fetch/oecd_mei.py +131 -0
- puremacro/fetch/oecd_qna_expenditure.py +158 -0
- puremacro/fetch/oecd_qna_labor.py +138 -0
- puremacro/fetch/oecd_qna_local.py +134 -0
- puremacro/fetch/qcew.py +83 -0
- puremacro/fetch/sdmx.py +203 -0
- puremacro/fetch/state_industry_panel.py +469 -0
- puremacro/fetch/wb_pink_sheet.py +102 -0
- puremacro/fetch/wui.py +86 -0
- puremacro/fetch/wui_extras.py +92 -0
- puremacro/fetch/yahoo.py +77 -0
- puremacro/forecast/__init__.py +14 -0
- puremacro/forecast/compare.py +118 -0
- puremacro/forecast/density.py +230 -0
- puremacro/gar/__init__.py +32 -0
- puremacro/gar/fci.py +164 -0
- puremacro/gar/qar.py +127 -0
- puremacro/gar/skewt.py +334 -0
- puremacro/garch/__init__.py +5 -0
- puremacro/garch/_results.py +110 -0
- puremacro/garch/dcc.py +120 -0
- puremacro/garch/fit.py +82 -0
- puremacro/hfi/__init__.py +21 -0
- puremacro/hfi/_results.py +90 -0
- puremacro/hfi/jk2020.py +147 -0
- puremacro/hfi/surprises.py +153 -0
- puremacro/inference/__init__.py +56 -0
- puremacro/inference/_ols_helpers.py +52 -0
- puremacro/inference/_results.py +165 -0
- puremacro/inference/balanced_panel.py +262 -0
- puremacro/inference/block_bootstrap.py +69 -0
- puremacro/inference/bootstrap.py +108 -0
- puremacro/inference/dk.py +41 -0
- puremacro/inference/hac.py +28 -0
- puremacro/inference/hac_fixed_b.py +111 -0
- puremacro/inference/lewbel_iv.py +179 -0
- puremacro/inference/lp_block_bootstrap.py +154 -0
- puremacro/inference/moving_block.py +231 -0
- puremacro/inference/moving_block_bootstrap.py +229 -0
- puremacro/inference/newey_west.py +26 -0
- puremacro/inference/over_id.py +118 -0
- puremacro/inference/pesaran.py +55 -0
- puremacro/inference/pesaran_cce.py +51 -0
- puremacro/inference/quandt_andrews.py +128 -0
- puremacro/inference/spec_curve.py +110 -0
- puremacro/inference/supt.py +233 -0
- puremacro/inference/swamy.py +40 -0
- puremacro/inference/swamy_test.py +40 -0
- puremacro/inference/weak_iv.py +389 -0
- puremacro/inference/wild_bootstrap.py +97 -0
- puremacro/instruments/__init__.py +21 -0
- puremacro/instruments/_catalog.py +811 -0
- puremacro/instruments/_compose.py +199 -0
- puremacro/instruments/_core.py +209 -0
- puremacro/instruments/_helpers.py +118 -0
- puremacro/instruments/_registry.py +174 -0
- puremacro/instruments/external/__init__.py +14 -0
- puremacro/instruments/external/bis.py +136 -0
- puremacro/instruments/external/fred.py +128 -0
- puremacro/instruments/external/imf_weo.py +176 -0
- puremacro/instruments/literature/__init__.py +19 -0
- puremacro/instruments/literature/_helpers.py +9 -0
- puremacro/instruments/literature/bbd_epu.py +79 -0
- puremacro/instruments/literature/bloom_2009.py +84 -0
- puremacro/instruments/literature/caldara_iacoviello_gpr.py +78 -0
- puremacro/instruments/literature/romer_romer_2004.py +93 -0
- puremacro/klems.py +596 -0
- puremacro/korv_gmm.py +890 -0
- puremacro/labor_flows.py +286 -0
- puremacro/labor_share.py +72 -0
- puremacro/long_panel.py +421 -0
- puremacro/lp/__init__.py +28 -0
- puremacro/lp/_garch_utils.py +170 -0
- puremacro/lp/_panel_helpers.py +219 -0
- puremacro/lp/asymmetric.py +89 -0
- puremacro/lp/cce.py +124 -0
- puremacro/lp/garch_in_mean.py +32 -0
- puremacro/lp/garch_state.py +39 -0
- puremacro/lp/iv.py +95 -0
- puremacro/lp/iv_helpers.py +90 -0
- puremacro/lp/iv_lewbel.py +114 -0
- puremacro/lp/jorda.py +78 -0
- puremacro/lp/la_lp.py +100 -0
- puremacro/lp/lp_did.py +443 -0
- puremacro/lp/mean_group.py +89 -0
- puremacro/lp/panel.py +277 -0
- puremacro/lp/panel_dk.py +46 -0
- puremacro/lp/panel_iv.py +50 -0
- puremacro/lp/quantile.py +111 -0
- puremacro/lp/smooth.py +96 -0
- puremacro/lp/state_dep.py +249 -0
- puremacro/mcmc.py +254 -0
- puremacro/midas.py +282 -0
- puremacro/models/__init__.py +10 -0
- puremacro/models/dmp_regime_dependent.py +526 -0
- puremacro/models/nested_dmp/__init__.py +133 -0
- puremacro/models/nested_dmp/backend.py +23 -0
- puremacro/models/nested_dmp/dynamics.py +417 -0
- puremacro/models/nested_dmp/equilibrium.py +392 -0
- puremacro/models/nested_dmp/estimation.py +332 -0
- puremacro/models/nested_dmp/kernels.py +158 -0
- puremacro/models/nested_dmp/kernels_numba.py +47 -0
- puremacro/models/nested_dmp/params.py +107 -0
- puremacro/models/nested_dmp/welfare.py +727 -0
- puremacro/narrative/__init__.py +119 -0
- puremacro/narrative/_local_engines.py +280 -0
- puremacro/narrative/_signal_quality.py +66 -0
- puremacro/narrative/aggregate.py +283 -0
- puremacro/narrative/dedup.py +177 -0
- puremacro/narrative/indices/__init__.py +55 -0
- puremacro/narrative/indices/_embedding_kernel.py +176 -0
- puremacro/narrative/indices/_fed_districts.py +221 -0
- puremacro/narrative/indices/_kernels.py +430 -0
- puremacro/narrative/indices/_lexicons.py +2352 -0
- puremacro/narrative/indices/_lexicons_expanded.py +609 -0
- puremacro/narrative/indices/_llm_kernel.py +312 -0
- puremacro/narrative/indices/_mnl_kernel.py +189 -0
- puremacro/narrative/indices/beige_book.py +181 -0
- puremacro/narrative/indices/bluesky.py +124 -0
- puremacro/narrative/indices/cross_source.py +133 -0
- puremacro/narrative/indices/epu.py +71 -0
- puremacro/narrative/indices/eu_legislative.py +94 -0
- puremacro/narrative/indices/gpr.py +56 -0
- puremacro/narrative/indices/ltui.py +182 -0
- puremacro/narrative/indices/lui.py +75 -0
- puremacro/narrative/indices/lwui.py +143 -0
- puremacro/narrative/indices/mpu.py +64 -0
- puremacro/narrative/indices/tone.py +100 -0
- puremacro/narrative/indices/us_executive.py +116 -0
- puremacro/narrative/indices/wui.py +54 -0
- puremacro/narrative/panel.py +516 -0
- puremacro/narrative/quality/__init__.py +7 -0
- puremacro/narrative/quality/magnitude_harmonizer.py +81 -0
- puremacro/narrative/quality/pipeline.py +71 -0
- puremacro/narrative/quality/surprise_filter.py +74 -0
- puremacro/narrative/quality/target_auditor.py +101 -0
- puremacro/narrative/replication/__init__.py +75 -0
- puremacro/narrative/replication/_within_year.py +94 -0
- puremacro/narrative/replication/adler_2024_consolidations.py +230 -0
- puremacro/narrative/replication/cloyne_2013_uk.py +110 -0
- puremacro/narrative/replication/devries_2011_consolidations.py +210 -0
- puremacro/narrative/replication/dglp_2011_consolidations.py +249 -0
- puremacro/narrative/replication/eu_nms_2023_consolidations.py +301 -0
- puremacro/narrative/replication/guajardo_2011_aeipf.py +243 -0
- puremacro/narrative/replication/imf_covid_2022_announcements.py +324 -0
- puremacro/narrative/replication/mertens_ravn_2013.py +171 -0
- puremacro/narrative/replication/mitchell_historical.py +134 -0
- puremacro/narrative/replication/ramey_2011_defense.py +118 -0
- puremacro/narrative/replication/romer_romer_2010.py +141 -0
- puremacro/narrative/replication/romer_romer_2017.py +122 -0
- puremacro/narrative/scoring/__init__.py +14 -0
- puremacro/narrative/scoring/keyword.py +231 -0
- puremacro/narrative/scoring/llm.py +536 -0
- puremacro/narrative/scoring/manual.py +79 -0
- puremacro/narrative/sources/__init__.py +121 -0
- puremacro/narrative/sources/_extractors.py +394 -0
- puremacro/narrative/sources/_fallback.py +181 -0
- puremacro/narrative/sources/_http.py +20 -0
- puremacro/narrative/sources/_playwright_helper.py +114 -0
- puremacro/narrative/sources/_ratedoc.py +85 -0
- puremacro/narrative/sources/_rss.py +104 -0
- puremacro/narrative/sources/_rss_filtered.py +66 -0
- puremacro/narrative/sources/_schema.py +108 -0
- puremacro/narrative/sources/_schema_check.py +78 -0
- puremacro/narrative/sources/_speeches.py +37 -0
- puremacro/narrative/sources/_telemetry.py +169 -0
- puremacro/narrative/sources/_wayback.py +61 -0
- puremacro/narrative/sources/banrep.py +20 -0
- puremacro/narrative/sources/banxico.py +127 -0
- puremacro/narrative/sources/bcb.py +176 -0
- puremacro/narrative/sources/bccl.py +20 -0
- puremacro/narrative/sources/bcra.py +20 -0
- puremacro/narrative/sources/beige_book.py +1100 -0
- puremacro/narrative/sources/bi.py +198 -0
- puremacro/narrative/sources/bis_speeches.py +204 -0
- puremacro/narrative/sources/bluesky.py +340 -0
- puremacro/narrative/sources/bnm.py +177 -0
- puremacro/narrative/sources/boe_decision.py +34 -0
- puremacro/narrative/sources/boe_minutes.py +105 -0
- puremacro/narrative/sources/boe_speeches.py +154 -0
- puremacro/narrative/sources/boj_decision.py +35 -0
- puremacro/narrative/sources/boj_speeches.py +104 -0
- puremacro/narrative/sources/bok.py +106 -0
- puremacro/narrative/sources/bot.py +128 -0
- puremacro/narrative/sources/bsp.py +315 -0
- puremacro/narrative/sources/ca_dof.py +38 -0
- puremacro/narrative/sources/cbe.py +200 -0
- puremacro/narrative/sources/cbk.py +218 -0
- puremacro/narrative/sources/cbn.py +213 -0
- puremacro/narrative/sources/de_bmf.py +42 -0
- puremacro/narrative/sources/ecb_decision.py +70 -0
- puremacro/narrative/sources/ecb_minutes.py +40 -0
- puremacro/narrative/sources/ecb_press.py +55 -0
- puremacro/narrative/sources/ecb_press_conf.py +56 -0
- puremacro/narrative/sources/ecb_speeches.py +41 -0
- puremacro/narrative/sources/eu_ecfin.py +63 -0
- puremacro/narrative/sources/eu_eurlex.py +354 -0
- puremacro/narrative/sources/eu_parliament.py +206 -0
- puremacro/narrative/sources/fed_decision.py +83 -0
- puremacro/narrative/sources/fed_minutes.py +140 -0
- puremacro/narrative/sources/fed_press_conf.py +64 -0
- puremacro/narrative/sources/fed_speeches.py +48 -0
- puremacro/narrative/sources/fr_tresor.py +28 -0
- puremacro/narrative/sources/frdb_strikes.py +170 -0
- puremacro/narrative/sources/google_news.py +77 -0
- puremacro/narrative/sources/hackernews.py +112 -0
- puremacro/narrative/sources/health.py +184 -0
- puremacro/narrative/sources/imf_articleiv.py +221 -0
- puremacro/narrative/sources/imf_news.py +39 -0
- puremacro/narrative/sources/it_mef.py +40 -0
- puremacro/narrative/sources/jp_mof.py +20 -0
- puremacro/narrative/sources/local_csv.py +30 -0
- puremacro/narrative/sources/mas.py +21 -0
- puremacro/narrative/sources/nber_wp.py +80 -0
- puremacro/narrative/sources/news_api.py +75 -0
- puremacro/narrative/sources/norges.py +21 -0
- puremacro/narrative/sources/oecd_surveys.py +209 -0
- puremacro/narrative/sources/pboc.py +92 -0
- puremacro/narrative/sources/rba.py +111 -0
- puremacro/narrative/sources/rbi.py +21 -0
- puremacro/narrative/sources/rbnz.py +26 -0
- puremacro/narrative/sources/reddit.py +105 -0
- puremacro/narrative/sources/riksbank.py +95 -0
- puremacro/narrative/sources/sarb.py +82 -0
- puremacro/narrative/sources/uk_hmt.py +23 -0
- puremacro/narrative/sources/uk_obr.py +21 -0
- puremacro/narrative/sources/us_cbo.py +269 -0
- puremacro/narrative/sources/us_dod_contracts.py +81 -0
- puremacro/narrative/sources/us_erp.py +223 -0
- puremacro/narrative/sources/us_federal_register.py +77 -0
- puremacro/narrative/sources/us_sotu.py +244 -0
- puremacro/narrative/sources/us_treasury.py +76 -0
- puremacro/narrative/sources/us_warn.py +1251 -0
- puremacro/narrative/types.py +529 -0
- puremacro/narrative/validate.py +138 -0
- puremacro/narrative/validation/__init__.py +36 -0
- puremacro/narrative/validation/external_benchmarks.py +295 -0
- puremacro/narrative/validation/gap_filler.py +87 -0
- puremacro/narrative/validation/report.py +182 -0
- puremacro/narrative/validation/spec_curve.py +275 -0
- puremacro/narrative/validation/stability.py +148 -0
- puremacro/nowcast/__init__.py +49 -0
- puremacro/nowcast/combine.py +192 -0
- puremacro/nowcast/dfm.py +171 -0
- puremacro/nowcast/mfvar.py +175 -0
- puremacro/nowcast/scoring.py +182 -0
- puremacro/numerics.py +167 -0
- puremacro/plot.py +227 -0
- puremacro/plotting/__init__.py +0 -0
- puremacro/plotting/bw_style.py +136 -0
- puremacro/plotting/irf_plot.py +162 -0
- puremacro/posterior.py +79 -0
- puremacro/realized_vol.py +124 -0
- puremacro/regime_dates.py +104 -0
- puremacro/regimes.py +95 -0
- puremacro/regress/__init__.py +4 -0
- puremacro/regress/lp.py +167 -0
- puremacro/replication/__init__.py +23 -0
- puremacro/replication/_data.py +17 -0
- puremacro/replication/_model.py +134 -0
- puremacro/replication/cases_ha_dsge_causal.py +144 -0
- puremacro/replication/cases_stylized_facts.py +44 -0
- puremacro/replication/data/__init__.py +0 -0
- puremacro/replication/data/gali1999.csv +314 -0
- puremacro/replication/data/kilian2009.csv +433 -0
- puremacro/replication/data/okun.csv +940 -0
- puremacro/replication/data/rz2018.csv +509 -0
- puremacro/replication/data/tax14_narrative_tax_shocks.csv +253 -0
- puremacro/replication/data/tax14_us_fiscal.csv +318 -0
- puremacro/replication/runner.py +51 -0
- puremacro/reports.py +235 -0
- puremacro/sa/__init__.py +25 -0
- puremacro/sa/stl.py +132 -0
- puremacro/sa/x13.py +159 -0
- puremacro/scale.py +98 -0
- puremacro/shock_atlas.py +321 -0
- puremacro/sigma/__init__.py +4 -0
- puremacro/sigma/shapley.py +232 -0
- puremacro/sigma/sigma_numpy.py +64 -0
- puremacro/spectral.py +186 -0
- puremacro/state_space.py +378 -0
- puremacro/synthetic_control.py +202 -0
- puremacro/teaching/__init__.py +0 -0
- puremacro/teaching/bartik.py +54 -0
- puremacro/teaching/bartik_importance.py +199 -0
- puremacro/teaching/bartik_research.py +66 -0
- puremacro/teaching/bq_canonical.py +115 -0
- puremacro/teaching/data.py +73 -0
- puremacro/teaching/data_long.py +197 -0
- puremacro/teaching/data_states.py +121 -0
- puremacro/teaching/experiment.py +181 -0
- puremacro/teaching/garch_arch.py +20 -0
- puremacro/teaching/garch_iv.py +493 -0
- puremacro/teaching/integration_checks.py +173 -0
- puremacro/teaching/irf_scale.py +114 -0
- puremacro/teaching/lp_sm.py +47 -0
- puremacro/teaching/panel_lm.py +74 -0
- puremacro/teaching/plot.py +70 -0
- puremacro/teaching/recipes.py +142 -0
- puremacro/teaching/svar_panel.py +592 -0
- puremacro/teaching/var_sm.py +195 -0
- puremacro/tests/__init__.py +0 -0
- puremacro/tests/breaks.py +217 -0
- puremacro/tests/test_var/__init__.py +0 -0
- puremacro/tests/test_var/test_proxy_axis.py +35 -0
- puremacro/tests/unit_root.py +313 -0
- puremacro/uncertainty/__init__.py +49 -0
- puremacro/uncertainty/composite.py +332 -0
- puremacro/uncertainty/decomposition.py +111 -0
- puremacro/uncertainty/diagnostics.py +121 -0
- puremacro/uncertainty/lp_long.py +181 -0
- puremacro/uncertainty/regimes.py +125 -0
- puremacro/validation/__init__.py +23 -0
- puremacro/validation/_fixtures.py +19 -0
- puremacro/validation/_goldens.py +29 -0
- puremacro/validation/_model.py +143 -0
- puremacro/validation/cases_dsge.py +350 -0
- puremacro/validation/cases_dynpanel.py +298 -0
- puremacro/validation/cases_forecast.py +234 -0
- puremacro/validation/cases_garch.py +221 -0
- puremacro/validation/cases_inference.py +361 -0
- puremacro/validation/cases_lp.py +244 -0
- puremacro/validation/cases_narrative.py +284 -0
- puremacro/validation/cases_spectral.py +342 -0
- puremacro/validation/cases_state_space.py +272 -0
- puremacro/validation/cases_var.py +78 -0
- puremacro/validation/cases_vfi.py +297 -0
- puremacro/validation/goldens/__init__.py +0 -0
- puremacro/validation/goldens/_t.json +4 -0
- puremacro/validation/goldens/garch.json +4818 -0
- puremacro/validation/goldens/inference.json +22 -0
- puremacro/validation/goldens/lp.json +40 -0
- puremacro/validation/goldens/state_space.json +239 -0
- puremacro/validation/goldens/var.json +142 -0
- puremacro/validation/runner.py +55 -0
- puremacro/var/__init__.py +15 -0
- puremacro/var/_results.py +60 -0
- puremacro/var/bootstrap.py +180 -0
- puremacro/var/bvar.py +476 -0
- puremacro/var/diagnostics.py +132 -0
- puremacro/var/estimate.py +82 -0
- puremacro/var/identify/__init__.py +37 -0
- puremacro/var/identify/_results.py +613 -0
- puremacro/var/identify/bq.py +128 -0
- puremacro/var/identify/cholesky.py +197 -0
- puremacro/var/identify/hetero.py +229 -0
- puremacro/var/identify/magmav.py +394 -0
- puremacro/var/identify/maxshare.py +409 -0
- puremacro/var/identify/narrative_sign.py +568 -0
- puremacro/var/identify/non_gaussian.py +230 -0
- puremacro/var/identify/panel.py +125 -0
- puremacro/var/identify/proxy.py +117 -0
- puremacro/var/identify/sign.py +77 -0
- puremacro/var/identify/sign_robust.py +212 -0
- puremacro/var/identify/sign_zero.py +167 -0
- puremacro/var/irf.py +153 -0
- puremacro/var/panel.py +250 -0
- puremacro/var/peak.py +121 -0
- puremacro/var/regime/__init__.py +13 -0
- puremacro/var/regime/girf.py +511 -0
- puremacro/var/regime/ms_var.py +250 -0
- puremacro/var/regime/threshold.py +137 -0
- puremacro/var/regime/tvecm.py +179 -0
- puremacro/var/tvp.py +471 -0
- puremacro/var/vecm.py +279 -0
- puremacro/vfi/__init__.py +98 -0
- puremacro/vfi/aggregate.py +111 -0
- puremacro/vfi/case2.py +128 -0
- puremacro/vfi/compat.py +57 -0
- puremacro/vfi/discretize.py +197 -0
- puremacro/vfi/distribution.py +140 -0
- puremacro/vfi/egm.py +83 -0
- puremacro/vfi/epstein_zin.py +110 -0
- puremacro/vfi/equilibrium.py +113 -0
- puremacro/vfi/estimate.py +86 -0
- puremacro/vfi/examples.py +379 -0
- puremacro/vfi/finite_horizon.py +227 -0
- puremacro/vfi/firm_dynamics.py +113 -0
- puremacro/vfi/kernels.py +28 -0
- puremacro/vfi/kernels_numba.py +174 -0
- puremacro/vfi/krusell_smith.py +261 -0
- puremacro/vfi/olg.py +124 -0
- puremacro/vfi/permanent_types.py +96 -0
- puremacro/vfi/problem.py +205 -0
- puremacro/vfi/returnfn.py +71 -0
- puremacro/vfi/simulate.py +77 -0
- puremacro/vfi/solve.py +60 -0
- puremacro/vfi/transition.py +91 -0
- puremacro/vintages.py +303 -0
- puremacro/volatility/__init__.py +32 -0
- puremacro/volatility/diagnostics.py +94 -0
- puremacro/volatility/har.py +79 -0
- puremacro/volatility/multivariate.py +212 -0
- puremacro/volatility/range.py +129 -0
- puremacro/volatility/sigma.py +262 -0
- puremacro/wavelet.py +251 -0
- puremacro-0.92.0.dist-info/METADATA +363 -0
- puremacro-0.92.0.dist-info/RECORD +564 -0
- puremacro-0.92.0.dist-info/WHEEL +5 -0
- puremacro-0.92.0.dist-info/licenses/LICENSE +21 -0
- puremacro-0.92.0.dist-info/top_level.txt +1 -0
puremacro/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""puremacro — Pyodide-compatible empirical macro toolbox.
|
|
2
|
+
|
|
3
|
+
See README.md for the iPad / juno.sh quickstart and module-by-module
|
|
4
|
+
overview. Public API is curated in submodules; nothing is exported at
|
|
5
|
+
the top level beyond the version string.
|
|
6
|
+
"""
|
|
7
|
+
__version__ = "0.92.0"
|
|
8
|
+
__all__ = ["__version__"]
|
puremacro/_backend.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Shared pluggable compute backends for puremacro's compute-heavy modules.
|
|
2
|
+
|
|
3
|
+
Four tiers:
|
|
4
|
+
- "numpy" : reference implementation and correctness ORACLE. Always
|
|
5
|
+
available; keeps puremacro's Pyodide-compatible core intact.
|
|
6
|
+
- "numba" : CPU JIT (+ prange). Compiled kernels, NOT an array namespace.
|
|
7
|
+
- "mlx" : Apple-Silicon GPU. numpy-like namespace (mlx.core); xp-generic
|
|
8
|
+
kernels run on it unchanged. float32-only (Metal has no float64).
|
|
9
|
+
- "cupy" : NVIDIA GPU. numpy-like namespace (cupy); xp-generic kernels run
|
|
10
|
+
unchanged. Optional [cuda] extra; cannot be installed/run without CUDA.
|
|
11
|
+
|
|
12
|
+
numba/mlx/cupy are optional extras detected via importlib.util.find_spec and
|
|
13
|
+
skipped, never hard-failed. (Promoted from models/nested_dmp/backend.py so the
|
|
14
|
+
nested DMP model and the general vfi engine share one source of truth.)
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import importlib.util
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
SUPPORTED = ("numpy", "numba", "mlx", "cupy")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def backend_available(name: str) -> bool:
|
|
26
|
+
"""True if backend ``name`` can be used in this environment."""
|
|
27
|
+
if name == "numpy":
|
|
28
|
+
return True
|
|
29
|
+
if name in ("numba", "mlx", "cupy"):
|
|
30
|
+
return importlib.util.find_spec(name) is not None
|
|
31
|
+
raise ValueError(f"Unknown backend {name!r}; supported: {SUPPORTED}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def available_backends() -> tuple[str, ...]:
|
|
35
|
+
"""Tuple of installed backends, numpy first."""
|
|
36
|
+
return tuple(b for b in SUPPORTED if backend_available(b))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_array_namespace(name: str):
|
|
40
|
+
"""Return a numpy-like array module for namespace-style backends.
|
|
41
|
+
|
|
42
|
+
"numpy"->numpy; "mlx"->mlx.core; "cupy"->cupy. "numba" is not a namespace.
|
|
43
|
+
"""
|
|
44
|
+
if name == "numpy":
|
|
45
|
+
return np
|
|
46
|
+
if name == "mlx":
|
|
47
|
+
if not backend_available("mlx"):
|
|
48
|
+
raise ImportError(
|
|
49
|
+
"mlx not installed; on Apple Silicon run "
|
|
50
|
+
"`pip install puremacro[backend]`."
|
|
51
|
+
)
|
|
52
|
+
import mlx.core as mx
|
|
53
|
+
|
|
54
|
+
return mx
|
|
55
|
+
if name == "cupy":
|
|
56
|
+
if not backend_available("cupy"):
|
|
57
|
+
raise ImportError(
|
|
58
|
+
"cupy not installed; on an NVIDIA host run "
|
|
59
|
+
"`pip install puremacro[cuda]` (choose the wheel matching your "
|
|
60
|
+
"CUDA toolkit, e.g. cupy-cuda12x)."
|
|
61
|
+
)
|
|
62
|
+
import cupy as cp
|
|
63
|
+
|
|
64
|
+
return cp
|
|
65
|
+
if name == "numba":
|
|
66
|
+
raise ValueError(
|
|
67
|
+
"numba backend uses compiled kernels, not an array namespace; "
|
|
68
|
+
"call the kernels_numba.* functions directly."
|
|
69
|
+
)
|
|
70
|
+
raise ValueError(f"Unknown backend {name!r}; supported: {SUPPORTED}")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def to_numpy(x) -> np.ndarray:
|
|
74
|
+
"""Bring an array from any backend back to numpy.
|
|
75
|
+
|
|
76
|
+
cupy blocks implicit conversion, so route it through cp.asnumpy; numpy and
|
|
77
|
+
mlx (which forces evaluation) go through np.asarray.
|
|
78
|
+
"""
|
|
79
|
+
if type(x).__module__.split(".")[0] == "cupy":
|
|
80
|
+
import cupy as cp
|
|
81
|
+
|
|
82
|
+
return cp.asnumpy(x)
|
|
83
|
+
return np.asarray(x)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
__all__ = [
|
|
87
|
+
"SUPPORTED",
|
|
88
|
+
"backend_available",
|
|
89
|
+
"available_backends",
|
|
90
|
+
"get_array_namespace",
|
|
91
|
+
"to_numpy",
|
|
92
|
+
]
|
puremacro/_cache_db.py
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""SQLite connection manager + schema bootstrap for puremacro's data
|
|
2
|
+
infrastructure.
|
|
3
|
+
|
|
4
|
+
A single SQLite file at ``~/.cache/puremacro/cache.db`` (overridable
|
|
5
|
+
via ``$PUREMACRO_HTTP_CACHE_DIR``) hosts three tables:
|
|
6
|
+
|
|
7
|
+
- ``http_cache`` — replaces the flat-file HTTP cache; backs
|
|
8
|
+
``puremacro._http_cache.cache_read``/``cache_write``.
|
|
9
|
+
- ``alfred_vintages`` — persistent FRED-ALFRED vintage panel; backs
|
|
10
|
+
``puremacro.vintages.AlfredVintageStore``.
|
|
11
|
+
- ``schema_version`` — registry for future migrations.
|
|
12
|
+
|
|
13
|
+
WAL journal mode is enabled so multiple notebooks against the same DB
|
|
14
|
+
do not block each other on writes. One ``sqlite3.Connection`` per
|
|
15
|
+
process is kept alive in a module-level singleton.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import sqlite3
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_DDL_HTTP_CACHE = """
|
|
25
|
+
CREATE TABLE IF NOT EXISTS http_cache (
|
|
26
|
+
key TEXT PRIMARY KEY,
|
|
27
|
+
url TEXT NOT NULL,
|
|
28
|
+
fetched_at INTEGER NOT NULL,
|
|
29
|
+
content_type TEXT,
|
|
30
|
+
body BLOB NOT NULL
|
|
31
|
+
);
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
_DDL_HTTP_CACHE_IDX = (
|
|
35
|
+
"CREATE INDEX IF NOT EXISTS http_cache_fetched_at_idx "
|
|
36
|
+
"ON http_cache(fetched_at);"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
_DDL_ALFRED_VINTAGES = """
|
|
40
|
+
CREATE TABLE IF NOT EXISTS alfred_vintages (
|
|
41
|
+
series_id TEXT NOT NULL,
|
|
42
|
+
observation_date TEXT NOT NULL,
|
|
43
|
+
vintage_date TEXT NOT NULL,
|
|
44
|
+
value REAL,
|
|
45
|
+
PRIMARY KEY (series_id, observation_date, vintage_date)
|
|
46
|
+
);
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
_DDL_ALFRED_VINTAGES_IDX = (
|
|
50
|
+
"CREATE INDEX IF NOT EXISTS alfred_vintages_series_vintage_idx "
|
|
51
|
+
"ON alfred_vintages(series_id, vintage_date);"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
_DDL_CONNECTOR_EVENTS = """
|
|
55
|
+
CREATE TABLE IF NOT EXISTS connector_events (
|
|
56
|
+
ts INTEGER NOT NULL,
|
|
57
|
+
source TEXT NOT NULL,
|
|
58
|
+
outcome TEXT NOT NULL,
|
|
59
|
+
fallback_used TEXT NOT NULL
|
|
60
|
+
);
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
_DDL_CONNECTOR_EVENTS_IDX = (
|
|
64
|
+
"CREATE INDEX IF NOT EXISTS connector_events_ts_source_idx "
|
|
65
|
+
"ON connector_events(ts, source);"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
_DDL_SCHEMA_VERSION = """
|
|
69
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
70
|
+
component TEXT PRIMARY KEY,
|
|
71
|
+
version INTEGER NOT NULL
|
|
72
|
+
);
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
_SCHEMA_SEED = [("http_cache", 1), ("alfred_vintages", 1),
|
|
76
|
+
("connector_events", 1)]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def default_db_path() -> Path:
|
|
80
|
+
"""Resolve the canonical cache DB path.
|
|
81
|
+
|
|
82
|
+
``$PUREMACRO_HTTP_CACHE_DIR`` overrides everything:
|
|
83
|
+
- if it ends with ``.db``, return that path verbatim;
|
|
84
|
+
- otherwise treat as a directory and return ``<dir>/cache.db``.
|
|
85
|
+
Default: ``~/.cache/puremacro/cache.db``.
|
|
86
|
+
"""
|
|
87
|
+
env = os.environ.get("PUREMACRO_HTTP_CACHE_DIR")
|
|
88
|
+
if env:
|
|
89
|
+
p = Path(env)
|
|
90
|
+
if p.suffix == ".db":
|
|
91
|
+
return p
|
|
92
|
+
return p / "cache.db"
|
|
93
|
+
return Path.home() / ".cache" / "puremacro" / "cache.db"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def bootstrap_schema(conn: sqlite3.Connection) -> None:
|
|
97
|
+
"""Idempotent: create tables + seed schema_version rows if missing."""
|
|
98
|
+
cur = conn.cursor()
|
|
99
|
+
cur.execute(_DDL_HTTP_CACHE)
|
|
100
|
+
cur.execute(_DDL_HTTP_CACHE_IDX)
|
|
101
|
+
cur.execute(_DDL_ALFRED_VINTAGES)
|
|
102
|
+
cur.execute(_DDL_ALFRED_VINTAGES_IDX)
|
|
103
|
+
cur.execute(_DDL_CONNECTOR_EVENTS)
|
|
104
|
+
cur.execute(_DDL_CONNECTOR_EVENTS_IDX)
|
|
105
|
+
cur.execute(_DDL_SCHEMA_VERSION)
|
|
106
|
+
for component, version in _SCHEMA_SEED:
|
|
107
|
+
cur.execute(
|
|
108
|
+
"INSERT OR IGNORE INTO schema_version (component, version) "
|
|
109
|
+
"VALUES (?, ?)",
|
|
110
|
+
(component, version),
|
|
111
|
+
)
|
|
112
|
+
conn.commit()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
_CONN: sqlite3.Connection | None = None
|
|
116
|
+
_CONN_PATH: Path | None = None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def get_conn(db_path: Path | None = None) -> sqlite3.Connection:
|
|
120
|
+
"""Return the module-level singleton SQLite connection.
|
|
121
|
+
|
|
122
|
+
Lazily opens on first call. Enables WAL mode and bootstraps the
|
|
123
|
+
schema. If called twice with different ``db_path`` arguments
|
|
124
|
+
(e.g., in tests), closes the previous connection and opens a new one.
|
|
125
|
+
"""
|
|
126
|
+
global _CONN, _CONN_PATH
|
|
127
|
+
target = db_path or default_db_path()
|
|
128
|
+
if _CONN is not None and _CONN_PATH == target:
|
|
129
|
+
return _CONN
|
|
130
|
+
if _CONN is not None:
|
|
131
|
+
_CONN.close()
|
|
132
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
conn = sqlite3.connect(
|
|
134
|
+
target, timeout=30.0, isolation_level=None, check_same_thread=False,
|
|
135
|
+
)
|
|
136
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
137
|
+
conn.execute("PRAGMA synchronous=NORMAL")
|
|
138
|
+
conn.execute("PRAGMA foreign_keys=ON")
|
|
139
|
+
bootstrap_schema(conn)
|
|
140
|
+
_CONN = conn
|
|
141
|
+
_CONN_PATH = target
|
|
142
|
+
return _CONN
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def close_conn() -> None:
|
|
146
|
+
"""Close the singleton connection (used by tests)."""
|
|
147
|
+
global _CONN, _CONN_PATH
|
|
148
|
+
if _CONN is not None:
|
|
149
|
+
try:
|
|
150
|
+
_CONN.close()
|
|
151
|
+
finally:
|
|
152
|
+
_CONN = None
|
|
153
|
+
_CONN_PATH = None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def migrate_from_flat_files(
|
|
157
|
+
conn: sqlite3.Connection,
|
|
158
|
+
flat_cache_dir: Path,
|
|
159
|
+
*,
|
|
160
|
+
remove: bool = False,
|
|
161
|
+
) -> int:
|
|
162
|
+
"""Walk ``flat_cache_dir`` for ``*.bin`` + ``*.json`` sidecar pairs.
|
|
163
|
+
|
|
164
|
+
Inserts each into ``http_cache`` with ``INSERT OR IGNORE`` semantics
|
|
165
|
+
(idempotent). If ``remove=True``, unlinks each migrated file pair
|
|
166
|
+
after successful insert. Returns the count actually migrated.
|
|
167
|
+
|
|
168
|
+
Failures on a per-file basis are warned-and-skipped, not raised —
|
|
169
|
+
migration is opportunistic.
|
|
170
|
+
"""
|
|
171
|
+
import json
|
|
172
|
+
import warnings
|
|
173
|
+
|
|
174
|
+
if not flat_cache_dir.exists():
|
|
175
|
+
return 0
|
|
176
|
+
migrated = 0
|
|
177
|
+
for bin_path in flat_cache_dir.glob("*.bin"):
|
|
178
|
+
sidecar = bin_path.with_suffix(".json")
|
|
179
|
+
if not sidecar.exists():
|
|
180
|
+
continue
|
|
181
|
+
try:
|
|
182
|
+
meta = json.loads(sidecar.read_text())
|
|
183
|
+
url = meta["url"]
|
|
184
|
+
fetched_at = int(float(meta["fetched_at"]))
|
|
185
|
+
content_type = meta.get("content_type")
|
|
186
|
+
body = bin_path.read_bytes()
|
|
187
|
+
key = bin_path.stem
|
|
188
|
+
cur = conn.execute(
|
|
189
|
+
"INSERT OR IGNORE INTO http_cache "
|
|
190
|
+
"(key, url, fetched_at, content_type, body) "
|
|
191
|
+
"VALUES (?, ?, ?, ?, ?)",
|
|
192
|
+
(key, url, fetched_at, content_type, body),
|
|
193
|
+
)
|
|
194
|
+
if cur.rowcount > 0:
|
|
195
|
+
migrated += 1
|
|
196
|
+
if remove:
|
|
197
|
+
bin_path.unlink(missing_ok=True)
|
|
198
|
+
sidecar.unlink(missing_ok=True)
|
|
199
|
+
except (OSError, ValueError, KeyError) as e:
|
|
200
|
+
warnings.warn(
|
|
201
|
+
f"puremacro._cache_db: skipping {bin_path.name}: {e}",
|
|
202
|
+
UserWarning,
|
|
203
|
+
stacklevel=2,
|
|
204
|
+
)
|
|
205
|
+
continue
|
|
206
|
+
conn.commit()
|
|
207
|
+
return migrated
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
__all__ = [
|
|
211
|
+
"default_db_path",
|
|
212
|
+
"bootstrap_schema",
|
|
213
|
+
"get_conn",
|
|
214
|
+
"close_conn",
|
|
215
|
+
"migrate_from_flat_files",
|
|
216
|
+
]
|
puremacro/_codes.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Country / aggregate code canonicalization.
|
|
2
|
+
|
|
3
|
+
Used everywhere the panel might receive aggregate codes from upstream
|
|
4
|
+
fetchers (OECD `EA20`, `EU27`, `OECD`; WUI / GPR `WLD`; etc.) so the
|
|
5
|
+
panel only ever contains pure-country observations.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
IS_AGGREGATE: frozenset[str] = frozenset({
|
|
12
|
+
"EA", "EA12", "EA17", "EA19", "EA20",
|
|
13
|
+
"EU", "EU27", "EU27_2020", "EU28",
|
|
14
|
+
"OECD", "OECDE", "OECDA",
|
|
15
|
+
"G7", "G20", "BRICS",
|
|
16
|
+
"WLD", "WORLD", "ADV", "EME",
|
|
17
|
+
"LATAM", "ASIA", "AFRICA", "MENA",
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_country(code: object) -> bool:
|
|
22
|
+
"""True iff *code* is a length-3 alpha string and not in IS_AGGREGATE."""
|
|
23
|
+
if not isinstance(code, str):
|
|
24
|
+
return False
|
|
25
|
+
return len(code) == 3 and code.isalpha() and code.upper() not in IS_AGGREGATE
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def drop_aggregates(df: pd.DataFrame, code_col: str = "code") -> pd.DataFrame:
|
|
29
|
+
"""Return a copy of *df* with rows whose *code_col* fails ``is_country`` removed."""
|
|
30
|
+
mask = df[code_col].apply(is_country)
|
|
31
|
+
return df.loc[mask].reset_index(drop=True)
|
puremacro/_http.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Shared HTTP helpers for puremacro fetchers and connectors.
|
|
2
|
+
|
|
3
|
+
This module is the single canonical home of ``safe_get_bytes``,
|
|
4
|
+
``safe_get_text``, ``safe_get_json`` and the supporting ``USER_AGENT``
|
|
5
|
+
+ ``DEFAULT_TIMEOUT`` constants. Promoted from
|
|
6
|
+
``puremacro.narrative.sources._http`` in 0.6.0 so all fetchers
|
|
7
|
+
(narrative connectors, ``puremacro.fetch.*``, instrument loaders)
|
|
8
|
+
share one hardened path.
|
|
9
|
+
|
|
10
|
+
See ``puremacro/narrative/sources/RETRY_POLICY.md`` for the
|
|
11
|
+
contract every consumer adheres to: 30s default timeout, one-shot
|
|
12
|
+
SSL fallback for older endpoints with stale CA bundles, optional
|
|
13
|
+
keyword-only ``user_agent=`` override (added in 0.4.1) for endpoints
|
|
14
|
+
behind a WAF that blocks the default agent string.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import ssl
|
|
20
|
+
import urllib.error
|
|
21
|
+
import urllib.request
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
USER_AGENT = "Mozilla/5.0 (puremacro/narrative)"
|
|
25
|
+
DEFAULT_TIMEOUT = 30.0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _request(url: str, timeout: float, user_agent: str | None = None) -> bytes:
|
|
29
|
+
ua = user_agent or USER_AGENT
|
|
30
|
+
req = urllib.request.Request(url, headers={"User-Agent": ua})
|
|
31
|
+
try:
|
|
32
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
33
|
+
return resp.read()
|
|
34
|
+
except (urllib.error.URLError, ssl.SSLError):
|
|
35
|
+
# One-shot fallback: some public endpoints (older OECD / IMF /
|
|
36
|
+
# ministry sites) ship certificates that Python's bundled CA
|
|
37
|
+
# store does not validate. Retry once with verification off.
|
|
38
|
+
# See RETRY_POLICY.md §3 for why we do not loop further.
|
|
39
|
+
ctx = ssl._create_unverified_context()
|
|
40
|
+
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp:
|
|
41
|
+
return resp.read()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def safe_get_bytes(url: str, timeout: float = DEFAULT_TIMEOUT,
|
|
45
|
+
*, user_agent: str | None = None) -> bytes:
|
|
46
|
+
"""Fetch ``url`` and return raw bytes. SSL fallback applied once.
|
|
47
|
+
|
|
48
|
+
``user_agent`` overrides the default ``Mozilla/5.0 (puremacro/narrative)``
|
|
49
|
+
UA — needed for endpoints behind a WAF that blocks scripted clients.
|
|
50
|
+
"""
|
|
51
|
+
return _request(url, timeout, user_agent=user_agent)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def safe_get_text(url: str, timeout: float = DEFAULT_TIMEOUT,
|
|
55
|
+
*, user_agent: str | None = None) -> str:
|
|
56
|
+
"""Fetch ``url`` and return UTF-8 text (decode errors ignored).
|
|
57
|
+
|
|
58
|
+
See ``safe_get_bytes`` for the ``user_agent=`` semantics and
|
|
59
|
+
``RETRY_POLICY.md §7`` for the WAF-bypass pattern.
|
|
60
|
+
"""
|
|
61
|
+
return _request(url, timeout, user_agent=user_agent).decode(
|
|
62
|
+
"utf-8", errors="ignore",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def safe_get_json(url: str, timeout: float = DEFAULT_TIMEOUT,
|
|
67
|
+
*, user_agent: str | None = None) -> dict:
|
|
68
|
+
"""Fetch ``url`` and return decoded JSON.
|
|
69
|
+
|
|
70
|
+
Empty / whitespace-only bodies return ``{}`` rather than raising,
|
|
71
|
+
matching the existing API-connector behaviour (e.g. GDELT v2 rate
|
|
72
|
+
limits sometimes return blank pages).
|
|
73
|
+
|
|
74
|
+
See ``safe_get_bytes`` for the ``user_agent=`` semantics and
|
|
75
|
+
``RETRY_POLICY.md §7`` for the WAF-bypass pattern.
|
|
76
|
+
"""
|
|
77
|
+
text = safe_get_text(url, timeout, user_agent=user_agent)
|
|
78
|
+
if not text.strip():
|
|
79
|
+
return {}
|
|
80
|
+
return json.loads(text)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def post_json(url: str, payload: dict, *, timeout: float = DEFAULT_TIMEOUT,
|
|
84
|
+
headers: dict | None = None) -> dict:
|
|
85
|
+
"""POST ``payload`` as JSON and return the decoded JSON response.
|
|
86
|
+
|
|
87
|
+
urllib-only (Pyodide-safe). HTTP errors propagate (not retried); a
|
|
88
|
+
transport/SSL error retries once with verification off, matching
|
|
89
|
+
``_request``. Used by the local-LLM HTTP engine (Ollama / OpenAI-compatible).
|
|
90
|
+
"""
|
|
91
|
+
data = json.dumps(payload).encode("utf-8")
|
|
92
|
+
hdrs = {"Content-Type": "application/json", "User-Agent": USER_AGENT}
|
|
93
|
+
if headers:
|
|
94
|
+
hdrs.update(headers)
|
|
95
|
+
req = urllib.request.Request(url, data=data, headers=hdrs, method="POST")
|
|
96
|
+
try:
|
|
97
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
98
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
99
|
+
except urllib.error.HTTPError:
|
|
100
|
+
raise
|
|
101
|
+
except (urllib.error.URLError, ssl.SSLError):
|
|
102
|
+
ctx = ssl._create_unverified_context()
|
|
103
|
+
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp:
|
|
104
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
# Opt-in cached + rate-limited variants (0.60.0)
|
|
109
|
+
#
|
|
110
|
+
# Callers who want caching switch from ``safe_get_text`` to
|
|
111
|
+
# ``safe_get_text_cached``. The existing helpers are unchanged.
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
import os
|
|
115
|
+
import time
|
|
116
|
+
import urllib.parse
|
|
117
|
+
|
|
118
|
+
from ._http_cache import default_cache_dir, cache_read, cache_write
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# Per-host last-fetch timestamps (monotonic seconds).
|
|
122
|
+
_HOST_LAST_FETCH: dict[str, float] = {}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _reset_throttle_state() -> None:
|
|
126
|
+
"""Clear per-host throttle state. Test helper."""
|
|
127
|
+
_HOST_LAST_FETCH.clear()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _throttle(host: str, rate_limit_seconds: float) -> None:
|
|
131
|
+
"""Sleep until ``rate_limit_seconds`` have elapsed since the last
|
|
132
|
+
fetch to ``host``. No-op on first call to a host."""
|
|
133
|
+
if rate_limit_seconds <= 0:
|
|
134
|
+
return
|
|
135
|
+
now = time.monotonic()
|
|
136
|
+
last = _HOST_LAST_FETCH.get(host)
|
|
137
|
+
if last is not None:
|
|
138
|
+
wait = rate_limit_seconds - (now - last)
|
|
139
|
+
if wait > 0:
|
|
140
|
+
time.sleep(wait)
|
|
141
|
+
_HOST_LAST_FETCH[host] = time.monotonic()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _host_of(url: str) -> str:
|
|
145
|
+
return urllib.parse.urlparse(url).netloc.lower()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def safe_get_bytes_cached(
|
|
149
|
+
url: str,
|
|
150
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
151
|
+
*,
|
|
152
|
+
user_agent: str | None = None,
|
|
153
|
+
ttl_seconds: int = 30 * 24 * 3600,
|
|
154
|
+
rate_limit_seconds: float = 0.5,
|
|
155
|
+
) -> bytes:
|
|
156
|
+
"""Like ``safe_get_bytes`` but cached on disk.
|
|
157
|
+
|
|
158
|
+
Cache root: ``$PUREMACRO_HTTP_CACHE_DIR`` or ``~/.cache/puremacro/http``.
|
|
159
|
+
Bypass entirely with ``PUREMACRO_HTTP_NO_CACHE=1``.
|
|
160
|
+
"""
|
|
161
|
+
bypass = os.environ.get("PUREMACRO_HTTP_NO_CACHE") == "1"
|
|
162
|
+
cache_dir = default_cache_dir()
|
|
163
|
+
if not bypass:
|
|
164
|
+
hit = cache_read(cache_dir, url, ttl_seconds=ttl_seconds)
|
|
165
|
+
if hit is not None:
|
|
166
|
+
return hit
|
|
167
|
+
_throttle(_host_of(url), rate_limit_seconds)
|
|
168
|
+
body = _request(url, timeout, user_agent=user_agent)
|
|
169
|
+
if not bypass:
|
|
170
|
+
cache_write(cache_dir, url, body)
|
|
171
|
+
return body
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def safe_get_text_cached(
|
|
175
|
+
url: str,
|
|
176
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
177
|
+
*,
|
|
178
|
+
user_agent: str | None = None,
|
|
179
|
+
ttl_seconds: int = 30 * 24 * 3600,
|
|
180
|
+
rate_limit_seconds: float = 0.5,
|
|
181
|
+
) -> str:
|
|
182
|
+
"""Like ``safe_get_text`` but cached on disk. See
|
|
183
|
+
``safe_get_bytes_cached`` for env-var semantics.
|
|
184
|
+
"""
|
|
185
|
+
return safe_get_bytes_cached(
|
|
186
|
+
url, timeout,
|
|
187
|
+
user_agent=user_agent,
|
|
188
|
+
ttl_seconds=ttl_seconds,
|
|
189
|
+
rate_limit_seconds=rate_limit_seconds,
|
|
190
|
+
).decode("utf-8", errors="ignore")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
__all__ = [
|
|
194
|
+
"USER_AGENT", "DEFAULT_TIMEOUT",
|
|
195
|
+
"safe_get_bytes", "safe_get_text", "safe_get_json",
|
|
196
|
+
"post_json",
|
|
197
|
+
"safe_get_bytes_cached", "safe_get_text_cached",
|
|
198
|
+
]
|