opencode-knowledge-work 0.0.0-stage → 0.1.0

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 (472) hide show
  1. package/LICENSE +212 -0
  2. package/README.md +124 -2
  3. package/content/bio-research/.mcp.json +48 -0
  4. package/content/bio-research/CONNECTORS.md +23 -0
  5. package/content/bio-research/LICENSE +202 -0
  6. package/content/bio-research/README.md +83 -0
  7. package/content/bio-research/skills/bio-research-instrument-data-to-allotrope/LICENSE.txt +201 -0
  8. package/content/bio-research/skills/bio-research-instrument-data-to-allotrope/SKILL.md +280 -0
  9. package/content/bio-research/skills/bio-research-instrument-data-to-allotrope/references/asm_schema_overview.md +226 -0
  10. package/content/bio-research/skills/bio-research-instrument-data-to-allotrope/references/field_classification_guide.md +503 -0
  11. package/content/bio-research/skills/bio-research-instrument-data-to-allotrope/references/flattening_guide.md +254 -0
  12. package/content/bio-research/skills/bio-research-instrument-data-to-allotrope/references/supported_instruments.md +151 -0
  13. package/content/bio-research/skills/bio-research-instrument-data-to-allotrope/requirements.txt +26 -0
  14. package/content/bio-research/skills/bio-research-instrument-data-to-allotrope/scripts/convert_to_asm.py +543 -0
  15. package/content/bio-research/skills/bio-research-instrument-data-to-allotrope/scripts/export_parser.py +481 -0
  16. package/content/bio-research/skills/bio-research-instrument-data-to-allotrope/scripts/flatten_asm.py +254 -0
  17. package/content/bio-research/skills/bio-research-instrument-data-to-allotrope/scripts/validate_asm.py +1102 -0
  18. package/content/bio-research/skills/bio-research-nextflow-development/LICENSE.txt +201 -0
  19. package/content/bio-research/skills/bio-research-nextflow-development/SKILL.md +290 -0
  20. package/content/bio-research/skills/bio-research-nextflow-development/references/geo-sra-acquisition.md +416 -0
  21. package/content/bio-research/skills/bio-research-nextflow-development/references/installation.md +96 -0
  22. package/content/bio-research/skills/bio-research-nextflow-development/references/pipelines/atacseq.md +138 -0
  23. package/content/bio-research/skills/bio-research-nextflow-development/references/pipelines/rnaseq.md +118 -0
  24. package/content/bio-research/skills/bio-research-nextflow-development/references/pipelines/sarek.md +145 -0
  25. package/content/bio-research/skills/bio-research-nextflow-development/references/troubleshooting.md +137 -0
  26. package/content/bio-research/skills/bio-research-nextflow-development/scripts/check_environment.py +452 -0
  27. package/content/bio-research/skills/bio-research-nextflow-development/scripts/config/genomes.yaml +148 -0
  28. package/content/bio-research/skills/bio-research-nextflow-development/scripts/config/pipelines/atacseq.yaml +187 -0
  29. package/content/bio-research/skills/bio-research-nextflow-development/scripts/config/pipelines/rnaseq.yaml +147 -0
  30. package/content/bio-research/skills/bio-research-nextflow-development/scripts/config/pipelines/sarek.yaml +233 -0
  31. package/content/bio-research/skills/bio-research-nextflow-development/scripts/detect_data_type.py +300 -0
  32. package/content/bio-research/skills/bio-research-nextflow-development/scripts/generate_samplesheet.py +455 -0
  33. package/content/bio-research/skills/bio-research-nextflow-development/scripts/manage_genomes.py +521 -0
  34. package/content/bio-research/skills/bio-research-nextflow-development/scripts/sra_geo_fetch.py +732 -0
  35. package/content/bio-research/skills/bio-research-nextflow-development/scripts/utils/__init__.py +69 -0
  36. package/content/bio-research/skills/bio-research-nextflow-development/scripts/utils/file_discovery.py +189 -0
  37. package/content/bio-research/skills/bio-research-nextflow-development/scripts/utils/ncbi_utils.py +808 -0
  38. package/content/bio-research/skills/bio-research-nextflow-development/scripts/utils/sample_inference.py +290 -0
  39. package/content/bio-research/skills/bio-research-nextflow-development/scripts/utils/validators.py +256 -0
  40. package/content/bio-research/skills/bio-research-scientific-problem-selection/LICENSE.txt +201 -0
  41. package/content/bio-research/skills/bio-research-scientific-problem-selection/SKILL.md +269 -0
  42. package/content/bio-research/skills/bio-research-scientific-problem-selection/references/01-intuition-pumps.md +264 -0
  43. package/content/bio-research/skills/bio-research-scientific-problem-selection/references/02-risk-assessment.md +323 -0
  44. package/content/bio-research/skills/bio-research-scientific-problem-selection/references/03-optimization-function.md +481 -0
  45. package/content/bio-research/skills/bio-research-scientific-problem-selection/references/04-parameter-strategy.md +396 -0
  46. package/content/bio-research/skills/bio-research-scientific-problem-selection/references/05-decision-tree.md +85 -0
  47. package/content/bio-research/skills/bio-research-scientific-problem-selection/references/06-adversity-planning.md +123 -0
  48. package/content/bio-research/skills/bio-research-scientific-problem-selection/references/07-problem-inversion.md +152 -0
  49. package/content/bio-research/skills/bio-research-scientific-problem-selection/references/08-integration-synthesis.md +173 -0
  50. package/content/bio-research/skills/bio-research-scientific-problem-selection/references/09-meta-framework.md +504 -0
  51. package/content/bio-research/skills/bio-research-scvi-tools/LICENSE.txt +201 -0
  52. package/content/bio-research/skills/bio-research-scvi-tools/SKILL.md +155 -0
  53. package/content/bio-research/skills/bio-research-scvi-tools/references/atac_peakvi.md +398 -0
  54. package/content/bio-research/skills/bio-research-scvi-tools/references/batch_correction_sysvi.md +417 -0
  55. package/content/bio-research/skills/bio-research-scvi-tools/references/citeseq_totalvi.md +420 -0
  56. package/content/bio-research/skills/bio-research-scvi-tools/references/data_preparation.md +286 -0
  57. package/content/bio-research/skills/bio-research-scvi-tools/references/environment_setup.md +254 -0
  58. package/content/bio-research/skills/bio-research-scvi-tools/references/label_transfer.md +373 -0
  59. package/content/bio-research/skills/bio-research-scvi-tools/references/multiome_multivi.md +384 -0
  60. package/content/bio-research/skills/bio-research-scvi-tools/references/rna_velocity_velovi.md +410 -0
  61. package/content/bio-research/skills/bio-research-scvi-tools/references/scarches_mapping.md +401 -0
  62. package/content/bio-research/skills/bio-research-scvi-tools/references/scrna_integration.md +429 -0
  63. package/content/bio-research/skills/bio-research-scvi-tools/references/spatial_deconvolution.md +438 -0
  64. package/content/bio-research/skills/bio-research-scvi-tools/references/troubleshooting.md +420 -0
  65. package/content/bio-research/skills/bio-research-scvi-tools/scripts/cluster_embed.py +212 -0
  66. package/content/bio-research/skills/bio-research-scvi-tools/scripts/differential_expression.py +220 -0
  67. package/content/bio-research/skills/bio-research-scvi-tools/scripts/integrate_datasets.py +237 -0
  68. package/content/bio-research/skills/bio-research-scvi-tools/scripts/model_utils.py +634 -0
  69. package/content/bio-research/skills/bio-research-scvi-tools/scripts/prepare_data.py +169 -0
  70. package/content/bio-research/skills/bio-research-scvi-tools/scripts/train_model.py +370 -0
  71. package/content/bio-research/skills/bio-research-scvi-tools/scripts/transfer_labels.py +224 -0
  72. package/content/bio-research/skills/bio-research-scvi-tools/scripts/validate_adata.py +397 -0
  73. package/content/bio-research/skills/bio-research-single-cell-rna-qc/LICENSE.txt +201 -0
  74. package/content/bio-research/skills/bio-research-single-cell-rna-qc/SKILL.md +175 -0
  75. package/content/bio-research/skills/bio-research-single-cell-rna-qc/references/scverse_qc_guidelines.md +186 -0
  76. package/content/bio-research/skills/bio-research-single-cell-rna-qc/scripts/qc_analysis.py +232 -0
  77. package/content/bio-research/skills/bio-research-single-cell-rna-qc/scripts/qc_core.py +233 -0
  78. package/content/bio-research/skills/bio-research-single-cell-rna-qc/scripts/qc_plotting.py +235 -0
  79. package/content/bio-research/skills/bio-research-start/SKILL.md +79 -0
  80. package/content/customer-support/.mcp.json +40 -0
  81. package/content/customer-support/CONNECTORS.md +19 -0
  82. package/content/customer-support/LICENSE +202 -0
  83. package/content/customer-support/README.md +140 -0
  84. package/content/customer-support/skills/customer-support-customer-escalation/SKILL.md +248 -0
  85. package/content/customer-support/skills/customer-support-customer-research/SKILL.md +252 -0
  86. package/content/customer-support/skills/customer-support-draft-response/SKILL.md +418 -0
  87. package/content/customer-support/skills/customer-support-kb-article/SKILL.md +351 -0
  88. package/content/customer-support/skills/customer-support-ticket-triage/SKILL.md +275 -0
  89. package/content/data/.mcp.json +36 -0
  90. package/content/data/CONNECTORS.md +18 -0
  91. package/content/data/LICENSE +202 -0
  92. package/content/data/README.md +121 -0
  93. package/content/data/skills/data-analyze/SKILL.md +119 -0
  94. package/content/data/skills/data-build-dashboard/SKILL.md +924 -0
  95. package/content/data/skills/data-create-viz/SKILL.md +154 -0
  96. package/content/data/skills/data-data-context-extractor/SKILL.md +227 -0
  97. package/content/data/skills/data-data-context-extractor/references/domain-template.md +147 -0
  98. package/content/data/skills/data-data-context-extractor/references/example-output.md +198 -0
  99. package/content/data/skills/data-data-context-extractor/references/skill-template.md +148 -0
  100. package/content/data/skills/data-data-context-extractor/references/sql-dialects.md +121 -0
  101. package/content/data/skills/data-data-context-extractor/scripts/package_data_skill.py +126 -0
  102. package/content/data/skills/data-data-visualization/SKILL.md +305 -0
  103. package/content/data/skills/data-explore-data/SKILL.md +325 -0
  104. package/content/data/skills/data-sql-queries/SKILL.md +428 -0
  105. package/content/data/skills/data-statistical-analysis/SKILL.md +245 -0
  106. package/content/data/skills/data-validate-data/SKILL.md +383 -0
  107. package/content/data/skills/data-write-query/SKILL.md +122 -0
  108. package/content/design/.mcp.json +44 -0
  109. package/content/design/CONNECTORS.md +18 -0
  110. package/content/design/README.md +117 -0
  111. package/content/design/skills/design-accessibility-review/SKILL.md +128 -0
  112. package/content/design/skills/design-design-critique/SKILL.md +118 -0
  113. package/content/design/skills/design-design-handoff/SKILL.md +131 -0
  114. package/content/design/skills/design-design-system/SKILL.md +190 -0
  115. package/content/design/skills/design-research-synthesis/SKILL.md +92 -0
  116. package/content/design/skills/design-user-research/SKILL.md +41 -0
  117. package/content/design/skills/design-ux-copy/SKILL.md +107 -0
  118. package/content/engineering/.mcp.json +48 -0
  119. package/content/engineering/CONNECTORS.md +19 -0
  120. package/content/engineering/README.md +136 -0
  121. package/content/engineering/skills/engineering-architecture/SKILL.md +85 -0
  122. package/content/engineering/skills/engineering-code-review/SKILL.md +118 -0
  123. package/content/engineering/skills/engineering-debug/SKILL.md +95 -0
  124. package/content/engineering/skills/engineering-deploy-checklist/SKILL.md +78 -0
  125. package/content/engineering/skills/engineering-documentation/SKILL.md +49 -0
  126. package/content/engineering/skills/engineering-incident-response/SKILL.md +158 -0
  127. package/content/engineering/skills/engineering-standup/SKILL.md +75 -0
  128. package/content/engineering/skills/engineering-system-design/SKILL.md +42 -0
  129. package/content/engineering/skills/engineering-tech-debt/SKILL.md +32 -0
  130. package/content/engineering/skills/engineering-testing-strategy/SKILL.md +33 -0
  131. package/content/enterprise-search/.mcp.json +36 -0
  132. package/content/enterprise-search/CONNECTORS.md +21 -0
  133. package/content/enterprise-search/LICENSE +202 -0
  134. package/content/enterprise-search/README.md +157 -0
  135. package/content/enterprise-search/skills/enterprise-search-digest/SKILL.md +173 -0
  136. package/content/enterprise-search/skills/enterprise-search-knowledge-synthesis/SKILL.md +258 -0
  137. package/content/enterprise-search/skills/enterprise-search-search/SKILL.md +178 -0
  138. package/content/enterprise-search/skills/enterprise-search-search-strategy/SKILL.md +222 -0
  139. package/content/enterprise-search/skills/enterprise-search-source-management/SKILL.md +173 -0
  140. package/content/finance/.mcp.json +32 -0
  141. package/content/finance/CONNECTORS.md +20 -0
  142. package/content/finance/LICENSE +202 -0
  143. package/content/finance/README.md +92 -0
  144. package/content/finance/skills/finance-audit-support/SKILL.md +374 -0
  145. package/content/finance/skills/finance-close-management/SKILL.md +221 -0
  146. package/content/finance/skills/finance-financial-statements/SKILL.md +335 -0
  147. package/content/finance/skills/finance-journal-entry/SKILL.md +131 -0
  148. package/content/finance/skills/finance-journal-entry-prep/SKILL.md +186 -0
  149. package/content/finance/skills/finance-reconciliation/SKILL.md +175 -0
  150. package/content/finance/skills/finance-sox-testing/SKILL.md +218 -0
  151. package/content/finance/skills/finance-variance-analysis/SKILL.md +266 -0
  152. package/content/human-resources/.mcp.json +28 -0
  153. package/content/human-resources/CONNECTORS.md +19 -0
  154. package/content/human-resources/README.md +138 -0
  155. package/content/human-resources/skills/human-resources-comp-analysis/SKILL.md +92 -0
  156. package/content/human-resources/skills/human-resources-draft-offer/SKILL.md +82 -0
  157. package/content/human-resources/skills/human-resources-interview-prep/SKILL.md +37 -0
  158. package/content/human-resources/skills/human-resources-onboarding/SKILL.md +104 -0
  159. package/content/human-resources/skills/human-resources-org-planning/SKILL.md +28 -0
  160. package/content/human-resources/skills/human-resources-people-report/SKILL.md +99 -0
  161. package/content/human-resources/skills/human-resources-performance-review/SKILL.md +149 -0
  162. package/content/human-resources/skills/human-resources-policy-lookup/SKILL.md +94 -0
  163. package/content/human-resources/skills/human-resources-recruiting-pipeline/SKILL.md +31 -0
  164. package/content/legal/.mcp.json +36 -0
  165. package/content/legal/CONNECTORS.md +21 -0
  166. package/content/legal/LICENSE +202 -0
  167. package/content/legal/README.md +234 -0
  168. package/content/legal/skills/legal-brief/SKILL.md +208 -0
  169. package/content/legal/skills/legal-compliance-check/SKILL.md +274 -0
  170. package/content/legal/skills/legal-legal-response/SKILL.md +457 -0
  171. package/content/legal/skills/legal-legal-risk-assessment/SKILL.md +265 -0
  172. package/content/legal/skills/legal-meeting-briefing/SKILL.md +220 -0
  173. package/content/legal/skills/legal-review-contract/SKILL.md +358 -0
  174. package/content/legal/skills/legal-signature-request/SKILL.md +104 -0
  175. package/content/legal/skills/legal-triage-nda/SKILL.md +262 -0
  176. package/content/legal/skills/legal-vendor-check/SKILL.md +159 -0
  177. package/content/manifest.json +86 -0
  178. package/content/marketing/.mcp.json +60 -0
  179. package/content/marketing/CONNECTORS.md +20 -0
  180. package/content/marketing/LICENSE +202 -0
  181. package/content/marketing/README.md +90 -0
  182. package/content/marketing/skills/marketing-brand-review/SKILL.md +276 -0
  183. package/content/marketing/skills/marketing-campaign-plan/SKILL.md +308 -0
  184. package/content/marketing/skills/marketing-competitive-brief/SKILL.md +331 -0
  185. package/content/marketing/skills/marketing-content-creation/SKILL.md +157 -0
  186. package/content/marketing/skills/marketing-draft-content/SKILL.md +117 -0
  187. package/content/marketing/skills/marketing-email-sequence/SKILL.md +220 -0
  188. package/content/marketing/skills/marketing-performance-report/SKILL.md +392 -0
  189. package/content/marketing/skills/marketing-seo-audit/SKILL.md +190 -0
  190. package/content/operations/.mcp.json +32 -0
  191. package/content/operations/CONNECTORS.md +20 -0
  192. package/content/operations/README.md +135 -0
  193. package/content/operations/skills/operations-capacity-plan/SKILL.md +113 -0
  194. package/content/operations/skills/operations-change-request/SKILL.md +121 -0
  195. package/content/operations/skills/operations-compliance-tracking/SKILL.md +44 -0
  196. package/content/operations/skills/operations-process-doc/SKILL.md +82 -0
  197. package/content/operations/skills/operations-process-optimization/SKILL.md +39 -0
  198. package/content/operations/skills/operations-risk-assessment/SKILL.md +40 -0
  199. package/content/operations/skills/operations-runbook/SKILL.md +87 -0
  200. package/content/operations/skills/operations-status-report/SKILL.md +77 -0
  201. package/content/operations/skills/operations-vendor-review/SKILL.md +104 -0
  202. package/content/pdf-viewer/.mcp.json +8 -0
  203. package/content/pdf-viewer/CONNECTORS.md +15 -0
  204. package/content/pdf-viewer/LICENSE +202 -0
  205. package/content/pdf-viewer/README.md +57 -0
  206. package/content/pdf-viewer/commands/annotate.md +51 -0
  207. package/content/pdf-viewer/commands/fill-form.md +73 -0
  208. package/content/pdf-viewer/commands/open.md +34 -0
  209. package/content/pdf-viewer/commands/sign.md +54 -0
  210. package/content/pdf-viewer/skills/pdf-viewer-view-pdf/SKILL.md +147 -0
  211. package/content/product-management/.mcp.json +72 -0
  212. package/content/product-management/CONNECTORS.md +22 -0
  213. package/content/product-management/LICENSE +202 -0
  214. package/content/product-management/README.md +118 -0
  215. package/content/product-management/commands/brainstorm.md +120 -0
  216. package/content/product-management/skills/product-management-competitive-brief/SKILL.md +302 -0
  217. package/content/product-management/skills/product-management-metrics-review/SKILL.md +388 -0
  218. package/content/product-management/skills/product-management-product-brainstorming/SKILL.md +273 -0
  219. package/content/product-management/skills/product-management-roadmap-update/SKILL.md +266 -0
  220. package/content/product-management/skills/product-management-sprint-planning/SKILL.md +94 -0
  221. package/content/product-management/skills/product-management-stakeholder-update/SKILL.md +352 -0
  222. package/content/product-management/skills/product-management-synthesize-research/SKILL.md +312 -0
  223. package/content/product-management/skills/product-management-write-spec/SKILL.md +250 -0
  224. package/content/productivity/.mcp.json +44 -0
  225. package/content/productivity/CONNECTORS.md +18 -0
  226. package/content/productivity/LICENSE +202 -0
  227. package/content/productivity/README.md +97 -0
  228. package/content/productivity/skills/dashboard.html +3111 -0
  229. package/content/productivity/skills/productivity-memory-management/SKILL.md +323 -0
  230. package/content/productivity/skills/productivity-start/SKILL.md +158 -0
  231. package/content/productivity/skills/productivity-task-management/SKILL.md +91 -0
  232. package/content/productivity/skills/productivity-update/SKILL.md +168 -0
  233. package/content/sales/.mcp.json +100 -0
  234. package/content/sales/CONNECTORS.md +36 -0
  235. package/content/sales/LICENSE +202 -0
  236. package/content/sales/README.md +148 -0
  237. package/content/sales/skills/sales-account-context/SKILL.md +94 -0
  238. package/content/sales/skills/sales-account-plan/SKILL.md +95 -0
  239. package/content/sales/skills/sales-account-research/SKILL.md +99 -0
  240. package/content/sales/skills/sales-account-tiering/SKILL.md +101 -0
  241. package/content/sales/skills/sales-call-prep/SKILL.md +111 -0
  242. package/content/sales/skills/sales-call-summary/SKILL.md +111 -0
  243. package/content/sales/skills/sales-close-plan/SKILL.md +106 -0
  244. package/content/sales/skills/sales-competitive-intelligence/SKILL.md +102 -0
  245. package/content/sales/skills/sales-create-an-asset/SKILL.md +105 -0
  246. package/content/sales/skills/sales-crm-hygiene-check/SKILL.md +103 -0
  247. package/content/sales/skills/sales-customer-health/SKILL.md +108 -0
  248. package/content/sales/skills/sales-customer-voice/SKILL.md +94 -0
  249. package/content/sales/skills/sales-daily-briefing/SKILL.md +61 -0
  250. package/content/sales/skills/sales-deal-advance-gap/SKILL.md +102 -0
  251. package/content/sales/skills/sales-deal-review/SKILL.md +105 -0
  252. package/content/sales/skills/sales-deal-signals/SKILL.md +96 -0
  253. package/content/sales/skills/sales-deal-slip-scenario/SKILL.md +101 -0
  254. package/content/sales/skills/sales-draft-outreach/SKILL.md +116 -0
  255. package/content/sales/skills/sales-end-of-day/SKILL.md +114 -0
  256. package/content/sales/skills/sales-expansion-whitespace/SKILL.md +97 -0
  257. package/content/sales/skills/sales-forecast/SKILL.md +100 -0
  258. package/content/sales/skills/sales-handle-objection/SKILL.md +93 -0
  259. package/content/sales/skills/sales-inbox-sweep/SKILL.md +114 -0
  260. package/content/sales/skills/sales-lead-triage/SKILL.md +105 -0
  261. package/content/sales/skills/sales-log-activity/SKILL.md +111 -0
  262. package/content/sales/skills/sales-pipeline-review/SKILL.md +97 -0
  263. package/content/sales/skills/sales-renewal-radar/SKILL.md +95 -0
  264. package/content/sales/skills/sales-rep-context/SKILL.md +90 -0
  265. package/content/sales/skills/sales-route-lead/SKILL.md +111 -0
  266. package/content/sales/skills/sales-schedule-meeting/SKILL.md +99 -0
  267. package/content/sales/skills/sales-setup/SKILL.md +195 -0
  268. package/content/sales/skills/sales-stakeholder-map/SKILL.md +107 -0
  269. package/content/sales/skills/sales-team-pipeline/SKILL.md +105 -0
  270. package/content/sales/skills/sales-update-opportunity/SKILL.md +111 -0
  271. package/content/sales/skills/sales-weekly-wrap/SKILL.md +90 -0
  272. package/content/sales/skills/sales-win-loss-review/SKILL.md +88 -0
  273. package/content/small-business/.mcp.json +148 -0
  274. package/content/small-business/README.md +117 -0
  275. package/content/small-business/skills/small-business-ad-manager/SKILL.md +128 -0
  276. package/content/small-business/skills/small-business-ad-manager/reference/ad_copy.md +112 -0
  277. package/content/small-business/skills/small-business-ad-manager/reference/change_playbook.md +127 -0
  278. package/content/small-business/skills/small-business-ad-manager/reference/data_sources.md +100 -0
  279. package/content/small-business/skills/small-business-ad-manager/reference/diagnostics.md +89 -0
  280. package/content/small-business/skills/small-business-ad-manager/reference/gotchas.md +181 -0
  281. package/content/small-business/skills/small-business-ap-processor/SKILL.md +158 -0
  282. package/content/small-business/skills/small-business-ap-processor/reference/coding_rules.md +70 -0
  283. package/content/small-business/skills/small-business-ap-processor/reference/gotchas.md +131 -0
  284. package/content/small-business/skills/small-business-ap-processor/reference/intake_and_extraction.md +82 -0
  285. package/content/small-business/skills/small-business-ap-processor/reference/matching_and_exceptions.md +80 -0
  286. package/content/small-business/skills/small-business-ap-processor/reference/payment_run.md +77 -0
  287. package/content/small-business/skills/small-business-brand-style/SKILL.md +106 -0
  288. package/content/small-business/skills/small-business-build-agent/SKILL.md +105 -0
  289. package/content/small-business/skills/small-business-build-agent/reference/capture.md +133 -0
  290. package/content/small-business/skills/small-business-build-agent/reference/gotchas.md +204 -0
  291. package/content/small-business/skills/small-business-build-agent/reference/skill_authoring.md +165 -0
  292. package/content/small-business/skills/small-business-build-connector/SKILL.md +102 -0
  293. package/content/small-business/skills/small-business-build-connector/reference/discovery.md +120 -0
  294. package/content/small-business/skills/small-business-build-connector/reference/gotchas.md +237 -0
  295. package/content/small-business/skills/small-business-build-connector/reference/paths.md +106 -0
  296. package/content/small-business/skills/small-business-build-connector/reference/use-zapier.md +128 -0
  297. package/content/small-business/skills/small-business-business-pulse/SKILL.md +181 -0
  298. package/content/small-business/skills/small-business-business-pulse/reference/data_sources.md +173 -0
  299. package/content/small-business/skills/small-business-business-pulse/reference/gotchas.md +103 -0
  300. package/content/small-business/skills/small-business-business-pulse/reference/output_template.md +109 -0
  301. package/content/small-business/skills/small-business-business-pulse/reference/thresholds.md +71 -0
  302. package/content/small-business/skills/small-business-business-pulse/reference/v2_sources.md +63 -0
  303. package/content/small-business/skills/small-business-call-list/SKILL.md +82 -0
  304. package/content/small-business/skills/small-business-canva-creator/SKILL.md +448 -0
  305. package/content/small-business/skills/small-business-canva-creator/reference/canva-api.md +208 -0
  306. package/content/small-business/skills/small-business-canva-creator/reference/examples/boutique-brief-campaign.md +181 -0
  307. package/content/small-business/skills/small-business-canva-creator/reference/gotchas.md +380 -0
  308. package/content/small-business/skills/small-business-canva-creator/reference/hubspot-staging.md +137 -0
  309. package/content/small-business/skills/small-business-cash-flow-snapshot/SKILL.md +251 -0
  310. package/content/small-business/skills/small-business-cash-flow-snapshot/reference/examples/worked-example.md +92 -0
  311. package/content/small-business/skills/small-business-cash-flow-snapshot/reference/gotchas.md +62 -0
  312. package/content/small-business/skills/small-business-cash-flow-snapshot/reference/v2_sources.md +84 -0
  313. package/content/small-business/skills/small-business-close-month/SKILL.md +86 -0
  314. package/content/small-business/skills/small-business-content-strategy/SKILL.md +152 -0
  315. package/content/small-business/skills/small-business-content-strategy/reference/examples/retail-boutique-example.md +71 -0
  316. package/content/small-business/skills/small-business-content-strategy/reference/gotchas.md +104 -0
  317. package/content/small-business/skills/small-business-content-strategy/reference/v2_sources.md +40 -0
  318. package/content/small-business/skills/small-business-contract-review/SKILL.md +158 -0
  319. package/content/small-business/skills/small-business-contract-review/reference/docusign-fetch.md +32 -0
  320. package/content/small-business/skills/small-business-contract-review/reference/examples/flagged-summary-saas.md +97 -0
  321. package/content/small-business/skills/small-business-contract-review/reference/gmail-fetch.md +30 -0
  322. package/content/small-business/skills/small-business-contract-review/reference/gotchas.md +105 -0
  323. package/content/small-business/skills/small-business-contract-review/reference/m365-fetch.md +60 -0
  324. package/content/small-business/skills/small-business-crm-autopilot/SKILL.md +162 -0
  325. package/content/small-business/skills/small-business-crm-autopilot/reference/cleanup-checklist.md +82 -0
  326. package/content/small-business/skills/small-business-crm-autopilot/reference/examples/cleanup-deal.md +83 -0
  327. package/content/small-business/skills/small-business-crm-autopilot/reference/examples/log-call-happy-path.md +68 -0
  328. package/content/small-business/skills/small-business-crm-autopilot/reference/examples/log-email-happy-path.md +50 -0
  329. package/content/small-business/skills/small-business-crm-autopilot/reference/gotchas.md +110 -0
  330. package/content/small-business/skills/small-business-crm-autopilot/reference/hubspot-fields.md +62 -0
  331. package/content/small-business/skills/small-business-crm-autopilot/reference/lightweight_crm.md +125 -0
  332. package/content/small-business/skills/small-business-crm-autopilot/reference/salesforce-fields.md +122 -0
  333. package/content/small-business/skills/small-business-crm-autopilot/reference/standing_mode.md +144 -0
  334. package/content/small-business/skills/small-business-grant-rfp-writer/SKILL.md +162 -0
  335. package/content/small-business/skills/small-business-grant-rfp-writer/reference/drafting.md +129 -0
  336. package/content/small-business/skills/small-business-grant-rfp-writer/reference/go_no_go.md +125 -0
  337. package/content/small-business/skills/small-business-grant-rfp-writer/reference/gotchas.md +180 -0
  338. package/content/small-business/skills/small-business-grant-rfp-writer/reference/opportunity_sources.md +90 -0
  339. package/content/small-business/skills/small-business-grant-rfp-writer/reference/org_profile.md +81 -0
  340. package/content/small-business/skills/small-business-grow-pipeline/SKILL.md +92 -0
  341. package/content/small-business/skills/small-business-growth-pulse/SKILL.md +143 -0
  342. package/content/small-business/skills/small-business-growth-pulse/reference/data_sources.md +113 -0
  343. package/content/small-business/skills/small-business-growth-pulse/reference/gotchas.md +175 -0
  344. package/content/small-business/skills/small-business-growth-pulse/reference/output_template.md +122 -0
  345. package/content/small-business/skills/small-business-growth-pulse/reference/thresholds.md +110 -0
  346. package/content/small-business/skills/small-business-hiring-screener/SKILL.md +137 -0
  347. package/content/small-business/skills/small-business-hiring-screener/reference/candidate_comms.md +132 -0
  348. package/content/small-business/skills/small-business-hiring-screener/reference/fair_screening.md +103 -0
  349. package/content/small-business/skills/small-business-hiring-screener/reference/gotchas.md +183 -0
  350. package/content/small-business/skills/small-business-hiring-screener/reference/onboarding.md +82 -0
  351. package/content/small-business/skills/small-business-hiring-screener/reference/rubric.md +122 -0
  352. package/content/small-business/skills/small-business-inbox-manager/SKILL.md +130 -0
  353. package/content/small-business/skills/small-business-inbox-manager/reference/digest_format.md +110 -0
  354. package/content/small-business/skills/small-business-inbox-manager/reference/gotchas.md +220 -0
  355. package/content/small-business/skills/small-business-inbox-manager/reference/handoffs.md +77 -0
  356. package/content/small-business/skills/small-business-inbox-manager/reference/reply_drafting.md +116 -0
  357. package/content/small-business/skills/small-business-inbox-manager/reference/triage_rules.md +72 -0
  358. package/content/small-business/skills/small-business-inventory-planner/SKILL.md +126 -0
  359. package/content/small-business/skills/small-business-inventory-planner/reference/data_sources.md +94 -0
  360. package/content/small-business/skills/small-business-inventory-planner/reference/gotchas.md +153 -0
  361. package/content/small-business/skills/small-business-inventory-planner/reference/po_drafting.md +92 -0
  362. package/content/small-business/skills/small-business-inventory-planner/reference/seasonality.md +112 -0
  363. package/content/small-business/skills/small-business-inventory-planner/reference/velocity_and_reorder.md +154 -0
  364. package/content/small-business/skills/small-business-invoice-chase/SKILL.md +131 -0
  365. package/content/small-business/skills/small-business-invoice-chase/reference/examples/firm-reminder.md +28 -0
  366. package/content/small-business/skills/small-business-invoice-chase/reference/examples/gentle-reminder.md +27 -0
  367. package/content/small-business/skills/small-business-invoice-chase/reference/gotchas.md +75 -0
  368. package/content/small-business/skills/small-business-invoice-chase/reference/tone-matching.md +44 -0
  369. package/content/small-business/skills/small-business-invoice-chase/reference/v2_sources.md +78 -0
  370. package/content/small-business/skills/small-business-job-post-builder/SKILL.md +204 -0
  371. package/content/small-business/skills/small-business-job-post-builder/reference/docusign-routing.md +63 -0
  372. package/content/small-business/skills/small-business-job-post-builder/reference/examples/worked-example.md +110 -0
  373. package/content/small-business/skills/small-business-job-post-builder/reference/existing-document-merge.md +58 -0
  374. package/content/small-business/skills/small-business-job-post-builder/reference/gotchas.md +68 -0
  375. package/content/small-business/skills/small-business-job-post-builder/reference/interview-guide-structure.md +93 -0
  376. package/content/small-business/skills/small-business-job-post-builder/reference/job-post-structure.md +62 -0
  377. package/content/small-business/skills/small-business-job-post-builder/reference/offer-letter-template.md +145 -0
  378. package/content/small-business/skills/small-business-job-post-builder/reference/role-intake.md +73 -0
  379. package/content/small-business/skills/small-business-lead-finder/SKILL.md +119 -0
  380. package/content/small-business/skills/small-business-lead-finder/reference/gotchas.md +152 -0
  381. package/content/small-business/skills/small-business-lead-finder/reference/icp_method.md +97 -0
  382. package/content/small-business/skills/small-business-lead-finder/reference/output_template.md +124 -0
  383. package/content/small-business/skills/small-business-lead-finder/reference/scoring.md +92 -0
  384. package/content/small-business/skills/small-business-lead-finder/reference/sourcing.md +78 -0
  385. package/content/small-business/skills/small-business-lead-triage/SKILL.md +81 -0
  386. package/content/small-business/skills/small-business-lead-triage/reference/examples/happy-path-triage.md +68 -0
  387. package/content/small-business/skills/small-business-lead-triage/reference/gotchas.md +76 -0
  388. package/content/small-business/skills/small-business-lead-triage/reference/hubspot-scoring.md +169 -0
  389. package/content/small-business/skills/small-business-marketing-monday/SKILL.md +108 -0
  390. package/content/small-business/skills/small-business-monday-brief/SKILL.md +97 -0
  391. package/content/small-business/skills/small-business-month-end-prep/SKILL.md +331 -0
  392. package/content/small-business/skills/small-business-month-end-prep/reference/close-packet-format.md +127 -0
  393. package/content/small-business/skills/small-business-month-end-prep/reference/examples/pl-narrative.md +52 -0
  394. package/content/small-business/skills/small-business-month-end-prep/reference/gotchas.md +113 -0
  395. package/content/small-business/skills/small-business-month-end-prep/reference/paypal-settlements.md +110 -0
  396. package/content/small-business/skills/small-business-month-end-prep/reference/quickbooks-reconcile.md +71 -0
  397. package/content/small-business/skills/small-business-month-end-prep/reference/v2_sources.md +125 -0
  398. package/content/small-business/skills/small-business-month-end-prep/reference/xero-reconcile.md +175 -0
  399. package/content/small-business/skills/small-business-month-end-prep/reference/zoho-books-reconcile.md +168 -0
  400. package/content/small-business/skills/small-business-outreach-composer/SKILL.md +144 -0
  401. package/content/small-business/skills/small-business-outreach-composer/reference/gotchas.md +213 -0
  402. package/content/small-business/skills/small-business-outreach-composer/reference/output_template.md +147 -0
  403. package/content/small-business/skills/small-business-outreach-composer/reference/sequence_patterns.md +99 -0
  404. package/content/small-business/skills/small-business-outreach-composer/reference/slop_test.md +164 -0
  405. package/content/small-business/skills/small-business-pay-the-bills/SKILL.md +101 -0
  406. package/content/small-business/skills/small-business-payroll-prep/SKILL.md +139 -0
  407. package/content/small-business/skills/small-business-payroll-prep/reference/anomaly_rules.md +99 -0
  408. package/content/small-business/skills/small-business-payroll-prep/reference/books_sync.md +72 -0
  409. package/content/small-business/skills/small-business-payroll-prep/reference/gotchas.md +153 -0
  410. package/content/small-business/skills/small-business-payroll-prep/reference/run_sheet_format.md +90 -0
  411. package/content/small-business/skills/small-business-payroll-prep/reference/timesheet_intake.md +118 -0
  412. package/content/small-business/skills/small-business-plan-payroll/SKILL.md +88 -0
  413. package/content/small-business/skills/small-business-proposal-builder/SKILL.md +168 -0
  414. package/content/small-business/skills/small-business-proposal-builder/reference/discovery_extraction.md +138 -0
  415. package/content/small-business/skills/small-business-proposal-builder/reference/gotchas.md +184 -0
  416. package/content/small-business/skills/small-business-proposal-builder/reference/output_template.md +128 -0
  417. package/content/small-business/skills/small-business-proposal-builder/reference/pricing_method.md +125 -0
  418. package/content/small-business/skills/small-business-proposal-builder/reference/proposal_structure.md +120 -0
  419. package/content/small-business/skills/small-business-reactivate/SKILL.md +109 -0
  420. package/content/small-business/skills/small-business-report-builder/SKILL.md +142 -0
  421. package/content/small-business/skills/small-business-report-builder/reference/data_sources.md +168 -0
  422. package/content/small-business/skills/small-business-report-builder/reference/gotchas.md +155 -0
  423. package/content/small-business/skills/small-business-report-builder/reference/output_template.md +109 -0
  424. package/content/small-business/skills/small-business-report-builder/reference/report_spec.md +109 -0
  425. package/content/small-business/skills/small-business-report-builder/reference/saved_reports.md +50 -0
  426. package/content/small-business/skills/small-business-report-pack/SKILL.md +83 -0
  427. package/content/small-business/skills/small-business-restock/SKILL.md +88 -0
  428. package/content/small-business/skills/small-business-review-reputation/SKILL.md +130 -0
  429. package/content/small-business/skills/small-business-review-reputation/reference/churn-signals.md +110 -0
  430. package/content/small-business/skills/small-business-review-reputation/reference/examples/example-report.md +150 -0
  431. package/content/small-business/skills/small-business-review-reputation/reference/gotchas.md +164 -0
  432. package/content/small-business/skills/small-business-review-reputation/reference/response-patterns.md +123 -0
  433. package/content/small-business/skills/small-business-review-reputation/reference/sources.md +116 -0
  434. package/content/small-business/skills/small-business-seo-ai-visibility/SKILL.md +143 -0
  435. package/content/small-business/skills/small-business-seo-ai-visibility/reference/ai_visibility.md +173 -0
  436. package/content/small-business/skills/small-business-seo-ai-visibility/reference/audit_checklist.md +134 -0
  437. package/content/small-business/skills/small-business-seo-ai-visibility/reference/catalog_refresh.md +126 -0
  438. package/content/small-business/skills/small-business-seo-ai-visibility/reference/content_rewrite.md +113 -0
  439. package/content/small-business/skills/small-business-seo-ai-visibility/reference/gotchas.md +211 -0
  440. package/content/small-business/skills/small-business-smb-onboard/SKILL.md +82 -0
  441. package/content/small-business/skills/small-business-smb-onboard/reference/examples/happy-path.md +190 -0
  442. package/content/small-business/skills/small-business-smb-onboard/reference/gotchas.md +111 -0
  443. package/content/small-business/skills/small-business-smb-onboard/reference/onboard-checklist.md +99 -0
  444. package/content/small-business/skills/small-business-smb-onboard/reference/website-research.md +82 -0
  445. package/content/small-business/skills/small-business-smb-router/SKILL.md +216 -0
  446. package/content/small-business/skills/small-business-smb-router/reference/connector-map.md +96 -0
  447. package/content/small-business/skills/small-business-social-content-engine/SKILL.md +162 -0
  448. package/content/small-business/skills/small-business-social-content-engine/reference/calendar.md +116 -0
  449. package/content/small-business/skills/small-business-social-content-engine/reference/canva-api.md +208 -0
  450. package/content/small-business/skills/small-business-social-content-engine/reference/examples/okonkwo-campaign.md +190 -0
  451. package/content/small-business/skills/small-business-social-content-engine/reference/gotchas.md +447 -0
  452. package/content/small-business/skills/small-business-social-content-engine/reference/publishing.md +166 -0
  453. package/content/small-business/skills/small-business-social-content-engine/reference/shopify-assets.md +77 -0
  454. package/content/small-business/skills/small-business-speed-to-lead/SKILL.md +172 -0
  455. package/content/small-business/skills/small-business-speed-to-lead/reference/approval.md +61 -0
  456. package/content/small-business/skills/small-business-speed-to-lead/reference/gotchas.md +167 -0
  457. package/content/small-business/skills/small-business-speed-to-lead/reference/qualification.md +153 -0
  458. package/content/small-business/skills/small-business-speed-to-lead/reference/response_patterns.md +184 -0
  459. package/content/small-business/skills/small-business-tax-prep/SKILL.md +85 -0
  460. package/content/small-business/skills/small-business-tax-season-organizer/SKILL.md +210 -0
  461. package/content/small-business/skills/small-business-tax-season-organizer/reference/calculation-assumptions.md +177 -0
  462. package/content/small-business/skills/small-business-tax-season-organizer/reference/connector-queries.md +267 -0
  463. package/content/small-business/skills/small-business-tax-season-organizer/reference/examples/quarterly-estimate.md +92 -0
  464. package/content/small-business/skills/small-business-tax-season-organizer/reference/examples/year-end-1099.md +102 -0
  465. package/content/small-business/skills/small-business-tax-season-organizer/reference/gotchas.md +104 -0
  466. package/content/small-business/skills/small-business-tax-season-organizer/reference/output-formats.md +68 -0
  467. package/content/small-business/skills/small-business-ticket-deflector/SKILL.md +164 -0
  468. package/content/small-business/skills/small-business-ticket-deflector/reference/examples/deny-refund-request.md +98 -0
  469. package/content/small-business/skills/small-business-ticket-deflector/reference/examples/respond-refund-request.md +77 -0
  470. package/content/small-business/skills/small-business-ticket-deflector/reference/gotchas.md +57 -0
  471. package/index.ts +84 -0
  472. package/package.json +30 -3
