forecasting-tools 0.3.4__tar.gz → 0.3.5__tar.gz

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.
Files changed (228) hide show
  1. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/PKG-INFO +3 -5
  2. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/README.md +1 -3
  3. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/data_models/multiple_choice_report.py +60 -22
  4. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/data_models/numeric_report.py +11 -0
  5. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/helpers/metaculus_client.py +142 -8
  6. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/util/misc.py +13 -1
  7. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/pyproject.toml +2 -2
  8. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/LICENSE +0 -0
  9. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/__init__.py +0 -0
  10. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/__init__.py +0 -0
  11. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/ai_congress_v2/__init__.py +0 -0
  12. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/ai_congress_v2/congress_member_agent.py +0 -0
  13. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/ai_congress_v2/congress_orchestrator.py +0 -0
  14. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/ai_congress_v2/data_models.py +0 -0
  15. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/ai_congress_v2/member_profiles.py +0 -0
  16. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/ai_congress_v2/tools.py +0 -0
  17. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/base_rates/base_rate_researcher.py +0 -0
  18. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/base_rates/deduplicator.py +0 -0
  19. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/base_rates/estimator.py +0 -0
  20. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/base_rates/niche_list_researcher.py +0 -0
  21. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/deprecated/configured_llms.py +0 -0
  22. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/deprecated/general_researcher.py +0 -0
  23. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/deprecated/question_generator.py +0 -0
  24. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/deprecated/question_responder.py +0 -0
  25. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/deprecated/question_router.py +0 -0
  26. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/deprecated/research_coordinator.py +0 -0
  27. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/minor_tools.py +0 -0
  28. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/other/data_analyzer.py +0 -0
  29. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/other/hosted_file.py +0 -0
  30. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/question_generators/generated_question.py +0 -0
  31. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/question_generators/harmful_question_identifier.py +0 -0
  32. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/question_generators/q3_q4_quarterly_questions.json +0 -0
  33. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/question_generators/question_decomposer.py +0 -0
  34. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/question_generators/question_operationalizer.py +0 -0
  35. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/question_generators/simple_question.py +0 -0
  36. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/question_generators/topic_generator.py +0 -0
  37. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/research/computer_use.py +0 -0
  38. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/research/find_a_dataset.py +0 -0
  39. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/research/key_factors_researcher.py +0 -0
  40. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/research/smart_searcher.py +0 -0
  41. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/__init__.py +0 -0
  42. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/agent_runner.py +0 -0
  43. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/data_models.py +0 -0
  44. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/effect_engine.py +0 -0
  45. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/example_situations/industrial_basin.json +0 -0
  46. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/example_situations/milbrook.json +0 -0
  47. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/example_situations/simple_business.json +0 -0
  48. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/example_situations/simple_dnd.json +0 -0
  49. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/example_situations/simple_election.json +0 -0
  50. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/example_situations/willowbrook.json +0 -0
  51. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/example_situations/z_broken_mafia.json +0 -0
  52. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/intervention_testing/__init__.py +0 -0
  53. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/intervention_testing/data_models.py +0 -0
  54. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/intervention_testing/forecast_resolver.py +0 -0
  55. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/intervention_testing/intervention_policy_agent.py +0 -0
  56. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/intervention_testing/intervention_runner.py +0 -0
  57. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/intervention_testing/intervention_storage.py +0 -0
  58. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/simulator.py +0 -0
  59. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/situation_simulator/situation_generator.py +0 -0
  60. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/README.md +0 -0
  61. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/__init__.py +0 -0
  62. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/benchmark.py +0 -0
  63. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/canonicalize.py +0 -0
  64. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/catalog.py +0 -0
  65. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/cli.py +0 -0
  66. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/config.py +0 -0
  67. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/content_store.py +0 -0
  68. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/cost.py +0 -0
  69. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/coverage.py +0 -0
  70. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/fetchers/__init__.py +0 -0
  71. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/fetchers/base.py +0 -0
  72. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/fetchers/cloakbrowser_fetcher.py +0 -0
  73. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/fetchers/firecrawl_fetcher.py +0 -0
  74. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/fetchers/hyperbrowser_fetcher.py +0 -0
  75. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/fetchers/pdf_fetcher.py +0 -0
  76. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/fetchers/playwright_fetcher.py +0 -0
  77. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/fetchers/tiered.py +0 -0
  78. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/ingest/__init__.py +0 -0
  79. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/ingest/trace_extraction.py +0 -0
  80. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/ingest/url_extraction.py +0 -0
  81. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/layout.py +0 -0
  82. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/manifest.py +0 -0
  83. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/models.py +0 -0
  84. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/pipeline.py +0 -0
  85. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/quality.py +0 -0
  86. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/reindex.py +0 -0
  87. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/reports.py +0 -0
  88. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/storage/__init__.py +0 -0
  89. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/storage/blob_store.py +0 -0
  90. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/storage/local_store.py +0 -0
  91. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/storage/s3_store.py +0 -0
  92. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/agents_and_tools/source_archive/viewer.py +0 -0
  93. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/__init__.py +0 -0
  94. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/agent_wrappers.py +0 -0
  95. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/ai_utils/__init__.py +0 -0
  96. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/ai_utils/openai_utils.py +0 -0
  97. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/ai_utils/response_types.py +0 -0
  98. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/deprecated_model_classes/README.md +0 -0
  99. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/deprecated_model_classes/claude35sonnet.py +0 -0
  100. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/deprecated_model_classes/deepseek_r1.py +0 -0
  101. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/deprecated_model_classes/gpt4o.py +0 -0
  102. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/deprecated_model_classes/gpt4ovision.py +0 -0
  103. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/deprecated_model_classes/gpto1.py +0 -0
  104. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/deprecated_model_classes/gpto1preview.py +0 -0
  105. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/deprecated_model_classes/metaculus4o.py +0 -0
  106. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/deprecated_model_classes/perplexity.py +0 -0
  107. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/exa_searcher.py +0 -0
  108. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/general_llm.py +0 -0
  109. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/model_interfaces/__init__.py +0 -0
  110. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/model_interfaces/ai_model.py +0 -0
  111. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/model_interfaces/combined_llm_archetype.py +0 -0
  112. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/model_interfaces/incurs_cost.py +0 -0
  113. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/model_interfaces/named_model.py +0 -0
  114. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/model_interfaces/outputs_text.py +0 -0
  115. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/model_interfaces/priced_per_request.py +0 -0
  116. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/model_interfaces/request_limited_model.py +0 -0
  117. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/model_interfaces/retryable_model.py +0 -0
  118. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/model_interfaces/time_limited_model.py +0 -0
  119. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/model_interfaces/token_limited_model.py +0 -0
  120. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/model_interfaces/tokens_are_calculatable.py +0 -0
  121. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/model_interfaces/tokens_incur_cost.py +0 -0
  122. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/model_tracker.py +0 -0
  123. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/resource_managers/__init__.py +0 -0
  124. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/resource_managers/hard_limit_manager.py +0 -0
  125. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/resource_managers/monetary_cost_manager.py +0 -0
  126. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/ai_models/resource_managers/refreshing_bucket_rate_limiter.py +0 -0
  127. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/auto_optimizers/bot_evaluator.py +0 -0
  128. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/auto_optimizers/bot_optimizer.py +0 -0
  129. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/auto_optimizers/control_prompt.py +0 -0
  130. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/auto_optimizers/customizable_bot.py +0 -0
  131. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/auto_optimizers/prompt_data_models.py +0 -0
  132. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/auto_optimizers/prompt_optimizer.py +0 -0
  133. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/auto_optimizers/question_plus_research.py +0 -0
  134. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/calibration_adjustment/.gitignore +0 -0
  135. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/calibration_adjustment/__init__.py +0 -0
  136. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/calibration_adjustment/_training_data.py +0 -0
  137. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/calibration_adjustment/benchmark.ipynb +0 -0
  138. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/calibration_adjustment/calibration_adjuster.py +0 -0
  139. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/calibration_adjustment/constant_shift_adjuster.py +0 -0
  140. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/calibration_adjustment/decision_tree_adjuster.py +0 -0
  141. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/calibration_adjustment/k_means_adjuster.py +0 -0
  142. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/calibration_adjustment/logistic_recalibration_adjuster.py +0 -0
  143. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/calibration_adjustment/step_adjuster.py +0 -0
  144. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/cp_benchmarking/benchmark_displayer.py +0 -0
  145. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/cp_benchmarking/benchmark_for_bot.py +0 -0
  146. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/cp_benchmarking/benchmarker.py +0 -0
  147. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/data_models/__init__.py +0 -0
  148. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/data_models/binary_report.py +0 -0
  149. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/data_models/coherence_link.py +0 -0
  150. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/data_models/conditional_models.py +0 -0
  151. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/data_models/conditional_report.py +0 -0
  152. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/data_models/data_organizer.py +0 -0
  153. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/data_models/forecast_report.py +0 -0
  154. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/data_models/markdown_tree.py +0 -0
  155. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/data_models/questions.py +0 -0
  156. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/data_models/timestamped_predictions.py +0 -0
  157. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/data_models/user_response.py +0 -0
  158. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/__init__.py +0 -0
  159. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/bot_lists.py +0 -0
  160. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/experiments/__init__.py +0 -0
  161. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/experiments/q1_veritas_bot.py +0 -0
  162. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/experiments/q1t_w_convert_to_binary.py +0 -0
  163. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/experiments/q1t_w_personas_and_exa.py +0 -0
  164. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/experiments/q2t_w_decomposition.py +0 -0
  165. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/experiments/q3t_w_asknews.py +0 -0
  166. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/experiments/q3t_w_exa.py +0 -0
  167. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/experiments/q3t_w_q4vbinary.py +0 -0
  168. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/experiments/q4_veritas_bot.py +0 -0
  169. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/experiments/q4v_w_exa.py +0 -0
  170. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/experiments/q4v_w_exa_and_dseekr1.py +0 -0
  171. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/experiments/q4v_w_exa_and_o1_preview.py +0 -0
  172. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/forecast_bot.py +0 -0
  173. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/main_bot.py +0 -0
  174. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/official_bots/__init__.py +0 -0
  175. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/official_bots/gpt_4_1_optimized_bot.py +0 -0
  176. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/official_bots/no_research_one_shot_bot.py +0 -0
  177. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/official_bots/q1_template_bot.py +0 -0
  178. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/official_bots/q2_template_bot.py +0 -0
  179. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/official_bots/q3_template_bot.py +0 -0
  180. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/official_bots/q4_template_bot.py +0 -0
  181. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/official_bots/research_only_bot_2025_fall.py +0 -0
  182. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/official_bots/template_bot_2025_fall.py +0 -0
  183. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/official_bots/template_bot_2026_fall.py +0 -0
  184. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/official_bots/template_bot_2026_spring.py +0 -0
  185. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/official_bots/template_bot_2026_summer.py +0 -0
  186. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/official_bots/uniform_probability_bot.py +0 -0
  187. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/forecast_bots/template_bot.py +0 -0
  188. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/Home.py +0 -0
  189. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/__init__.py +0 -0
  190. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/app_pages/base_rate_page.py +0 -0
  191. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/app_pages/benchmark_page.py +0 -0
  192. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/app_pages/chat_page.py +0 -0
  193. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/app_pages/congress_v2_page.py +0 -0
  194. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/app_pages/csv_agent.py +0 -0
  195. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/app_pages/estimator_page.py +0 -0
  196. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/app_pages/forecaster_page.py +0 -0
  197. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/app_pages/intervention_leaderboard_page.py +0 -0
  198. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/app_pages/key_factors_page.py +0 -0
  199. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/app_pages/niche_list_researcher_page.py +0 -0
  200. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/app_pages/simulator_page.py +0 -0
  201. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/example_outputs/base_rate_page_examples.json +0 -0
  202. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/example_outputs/congress_page_example.json +0 -0
  203. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/example_outputs/congress_v2_page_example.json +0 -0
  204. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/example_outputs/estimator_page_examples.json +0 -0
  205. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/example_outputs/forecast_page_examples.json +0 -0
  206. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/example_outputs/key_factors_page_examples.json +0 -0
  207. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/example_outputs/niche_list_page_examples.json +0 -0
  208. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/example_outputs/question_generator_page_examples.json +0 -0
  209. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/helpers/app_page.py +0 -0
  210. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/helpers/custom_auth.py +0 -0
  211. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/helpers/report_displayer.py +0 -0
  212. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/front_end/helpers/tool_page.py +0 -0
  213. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/helpers/__init__.py +0 -0
  214. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/helpers/asknews_cache.py +0 -0
  215. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/helpers/asknews_searcher.py +0 -0
  216. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/helpers/forecast_database_manager.py +0 -0
  217. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/helpers/metaculus_api.py +0 -0
  218. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/helpers/prediction_extractor.py +0 -0
  219. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/helpers/structure_output.py +0 -0
  220. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/helpers/works_cited_creator.py +0 -0
  221. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/util/__init__.py +0 -0
  222. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/util/async_batching.py +0 -0
  223. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/util/coda_utils.py +0 -0
  224. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/util/custom_logger.py +0 -0
  225. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/util/file_manipulation.py +0 -0
  226. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/util/jsonable.py +0 -0
  227. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/util/optional_imports.py +0 -0
  228. {forecasting_tools-0.3.4 → forecasting_tools-0.3.5}/forecasting_tools/util/stats.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forecasting-tools
