qwen-sci 0.2.2__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.
Files changed (549) hide show
  1. qwen_sci-0.2.2.dist-info/METADATA +492 -0
  2. qwen_sci-0.2.2.dist-info/RECORD +549 -0
  3. qwen_sci-0.2.2.dist-info/WHEEL +4 -0
  4. qwen_sci-0.2.2.dist-info/entry_points.txt +6 -0
  5. qwen_sci-0.2.2.dist-info/licenses/LICENSE +21 -0
  6. src/1.txt +1 -0
  7. src/__init__.py +1 -0
  8. src/__main__.py +5 -0
  9. src/agents/__init__.py +1 -0
  10. src/agents/experiment_design_agent/__init__.py +268 -0
  11. src/agents/experiment_design_agent/artifacts.py +747 -0
  12. src/agents/experiment_design_agent/cache.py +319 -0
  13. src/agents/experiment_design_agent/completeness.py +130 -0
  14. src/agents/experiment_design_agent/contracts.py +771 -0
  15. src/agents/experiment_design_agent/counterexample_analyzer.py +187 -0
  16. src/agents/experiment_design_agent/design_evidence_paper_screener.py +470 -0
  17. src/agents/experiment_design_agent/discipline_catalog.py +178 -0
  18. src/agents/experiment_design_agent/evidence_cards.py +338 -0
  19. src/agents/experiment_design_agent/evidence_planner.py +452 -0
  20. src/agents/experiment_design_agent/formal_reasoning_planner.py +884 -0
  21. src/agents/experiment_design_agent/fulltext_acquisition.py +863 -0
  22. src/agents/experiment_design_agent/idea_adapter.py +100 -0
  23. src/agents/experiment_design_agent/idea_intake.py +87 -0
  24. src/agents/experiment_design_agent/llm_json.py +328 -0
  25. src/agents/experiment_design_agent/orchestrator.py +1220 -0
  26. src/agents/experiment_design_agent/reasoning_context.py +183 -0
  27. src/agents/experiment_design_agent/reasoning_validation.py +546 -0
  28. src/agents/experiment_design_agent/run.py +481 -0
  29. src/agents/experiment_design_agent/run_logging.py +292 -0
  30. src/agents/experiment_design_agent/scope_gate.py +198 -0
  31. src/agents/experiment_design_agent/study_type_composer.py +1360 -0
  32. src/agents/experiment_design_agent/survey_evidence.py +1381 -0
  33. src/agents/experiment_design_agent/template_router.py +195 -0
  34. src/agents/experiment_design_agent/variable_claim_extractor.py +93 -0
  35. src/agents/idea_agent/__init__.py +1 -0
  36. src/agents/idea_agent/agent/__init__.py +6 -0
  37. src/agents/idea_agent/agent/artifacts.py +308 -0
  38. src/agents/idea_agent/agent/base.py +122 -0
  39. src/agents/idea_agent/agent/ligagent.py +488 -0
  40. src/agents/idea_agent/agent/mcts.py +2060 -0
  41. src/agents/idea_agent/agent/prompts/__init__.py +48 -0
  42. src/agents/idea_agent/agent/prompts/advanced_analysis.py +235 -0
  43. src/agents/idea_agent/agent/prompts/algorithm_alignment.py +55 -0
  44. src/agents/idea_agent/agent/prompts/algorithm_structuring.py +48 -0
  45. src/agents/idea_agent/agent/prompts/component_extraction.py +62 -0
  46. src/agents/idea_agent/agent/prompts/component_novelty_evaluation.py +54 -0
  47. src/agents/idea_agent/agent/prompts/experiment_findings_extraction.py +39 -0
  48. src/agents/idea_agent/agent/prompts/idea_fusion.py +295 -0
  49. src/agents/idea_agent/agent/prompts/idea_introduction.py +30 -0
  50. src/agents/idea_agent/agent/prompts/idea_result_alignment.py +39 -0
  51. src/agents/idea_agent/agent/prompts/input_interpreter.py +52 -0
  52. src/agents/idea_agent/agent/prompts/keynote_ops.py +78 -0
  53. src/agents/idea_agent/agent/prompts/mcts_evaluation.py +235 -0
  54. src/agents/idea_agent/agent/prompts/mcts_generation.py +140 -0
  55. src/agents/idea_agent/agent/prompts/mechanism_commit_query.py +99 -0
  56. src/agents/idea_agent/agent/prompts/memory_selection.py +0 -0
  57. src/agents/idea_agent/agent/prompts/prompt_modes.py +16 -0
  58. src/agents/idea_agent/agent/prompts/rag_query.py +34 -0
  59. src/agents/idea_agent/agent/prompts/re_analysis_replan.py +48 -0
  60. src/agents/idea_agent/agent/prompts/reference_grounding.py +34 -0
  61. src/agents/idea_agent/agent/prompts/root_domain_classification.py +32 -0
  62. src/agents/idea_agent/agent/prompts/scientific_debate.py +88 -0
  63. src/agents/idea_agent/agent/prompts/scientific_materialization.py +45 -0
  64. src/agents/idea_agent/agent/prompts/skill_instantiation.py +193 -0
  65. src/agents/idea_agent/agent/prompts/theory_transfer_query.py +93 -0
  66. src/agents/idea_agent/agent/prompts/topic_background.py +15 -0
  67. src/agents/idea_agent/agent/skills/DEFAULT_SKILL_TEMPLATES.json +263 -0
  68. src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/SKILL.md +40 -0
  69. src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/references/contrast_patterns.md +22 -0
  70. src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/SKILL.md +40 -0
  71. src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/references/mechanism_cards.md +24 -0
  72. src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/SKILL.md +39 -0
  73. src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/references/hierarchy_patterns.md +22 -0
  74. src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/SKILL.md +39 -0
  75. src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/references/mechanism_commit_patterns.md +24 -0
  76. src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/SKILL.md +39 -0
  77. src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/references/coordination_patterns.md +22 -0
  78. src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/SKILL.md +41 -0
  79. src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/references/speculation_patterns.md +22 -0
  80. src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/SKILL.md +39 -0
  81. src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/references/modularity_patterns.md +22 -0
  82. src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/SKILL.md +40 -0
  83. src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/references/transfer_patterns.md +24 -0
  84. src/agents/idea_agent/example/idea_result.json +900 -0
  85. src/agents/idea_agent/run.py +276 -0
  86. src/agents/idea_agent/tests/test_advanced_analysis_contract.py +90 -0
  87. src/agents/idea_agent/tests/test_gap_hypothesis_bridge.py +166 -0
  88. src/agents/idea_agent/tests/test_idea_debate.py +480 -0
  89. src/agents/idea_agent/tests/test_idea_result_v5.py +99 -0
  90. src/agents/idea_agent/tests/test_idea_synthesis.py +92 -0
  91. src/agents/idea_agent/tests/test_mature_idea_sources.py +27 -0
  92. src/agents/idea_agent/tests/test_multimodal_idea_integration.py +386 -0
  93. src/agents/idea_agent/tests/test_profile_materialization_and_scoring.py +219 -0
  94. src/agents/idea_agent/tests/test_profile_native_skill_rendering.py +122 -0
  95. src/agents/idea_agent/tests/test_result_persistence.py +183 -0
  96. src/agents/idea_agent/tests/test_scientific_intervention_profiles.py +241 -0
  97. src/agents/idea_agent/tests/test_scientific_rubric_and_prompts.py +559 -0
  98. src/agents/idea_agent/tests/test_strict_json_object_responses.py +48 -0
  99. src/agents/idea_agent/utils/core/__init__.py +1 -0
  100. src/agents/idea_agent/utils/core/ablation_inputs.py +60 -0
  101. src/agents/idea_agent/utils/core/chat_errors.py +95 -0
  102. src/agents/idea_agent/utils/core/chat_router.py +130 -0
  103. src/agents/idea_agent/utils/core/chat_transport.py +188 -0
  104. src/agents/idea_agent/utils/core/config_loader.py +166 -0
  105. src/agents/idea_agent/utils/core/json_utils.py +28 -0
  106. src/agents/idea_agent/utils/core/logger.py +287 -0
  107. src/agents/idea_agent/utils/core/progress.py +58 -0
  108. src/agents/idea_agent/utils/core/response_parsing.py +57 -0
  109. src/agents/idea_agent/utils/core/run_inputs.py +198 -0
  110. src/agents/idea_agent/utils/mcts/__init__.py +1 -0
  111. src/agents/idea_agent/utils/mcts/component_novelty.py +342 -0
  112. src/agents/idea_agent/utils/mcts/defect_registry.py +302 -0
  113. src/agents/idea_agent/utils/mcts/idea_routes.py +61 -0
  114. src/agents/idea_agent/utils/mcts/idea_taste_presets.py +312 -0
  115. src/agents/idea_agent/utils/mcts/mcts_helpers.py +738 -0
  116. src/agents/idea_agent/utils/mcts/mcts_runtime.py +3000 -0
  117. src/agents/idea_agent/utils/mcts/scientific_intervention_ontology.py +1561 -0
  118. src/agents/idea_agent/utils/mcts/scientific_rubric.py +211 -0
  119. src/agents/idea_agent/utils/mcts/skill_parsing.py +92 -0
  120. src/agents/idea_agent/utils/papers/__init__.py +1 -0
  121. src/agents/idea_agent/utils/papers/paper_graph_vector_store.py +499 -0
  122. src/agents/idea_agent/utils/papers/paper_repository.py +372 -0
  123. src/agents/idea_agent/utils/prompting/__init__.py +1 -0
  124. src/agents/idea_agent/utils/prompting/prompt_views.py +637 -0
  125. src/agents/idea_agent/utils/workflow/__init__.py +1 -0
  126. src/agents/idea_agent/utils/workflow/advanced_analysis_contract.py +244 -0
  127. src/agents/idea_agent/utils/workflow/gap_hypothesis_bridge.py +461 -0
  128. src/agents/idea_agent/utils/workflow/idea_contract.py +426 -0
  129. src/agents/idea_agent/utils/workflow/idea_debate.py +1238 -0
  130. src/agents/idea_agent/utils/workflow/idea_diversity.py +163 -0
  131. src/agents/idea_agent/utils/workflow/idea_fusion.py +889 -0
  132. src/agents/idea_agent/utils/workflow/idea_helpers.py +478 -0
  133. src/agents/idea_agent/utils/workflow/idea_portfolio.py +422 -0
  134. src/agents/idea_agent/utils/workflow/idea_synthesis.py +482 -0
  135. src/agents/idea_agent/utils/workflow/ligagent_flow.py +581 -0
  136. src/agents/idea_agent/utils/workflow/ligagent_handlers.py +1836 -0
  137. src/agents/idea_agent/utils/workflow/ligagent_helpers.py +1331 -0
  138. src/agents/idea_agent/utils/workflow/ligagent_utils.py +322 -0
  139. src/agents/idea_agent/utils/workflow/mature_idea_sources.py +648 -0
  140. src/agents/idea_agent/utils/workflow/multimodal_data_anchoring.py +578 -0
  141. src/agents/idea_agent/utils/workflow/result_persistence.py +774 -0
  142. src/agents/idea_agent/utils/workflow/stage_contract.py +76 -0
  143. src/agents/idea_agent/utils/workflow/workflow_runtime.py +288 -0
  144. src/agents/research_plan_author/__init__.py +50 -0
  145. src/agents/research_plan_author/artifacts.py +270 -0
  146. src/agents/research_plan_author/authoring_blueprint.py +1246 -0
  147. src/agents/research_plan_author/bibtex_renderer.py +252 -0
  148. src/agents/research_plan_author/contract_repair.py +131 -0
  149. src/agents/research_plan_author/contracts.py +745 -0
  150. src/agents/research_plan_author/cross_section_editor.py +232 -0
  151. src/agents/research_plan_author/document_quality.py +987 -0
  152. src/agents/research_plan_author/idea_evolution.py +223 -0
  153. src/agents/research_plan_author/input_loader.py +76 -0
  154. src/agents/research_plan_author/latex_compiler.py +260 -0
  155. src/agents/research_plan_author/latex_safety.py +159 -0
  156. src/agents/research_plan_author/llm_json.py +74 -0
  157. src/agents/research_plan_author/markdown_renderer.py +275 -0
  158. src/agents/research_plan_author/pdf_validator.py +149 -0
  159. src/agents/research_plan_author/render.py +166 -0
  160. src/agents/research_plan_author/render_artifacts.py +187 -0
  161. src/agents/research_plan_author/run.py +765 -0
  162. src/agents/research_plan_author/run_logging.py +209 -0
  163. src/agents/research_plan_author/section_cache.py +184 -0
  164. src/agents/research_plan_author/section_composer.py +1764 -0
  165. src/agents/research_plan_author/section_router.py +209 -0
  166. src/agents/research_plan_author/semantic_validator.py +254 -0
  167. src/agents/research_plan_author/source_bundle.py +46 -0
  168. src/agents/research_plan_author/source_registry.py +208 -0
  169. src/agents/research_plan_author/survey_source_loader.py +137 -0
  170. src/agents/research_plan_author/template_adapter.py +173 -0
  171. src/agents/research_plan_author/template_profile.py +204 -0
  172. src/agents/research_plan_author/tex_renderer.py +496 -0
  173. src/agents/research_plan_author/theory_presentation.py +134 -0
  174. src/agents/research_plan_author/theory_spine.py +703 -0
  175. src/agents/survey_agent/Dockerfile +54 -0
  176. src/agents/survey_agent/README.md +350 -0
  177. src/agents/survey_agent/config/deep_survey.yaml +217 -0
  178. src/agents/survey_agent/config/deep_survey_batch.yaml +176 -0
  179. src/agents/survey_agent/config/deep_survey_fast.yaml +183 -0
  180. src/agents/survey_agent/config/evaluate_baseline.yaml +197 -0
  181. src/agents/survey_agent/config/outcomeRAG.yaml +159 -0
  182. src/agents/survey_agent/env_report.md +348 -0
  183. src/agents/survey_agent/modules/__init__.py +0 -0
  184. src/agents/survey_agent/modules/code_collector.py +2194 -0
  185. src/agents/survey_agent/modules/code_report_generator.py +1282 -0
  186. src/agents/survey_agent/modules/data_manager.py +1713 -0
  187. src/agents/survey_agent/modules/database.py +167 -0
  188. src/agents/survey_agent/modules/fulltext_download_cache.py +462 -0
  189. src/agents/survey_agent/modules/fulltext_http.py +134 -0
  190. src/agents/survey_agent/modules/fulltext_resolution.py +749 -0
  191. src/agents/survey_agent/modules/judge.py +434 -0
  192. src/agents/survey_agent/modules/outcome_RAG.py +236 -0
  193. src/agents/survey_agent/modules/paper_graph_retriever.py +1075 -0
  194. src/agents/survey_agent/modules/pe.py +1729 -0
  195. src/agents/survey_agent/modules/pseudo_creator.py +782 -0
  196. src/agents/survey_agent/modules/pseudo_reviser.py +537 -0
  197. src/agents/survey_agent/modules/refine_agent.py +1952 -0
  198. src/agents/survey_agent/modules/survey_generator.py +5691 -0
  199. src/agents/survey_agent/modules/survey_visualizer.py +1817 -0
  200. src/agents/survey_agent/modules/visual_prompts.py +327 -0
  201. src/agents/survey_agent/modules/work_analyzer.py +2750 -0
  202. src/agents/survey_agent/modules/work_collector.py +5649 -0
  203. src/agents/survey_agent/scripts/baseline_evaluation.py +411 -0
  204. src/agents/survey_agent/scripts/run_deep_survey.py +224 -0
  205. src/agents/survey_agent/scripts/run_deep_survey_adapter.py +407 -0
  206. src/agents/survey_agent/scripts/run_deep_survey_adapter_with_input.py +409 -0
  207. src/agents/survey_agent/scripts/run_deep_survey_batch.py +228 -0
  208. src/agents/survey_agent/scripts/run_deep_survey_batch_arg.py +243 -0
  209. src/agents/survey_agent/scripts/run_deep_survey_batch_settings.py +218 -0
  210. src/agents/survey_agent/scripts/run_keynotes_gen.py +128 -0
  211. src/agents/survey_agent/scripts/run_work_clusters.py +140 -0
  212. src/agents/survey_agent/utils/__init__.py +0 -0
  213. src/agents/survey_agent/utils/ablation_utils.py +317 -0
  214. src/agents/survey_agent/utils/api_call.py +2518 -0
  215. src/agents/survey_agent/utils/chat_utils.py +82 -0
  216. src/agents/survey_agent/utils/config_utils.py +70 -0
  217. src/agents/survey_agent/utils/convert_to_md.py +29 -0
  218. src/agents/survey_agent/utils/err_info.py +24 -0
  219. src/agents/survey_agent/utils/file_utils.py +176 -0
  220. src/agents/survey_agent/utils/gpu_utils.py +145 -0
  221. src/agents/survey_agent/utils/graph_visualizer.py +279 -0
  222. src/agents/survey_agent/utils/html_utils.py +181 -0
  223. src/agents/survey_agent/utils/latex_generator_intra_cluster.py +529 -0
  224. src/agents/survey_agent/utils/latex_generator_relation_table.py +193 -0
  225. src/agents/survey_agent/utils/mineru_section_packer.py +334 -0
  226. src/agents/survey_agent/utils/mineru_utils.py +396 -0
  227. src/agents/survey_agent/utils/paper.py +144 -0
  228. src/agents/survey_agent/utils/read_yaml.py +15 -0
  229. src/agents/survey_agent/utils/repo_utils.py +167 -0
  230. src/agents/survey_agent/utils/rich_logger.py +77 -0
  231. src/agents/survey_agent/utils/step2v2.py +874 -0
  232. src/agents/survey_agent/utils/step2v2_extractor.py +856 -0
  233. src/agents/survey_agent/utils/topic_survey_storage.py +309 -0
  234. src/agents/survey_agent/utils/utils.py +118 -0
  235. src/agents/survey_agent/utils/work.py +2 -0
  236. src/cli.py +1611 -0
  237. src/config/__init__.py +213 -0
  238. src/config/default.yaml +1122 -0
  239. src/harness/LICENSE +21 -0
  240. src/harness/VENDORED.md +14 -0
  241. src/harness/src/__init__.py +0 -0
  242. src/harness/src/openharness/__init__.py +0 -0
  243. src/harness/src/openharness/__main__.py +6 -0
  244. src/harness/src/openharness/api/__init__.py +21 -0
  245. src/harness/src/openharness/api/client.py +281 -0
  246. src/harness/src/openharness/api/codex_client.py +407 -0
  247. src/harness/src/openharness/api/copilot_auth.py +240 -0
  248. src/harness/src/openharness/api/copilot_client.py +134 -0
  249. src/harness/src/openharness/api/errors.py +19 -0
  250. src/harness/src/openharness/api/openai_client.py +577 -0
  251. src/harness/src/openharness/api/provider.py +186 -0
  252. src/harness/src/openharness/api/registry.py +437 -0
  253. src/harness/src/openharness/api/usage.py +17 -0
  254. src/harness/src/openharness/auth/__init__.py +29 -0
  255. src/harness/src/openharness/auth/external.py +610 -0
  256. src/harness/src/openharness/auth/flows.py +189 -0
  257. src/harness/src/openharness/auth/manager.py +482 -0
  258. src/harness/src/openharness/auth/storage.py +269 -0
  259. src/harness/src/openharness/autopilot/__init__.py +23 -0
  260. src/harness/src/openharness/autopilot/service.py +2239 -0
  261. src/harness/src/openharness/autopilot/types.py +91 -0
  262. src/harness/src/openharness/bridge/__init__.py +20 -0
  263. src/harness/src/openharness/bridge/manager.py +106 -0
  264. src/harness/src/openharness/bridge/session_runner.py +46 -0
  265. src/harness/src/openharness/bridge/types.py +37 -0
  266. src/harness/src/openharness/bridge/work_secret.py +41 -0
  267. src/harness/src/openharness/channels/UPSTREAM +6 -0
  268. src/harness/src/openharness/channels/__init__.py +22 -0
  269. src/harness/src/openharness/channels/adapter.py +131 -0
  270. src/harness/src/openharness/channels/bus/__init__.py +6 -0
  271. src/harness/src/openharness/channels/bus/events.py +38 -0
  272. src/harness/src/openharness/channels/bus/queue.py +44 -0
  273. src/harness/src/openharness/channels/impl/__init__.py +6 -0
  274. src/harness/src/openharness/channels/impl/base.py +141 -0
  275. src/harness/src/openharness/channels/impl/dingtalk.py +445 -0
  276. src/harness/src/openharness/channels/impl/discord.py +311 -0
  277. src/harness/src/openharness/channels/impl/email.py +410 -0
  278. src/harness/src/openharness/channels/impl/feishu.py +1342 -0
  279. src/harness/src/openharness/channels/impl/manager.py +257 -0
  280. src/harness/src/openharness/channels/impl/matrix.py +700 -0
  281. src/harness/src/openharness/channels/impl/mochat.py +897 -0
  282. src/harness/src/openharness/channels/impl/qq.py +141 -0
  283. src/harness/src/openharness/channels/impl/slack.py +284 -0
  284. src/harness/src/openharness/channels/impl/telegram.py +525 -0
  285. src/harness/src/openharness/channels/impl/whatsapp.py +159 -0
  286. src/harness/src/openharness/cli.py +2551 -0
  287. src/harness/src/openharness/commands/__init__.py +21 -0
  288. src/harness/src/openharness/commands/registry.py +2783 -0
  289. src/harness/src/openharness/config/__init__.py +34 -0
  290. src/harness/src/openharness/config/paths.py +160 -0
  291. src/harness/src/openharness/config/schema.py +119 -0
  292. src/harness/src/openharness/config/settings.py +1104 -0
  293. src/harness/src/openharness/coordinator/__init__.py +12 -0
  294. src/harness/src/openharness/coordinator/agent_definitions.py +975 -0
  295. src/harness/src/openharness/coordinator/coordinator_mode.py +520 -0
  296. src/harness/src/openharness/engine/__init__.py +80 -0
  297. src/harness/src/openharness/engine/cost_tracker.py +24 -0
  298. src/harness/src/openharness/engine/messages.py +222 -0
  299. src/harness/src/openharness/engine/query.py +1188 -0
  300. src/harness/src/openharness/engine/query_engine.py +313 -0
  301. src/harness/src/openharness/engine/stream_events.py +90 -0
  302. src/harness/src/openharness/hooks/__init__.py +50 -0
  303. src/harness/src/openharness/hooks/events.py +20 -0
  304. src/harness/src/openharness/hooks/executor.py +242 -0
  305. src/harness/src/openharness/hooks/hot_reload.py +31 -0
  306. src/harness/src/openharness/hooks/loader.py +68 -0
  307. src/harness/src/openharness/hooks/schemas.py +66 -0
  308. src/harness/src/openharness/hooks/types.py +38 -0
  309. src/harness/src/openharness/keybindings/__init__.py +14 -0
  310. src/harness/src/openharness/keybindings/default_bindings.py +11 -0
  311. src/harness/src/openharness/keybindings/loader.py +22 -0
  312. src/harness/src/openharness/keybindings/parser.py +18 -0
  313. src/harness/src/openharness/keybindings/resolver.py +13 -0
  314. src/harness/src/openharness/mcp/__init__.py +79 -0
  315. src/harness/src/openharness/mcp/client.py +298 -0
  316. src/harness/src/openharness/mcp/config.py +16 -0
  317. src/harness/src/openharness/mcp/types.py +76 -0
  318. src/harness/src/openharness/memory/__init__.py +25 -0
  319. src/harness/src/openharness/memory/agent.py +91 -0
  320. src/harness/src/openharness/memory/manager.py +183 -0
  321. src/harness/src/openharness/memory/memdir.py +52 -0
  322. src/harness/src/openharness/memory/migrate.py +137 -0
  323. src/harness/src/openharness/memory/paths.py +22 -0
  324. src/harness/src/openharness/memory/relevance.py +145 -0
  325. src/harness/src/openharness/memory/scan.py +116 -0
  326. src/harness/src/openharness/memory/schema.py +443 -0
  327. src/harness/src/openharness/memory/search.py +71 -0
  328. src/harness/src/openharness/memory/team.py +90 -0
  329. src/harness/src/openharness/memory/types.py +33 -0
  330. src/harness/src/openharness/memory/usage.py +153 -0
  331. src/harness/src/openharness/output_styles/__init__.py +5 -0
  332. src/harness/src/openharness/output_styles/loader.py +42 -0
  333. src/harness/src/openharness/permissions/__init__.py +26 -0
  334. src/harness/src/openharness/permissions/checker.py +200 -0
  335. src/harness/src/openharness/permissions/modes.py +13 -0
  336. src/harness/src/openharness/personalization/__init__.py +6 -0
  337. src/harness/src/openharness/personalization/extractor.py +138 -0
  338. src/harness/src/openharness/personalization/rules.py +64 -0
  339. src/harness/src/openharness/personalization/session_hook.py +65 -0
  340. src/harness/src/openharness/platforms.py +86 -0
  341. src/harness/src/openharness/plugins/__init__.py +53 -0
  342. src/harness/src/openharness/plugins/bundled/__init__.py +0 -0
  343. src/harness/src/openharness/plugins/installer.py +39 -0
  344. src/harness/src/openharness/plugins/loader.py +730 -0
  345. src/harness/src/openharness/plugins/schemas.py +24 -0
  346. src/harness/src/openharness/plugins/types.py +59 -0
  347. src/harness/src/openharness/prompts/__init__.py +14 -0
  348. src/harness/src/openharness/prompts/claudemd.py +48 -0
  349. src/harness/src/openharness/prompts/context.py +187 -0
  350. src/harness/src/openharness/prompts/environment.py +135 -0
  351. src/harness/src/openharness/prompts/system_prompt.py +109 -0
  352. src/harness/src/openharness/sandbox/Dockerfile +6 -0
  353. src/harness/src/openharness/sandbox/__init__.py +33 -0
  354. src/harness/src/openharness/sandbox/adapter.py +148 -0
  355. src/harness/src/openharness/sandbox/docker_backend.py +232 -0
  356. src/harness/src/openharness/sandbox/docker_image.py +103 -0
  357. src/harness/src/openharness/sandbox/path_validator.py +37 -0
  358. src/harness/src/openharness/sandbox/session.py +63 -0
  359. src/harness/src/openharness/services/__init__.py +30 -0
  360. src/harness/src/openharness/services/autodream/__init__.py +34 -0
  361. src/harness/src/openharness/services/autodream/backup.py +104 -0
  362. src/harness/src/openharness/services/autodream/lock.py +138 -0
  363. src/harness/src/openharness/services/autodream/prompt.py +128 -0
  364. src/harness/src/openharness/services/autodream/service.py +313 -0
  365. src/harness/src/openharness/services/compact/__init__.py +1725 -0
  366. src/harness/src/openharness/services/cron.py +137 -0
  367. src/harness/src/openharness/services/cron_scheduler.py +595 -0
  368. src/harness/src/openharness/services/lsp/__init__.py +216 -0
  369. src/harness/src/openharness/services/memory_extract/__init__.py +261 -0
  370. src/harness/src/openharness/services/oauth/__init__.py +0 -0
  371. src/harness/src/openharness/services/session_backend.py +97 -0
  372. src/harness/src/openharness/services/session_memory/__init__.py +139 -0
  373. src/harness/src/openharness/services/session_storage.py +230 -0
  374. src/harness/src/openharness/services/token_estimation.py +15 -0
  375. src/harness/src/openharness/services/tool_outputs.py +55 -0
  376. src/harness/src/openharness/skills/__init__.py +44 -0
  377. src/harness/src/openharness/skills/_frontmatter.py +97 -0
  378. src/harness/src/openharness/skills/bundled/__init__.py +75 -0
  379. src/harness/src/openharness/skills/bundled/content/commit.md +25 -0
  380. src/harness/src/openharness/skills/bundled/content/debug.md +25 -0
  381. src/harness/src/openharness/skills/bundled/content/diagnose.md +35 -0
  382. src/harness/src/openharness/skills/bundled/content/plan.md +39 -0
  383. src/harness/src/openharness/skills/bundled/content/review.md +33 -0
  384. src/harness/src/openharness/skills/bundled/content/simplify.md +26 -0
  385. src/harness/src/openharness/skills/bundled/content/skill-creator.md +175 -0
  386. src/harness/src/openharness/skills/bundled/content/test.md +38 -0
  387. src/harness/src/openharness/skills/loader.py +229 -0
  388. src/harness/src/openharness/skills/registry.py +29 -0
  389. src/harness/src/openharness/skills/types.py +24 -0
  390. src/harness/src/openharness/state/__init__.py +6 -0
  391. src/harness/src/openharness/state/app_state.py +30 -0
  392. src/harness/src/openharness/state/store.py +40 -0
  393. src/harness/src/openharness/swarm/__init__.py +70 -0
  394. src/harness/src/openharness/swarm/in_process.py +693 -0
  395. src/harness/src/openharness/swarm/lockfile.py +24 -0
  396. src/harness/src/openharness/swarm/mailbox.py +522 -0
  397. src/harness/src/openharness/swarm/permission_sync.py +1168 -0
  398. src/harness/src/openharness/swarm/registry.py +410 -0
  399. src/harness/src/openharness/swarm/spawn_utils.py +229 -0
  400. src/harness/src/openharness/swarm/subprocess_backend.py +171 -0
  401. src/harness/src/openharness/swarm/team_lifecycle.py +910 -0
  402. src/harness/src/openharness/swarm/types.py +398 -0
  403. src/harness/src/openharness/swarm/worktree.py +315 -0
  404. src/harness/src/openharness/tasks/__init__.py +18 -0
  405. src/harness/src/openharness/tasks/local_agent_task.py +28 -0
  406. src/harness/src/openharness/tasks/local_shell_task.py +17 -0
  407. src/harness/src/openharness/tasks/manager.py +475 -0
  408. src/harness/src/openharness/tasks/stop_task.py +11 -0
  409. src/harness/src/openharness/tasks/types.py +32 -0
  410. src/harness/src/openharness/themes/__init__.py +21 -0
  411. src/harness/src/openharness/themes/builtin.py +89 -0
  412. src/harness/src/openharness/themes/loader.py +55 -0
  413. src/harness/src/openharness/themes/schema.py +54 -0
  414. src/harness/src/openharness/tools/__init__.py +107 -0
  415. src/harness/src/openharness/tools/agent_tool.py +141 -0
  416. src/harness/src/openharness/tools/ask_user_question_tool.py +46 -0
  417. src/harness/src/openharness/tools/base.py +80 -0
  418. src/harness/src/openharness/tools/bash_tool.py +259 -0
  419. src/harness/src/openharness/tools/brief_tool.py +33 -0
  420. src/harness/src/openharness/tools/config_tool.py +54 -0
  421. src/harness/src/openharness/tools/cron_create_tool.py +105 -0
  422. src/harness/src/openharness/tools/cron_delete_tool.py +32 -0
  423. src/harness/src/openharness/tools/cron_list_tool.py +69 -0
  424. src/harness/src/openharness/tools/cron_toggle_tool.py +37 -0
  425. src/harness/src/openharness/tools/enter_plan_mode_tool.py +28 -0
  426. src/harness/src/openharness/tools/enter_worktree_tool.py +80 -0
  427. src/harness/src/openharness/tools/exit_plan_mode_tool.py +28 -0
  428. src/harness/src/openharness/tools/exit_worktree_tool.py +42 -0
  429. src/harness/src/openharness/tools/file_edit_tool.py +95 -0
  430. src/harness/src/openharness/tools/file_read_tool.py +72 -0
  431. src/harness/src/openharness/tools/file_write_tool.py +87 -0
  432. src/harness/src/openharness/tools/glob_tool.py +175 -0
  433. src/harness/src/openharness/tools/grep_tool.py +374 -0
  434. src/harness/src/openharness/tools/image_generation_tool.py +447 -0
  435. src/harness/src/openharness/tools/image_to_text_tool.py +237 -0
  436. src/harness/src/openharness/tools/list_mcp_resources_tool.py +36 -0
  437. src/harness/src/openharness/tools/lsp_tool.py +154 -0
  438. src/harness/src/openharness/tools/mcp_auth_tool.py +71 -0
  439. src/harness/src/openharness/tools/mcp_tool.py +72 -0
  440. src/harness/src/openharness/tools/notebook_edit_tool.py +97 -0
  441. src/harness/src/openharness/tools/read_mcp_resource_tool.py +38 -0
  442. src/harness/src/openharness/tools/remote_trigger_tool.py +74 -0
  443. src/harness/src/openharness/tools/send_message_tool.py +58 -0
  444. src/harness/src/openharness/tools/skill_tool.py +43 -0
  445. src/harness/src/openharness/tools/sleep_tool.py +32 -0
  446. src/harness/src/openharness/tools/task_create_tool.py +56 -0
  447. src/harness/src/openharness/tools/task_get_tool.py +33 -0
  448. src/harness/src/openharness/tools/task_list_tool.py +35 -0
  449. src/harness/src/openharness/tools/task_output_tool.py +35 -0
  450. src/harness/src/openharness/tools/task_stop_tool.py +30 -0
  451. src/harness/src/openharness/tools/task_update_tool.py +50 -0
  452. src/harness/src/openharness/tools/team_create_tool.py +31 -0
  453. src/harness/src/openharness/tools/team_delete_tool.py +30 -0
  454. src/harness/src/openharness/tools/todo_write_tool.py +46 -0
  455. src/harness/src/openharness/tools/tool_search_tool.py +38 -0
  456. src/harness/src/openharness/tools/web_fetch_tool.py +117 -0
  457. src/harness/src/openharness/tools/web_search_tool.py +119 -0
  458. src/harness/src/openharness/ui/__init__.py +5 -0
  459. src/harness/src/openharness/ui/app.py +320 -0
  460. src/harness/src/openharness/ui/backend_host.py +941 -0
  461. src/harness/src/openharness/ui/coordinator_drain.py +198 -0
  462. src/harness/src/openharness/ui/input.py +32 -0
  463. src/harness/src/openharness/ui/output.py +265 -0
  464. src/harness/src/openharness/ui/permission_dialog.py +14 -0
  465. src/harness/src/openharness/ui/protocol.py +247 -0
  466. src/harness/src/openharness/ui/react_launcher.py +179 -0
  467. src/harness/src/openharness/ui/runtime.py +799 -0
  468. src/harness/src/openharness/ui/textual_app.py +495 -0
  469. src/harness/src/openharness/utils/__init__.py +0 -0
  470. src/harness/src/openharness/utils/file_lock.py +86 -0
  471. src/harness/src/openharness/utils/fs.py +98 -0
  472. src/harness/src/openharness/utils/helpers.py +77 -0
  473. src/harness/src/openharness/utils/network_guard.py +340 -0
  474. src/harness/src/openharness/utils/shell.py +147 -0
  475. src/harness/src/openharness/vim/__init__.py +5 -0
  476. src/harness/src/openharness/vim/transitions.py +8 -0
  477. src/harness/src/openharness/voice/__init__.py +7 -0
  478. src/harness/src/openharness/voice/keyterms.py +10 -0
  479. src/harness/src/openharness/voice/stream_stt.py +8 -0
  480. src/harness/src/openharness/voice/voice_mode.py +44 -0
  481. src/llm/__init__.py +29 -0
  482. src/llm/image_generation.py +402 -0
  483. src/llm/provider_registry.py +419 -0
  484. src/llm/runtime_env.py +64 -0
  485. src/llm/structured_output.py +131 -0
  486. src/llm/vision.py +198 -0
  487. src/memory/__init__.py +2 -0
  488. src/memory/api/__init__.py +1 -0
  489. src/memory/api/base_memory_system_api.py +98 -0
  490. src/memory/api/base_symbolic_memory_system_api.py +190 -0
  491. src/memory/api/base_vector_memory_system_api.py +116 -0
  492. src/memory/api/faiss_memory_system_api.py +228 -0
  493. src/memory/api/slot_process_api.py +827 -0
  494. src/memory/api/symbolic_memory_system_api.py +572 -0
  495. src/memory/memory_system/__init__.py +48 -0
  496. src/memory/memory_system/component_taxonomy.py +430 -0
  497. src/memory/memory_system/llm.py +437 -0
  498. src/memory/memory_system/models.py +168 -0
  499. src/memory/memory_system/user_prompt.py +418 -0
  500. src/memory/memory_system/utils.py +282 -0
  501. src/memory/memory_system/vectorstore.py +311 -0
  502. src/memory/memory_system/working_slot.py +99 -0
  503. src/pipeline/__init__.py +129 -0
  504. src/pipeline/contracts.py +531 -0
  505. src/pipeline/discipline_taxonomy.py +916 -0
  506. src/pipeline/evidence_coverage_ledger.py +1150 -0
  507. src/pipeline/evidence_refinement.py +620 -0
  508. src/pipeline/experiment_to_symbolic.py +137 -0
  509. src/pipeline/multimodal_evidence/__init__.py +28 -0
  510. src/pipeline/multimodal_evidence/capabilities.py +107 -0
  511. src/pipeline/multimodal_evidence/claims.py +135 -0
  512. src/pipeline/multimodal_evidence/contract.py +291 -0
  513. src/pipeline/multimodal_evidence/data_sh_compiler.py +505 -0
  514. src/pipeline/multimodal_evidence/handoff_binding.py +135 -0
  515. src/pipeline/multimodal_evidence/manifest.py +254 -0
  516. src/pipeline/multimodal_evidence/native_analysis.py +479 -0
  517. src/pipeline/multimodal_evidence/perception.py +277 -0
  518. src/pipeline/multimodal_evidence/query_binding.py +65 -0
  519. src/pipeline/multimodal_evidence/rendering.py +120 -0
  520. src/pipeline/multimodal_evidence/safety.py +42 -0
  521. src/pipeline/multimodal_evidence/sampling.py +69 -0
  522. src/pipeline/multimodal_evidence/service.py +148 -0
  523. src/pipeline/multimodal_evidence/survey_integration.py +460 -0
  524. src/pipeline/paper_identity.py +52 -0
  525. src/pipeline/research_design_inventory.py +338 -0
  526. src/pipeline/research_domain_resolution.py +669 -0
  527. src/pipeline/research_identity.py +548 -0
  528. src/pipeline/research_question_contract.py +646 -0
  529. src/pipeline/retrieval_lanes.py +889 -0
  530. src/pipeline/run_loop.py +862 -0
  531. src/pipeline/science_events.py +30 -0
  532. src/pipeline/science_manifests.py +602 -0
  533. src/pipeline/science_run.py +918 -0
  534. src/pipeline/science_stages.py +709 -0
  535. src/pipeline/science_workflow.py +807 -0
  536. src/pipeline/sh_cluster_projection.py +306 -0
  537. src/pipeline/sh_graph_provenance.py +648 -0
  538. src/pipeline/subhypothesis_decomposition.py +392 -0
  539. src/pipeline/subhypothesis_evidence.py +856 -0
  540. src/pipeline/survey_evidence_plan.py +858 -0
  541. src/pipeline/survey_gap_adjudication.py +647 -0
  542. src/pipeline/survey_gap_candidates.py +840 -0
  543. src/pipeline/survey_gap_ledger.py +683 -0
  544. src/pipeline/survey_gap_triage.py +401 -0
  545. src/pipeline/survey_handoff_persistence.py +481 -0
  546. src/pipeline/survey_handoff_projection.py +457 -0
  547. src/pipeline/survey_idea_handoff.py +1112 -0
  548. src/pipeline/survey_idea_loader.py +289 -0
  549. src/research_run_ids.py +11 -0