@@ -0,0 +1,808 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ NCBI Utilities for GEO/SRA Data Access
4
+ ======================================
5
+ Shared utilities for fetching metadata and downloading data from NCBI services.
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ import re
11
+ import shutil
12
+ import time
13
+ from pathlib import Path
14
+ from typing import Dict, List, Optional, Tuple
15
+ from urllib.request import Request, urlopen
16
+ from urllib.error import URLError, HTTPError
17
+
18
+ # Set up logging
19
+ logging.basicConfig(
20
+ level=logging.INFO,
21
+ format='%(asctime)s - %(levelname)s - %(message)s'
22
+ )
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # NCBI rate limiting - track last request time
26
+ _last_ncbi_request_time = 0.0
27
+ _NCBI_MIN_DELAY = 0.34 # 3 requests per second max without API key
28
+
29
+
30
+ def _rate_limit_ncbi():
31
+ """Enforce NCBI rate limit of 3 requests/second."""
32
+ global _last_ncbi_request_time
33
+ current_time = time.time()
34
+ elapsed = current_time - _last_ncbi_request_time
35
+ if elapsed < _NCBI_MIN_DELAY:
36
+ time.sleep(_NCBI_MIN_DELAY - elapsed)
37
+ _last_ncbi_request_time = time.time()
38
+
39
+
40
+ # Try to import requests for better HTTP handling
41
+ try:
42
+ import requests
43
+ HAS_REQUESTS = True
44
+ except ImportError:
45
+ HAS_REQUESTS = False
46
+ logger.debug("requests not installed - using urllib fallback")
47
+
48
+
49
+ def check_network_access() -> Tuple[bool, str]:
50
+ """
51
+ Check if NCBI/ENA servers are accessible.
52
+
53
+ Returns:
54
+ Tuple of (success, message)
55
+ """
56
+ test_urls = [
57
+ ("NCBI Entrez", "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/einfo.fcgi"),
58
+ ("NCBI FTP", "https://ftp.ncbi.nlm.nih.gov/"),
59
+ ("ENA API", "https://www.ebi.ac.uk/ena/portal/api/"),
60
+ ]
61
+
62
+ results = []
63
+ for name, url in test_urls:
64
+ try:
65
+ if HAS_REQUESTS:
66
+ # Use GET instead of HEAD - NCBI Entrez returns 405 for HEAD
67
+ response = requests.get(url, timeout=10)
68
+ success = response.status_code < 400
69
+ else:
70
+ req = Request(url, headers={'User-Agent': 'geo-sra-skill/1.0'})
71
+ with urlopen(req, timeout=10) as response:
72
+ success = True
73
+ results.append((name, success, None))
74
+ except Exception as e:
75
+ results.append((name, False, str(e)))
76
+
77
+ all_success = all(r[1] for r in results)
78
+
79
+ msg_parts = []
80
+ for name, success, error in results:
81
+ status = "✓" if success else "✗"
82
+ msg_parts.append(f" {status} {name}: {'OK' if success else error or 'Failed'}")
83
+
84
+ return all_success, "\n".join(msg_parts)
85
+
86
+
87
+ def fetch_geo_metadata(geo_id: str) -> Optional[Dict]:
88
+ """
89
+ Fetch GEO study metadata using NCBI Entrez E-utilities.
90
+
91
+ Args:
92
+ geo_id: GEO accession (e.g., 'GSE110004')
93
+
94
+ Returns:
95
+ Dict with study metadata or None if failed
96
+ """
97
+ try:
98
+ # Use esearch to get GEO UID
99
+ search_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=gds&term={geo_id}[Accession]&retmode=json"
100
+
101
+ _rate_limit_ncbi()
102
+ if HAS_REQUESTS:
103
+ response = requests.get(search_url, timeout=30)
104
+ data = response.json()
105
+ else:
106
+ with urlopen(search_url, timeout=30) as response:
107
+ data = json.loads(response.read().decode())
108
+
109
+ id_list = data.get('esearchresult', {}).get('idlist', [])
110
+ if not id_list:
111
+ logger.warning(f"No GEO entry found for {geo_id}")
112
+ return None
113
+
114
+ # Use esummary to get metadata
115
+ uid = id_list[0]
116
+ summary_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=gds&id={uid}&retmode=json"
117
+
118
+ _rate_limit_ncbi()
119
+ if HAS_REQUESTS:
120
+ response = requests.get(summary_url, timeout=30)
121
+ data = response.json()
122
+ else:
123
+ with urlopen(summary_url, timeout=30) as response:
124
+ data = json.loads(response.read().decode())
125
+
126
+ result = data.get('result', {}).get(uid, {})
127
+
128
+ return {
129
+ 'geo_id': geo_id,
130
+ 'title': result.get('title', 'N/A'),
131
+ 'summary': result.get('summary', 'N/A'),
132
+ 'organism': result.get('taxon', 'N/A'),
133
+ 'n_samples': result.get('n_samples', 0),
134
+ 'gpl': result.get('gpl', 'N/A'),
135
+ 'entrytype': result.get('entrytype', 'N/A'),
136
+ 'pubmed_ids': result.get('pubmedids', []),
137
+ }
138
+
139
+ except Exception as e:
140
+ logger.error(f"Error fetching GEO metadata for {geo_id}: {e}")
141
+ return None
142
+
143
+
144
+ def fetch_sra_study_accession(geo_id: str) -> Optional[str]:
145
+ """
146
+ Get the SRA study accession (SRPxxxxxx) for a GEO accession.
147
+
148
+ Args:
149
+ geo_id: GEO accession (e.g., 'GSE110004')
150
+
151
+ Returns:
152
+ SRA study accession (e.g., 'SRP126328') or None
153
+ """
154
+ try:
155
+ # Search for SRA study linked to GEO
156
+ search_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=sra&term={geo_id}[GEO]&retmode=json"
157
+
158
+ _rate_limit_ncbi()
159
+ if HAS_REQUESTS:
160
+ response = requests.get(search_url, timeout=30)
161
+ data = response.json()
162
+ else:
163
+ with urlopen(search_url, timeout=30) as response:
164
+ data = json.loads(response.read().decode())
165
+
166
+ id_list = data.get('esearchresult', {}).get('idlist', [])
167
+ if not id_list:
168
+ return None
169
+
170
+ # Get summary to extract SRP accession
171
+ uid = id_list[0]
172
+ summary_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=sra&id={uid}&retmode=json"
173
+
174
+ _rate_limit_ncbi()
175
+ if HAS_REQUESTS:
176
+ response = requests.get(summary_url, timeout=30)
177
+ data = response.json()
178
+ else:
179
+ with urlopen(summary_url, timeout=30) as response:
180
+ data = json.loads(response.read().decode())
181
+
182
+ result = data.get('result', {}).get(uid, {})
183
+ exp_xml = result.get('expxml', '')
184
+
185
+ # Extract SRP from the XML
186
+ srp_match = re.search(r'<Study acc="(SRP\d+)"', exp_xml)
187
+ if srp_match:
188
+ return srp_match.group(1)
189
+
190
+ return None
191
+
192
+ except Exception as e:
193
+ logger.debug(f"Error fetching SRA study for {geo_id}: {e}")
194
+ return None
195
+
196
+
197
+ def fetch_sra_run_info(geo_id: str, bioproject: Optional[str] = None) -> List[Dict]:
198
+ """
199
+ Fetch SRA run information for all samples in a GEO study.
200
+
201
+ Args:
202
+ geo_id: GEO accession (e.g., 'GSE110004')
203
+ bioproject: Optional BioProject accession for fallback search
204
+
205
+ Returns:
206
+ List of dicts with run info (srr, gsm, layout, library_strategy, etc.)
207
+ """
208
+ runs = []
209
+
210
+ try:
211
+ # First get the BioProject accession
212
+ search_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=sra&term={geo_id}[GEO]&retmax=1000&retmode=json"
213
+
214
+ _rate_limit_ncbi()
215
+ if HAS_REQUESTS:
216
+ response = requests.get(search_url, timeout=30)
217
+ data = response.json()
218
+ else:
219
+ with urlopen(search_url, timeout=30) as response:
220
+ data = json.loads(response.read().decode())
221
+
222
+ id_list = data.get('esearchresult', {}).get('idlist', [])
223
+
224
+ # If no results, try BioProject fallback
225
+ if not id_list:
226
+ if not bioproject:
227
+ bioproject = fetch_bioproject_from_geo(geo_id)
228
+
229
+ if bioproject:
230
+ logger.info(f"Using BioProject {bioproject} for {geo_id}")
231
+ search_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=sra&term={bioproject}&retmax=1000&retmode=json"
232
+
233
+ _rate_limit_ncbi()
234
+ if HAS_REQUESTS:
235
+ response = requests.get(search_url, timeout=30)
236
+ data = response.json()
237
+ else:
238
+ with urlopen(search_url, timeout=30) as response:
239
+ data = json.loads(response.read().decode())
240
+
241
+ id_list = data.get('esearchresult', {}).get('idlist', [])
242
+
243
+ if not id_list:
244
+ logger.warning(f"No SRA entries found for {geo_id}")
245
+ return runs
246
+
247
+ # Batch fetch summaries
248
+ ids_str = ','.join(id_list)
249
+ summary_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=sra&id={ids_str}&retmode=json"
250
+
251
+ _rate_limit_ncbi()
252
+ if HAS_REQUESTS:
253
+ response = requests.get(summary_url, timeout=60)
254
+ data = response.json()
255
+ else:
256
+ with urlopen(summary_url, timeout=60) as response:
257
+ data = json.loads(response.read().decode())
258
+
259
+ result = data.get('result', {})
260
+
261
+ for uid in id_list:
262
+ entry = result.get(uid, {})
263
+ if not entry:
264
+ continue
265
+
266
+ exp_xml = entry.get('expxml', '')
267
+ runs_xml = entry.get('runs', '')
268
+
269
+ # Extract metadata from XML
270
+ layout_match = re.search(r'<LIBRARY_LAYOUT>\s*<(\w+)', exp_xml)
271
+ strategy_match = re.search(r'<LIBRARY_STRATEGY>(\w+)', exp_xml)
272
+ source_match = re.search(r'<LIBRARY_SOURCE>(\w+)', exp_xml)
273
+ gsm_match = re.search(r'<Sample acc="(GSM\d+)"', exp_xml)
274
+ srx_match = re.search(r'<Experiment acc="(SRX\d+)"', exp_xml)
275
+
276
+ # Extract run accessions
277
+ srr_matches = re.findall(r'<Run acc="(SRR\d+)"[^>]*total_spots="(\d+)"[^>]*total_bases="(\d+)"', runs_xml)
278
+
279
+ for srr, spots, bases in srr_matches:
280
+ runs.append({
281
+ 'srr': srr,
282
+ 'srx': srx_match.group(1) if srx_match else '',
283
+ 'gsm': gsm_match.group(1) if gsm_match else '',
284
+ 'layout': layout_match.group(1).upper() if layout_match else 'UNKNOWN',
285
+ 'library_strategy': strategy_match.group(1) if strategy_match else 'UNKNOWN',
286
+ 'library_source': source_match.group(1) if source_match else 'UNKNOWN',
287
+ 'spots': int(spots),
288
+ 'bases': int(bases),
289
+ })
290
+
291
+ return runs
292
+
293
+ except Exception as e:
294
+ logger.error(f"Error fetching SRA run info for {geo_id}: {e}")
295
+ return runs
296
+
297
+
298
+ def fetch_ena_fastq_urls(study_accession: str) -> Dict[str, List[str]]:
299
+ """
300
+ Get FASTQ download URLs from ENA for an SRA study.
301
+
302
+ ENA provides faster downloads than SRA with pre-split paired files.
303
+
304
+ Args:
305
+ study_accession: SRA study accession (e.g., 'SRP126328')
306
+
307
+ Returns:
308
+ Dict mapping SRR accession to list of FASTQ URLs
309
+ """
310
+ fastq_urls = {}
311
+
312
+ try:
313
+ # Query ENA API
314
+ ena_url = f"https://www.ebi.ac.uk/ena/portal/api/filereport?accession={study_accession}&result=read_run&fields=run_accession,sample_alias,fastq_ftp&format=tsv"
315
+
316
+ if HAS_REQUESTS:
317
+ response = requests.get(ena_url, timeout=60)
318
+ content = response.text
319
+ else:
320
+ with urlopen(ena_url, timeout=60) as response:
321
+ content = response.read().decode()
322
+
323
+ lines = content.strip().split('\n')
324
+ if len(lines) < 2:
325
+ logger.warning(f"No FASTQ URLs found in ENA for {study_accession}")
326
+ return fastq_urls
327
+
328
+ # Parse TSV
329
+ header = lines[0].split('\t')
330
+ run_idx = header.index('run_accession') if 'run_accession' in header else 0
331
+ ftp_idx = header.index('fastq_ftp') if 'fastq_ftp' in header else 2
332
+
333
+ for line in lines[1:]:
334
+ if not line.strip():
335
+ continue
336
+ fields = line.split('\t')
337
+ if len(fields) > max(run_idx, ftp_idx):
338
+ srr = fields[run_idx]
339
+ ftp_urls = fields[ftp_idx]
340
+ if ftp_urls:
341
+ # URLs are semicolon-separated, convert to HTTP URLs
342
+ # ENA supports both FTP and HTTP, HTTP is easier with requests
343
+ urls = [f"http://{url}" for url in ftp_urls.split(';') if url]
344
+ fastq_urls[srr] = urls
345
+
346
+ return fastq_urls
347
+
348
+ except Exception as e:
349
+ logger.error(f"Error fetching ENA URLs for {study_accession}: {e}")
350
+ return fastq_urls
351
+
352
+
353
+ def download_file(url: str, output_path: Path, timeout: int = 300, show_progress: bool = True) -> bool:
354
+ """
355
+ Download a file with progress indication.
356
+
357
+ Args:
358
+ url: URL to download
359
+ output_path: Path to save file
360
+ timeout: Download timeout in seconds
361
+ show_progress: Show progress bar
362
+
363
+ Returns:
364
+ True if successful, False otherwise
365
+ """
366
+ try:
367
+ output_path.parent.mkdir(parents=True, exist_ok=True)
368
+
369
+ if HAS_REQUESTS:
370
+ response = requests.get(url, stream=True, timeout=timeout)
371
+ response.raise_for_status()
372
+
373
+ total_size = int(response.headers.get('content-length', 0))
374
+
375
+ with open(output_path, 'wb') as f:
376
+ downloaded = 0
377
+ for chunk in response.iter_content(chunk_size=8192):
378
+ f.write(chunk)
379
+ downloaded += len(chunk)
380
+ if show_progress and total_size > 0:
381
+ pct = (downloaded / total_size) * 100
382
+ print(f"\r Progress: {pct:.1f}%", end='', flush=True)
383
+ if show_progress:
384
+ print() # New line after progress
385
+ return True
386
+ else:
387
+ # Fallback to urllib
388
+ req = Request(url, headers={'User-Agent': 'geo-sra-skill/1.0'})
389
+ with urlopen(req, timeout=timeout) as response:
390
+ with open(output_path, 'wb') as f:
391
+ shutil.copyfileobj(response, f)
392
+ return True
393
+
394
+ except Exception as e:
395
+ logger.error(f"Download error for {url}: {e}")
396
+ return False
397
+
398
+
399
+ def fetch_pubmed_metadata(pmid: str, max_retries: int = 3) -> Optional[Dict]:
400
+ """
401
+ Fetch paper metadata from PubMed.
402
+
403
+ Args:
404
+ pmid: PubMed ID
405
+ max_retries: Number of retries on failure
406
+
407
+ Returns:
408
+ Dict with 'authors', 'year', 'journal', 'doi' or None
409
+ """
410
+ url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&id={pmid}&retmode=json"
411
+
412
+ for attempt in range(max_retries):
413
+ try:
414
+ _rate_limit_ncbi()
415
+ if HAS_REQUESTS:
416
+ response = requests.get(url, timeout=30)
417
+ data = response.json()
418
+ else:
419
+ with urlopen(url, timeout=30) as response:
420
+ data = json.loads(response.read().decode())
421
+
422
+ result = data.get('result', {}).get(pmid, {})
423
+
424
+ if not result or 'error' in result:
425
+ if attempt < max_retries - 1:
426
+ time.sleep(1 * (attempt + 1))
427
+ continue
428
+ return None
429
+
430
+ # Extract authors
431
+ authors_list = result.get('authors', [])
432
+ if not authors_list:
433
+ if attempt < max_retries - 1:
434
+ time.sleep(1 * (attempt + 1))
435
+ continue
436
+ return None
437
+
438
+ author_names = [f"{a.get('name', '')}" for a in authors_list[:3]]
439
+ authors = ', '.join(author_names)
440
+ if len(authors_list) > 3:
441
+ authors += ', et al.'
442
+
443
+ # Extract year
444
+ pubdate = result.get('pubdate', '')
445
+ year_match = re.search(r'\b(20\d{2})\b', pubdate)
446
+ year = year_match.group(1) if year_match else "Unknown"
447
+
448
+ # Extract journal
449
+ journal = result.get('source', 'Unknown')
450
+
451
+ # Extract DOI
452
+ doi = ""
453
+ for aid in result.get('articleids', []):
454
+ if aid.get('idtype') == 'doi':
455
+ doi = aid.get('value', '')
456
+ break
457
+
458
+ return {
459
+ 'authors': authors,
460
+ 'year': year,
461
+ 'journal': journal,
462
+ 'doi': doi,
463
+ 'title': result.get('title', '')
464
+ }
465
+
466
+ except Exception as e:
467
+ logger.debug(f"PubMed fetch error for PMID {pmid} (attempt {attempt + 1}): {e}")
468
+ if attempt < max_retries - 1:
469
+ time.sleep(1 * (attempt + 1))
470
+ continue
471
+
472
+ return None
473
+
474
+
475
+ def format_file_size(size_bytes: int) -> str:
476
+ """Format file size in human-readable format."""
477
+ if size_bytes < 1024:
478
+ return f"{size_bytes} B"
479
+ elif size_bytes < 1024 * 1024:
480
+ return f"{size_bytes / 1024:.1f} KB"
481
+ elif size_bytes < 1024 * 1024 * 1024:
482
+ return f"{size_bytes / (1024 * 1024):.1f} MB"
483
+ else:
484
+ return f"{size_bytes / (1024 * 1024 * 1024):.1f} GB"
485
+
486
+
487
+ def estimate_download_size(runs: List[Dict]) -> int:
488
+ """
489
+ Estimate total download size from SRA run info.
490
+
491
+ Args:
492
+ runs: List of run info dicts with 'bases' field
493
+
494
+ Returns:
495
+ Estimated size in bytes (rough estimate based on bases)
496
+ """
497
+ total_bases = sum(r.get('bases', 0) for r in runs)
498
+ # FASTQ is roughly 1 byte per base when compressed
499
+ return total_bases // 4 # Rough compression ratio
500
+
501
+
502
+ def fetch_bioproject_from_geo(geo_id: str) -> Optional[str]:
503
+ """
504
+ Fetch BioProject accession linked to a GEO study.
505
+
506
+ Args:
507
+ geo_id: GEO accession (e.g., 'GSE110004')
508
+
509
+ Returns:
510
+ BioProject accession (e.g., 'PRJNA432544') or None
511
+ """
512
+ try:
513
+ # First get GDS UID
514
+ search_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=gds&term={geo_id}[Accession]&retmode=json"
515
+
516
+ _rate_limit_ncbi()
517
+ if HAS_REQUESTS:
518
+ response = requests.get(search_url, timeout=30)
519
+ data = response.json()
520
+ else:
521
+ with urlopen(search_url, timeout=30) as response:
522
+ data = json.loads(response.read().decode())
523
+
524
+ gds_ids = data.get('esearchresult', {}).get('idlist', [])
525
+ if not gds_ids:
526
+ return None
527
+
528
+ # Get linked BioProject
529
+ elink_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/elink.fcgi?dbfrom=gds&db=bioproject&id={gds_ids[0]}&retmode=json"
530
+
531
+ _rate_limit_ncbi()
532
+ if HAS_REQUESTS:
533
+ response = requests.get(elink_url, timeout=30)
534
+ data = response.json()
535
+ else:
536
+ with urlopen(elink_url, timeout=30) as response:
537
+ data = json.loads(response.read().decode())
538
+
539
+ linksets = data.get('linksets', [])
540
+ if linksets and linksets[0].get('linksetdbs'):
541
+ for linksetdb in linksets[0]['linksetdbs']:
542
+ if linksetdb.get('dbto') == 'bioproject':
543
+ bp_ids = linksetdb.get('links', [])
544
+ if bp_ids:
545
+ # Get BioProject accession
546
+ summary_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=bioproject&id={bp_ids[0]}&retmode=json"
547
+ _rate_limit_ncbi()
548
+ if HAS_REQUESTS:
549
+ response = requests.get(summary_url, timeout=30)
550
+ data = response.json()
551
+ else:
552
+ with urlopen(summary_url, timeout=30) as response:
553
+ data = json.loads(response.read().decode())
554
+
555
+ result = data.get('result', {}).get(str(bp_ids[0]), {})
556
+ return result.get('project_acc')
557
+
558
+ return None
559
+
560
+ except Exception as e:
561
+ logger.debug(f"Error fetching BioProject for {geo_id}: {e}")
562
+ return None
563
+
564
+
565
+ def fetch_sra_run_info_detailed(geo_id: str, bioproject: Optional[str] = None) -> List[Dict]:
566
+ """
567
+ Fetch detailed SRA run information using efetch CSV format.
568
+
569
+ This provides richer metadata than esummary, including sample names.
570
+
571
+ Args:
572
+ geo_id: GEO accession (e.g., 'GSE110004')
573
+ bioproject: Optional BioProject accession for fallback search
574
+
575
+ Returns:
576
+ List of dicts with detailed run info
577
+ """
578
+ runs = []
579
+
580
+ try:
581
+ # First get SRA UIDs using GEO search
582
+ search_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=sra&term={geo_id}[GEO]&retmax=1000&retmode=json"
583
+
584
+ _rate_limit_ncbi()
585
+ if HAS_REQUESTS:
586
+ response = requests.get(search_url, timeout=30)
587
+ data = response.json()
588
+ else:
589
+ with urlopen(search_url, timeout=30) as response:
590
+ data = json.loads(response.read().decode())
591
+
592
+ id_list = data.get('esearchresult', {}).get('idlist', [])
593
+
594
+ # If no results with GEO search, try BioProject
595
+ if not id_list:
596
+ # Try to find BioProject if not provided
597
+ if not bioproject:
598
+ logger.info(f"No direct SRA link for {geo_id}, searching for BioProject...")
599
+ bioproject = fetch_bioproject_from_geo(geo_id)
600
+
601
+ if bioproject:
602
+ logger.info(f"Found BioProject: {bioproject}")
603
+ search_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=sra&term={bioproject}&retmax=1000&retmode=json"
604
+
605
+ _rate_limit_ncbi()
606
+ if HAS_REQUESTS:
607
+ response = requests.get(search_url, timeout=30)
608
+ data = response.json()
609
+ else:
610
+ with urlopen(search_url, timeout=30) as response:
611
+ data = json.loads(response.read().decode())
612
+
613
+ id_list = data.get('esearchresult', {}).get('idlist', [])
614
+
615
+ if not id_list:
616
+ logger.warning(f"No SRA entries found for {geo_id}")
617
+ return runs
618
+
619
+ # Fetch run info in CSV format using efetch
620
+ ids_str = ','.join(id_list)
621
+ efetch_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=sra&id={ids_str}&rettype=runinfo&retmode=csv"
622
+
623
+ _rate_limit_ncbi()
624
+ if HAS_REQUESTS:
625
+ response = requests.get(efetch_url, timeout=60)
626
+ content = response.text
627
+ else:
628
+ with urlopen(efetch_url, timeout=60) as response:
629
+ content = response.read().decode()
630
+
631
+ lines = content.strip().split('\n')
632
+ if len(lines) < 1:
633
+ return runs
634
+
635
+ # NCBI efetch runinfo CSV doesn't include headers
636
+ # Define the fixed column order for SRA runinfo format
637
+ header = [
638
+ 'Run', 'ReleaseDate', 'LoadDate', 'spots', 'bases', 'spots_with_mates',
639
+ 'avgLength', 'size_MB', 'AssemblyName', 'download_path', 'Experiment',
640
+ 'LibraryName', 'LibraryStrategy', 'LibrarySelection', 'LibrarySource',
641
+ 'LibraryLayout', 'InsertSize', 'InsertDev', 'Platform', 'Model',
642
+ 'SRAStudy', 'BioProject', 'Study_Pubmed_id', 'ProjectID', 'Sample',
643
+ 'BioSample', 'SampleType', 'TaxID', 'ScientificName', 'SampleName',
644
+ 'g1k_pop_code', 'source', 'g1k_analysis_group', 'Subject_ID', 'Sex',
645
+ 'Disease', 'Tumor', 'Affection_Status', 'Analyte_Type', 'Histological_Type',
646
+ 'Body_Site', 'CenterName', 'Submission', 'dbgap_study_accession', 'Consent',
647
+ 'RunHash', 'ReadHash'
648
+ ]
649
+
650
+ # Map column names to indices
651
+ col_map = {col: idx for idx, col in enumerate(header)}
652
+
653
+ for line in lines:
654
+ if not line.strip():
655
+ continue
656
+
657
+ # Handle CSV fields (some may contain commas in quotes)
658
+ fields = _parse_csv_line(line)
659
+ if len(fields) < len(header):
660
+ continue
661
+
662
+ def get_field(name, default=''):
663
+ idx = col_map.get(name, -1)
664
+ return fields[idx] if idx >= 0 and idx < len(fields) else default
665
+
666
+ run = {
667
+ 'srr': get_field('Run'),
668
+ 'srx': get_field('Experiment'),
669
+ 'gsm': get_field('SampleName'), # Often GSM ID
670
+ 'sample_name': get_field('SampleName'),
671
+ 'library_name': get_field('LibraryName'),
672
+ 'layout': get_field('LibraryLayout', 'UNKNOWN').upper(),
673
+ 'library_strategy': get_field('LibraryStrategy', 'UNKNOWN'),
674
+ 'library_source': get_field('LibrarySource', 'UNKNOWN'),
675
+ 'library_selection': get_field('LibrarySelection', ''),
676
+ 'platform': get_field('Platform'),
677
+ 'model': get_field('Model'),
678
+ 'organism': get_field('ScientificName', ''),
679
+ 'spots': int(get_field('spots', 0) or 0),
680
+ 'bases': int(get_field('bases', 0) or 0),
681
+ 'size_mb': float(get_field('size_MB', 0) or 0),
682
+ 'bioproject': get_field('BioProject'),
683
+ 'biosample': get_field('BioSample'),
684
+ 'sra_study': get_field('SRAStudy'),
685
+ }
686
+
687
+ # Only add if we have a valid SRR
688
+ if run['srr'].startswith('SRR'):
689
+ runs.append(run)
690
+
691
+ return runs
692
+
693
+ except Exception as e:
694
+ logger.error(f"Error fetching detailed SRA run info for {geo_id}: {e}")
695
+ return runs
696
+
697
+
698
+ def _parse_csv_line(line: str) -> List[str]:
699
+ """Parse a CSV line handling quoted fields."""
700
+ import csv
701
+ import io
702
+ reader = csv.reader(io.StringIO(line))
703
+ for row in reader:
704
+ return row
705
+ return []
706
+
707
+
708
+ def group_samples_by_type(runs: List[Dict]) -> Dict[str, Dict]:
709
+ """
710
+ Group SRA runs by library type and layout.
711
+
712
+ Returns dict with group names as keys and info dicts as values:
713
+ {
714
+ 'RNA-Seq:PAIRED': {
715
+ 'runs': [...],
716
+ 'count': 18,
717
+ 'gsm_range': 'GSM2879618-GSM2879635',
718
+ 'size_estimate': 50000000000,
719
+ 'description': 'RNA-Seq paired-end'
720
+ },
721
+ ...
722
+ }
723
+ """
724
+ groups = {}
725
+
726
+ for run in runs:
727
+ strategy = run.get('library_strategy', 'UNKNOWN')
728
+ layout = run.get('layout', 'UNKNOWN')
729
+ key = f"{strategy}:{layout}"
730
+
731
+ if key not in groups:
732
+ groups[key] = {
733
+ 'runs': [],
734
+ 'gsm_ids': set(),
735
+ 'total_bases': 0,
736
+ 'strategy': strategy,
737
+ 'layout': layout,
738
+ }
739
+
740
+ groups[key]['runs'].append(run)
741
+ gsm = run.get('gsm', '')
742
+ if gsm.startswith('GSM'):
743
+ groups[key]['gsm_ids'].add(gsm)
744
+ groups[key]['total_bases'] += run.get('bases', 0)
745
+
746
+ # Post-process groups
747
+ result = {}
748
+ for key, info in groups.items():
749
+ gsm_list = sorted(info['gsm_ids'])
750
+ gsm_range = _format_gsm_range(gsm_list) if gsm_list else 'N/A'
751
+
752
+ result[key] = {
753
+ 'runs': info['runs'],
754
+ 'count': len(info['runs']),
755
+ 'gsm_range': gsm_range,
756
+ 'gsm_ids': gsm_list,
757
+ 'size_estimate': info['total_bases'] // 4, # Rough compressed size
758
+ 'strategy': info['strategy'],
759
+ 'layout': info['layout'],
760
+ 'description': f"{info['strategy']} {info['layout'].lower()}",
761
+ }
762
+
763
+ return result
764
+
765
+
766
+ def _format_gsm_range(gsm_list: List[str]) -> str:
767
+ """Format list of GSM IDs as a range if consecutive."""
768
+ if not gsm_list:
769
+ return 'N/A'
770
+
771
+ if len(gsm_list) == 1:
772
+ return gsm_list[0]
773
+
774
+ # Extract numbers and check if consecutive
775
+ try:
776
+ numbers = [int(gsm.replace('GSM', '')) for gsm in gsm_list]
777
+ numbers.sort()
778
+
779
+ if numbers[-1] - numbers[0] == len(numbers) - 1:
780
+ # Consecutive
781
+ return f"GSM{numbers[0]}-GSM{numbers[-1]}"
782
+ else:
783
+ # Not consecutive, show count
784
+ return f"{gsm_list[0]}...({len(gsm_list)} samples)"
785
+ except ValueError:
786
+ return f"{len(gsm_list)} samples"
787
+
788
+
789
+ def format_sample_groups_table(groups: Dict[str, Dict]) -> str:
790
+ """Format sample groups as a readable table."""
791
+ lines = []
792
+ lines.append("")
793
+ lines.append(f"{'Sample Group':<20} {'Count':>6} {'Layout':<10} {'GSM Range':<25} {'Est. Size':>12}")
794
+ lines.append("-" * 80)
795
+
796
+ for key, info in sorted(groups.items(), key=lambda x: -x[1]['count']):
797
+ size_str = format_file_size(info['size_estimate'])
798
+ lines.append(
799
+ f"{info['strategy']:<20} {info['count']:>6} {info['layout']:<10} "
800
+ f"{info['gsm_range']:<25} {size_str:>12}"
801
+ )
802
+
803
+ lines.append("-" * 80)
804
+ total_runs = sum(g['count'] for g in groups.values())
805
+ total_size = sum(g['size_estimate'] for g in groups.values())
806
+ lines.append(f"{'TOTAL':<20} {total_runs:>6} {'':<10} {'':<25} {format_file_size(total_size):>12}")
807
+
808
+ return '\n'.join(lines)