3
- Version: 0.3.4
3
+ Version: 0.3.5
4
4
  Summary: AI forecasting and research tools to help humans reason about and forecast the future
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -35,7 +35,7 @@ Requires-Dist: exceptiongroup (>=1.2.2,<2.0.0)
35
35
  Requires-Dist: faker (>=37.0.0,<41.0.0) ; extra == "agents" or extra == "front-end" or extra == "all"
36
36
  Requires-Dist: firecrawl-py (>=4.0,<5.0.0) ; extra == "source-archive" or extra == "all"
37
37
  Requires-Dist: hyperbrowser (>=0.53.0,<2.0.0) ; extra == "source-archive" or extra == "agents" or extra == "front-end" or extra == "all"
38
- Requires-Dist: litellm (>=1.59.1,<2.0.0,!=1.76.*,!=1.77.*,!=1.82.7,!=1.82.8)
38
+ Requires-Dist: litellm (>=1.59.1,<2.0.0,!=1.76.*,!=1.77.*,!=1.82.7,!=1.82.8,!=1.107.*,!=1.108.*,!=1.109.*)
39
39
  Requires-Dist: nest-asyncio (>=1.5.8,<2.0.0)
40
40
  Requires-Dist: numpy (>=1.26.0,<3.0.0)
41
41
  Requires-Dist: openai (>=1.51.0,<4.0.0)
