opencode-knowledge-work 0.0.0-stage → 0.1.1

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 +83 -0
  472. package/package.json +28 -3
@@ -0,0 +1,1102 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ ASM Output Validation Script
4
+
5
+ Validates ASM JSON output against common issues:
6
+ - Wrong technique selection
7
+ - Hyphenated field names (should be space-separated)
8
+ - Missing statistics documents
9
+ - Incorrect units
10
+ - Missing required fields
11
+ - Missing calculated data traceability
12
+ - Improperly flattened nested documents (sample document, device control, etc.)
13
+
14
+ Validation Rules:
15
+ Based on: Allotrope ASM specification (December 2024)
16
+ Last Updated: 2026-01-07
17
+ Source: https://gitlab.com/allotrope-public/asm/-/tree/main/json-schemas/adm
18
+
19
+ Note: Unknown techniques/units generate WARNINGS (not errors) to allow for new
20
+ additions to the Allotrope specification. This prevents blocking valid data
21
+ when the Allotrope foundation adds new techniques or units.
22
+
23
+ Usage:
24
+ python validate_asm.py output.json
25
+ python validate_asm.py output.json --reference reference.json
26
+ python validate_asm.py output.json --strict
27
+ """
28
+
29
+ import json
30
+ import re
31
+ import sys
32
+ import argparse
33
+ from typing import Dict, List, Tuple, Any, Optional
34
+
35
+ # Validation metadata
36
+ ASM_SPEC_VERSION = "2024-12"
37
+ VALIDATION_RULES_DATE = "2026-01-07"
38
+ SCHEMA_SOURCE = "https://gitlab.com/allotrope-public/asm"
39
+
40
+
41
+ # All valid ASM techniques from https://gitlab.com/allotrope-public/asm/-/tree/main/json-schemas/adm
42
+ VALID_TECHNIQUES = [
43
+ "absorbance",
44
+ "automated-reactors",
45
+ "balance",
46
+ "bga",
47
+ "binding-affinity",
48
+ "bulk-density",
49
+ "cell-counting",
50
+ "cell-culture-analyzer",
51
+ "chromatography",
52
+ "code-reader",
53
+ "conductance",
54
+ "conductivity",
55
+ "disintegration",
56
+ "dsc",
57
+ "dvs",
58
+ "electronic-lab-notebook",
59
+ "electronic-spectrometry",
60
+ "electrophoresis",
61
+ "flow-cytometry",
62
+ "fluorescence",
63
+ "foam-height",
64
+ "foam-qualification",
65
+ "fplc",
66
+ "ftir",
67
+ "gas-chromatography",
68
+ "gc-ms",
69
+ "gloss",
70
+ "hot-tack",
71
+ "impedance",
72
+ "lc-ms",
73
+ "light-obscuration",
74
+ "liquid-chromatography",
75
+ "liquid-handler", # Added for liquid handler support
76
+ "loss-on-drying",
77
+ "luminescence",
78
+ "mass-spectrometry",
79
+ "metabolite-analyzer",
80
+ "multi-analyte-profiling",
81
+ "nephelometry",
82
+ "nmr",
83
+ "optical-imaging",
84
+ "optical-microscopy",
85
+ "osmolality",
86
+ "oven-kf",
87
+ "pcr",
88
+ "ph",
89
+ "plate-reader",
90
+ "pressure-monitoring",
91
+ "psd",
92
+ "pumping",
93
+ "raman",
94
+ "rheometry",
95
+ "sem",
96
+ "solution-analyzer",
97
+ "specific-rotation",
98
+ "spectrophotometry",
99
+ "stirring",
100
+ "surface-area-analysis",
101
+ "tablet-hardness",
102
+ "temperature-monitoring",
103
+ "tensile-test",
104
+ "thermogravimetric-analysis",
105
+ "titration",
106
+ "ultraviolet-absorbance",
107
+ "x-ray-powder-diffraction",
108
+ ]
109
+
110
+ # Instrument keywords that indicate specific techniques
111
+ TECHNIQUE_INDICATORS = {
112
+ "multi-analyte-profiling": [
113
+ "bead",
114
+ "luminex",
115
+ "bio-plex",
116
+ "bioplex",
117
+ "multiplex",
118
+ "plex",
119
+ "msd",
120
+ "region",
121
+ ],
122
+ "electrophoresis": [
123
+ "tapestation",
124
+ "bioanalyzer",
125
+ "labchip",
126
+ "fragment",
127
+ "din",
128
+ "rin",
129
+ "gel",
130
+ "capillary",
131
+ ],
132
+ "spectrophotometry": ["nanodrop", "lunatic", "a260", "a280", "wavelength"],
133
+ "cell-counting": [
134
+ "viability",
135
+ "viable cell",
136
+ "cell count",
137
+ "vi-cell",
138
+ "vicell",
139
+ "nucleocounter",
140
+ "cell density",
141
+ ],
142
+ "pcr": [
143
+ "ct",
144
+ "quantstudio",
145
+ "cfx",
146
+ "amplification",
147
+ "melt curve",
148
+ "qpcr",
149
+ "cycle threshold",
150
+ ],
151
+ "plate-reader": [
152
+ "microplate",
153
+ "96-well",
154
+ "384-well",
155
+ "plate reader",
156
+ "envision",
157
+ "spectramax",
158
+ ],
159
+ "liquid-chromatography": [
160
+ "hplc",
161
+ "uplc",
162
+ "retention time",
163
+ "chromatogram",
164
+ "empower",
165
+ "chromeleon",
166
+ ],
167
+ "flow-cytometry": ["facs", "flow cytometry", "scatter", "gating", "cytometer"],
168
+ "mass-spectrometry": ["m/z", "mass spec", "ms/ms", "lcms", "maldi"],
169
+ "fluorescence": ["fluorescence", "excitation", "emission", "fluorimeter"],
170
+ "luminescence": ["luminescence", "bioluminescence", "chemiluminescence"],
171
+ "absorbance": ["absorbance", "optical density", "od600"],
172
+ "ph": ["ph meter", "ph measurement"],
173
+ "osmolality": ["osmolality", "osmometer"],
174
+ "conductivity": ["conductivity", "conductance"],
175
+ "balance": ["balance", "weight", "mass measurement"],
176
+ "nmr": ["nmr", "nuclear magnetic resonance"],
177
+ "ftir": ["ftir", "infrared", "ir spectrum"],
178
+ "raman": ["raman", "raman spectroscopy"],
179
+ "liquid-handler": [
180
+ "biomek",
181
+ "liquid handler",
182
+ "aspirate",
183
+ "dispense",
184
+ "transfer volume",
185
+ "liquid handling",
186
+ ],
187
+ }
188
+
189
+ # Fields that should typically be in calculated-data-document, not measurement-document
190
+ SHOULD_BE_CALCULATED = [
191
+ "dna integrity number",
192
+ "rna integrity number",
193
+ "din",
194
+ "rin",
195
+ "viability",
196
+ "260/280",
197
+ "a260/a280",
198
+ "concentration", # When derived from standard curve
199
+ "percent of total",
200
+ "average size",
201
+ "molarity", # When calculated from concentration
202
+ "relative quantity",
203
+ "fold change",
204
+ "coefficient of variation",
205
+ ]
206
+
207
+ # =============================================================================
208
+ # NESTED DOCUMENT STRUCTURE DEFINITIONS
209
+ # =============================================================================
210
+
211
+ # Fields that MUST be inside 'sample document' (space or hyphen separated)
212
+ SAMPLE_DOCUMENT_FIELDS = {
213
+ # Core sample identification
214
+ "sample identifier",
215
+ "sample-identifier",
216
+ "written name",
217
+ "written-name",
218
+ "batch identifier",
219
+ "batch-identifier",
220
+ "sample role type",
221
+ "sample-role-type",
222
+ "description",
223
+ # Location fields (should be in sample document for most techniques)
224
+ "location identifier",
225
+ "location-identifier",
226
+ "well location identifier",
227
+ "well-location-identifier",
228
+ "well plate identifier",
229
+ "well-plate-identifier",
230
+ # Liquid handler specific - source/destination pairs
231
+ "source location identifier",
232
+ "source-location-identifier",
233
+ "destination location identifier",
234
+ "destination-location-identifier",
235
+ "source well plate identifier",
236
+ "source-well-plate-identifier",
237
+ "destination well plate identifier",
238
+ "destination-well-plate-identifier",
239
+ "source well location identifier",
240
+ "source-well-location-identifier",
241
+ "destination well location identifier",
242
+ "destination-well-location-identifier",
243
+ }
244
+
245
+ # Fields that MUST be inside 'device control aggregate document' -> 'device control document'
246
+ DEVICE_CONTROL_FIELDS = {
247
+ # General device control
248
+ "device type",
249
+ "device-type",
250
+ "detector wavelength setting",
251
+ "detector-wavelength-setting",
252
+ "compartment temperature",
253
+ "compartment-temperature",
254
+ "sample volume setting",
255
+ "sample-volume-setting",
256
+ "flow rate",
257
+ "flow-rate",
258
+ "exposure duration setting",
259
+ "exposure-duration-setting",
260
+ "detector gain setting",
261
+ "detector-gain-setting",
262
+ "illumination setting",
263
+ "illumination-setting",
264
+ # Liquid handler specific
265
+ "liquid handling technique",
266
+ "liquid-handling-technique",
267
+ "source liquid handling technique",
268
+ "source-liquid-handling-technique",
269
+ "destination liquid handling technique",
270
+ "destination-liquid-handling-technique",
271
+ }
272
+
273
+ # Fields that should be in 'custom information document' (vendor-specific)
274
+ CUSTOM_INFO_FIELDS = {
275
+ # Liquid handler specific
276
+ "probe",
277
+ "pod",
278
+ "source labware name",
279
+ "source-labware-name",
280
+ "destination labware name",
281
+ "destination-labware-name",
282
+ "deck position",
283
+ "deck-position",
284
+ }
285
+
286
+ # Fields that commonly get incorrectly flattened (superset for general checking)
287
+ COMMONLY_FLATTENED_FIELDS = {
288
+ # Sample-related (often incorrectly put directly on measurement)
289
+ "sample identifier",
290
+ "sample-identifier",
291
+ "sample barcode",
292
+ "sample-barcode",
293
+ "well index",
294
+ "well-index",
295
+ "location identifier",
296
+ "location-identifier",
297
+ # Device control related (often incorrectly put directly on measurement)
298
+ "probe identifier",
299
+ "probe-identifier",
300
+ "device identifier", # When it should be in device control doc, not measurement
301
+ "device-identifier",
302
+ "technique", # Should be "liquid handling technique" in device control
303
+ "transfer type", # Should be structured differently
304
+ "transfer-type",
305
+ }
306
+
307
+ # Standard ASM units
308
+ VALID_UNITS = {
309
+ "fluorescence": ["RFU", "MFI", "(unitless)"],
310
+ "counts": ["#"],
311
+ "volume": ["μL", "mL", "L", "µL"],
312
+ "concentration": [
313
+ "ng/μL",
314
+ "ng/mL",
315
+ "pg/mL",
316
+ "mg/mL",
317
+ "μg/mL",
318
+ "M",
319
+ "mM",
320
+ "μM",
321
+ "nM",
322
+ ],
323
+ "temperature": ["degC"],
324
+ "unitless": ["(unitless)", "%"],
325
+ "molecular_weight": ["bp", "Da", "kDa"],
326
+ "time": ["s", "min", "h"],
327
+ }
328
+
329
+ # Standard sample role types
330
+ VALID_SAMPLE_ROLES = [
331
+ "standard sample role",
332
+ "blank role",
333
+ "control sample role",
334
+ "unknown sample role",
335
+ "reference sample role",
336
+ "calibration sample role",
337
+ ]
338
+
339
+ # Standard statistic datum roles
340
+ VALID_STATISTIC_ROLES = [
341
+ "median role",
342
+ "arithmetic mean role",
343
+ "coefficient of variation role",
344
+ "standard deviation role",
345
+ "standard error role",
346
+ "trimmed arithmetic mean role",
347
+ "trimmed standard deviation role",
348
+ "minimum value role",
349
+ "maximum value role",
350
+ ]
351
+
352
+
353
+ class ValidationResult:
354
+ """Container for validation results."""
355
+
356
+ def __init__(self):
357
+ self.errors: List[str] = []
358
+ self.warnings: List[str] = []
359
+ self.info: List[str] = []
360
+ self.metrics: Dict[str, Any] = {}
361
+
362
+ def add_error(self, msg: str):
363
+ self.errors.append(f"ERROR: {msg}")
364
+
365
+ def add_warning(self, msg: str):
366
+ self.warnings.append(f"WARNING: {msg}")
367
+
368
+ def add_info(self, msg: str):
369
+ self.info.append(f"INFO: {msg}")
370
+
371
+ def is_valid(self) -> bool:
372
+ return len(self.errors) == 0
373
+
374
+ def print_report(self):
375
+ print("\n" + "=" * 60)
376
+ print("ASM VALIDATION REPORT")
377
+ print("=" * 60)
378
+
379
+ # Print metrics
380
+ if self.metrics:
381
+ print("\nMetrics:")
382
+ for key, value in self.metrics.items():
383
+ print(f" {key}: {value}")
384
+
385
+ # Print info
386
+ if self.info:
387
+ print("\n" + "\n".join(self.info))
388
+
389
+ # Print warnings
390
+ if self.warnings:
391
+ print("\n" + "\n".join(self.warnings))
392
+
393
+ # Print errors
394
+ if self.errors:
395
+ print("\n" + "\n".join(self.errors))
396
+
397
+ # Summary
398
+ print("\n" + "-" * 60)
399
+ if self.is_valid():
400
+ if self.warnings:
401
+ print(f"PASSED with {len(self.warnings)} warning(s)")
402
+ else:
403
+ print("PASSED - No issues found")
404
+ else:
405
+ print(
406
+ f"FAILED - {len(self.errors)} error(s), {len(self.warnings)} warning(s)"
407
+ )
408
+ print("=" * 60 + "\n")
409
+
410
+
411
+ def validate_manifest(asm: Dict, result: ValidationResult):
412
+ """Check for valid manifest."""
413
+ if "$asm.manifest" not in asm:
414
+ result.add_error("Missing $asm.manifest")
415
+ return
416
+
417
+ manifest = asm["$asm.manifest"]
418
+ if isinstance(manifest, str):
419
+ if "allotrope.org" in manifest:
420
+ result.add_info(f"Manifest: {manifest}")
421
+ else:
422
+ result.add_warning(f"Non-standard manifest URL: {manifest}")
423
+ elif isinstance(manifest, dict):
424
+ if "vocabulary" in manifest or "contexts" in manifest:
425
+ result.add_info("Manifest: Object format with vocabulary/contexts")
426
+ else:
427
+ result.add_warning("Manifest object missing vocabulary or contexts")
428
+
429
+
430
+ def detect_technique(asm: Dict) -> Tuple[str, float]:
431
+ """Detect technique from ASM structure."""
432
+ # Check for technique in top-level keys
433
+ for key in asm.keys():
434
+ if key == "$asm.manifest":
435
+ continue
436
+ # Extract technique name from aggregate document key
437
+ # Handle both "liquid handler aggregate document" and "liquid-handler-aggregate-document"
438
+ key_normalized = key.lower().replace("-", " ")
439
+ if "aggregate document" in key_normalized:
440
+ technique = key_normalized.replace(" aggregate document", "").strip()
441
+ return technique, 100.0
442
+
443
+ return "unknown", 0.0
444
+
445
+
446
+ def validate_technique(asm: Dict, result: ValidationResult, content_str: str):
447
+ """Validate technique selection."""
448
+ technique, confidence = detect_technique(asm)
449
+ result.metrics["technique"] = technique
450
+ result.metrics["technique_confidence"] = confidence
451
+
452
+ if technique == "unknown":
453
+ result.add_warning("Could not detect technique from ASM structure")
454
+ return
455
+
456
+ result.add_info(f"Detected technique: {technique}")
457
+
458
+ # Check if technique is in known list (soft validation)
459
+ technique_normalized = technique.replace(" ", "-")
460
+ if technique_normalized not in VALID_TECHNIQUES:
461
+ result.add_warning(
462
+ f"Unknown technique '{technique}' not in known list (as of {VALIDATION_RULES_DATE}). "
463
+ f"This may be a new Allotrope addition. Verify at: {SCHEMA_SOURCE}"
464
+ )
465
+
466
+ # Check if technique seems appropriate for content
467
+ content_lower = content_str.lower()
468
+ suggested_technique = None
469
+
470
+ for tech, keywords in TECHNIQUE_INDICATORS.items():
471
+ matches = sum(1 for kw in keywords if kw in content_lower)
472
+ if matches >= 2: # Multiple keyword matches
473
+ if tech != technique.replace(" ", "-"):
474
+ suggested_technique = tech
475
+ break
476
+
477
+ if suggested_technique:
478
+ result.add_warning(
479
+ f"Content suggests '{suggested_technique}' but ASM uses '{technique}' - "
480
+ "verify correct technique selection"
481
+ )
482
+
483
+
484
+ def validate_naming_conventions(content_str: str, result: ValidationResult):
485
+ """Check for proper space-separated naming (not hyphens)."""
486
+ # Find all keys that look like ASM field names
487
+ # Hyphenated keys in ASM are typically wrong (should be space-separated)
488
+ hyphenated_keys = re.findall(r'"([a-z]+-[a-z]+-?[a-z]*-?[a-z]*)":', content_str)
489
+
490
+ # Filter to likely ASM fields (not URLs, not manifest)
491
+ asm_hyphenated = []
492
+ for key in hyphenated_keys:
493
+ if "http" in key or "manifest" in key:
494
+ continue
495
+ # Known hyphenated keys that are OK
496
+ if key in ["data-source-identifier", "data-source-feature"]:
497
+ continue
498
+ asm_hyphenated.append(key)
499
+
500
+ if asm_hyphenated:
501
+ unique = list(set(asm_hyphenated))[:10]
502
+ result.add_warning(
503
+ f"Found hyphenated field names (ASM uses spaces): {unique}"
504
+ + (" ... and more" if len(set(asm_hyphenated)) > 10 else "")
505
+ )
506
+ result.add_info("Tip: Use 'sample identifier' not 'sample-identifier'")
507
+
508
+
509
+ def count_measurements(content_str: str) -> int:
510
+ """Count measurement documents in ASM."""
511
+ # Count occurrences of measurement document patterns
512
+ count = len(re.findall(r'"measurement identifier":', content_str))
513
+ if count == 0:
514
+ count = len(re.findall(r'"measurement-identifier":', content_str))
515
+ return count
516
+
517
+
518
+ def validate_measurements(content_str: str, result: ValidationResult):
519
+ """Validate measurement documents."""
520
+ count = count_measurements(content_str)
521
+ result.metrics["measurement_count"] = count
522
+
523
+ if count == 0:
524
+ result.add_warning("No measurement documents found")
525
+ else:
526
+ result.add_info(f"Measurement count: {count}")
527
+
528
+
529
+ def validate_sample_roles(content_str: str, result: ValidationResult):
530
+ """Check for valid sample roles."""
531
+ roles = re.findall(r'"sample.role.type":\s*"([^"]+)"', content_str)
532
+ if not roles:
533
+ roles = re.findall(r'"sample role type":\s*"([^"]+)"', content_str)
534
+
535
+ if roles:
536
+ unknown_roles = [r for r in set(roles) if r not in VALID_SAMPLE_ROLES]
537
+ if unknown_roles:
538
+ result.add_warning(
539
+ f"Unknown sample roles not in known list (as of {VALIDATION_RULES_DATE}): {unknown_roles}. "
540
+ f"These may be valid Allotrope roles added after spec version {ASM_SPEC_VERSION}. "
541
+ f"Verify at: {SCHEMA_SOURCE}"
542
+ )
543
+
544
+
545
+ def validate_statistics(asm: Dict, content_str: str, result: ValidationResult):
546
+ """Check for statistics documents where expected."""
547
+ technique, _ = detect_technique(asm)
548
+
549
+ has_stats = (
550
+ "statistics aggregate document" in content_str.lower()
551
+ or "statistics-aggregate-document" in content_str
552
+ )
553
+
554
+ result.metrics["has_statistics"] = has_stats
555
+
556
+ # Statistics are required for multi-analyte profiling
557
+ if "multi analyte" in technique or "multiplex" in content_str.lower():
558
+ if not has_stats:
559
+ result.add_warning(
560
+ "No statistics aggregate document found - bead-based assays should include "
561
+ "median, mean, CV, std dev per analyte"
562
+ )
563
+ else:
564
+ result.add_info("Statistics document: Present")
565
+
566
+
567
+ def validate_units(content_str: str, result: ValidationResult):
568
+ """Check for valid units."""
569
+ # Find all unit values
570
+ units = re.findall(r'"unit":\s*"([^"]+)"', content_str)
571
+
572
+ # Check for common case-sensitivity issues
573
+ case_issues = []
574
+ for unit in set(units):
575
+ if unit.lower() in ["rfu", "mfi"] and unit not in ["RFU", "MFI"]:
576
+ case_issues.append(f"{unit} (should be uppercase)")
577
+ elif unit in ["ul", "uL", "µl"] and unit != "μL":
578
+ case_issues.append(f"{unit} (should be μL)")
579
+
580
+ if case_issues:
581
+ result.add_warning(f"Non-standard unit capitalization: {case_issues}")
582
+
583
+ # Soft validation: check against known units list
584
+ all_known_units = set()
585
+ for unit_list in VALID_UNITS.values():
586
+ all_known_units.update(unit_list)
587
+
588
+ unknown_units = []
589
+ for unit in set(units):
590
+ # Skip units that have case issues (already reported above)
591
+ if unit not in all_known_units and unit not in [u.lower() for u in case_issues]:
592
+ unknown_units.append(unit)
593
+
594
+ if unknown_units:
595
+ result.add_warning(
596
+ f"Unknown units not in known list (as of {VALIDATION_RULES_DATE}): {unknown_units}. "
597
+ f"These may be valid Allotrope units added after spec version {ASM_SPEC_VERSION}. "
598
+ f"Verify at: {SCHEMA_SOURCE}"
599
+ )
600
+
601
+
602
+ def validate_metadata(content_str: str, result: ValidationResult):
603
+ """Check for required metadata fields."""
604
+ required_fields = [
605
+ ("device system document", "equipment serial number"),
606
+ ("data system document", "software name"),
607
+ ("data system document", "software version"),
608
+ ]
609
+
610
+ missing = []
611
+ for _, field in required_fields:
612
+ if (
613
+ field not in content_str.lower()
614
+ and field.replace(" ", "-") not in content_str
615
+ ):
616
+ missing.append(field)
617
+
618
+ if missing:
619
+ result.add_warning(f"Missing recommended metadata: {missing}")
620
+
621
+
622
+ def validate_calculated_data(content_str: str, result: ValidationResult):
623
+ """Check calculated data has proper traceability."""
624
+ content_lower = content_str.lower()
625
+
626
+ has_calculated = (
627
+ "calculated data document" in content_lower
628
+ or "calculated-data-document" in content_str
629
+ )
630
+ has_data_source = (
631
+ "data source aggregate document" in content_lower
632
+ or "data-source-aggregate-document" in content_str
633
+ )
634
+
635
+ result.metrics["has_calculated_data"] = has_calculated
636
+ result.metrics["has_data_source_traceability"] = has_data_source
637
+
638
+ if has_calculated:
639
+ result.add_info("Calculated data document: Present")
640
+ if not has_data_source:
641
+ result.add_error(
642
+ "Calculated data found without data-source-aggregate-document - "
643
+ "traceability is required for audit/regulatory compliance"
644
+ )
645
+ else:
646
+ result.add_info("Data source traceability: Present")
647
+
648
+ # Check for calculated fields that might be incorrectly placed in measurement-document
649
+ misplaced = []
650
+ for field in SHOULD_BE_CALCULATED:
651
+ # Check if field appears in measurement document context but not in calculated data
652
+ field_pattern = field.replace("/", ".")
653
+ if field_pattern in content_lower:
654
+ # If we have the field but no calculated-data-document, it's misplaced
655
+ if not has_calculated:
656
+ misplaced.append(field)
657
+
658
+ if misplaced:
659
+ result.add_warning(
660
+ f"Fields that should likely be in calculated-data-document: {misplaced[:5]}"
661
+ + (f" ... and {len(misplaced)-5} more" if len(misplaced) > 5 else "")
662
+ )
663
+
664
+
665
+ def validate_unique_identifiers(content_str: str, result: ValidationResult):
666
+ """Validate that entities have unique identifiers for traceability."""
667
+ # Count different identifier types
668
+ measurement_ids = len(
669
+ re.findall(r'"measurement identifier":\s*"[^"]+"', content_str)
670
+ )
671
+ if measurement_ids == 0:
672
+ measurement_ids = len(
673
+ re.findall(r'"measurement-identifier":\s*"[^"]+"', content_str)
674
+ )
675
+
676
+ calculated_ids = len(
677
+ re.findall(r'"calculated data identifier":\s*"[^"]+"', content_str)
678
+ )
679
+ if calculated_ids == 0:
680
+ calculated_ids = len(
681
+ re.findall(r'"calculated-data-identifier":\s*"[^"]+"', content_str)
682
+ )
683
+
684
+ data_source_ids = len(
685
+ re.findall(r'"data source identifier":\s*"[^"]+"', content_str)
686
+ )
687
+ if data_source_ids == 0:
688
+ data_source_ids = len(
689
+ re.findall(r'"data-source-identifier":\s*"[^"]+"', content_str)
690
+ )
691
+
692
+ result.metrics["measurement_identifiers"] = measurement_ids
693
+ result.metrics["calculated_data_identifiers"] = calculated_ids
694
+ result.metrics["data_source_identifiers"] = data_source_ids
695
+
696
+ if measurement_ids == 0:
697
+ result.add_warning(
698
+ "No measurement identifiers found - required for traceability"
699
+ )
700
+
701
+ # If we have calculated data but no data source identifiers, that's a problem
702
+ if calculated_ids > 0 and data_source_ids == 0:
703
+ result.add_error(
704
+ f"Found {calculated_ids} calculated data entries but no data source identifiers - "
705
+ "each calculated value should reference its source"
706
+ )
707
+
708
+
709
+ # =============================================================================
710
+ # NEW: NESTED DOCUMENT STRUCTURE VALIDATION
711
+ # =============================================================================
712
+
713
+
714
+ def validate_nested_document_structure(
715
+ asm: Dict, content_str: str, result: ValidationResult
716
+ ):
717
+ """
718
+ Validate that fields are properly nested in their correct documents.
719
+
720
+ This checks for common mistakes like:
721
+ - Sample fields flattened directly onto measurement instead of in 'sample document'
722
+ - Device control fields flattened instead of in 'device control aggregate document'
723
+ - Custom/vendor fields not wrapped in 'custom information document'
724
+ """
725
+ content_lower = content_str.lower()
726
+
727
+ # Check if proper nested documents exist
728
+ has_sample_doc = (
729
+ '"sample document"' in content_lower or '"sample-document"' in content_str
730
+ )
731
+ has_device_control_doc = (
732
+ '"device control aggregate document"' in content_lower
733
+ or '"device-control-aggregate-document"' in content_str
734
+ )
735
+ has_custom_info_doc = (
736
+ '"custom information document"' in content_lower
737
+ or '"custom-information-document"' in content_str
738
+ )
739
+
740
+ result.metrics["has_sample_document"] = has_sample_doc
741
+ result.metrics["has_device_control_document"] = has_device_control_doc
742
+ result.metrics["has_custom_information_document"] = has_custom_info_doc
743
+
744
+ # Parse ASM to check field locations
745
+ def find_flattened_fields_in_measurements(obj, path=""):
746
+ """Recursively find fields that appear directly on measurement documents."""
747
+ issues = {"sample": [], "device_control": [], "custom": []}
748
+
749
+ if isinstance(obj, dict):
750
+ # Check if we're inside a measurement document
751
+ in_measurement = (
752
+ "measurement document" in path.lower() or "measurement-document" in path
753
+ )
754
+ in_sample_doc = (
755
+ "sample document" in path.lower() or "sample-document" in path
756
+ )
757
+ in_device_control = (
758
+ "device control" in path.lower() or "device-control" in path
759
+ )
760
+ in_custom_info = (
761
+ "custom information" in path.lower() or "custom-information" in path
762
+ )
763
+
764
+ for key, value in obj.items():
765
+ key_normalized = key.lower().replace("-", " ")
766
+ new_path = f"{path}.{key}"
767
+
768
+ # Check if this key should be nested but isn't
769
+ if in_measurement and not in_sample_doc:
770
+ if key_normalized in [
771
+ f.lower().replace("-", " ") for f in SAMPLE_DOCUMENT_FIELDS
772
+ ]:
773
+ issues["sample"].append(key)
774
+
775
+ if in_measurement and not in_device_control:
776
+ if key_normalized in [
777
+ f.lower().replace("-", " ") for f in DEVICE_CONTROL_FIELDS
778
+ ]:
779
+ issues["device_control"].append(key)
780
+
781
+ if in_measurement and not in_custom_info:
782
+ if key_normalized in [
783
+ f.lower().replace("-", " ") for f in CUSTOM_INFO_FIELDS
784
+ ]:
785
+ issues["custom"].append(key)
786
+
787
+ # Recurse
788
+ child_issues = find_flattened_fields_in_measurements(value, new_path)
789
+ for k in issues:
790
+ issues[k].extend(child_issues[k])
791
+
792
+ elif isinstance(obj, list):
793
+ for i, item in enumerate(obj):
794
+ child_issues = find_flattened_fields_in_measurements(
795
+ item, f"{path}[{i}]"
796
+ )
797
+ for k in issues:
798
+ issues[k].extend(child_issues[k])
799
+
800
+ return issues
801
+
802
+ issues = find_flattened_fields_in_measurements(asm)
803
+ flattened_sample_fields = list(set(issues["sample"]))
804
+ flattened_device_control_fields = list(set(issues["device_control"]))
805
+ flattened_custom_fields = list(set(issues["custom"]))
806
+
807
+ # Report issues
808
+ if flattened_sample_fields:
809
+ result.add_error(
810
+ f"Fields that should be nested in 'sample document' are flattened on measurement: "
811
+ f"{flattened_sample_fields[:5]}"
812
+ + (
813
+ f" ... and {len(flattened_sample_fields)-5} more"
814
+ if len(flattened_sample_fields) > 5
815
+ else ""
816
+ )
817
+ )
818
+ result.add_info(
819
+ "Tip: Wrap sample fields in a 'sample document' object inside each measurement"
820
+ )
821
+
822
+ if flattened_device_control_fields:
823
+ result.add_error(
824
+ f"Fields that should be nested in 'device control aggregate document' are flattened: "
825
+ f"{flattened_device_control_fields[:5]}"
826
+ + (
827
+ f" ... and {len(flattened_device_control_fields)-5} more"
828
+ if len(flattened_device_control_fields) > 5
829
+ else ""
830
+ )
831
+ )
832
+ result.add_info(
833
+ "Tip: Wrap device control fields in 'device control aggregate document' → 'device control document'"
834
+ )
835
+
836
+ if flattened_custom_fields:
837
+ result.add_warning(
838
+ f"Vendor-specific fields that should be in 'custom information document': "
839
+ f"{flattened_custom_fields[:5]}"
840
+ + (
841
+ f" ... and {len(flattened_custom_fields)-5} more"
842
+ if len(flattened_custom_fields) > 5
843
+ else ""
844
+ )
845
+ )
846
+
847
+
848
+ def validate_liquid_handler_structure(
849
+ asm: Dict, content_str: str, result: ValidationResult
850
+ ):
851
+ """
852
+ Specific validation for liquid handler ASM documents.
853
+
854
+ Checks for:
855
+ - Proper transfer pairing (aspirate + dispense = 1 measurement)
856
+ - Source/destination field pairs
857
+ - Aspiration volume + transfer volume instead of single volume
858
+ """
859
+ technique, _ = detect_technique(asm)
860
+
861
+ # Only run for liquid handler techniques
862
+ if "liquid" not in technique.lower() and "handler" not in technique.lower():
863
+ # Also check content for liquid handler indicators
864
+ content_lower = content_str.lower()
865
+ if not any(
866
+ kw in content_lower
867
+ for kw in ["aspirate", "dispense", "liquid handler", "biomek"]
868
+ ):
869
+ return
870
+
871
+ result.add_info("Liquid handler specific validation...")
872
+
873
+ content_lower = content_str.lower()
874
+
875
+ # Check for proper volume field structure
876
+ has_aspiration_volume = (
877
+ "aspiration volume" in content_lower or "aspiration-volume" in content_str
878
+ )
879
+ has_transfer_volume = (
880
+ "transfer volume" in content_lower or "transfer-volume" in content_str
881
+ )
882
+ has_single_volume = (
883
+ '"volume"' in content_str
884
+ and not has_aspiration_volume
885
+ and not has_transfer_volume
886
+ )
887
+
888
+ if has_single_volume and not has_aspiration_volume:
889
+ result.add_warning(
890
+ "Liquid handler ASM uses single 'volume' field - "
891
+ "consider using 'aspiration volume' and 'transfer volume' for full transfer semantics"
892
+ )
893
+
894
+ if has_aspiration_volume and has_transfer_volume:
895
+ result.add_info("Volume fields: Proper aspiration/transfer volume structure")
896
+
897
+ # Check for source/destination pairing
898
+ has_source_dest = (
899
+ "source location" in content_lower or "source-location" in content_str
900
+ ) and (
901
+ "destination location" in content_lower or "destination-location" in content_str
902
+ )
903
+
904
+ has_separate_transfer_type = (
905
+ "transfer type" in content_lower or "transfer-type" in content_str
906
+ )
907
+
908
+ if has_separate_transfer_type and not has_source_dest:
909
+ result.add_warning(
910
+ "Found 'transfer type' field (Aspirate/Dispense as separate records) - "
911
+ "proper ASM pairs source→destination in single measurement with 'source location identifier' "
912
+ "and 'destination location identifier'"
913
+ )
914
+ result.add_info(
915
+ "Tip: Pair aspirate+dispense operations by probe number into single transfer measurements"
916
+ )
917
+
918
+ if has_source_dest:
919
+ result.add_info("Source/destination: Proper paired transfer structure")
920
+
921
+ # Check for labware name fields in custom information document
922
+ has_labware_names = (
923
+ "source labware name" in content_lower
924
+ or "destination labware name" in content_lower
925
+ )
926
+
927
+ if has_labware_names:
928
+ result.add_info(
929
+ "Labware names: Present (should be in custom information document)"
930
+ )
931
+
932
+
933
+ def compare_to_reference(
934
+ asm: Dict,
935
+ reference: Dict,
936
+ content_str: str,
937
+ ref_content: str,
938
+ result: ValidationResult,
939
+ ):
940
+ """Compare generated ASM to reference ASM."""
941
+ result.add_info("Comparing to reference ASM...")
942
+
943
+ # Compare techniques
944
+ gen_tech, _ = detect_technique(asm)
945
+ ref_tech, _ = detect_technique(reference)
946
+
947
+ if gen_tech.replace("-", " ") != ref_tech.replace("-", " "):
948
+ result.add_error(
949
+ f"Technique mismatch: generated '{gen_tech}' vs reference '{ref_tech}'"
950
+ )
951
+
952
+ # Compare measurement counts
953
+ gen_count = count_measurements(content_str)
954
+ ref_count = count_measurements(ref_content)
955
+
956
+ result.metrics["reference_measurement_count"] = ref_count
957
+
958
+ if gen_count != ref_count:
959
+ diff = ref_count - gen_count
960
+ if diff > 0:
961
+ result.add_error(
962
+ f"Missing {diff} measurements: generated {gen_count} vs reference {ref_count}"
963
+ )
964
+ else:
965
+ result.add_warning(
966
+ f"Extra {-diff} measurements: generated {gen_count} vs reference {ref_count}"
967
+ )
968
+
969
+ # Compare sample roles
970
+ gen_roles = set(re.findall(r'"sample.role.type":\s*"([^"]+)"', content_str))
971
+ ref_roles = set(re.findall(r'"sample role type":\s*"([^"]+)"', ref_content))
972
+
973
+ missing_roles = ref_roles - gen_roles
974
+ if missing_roles:
975
+ result.add_warning(f"Missing sample roles from reference: {missing_roles}")
976
+
977
+ # Compare nested document presence
978
+ ref_has_sample_doc = '"sample document"' in ref_content.lower()
979
+ gen_has_sample_doc = (
980
+ '"sample document"' in content_str.lower() or '"sample-document"' in content_str
981
+ )
982
+
983
+ if ref_has_sample_doc and not gen_has_sample_doc:
984
+ result.add_error(
985
+ "Reference has 'sample document' but generated ASM does not - fields may be incorrectly flattened"
986
+ )
987
+
988
+ ref_has_device_control = (
989
+ '"device control aggregate document"' in ref_content.lower()
990
+ )
991
+ gen_has_device_control = (
992
+ '"device control aggregate document"' in content_str.lower()
993
+ or '"device-control-aggregate-document"' in content_str
994
+ )
995
+
996
+ if ref_has_device_control and not gen_has_device_control:
997
+ result.add_error(
998
+ "Reference has 'device control aggregate document' but generated ASM does not"
999
+ )
1000
+
1001
+ ref_has_custom_info = '"custom information document"' in ref_content.lower()
1002
+ gen_has_custom_info = (
1003
+ '"custom information document"' in content_str.lower()
1004
+ or '"custom-information-document"' in content_str
1005
+ )
1006
+
1007
+ if ref_has_custom_info and not gen_has_custom_info:
1008
+ result.add_warning(
1009
+ "Reference has 'custom information document' for vendor fields but generated ASM does not"
1010
+ )
1011
+
1012
+
1013
+ def validate_asm(
1014
+ filepath: str, reference_path: Optional[str] = None, strict: bool = False
1015
+ ) -> ValidationResult:
1016
+ """
1017
+ Validate ASM JSON file.
1018
+
1019
+ Args:
1020
+ filepath: Path to ASM JSON file
1021
+ reference_path: Optional path to reference ASM for comparison
1022
+ strict: If True, treat warnings as errors
1023
+
1024
+ Returns:
1025
+ ValidationResult with errors, warnings, and metrics
1026
+ """
1027
+ result = ValidationResult()
1028
+
1029
+ # Load ASM file
1030
+ try:
1031
+ with open(filepath, "r", encoding="utf-8") as f:
1032
+ content_str = f.read()
1033
+ asm = json.loads(content_str)
1034
+ except json.JSONDecodeError as e:
1035
+ result.add_error(f"Invalid JSON: {e}")
1036
+ return result
1037
+ except FileNotFoundError:
1038
+ result.add_error(f"File not found: {filepath}")
1039
+ return result
1040
+
1041
+ result.add_info(f"Validating: {filepath}")
1042
+
1043
+ # Run validations
1044
+ validate_manifest(asm, result)
1045
+ validate_technique(asm, result, content_str)
1046
+ validate_naming_conventions(content_str, result)
1047
+ validate_measurements(content_str, result)
1048
+ validate_sample_roles(content_str, result)
1049
+ validate_statistics(asm, content_str, result)
1050
+ validate_units(content_str, result)
1051
+ validate_metadata(content_str, result)
1052
+ validate_calculated_data(content_str, result)
1053
+ validate_unique_identifiers(content_str, result)
1054
+
1055
+ # NEW: Nested document structure validation
1056
+ validate_nested_document_structure(asm, content_str, result)
1057
+ validate_liquid_handler_structure(asm, content_str, result)
1058
+
1059
+ # Compare to reference if provided
1060
+ if reference_path:
1061
+ try:
1062
+ with open(reference_path, "r", encoding="utf-8") as f:
1063
+ ref_content = f.read()
1064
+ reference = json.loads(ref_content)
1065
+ compare_to_reference(asm, reference, content_str, ref_content, result)
1066
+ except Exception as e:
1067
+ result.add_warning(f"Could not load reference file: {e}")
1068
+
1069
+ # In strict mode, convert warnings to errors
1070
+ if strict:
1071
+ result.errors.extend([w.replace("WARNING", "ERROR") for w in result.warnings])
1072
+ result.warnings = []
1073
+
1074
+ return result
1075
+
1076
+
1077
+ def main():
1078
+ parser = argparse.ArgumentParser(description="Validate ASM JSON output")
1079
+ parser.add_argument("input", help="ASM JSON file to validate")
1080
+ parser.add_argument("--reference", "-r", help="Reference ASM file for comparison")
1081
+ parser.add_argument(
1082
+ "--strict", "-s", action="store_true", help="Treat warnings as errors"
1083
+ )
1084
+ parser.add_argument("--quiet", "-q", action="store_true", help="Only show errors")
1085
+
1086
+ args = parser.parse_args()
1087
+
1088
+ result = validate_asm(args.input, args.reference, args.strict)
1089
+
1090
+ if args.quiet:
1091
+ if result.errors:
1092
+ for error in result.errors:
1093
+ print(error)
1094
+ sys.exit(1)
1095
+ sys.exit(0)
1096
+
1097
+ result.print_report()
1098
+ sys.exit(0 if result.is_valid() else 1)
1099
+
1100
+
1101
+ if __name__ == "__main__":
1102
+ main()