@brainpilot/skills 0.0.6 → 0.0.7

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 (285) hide show
  1. package/package.json +2 -2
  2. package/skills/01_Meta-Skills/academic-research-hub/SKILL.md +108 -0
  3. package/skills/01_Meta-Skills/academic-research-hub/scripts/requirements.txt +17 -0
  4. package/skills/01_Meta-Skills/academic-research-hub/scripts/research.py +781 -0
  5. package/skills/01_Meta-Skills/beautiful-log/SKILL.md +64 -0
  6. package/skills/01_Meta-Skills/beautiful-log/scripts/beautiful_log.py +274 -0
  7. package/skills/01_Meta-Skills/ethoclaw-daily-paper/SKILL.md +130 -0
  8. package/skills/01_Meta-Skills/ethoclaw-daily-paper/assets/config.template.yaml +54 -0
  9. package/skills/01_Meta-Skills/ethoclaw-daily-paper/assets/top5_digest_template.md +5 -0
  10. package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/build_top5_digest.py +300 -0
  11. package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/common.py +137 -0
  12. package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/merge_results.py +106 -0
  13. package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/run_pipeline.py +177 -0
  14. package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/search_arxiv.py +162 -0
  15. package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/search_pubmed.py +202 -0
  16. package/skills/01_Meta-Skills/ethoclaw-normalize-tabular/SKILL.md +173 -0
  17. package/skills/01_Meta-Skills/ethoclaw-normalize-tabular/scripts/normalize_data.py +874 -0
  18. package/skills/01_Meta-Skills/ethoclaw-pdf-research/SKILL.md +134 -0
  19. package/skills/01_Meta-Skills/ethoclaw-pdf-research/references/confirmation-prompts.md +31 -0
  20. package/skills/01_Meta-Skills/ethoclaw-pdf-research/references/output-patterns.md +45 -0
  21. package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/build_markdown_deliverables.py +41 -0
  22. package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/build_research_log.py +84 -0
  23. package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/build_summary_md.py +63 -0
  24. package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/extract_pdf_bundle.py +140 -0
  25. package/skills/01_Meta-Skills/experiment-controller/SKILL.md +140 -0
  26. package/skills/01_Meta-Skills/knowledge-graph-builder/SKILL.md +366 -0
  27. package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/entity_resolution.py +120 -0
  28. package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/extraction_prompt_template.txt +19 -0
  29. package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/graph_query.py +106 -0
  30. package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/hypothesis_cli_reference.py +42 -0
  31. package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/new_data_source_template.py +116 -0
  32. package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/requirements.txt +15 -0
  33. package/skills/01_Meta-Skills/method-design/SKILL.md +61 -0
  34. package/skills/01_Meta-Skills/multi-search-engine/SKILL.md +119 -0
  35. package/skills/01_Meta-Skills/research-idea/SKILL.md +65 -0
  36. package/skills/05_EEG_ERP/eeg-skill/SKILL.md +197 -0
  37. package/skills/05_EEG_ERP/meg-skill/SKILL.md +188 -0
  38. package/skills/05_EEG_ERP/meg-skill/scripts/time_frequency.py +223 -0
  39. package/skills/05_EEG_ERP/mne-eeg-tool/SKILL.md +165 -0
  40. package/skills/05_EEG_ERP/mne-eeg-tool/scripts/eeg_pipeline_reference.py +231 -0
  41. package/skills/05_EEG_ERP/seed-iv-skill/SKILL.md +184 -0
  42. package/skills/05_EEG_ERP/seed-iv-skill/scripts/classify_seed_iv.py +154 -0
  43. package/skills/05_EEG_ERP/seed-iv-skill/scripts/extract_seed_iv_features.py +190 -0
  44. package/skills/05_EEG_ERP/seed-iv-skill/scripts/validate_seed_iv.py +102 -0
  45. package/skills/05_EEG_ERP/seed-vig-skill/SKILL.md +182 -0
  46. package/skills/05_EEG_ERP/seed-vig-skill/scripts/classify_seed_vig.py +165 -0
  47. package/skills/05_EEG_ERP/seed-vig-skill/scripts/extract_seed_vig_features.py +185 -0
  48. package/skills/05_EEG_ERP/seed-vig-skill/scripts/validate_seed_vig.py +88 -0
  49. package/skills/06_fMRI_Neuroimaging/abcd-skill/SKILL.md +308 -0
  50. package/skills/06_fMRI_Neuroimaging/abcd-skill/scripts/abcd_qc_summary.py +449 -0
  51. package/skills/06_fMRI_Neuroimaging/abcd-skill/scripts/extract_abcd_phenotype.py +292 -0
  52. package/skills/06_fMRI_Neuroimaging/abcd-skill/scripts/reorganize_abcd.py +387 -0
  53. package/skills/06_fMRI_Neuroimaging/abide-skill/SKILL.md +302 -0
  54. package/skills/06_fMRI_Neuroimaging/abide-skill/scripts/abide_qc_summary.py +317 -0
  55. package/skills/06_fMRI_Neuroimaging/abide-skill/scripts/extract_abide_phenotype.py +267 -0
  56. package/skills/06_fMRI_Neuroimaging/abide-skill/scripts/reorganize_abide.py +387 -0
  57. package/skills/06_fMRI_Neuroimaging/adhd200-skill/SKILL.md +244 -0
  58. package/skills/06_fMRI_Neuroimaging/adhd200-skill/scripts/adhd200_qc_summary.py +98 -0
  59. package/skills/06_fMRI_Neuroimaging/adhd200-skill/scripts/extract_adhd200_phenotype.py +134 -0
  60. package/skills/06_fMRI_Neuroimaging/adhd200-skill/scripts/reorganize_adhd200.py +206 -0
  61. package/skills/06_fMRI_Neuroimaging/adni-skill/SKILL.md +358 -0
  62. package/skills/06_fMRI_Neuroimaging/adni-skill/scripts/generate_adni_task_files.py +1305 -0
  63. package/skills/06_fMRI_Neuroimaging/adni-skill/scripts/generate_vqa_from_tasks.py +766 -0
  64. package/skills/06_fMRI_Neuroimaging/adni-skill/scripts/reorganize_adni.py +491 -0
  65. package/skills/06_fMRI_Neuroimaging/aibl-skill/SKILL.md +295 -0
  66. package/skills/06_fMRI_Neuroimaging/aibl-skill/scripts/aibl_qc_summary.py +260 -0
  67. package/skills/06_fMRI_Neuroimaging/aibl-skill/scripts/extract_aibl_phenotype.py +365 -0
  68. package/skills/06_fMRI_Neuroimaging/aibl-skill/scripts/reorganize_aibl.py +394 -0
  69. package/skills/06_fMRI_Neuroimaging/aomic-skill/SKILL.md +292 -0
  70. package/skills/06_fMRI_Neuroimaging/aomic-skill/scripts/aomic_qc_summary.py +258 -0
  71. package/skills/06_fMRI_Neuroimaging/aomic-skill/scripts/extract_aomic_phenotype.py +284 -0
  72. package/skills/06_fMRI_Neuroimaging/aomic-skill/scripts/reorganize_aomic.py +322 -0
  73. package/skills/06_fMRI_Neuroimaging/asl-skill/SKILL.md +168 -0
  74. package/skills/06_fMRI_Neuroimaging/asl-skill/scripts/compute_cbf.py +224 -0
  75. package/skills/06_fMRI_Neuroimaging/bids-organizer/SKILL.md +241 -0
  76. package/skills/06_fMRI_Neuroimaging/bold5000-skill/SKILL.md +186 -0
  77. package/skills/06_fMRI_Neuroimaging/bold5000-skill/scripts/bold5000_qc_summary.py +96 -0
  78. package/skills/06_fMRI_Neuroimaging/bold5000-skill/scripts/extract_bold5000_stimulus.py +125 -0
  79. package/skills/06_fMRI_Neuroimaging/bold5000-skill/scripts/reorganize_bold5000.py +102 -0
  80. package/skills/06_fMRI_Neuroimaging/camcan-skill/SKILL.md +213 -0
  81. package/skills/06_fMRI_Neuroimaging/camcan-skill/scripts/camcan_qc_summary.py +131 -0
  82. package/skills/06_fMRI_Neuroimaging/camcan-skill/scripts/extract_camcan_phenotype.py +145 -0
  83. package/skills/06_fMRI_Neuroimaging/camcan-skill/scripts/validate_camcan.py +141 -0
  84. package/skills/06_fMRI_Neuroimaging/cobre-skill/SKILL.md +201 -0
  85. package/skills/06_fMRI_Neuroimaging/cobre-skill/scripts/cobre_qc_summary.py +95 -0
  86. package/skills/06_fMRI_Neuroimaging/cobre-skill/scripts/extract_cobre_phenotype.py +104 -0
  87. package/skills/06_fMRI_Neuroimaging/cobre-skill/scripts/reorganize_cobre.py +140 -0
  88. package/skills/06_fMRI_Neuroimaging/conn-tool/SKILL.md +180 -0
  89. package/skills/06_fMRI_Neuroimaging/dcm2nii/SKILL.md +189 -0
  90. package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/SKILL.md +183 -0
  91. package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/scripts/dmt_har_med_qc_summary.py +96 -0
  92. package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/scripts/extract_dmt_har_med_phenotype.py +121 -0
  93. package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/scripts/reorganize_dmt_har_med.py +125 -0
  94. package/skills/06_fMRI_Neuroimaging/dwi-skill/SKILL.md +359 -0
  95. package/skills/06_fMRI_Neuroimaging/fmri-skill/SKILL.md +371 -0
  96. package/skills/06_fMRI_Neuroimaging/fmriprep-tool/SKILL.md +228 -0
  97. package/skills/06_fMRI_Neuroimaging/freesurfer-tool/SKILL.md +286 -0
  98. package/skills/06_fMRI_Neuroimaging/freesurfer-tool/scripts/freesurfer_processor.py +145 -0
  99. package/skills/06_fMRI_Neuroimaging/fsl-tool/SKILL.md +208 -0
  100. package/skills/06_fMRI_Neuroimaging/hbn-skill/SKILL.md +271 -0
  101. package/skills/06_fMRI_Neuroimaging/hbn-skill/scripts/extract_hbn_phenotype.py +107 -0
  102. package/skills/06_fMRI_Neuroimaging/hbn-skill/scripts/hbn_qc_summary.py +96 -0
  103. package/skills/06_fMRI_Neuroimaging/hbn-skill/scripts/reorganize_hbn.py +150 -0
  104. package/skills/06_fMRI_Neuroimaging/hcpa-skill/SKILL.md +210 -0
  105. package/skills/06_fMRI_Neuroimaging/hcpa-skill/scripts/extract_hcpa_phenotype.py +146 -0
  106. package/skills/06_fMRI_Neuroimaging/hcpa-skill/scripts/hcpa_qc_summary.py +120 -0
  107. package/skills/06_fMRI_Neuroimaging/hcpa-skill/scripts/reorganize_hcpa.py +155 -0
  108. package/skills/06_fMRI_Neuroimaging/hcpd-skill/SKILL.md +210 -0
  109. package/skills/06_fMRI_Neuroimaging/hcpd-skill/scripts/extract_hcpd_phenotype.py +148 -0
  110. package/skills/06_fMRI_Neuroimaging/hcpd-skill/scripts/hcpd_qc_summary.py +125 -0
  111. package/skills/06_fMRI_Neuroimaging/hcpd-skill/scripts/reorganize_hcpd.py +146 -0
  112. package/skills/06_fMRI_Neuroimaging/hcpep-skill/SKILL.md +215 -0
  113. package/skills/06_fMRI_Neuroimaging/hcpep-skill/scripts/extract_hcpep_phenotype.py +157 -0
  114. package/skills/06_fMRI_Neuroimaging/hcpep-skill/scripts/hcpep_qc_summary.py +143 -0
  115. package/skills/06_fMRI_Neuroimaging/hcpep-skill/scripts/reorganize_hcpep.py +146 -0
  116. package/skills/06_fMRI_Neuroimaging/hcppipeline-tool/SKILL.md +217 -0
  117. package/skills/06_fMRI_Neuroimaging/hcpya-skill/SKILL.md +214 -0
  118. package/skills/06_fMRI_Neuroimaging/hcpya-skill/scripts/extract_hcpya_phenotype.py +190 -0
  119. package/skills/06_fMRI_Neuroimaging/hcpya-skill/scripts/hcpya_qc_summary.py +152 -0
  120. package/skills/06_fMRI_Neuroimaging/hcpya-skill/scripts/reorganize_hcpya.py +203 -0
  121. package/skills/06_fMRI_Neuroimaging/ixi-skill/SKILL.md +198 -0
  122. package/skills/06_fMRI_Neuroimaging/ixi-skill/scripts/ixi_qc_summary.py +137 -0
  123. package/skills/06_fMRI_Neuroimaging/ixi-skill/scripts/reorganize_ixi.py +190 -0
  124. package/skills/06_fMRI_Neuroimaging/mnd-skill/SKILL.md +191 -0
  125. package/skills/06_fMRI_Neuroimaging/mnd-skill/scripts/extract_mnd_phenotype.py +143 -0
  126. package/skills/06_fMRI_Neuroimaging/mnd-skill/scripts/mnd_qc_summary.py +120 -0
  127. package/skills/06_fMRI_Neuroimaging/mnd-skill/scripts/validate_mnd.py +107 -0
  128. package/skills/06_fMRI_Neuroimaging/mschallenge-skill/SKILL.md +203 -0
  129. package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/analyze_lesions.py +119 -0
  130. package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/longitudinal_lesion.py +148 -0
  131. package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/mschallenge_qc_summary.py +132 -0
  132. package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/validate_mschallenge.py +116 -0
  133. package/skills/06_fMRI_Neuroimaging/nibabel-skill/SKILL.md +184 -0
  134. package/skills/06_fMRI_Neuroimaging/nibabel-skill/scripts/atlas_coordinate_reference.py +61 -0
  135. package/skills/06_fMRI_Neuroimaging/nibabel-skill/scripts/freesurfer_io_reference.py +34 -0
  136. package/skills/06_fMRI_Neuroimaging/nibabel-skill/scripts/nifti_inspection_reference.py +35 -0
  137. package/skills/06_fMRI_Neuroimaging/nifd-skill/SKILL.md +205 -0
  138. package/skills/06_fMRI_Neuroimaging/nifd-skill/scripts/extract_nifd_phenotype.py +132 -0
  139. package/skills/06_fMRI_Neuroimaging/nifd-skill/scripts/nifd_qc_summary.py +111 -0
  140. package/skills/06_fMRI_Neuroimaging/nifd-skill/scripts/validate_nifd.py +111 -0
  141. package/skills/06_fMRI_Neuroimaging/nii2dcm/SKILL.md +143 -0
  142. package/skills/06_fMRI_Neuroimaging/nilearn-tool/SKILL.md +266 -0
  143. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/connectome_reference.py +65 -0
  144. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/denoise_timeseries_reference.py +58 -0
  145. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/hierarchical_parcellation_reference.py +53 -0
  146. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/kmeans_parcellation_reference.py +53 -0
  147. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/preprocess_bold_reference.py +76 -0
  148. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/rest_dictlearning_reference.py +56 -0
  149. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/rest_ica_reference.py +59 -0
  150. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/second_level_glm_reference.py +58 -0
  151. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/spacenet_classifier_reference.py +59 -0
  152. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/svm_classifier_reference.py +60 -0
  153. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/task_glm_reference.py +63 -0
  154. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/zalff_summary_reference.py +109 -0
  155. package/skills/06_fMRI_Neuroimaging/nsd-skill/SKILL.md +210 -0
  156. package/skills/06_fMRI_Neuroimaging/nsd-skill/scripts/extract_nsd_stimulus.py +171 -0
  157. package/skills/06_fMRI_Neuroimaging/nsd-skill/scripts/nsd_qc_summary.py +142 -0
  158. package/skills/06_fMRI_Neuroimaging/nsd-skill/scripts/validate_nsd.py +142 -0
  159. package/skills/06_fMRI_Neuroimaging/oasis-skill/SKILL.md +205 -0
  160. package/skills/06_fMRI_Neuroimaging/oasis-skill/scripts/extract_oasis_phenotype.py +126 -0
  161. package/skills/06_fMRI_Neuroimaging/oasis-skill/scripts/oasis_qc_summary.py +115 -0
  162. package/skills/06_fMRI_Neuroimaging/oasis-skill/scripts/validate_oasis.py +119 -0
  163. package/skills/06_fMRI_Neuroimaging/pet-skill/SKILL.md +173 -0
  164. package/skills/06_fMRI_Neuroimaging/pet-skill/scripts/compute_suvr.py +202 -0
  165. package/skills/06_fMRI_Neuroimaging/pnc-skill/SKILL.md +206 -0
  166. package/skills/06_fMRI_Neuroimaging/pnc-skill/scripts/extract_pnc_phenotype.py +136 -0
  167. package/skills/06_fMRI_Neuroimaging/pnc-skill/scripts/pnc_qc_summary.py +116 -0
  168. package/skills/06_fMRI_Neuroimaging/pnc-skill/scripts/validate_pnc.py +120 -0
  169. package/skills/06_fMRI_Neuroimaging/ppmi-skill/SKILL.md +209 -0
  170. package/skills/06_fMRI_Neuroimaging/ppmi-skill/scripts/extract_ppmi_phenotype.py +138 -0
  171. package/skills/06_fMRI_Neuroimaging/ppmi-skill/scripts/ppmi_qc_summary.py +111 -0
  172. package/skills/06_fMRI_Neuroimaging/ppmi-skill/scripts/validate_ppmi.py +117 -0
  173. package/skills/06_fMRI_Neuroimaging/qsiprep-tool/SKILL.md +320 -0
  174. package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/SKILL.md +215 -0
  175. package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/extract_rest_mdd_phenotype.py +132 -0
  176. package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/harmonize_sites.py +152 -0
  177. package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/rest_mdd_qc_summary.py +124 -0
  178. package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/validate_rest_mdd.py +103 -0
  179. package/skills/06_fMRI_Neuroimaging/smri-skill/SKILL.md +302 -0
  180. package/skills/06_fMRI_Neuroimaging/tcp-skill/SKILL.md +204 -0
  181. package/skills/06_fMRI_Neuroimaging/tcp-skill/scripts/extract_tcp_phenotype.py +139 -0
  182. package/skills/06_fMRI_Neuroimaging/tcp-skill/scripts/tcp_qc_summary.py +111 -0
  183. package/skills/06_fMRI_Neuroimaging/tcp-skill/scripts/validate_tcp.py +99 -0
  184. package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/SKILL.md +217 -0
  185. package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/scripts/extract_ucla_cnp_phenotype.py +145 -0
  186. package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/scripts/ucla_cnp_qc_summary.py +111 -0
  187. package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/scripts/validate_ucla_cnp.py +113 -0
  188. package/skills/06_fMRI_Neuroimaging/ukb-skill/SKILL.md +310 -0
  189. package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/build_ukb_survival.py +210 -0
  190. package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/extract_ukb_cases.py +308 -0
  191. package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/extract_ukb_phenotype.py +232 -0
  192. package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/ukb_qc_summary.py +158 -0
  193. package/skills/06_fMRI_Neuroimaging/wmh-segmentation/SKILL.md +133 -0
  194. package/skills/07_Computational_Modeling/detrending/SKILL.md +118 -0
  195. package/skills/07_Computational_Modeling/dictlearning/SKILL.md +122 -0
  196. package/skills/07_Computational_Modeling/filtering/SKILL.md +121 -0
  197. package/skills/07_Computational_Modeling/glm/SKILL.md +153 -0
  198. package/skills/07_Computational_Modeling/hierarchical/SKILL.md +121 -0
  199. package/skills/07_Computational_Modeling/ica/SKILL.md +122 -0
  200. package/skills/07_Computational_Modeling/kmeans/SKILL.md +119 -0
  201. package/skills/07_Computational_Modeling/run_models/SKILL.md +427 -0
  202. package/skills/07_Computational_Modeling/spacenet/SKILL.md +122 -0
  203. package/skills/07_Computational_Modeling/svm/SKILL.md +120 -0
  204. package/skills/08_Computational_Neuroscience/brain_gnn/SKILL.md +183 -0
  205. package/skills/08_Computational_Neuroscience/dipy-tool/SKILL.md +239 -0
  206. package/skills/08_Computational_Neuroscience/dipy-tool/scripts/dti_metrics_reference.py +70 -0
  207. package/skills/08_Computational_Neuroscience/dipy-tool/scripts/load_and_mask_reference.py +76 -0
  208. package/skills/08_Computational_Neuroscience/dipy-tool/scripts/roi_stats_reference.py +59 -0
  209. package/skills/08_Computational_Neuroscience/fm_app/SKILL.md +195 -0
  210. package/skills/08_Computational_Neuroscience/neurostorm/SKILL.md +151 -0
  211. package/skills/13_Visualization/brain-visualization/SKILL.md +191 -0
  212. package/skills/13_Visualization/brain-visualization/scripts/connectome_reference.py +108 -0
  213. package/skills/13_Visualization/brain-visualization/scripts/freesurfer_ply_reference.py +54 -0
  214. package/skills/13_Visualization/brain-visualization/scripts/zalff_summary_reference.py +116 -0
  215. package/skills/13_Visualization/ethoclaw-paper-figure-layout/SKILL.md +78 -0
  216. package/skills/13_Visualization/ethoclaw-paper-figure-layout/assets/naturecomm_figures.tex +74 -0
  217. package/skills/13_Visualization/ethoclaw-paper-figure-layout/scripts/layout_results_foldered.py +579 -0
  218. package/skills/14_Writing/overleaf-skill/SKILL.md +184 -0
  219. package/skills/14_Writing/overleaf-skill/scripts/install.sh +30 -0
  220. package/skills/14_Writing/paper-writing/SKILL.md +146 -0
  221. package/skills/14_Writing/paper-writing/scripts/data_statement_templates.py +164 -0
  222. package/skills/14_Writing/paper-writing/scripts/figure_templates.py +315 -0
  223. package/skills/14_Writing/paper-writing/scripts/nature_figure_style.py +214 -0
  224. package/skills/14_Writing/paper-writing/scripts/section_phrasebank.py +246 -0
  225. package/skills/16_Animal_Behavior/deeplabcut/SKILL.md +154 -0
  226. package/skills/16_Animal_Behavior/deeplabcut/references/3d-pose.md +89 -0
  227. package/skills/16_Animal_Behavior/deeplabcut/references/maDLC.md +123 -0
  228. package/skills/16_Animal_Behavior/deeplabcut/references/modelzoo.md +98 -0
  229. package/skills/16_Animal_Behavior/deeplabcut/references/standard-pipeline.md +165 -0
  230. package/skills/16_Animal_Behavior/deeplabcut/references/utilities.md +146 -0
  231. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/SKILL.md +274 -0
  232. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/report_template_en.html +112 -0
  233. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/report_template_en.md +21 -0
  234. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/cluster-section.md +5 -0
  235. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/heatmap-section.md +5 -0
  236. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/integrated-interpretation.md +3 -0
  237. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/overview.md +3 -0
  238. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/project-summary.md +3 -0
  239. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/radar-section.md +5 -0
  240. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/raw-trajectory.md +3 -0
  241. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/sample-check.md +3 -0
  242. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/single-subject-section.md +3 -0
  243. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/stats-section.md +5 -0
  244. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/epm.md +52 -0
  245. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/fst.md +37 -0
  246. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/nor.md +39 -0
  247. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/oft.md +43 -0
  248. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/tcst.md +45 -0
  249. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/tst.md +36 -0
  250. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/input-types.md +59 -0
  251. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/interpretation-guardrails.md +45 -0
  252. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/metadata-schema.md +57 -0
  253. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/report-sections.md +86 -0
  254. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/section-selection-rules.md +169 -0
  255. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/scripts/build_report_manifest.py +27 -0
  256. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/scripts/render_report.py +34 -0
  257. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/scripts/report_utils.py +1121 -0
  258. package/skills/16_Animal_Behavior/ethoclaw-animal-grounding/SKILL.md +390 -0
  259. package/skills/16_Animal_Behavior/ethoclaw-animal-grounding/reference_code.py +98 -0
  260. package/skills/16_Animal_Behavior/ethoclaw-animal-pose-estimation/SKILL.md +336 -0
  261. package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/README.md +21 -0
  262. package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/SKILL.md +41 -0
  263. package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/batch_kinematic_generator.py +663 -0
  264. package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/config.json +19 -0
  265. package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/generate_kinematic_parameter.py +401 -0
  266. package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/kinematic_generator.py +265 -0
  267. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/SKILL.md +72 -0
  268. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/references/config.example.toml +56 -0
  269. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/scripts/cluster_all_params.py +232 -0
  270. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/scripts/cluster_all_params_from_config.py +236 -0
  271. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-radar-generate/SKILL.md +68 -0
  272. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-radar-generate/references/notes.md +5 -0
  273. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-radar-generate/scripts/plot_h5_radar.py +513 -0
  274. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/SKILL.md +52 -0
  275. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/config.toml +81 -0
  276. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/references/stats-rule.md +18 -0
  277. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/scripts/h5_inspect.py +79 -0
  278. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/scripts/h5_violin_batch.py +624 -0
  279. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/scripts/h5_violin_stats.py +438 -0
  280. package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/SKILL.md +280 -0
  281. package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/core_scripts/heatmap_trajectory.py +790 -0
  282. package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/core_scripts/heatmap_velocity.py +855 -0
  283. package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/reference_data/reference_2d.csv +101 -0
  284. package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/reference_data/reference_2d.h5 +0 -0
  285. package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/reference_data/reference_data_readme.md +126 -0
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import json
4
+ from pathlib import Path
5
+
6
+
7
+ REQUIRED_AGENT_FIELDS =[
8
+ "title",
9
+ "summary",
10
+ "selection_reason",
11
+ ]
12
+
13
+
14
+ def parse_index_list(raw_value):
15
+ values =[]
16
+ for chunk in (raw_value or "").split(","):
17
+ chunk = chunk.strip()
18
+ if not chunk:
19
+ continue
20
+ try:
21
+ values.append(int(chunk))
22
+ except ValueError as exc:
23
+ raise SystemExit(f"Invalid paper index: {chunk}") from exc
24
+ return values
25
+
26
+
27
+ def join_authors(authors, limit=10):
28
+ if not authors:
29
+ return "Unknown"
30
+ return ", ".join(authors[:limit])
31
+
32
+
33
+ def load_json(path):
34
+ return json.loads(Path(path).read_text(encoding="utf-8-sig"))
35
+
36
+
37
+ def selected_papers_from_merged(merged_payload, selected_indexes):
38
+ items = merged_payload.get("items",[])
39
+ if not items:
40
+ raise SystemExit("Merged input contains no papers.")
41
+
42
+ selected_papers =[]
43
+ seen = set()
44
+ for slot, candidate_index in enumerate(selected_indexes, start=1):
45
+ if candidate_index in seen:
46
+ raise SystemExit(f"Duplicate candidate index: {candidate_index}")
47
+ seen.add(candidate_index)
48
+
49
+ zero_based = candidate_index - 1
50
+ if zero_based < 0 or zero_based >= len(items):
51
+ raise SystemExit(f"Candidate index out of range: {candidate_index}")
52
+
53
+ item = items[zero_based]
54
+ selected_papers.append(
55
+ {
56
+ "slot": slot,
57
+ "candidate_index": candidate_index,
58
+ "title": item.get("title", ""),
59
+ "source": item.get("source_label") or item.get("source", "unknown"),
60
+ "authors": item.get("authors", []),
61
+ "published": item.get("published", "")[:10],
62
+ "journal": item.get("journal", ""),
63
+ "url": item.get("url", ""),
64
+ "pdf_url": item.get("pdf_url", ""),
65
+ "abstract": item.get("summary", ""),
66
+ "weighted_score": item.get("weighted_score", item.get("relevance_score", "")),
67
+ "title": "",
68
+ "summary": "",
69
+ "selection_reason": "",
70
+ }
71
+ )
72
+ return selected_papers
73
+
74
+
75
+ def build_agent_packet(merged_payload, selected_indexes):
76
+ return {
77
+ "generated_at": merged_payload.get("generated_at", ""),
78
+ "candidate_count": merged_payload.get("count", 0),
79
+ "selected_indexes": selected_indexes,
80
+ "selection_policy": {
81
+ "rank_on_titles_only": True,
82
+ "draft_from_title_and_abstract": True,
83
+ "draft_in_single_session": True,
84
+ },
85
+ "agent_guidance": {
86
+ "goal": "First filter the Top 5 by looking only at candidate titles, then read the English titles and abstracts one by one in the same session, and write the titles and summaries.",
87
+ "recommended_process":[
88
+ "First, read candidate_titles.md and determine the Top 5 based solely on the titles.",
89
+ "After determining the Top 5, read the titles and English abstracts of these 5 papers. Do not re-read the full candidate pool.",
90
+ "Complete the title, selection reason, and summary one by one.",
91
+ "Write the summary in one or a few natural paragraphs without using subheadings, but ensure the content covers the background, methods, findings, significance, and limitations.",
92
+ "Write the content directly into the final Markdown, or fill in the JSON first and then render the Markdown.",
93
+ ],
94
+ "required_sections": [
95
+ "title",
96
+ "summary",
97
+ "selection_reason",
98
+ ],
99
+ "style_rules":[
100
+ "The summary must cover the background, methods, findings, significance, and limitations. Do not just write a generic abstract.",
101
+ "Do not split the summary with subheadings like 'Background/Methods/Findings'. Use natural paragraph breaks to improve readability.",
102
+ "Prioritize retaining key information such as species, brain regions, behavioral paradigms, and recording or manipulation methods.",
103
+ "Explicitly state limitations when the abstract lacks sufficient evidence; do not fabricate information.",
104
+ ],
105
+ },
106
+ "selected_papers": selected_papers_from_merged(merged_payload, selected_indexes),
107
+ }
108
+
109
+
110
+ def render_agent_review_markdown(packet):
111
+ lines =[
112
+ "# Top 5 Review Packet",
113
+ "",
114
+ f"- Generated At: {packet.get('generated_at', '')}",
115
+ f"- Candidate Count: {packet.get('candidate_count', 0)}",
116
+ f"- Selected Indexes: {', '.join(str(value) for value in packet.get('selected_indexes', []))}",
117
+ "",
118
+ "> First complete the Top 5 screening using only titles. Once selected, read only the English titles and abstracts of these 5 papers, and complete the summaries in the same session.",
119
+ "",
120
+ ]
121
+
122
+ guidance = packet.get("agent_guidance", {})
123
+ if guidance:
124
+ lines.extend([
125
+ "## Packet Guidance",
126
+ "",
127
+ f"- Goal: {guidance.get('goal', '')}",
128
+ "- Recommended Process:",
129
+ ]
130
+ )
131
+ for step in guidance.get("recommended_process",[]):
132
+ lines.append(f" - {step}")
133
+ lines.append("- Style Rules:")
134
+ for rule in guidance.get("style_rules",[]):
135
+ lines.append(f" - {rule}")
136
+ lines.extend(["", "## Required JSON Fields", ""])
137
+ for field in guidance.get("required_sections",[]):
138
+ lines.append(f"- {field}")
139
+ lines.append("")
140
+
141
+ for paper in packet.get("selected_papers",[]):
142
+ lines.extend(
143
+ [
144
+ f"## Slot {paper['slot']}: {paper.get('title', '').strip() or '(untitled)'}",
145
+ "",
146
+ f"- Candidate Index: {paper['candidate_index']}",
147
+ f"- Source: {paper.get('source', 'unknown')}",
148
+ f"- Authors: {join_authors(paper.get('authors',[]))}",
149
+ f"- Published: {paper.get('published', '') or 'Unknown'}",
150
+ f"- Journal: {paper.get('journal') or 'Unknown'}",
151
+ f"- URL: {paper.get('url') or 'N/A'}",
152
+ f"- PDF URL: {paper.get('pdf_url') or 'N/A'}",
153
+ f"- Weighted Score: {paper.get('weighted_score', 'N/A')}",
154
+ "",
155
+ "### Title + Abstract",
156
+ "",
157
+ f"- English Title: {paper.get('title', '').strip() or '(untitled)'}",
158
+ "",
159
+ paper.get("abstract", "").strip() or "No abstract available.",
160
+ "",
161
+ "### JSON Fields To Fill",
162
+ "",
163
+ "- title:",
164
+ "- summary:",
165
+ "- selection_reason:",
166
+ "",
167
+ ]
168
+ )
169
+ return "\n".join(lines).rstrip() + "\n"
170
+
171
+
172
+ def render_paper_block(paper):
173
+ return "\n".join(
174
+ [
175
+ f"## {paper['slot']}. {paper.get('title', '').strip() or '(untitled)'}",
176
+ "",
177
+ f"- Title: {paper.get('title', '').strip() or 'To be added'}",
178
+ f"- Source: {paper.get('source', 'unknown')}",
179
+ f"- Authors: {join_authors(paper.get('authors',[]))}",
180
+ f"- Published: {paper.get('published', '') or 'Unknown'}",
181
+ f"- Selection Reason: {paper.get('selection_reason', '').strip() or 'To be added'}",
182
+ f"- URL: {paper.get('url') or 'N/A'}",
183
+ f"- PDF URL: {paper.get('pdf_url') or 'N/A'}",
184
+ "",
185
+ "### Summary",
186
+ "",
187
+ paper.get("summary", "").strip() or "To be added",
188
+ "",
189
+ "### Original Abstract",
190
+ "",
191
+ paper.get("abstract", "").strip() or "No abstract available",
192
+ "",
193
+ ]
194
+ )
195
+
196
+
197
+ def render_final_markdown(template_text, packet, generator_label):
198
+ papers_blocks =[render_paper_block(paper) for paper in packet.get("selected_papers", [])]
199
+ return (
200
+ template_text.replace("{{generated_at}}", packet.get("generated_at", ""))
201
+ .replace("{{candidate_count}}", str(packet.get("candidate_count", 0)))
202
+ .replace("{{generator_label}}", generator_label)
203
+ .replace("{{papers}}", "\n".join(papers_blocks).strip())
204
+ .rstrip()
205
+ + "\n"
206
+ )
207
+
208
+
209
+ def render_direct_markdown(merged_payload, selected_indexes, template_text, generator_label):
210
+ packet = {
211
+ "generated_at": merged_payload.get("generated_at", ""),
212
+ "candidate_count": merged_payload.get("count", 0),
213
+ "selected_papers": selected_papers_from_merged(merged_payload, selected_indexes),
214
+ }
215
+ return render_final_markdown(template_text, packet, generator_label)
216
+
217
+
218
+ def ensure_agent_fields(packet):
219
+ papers = packet.get("selected_papers",[])
220
+ if not papers:
221
+ raise SystemExit("Agent packet contains no selected papers.")
222
+ for paper in papers:
223
+ for field in REQUIRED_AGENT_FIELDS:
224
+ if not str(paper.get(field, "")).strip():
225
+ raise SystemExit(f"Missing `{field}` for slot {paper.get('slot', '?')}.")
226
+
227
+
228
+ def write_output(path_str, content):
229
+ output_path = Path(path_str).expanduser().resolve()
230
+ output_path.parent.mkdir(parents=True, exist_ok=True)
231
+ output_path.write_text(content, encoding="utf-8")
232
+ print(output_path)
233
+
234
+
235
+ def default_template_path():
236
+ return Path(__file__).resolve().parents[1] / "assets" / "top5_digest_template.md"
237
+
238
+
239
+ def main():
240
+ parser = argparse.ArgumentParser(description="Prepare or render a Top 5 neuroethology digest without calling an API.")
241
+ parser.add_argument("--input", default="", help="Merged JSON generated by merge_results.py")
242
+ parser.add_argument("--paper-indexes", default="", help="Comma-separated candidate indexes chosen by the agent")
243
+ parser.add_argument("--agent-json-output", default="", help="Write the agent packet JSON here")
244
+ parser.add_argument("--review-md-output", default="", help="Write the review packet Markdown here")
245
+ parser.add_argument("--from-agent-json", default="", help="Render final digest from a completed agent packet JSON")
246
+ parser.add_argument("--direct-md-output", default="", help="Render a final Markdown skeleton directly from merged JSON and selected indexes")
247
+ parser.add_argument("--output", default="", help="Final Markdown output path when using --from-agent-json")
248
+ parser.add_argument("--template", default="", help="Markdown template path")
249
+ parser.add_argument("--generator-label", default="same-agent", help="Label recorded in the final Markdown")
250
+ args = parser.parse_args()
251
+
252
+ template_path = Path(args.template) if args.template else default_template_path()
253
+ template_text = template_path.read_text(encoding="utf-8")
254
+
255
+ if args.from-agent_json:
256
+ if not args.output:
257
+ raise SystemExit("`--output` is required when using `--from-agent-json`.")
258
+ packet = load_json(args.from-agent_json)
259
+ ensure_agent_fields(packet)
260
+ markdown = render_final_markdown(template_text, packet, args.generator_label)
261
+ write_output(args.output, markdown)
262
+ return
263
+
264
+ if args.direct_md_output:
265
+ if not args.input:
266
+ raise SystemExit("`--input` is required when using `--direct-md-output`.")
267
+ if not args.paper_indexes:
268
+ raise SystemExit("`--paper-indexes` is required when using `--direct-md-output`.")
269
+ merged_payload = load_json(args.input)
270
+ selected_indexes = parse_index_list(args.paper_indexes)
271
+ markdown = render_direct_markdown(merged_payload, selected_indexes, template_text, args.generator_label)
272
+ write_output(args.direct_md_output, markdown)
273
+ return
274
+
275
+ if not args.input:
276
+ raise SystemExit("`--input` is required when preparing an agent packet.")
277
+ if not args.paper_indexes:
278
+ raise SystemExit("`--paper-indexes` is required when preparing an agent packet.")
279
+ if not args.agent_json_output and not args.review_md_output:
280
+ raise SystemExit("Provide at least one of `--agent-json-output` or `--review-md-output`.")
281
+
282
+ merged_payload = load_json(args.input)
283
+ selected_indexes = parse_index_list(args.paper_indexes)
284
+ packet = build_agent_packet(merged_payload, selected_indexes)
285
+
286
+ if args.agent_json_output:
287
+ json_output_path = Path(args.agent_json_output).expanduser().resolve()
288
+ json_output_path.parent.mkdir(parents=True, exist_ok=True)
289
+ json_output_path.write_text(json.dumps(packet, ensure_ascii=False, indent=2), encoding="utf-8")
290
+ print(json_output_path)
291
+
292
+ if args.review_md_output:
293
+ review_output_path = Path(args.review_md_output).expanduser().resolve()
294
+ review_output_path.parent.mkdir(parents=True, exist_ok=True)
295
+ review_output_path.write_text(render_agent_review_markdown(packet), encoding="utf-8")
296
+ print(review_output_path)
297
+
298
+
299
+ if __name__ == "__main__":
300
+ main()
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env python3
2
+ import json
3
+ import os
4
+ import re
5
+ import urllib.error
6
+ import urllib.request
7
+ from pathlib import Path
8
+
9
+
10
+ def parse_simple_yaml(path: Path):
11
+ data = {}
12
+ current_key = None
13
+ for raw_line in path.read_text(encoding="utf-8").splitlines():
14
+ line = raw_line.rstrip()
15
+ stripped = line.strip()
16
+ if not stripped or stripped.startswith("#"):
17
+ continue
18
+ if line.startswith(" - ") and current_key:
19
+ data.setdefault(current_key, []).append(stripped[2:].strip())
20
+ continue
21
+ if ":" in line and not line.startswith(" "):
22
+ key, value = line.split(":", 1)
23
+ current_key = key.strip()
24
+ value = value.strip()
25
+ if not value:
26
+ data[current_key] = []
27
+ else:
28
+ if value.isdigit():
29
+ data[current_key] = int(value)
30
+ else:
31
+ data[current_key] = value.strip('"').strip("'")
32
+ return data
33
+
34
+
35
+ def load_config(path_str: str | None):
36
+ if not path_str:
37
+ return {}
38
+ path = Path(path_str)
39
+ if not path.exists():
40
+ raise FileNotFoundError(f"Config file not found: {path}")
41
+ return parse_simple_yaml(path)
42
+
43
+
44
+ def normalize(text_value: str):
45
+ return re.sub(r"\s+", " ", (text_value or "")).strip().lower()
46
+
47
+
48
+ def load_json(path):
49
+ return json.loads(Path(path).read_text(encoding="utf-8-sig"))
50
+
51
+
52
+ def write_json(path, payload):
53
+ output_path = Path(path).expanduser().resolve()
54
+ output_path.parent.mkdir(parents=True, exist_ok=True)
55
+ output_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
56
+ return output_path
57
+
58
+
59
+ def responses_endpoint(base_url: str | None = None):
60
+ base = (base_url or os.environ.get("OPENAI_BASE_URL") or "https://api.openai.com/v1").rstrip("/")
61
+ return base if base.endswith("/responses") else base + "/responses"
62
+
63
+
64
+ def extract_output_text(response_payload: dict):
65
+ chunks = []
66
+ for item in response_payload.get("output", []):
67
+ if item.get("type") != "message":
68
+ continue
69
+ for part in item.get("content", []):
70
+ if part.get("type") == "output_text" and part.get("text"):
71
+ chunks.append(part["text"])
72
+ return "".join(chunks).strip()
73
+
74
+
75
+ def call_openai_structured(
76
+ *,
77
+ model: str,
78
+ instructions: str,
79
+ input_text: str,
80
+ schema_name: str,
81
+ schema: dict,
82
+ api_key: str | None = None,
83
+ timeout: int = 180,
84
+ reasoning_effort: str = "low",
85
+ verbosity: str = "low",
86
+ ):
87
+ api_key = api_key or os.environ.get("OPENAI_API_KEY")
88
+ if not api_key:
89
+ raise SystemExit("Missing OPENAI_API_KEY. Set it before running the LLM digest script.")
90
+
91
+ payload = {
92
+ "model": model,
93
+ "instructions": instructions,
94
+ "input": input_text,
95
+ "store": False,
96
+ "text": {
97
+ "format": {
98
+ "type": "json_schema",
99
+ "name": schema_name,
100
+ "strict": True,
101
+ "schema": schema,
102
+ },
103
+ "verbosity": verbosity,
104
+ },
105
+ "reasoning": {
106
+ "effort": reasoning_effort,
107
+ },
108
+ }
109
+
110
+ body = json.dumps(payload).encode("utf-8")
111
+ request = urllib.request.Request(
112
+ responses_endpoint(),
113
+ data=body,
114
+ headers={
115
+ "Authorization": f"Bearer {api_key}",
116
+ "Content-Type": "application/json",
117
+ },
118
+ method="POST",
119
+ )
120
+
121
+ try:
122
+ with urllib.request.urlopen(request, timeout=timeout) as response:
123
+ response_payload = json.loads(response.read().decode("utf-8"))
124
+ except urllib.error.HTTPError as exc:
125
+ details = exc.read().decode("utf-8", errors="replace")
126
+ raise SystemExit(f"OpenAI API request failed with HTTP {exc.code}: {details}") from exc
127
+ except urllib.error.URLError as exc:
128
+ raise SystemExit(f"OpenAI API request failed: {exc}") from exc
129
+
130
+ output_text = extract_output_text(response_payload)
131
+ if not output_text:
132
+ raise SystemExit("OpenAI API returned no text output.")
133
+
134
+ try:
135
+ return json.loads(output_text)
136
+ except json.JSONDecodeError as exc:
137
+ raise SystemExit(f"Model output was not valid JSON: {output_text}") from exc
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import hashlib
4
+ import json
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ SCRIPT_DIR = Path(__file__).resolve().parent
9
+ if str(SCRIPT_DIR) not in sys.path:
10
+ sys.path.insert(0, str(SCRIPT_DIR))
11
+
12
+ from common import normalize # noqa: E402
13
+
14
+
15
+ def make_key(item):
16
+ if item.get("doi"):
17
+ return "doi:" + normalize(item["doi"])
18
+ if item.get("pmid"):
19
+ return "pmid:" + normalize(item["pmid"])
20
+ if item.get("id") and item.get("source") == "arxiv":
21
+ return "arxiv:" + normalize(item["id"])
22
+ title_key = normalize(item.get("title", ""))
23
+ if title_key:
24
+ return "title:" + hashlib.md5(title_key.encode("utf-8")).hexdigest()
25
+ return "fallback:" + hashlib.md5(json.dumps(item, sort_keys=True, ensure_ascii=False).encode("utf-8")).hexdigest()
26
+
27
+
28
+ def choose_better(existing, candidate):
29
+ source_priority = {"pubmed": 3, "arxiv": 2}
30
+ existing_p = source_priority.get(existing.get("source"), 0)
31
+ candidate_p = source_priority.get(candidate.get("source"), 0)
32
+ if candidate_p > existing_p:
33
+ base, other = candidate.copy(), existing
34
+ else:
35
+ base, other = existing.copy(), candidate
36
+
37
+ for field in ["doi", "pmid", "pdf_url", "journal", "summary", "url", "published", "updated"]:
38
+ if not base.get(field) and other.get(field):
39
+ base[field] = other[field]
40
+ if len(base.get("authors", [])) < len(other.get("authors", [])):
41
+ base["authors"] = other.get("authors", [])
42
+ base_sources = []
43
+ for value in [existing.get("source"), candidate.get("source")]:
44
+ if value and value not in base_sources:
45
+ base_sources.append(value)
46
+ base["sources"] = base_sources
47
+ if len(base_sources) > 1:
48
+ base["source_label"] = "+".join(s.capitalize() for s in base_sources)
49
+ return base
50
+
51
+
52
+ def main():
53
+ parser = argparse.ArgumentParser(description="Merge multi-source paper results and deduplicate.")
54
+ parser.add_argument("inputs", nargs="+", help="Input JSON files")
55
+ parser.add_argument("--output", help="Write merged JSON to this file")
56
+ parser.add_argument("--pubmed-weight", type=float, default=1.25)
57
+ parser.add_argument("--arxiv-weight", type=float, default=1.0)
58
+ args = parser.parse_args()
59
+
60
+ merged = {}
61
+ queries = []
62
+ generated_at = []
63
+ source_counts = {}
64
+
65
+ for path_str in args.inputs:
66
+ payload = json.loads(Path(path_str).read_text(encoding="utf-8-sig"))
67
+ query = payload.get("query")
68
+ if query:
69
+ queries.append(query)
70
+ if payload.get("generated_at"):
71
+ generated_at.append(payload["generated_at"])
72
+ for item in payload.get("items", []):
73
+ key = make_key(item)
74
+ if key in merged:
75
+ merged[key] = choose_better(merged[key], item)
76
+ else:
77
+ merged[key] = item
78
+ src = item.get("source", "unknown")
79
+ source_counts[src] = source_counts.get(src, 0) + 1
80
+
81
+ items = []
82
+ for item in merged.values():
83
+ source = item.get("source")
84
+ weight = args.pubmed_weight if source == "pubmed" else args.arxiv_weight if source == "arxiv" else 1.0
85
+ item["weighted_score"] = round(item.get("relevance_score", item.get("keyword_score", 0)) * weight, 3)
86
+ item["source_weight"] = weight
87
+ items.append(item)
88
+
89
+ items.sort(key=lambda d: (d.get("weighted_score", 0), d.get("published", "")), reverse=True)
90
+ payload = {
91
+ "query": " || ".join(queries),
92
+ "generated_at": max(generated_at) if generated_at else "",
93
+ "count": len(items),
94
+ "source_counts": source_counts,
95
+ "items": items,
96
+ }
97
+
98
+ if args.output:
99
+ Path(args.output).write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
100
+ else:
101
+ json.dump(payload, sys.stdout, ensure_ascii=False, indent=2)
102
+ sys.stdout.write("\n")
103
+
104
+
105
+ if __name__ == "__main__":
106
+ main()