@@ -71,8 +71,6 @@ Description-Content-Type: text/markdown
71
71
  # Quick Start
72
72
  Install this package with `pip install forecasting-tools`
73
73
 
74
- Demo website: https://forecasting-tools.streamlit.app/
75
-
76
74
  Demo repo (get a Metaculus bot running in 30min): https://github.com/Metaculus/metac-bot-template
77
75
 
78
76
  # Overview
@@ -87,7 +85,7 @@ Here are the tools most likely to be useful to you:
87
85
  Here are some other features of the project (not all are documented yet):
88
86
  - **General LLM Wrapper:** A unified interface around litellm with retry logic, the Metaculus proxy, structured outputs, and cost tracking
89
87
  - **Monetary Cost Manager:** for tracking AI and API expenses
90
- - **Other experimental tools:** See the demo site for other AI forecasting tools that this project supports (not all are documented). Also see the `scripts` folder for other common workflows and entry points into the code.
88
+ - **Other experimental tools:** See the Streamlit front end in `forecasting_tools/front_end/` (see "Running the Front End" below) for other AI forecasting tools that this project supports (not all are documented). Also see the `scripts` folder for other common workflows and entry points into the code.
91
89
 
92
90
  All the examples below are in a Jupyter Notebook called `README.ipynb` which you can run locally to test the package (make sure to run the first cell though).