@@ -0,0 +1,1561 @@
1
+ """Domain-aware scientific intervention profiles for Idea Agent roots.
2
+
3
+ The runtime discipline taxonomy remains the source of canonical discipline IDs.
4
+ This module adds an Idea-Agent-local crosswalk from PaperSeek OpenAlex fields to
5
+ domain-native intervention profiles. It intentionally contains no LLM calls or
6
+ filesystem lookups so profile selection is deterministic and easy to test.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ import re
13
+ from typing import Any, Dict, Mapping, Optional, Sequence
14
+
15
+ from src.pipeline.discipline_taxonomy import (
16
+ canonicalize_discipline_key,
17
+ resolve_discipline_taxonomy,
18
+ )
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class ComponentRoleSpec:
23
+ role_id: str
24
+ label: str
25
+ description: str
26
+ examples: tuple[str, ...] = ()
27
+ required_for_root: bool = True
28
+
29
+ def to_payload(self) -> Dict[str, Any]:
30
+ return {
31
+ "role_id": self.role_id,
32
+ "label": self.label,
33
+ "description": self.description,
34
+ "examples": list(self.examples),
35
+ "required_for_root": self.required_for_root,
36
+ }
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class ContributionModeSpec:
41
+ mode_id: str
42
+ label: str
43
+ primary_contribution: str
44
+ secondary_only: str
45
+ required_evidence_or_validation: str
46
+
47
+ def to_payload(self) -> Dict[str, Any]:
48
+ return {
49
+ "mode_id": self.mode_id,
50
+ "label": self.label,
51
+ "primary_contribution": self.primary_contribution,
52
+ "secondary_only": self.secondary_only,
53
+ "required_evidence_or_validation": self.required_evidence_or_validation,
54
+ }
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class ValidationRequirement:
59
+ requirement_id: str
60
+ label: str
61
+ description: str
62
+
63
+ def to_payload(self) -> Dict[str, str]:
64
+ return {
65
+ "requirement_id": self.requirement_id,
66
+ "label": self.label,
67
+ "description": self.description,
68
+ }
69
+
70
+
71
+ @dataclass(frozen=True)
72
+ class ScientificObjectSchema:
73
+ """Profile-native vocabulary for representing a scientific intervention.
74
+
75
+ The legacy MCTS still stores a list of ``components``. This schema gives
76
+ those components a domain-native interpretation without requiring a new
77
+ atomic-edit protocol, so old idea contracts remain readable.
78
+ """
79
+
80
+ profile_id: str
81
+ object_types: tuple[str, ...]
82
+ allowed_operations: tuple[str, ...]
83
+ target_object_roles: tuple[str, ...] = ()
84
+ claim_delta_roles: tuple[str, ...] = ()
85
+ mechanism_delta_roles: tuple[str, ...] = ()
86
+ evidence_obligation_roles: tuple[str, ...] = ()
87
+ boundary_condition_roles: tuple[str, ...] = ()
88
+ measurement_or_observation_roles: tuple[str, ...] = ()
89
+
90
+ def to_payload(self) -> Dict[str, Any]:
91
+ return {
92
+ "schema_version": "scientific_object_schema_v1",
93
+ "profile_id": self.profile_id,
94
+ "object_types": list(self.object_types),
95
+ "allowed_operations": list(self.allowed_operations),
96
+ "target_object_roles": list(self.target_object_roles),
97
+ "claim_delta_roles": list(self.claim_delta_roles),
98
+ "mechanism_delta_roles": list(self.mechanism_delta_roles),
99
+ "evidence_obligation_roles": list(self.evidence_obligation_roles),
100
+ "boundary_condition_roles": list(self.boundary_condition_roles),
101
+ "measurement_or_observation_roles": list(self.measurement_or_observation_roles),
102
+ }
103
+
104
+
105
+ @dataclass(frozen=True)
106
+ class ScientificInterventionProfile:
107
+ profile_id: str
108
+ label: str
109
+ discipline_keys: tuple[str, ...]
110
+ component_roles: tuple[ComponentRoleSpec, ...]
111
+ contribution_modes: tuple[ContributionModeSpec, ...]
112
+ defect_tags: tuple[str, ...]
113
+ validation_requirements: tuple[ValidationRequirement, ...]
114
+ generation_rules: tuple[str, ...]
115
+ evaluation_anchors: Dict[str, tuple[str, ...]]
116
+ forbidden_default_patterns: tuple[str, ...]
117
+
118
+ def default_component_roles(self) -> tuple[ComponentRoleSpec, ...]:
119
+ return tuple(role for role in self.component_roles if role.required_for_root)
120
+
121
+ def default_component_names(self) -> list[str]:
122
+ return [role.role_id for role in self.default_component_roles()]
123
+
124
+ def to_payload(self) -> Dict[str, Any]:
125
+ payload = {
126
+ "schema_version": "scientific_intervention_profile_v1",
127
+ "profile_id": self.profile_id,
128
+ "label": self.label,
129
+ "discipline_keys": list(self.discipline_keys),
130
+ "component_roles": [role.to_payload() for role in self.component_roles],
131
+ "contribution_modes": [mode.to_payload() for mode in self.contribution_modes],
132
+ "defect_tags": list(self.defect_tags),
133
+ "validation_requirements": [
134
+ requirement.to_payload() for requirement in self.validation_requirements
135
+ ],
136
+ "generation_rules": list(self.generation_rules),
137
+ "evaluation_anchors": {
138
+ key: list(values) for key, values in self.evaluation_anchors.items()
139
+ },
140
+ "forbidden_default_patterns": list(self.forbidden_default_patterns),
141
+ }
142
+ object_schema = get_scientific_object_schema(self.profile_id)
143
+ if object_schema is not None:
144
+ object_payload = object_schema.to_payload()
145
+ payload["scientific_object_schema"] = object_payload
146
+ payload["object_roles"] = list(object_payload.get("object_types") or [])
147
+ payload["allowed_operations"] = list(object_payload.get("allowed_operations") or [])
148
+ payload["evidence_obligations"] = list(
149
+ object_payload.get("evidence_obligation_roles") or []
150
+ )
151
+ payload["boundary_obligations"] = list(
152
+ object_payload.get("boundary_condition_roles") or []
153
+ )
154
+ return payload
155
+
156
+
157
+ @dataclass(frozen=True)
158
+ class ScientificObjectSpec:
159
+ object_id: str
160
+ profile_id: str
161
+ label: str
162
+ description: str
163
+ examples: tuple[str, ...] = ()
164
+ object_type: str = "scientific_object"
165
+ allowed_operations: tuple[str, ...] = ()
166
+ evidence_obligations: tuple[str, ...] = ()
167
+ boundary_roles: tuple[str, ...] = ()
168
+ measurement_roles: tuple[str, ...] = ()
169
+
170
+ def to_payload(self) -> Dict[str, Any]:
171
+ return {
172
+ "object_id": self.object_id,
173
+ "profile_id": self.profile_id,
174
+ "label": self.label,
175
+ "description": self.description,
176
+ "examples": list(self.examples),
177
+ "object_type": self.object_type,
178
+ "allowed_operations": list(self.allowed_operations),
179
+ "evidence_obligations": list(self.evidence_obligations),
180
+ "boundary_roles": list(self.boundary_roles),
181
+ "measurement_roles": list(self.measurement_roles),
182
+ }
183
+
184
+
185
+ @dataclass(frozen=True)
186
+ class ScientificFieldSpec:
187
+ field_id: str
188
+ label: str
189
+ paperseek_domain: str
190
+ discipline_key: str
191
+ primary_profile: str
192
+ secondary_profiles: tuple[str, ...] = ()
193
+ object_role_ids: tuple[str, ...] = ()
194
+
195
+ def to_payload(self) -> Dict[str, Any]:
196
+ return {
197
+ "field_id": self.field_id,
198
+ "label": self.label,
199
+ "paperseek_domain": self.paperseek_domain,
200
+ "discipline_key": self.discipline_key,
201
+ "primary_profile": self.primary_profile,
202
+ "secondary_profiles": list(self.secondary_profiles),
203
+ "object_role_ids": list(self.object_role_ids),
204
+ }
205
+
206
+
207
+ RETAINED_PAPERSEEK_OPENALEX_FIELDS: tuple[str, ...] = (
208
+ "11",
209
+ "13",
210
+ "15",
211
+ "16",
212
+ "17",
213
+ "19",
214
+ "21",
215
+ "22",
216
+ "23",
217
+ "24",
218
+ "25",
219
+ "26",
220
+ "27",
221
+ "28",
222
+ "29",
223
+ "30",
224
+ "31",
225
+ "34",
226
+ "35",
227
+ "36",
228
+ )
229
+
230
+ IGNORED_PAPERSEEK_OPENALEX_FIELDS: frozenset[str] = frozenset(
231
+ {"12", "14", "18", "20", "32", "33"}
232
+ )
233
+
234
+
235
+ def _role(
236
+ role_id: str,
237
+ label: str,
238
+ description: str,
239
+ *examples: str,
240
+ ) -> ComponentRoleSpec:
241
+ return ComponentRoleSpec(
242
+ role_id=role_id,
243
+ label=label,
244
+ description=description,
245
+ examples=tuple(examples),
246
+ )
247
+
248
+
249
+ def _mode(
250
+ mode_id: str,
251
+ label: str,
252
+ primary_contribution: str,
253
+ secondary_only: str,
254
+ required_evidence_or_validation: str,
255
+ ) -> ContributionModeSpec:
256
+ return ContributionModeSpec(
257
+ mode_id=mode_id,
258
+ label=label,
259
+ primary_contribution=primary_contribution,
260
+ secondary_only=secondary_only,
261
+ required_evidence_or_validation=required_evidence_or_validation,
262
+ )
263
+
264
+
265
+ def _validation(requirement_id: str, label: str, description: str) -> ValidationRequirement:
266
+ return ValidationRequirement(requirement_id, label, description)
267
+
268
+
269
+ _COMMON_GENERATION_RULES = (
270
+ "Introduce one concrete intervention compatible with the fixed scientific profile.",
271
+ "Keep the primary contribution tied to a mechanism, relation, measurement, or boundary.",
272
+ "Do not make a generic benchmark, audit, or reporting layer the primary contribution.",
273
+ )
274
+
275
+ _COMPUTATIONAL_ROLES = (
276
+ _role("backbone_model", "Representation or model", "Computational representation or model family."),
277
+ _role("objective", "Objective or decision rule", "Optimization, inference, or decision criterion."),
278
+ _role("evaluation_harness", "Task and evaluation protocol", "Task interface, protocol, and measurable outcome."),
279
+ )
280
+
281
+ _PROFILE_REGISTRY: Dict[str, ScientificInterventionProfile] = {
282
+ "computational_algorithmic": ScientificInterventionProfile(
283
+ profile_id="computational_algorithmic",
284
+ label="Computational and Algorithmic Research",
285
+ discipline_keys=("computer_science",),
286
+ component_roles=_COMPUTATIONAL_ROLES,
287
+ contribution_modes=(
288
+ _mode(
289
+ "algorithmic_mechanism",
290
+ "Algorithmic mechanism",
291
+ "Change representation, algorithm, objective, training, or inference behavior.",
292
+ "A benchmark or generic monitoring wrapper without a mechanism change.",
293
+ "Ablation, counterfactual, stress, resource, and task-level comparison.",
294
+ ),
295
+ ),
296
+ defect_tags=(
297
+ "feature_dumping",
298
+ "latency_bottleneck",
299
+ "weak_fallback_behavior",
300
+ "monolithic_design",
301
+ "unresolved_alternative_explanation",
302
+ ),
303
+ validation_requirements=(
304
+ _validation("task_baseline", "Task baseline", "Compare against a relevant task and method baseline."),
305
+ _validation("mechanism_ablation", "Mechanism ablation", "Remove or replace the proposed mechanism."),
306
+ _validation("resource_stress", "Resource stress", "Report relevant compute, latency, or memory boundaries."),
307
+ ),
308
+ generation_rules=_COMMON_GENERATION_RULES
309
+ + ("Algorithmic or training language is valid when grounded in the topic.",),
310
+ evaluation_anchors={
311
+ "explanatory_power": ("changes task-solving mechanism", "connects intervention to behavior"),
312
+ "identifiability": ("ablation", "counterfactual", "mechanism comparison"),
313
+ "protocol_score": ("baseline", "stress", "resource", "ablation"),
314
+ },
315
+ forbidden_default_patterns=(),
316
+ ),
317
+ "formal_theoretical": ScientificInterventionProfile(
318
+ profile_id="formal_theoretical",
319
+ label="Formal and Theoretical Research",
320
+ discipline_keys=("mathematics", "physics_astronomy"),
321
+ component_roles=(
322
+ _role("formal_object", "Formal object", "Object, structure, or system under formal study."),
323
+ _role("assumptions_or_axioms", "Assumptions or axioms", "Conditions under which the claim is posed."),
324
+ _role("target_relation", "Target proposition or relation", "The theorem, relation, or impossibility claim."),
325
+ _role("derivation_or_construction", "Derivation or construction", "Proof, construction, or mathematical derivation."),
326
+ _role("validity_domain", "Validity domain", "Regime, domain, or conditions where the result applies."),
327
+ _role("counterexample_boundary", "Counterexample or boundary", "Counterexample space and limiting cases."),
328
+ _role("proof_or_verification", "Proof or verification", "Proof audit or reproducible computational check."),
329
+ ),
330
+ contribution_modes=(
331
+ _mode("theorem_or_condition_relaxation", "Theorem or condition relaxation", "Add, relax, or unify a formal condition with a justified result.", "A numerical benchmark without a formal claim.", "Complete assumptions, derivation or proof, and boundary cases."),
332
+ _mode("counterexample_or_unification", "Counterexample or unification", "Separate competing claims with a counterexample or unifying relation.", "A new software module without a formal consequence.", "Reproducible proof, counterexample, or symbolic/numerical verification."),
333
+ ),
334
+ defect_tags=("missing_assumption", "proof_gap", "missing_counterexample", "invalid_generalization", "unresolved_alternative_explanation"),
335
+ validation_requirements=(
336
+ _validation("complete_assumptions", "Complete assumptions", "State assumptions and quantifiers explicitly."),
337
+ _validation("proof_or_derivation", "Proof or derivation", "Provide a proof, derivation, or auditable construction."),
338
+ _validation("boundary_case", "Boundary case", "Check a counterexample or limiting regime."),
339
+ ),
340
+ generation_rules=_COMMON_GENERATION_RULES + ("Prefer formal assumptions, relations, constructions, proofs, or counterexamples over software components.",),
341
+ evaluation_anchors={
342
+ "explanatory_power": ("unifies a relation", "explains a formal obstruction"),
343
+ "identifiability": ("proof", "counterexample", "condition boundary"),
344
+ "protocol_score": ("formal proof", "derivation audit", "numerical example"),
345
+ },
346
+ forbidden_default_patterns=("backbone_model", "objective", "evaluation_harness"),
347
+ ),
348
+ "physical_materials_chemical": ScientificInterventionProfile(
349
+ profile_id="physical_materials_chemical",
350
+ label="Physical, Materials, and Chemical Research",
351
+ discipline_keys=("materials_science", "chemistry", "chemical_engineering"),
352
+ component_roles=(
353
+ _role("material_or_reactant_system", "Material or reactant system", "Material, composition, reactant, or sample system."),
354
+ _role("controllable_process_or_composition", "Controllable process or composition", "Composition, synthesis, processing, or operating variable."),
355
+ _role("structural_or_state", "Structural or state variable", "Microstructure, phase, state, or intermediate species."),
356
+ _role("candidate_mechanism", "Candidate mechanism", "Structure-process-property or reaction mechanism."),
357
+ _role("property_or_performance_endpoint", "Property or performance endpoint", "Measured property, yield, stability, or performance endpoint."),
358
+ _role("characterization_and_comparator", "Characterization and comparator", "Characterization method, reference sample, and comparison."),
359
+ ),
360
+ contribution_modes=(
361
+ _mode("structure_property_mechanism", "Structure-property mechanism", "Explain or discriminate a structure/process/property mechanism.", "A characterization list without a mechanism or validity question.", "Reference comparison, direct or proxy characterization, endpoint, and competing mechanism test."),
362
+ _mode("process_structure_control", "Process-structure control", "Identify a controllable processing window and its structural consequence.", "A parameter sweep without a causal or reproducible interpretation.", "Process baseline, structure characterization, endpoint, and environmental boundary."),
363
+ _mode("measurement_or_characterization_validity", "Measurement or characterization validity", "Resolve a measurement construct or calibration bottleneck.", "A generic reporting protocol unrelated to a scientific measurement claim.", "Calibration, comparator, repeatability, and construct validity."),
364
+ ),
365
+ defect_tags=("measurement_construct_mismatch", "unsupported_causal_link", "missing_boundary_condition", "insufficient_reproducibility", "unresolved_alternative_explanation"),
366
+ validation_requirements=(
367
+ _validation("material_or_process_comparator", "Material or process comparator", "Include a baseline material, composition, or process."),
368
+ _validation("structure_characterization", "Structure characterization", "Measure a direct or proxy structural state."),
369
+ _validation("property_endpoint", "Property endpoint", "Define a measurable property or performance endpoint."),
370
+ _validation("mechanism_competition", "Mechanism competition", "Design an observation that distinguishes candidate mechanisms."),
371
+ _validation("process_boundary", "Process boundary", "Report process, environmental, and failure boundaries."),
372
+ ),
373
+ generation_rules=_COMMON_GENERATION_RULES + ("Prefer material, process, structure, mechanism, property, and characterization language.",),
374
+ evaluation_anchors={
375
+ "explanatory_power": ("structure-process-property mechanism", "reaction path"),
376
+ "identifiability": ("characterization distinguishes mechanisms", "reference comparator"),
377
+ "protocol_score": ("process control", "characterization", "performance", "repeatability"),
378
+ },
379
+ forbidden_default_patterns=("backbone_model", "objective", "evaluation_harness"),
380
+ ),
381
+ "life_molecular_mechanistic": ScientificInterventionProfile(
382
+ profile_id="life_molecular_mechanistic",
383
+ label="Life and Molecular Mechanistic Research",
384
+ discipline_keys=("agricultural_biological_sciences", "biochemistry_genetics_molecular_biology", "immunology_microbiology", "neuroscience", "pharmacology_toxicology_pharmaceutics", "veterinary"),
385
+ component_roles=(
386
+ _role("biological_system", "Biological system", "Organism, cell, tissue, pathogen, or biological system."),
387
+ _role("intervention_or_perturbation", "Intervention or perturbation", "Treatment, exposure, perturbation, or environmental condition."),
388
+ _role("mediator_or_pathway", "Mediator or pathway", "Mediator, pathway, molecular process, or circuit."),
389
+ _role("phenotype_or_endpoint", "Phenotype or endpoint", "Phenotype, assay output, or biological endpoint."),
390
+ _role("assay_or_measurement", "Assay or measurement", "Assay, imaging, sampling, or measurement mapping."),
391
+ _role("comparator_or_control", "Comparator or control", "Control, counterfactual, or competing biological explanation."),
392
+ ),
393
+ contribution_modes=(
394
+ _mode("causal_mechanism", "Causal mechanism", "Identify a perturbation-mediator-phenotype mechanism.", "A descriptive association without intervention or mediator evidence.", "Perturbation, comparator, mediator measurement, endpoint, and boundary."),
395
+ _mode("pathway_or_biomarker", "Pathway or biomarker", "Discriminate a pathway or biomarker with a causal or mechanistic role.", "A predictive marker with no scientific interpretation.", "Assay validity, competing pathway, temporal ordering, and replication."),
396
+ _mode("dose_or_condition_boundary", "Dose or condition boundary", "Identify a dose, exposure, or condition range with a mechanistic transition.", "A threshold selected only for benchmark performance.", "Dose/exposure control, response measurement, safety or ecological boundary."),
397
+ ),
398
+ defect_tags=("unsupported_causal_link", "missing_comparator", "measurement_construct_mismatch", "missing_boundary_condition", "insufficient_reproducibility", "confounding_or_selection_bias"),
399
+ validation_requirements=(
400
+ _validation("perturbation_control", "Perturbation control", "Specify intervention, exposure, or perturbation."),
401
+ _validation("mediator_measurement", "Mediator measurement", "Measure a mediator or pathway rather than only an endpoint."),
402
+ _validation("biological_comparator", "Biological comparator", "Include a control, counterfactual, or competing explanation."),
403
+ _validation("replication_boundary", "Replication and boundary", "Report replication, condition, and failure boundaries."),
404
+ ),
405
+ generation_rules=_COMMON_GENERATION_RULES + ("Prefer biological system, perturbation, mediator, phenotype, assay, and comparator language.",),
406
+ evaluation_anchors={
407
+ "explanatory_power": ("intervention-mediator-phenotype path", "pathway mechanism"),
408
+ "identifiability": ("mediator measurement", "control", "temporal ordering"),
409
+ "protocol_score": ("perturbation", "assay", "replication", "dose boundary"),
410
+ },
411
+ forbidden_default_patterns=("backbone_model", "objective", "evaluation_harness"),
412
+ ),
413
+ "clinical_health": ScientificInterventionProfile(
414
+ profile_id="clinical_health",
415
+ label="Clinical and Health Research",
416
+ discipline_keys=("medicine", "nursing", "dentistry", "health_professions"),
417
+ component_roles=(
418
+ _role("target_population_or_cohort", "Target population or cohort", "Population, cohort, inclusion, and exclusion scope."),
419
+ _role("intervention_or_exposure", "Intervention or exposure", "Treatment, diagnostic action, care pathway, or exposure."),
420
+ _role("comparator_or_counterfactual", "Comparator or counterfactual", "Usual care, control, counterfactual, or comparator."),
421
+ _role("biological_or_clinical_mediator", "Biological or clinical mediator", "Mediator, mechanism, or clinical process."),
422
+ _role("clinical_endpoint", "Clinical endpoint", "Outcome, endpoint, time point, and clinical utility."),
423
+ _role("measurement_and_confounding_control", "Measurement and confounding control", "Measurement validity, bias, confounding, and selection control."),
424
+ _role("safety_or_external_validity_boundary", "Safety or external validity boundary", "Safety, subgroup, transportability, and external validity boundary."),
425
+ ),
426
+ contribution_modes=(
427
+ _mode("causal_mechanism_or_mediation", "Causal mechanism or mediation", "Identify a treatment/exposure-mediator-outcome relation.", "A predictor-only model without clinical causal or utility interpretation.", "Population, comparator, endpoint timing, confounding, and mediator evidence."),
428
+ _mode("treatment_or_diagnostic_strategy", "Treatment or diagnostic strategy", "Improve a clinically actionable intervention or diagnostic decision.", "A benchmark-only score increase without utility or safety evidence.", "Comparator, clinical endpoint, harms, subgroup, and external validity."),
429
+ _mode("outcome_or_measurement_validity", "Outcome or measurement validity", "Resolve an endpoint, measurement, or clinical construct validity bottleneck.", "A generic dashboard or reporting layer.", "Measurement timing, calibration, comparator, reproducibility, and clinical relevance."),
430
+ ),
431
+ defect_tags=("missing_comparator", "confounding_or_selection_bias", "invalid_generalization", "claim_overreach", "measurement_construct_mismatch", "missing_boundary_condition"),
432
+ validation_requirements=(
433
+ _validation("population_scope", "Population scope", "State population and inclusion/exclusion scope."),
434
+ _validation("clinical_comparator", "Clinical comparator", "Specify usual care, control, or counterfactual."),
435
+ _validation("endpoint_timing", "Endpoint timing", "Define endpoint and measurement time points."),
436
+ _validation("bias_and_safety", "Bias and safety", "Address confounding, selection, safety, and subgroup limits."),
437
+ _validation("external_validity", "External validity", "State transportability and generalization boundaries."),
438
+ ),
439
+ generation_rules=_COMMON_GENERATION_RULES + ("Prefer population, intervention, comparator, mediator, endpoint, confounding, safety, and external validity language.",),
440
+ evaluation_anchors={
441
+ "explanatory_power": ("intervention-mediator-outcome path", "clinical mechanism"),
442
+ "identifiability": ("comparator", "confounding control", "endpoint validity"),
443
+ "protocol_score": ("cohort", "endpoint", "safety", "external validity"),
444
+ },
445
+ forbidden_default_patterns=("backbone_model", "objective", "evaluation_harness"),
446
+ ),
447
+ "earth_environment_agro": ScientificInterventionProfile(
448
+ profile_id="earth_environment_agro",
449
+ label="Earth, Environmental, and Agro-ecological Research",
450
+ discipline_keys=("earth_planetary_science", "environmental_science", "agricultural_biological_sciences"),
451
+ component_roles=(
452
+ _role("system_or_ecosystem", "System or ecosystem", "Earth, environmental, agricultural, or ecological system."),
453
+ _role("forcing_or_exposure", "Forcing or exposure", "Forcing, exposure, intervention, or environmental driver."),
454
+ _role("spatiotemporal_scale", "Spatiotemporal scale", "Spatial, temporal, seasonal, or planetary scale."),
455
+ _role("process_or_pathway", "Process or pathway", "Physical, chemical, ecological, or biogeochemical process."),
456
+ _role("response_or_endpoint", "Response or endpoint", "Observed response, yield, risk, or environmental endpoint."),
457
+ _role("observation_platform", "Observation platform", "Sensor, field observation, sample, or model-observation link."),
458
+ ),
459
+ contribution_modes=(
460
+ _mode("forcing_process_response", "Forcing-process-response mechanism", "Explain a forcing or exposure through a process to a response.", "A map, dataset, or forecast benchmark without process interpretation.", "Scale, observation, process comparison, and scenario boundary."),
461
+ _mode("scale_or_regime_boundary", "Scale or regime boundary", "Identify where a process changes across spatial, temporal, or environmental regimes.", "A parameter threshold with no process or scale meaning.", "Multi-scale observation or model comparison and boundary conditions."),
462
+ _mode("observation_or_attribution_design", "Observation or attribution design", "Resolve an observation or causal attribution bottleneck.", "A generic data pipeline without a measurement or attribution claim.", "Observation validity, forcing control, confounders, and cross-validation."),
463
+ ),
464
+ defect_tags=("unsupported_causal_link", "missing_boundary_condition", "measurement_construct_mismatch", "invalid_generalization", "confounding_or_selection_bias", "insufficient_reproducibility"),
465
+ validation_requirements=(
466
+ _validation("forcing_definition", "Forcing definition", "Define driver, exposure, or intervention."),
467
+ _validation("scale_alignment", "Scale alignment", "Match process, observation, and claim scales."),
468
+ _validation("process_observation_link", "Process-observation link", "Connect process claim to observation or model evidence."),
469
+ _validation("scenario_boundary", "Scenario boundary", "Report spatial, temporal, and scenario limits."),
470
+ ),
471
+ generation_rules=_COMMON_GENERATION_RULES + ("Prefer system, forcing, scale, process, response, observation, and scenario language.",),
472
+ evaluation_anchors={
473
+ "explanatory_power": ("forcing-process-response chain", "scale mechanism"),
474
+ "identifiability": ("observation distinguishes process", "spatiotemporal alignment"),
475
+ "protocol_score": ("scenario", "scale sensitivity", "observation/model cross-check"),
476
+ },
477
+ forbidden_default_patterns=("backbone_model", "objective", "evaluation_harness"),
478
+ ),
479
+ "energy_engineering_systems": ScientificInterventionProfile(
480
+ profile_id="energy_engineering_systems",
481
+ label="Energy, Engineering, and Systems Research",
482
+ discipline_keys=("energy", "engineering", "electrical_engineering_systems"),
483
+ component_roles=(
484
+ _role("engineered_system", "Engineered system", "Device, process, infrastructure, or system boundary."),
485
+ _role("controllable_variable", "Controllable variable", "Design, operating, control, or material variable."),
486
+ _role("physical_mechanism", "Physical mechanism", "Mechanism linking controllable variable to system state."),
487
+ _role("constraint_or_safety_condition", "Constraint or safety condition", "Physical, resource, stability, or safety constraint."),
488
+ _role("operating_state", "Operating state", "Operating regime, load, environment, or transient state."),
489
+ _role("performance_or_safety_readout", "Performance or safety readout", "Performance, reliability, efficiency, or safety endpoint."),
490
+ ),
491
+ contribution_modes=(
492
+ _mode("mechanism_or_design_rule", "Mechanism or design rule", "Derive a mechanism-grounded design or operating rule.", "A control wrapper or benchmark without a physical/system mechanism.", "Baseline, mechanism measurement, operating states, and constraints."),
493
+ _mode("stability_or_safety_boundary", "Stability or safety boundary", "Identify a stability, reliability, or safety boundary.", "A metric improvement without a safety or operating interpretation.", "Stress, failure boundary, repeatability, and safety evidence."),
494
+ _mode("cross_scale_system_optimization", "Cross-scale system optimization", "Link component, process, and system-scale variables under constraints.", "A generic optimizer without system-level causal interpretation.", "Operating scenarios, constraint checks, and component/system comparison."),
495
+ ),
496
+ defect_tags=("unsupported_causal_link", "missing_boundary_condition", "insufficient_reproducibility", "claim_overreach", "weak_fallback_behavior", "invalid_generalization"),
497
+ validation_requirements=(
498
+ _validation("system_baseline", "System baseline", "Compare against a relevant design or operating baseline."),
499
+ _validation("mechanism_measurement", "Mechanism measurement", "Measure the physical or system mechanism."),
500
+ _validation("constraint_check", "Constraint and safety check", "Check operating, stability, resource, and safety constraints."),
501
+ _validation("regime_stress", "Regime stress", "Evaluate relevant load, environment, and failure regimes."),
502
+ ),
503
+ generation_rules=_COMMON_GENERATION_RULES + ("Prefer system, controllable variable, mechanism, constraint, operating state, and performance language.",),
504
+ evaluation_anchors={
505
+ "explanatory_power": ("physical mechanism", "system constraint closure"),
506
+ "identifiability": ("measurable state", "constraint or mechanism contrast"),
507
+ "protocol_score": ("baseline", "operating regime", "stability", "safety"),
508
+ },
509
+ forbidden_default_patterns=("backbone_model", "objective", "evaluation_harness"),
510
+ ),
511
+ "generic_scientific": ScientificInterventionProfile(
512
+ profile_id="generic_scientific",
513
+ label="Generic Scientific Intervention",
514
+ discipline_keys=(),
515
+ component_roles=(
516
+ _role("research_object", "Research object", "Object, system, population, or formal entity under study."),
517
+ _role("manipulable_condition", "Manipulable condition", "Condition, exposure, assumption, or intervention that can vary."),
518
+ _role("candidate_mechanism", "Candidate mechanism", "Mechanism or relation proposed to explain the observation."),
519
+ _role("observable_or_endpoint", "Observable or endpoint", "Observable, outcome, property, or formal consequence."),
520
+ _role("comparator_or_counterfactual", "Comparator or counterfactual", "Comparator, control, alternative, or counterfactual."),
521
+ _role("boundary_condition", "Boundary condition", "Conditions where the relation holds or fails."),
522
+ ),
523
+ contribution_modes=(
524
+ _mode("testable_mechanism", "Testable mechanism", "Propose a falsifiable relation between intervention and observable.", "A generic software or reporting wrapper.", "Comparator, discriminating observation, and boundary."),
525
+ _mode("measurement_or_relation", "Measurement or relation", "Clarify an observable mapping or scientific relation.", "A measurement change with no construct or relation claim.", "Measurement validity, alternative explanation, and reproducible check."),
526
+ ),
527
+ defect_tags=("unsupported_causal_link", "unresolved_alternative_explanation", "missing_comparator", "missing_boundary_condition", "claim_overreach"),
528
+ validation_requirements=(
529
+ _validation("object_scope", "Object scope", "State object, scope, and unit of analysis."),
530
+ _validation("discriminating_observation", "Discriminating observation", "Identify an observation that separates explanations."),
531
+ _validation("boundary_or_falsifier", "Boundary or falsifier", "State failure conditions or a falsifier."),
532
+ ),
533
+ generation_rules=_COMMON_GENERATION_RULES + ("Use domain-neutral scientific objects, interventions, mechanisms, observables, comparators, and boundaries.",),
534
+ evaluation_anchors={
535
+ "explanatory_power": ("mechanism explains observation", "relation is explicit"),
536
+ "identifiability": ("comparator", "discriminating observation", "falsifier"),
537
+ "protocol_score": ("reproducible check", "boundary", "alternative explanation"),
538
+ },
539
+ forbidden_default_patterns=("backbone_model", "objective", "evaluation_harness"),
540
+ ),
541
+ }
542
+
543
+ SCIENTIFIC_INTERVENTION_PROFILES = _PROFILE_REGISTRY
544
+
545
+
546
+ PAPERSEEK_FIELD_TO_PROFILE: Dict[str, str] = {
547
+ "11": "earth_environment_agro",
548
+ "13": "life_molecular_mechanistic",
549
+ "15": "physical_materials_chemical",
550
+ "16": "physical_materials_chemical",
551
+ "17": "computational_algorithmic",
552
+ "19": "earth_environment_agro",
553
+ "21": "energy_engineering_systems",
554
+ "22": "energy_engineering_systems",
555
+ "23": "earth_environment_agro",
556
+ "24": "life_molecular_mechanistic",
557
+ "25": "physical_materials_chemical",
558
+ "26": "formal_theoretical",
559
+ "27": "clinical_health",
560
+ "28": "life_molecular_mechanistic",
561
+ "29": "clinical_health",
562
+ "30": "life_molecular_mechanistic",
563
+ "31": "formal_theoretical",
564
+ "34": "life_molecular_mechanistic",
565
+ "35": "clinical_health",
566
+ "36": "clinical_health",
567
+ }
568
+
569
+ PAPERSEEK_FIELD_LABELS: Dict[str, str] = {
570
+ "11": "Agricultural and Biological Sciences",
571
+ "13": "Biochemistry, Genetics and Molecular Biology",
572
+ "15": "Chemical Engineering",
573
+ "16": "Chemistry",
574
+ "17": "Computer Science",
575
+ "19": "Earth and Planetary Sciences",
576
+ "21": "Energy",
577
+ "22": "Engineering",
578
+ "23": "Environmental Science",
579
+ "24": "Immunology and Microbiology",
580
+ "25": "Materials Science",
581
+ "26": "Mathematics",
582
+ "27": "Medicine",
583
+ "28": "Neuroscience",
584
+ "29": "Nursing",
585
+ "30": "Pharmacology, Toxicology and Pharmaceutics",
586
+ "31": "Physics and Astronomy",
587
+ "34": "Veterinary",
588
+ "35": "Dentistry",
589
+ "36": "Health Professions",
590
+ }
591
+
592
+ PAPERSEEK_FIELD_CROSSWALK: Dict[str, Dict[str, str]] = {
593
+ field_id: {
594
+ "openalex_field_id": field_id,
595
+ "label": PAPERSEEK_FIELD_LABELS[field_id],
596
+ "profile_id": profile_id,
597
+ "coverage": "exact",
598
+ }
599
+ for field_id, profile_id in PAPERSEEK_FIELD_TO_PROFILE.items()
600
+ }
601
+
602
+ PAPERSEEK_FIELD_DOMAINS: Dict[str, str] = {
603
+ "11": "Life Sciences",
604
+ "13": "Life Sciences",
605
+ "15": "Physical Sciences",
606
+ "16": "Physical Sciences",
607
+ "17": "Physical Sciences",
608
+ "19": "Physical Sciences",
609
+ "21": "Physical Sciences",
610
+ "22": "Physical Sciences",
611
+ "23": "Physical Sciences",
612
+ "24": "Life Sciences",
613
+ "25": "Physical Sciences",
614
+ "26": "Physical Sciences",
615
+ "27": "Health Sciences",
616
+ "28": "Life Sciences",
617
+ "29": "Health Sciences",
618
+ "30": "Life Sciences",
619
+ "31": "Physical Sciences",
620
+ "34": "Health Sciences",
621
+ "35": "Health Sciences",
622
+ "36": "Health Sciences",
623
+ }
624
+
625
+ PAPERSEEK_FIELD_TO_DISCIPLINE: Dict[str, str] = {
626
+ "11": "agricultural_biological_sciences",
627
+ "13": "biochemistry_genetics_molecular_biology",
628
+ "15": "chemical_engineering",
629
+ "16": "chemistry",
630
+ "17": "computer_science",
631
+ "19": "earth_planetary_science",
632
+ "21": "energy",
633
+ "22": "engineering",
634
+ "23": "environmental_science",
635
+ "24": "immunology_microbiology",
636
+ "25": "materials_science",
637
+ "26": "mathematics",
638
+ "27": "medicine",
639
+ "28": "neuroscience",
640
+ "29": "nursing",
641
+ "30": "pharmacology_toxicology_pharmaceutics",
642
+ "31": "physics_astronomy",
643
+ "34": "veterinary",
644
+ "35": "dentistry",
645
+ "36": "health_professions",
646
+ }
647
+
648
+ PAPERSEEK_FIELD_SECONDARY_PROFILES: Dict[str, tuple[str, ...]] = {
649
+ "11": ("life_molecular_mechanistic",),
650
+ "28": ("clinical_health",),
651
+ "31": ("physical_materials_chemical",),
652
+ "34": ("clinical_health",),
653
+ }
654
+
655
+
656
+ def _object_schema(
657
+ profile_id: str,
658
+ object_types: Sequence[str],
659
+ allowed_operations: Sequence[str],
660
+ *,
661
+ target_object_roles: Sequence[str] = (),
662
+ claim_delta_roles: Sequence[str] = (),
663
+ mechanism_delta_roles: Sequence[str] = (),
664
+ evidence_obligation_roles: Sequence[str] = (),
665
+ boundary_condition_roles: Sequence[str] = (),
666
+ measurement_or_observation_roles: Sequence[str] = (),
667
+ ) -> ScientificObjectSchema:
668
+ return ScientificObjectSchema(
669
+ profile_id=profile_id,
670
+ object_types=tuple(object_types),
671
+ allowed_operations=tuple(allowed_operations),
672
+ target_object_roles=tuple(target_object_roles),
673
+ claim_delta_roles=tuple(claim_delta_roles),
674
+ mechanism_delta_roles=tuple(mechanism_delta_roles),
675
+ evidence_obligation_roles=tuple(evidence_obligation_roles),
676
+ boundary_condition_roles=tuple(boundary_condition_roles),
677
+ measurement_or_observation_roles=tuple(measurement_or_observation_roles),
678
+ )
679
+
680
+
681
+ PROFILE_NATIVE_OBJECT_SCHEMAS: Dict[str, ScientificObjectSchema] = {
682
+ "computational_algorithmic": _object_schema(
683
+ "computational_algorithmic",
684
+ ("backbone_model", "objective", "evaluation_harness"),
685
+ ("replace", "rewire", "optimize", "train", "infer", "ablate", "stress"),
686
+ target_object_roles=("backbone_model", "objective"),
687
+ claim_delta_roles=("objective", "evaluation_harness"),
688
+ mechanism_delta_roles=("backbone_model", "objective"),
689
+ evidence_obligation_roles=("evaluation_harness",),
690
+ boundary_condition_roles=("evaluation_harness",),
691
+ measurement_or_observation_roles=("evaluation_harness",),
692
+ ),
693
+ "formal_theoretical": _object_schema(
694
+ "formal_theoretical",
695
+ ("formal_object", "assumptions_or_axioms", "target_relation", "derivation_or_construction", "validity_domain", "counterexample_boundary", "proof_or_verification"),
696
+ ("assume", "relax", "derive", "construct", "unify", "falsify", "bound", "verify"),
697
+ target_object_roles=("formal_object", "target_relation"),
698
+ claim_delta_roles=("target_relation", "validity_domain"),
699
+ mechanism_delta_roles=("assumptions_or_axioms", "derivation_or_construction"),
700
+ evidence_obligation_roles=("proof_or_verification",),
701
+ boundary_condition_roles=("validity_domain", "counterexample_boundary"),
702
+ measurement_or_observation_roles=("proof_or_verification",),
703
+ ),
704
+ "physical_materials_chemical": _object_schema(
705
+ "physical_materials_chemical",
706
+ ("material_or_reactant_system", "controllable_process_or_composition", "structural_or_state", "candidate_mechanism", "property_or_performance_endpoint", "characterization_and_comparator"),
707
+ ("alter", "synthesize", "anneal", "perturb", "characterize", "compare", "calibrate", "bound"),
708
+ target_object_roles=("material_or_reactant_system", "structural_or_state"),
709
+ claim_delta_roles=("property_or_performance_endpoint", "controllable_process_or_composition"),
710
+ mechanism_delta_roles=("candidate_mechanism", "structural_or_state"),
711
+ evidence_obligation_roles=("characterization_and_comparator", "property_or_performance_endpoint"),
712
+ boundary_condition_roles=("controllable_process_or_composition", "characterization_and_comparator"),
713
+ measurement_or_observation_roles=("characterization_and_comparator", "property_or_performance_endpoint"),
714
+ ),
715
+ "life_molecular_mechanistic": _object_schema(
716
+ "life_molecular_mechanistic",
717
+ ("biological_system", "intervention_or_perturbation", "mediator_or_pathway", "phenotype_or_endpoint", "assay_or_measurement", "comparator_or_control"),
718
+ ("perturb", "intervene", "stratify", "mediate", "measure", "compare", "replicate", "bound"),
719
+ target_object_roles=("biological_system", "mediator_or_pathway"),
720
+ claim_delta_roles=("phenotype_or_endpoint", "intervention_or_perturbation"),
721
+ mechanism_delta_roles=("mediator_or_pathway", "intervention_or_perturbation"),
722
+ evidence_obligation_roles=("assay_or_measurement", "comparator_or_control"),
723
+ boundary_condition_roles=("comparator_or_control", "intervention_or_perturbation"),
724
+ measurement_or_observation_roles=("assay_or_measurement", "phenotype_or_endpoint"),
725
+ ),
726
+ "clinical_health": _object_schema(
727
+ "clinical_health",
728
+ ("target_population_or_cohort", "intervention_or_exposure", "comparator_or_counterfactual", "biological_or_clinical_mediator", "clinical_endpoint", "measurement_and_confounding_control", "safety_or_external_validity_boundary"),
729
+ ("intervene", "stratify", "mediate", "measure", "compare", "calibrate", "bound", "replicate"),
730
+ target_object_roles=("target_population_or_cohort", "clinical_endpoint"),
731
+ claim_delta_roles=("intervention_or_exposure", "clinical_endpoint"),
732
+ mechanism_delta_roles=("biological_or_clinical_mediator", "measurement_and_confounding_control"),
733
+ evidence_obligation_roles=("comparator_or_counterfactual", "measurement_and_confounding_control"),
734
+ boundary_condition_roles=("safety_or_external_validity_boundary", "target_population_or_cohort"),
735
+ measurement_or_observation_roles=("clinical_endpoint", "measurement_and_confounding_control"),
736
+ ),
737
+ "earth_environment_agro": _object_schema(
738
+ "earth_environment_agro",
739
+ ("system_or_ecosystem", "forcing_or_exposure", "spatiotemporal_scale", "process_or_pathway", "response_or_endpoint", "observation_platform"),
740
+ ("perturb", "rescale", "observe", "attribute", "compare", "calibrate", "bound", "scenario_test"),
741
+ target_object_roles=("system_or_ecosystem", "spatiotemporal_scale"),
742
+ claim_delta_roles=("response_or_endpoint", "forcing_or_exposure"),
743
+ mechanism_delta_roles=("process_or_pathway", "forcing_or_exposure"),
744
+ evidence_obligation_roles=("observation_platform", "process_or_pathway"),
745
+ boundary_condition_roles=("spatiotemporal_scale", "forcing_or_exposure"),
746
+ measurement_or_observation_roles=("observation_platform", "response_or_endpoint"),
747
+ ),
748
+ "energy_engineering_systems": _object_schema(
749
+ "energy_engineering_systems",
750
+ ("engineered_system", "controllable_variable", "physical_mechanism", "constraint_or_safety_condition", "operating_state", "performance_or_safety_readout"),
751
+ ("redesign", "operate", "stress", "constrain", "measure", "compare", "calibrate", "bound"),
752
+ target_object_roles=("engineered_system", "operating_state"),
753
+ claim_delta_roles=("performance_or_safety_readout", "controllable_variable"),
754
+ mechanism_delta_roles=("physical_mechanism", "controllable_variable"),
755
+ evidence_obligation_roles=("performance_or_safety_readout", "constraint_or_safety_condition"),
756
+ boundary_condition_roles=("constraint_or_safety_condition", "operating_state"),
757
+ measurement_or_observation_roles=("performance_or_safety_readout", "operating_state"),
758
+ ),
759
+ "generic_scientific": _object_schema(
760
+ "generic_scientific",
761
+ ("research_object", "manipulable_condition", "candidate_mechanism", "observable_or_endpoint", "comparator_or_counterfactual", "boundary_condition"),
762
+ ("define", "perturb", "propose", "measure", "compare", "calibrate", "falsify", "bound"),
763
+ target_object_roles=("research_object",),
764
+ claim_delta_roles=("observable_or_endpoint", "manipulable_condition"),
765
+ mechanism_delta_roles=("candidate_mechanism", "manipulable_condition"),
766
+ evidence_obligation_roles=("comparator_or_counterfactual", "observable_or_endpoint"),
767
+ boundary_condition_roles=("boundary_condition",),
768
+ measurement_or_observation_roles=("observable_or_endpoint",),
769
+ ),
770
+ }
771
+
772
+
773
+ def get_scientific_object_schema(profile_id: Any) -> Optional[ScientificObjectSchema]:
774
+ key = str(profile_id or "").strip().lower()
775
+ if not key:
776
+ return None
777
+ return PROFILE_NATIVE_OBJECT_SCHEMAS.get(key)
778
+
779
+
780
+ def list_scientific_object_schemas() -> tuple[ScientificObjectSchema, ...]:
781
+ return tuple(PROFILE_NATIVE_OBJECT_SCHEMAS.values())
782
+
783
+
784
+ def _build_scientific_object_registry() -> Dict[str, ScientificObjectSpec]:
785
+ registry: Dict[str, ScientificObjectSpec] = {}
786
+ for profile_id, profile in _PROFILE_REGISTRY.items():
787
+ for role in profile.component_roles:
788
+ key = f"{profile_id}:{role.role_id}"
789
+ registry[key] = ScientificObjectSpec(
790
+ object_id=role.role_id,
791
+ profile_id=profile_id,
792
+ label=role.label,
793
+ description=role.description,
794
+ examples=role.examples,
795
+ object_type=role.role_id,
796
+ allowed_operations=(get_scientific_object_schema(profile_id).allowed_operations
797
+ if get_scientific_object_schema(profile_id) else ()),
798
+ evidence_obligations=(
799
+ (get_scientific_object_schema(profile_id).evidence_obligation_roles
800
+ if get_scientific_object_schema(profile_id) else ())
801
+ ),
802
+ boundary_roles=(
803
+ (get_scientific_object_schema(profile_id).boundary_condition_roles
804
+ if get_scientific_object_schema(profile_id) else ())
805
+ ),
806
+ measurement_roles=(
807
+ (get_scientific_object_schema(profile_id).measurement_or_observation_roles
808
+ if get_scientific_object_schema(profile_id) else ())
809
+ ),
810
+ )
811
+ return registry
812
+
813
+
814
+ SCIENTIFIC_OBJECT_REGISTRY: Dict[str, ScientificObjectSpec] = (
815
+ _build_scientific_object_registry()
816
+ )
817
+
818
+ PAPERSEEK_FIELD_PROFILE_REGISTRY: Dict[str, ScientificFieldSpec] = {
819
+ field_id: ScientificFieldSpec(
820
+ field_id=field_id,
821
+ label=PAPERSEEK_FIELD_LABELS[field_id],
822
+ paperseek_domain=PAPERSEEK_FIELD_DOMAINS[field_id],
823
+ discipline_key=PAPERSEEK_FIELD_TO_DISCIPLINE[field_id],
824
+ primary_profile=profile_id,
825
+ secondary_profiles=PAPERSEEK_FIELD_SECONDARY_PROFILES.get(field_id, ()),
826
+ object_role_ids=tuple(
827
+ role.role_id for role in _PROFILE_REGISTRY[profile_id].default_component_roles()
828
+ ),
829
+ )
830
+ for field_id, profile_id in PAPERSEEK_FIELD_TO_PROFILE.items()
831
+ }
832
+ PAPERSEEK_FIELD_REGISTRY = PAPERSEEK_FIELD_PROFILE_REGISTRY
833
+
834
+
835
+ def get_scientific_field_spec(field_id: Any) -> Optional[ScientificFieldSpec]:
836
+ return PAPERSEEK_FIELD_PROFILE_REGISTRY.get(str(field_id or "").strip())
837
+
838
+
839
+ def list_scientific_field_specs() -> tuple[ScientificFieldSpec, ...]:
840
+ return tuple(PAPERSEEK_FIELD_PROFILE_REGISTRY.values())
841
+
842
+
843
+ def get_scientific_object_spec(
844
+ profile_id: Any,
845
+ object_id: Any,
846
+ ) -> Optional[ScientificObjectSpec]:
847
+ profile_key = str(profile_id or "").strip().lower()
848
+ object_key = str(object_id or "").strip().lower()
849
+ if not profile_key or not object_key:
850
+ return None
851
+ return SCIENTIFIC_OBJECT_REGISTRY.get(f"{profile_key}:{object_key}")
852
+
853
+
854
+ DISCIPLINE_TO_PROFILE: Dict[str, str] = {
855
+ discipline: profile_id
856
+ for profile_id, profile in _PROFILE_REGISTRY.items()
857
+ for discipline in profile.discipline_keys
858
+ }
859
+ DISCIPLINE_TO_PROFILE.update(
860
+ {
861
+ "quantitative_biology": "life_molecular_mechanistic",
862
+ "electrical_engineering": "energy_engineering_systems",
863
+ "statistics": "formal_theoretical",
864
+ }
865
+ )
866
+
867
+
868
+ def get_scientific_intervention_profile(profile_id: Any) -> Optional[ScientificInterventionProfile]:
869
+ key = str(profile_id or "").strip().lower()
870
+ if not key:
871
+ return None
872
+ return _PROFILE_REGISTRY.get(key)
873
+
874
+
875
+ def list_scientific_intervention_profiles() -> tuple[ScientificInterventionProfile, ...]:
876
+ return tuple(_PROFILE_REGISTRY.values())
877
+
878
+
879
+ def _as_string_list(value: Any) -> list[str]:
880
+ if value is None:
881
+ return []
882
+ values = value if isinstance(value, (list, tuple, set, frozenset)) else [value]
883
+ return [str(item).strip() for item in values if str(item).strip()]
884
+
885
+
886
+ def _extract_field_ids(resolution: Mapping[str, Any]) -> list[str]:
887
+ field_values: list[str] = []
888
+ for key in (
889
+ "paperseek_openalex_field_ids",
890
+ "openalex_field_ids",
891
+ "resolved_openalex_field_ids",
892
+ "selected_openalex_field_ids",
893
+ "openalex_field_id",
894
+ ):
895
+ field_values.extend(_as_string_list(resolution.get(key)))
896
+ provider_filters = resolution.get("provider_filters")
897
+ if isinstance(provider_filters, Mapping):
898
+ openalex = provider_filters.get("openalex")
899
+ if isinstance(openalex, Mapping):
900
+ field_values.extend(_as_string_list(openalex.get("resolved_field_ids")))
901
+ return list(dict.fromkeys(field_values))
902
+
903
+
904
+ def _iter_project_context_mappings(
905
+ project_context: Mapping[str, Any] | None,
906
+ ) -> tuple[Mapping[str, Any], ...]:
907
+ if not isinstance(project_context, Mapping):
908
+ return ()
909
+ mappings: list[Mapping[str, Any]] = []
910
+ queue: list[Mapping[str, Any]] = [project_context]
911
+ seen: set[int] = set()
912
+ while queue:
913
+ current = queue.pop(0)
914
+ marker = id(current)
915
+ if marker in seen:
916
+ continue
917
+ seen.add(marker)
918
+ mappings.append(current)
919
+ for key in (
920
+ "research_context",
921
+ "research_identity",
922
+ "llm_payload",
923
+ "domain_context",
924
+ "taxonomy_resolution",
925
+ "discovery_taxonomy",
926
+ "catalog_resolution",
927
+ ):
928
+ nested = current.get(key)
929
+ if isinstance(nested, Mapping):
930
+ queue.append(nested)
931
+ return tuple(mappings)
932
+
933
+
934
+ def _extract_project_context_domain_values(
935
+ project_context: Mapping[str, Any] | None,
936
+ ) -> list[str]:
937
+ values: list[str] = []
938
+ keys = (
939
+ "primary_discipline",
940
+ "primary_domain",
941
+ "discipline_ids",
942
+ "primary",
943
+ "declared_domain",
944
+ "domain",
945
+ "research_domains",
946
+ "secondary_discipline",
947
+ "secondary_disciplines",
948
+ "secondary_labels",
949
+ "taxonomy_labels",
950
+ "primary_label",
951
+ )
952
+ mappings = _iter_project_context_mappings(project_context)
953
+ for key in keys:
954
+ for mapping in mappings:
955
+ value = mapping.get(key)
956
+ if isinstance(value, Mapping):
957
+ value = value.get("label") or value.get("name") or value.get("id")
958
+ if isinstance(value, (list, tuple, set, frozenset)):
959
+ for item in value:
960
+ if isinstance(item, Mapping):
961
+ item = item.get("label") or item.get("name") or item.get("id")
962
+ text = str(item or "").strip()
963
+ if text:
964
+ values.append(text)
965
+ else:
966
+ text = str(value or "").strip()
967
+ if text:
968
+ values.append(text)
969
+ return list(dict.fromkeys(values))
970
+
971
+
972
+ def _extract_project_context_field_ids(
973
+ project_context: Mapping[str, Any] | None,
974
+ ) -> list[str]:
975
+ field_ids: list[str] = []
976
+ for mapping in _iter_project_context_mappings(project_context):
977
+ field_ids.extend(_extract_field_ids(mapping))
978
+ return list(dict.fromkeys(field_ids))
979
+
980
+
981
+ def normalize_project_context_discipline_resolution(
982
+ project_context: Mapping[str, Any] | None,
983
+ ) -> Dict[str, Any]:
984
+ """Extract and canonicalize discipline evidence published by Survey Agent.
985
+
986
+ Explicit OpenAlex field IDs in the artifact are preserved as the strongest
987
+ evidence. Domain labels and the LLM primary discipline are normalized
988
+ through the shared canonical discipline taxonomy only when no field ID is
989
+ available.
990
+ """
991
+
992
+ if not isinstance(project_context, Mapping):
993
+ return {}
994
+
995
+ field_ids = _extract_project_context_field_ids(project_context)
996
+ retained_fields = [
997
+ field_id for field_id in field_ids if field_id in RETAINED_PAPERSEEK_OPENALEX_FIELDS
998
+ ]
999
+ ignored_fields = [
1000
+ field_id for field_id in field_ids if field_id in IGNORED_PAPERSEEK_OPENALEX_FIELDS
1001
+ ]
1002
+ if retained_fields:
1003
+ discipline_ids = [
1004
+ PAPERSEEK_FIELD_TO_DISCIPLINE[field_id]
1005
+ for field_id in retained_fields
1006
+ if field_id in PAPERSEEK_FIELD_TO_DISCIPLINE
1007
+ ]
1008
+ field_spec = get_scientific_field_spec(retained_fields[0])
1009
+ return {
1010
+ "schema_version": "discipline_taxonomy_v1",
1011
+ "status": "resolved",
1012
+ "source": "survey_project_context_openalex_field",
1013
+ "primary_discipline": discipline_ids[0] if discipline_ids else None,
1014
+ "discipline_ids": discipline_ids,
1015
+ "paperseek_openalex_field_ids": retained_fields,
1016
+ "ignored_openalex_field_ids": ignored_fields,
1017
+ "coverage": "exact",
1018
+ "field_profile": field_spec.to_payload() if field_spec is not None else {},
1019
+ }
1020
+ if ignored_fields:
1021
+ return {
1022
+ "schema_version": "discipline_taxonomy_v1",
1023
+ "status": "out_of_scope",
1024
+ "source": "survey_project_context_openalex_field",
1025
+ "primary_discipline": None,
1026
+ "discipline_ids": [],
1027
+ "paperseek_openalex_field_ids": ignored_fields,
1028
+ "coverage": "unsupported",
1029
+ }
1030
+
1031
+ domain_values = _extract_project_context_domain_values(project_context)
1032
+ for value in domain_values:
1033
+ canonical_key = canonicalize_discipline_key(value)
1034
+ if not canonical_key:
1035
+ continue
1036
+ resolution = dict(resolve_discipline_taxonomy(canonical_key))
1037
+ resolution["source"] = "survey_project_context_domain"
1038
+ resolution["source_domain_value"] = value
1039
+ return resolution
1040
+
1041
+ domain_text = " ".join(domain_values).strip()
1042
+ if domain_text:
1043
+ resolution = dict(resolve_discipline_taxonomy(domain_text, query=domain_text))
1044
+ if resolution.get("status") in {"resolved", "ambiguous"}:
1045
+ resolution["source"] = "survey_project_context_domain_heuristic"
1046
+ resolution["source_domain_value"] = domain_values[0]
1047
+ return resolution
1048
+ return {
1049
+ "schema_version": "discipline_taxonomy_v1",
1050
+ "status": "unresolved",
1051
+ "source": "survey_project_context_domain",
1052
+ "primary_discipline": None,
1053
+ "discipline_ids": [],
1054
+ "coverage": "unsupported",
1055
+ }
1056
+
1057
+
1058
+ def _extract_discipline_keys(resolution: Mapping[str, Any]) -> list[str]:
1059
+ values: list[str] = []
1060
+ for key in ("primary_discipline", "primary_domain", "discipline_ids", "root_domains"):
1061
+ values.extend(_as_string_list(resolution.get(key)))
1062
+ return list(dict.fromkeys(canonicalize_discipline_key(value) for value in values if canonicalize_discipline_key(value)))
1063
+
1064
+
1065
+ def resolve_scientific_intervention_profile(
1066
+ discipline_resolution: Mapping[str, Any] | None,
1067
+ *,
1068
+ root_domains: Sequence[Any] = (),
1069
+ project_context: Mapping[str, Any] | None = None,
1070
+ ) -> Optional[ScientificInterventionProfile]:
1071
+ """Resolve a deterministic profile from a taxonomy resolution.
1072
+
1073
+ ``out_of_scope`` and explicitly ignored PaperSeek fields return ``None`` so
1074
+ callers can preserve the existing human-confirmation/rejection behavior.
1075
+ An unresolved topic without an ignored field maps to ``generic_scientific``;
1076
+ it never falls back to Computer Science.
1077
+ """
1078
+
1079
+ if not isinstance(discipline_resolution, Mapping):
1080
+ resolution: Dict[str, Any] = {}
1081
+ else:
1082
+ resolution = dict(discipline_resolution)
1083
+ nested = resolution.get("discipline_resolution")
1084
+ if isinstance(nested, Mapping):
1085
+ resolution = {**dict(nested), **resolution}
1086
+
1087
+ project_resolution = normalize_project_context_discipline_resolution(project_context)
1088
+ field_ids = _extract_project_context_field_ids(project_context)
1089
+ field_ids.extend(_extract_field_ids(resolution))
1090
+ field_ids = list(dict.fromkeys(field_ids))
1091
+ status = str(resolution.get("status") or "").strip().lower()
1092
+ if status == "out_of_scope" and not field_ids:
1093
+ return None
1094
+ ignored_fields = set(field_ids).intersection(IGNORED_PAPERSEEK_OPENALEX_FIELDS)
1095
+ retained_fields = set(field_ids).intersection(RETAINED_PAPERSEEK_OPENALEX_FIELDS)
1096
+ if ignored_fields and not retained_fields:
1097
+ return None
1098
+ for field_id in field_ids:
1099
+ profile_id = PAPERSEEK_FIELD_TO_PROFILE.get(field_id)
1100
+ if profile_id:
1101
+ return _PROFILE_REGISTRY[profile_id]
1102
+
1103
+ explicit_profile = resolution.get("scientific_intervention_profile") or resolution.get("profile_id")
1104
+ profile = get_scientific_intervention_profile(explicit_profile)
1105
+ if profile is not None:
1106
+ return profile
1107
+
1108
+ project_discipline_keys = _extract_discipline_keys(project_resolution)
1109
+ for discipline_key in project_discipline_keys:
1110
+ profile_id = DISCIPLINE_TO_PROFILE.get(discipline_key)
1111
+ if profile_id:
1112
+ return _PROFILE_REGISTRY[profile_id]
1113
+
1114
+ discipline_keys = _extract_discipline_keys(resolution)
1115
+ discipline_keys.extend(
1116
+ canonicalize_discipline_key(value)
1117
+ for value in root_domains
1118
+ if canonicalize_discipline_key(value)
1119
+ )
1120
+ for discipline_key in discipline_keys:
1121
+ profile_id = DISCIPLINE_TO_PROFILE.get(discipline_key)
1122
+ if profile_id:
1123
+ return _PROFILE_REGISTRY[profile_id]
1124
+
1125
+ if status in {"out_of_scope", "rejected"}:
1126
+ return None
1127
+ return _PROFILE_REGISTRY["generic_scientific"]
1128
+
1129
+
1130
+ def _component_role_for_name(
1131
+ component: str,
1132
+ profile: ScientificInterventionProfile,
1133
+ ) -> Optional[ComponentRoleSpec]:
1134
+ normalized = str(component or "").strip().lower().replace(" ", "_")
1135
+ if not normalized:
1136
+ return None
1137
+ for role in profile.component_roles:
1138
+ candidates = {role.role_id.lower(), role.label.lower().replace(" ", "_")}
1139
+ candidates.update(example.lower().replace(" ", "_") for example in role.examples)
1140
+ if normalized in candidates or any(candidate in normalized for candidate in candidates):
1141
+ return role
1142
+ return None
1143
+
1144
+
1145
+ def build_scientific_intervention_payload(
1146
+ profile: ScientificInterventionProfile,
1147
+ components: Sequence[Any],
1148
+ component_explanations: Mapping[str, Any] | None = None,
1149
+ existing: Mapping[str, Any] | None = None,
1150
+ ) -> Dict[str, Any]:
1151
+ """Build the backward-compatible structured root intervention payload."""
1152
+
1153
+ existing_payload = dict(existing) if isinstance(existing, Mapping) else {}
1154
+ normalized_components = [str(component).strip() for component in components if str(component).strip()]
1155
+ explanation_map = (
1156
+ {str(key).strip(): str(value).strip() for key, value in component_explanations.items()}
1157
+ if isinstance(component_explanations, Mapping)
1158
+ else {}
1159
+ )
1160
+ existing_roles = existing_payload.get("component_roles")
1161
+ existing_role_by_component: Dict[str, Mapping[str, Any]] = {}
1162
+ if isinstance(existing_roles, list):
1163
+ for role_entry in existing_roles:
1164
+ if not isinstance(role_entry, Mapping):
1165
+ continue
1166
+ component = str(role_entry.get("component") or "").strip()
1167
+ if component:
1168
+ existing_role_by_component[component] = role_entry
1169
+
1170
+ role_entries: list[Dict[str, Any]] = []
1171
+ schema_spec = get_scientific_object_schema(profile.profile_id)
1172
+ schema_payload = schema_spec.to_payload() if schema_spec is not None else {}
1173
+ for component in normalized_components:
1174
+ prior = existing_role_by_component.get(component, {})
1175
+ role = _component_role_for_name(component, profile)
1176
+ role_entries.append(
1177
+ {
1178
+ "role_id": str(prior.get("role_id") or (role.role_id if role else "unmapped_component")),
1179
+ "component": component,
1180
+ "role_explanation": str(
1181
+ prior.get("role_explanation")
1182
+ or explanation_map.get(component)
1183
+ or (role.description if role else "Existing idea component retained from the input." )
1184
+ ).strip(),
1185
+ "evidence_status": str(prior.get("evidence_status") or "existing").strip(),
1186
+ "object_type": str(
1187
+ prior.get("object_type")
1188
+ or (role.role_id if role else "scientific_object")
1189
+ ).strip(),
1190
+ "allowed_operations": list(schema_payload.get("allowed_operations") or []),
1191
+ }
1192
+ )
1193
+
1194
+ contribution_modes = profile.contribution_modes
1195
+ default_mode = contribution_modes[0].mode_id if contribution_modes else "testable_mechanism"
1196
+ requested_mode = str(existing_payload.get("contribution_mode") or "").strip()
1197
+ valid_mode_ids = {mode.mode_id for mode in contribution_modes}
1198
+ selected_mode = requested_mode if requested_mode in valid_mode_ids else default_mode
1199
+ payload = profile.to_payload()
1200
+ payload.update(existing_payload)
1201
+ payload.update(
1202
+ {
1203
+ "schema_version": "scientific_intervention_v1",
1204
+ "profile_id": profile.profile_id,
1205
+ "profile_label": profile.label,
1206
+ "algorithmic_semantics_allowed": profile.profile_id == "computational_algorithmic",
1207
+ "contribution_mode": selected_mode,
1208
+ "component_roles": role_entries,
1209
+ "validation_requirements": [
1210
+ requirement.to_payload() for requirement in profile.validation_requirements
1211
+ ],
1212
+ "forbidden_default_patterns": list(profile.forbidden_default_patterns),
1213
+ "scientific_object_schema": schema_payload or payload.get("scientific_object_schema", {}),
1214
+ "object_roles": list(schema_payload.get("object_types") or []),
1215
+ "allowed_operations": list(schema_payload.get("allowed_operations") or []),
1216
+ "evidence_obligations": list(schema_payload.get("evidence_obligation_roles") or []),
1217
+ "boundary_obligations": list(schema_payload.get("boundary_condition_roles") or []),
1218
+ "measurement_or_observation_roles": list(
1219
+ schema_payload.get("measurement_or_observation_roles") or []
1220
+ ),
1221
+ }
1222
+ )
1223
+ return payload
1224
+
1225
+
1226
+ def format_scientific_intervention_profile_for_prompt(
1227
+ intervention: Mapping[str, Any] | ScientificInterventionProfile | None,
1228
+ ) -> str:
1229
+ """Render a bounded profile block for generation and evaluation prompts."""
1230
+
1231
+ if isinstance(intervention, ScientificInterventionProfile):
1232
+ payload = intervention.to_payload()
1233
+ profile = intervention
1234
+ elif isinstance(intervention, Mapping):
1235
+ payload = dict(intervention)
1236
+ profile = get_scientific_intervention_profile(payload.get("profile_id"))
1237
+ else:
1238
+ payload = {}
1239
+ profile = get_scientific_intervention_profile("generic_scientific")
1240
+
1241
+ if profile is None:
1242
+ profile = get_scientific_intervention_profile("generic_scientific")
1243
+ if profile is None: # pragma: no cover - registry invariant
1244
+ return "Profile unavailable; use domain-neutral scientific objects, mechanisms, observables, and boundaries."
1245
+
1246
+ role_entries = payload.get("component_roles")
1247
+ role_lines: list[str] = []
1248
+ if isinstance(role_entries, list) and role_entries:
1249
+ for entry in role_entries[:10]:
1250
+ if not isinstance(entry, Mapping):
1251
+ continue
1252
+ role_id = str(entry.get("role_id") or "unmapped_component").strip()
1253
+ component = str(entry.get("component") or role_id).strip()
1254
+ explanation = str(entry.get("role_explanation") or "").strip()
1255
+ role_lines.append(f"- {role_id}: {component}{' — ' + explanation if explanation else ''}")
1256
+ if not role_lines:
1257
+ role_lines = [
1258
+ f"- {role.role_id}: {role.description}"
1259
+ for role in profile.default_component_roles()
1260
+ ]
1261
+
1262
+ modes = payload.get("contribution_modes")
1263
+ mode_lines: list[str] = []
1264
+ if isinstance(modes, list) and modes:
1265
+ for mode in modes[:8]:
1266
+ if not isinstance(mode, Mapping):
1267
+ continue
1268
+ mode_lines.append(
1269
+ f"- {mode.get('mode_id')}: {mode.get('primary_contribution')}"
1270
+ )
1271
+ if not mode_lines:
1272
+ mode_lines = [
1273
+ f"- {mode.mode_id}: {mode.primary_contribution}"
1274
+ for mode in profile.contribution_modes
1275
+ ]
1276
+
1277
+ rules = payload.get("generation_rules") or list(profile.generation_rules)
1278
+ rule_lines = [f"- {str(rule).strip()}" for rule in rules if str(rule).strip()][:8]
1279
+ forbidden = payload.get("forbidden_default_patterns")
1280
+ if not isinstance(forbidden, list):
1281
+ forbidden = list(profile.forbidden_default_patterns)
1282
+ if profile.profile_id == "computational_algorithmic":
1283
+ forbidden_lines = [f"- {str(item).strip()}" for item in forbidden if str(item).strip()]
1284
+ else:
1285
+ forbidden_lines = [
1286
+ "- Do not introduce unrelated software-specific objects or optimization contracts.",
1287
+ "- Do not treat an observation, measurement, or validation artifact as the primary intervention unless the profile explicitly allows it.",
1288
+ ]
1289
+ if not forbidden_lines:
1290
+ forbidden_lines = ["- None beyond the profile rules."]
1291
+
1292
+ object_schema = payload.get("scientific_object_schema")
1293
+ if not isinstance(object_schema, Mapping):
1294
+ schema = get_scientific_object_schema(profile.profile_id)
1295
+ object_schema = schema.to_payload() if schema is not None else {}
1296
+ object_types = object_schema.get("object_types") or []
1297
+ allowed_operations = object_schema.get("allowed_operations") or []
1298
+ target_object_roles = object_schema.get("target_object_roles") or []
1299
+ claim_delta_roles = object_schema.get("claim_delta_roles") or []
1300
+ mechanism_delta_roles = object_schema.get("mechanism_delta_roles") or []
1301
+ evidence_roles = object_schema.get("evidence_obligation_roles") or []
1302
+ boundary_roles = object_schema.get("boundary_condition_roles") or []
1303
+ measurement_roles = object_schema.get("measurement_or_observation_roles") or []
1304
+ schema_lines = [
1305
+ "Profile-native scientific object schema:",
1306
+ f"- object_types: {', '.join(str(item) for item in object_types) or 'domain-neutral scientific object'}",
1307
+ f"- allowed_operations: {', '.join(str(item) for item in allowed_operations) or 'define, compare, measure, bound'}",
1308
+ f"- target_object: {', '.join(str(item) for item in target_object_roles) or 'research object'}",
1309
+ f"- claim_delta: {', '.join(str(item) for item in claim_delta_roles) or 'explicit claim or relation'}",
1310
+ f"- mechanism_delta: {', '.join(str(item) for item in mechanism_delta_roles) or 'candidate mechanism'}",
1311
+ f"- evidence_obligation: {', '.join(str(item) for item in evidence_roles) or 'discriminating observation'}",
1312
+ f"- boundary_condition: {', '.join(str(item) for item in boundary_roles) or 'validity and failure boundary'}",
1313
+ f"- measurement_or_observation: {', '.join(str(item) for item in measurement_roles) or 'observable or endpoint'}",
1314
+ "Use these native objects and operations directly; do not translate them into software modules by default.",
1315
+ ]
1316
+ gap_routing = payload.get("gap_routing")
1317
+ routing_lines: list[str] = []
1318
+ if isinstance(gap_routing, Mapping):
1319
+ counts = {
1320
+ key: len(value)
1321
+ for key, value in gap_routing.items()
1322
+ if isinstance(value, list) and value
1323
+ }
1324
+ if counts:
1325
+ routing_lines = [
1326
+ "Survey Gap Routing:",
1327
+ "- core/active gaps may define the main hypothesis target.",
1328
+ "- provisional and exploratory gaps may diversify hypotheses but are not automatic novelty evidence.",
1329
+ "- supporting constraints affect feasibility, identifiability, and boundary calibration rather than primary novelty.",
1330
+ "- verification-only gaps constrain evidence or protocol and must not become primary novelty.",
1331
+ "- future-work seeds are exploratory, not established evidence.",
1332
+ "- routed counts: " + ", ".join(f"{key}={value}" for key, value in sorted(counts.items())),
1333
+ ]
1334
+
1335
+ return "\n".join(
1336
+ [
1337
+ f"Profile: {payload.get('profile_label') or profile.label}",
1338
+ f"Profile ID: {payload.get('profile_id') or profile.profile_id}",
1339
+ f"Selected contribution mode: {payload.get('contribution_mode') or profile.contribution_modes[0].mode_id}",
1340
+ "Required/root component roles:",
1341
+ *role_lines,
1342
+ "Allowed primary contribution modes:",
1343
+ *mode_lines,
1344
+ *schema_lines,
1345
+ "Generation rules:",
1346
+ *(rule_lines or ["- Follow the selected profile's scientific object and validation semantics."]),
1347
+ *routing_lines,
1348
+ "Do not default to:",
1349
+ *forbidden_lines,
1350
+ ]
1351
+ )
1352
+
1353
+
1354
+ _PROFILE_DRIFT_PRIMARY_PATTERNS: Dict[str, str] = {
1355
+ "training_objective": r"\b(?:training\s+objective|optimization\s+objective|loss\s+function|auxiliary\s+loss)\b",
1356
+ "neural_architecture": r"\b(?:neural\s+architecture|network\s+architecture|deep\s+architecture)\b",
1357
+ "backbone": r"\bbackbone(?:\s+model)?\b",
1358
+ "learned_controller": r"\b(?:learned\s+controller|neural\s+controller|learned\s+policy)\b",
1359
+ "benchmark": r"\bbenchmark(?:ing)?\b",
1360
+ "router": r"\b(?:router|routing\s+layer|mixture[-\s]of[-\s]experts)\b",
1361
+ "inference_time_policy": r"\b(?:inference[-\s]time\s+policy|test[-\s]time\s+policy)\b",
1362
+ "model_fine_tuning": r"\b(?:model\s+fine[-\s]?tun(?:e|ing)|fine[-\s]?tuning\s+the\s+model)\b",
1363
+ "machine_learning": r"\b(?:machine\s+learning|deep\s+learning)\b",
1364
+ }
1365
+
1366
+ _PROFILE_DRIFT_AUXILIARY_MARKERS = (
1367
+ "auxiliary",
1368
+ "for analysis",
1369
+ "data processing",
1370
+ "numerical simulation",
1371
+ "computational analysis",
1372
+ "secondary tool",
1373
+ "supporting tool",
1374
+ "post hoc",
1375
+ "downstream analysis",
1376
+ "machine learning as a tool",
1377
+ )
1378
+
1379
+ _PROFILE_DRIFT_CONTENT_FIELDS = (
1380
+ "title",
1381
+ "abstract",
1382
+ "core_contribution",
1383
+ "method",
1384
+ "central_hypothesis",
1385
+ "scientific_object",
1386
+ "mechanism_or_relation",
1387
+ "expected_mechanism",
1388
+ "intervention_or_transformation",
1389
+ "discriminating_observation",
1390
+ "boundary_or_failure_condition",
1391
+ "claim_scope",
1392
+ "assumptions",
1393
+ "alternative_explanations",
1394
+ "evidence_requirement",
1395
+ "evidence_basis",
1396
+ "falsifier",
1397
+ "risks",
1398
+ )
1399
+
1400
+
1401
+ def _profile_drift_content(value: Any) -> Any:
1402
+ """Return scientific candidate content without profile/control metadata."""
1403
+
1404
+ if not isinstance(value, Mapping):
1405
+ return value
1406
+ content = {
1407
+ field_name: value[field_name]
1408
+ for field_name in _PROFILE_DRIFT_CONTENT_FIELDS
1409
+ if field_name in value
1410
+ }
1411
+ return content
1412
+
1413
+
1414
+ def _flatten_profile_drift_text(value: Any, *, limit: int = 12000) -> str:
1415
+ chunks: list[str] = []
1416
+
1417
+ def visit(item: Any) -> None:
1418
+ if sum(len(chunk) for chunk in chunks) >= limit:
1419
+ return
1420
+ if isinstance(item, Mapping):
1421
+ for key, nested in item.items():
1422
+ visit(key)
1423
+ visit(nested)
1424
+ elif isinstance(item, (list, tuple, set)):
1425
+ for nested in item:
1426
+ visit(nested)
1427
+ elif item is not None:
1428
+ text = str(item).strip()
1429
+ if text:
1430
+ chunks.append(text)
1431
+
1432
+ visit(value)
1433
+ return " ".join(chunks)[:limit]
1434
+
1435
+
1436
+ def detect_profile_drift(
1437
+ profile_id: Any,
1438
+ text: Any = None,
1439
+ *,
1440
+ candidate: Mapping[str, Any] | None = None,
1441
+ survey_handoff: Mapping[str, Any] | None = None,
1442
+ research_object: Any = None,
1443
+ ) -> Dict[str, Any]:
1444
+ """Detect CS/ML primary-contribution drift without rejecting an idea.
1445
+
1446
+ Computational profiles are intentionally exempt. For other profiles,
1447
+ strong software/optimization terms are material only when they are used as
1448
+ the candidate's primary contribution; auxiliary analysis mentions remain
1449
+ soft secondary-tool signals.
1450
+ """
1451
+
1452
+ if isinstance(profile_id, ScientificInterventionProfile):
1453
+ resolved_profile_id = profile_id.profile_id
1454
+ elif isinstance(profile_id, Mapping):
1455
+ resolved_profile_id = str(profile_id.get("profile_id") or "generic_scientific")
1456
+ if candidate is None:
1457
+ candidate = profile_id
1458
+ else:
1459
+ resolved_profile_id = str(profile_id or "generic_scientific")
1460
+ resolved_profile_id = resolved_profile_id.strip().lower()
1461
+ candidate_value = candidate if candidate is not None else text
1462
+ candidate_text = _flatten_profile_drift_text(_profile_drift_content(candidate_value))
1463
+ anchor_text = _flatten_profile_drift_text(
1464
+ {
1465
+ "survey_handoff": survey_handoff or {},
1466
+ "research_object": research_object,
1467
+ }
1468
+ ).lower()
1469
+ anchor_text_normalized = anchor_text.replace("_", " ")
1470
+ candidate_lower = candidate_text.lower()
1471
+ matched_terms: list[str] = []
1472
+ secondary_tool_mentions: list[str] = []
1473
+ forbidden_primary_terms: list[str] = []
1474
+ rewrite_targets: list[str] = []
1475
+
1476
+ for term_name, pattern in _PROFILE_DRIFT_PRIMARY_PATTERNS.items():
1477
+ match = re.search(pattern, candidate_lower)
1478
+ if not match:
1479
+ continue
1480
+ matched_terms.append(term_name)
1481
+ start = max(0, match.start() - 90)
1482
+ end = min(len(candidate_lower), match.end() + 90)
1483
+ context = candidate_lower[start:end]
1484
+ is_auxiliary = any(marker in context for marker in _PROFILE_DRIFT_AUXILIARY_MARKERS)
1485
+ explicitly_scientific = term_name in anchor_text or term_name.replace("_", " ") in anchor_text_normalized
1486
+ explicitly_scientific = explicitly_scientific and any(
1487
+ marker in anchor_text_normalized
1488
+ for marker in ("research object", "scientific object", "mechanism", "gap", "target")
1489
+ )
1490
+ if is_auxiliary:
1491
+ secondary_tool_mentions.append(term_name)
1492
+ continue
1493
+ if explicitly_scientific:
1494
+ secondary_tool_mentions.append(term_name)
1495
+ continue
1496
+ forbidden_primary_terms.append(term_name)
1497
+ rewrite_targets.append(
1498
+ f"{term_name}: rewrite as the selected profile's native mechanism, process, relation, object, or boundary"
1499
+ )
1500
+
1501
+ if resolved_profile_id == "computational_algorithmic":
1502
+ return {
1503
+ "profile_id": resolved_profile_id,
1504
+ "primary_drift": False,
1505
+ "secondary_tool_mentions": [],
1506
+ "forbidden_primary_terms": [],
1507
+ "drift_severity": "none",
1508
+ "rewrite_targets": [],
1509
+ }
1510
+
1511
+ if forbidden_primary_terms:
1512
+ severity = "material"
1513
+ elif secondary_tool_mentions:
1514
+ severity = "soft"
1515
+ else:
1516
+ severity = "none"
1517
+ return {
1518
+ "profile_id": resolved_profile_id,
1519
+ "primary_drift": bool(forbidden_primary_terms),
1520
+ "secondary_tool_mentions": list(dict.fromkeys(secondary_tool_mentions)),
1521
+ "forbidden_primary_terms": list(dict.fromkeys(forbidden_primary_terms)),
1522
+ "drift_severity": severity,
1523
+ "rewrite_targets": list(dict.fromkeys(rewrite_targets)),
1524
+ }
1525
+
1526
+
1527
+ __all__ = [
1528
+ "ComponentRoleSpec",
1529
+ "ContributionModeSpec",
1530
+ "ValidationRequirement",
1531
+ "ScientificInterventionProfile",
1532
+ "ScientificObjectSchema",
1533
+ "ScientificObjectSpec",
1534
+ "ScientificFieldSpec",
1535
+ "RETAINED_PAPERSEEK_OPENALEX_FIELDS",
1536
+ "IGNORED_PAPERSEEK_OPENALEX_FIELDS",
1537
+ "PAPERSEEK_FIELD_TO_PROFILE",
1538
+ "PAPERSEEK_FIELD_LABELS",
1539
+ "PAPERSEEK_FIELD_CROSSWALK",
1540
+ "PAPERSEEK_FIELD_DOMAINS",
1541
+ "PAPERSEEK_FIELD_TO_DISCIPLINE",
1542
+ "PAPERSEEK_FIELD_SECONDARY_PROFILES",
1543
+ "PAPERSEEK_FIELD_PROFILE_REGISTRY",
1544
+ "PAPERSEEK_FIELD_REGISTRY",
1545
+ "SCIENTIFIC_OBJECT_REGISTRY",
1546
+ "PROFILE_NATIVE_OBJECT_SCHEMAS",
1547
+ "SCIENTIFIC_INTERVENTION_PROFILES",
1548
+ "DISCIPLINE_TO_PROFILE",
1549
+ "get_scientific_intervention_profile",
1550
+ "list_scientific_intervention_profiles",
1551
+ "get_scientific_field_spec",
1552
+ "list_scientific_field_specs",
1553
+ "get_scientific_object_spec",
1554
+ "get_scientific_object_schema",
1555
+ "list_scientific_object_schemas",
1556
+ "normalize_project_context_discipline_resolution",
1557
+ "resolve_scientific_intervention_profile",
1558
+ "build_scientific_intervention_payload",
1559
+ "format_scientific_intervention_profile_for_prompt",
1560
+ "detect_profile_drift",
1561
+ ]