93
91
 
@@ -9,8 +9,6 @@
9
9
  # Quick Start
10
10
  Install this package with `pip install forecasting-tools`
11
11
 
12
- Demo website: https://forecasting-tools.streamlit.app/
13
-
14
12
  Demo repo (get a Metaculus bot running in 30min): https://github.com/Metaculus/metac-bot-template
15
13
 
16
14
  # Overview
@@ -25,7 +23,7 @@ Here are the tools most likely to be useful to you:
25
23
  Here are some other features of the project (not all are documented yet):
26
24
  - **General LLM Wrapper:** A unified interface around litellm with retry logic, the Metaculus proxy, structured outputs, and cost tracking
27
25
  - **Monetary Cost Manager:** for tracking AI and API expenses
28
- - **Other experimental tools:** See the demo site for other AI forecasting tools that this project supports (not all are documented). Also see the `scripts` folder for other common workflows and entry points into the code.
26
+ - **Other experimental tools:** See the Streamlit front end in `forecasting_tools/front_end/` (see "Running the Front End" below) for other AI forecasting tools that this project supports (not all are documented). Also see the `scripts` folder for other common workflows and entry points into the code.
29
27
 
30
28
  All the examples below are in a Jupyter Notebook called `README.ipynb` which you can run locally to test the package (make sure to run the first cell though).
31
29
 
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
+ import math
4
5
  from typing import TYPE_CHECKING
5
6
 
6
7
  from pydantic import BaseModel, Field, model_validator
@@ -33,29 +34,16 @@ class PredictedOptionList(BaseModel):
33
34
  "too far from 1 to be confident that normalization will deliver an "
34
35
  "intended prediction."
35
36
  )
36
- logger.warning(
37
- f"Sum of option probabilities {sum_of_probabilities} is not 1, but is close to 1. "
38
- "Normalizing the probabilities."
39
- )
40
-
41
- # Step 1: Clamp values
42
- max_clamped_prob = 0.99
43
- min_clamped_prob = 0.01
44
- clamped_list = [
45
- max(min(x.probability, max_clamped_prob), min_clamped_prob)
46
- for x in self.predicted_options
47
- ]
48
-
49
- # Step 2: Calculate the sum of all elements
50
- total_sum_decimal = sum(clamped_list)
51
-
52
- # Step 3: Normalize the list so that all elements add up to 1
53
- normalized_list = [x / total_sum_decimal for x in clamped_list]
37
+ if not math.isclose(sum_of_probabilities, 1):
38
+ logger.warning(
39
+ f"Sum of option probabilities {sum_of_probabilities} is not 1, but is close to 1. "
40
+ "Normalizing the probabilities."
41
+ )
54
42
 
55
- # Step 4: Adjust for any small floating-point errors
56
- adjustment = 1.0 - sum(normalized_list)
57
- normalized_list[-1] += adjustment
58
- normalized_option_probabilities = normalized_list
43
+ normalized_option_probabilities = self._normalize_with_minimum(
44
+ [option.probability for option in self.predicted_options],
45
+ minimum_probability=0.001,
46
+ )
59
47
 
60
48
  new_sum = sum(normalized_option_probabilities)
61
49
  assert (
@@ -81,6 +69,56 @@ class PredictedOptionList(BaseModel):
81
69
  ]
82
70
  return self
83
71
 
72
+ @staticmethod
73
+ def _normalize_with_minimum(
74
+ probabilities: list[float], minimum_probability: float
75
+ ) -> list[float]:
76
+ """
77
+ Normalizes to sum to 1 while keeping every option at or above the minimum.
78
+ Options below the minimum are pinned to it and the excess is taken
79
+ proportionally from the remaining options, so with 2+ options no option
80
+ can exceed 1 - minimum_probability.
81
+ """
82
+ if minimum_probability * len(probabilities) >= 1:
83
+ raise ValueError(
84
+ f"Cannot give all {len(probabilities)} options a probability of at least {minimum_probability}"
85
+ )
86
+ total = sum(probabilities)
87
+ unadjusted = [probability / total for probability in probabilities]
88
+ normalized = unadjusted
89
+ pinned_indices: set[int] = set()
90
+ while True:
91
+ newly_pinned_indices = {
92
+ index
93
+ for index, probability in enumerate(normalized)
94
+ if probability < minimum_probability and index not in pinned_indices
95
+ }
96
+ if not newly_pinned_indices:
97
+ break
98
+ pinned_indices |= newly_pinned_indices
99
+ unpinned_mass = 1 - minimum_probability * len(pinned_indices)
100
+ unpinned_total = sum(
101
+ probability
102
+ for index, probability in enumerate(unadjusted)
103
+ if index not in pinned_indices
104
+ )
105
+ normalized = [
106
+ (
107
+ minimum_probability
108
+ if index in pinned_indices
109
+ else probability * unpinned_mass / unpinned_total
110
+ )
111
+ for index, probability in enumerate(unadjusted)
112
+ ]
113
+
114
+ largest_index = normalized.index(max(normalized))
115
+ normalized[largest_index] = 1.0 - sum(
116
+ probability
117
+ for index, probability in enumerate(normalized)
118
+ if index != largest_index
119
+ )
120
+ return normalized
121
+
84
122
  def to_dict(self) -> dict[str, float]:
85
123
  return {
86
124
  option.option_name: option.probability for option in self.predicted_options
@@ -585,9 +585,20 @@ class NumericDistribution(BaseModel):
585
585
  return float(cap_pmf(scale).sum())
586
586
 
587
587
  # find the appropriate scale search space
588
+ # An increasing CDF needs at most ~46 expansions (scale <= cap / min bin = 3800), so hitting
589
+ # the limit means no scale works (e.g. negative bins from a non-increasing CDF)
590
+ max_scale_expansions = 100
588
591
  lo = hi = scale = 1.0
592
+ expansions = 0
589
593
  while capped_sum(hi) < 1.0:
594
+ if expansions >= max_scale_expansions:
595
+ raise ValueError(
596
+ "Could not standardize CDF: no scale makes the capped probability mass sum to 1. "
597
+ f"Smallest in-range bin probability is {pmf[1:-1].min():.3g} "
598
+ "(negative means the CDF is not increasing)."
599
+ )
590
600
  hi *= 1.2
601
+ expansions += 1
591
602
  # hone in on scale value that makes capped sum 1
592
603
  for _ in range(100):
593
604
  scale = 0.5 * (lo + hi)
@@ -108,6 +108,16 @@ class AuthorMismatchError(ValueError):
108
108
  class MetaculusClient:
109
109
  """
110
110
  Documentation for the API can be found at https://www.metaculus.com/api/
111
+
112
+ Setting acting_user (a username or user id) makes the client forecast,
113
+ comment, create questions, and read "my forecasts" as that account while
114
+ authenticating with the token. The token must belong to the acting user's
115
+ bot owner, an account allowed to act as metac bots, or a superuser.
116
+ Getting a question by post id is done fully as the acting user, but
117
+ question lists are still fetched as the token's account, so a question only
118
+ the acting user can see will not be listed. Public comments and other
119
+ endpoints (e.g. coherence links, bot management) are not supported for the
120
+ acting user and act as the token's account or raise.
111
121
  """
112
122
 
113
123
  # NOTE: The tourament slug can be used for ID as well (e.g. "aibq2" or "quarterly-cup" instead of 32721 or 32630)
@@ -174,6 +184,7 @@ class MetaculusClient:
174
184
  sleep_seconds_between_requests: float = 3.5,
175
185
  sleep_jitter_seconds: float = 1,
176
186
  token: str | None = None,
187
+ acting_user: str | int | None = None,
177
188
  ):
178
189
  default_base_url = "https://www.metaculus.com/api"
179
190
  self.base_url = (
@@ -188,6 +199,7 @@ class MetaculusClient:
188
199
  logger.warning(
189
200
  "METACULUS_TOKEN environment variable and/or token field not set"
190
201
  )
202
+ self.acting_user = acting_user
191
203
 
192
204
  @retry_with_exponential_backoff()
193
205
  def get_user_bots(self) -> list[UserResponse]:
@@ -250,15 +262,27 @@ class MetaculusClient:
250
262
  is_private: bool = True,
251
263
  included_forecast: bool = True,
252
264
  ) -> None:
265
+ comment = {
266
+ "on_post": post_id,
267
+ "text": comment_text,
268
+ "is_private": is_private,
269
+ "included_forecast": included_forecast,
270
+ }
271
+ if self.acting_user is not None:
272
+ if not is_private:
273
+ raise ValueError(
274
+ "Public comments cannot be posted with acting_user set: the "
275
+ "bulk endpoint that supports acting_user only accepts private "
276
+ "comments."
277
+ )
278
+ self._post_bulk_forecasts_and_comments(comments=[comment])
279
+ logger.info(f"Posted comment on post {post_id} as {self.acting_user}")
280
+ return
281
+
253
282
  self._sleep_between_requests()
254
283
  response = requests.post(
255
284
  f"{self.base_url}/comments/create/",
256
- json={
257
- "on_post": post_id,
258
- "text": comment_text,
259
- "is_private": is_private,
260
- "included_forecast": included_forecast,
261
- },
285
+ json=comment,
262
286
  **self._get_auth_headers(), # type: ignore
263
287
  timeout=self.timeout,
264
288
  )
@@ -371,6 +395,7 @@ class MetaculusClient:
371
395
  headers = self._get_auth_headers()
372
396
  response = requests.get(
373
397
  url,
398
+ params={"acting_user": self.acting_user},
374
399
  **headers, # type: ignore
375
400
  timeout=self.timeout,
376
401
  )
@@ -614,6 +639,15 @@ class MetaculusClient:
614
639
  def _post_question_prediction(
615
640
  self, question_id: int, forecast_payload: dict
616
641
  ) -> None:
642
+ if self.acting_user is not None:
643
+ self._post_bulk_forecasts_and_comments(
644
+ forecasts=[{"question": question_id, **forecast_payload}]
645
+ )
646
+ logger.info(
647
+ f"Posted prediction on question {question_id} as {self.acting_user}"
648
+ )
649
+ return
650
+
617
651
  url = f"{self.base_url}/questions/forecast/"
618
652
  self._sleep_between_requests()
619
653
  response = requests.post(
@@ -631,6 +665,96 @@ class MetaculusClient:
631
665
  logger.info(f"Posted prediction on question {question_id}")
632
666
  raise_for_status_with_additional_info(response)
633
667
 
668
+ def _post_bulk_forecasts_and_comments(
669
+ self,
670
+ forecasts: list[dict] | None = None,
671
+ comments: list[dict] | None = None,
672
+ ) -> None:
673
+ """
674
+ Not retried here since callers are already wrapped in
675
+ retry_with_exponential_backoff.
676
+ """
677
+ self._sleep_between_requests()
678
+ response = requests.post(
679
+ f"{self.base_url}/questions/bulk-forecast-comment/",
680
+ json={
681
+ "acting_user": self.acting_user,
682
+ "forecasts": forecasts or [],
683
+ "comments": comments or [],
684
+ },
685
+ **self._get_auth_headers(), # type: ignore
686
+ timeout=self.timeout,
687
+ )
688
+ raise_for_status_with_additional_info(response)
689
+
690
+ def _get_acting_user_forecasts(
691
+ self, question_ids: list[int]
692
+ ) -> dict[int, list[dict]]:
693
+ """
694
+ Returns the acting user's forecasts (oldest first) keyed by question id.
695
+ Questions the acting user cannot view are left out. Not retried here
696
+ since callers are already wrapped in retry_with_exponential_backoff.
697
+ """
698
+ self._sleep_between_requests()
699
+ response = requests.post(
700
+ f"{self.base_url}/questions/bulk-forecast-read/",
701
+ json={"acting_user": self.acting_user, "question_ids": question_ids},
702
+ **self._get_auth_headers(), # type: ignore
703
+ timeout=self.timeout,
704
+ )
705
+ raise_for_status_with_additional_info(response)
706
+ results: list[dict] = json.loads(response.content)["results"]
707
+ return {result["question_id"]: result["forecasts"] for result in results}
708
+
709
+ def _set_my_forecasts_field_to_acting_users(self, post_jsons: list[dict]) -> None:
710
+ """
711
+ The posts list endpoint fills the "my_forecasts" field with the token
712
+ account's forecasts and, unlike the post detail endpoint, doesn't take
713
+ acting_user. With an acting user, this overwrites that field in the
714
+ given jsons with the acting user's forecasts so that
715
+ already_forecasted, previous_forecasts, etc. describe the acting user.
716
+ Score data is not available this way and is left empty.
717
+ """
718
+ if self.acting_user is None:
719
+ return
720
+ question_jsons = [
721
+ question_json
722
+ for post_json in post_jsons
723
+ for question_json in self._get_question_jsons_in_post(post_json)
724
+ ]
725
+ if not question_jsons:
726
+ return
727
+ forecasts_by_question = self._get_acting_user_forecasts(
728
+ sorted({question_json["id"] for question_json in question_jsons})
729
+ )
730
+ for question_json in question_jsons:
731
+ history = forecasts_by_question.get(question_json["id"], [])
732
+ question_json["my_forecasts"] = {
733
+ "history": history,
734
+ "latest": history[-1] if history else None,
735
+ "score_data": {},
736
+ }
737
+
738
+ @staticmethod
739
+ def _get_question_jsons_in_post(post_json: dict) -> list[dict]:
740
+ if "group_of_questions" in post_json:
741
+ return list(post_json["group_of_questions"]["questions"])
742
+ if "conditional" in post_json:
743
+ conditional: dict = post_json["conditional"]
744
+ return [
745
+ conditional[key]
746
+ for key in (
747
+ "condition",
748
+ "condition_child",
749
+ "question_yes",
750
+ "question_no",
751
+ )
752
+ if conditional.get(key)
753
+ ]
754
+ if post_json.get("question"):
755
+ return [post_json["question"]]
756
+ return []
757
+
634
758
  @retry_with_exponential_backoff()
635
759
  def _get_questions_from_api(
636
760
  self, params: dict[str, Any], group_question_mode: GroupQuestionMode
@@ -653,6 +777,7 @@ class MetaculusClient:
653
777
  f"Removed {len(removed_posts)} posts that "
654
778
  "are not supported (e.g. notebook or conditional question)"
655
779
  )
780
+ self._set_my_forecasts_field_to_acting_users(supported_posts)
656
781
 
657
782
  questions: list[MetaculusQuestion] = []
658
783
  for q in supported_posts:
@@ -885,7 +1010,9 @@ class MetaculusClient:
885
1010
  if api_filter.allowed_tournaments:
886
1011
  url_params["tournaments"] = api_filter.allowed_tournaments
887
1012
 
888
- if api_filter.is_previously_forecasted_by_user:
1013
+ # The server only allows forecaster_id to be the token's own account,
1014
+ # so for an acting user this is filtered locally instead
1015
+ if api_filter.is_previously_forecasted_by_user and self.acting_user is None:
889
1016
  user_id = self.get_current_user_id()
890
1017
  if user_id:
891
1018
  url_params["forecaster_id"] = user_id
@@ -961,6 +1088,11 @@ class MetaculusClient:
961
1088
  api_filter.cp_reveal_time_lt,
962
1089
  )
963
1090
 
1091
+ if api_filter.is_previously_forecasted_by_user and self.acting_user is not None:
1092
+ questions = [
1093
+ question for question in questions if question.already_forecasted
1094
+ ]
1095
+
964
1096
  return questions
965
1097
 
966
1098
  @staticmethod
@@ -1222,8 +1354,10 @@ class MetaculusClient:
1222
1354
  """
1223
1355
  Setting acting_user (a username or user id) creates the post with that user
1224
1356
  as author. The API token must own that bot, be allowed to act as metac bots,
1225
- or belong to a superuser.
1357
+ or belong to a superuser. Defaults to the client's acting_user.
1226
1358
  """
1359
+ if acting_user is None:
1360
+ acting_user = self.acting_user
1227
1361
  question_data = self._get_post_create_data(question, acting_user)
1228
1362
  self._sleep_between_requests()
1229
1363
  response = requests.post(
@@ -46,7 +46,14 @@ def raise_for_status_with_additional_info(
46
46
  f"Response JSON: {response_json}."
47
47
  )
48
48
  logger.error(error_message)
49
- raise requests.exceptions.HTTPError(error_message) from e
49
+ raise requests.exceptions.HTTPError(error_message, response=response) from e
50
+
51
+
52
+ def _is_permanent_client_error(error: Exception) -> bool:
53
+ if not isinstance(error, requests.exceptions.HTTPError) or error.response is None:
54
+ return False
55
+ status_code = error.response.status_code
56
+ return 400 <= status_code < 500 and status_code != 429
50
57
 
51
58
 
52
59
  def retry_with_exponential_backoff(
@@ -82,6 +89,11 @@ def retry_with_exponential_backoff(
82
89
  try:
83
90
  return func(*args, **kwargs)
84
91
  except retry_on_exceptions as e:
92
+ if _is_permanent_client_error(e):
93
+ logger.warning(
94
+ f"Not retrying {func.__name__} since 4xx errors (other than 429) are not transient. Error: {e}"
95
+ )
96
+ raise
85
97
  retries += 1
86
98
  if retries > max_retries:
87
99
  logger.error(
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "forecasting-tools"
3
- version = "0.3.4"
3
+ version = "0.3.5"
4
4
  description = "AI forecasting and research tools to help humans reason about and forecast the future"
5
5
  authors = ["Benjamin Wilson <ben@metaculus.com>"]
6
6
  license = "MIT"
@@ -38,7 +38,7 @@ pillow = ">=9.0.0,<13.0.0"
38
38
  typeguard = "^4.3.0"
39
39
  scikit-learn = {version = "^1.5.2", optional = true}
40
40
  scipy = {version = ">=1.10.0,<2.0.0", optional = true}
41
- litellm = ">=1.59.1,<2.0.0,!=1.76.*,!=1.77.*,!=1.82.7,!=1.82.8"
41
+ litellm = ">=1.59.1,<2.0.0,!=1.76.*,!=1.77.*,!=1.82.7,!=1.82.8,!=1.107.*,!=1.108.*,!=1.109.*"
42
42
  numpy = ">=1.26.0,<3.0.0"
43
43
  exceptiongroup = "^1.2.2"
44
44
  faker = {version = ">=37.0.0,<41.0.0", optional = true}