seqforge 2026.7.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. seqforge/__init__.py +16 -0
  2. seqforge/cli/__init__.py +38 -0
  3. seqforge/cli/__main__.py +8 -0
  4. seqforge/cli/_common.py +105 -0
  5. seqforge/cli/compose.py +119 -0
  6. seqforge/cli/eval.py +103 -0
  7. seqforge/cli/harvest.py +417 -0
  8. seqforge/cli/hook.py +247 -0
  9. seqforge/cli/io.py +502 -0
  10. seqforge/cli/kb.py +348 -0
  11. seqforge/cli/manifest.py +536 -0
  12. seqforge/cli/probe.py +43 -0
  13. seqforge/cli/processing.py +192 -0
  14. seqforge/cli/project.py +52 -0
  15. seqforge/cli/resolve.py +55 -0
  16. seqforge/cli/root.py +66 -0
  17. seqforge/cli/run.py +463 -0
  18. seqforge/cli/schema.py +41 -0
  19. seqforge/compose/__init__.py +28 -0
  20. seqforge/compose/core.py +515 -0
  21. seqforge/compose/gates.py +113 -0
  22. seqforge/compose/params.py +447 -0
  23. seqforge/e2e.py +1926 -0
  24. seqforge/evals/__init__.py +78 -0
  25. seqforge/evals/case.py +382 -0
  26. seqforge/evals/grade.py +300 -0
  27. seqforge/evals/run.py +420 -0
  28. seqforge/harvest/__init__.py +121 -0
  29. seqforge/harvest/extract.py +319 -0
  30. seqforge/harvest/fields.py +212 -0
  31. seqforge/harvest/normalize.py +537 -0
  32. seqforge/harvest/prep.py +41 -0
  33. seqforge/harvest/providers.py +321 -0
  34. seqforge/harvest/verify.py +251 -0
  35. seqforge/hooks/__init__.py +33 -0
  36. seqforge/hooks/guards.py +214 -0
  37. seqforge/io/__init__.py +61 -0
  38. seqforge/io/archive.py +450 -0
  39. seqforge/io/attributes.py +190 -0
  40. seqforge/io/biosample/attributes.json +6341 -0
  41. seqforge/io/efo/labels.json +55 -0
  42. seqforge/io/efo.py +138 -0
  43. seqforge/io/onlist.py +661 -0
  44. seqforge/io/onlists/3M-february-2018.codes.gz +0 -0
  45. seqforge/io/onlists/737K-arc-v1.codes.gz +0 -0
  46. seqforge/io/onlists/737K-august-2016.codes.gz +0 -0
  47. seqforge/io/onlists/bd-rhapsody-cls1-384.codes.gz +0 -0
  48. seqforge/io/onlists/bd-rhapsody-cls1.codes.gz +0 -0
  49. seqforge/io/onlists/bd-rhapsody-cls2-384.codes.gz +0 -0
  50. seqforge/io/onlists/bd-rhapsody-cls2.codes.gz +0 -0
  51. seqforge/io/onlists/bd-rhapsody-cls3-384.codes.gz +0 -0
  52. seqforge/io/onlists/bd-rhapsody-cls3.codes.gz +0 -0
  53. seqforge/io/onlists/index.json +74 -0
  54. seqforge/io/remote.py +659 -0
  55. seqforge/io/taxonomy.py +194 -0
  56. seqforge/kb/__init__.py +62 -0
  57. seqforge/kb/anchor.py +169 -0
  58. seqforge/kb/generate.py +147 -0
  59. seqforge/kb/loader.py +152 -0
  60. seqforge/kb/roundtrip.py +112 -0
  61. seqforge/kb/schema.py +422 -0
  62. seqforge/kb/specs/10x-3p-gex/spec.yaml +62 -0
  63. seqforge/kb/specs/10x-3p-gex-v2/README.md +41 -0
  64. seqforge/kb/specs/10x-3p-gex-v2/spec.yaml +83 -0
  65. seqforge/kb/specs/10x-3p-gex-v3/README.md +56 -0
  66. seqforge/kb/specs/10x-3p-gex-v3/spec.yaml +118 -0
  67. seqforge/kb/specs/10x-3p-gex-v3.1/README.md +56 -0
  68. seqforge/kb/specs/10x-3p-gex-v3.1/spec.yaml +124 -0
  69. seqforge/kb/specs/bd-rhapsody-wta/README.md +103 -0
  70. seqforge/kb/specs/bd-rhapsody-wta/spec.yaml +130 -0
  71. seqforge/kb/specs/bd-rhapsody-wta-enhanced/spec.yaml +99 -0
  72. seqforge/kb/specs/bd-rhapsody-wta-enhanced-v1/spec.yaml +93 -0
  73. seqforge/kb/specs/bd-rhapsody-wta-enhanced-v2/spec.yaml +81 -0
  74. seqforge/kb/specs/bulk-rnaseq-pe/README.md +35 -0
  75. seqforge/kb/specs/bulk-rnaseq-pe/spec.yaml +97 -0
  76. seqforge/kb/specs/splitseq/README.md +51 -0
  77. seqforge/kb/specs/splitseq/spec.yaml +157 -0
  78. seqforge/manifest/__init__.py +61 -0
  79. seqforge/manifest/fill.py +531 -0
  80. seqforge/manifest/hash.py +77 -0
  81. seqforge/manifest/instruct.py +114 -0
  82. seqforge/manifest/policy.py +409 -0
  83. seqforge/manifest/validate.py +274 -0
  84. seqforge/models/__init__.py +268 -0
  85. seqforge/models/assertion.py +68 -0
  86. seqforge/models/base.py +100 -0
  87. seqforge/models/blocker.py +71 -0
  88. seqforge/models/conflict.py +47 -0
  89. seqforge/models/dataset.py +320 -0
  90. seqforge/models/evidenced.py +54 -0
  91. seqforge/models/observation.py +157 -0
  92. seqforge/models/processing.py +231 -0
  93. seqforge/models/records.py +145 -0
  94. seqforge/models/resolve.py +216 -0
  95. seqforge/probe/__init__.py +46 -0
  96. seqforge/probe/core.py +232 -0
  97. seqforge/probe/signals.py +250 -0
  98. seqforge/probe/streaming.py +118 -0
  99. seqforge/project.py +177 -0
  100. seqforge/py.typed +0 -0
  101. seqforge/resolve/__init__.py +98 -0
  102. seqforge/resolve/assign.py +204 -0
  103. seqforge/resolve/cache.py +119 -0
  104. seqforge/resolve/confuse.py +215 -0
  105. seqforge/resolve/engine.py +646 -0
  106. seqforge/resolve/escalate.py +668 -0
  107. seqforge/resolve/evaluators.py +306 -0
  108. seqforge/resolve/geometry.py +89 -0
  109. seqforge/resolve/group.py +85 -0
  110. seqforge/resolve/records.py +550 -0
  111. seqforge/resolve/scoring.py +373 -0
  112. seqforge/resolve/window.py +206 -0
  113. seqforge/workflows/__init__.py +234 -0
  114. seqforge/workflows/cram.py +117 -0
  115. seqforge/workflows/h5ad.py +368 -0
  116. seqforge/workflows/map/star.smk +101 -0
  117. seqforge/workflows/map/starsolo.smk +360 -0
  118. seqforge/workflows/qc.py +157 -0
  119. seqforge/workspace.py +125 -0
  120. seqforge-2026.7.1.dist-info/METADATA +125 -0
  121. seqforge-2026.7.1.dist-info/RECORD +124 -0
  122. seqforge-2026.7.1.dist-info/WHEEL +4 -0
  123. seqforge-2026.7.1.dist-info/entry_points.txt +2 -0
  124. seqforge-2026.7.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,130 @@
1
+ schema_version: 1
2
+
3
+ # ============================================================================================
4
+ # BD RHAPSODY WTA (Whole Transcriptome Analysis) — the ORIGINAL fixed-offset cell-label bead.
5
+ #
6
+ # Read structure, linkers, whitelists and strand pinned from primary sources (never memory):
7
+ # - scg_lib_structs (CC-BY): https://teichlab.github.io/scg_lib_structs/methods_html/BD_Rhapsody.html
8
+ # - seqspec assay example: docs/examples/assays/bd_rhapsody_v1.spec.yaml (pachterlab/seqspec)
9
+ # - STARsolo maintainer-endorsed position string: github.com/alexdobin/STAR/issues/1111, #1607
10
+ # - EFO term verified live on EBI OLS4.
11
+ #
12
+ # Read 1 (the BEAD read), 5'->3' [structural length 60 bp; R1 is sequenced longer, into poly(T)/cDNA]:
13
+ # [CLS1 9bp][linker1 12bp][CLS2 9bp][linker2 13bp][CLS3 9bp][UMI 8bp]
14
+ # offsets: CLS1 [0,9) linker1 [9,21) CLS2 [21,30) linker2 [30,43) CLS3 [43,52) UMI [52,60)
15
+ # Read 2 = the cDNA read (top strand as template — sense to the mRNA, see soloStrand).
16
+ # The three cell-label sets (CLS1/CLS2/CLS3) are 97 x 9 bp EACH (BD publishes 97 per pool, not 96 —
17
+ # verified by downloading the shipped lists; see README). soloCBwhitelist order == CB-position order.
18
+ #
19
+ # THE VARIANT DISCRIMINATOR (the thing most likely to be silently mismatched): this entry is the
20
+ # ORIGINAL cell-label bead, whose CLS/UMI sit at FIXED offsets. The 2022 "Enhanced" bead prepends a
21
+ # VARIABLE-length diversity insert (0-3 bp) before CLS1 and shortens both linkers to 4 bp
22
+ # (GTGA / GACA) — that shifts every downstream offset and is NOT a fixed CB_UMI_Complex. seqforge
23
+ # models fixed-offset elements only, so the Enhanced bead needs anchored-element support first and is
24
+ # deliberately OUT OF SCOPE here — a separate future entry, never conflated with this one.
25
+ # ============================================================================================
26
+
27
+ identity:
28
+ id: bd-rhapsody-wta
29
+ version: v1-cell-label # the original fixed-offset bead (NOT the Enhanced variable bead)
30
+ name: "BD Rhapsody WTA (3' whole-transcriptome, original cell-label bead)"
31
+ aliases: ["BD Rhapsody", "Rhapsody", "BD Rhapsody WTA", "BD Rhapsody Whole Transcriptome Analysis",
32
+ "Rhapsody WTA", "BD Rhapsody Single-Cell"]
33
+ assay_ontology: ["EFO:0700003"] # "BD Rhapsody Whole Transcriptome Analysis" — verified via
34
+ # EBI OLS4 (obo_id=EFO:0700003). NB EFO:0700004 is the
35
+ # Targeted-mRNA panel, a different assay — never this one.
36
+ modality: rna
37
+
38
+ reads:
39
+ # Read 2 (cDNA). BD/STARsolo pass cDNA first (--readFilesIn R2 R1); the role is decided by the
40
+ # elements here, not by the read id, so declaring cdna on R2 is all that is needed.
41
+ - id: cdna
42
+ seqspec_read_id: R2
43
+ file_hint: "_R2_"
44
+ strand: pos
45
+ min_len: 25
46
+ max_len: null
47
+ elements:
48
+ - {type: cdna, name: cdna, start: 0, end: null, seqspec_region_type: cdna}
49
+ # Read 1 (the bead read): three fixed 9 bp cell-label blocks split by two fixed linkers, then an
50
+ # 8 bp UMI. `max_len: null` because R1 is over-sequenced past byte 60 into poly(T)/cDNA — CB/UMI are
51
+ # read from their fixed offsets and the tail is ignored (the two constant linkers are the signature,
52
+ # so no segment_length gate is needed to stay separable).
53
+ - id: bc
54
+ seqspec_read_id: R1
55
+ file_hint: "_R1_"
56
+ strand: pos
57
+ min_len: 60
58
+ max_len: null
59
+ elements:
60
+ - {type: barcode, name: cls1, start: 0, end: 9, onlist: cls1, seqspec_region_type: barcode}
61
+ # linker1 = the CLS1->CLS2 spacer (verbatim from scg_lib_structs / seqspec bd_rhapsody_v1)
62
+ - {type: linker, name: linker1, start: 9, end: 21, seqspec_region_type: linker,
63
+ sequence: "ACTGGCCTGCGA"}
64
+ - {type: barcode, name: cls2, start: 21, end: 30, onlist: cls2, seqspec_region_type: barcode}
65
+ # linker2 = the CLS2->CLS3 spacer (verbatim from scg_lib_structs / seqspec bd_rhapsody_v1)
66
+ - {type: linker, name: linker2, start: 30, end: 43, seqspec_region_type: linker,
67
+ sequence: "GGTAGCGGTGACA"}
68
+ - {type: barcode, name: cls3, start: 43, end: 52, onlist: cls3, seqspec_region_type: barcode}
69
+ - {type: umi, name: UMI, start: 52, end: 60, seqspec_region_type: umi}
70
+
71
+ onlists:
72
+ cls1: {registry: bd-rhapsody-cls1, role: cell_barcode, expected_orientation: forward} # 97 x 9 bp
73
+ cls2: {registry: bd-rhapsody-cls2, role: cell_barcode, expected_orientation: forward}
74
+ cls3: {registry: bd-rhapsody-cls3, role: cell_barcode, expected_orientation: forward}
75
+ tenx_probe: {registry: 3M-february-2018, role: cell_barcode, expected_orientation: forward} # exclude only
76
+
77
+ signature:
78
+ requires: # the two FIXED linkers are the structural signature
79
+ - {test: read_count, roles: 2}
80
+ - {test: has_segment, read: bc, start: 9, end: 21, kind: constant} # linker1 (12 bp)
81
+ - {test: has_segment, read: bc, start: 30, end: 43, kind: constant} # linker2 (13 bp)
82
+ supports: # each CLS block hits its own 97 x 9 bp whitelist
83
+ - when: {test: onlist_hit_rate, read: bc, element: cls1, onlist: cls1, orientation: forward, min: 0.5}
84
+ weight: 3.0
85
+ - when: {test: onlist_hit_rate, read: bc, element: cls2, onlist: cls2, orientation: forward, min: 0.5}
86
+ weight: 3.0
87
+ - when: {test: onlist_hit_rate, read: bc, element: cls3, onlist: cls3, orientation: forward, min: 0.5}
88
+ weight: 3.0
89
+ - when: {test: distinct_ratio, read: bc, start: 52, end: 60, expect: high} # UMI ~unique
90
+ weight: 1.0
91
+ - when: {test: distinct_ratio, read: cdna, start: 0, end: 20, expect: high} # cDNA role is scored too
92
+ weight: 1.0
93
+ excludes: # a 16 bp 10x CB at read start => not BD Rhapsody
94
+ - {test: onlist_hit_rate, read: bc, start: 0, end: 16, onlist: tenx_probe, orientation: forward, min: 0.6}
95
+
96
+ backend:
97
+ module: map/starsolo
98
+ params:
99
+ soloType: CB_UMI_Complex
100
+ soloCBwhitelist: ["{onlist:cls1}", "{onlist:cls2}", "{onlist:cls3}"] # order == CB-position order
101
+ # soloStrand: DERIVED, not pattern-matched (a wrong strand leaves most reads unassigned while
102
+ # STARsolo exits 0 and emits a matrix that merely looks thin — neither gate can catch it).
103
+ # DERIVATION: the bead oligo terminates in (dT)18 at its 3' end; that poly(dT) primes reverse
104
+ # transcription off the mRNA poly(A) tail, so R2 sequences the cDNA in the SAME orientation as the
105
+ # original mRNA — a standard 3' poly-dT capture library, exactly like 10x Chromium 3'. STARsolo
106
+ # defines Forward as "read strand same as the original RNA molecule" -> Forward (also its default).
107
+ # CORROBORATION: a full working BD Rhapsody STARsolo command sets --soloStrand Forward
108
+ # (github.com/alexdobin/STAR/issues/1607), and scg_lib_structs runs BD with STARsolo's Forward
109
+ # default. No source claims Reverse.
110
+ soloStrand: Forward
111
+ # soloCBposition / soloUMIposition are DERIVED from the element coordinates at compose time
112
+ # (compose/params.py::derived_params) -> "0_0_0_8 0_21_0_29 0_43_0_51" and UMI "0_52_0_59", with
113
+ # soloUMIlen 8. That quadruple set is byte-identical to the maintainer-endorsed STARsolo string
114
+ # on issue #1111, which is an independent cross-check on the geometry above. Never hand-enter it.
115
+ # soloFeatures is NOT here: it says what to COUNT (cells vs nuclei is sample prep, not chemistry),
116
+ # and lives in the processing manifest where a user may instruct it.
117
+
118
+ # The combinatorial CLS geometry + the two fixed linkers separate this from 10x / SPLiT-seq (different
119
+ # linker sequences and positions). What it does NOT separate itself from is the generic paired-end
120
+ # fallback, which forbids so little it accepts these two files at rungs 0-2 — the confusion runs in
121
+ # that direction, the relation is symmetric, so it is declared on both sides (see bulk-rnaseq-pe).
122
+ confusable_with:
123
+ - id: bulk-rnaseq-pe
124
+ relationship: processing_divergent
125
+ distinguishable_by: [onlist]
126
+ note: >
127
+ The generic paired-end fallback accepts cdna+bc on geometry alone. Rung 3 decides it: the three
128
+ CLS whitelists hit, and bulk has no whitelist to hit. Maximally divergent downstream
129
+ (map/starsolo with three cell-label rounds vs map/star with none), so a silent preference would
130
+ emit a bulk gene-count matrix for a single-cell library and exit 0.
@@ -0,0 +1,99 @@
1
+ schema_version: 1
2
+
3
+ # ============================================================================================
4
+ # BD RHAPSODY WTA — ENHANCED CELL CAPTURE BEAD (2022, "v2.0"): the ABSTRACT FAMILY NODE.
5
+ #
6
+ # The Enhanced bead prepends a VARIABLE-LENGTH 0-3 bp diversity insert to Read 1, so every cell-label
7
+ # block downstream STAGGERS per read — the fixed-offset original bead (`bd-rhapsody-wta`) cannot express
8
+ # this. Read 1 (5'->3'):
9
+ # [VB 0-3bp][CLS1 9][GTGA][CLS2 9][GACA][CLS3 9][UMI 8](dT)... (R1 over-sequenced into poly(T))
10
+ # The two 4 bp linkers GTGA / GACA (vs the original bead's 12/13 bp linkers) at their staggered
11
+ # positions are the byte signature. It recognises the bead by phase-locking that GTGA...GACA frame
12
+ # (`motif_present`, tolerant of the 0-3 bp stagger + 2 substitutions), exactly as rhapsodist does by
13
+ # Hamming-matching the linkers across a range(4) offset scan (workflow/src/workflow_functions.py).
14
+ #
15
+ # This node is a FAMILY, not a runnable chemistry: the two Enhanced sub-versions differ ONLY in
16
+ # whitelist size (96 vs 384 sequences per block) — an ONLIST-decided split, exactly like 10x v2/v3 — so
17
+ # there is no single recipe here. Descent resolves to a leaf:
18
+ # bd-rhapsody-wta-enhanced-v1 (97 x 3 cell-label lists, == the original bead's lists)
19
+ # bd-rhapsody-wta-enhanced-v2 (384 x 3 cell-label lists)
20
+ # It is NEVER scored as a candidate (the scorer skips backend-less nodes) and self-tests by RECOGNITION
21
+ # (test_a_family_node_recognizes_its_children_and_no_one_else), not `kb roundtrip`.
22
+ #
23
+ # Sources (never memory): read structure + linkers from the BD Rhapsody Enhanced v2.0 protocol and
24
+ # rhapsodist (imallona/rhapsodist, Moro & Mallona 2025); STARsolo adapter-anchor recipe endorsed by
25
+ # @alexdobin on github.com/alexdobin/STAR/issues/1607.
26
+ # ============================================================================================
27
+
28
+ identity:
29
+ id: bd-rhapsody-wta-enhanced
30
+ version: enhanced-family
31
+ name: "BD Rhapsody WTA — Enhanced Cell Capture Bead (family)"
32
+ aliases: ["BD Rhapsody Enhanced", "Rhapsody Enhanced", "BD Rhapsody Enhanced Beads",
33
+ "BD Enhanced Cell Capture Beads", "BD Rhapsody WTA Enhanced"]
34
+ assay_ontology: ["EFO:0700003"] # BD Rhapsody WTA — the assay the whole family instantiates
35
+ modality: rna
36
+
37
+ node_kind: family
38
+ # The children (96 / 384) share the byte-identical Enhanced geometry and are told apart ONLY by which
39
+ # cell-label whitelist their CLS blocks hit — the two pools are DISJOINT (0 shared 9-mers), so the
40
+ # onlist separates them cleanly at rung 3, exactly the v2/v3 pattern.
41
+ children_decided_by: [onlist]
42
+
43
+ reads:
44
+ # Read 2 = the cDNA (open-ended, sense to the mRNA — 3' poly-dT capture, like the original bead).
45
+ - id: cdna
46
+ seqspec_read_id: R2
47
+ file_hint: "_R2_"
48
+ strand: pos
49
+ min_len: 25
50
+ max_len: null
51
+ elements:
52
+ - {type: cdna, name: cdna, start: 0, end: null, seqspec_region_type: cdna}
53
+ # Read 1 = the bead read. The leading `vb` diversity insert is variable-length (0-3 bp); every element
54
+ # after it is ANCHORED to the previous one (a sequential chain), so the whole cell label FLOATS by the
55
+ # per-read insert length. The resolver phase-locks the frame on the GTGA/GACA linkers. `max_len: null`
56
+ # because R1 is over-sequenced past the ~43 bp structure into poly(T)/cDNA. The family's CLS blocks
57
+ # carry NO onlist — which whitelist they hit is exactly what descent decides.
58
+ - id: bc
59
+ seqspec_read_id: R1
60
+ file_hint: "_R1_"
61
+ strand: pos
62
+ min_len: 43
63
+ max_len: null
64
+ elements:
65
+ - {type: diversity, name: vb, min_len: 0, max_len: 3, seqspec_region_type: linker}
66
+ - {type: barcode, name: cls1, min_len: 9, max_len: 9, seqspec_region_type: barcode,
67
+ anchor: {relative_to: element, ref_element: vb, ref_side: end, offset: 0}}
68
+ - {type: linker, name: linker1, sequence: "GTGA", seqspec_region_type: linker,
69
+ anchor: {relative_to: element, ref_element: cls1, ref_side: end, offset: 0, max_mismatch: 1}}
70
+ - {type: barcode, name: cls2, min_len: 9, max_len: 9, seqspec_region_type: barcode,
71
+ anchor: {relative_to: element, ref_element: linker1, ref_side: end, offset: 0}}
72
+ - {type: linker, name: linker2, sequence: "GACA", seqspec_region_type: linker,
73
+ anchor: {relative_to: element, ref_element: cls2, ref_side: end, offset: 0, max_mismatch: 1}}
74
+ - {type: barcode, name: cls3, min_len: 9, max_len: 9, seqspec_region_type: barcode,
75
+ anchor: {relative_to: element, ref_element: linker2, ref_side: end, offset: 0}}
76
+ - {type: umi, name: UMI, min_len: 8, max_len: 8, seqspec_region_type: umi,
77
+ anchor: {relative_to: element, ref_element: cls3, ref_side: end, offset: 0}}
78
+
79
+ onlists: {}
80
+
81
+ signature:
82
+ # Coarse recognition only — enough to admit either Enhanced child at rungs 0-2 and reject everything
83
+ # else (the original fixed bead, 10x, bulk). The GTGA...GACA composite is the structural signature:
84
+ # `GTGANNNNNNNNNGACA` = GTGA + CLS2(9N) + GACA, searched across the 0-3 bp insert stagger (start
85
+ # positions 8-13), tolerating 2 substitutions. NO onlist here (that is the children's job).
86
+ requires:
87
+ - {test: read_count, roles: 2}
88
+ - {test: motif_present, read: bc, motif: "GTGANNNNNNNNNGACA", where: window,
89
+ search_start: 8, search_end: 13, max_mismatch: 2, min_rate: 0.5}
90
+ supports:
91
+ # the cell label recurs (low distinct-ratio, resolved at the anchored frame); UMI ~unique; cDNA diverse
92
+ - {when: {test: distinct_ratio, read: bc, element: cls1, expect: low}, weight: 2.0}
93
+ - {when: {test: distinct_ratio, read: bc, element: UMI, expect: high}, weight: 1.0}
94
+ - {when: {test: distinct_ratio, read: cdna, start: 0, end: 20, expect: high}, weight: 1.0}
95
+ excludes: []
96
+
97
+ # no backend: abstract classifier. no parent: a root family (distinct from the original-bead root
98
+ # `bd-rhapsody-wta` — the two beads are byte-separable by their linker structure, so they are not kin).
99
+ confusable_with: []
@@ -0,0 +1,93 @@
1
+ schema_version: 1
2
+
3
+ # ============================================================================================
4
+ # BD RHAPSODY WTA — ENHANCED bead v1, 96(/97) x 3 cell-label lists (rhapsodist class `enhanced`).
5
+ #
6
+ # A leaf of `bd-rhapsody-wta-enhanced`. "v1" is the FIRST Enhanced generation (96-plex), as against the
7
+ # `-v2` sibling (384-plex) — not the original fixed-offset bead, which is `bd-rhapsody-wta`. Byte-
8
+ # identical Enhanced geometry (0-3 bp diversity insert + GTGA/GACA linkers); what makes it THIS leaf is
9
+ # that its three CLS blocks hit the ORIGINAL 97 x 9 bp cell-label pools — the very same
10
+ # `bd-rhapsody-cls1/2/3` whitelists the original fixed-offset bead uses (verified: the shipped lists are
11
+ # byte-identical to rhapsodist's whitelist_96x3, 97 entries each). The `-v2` sibling uses the disjoint
12
+ # 384 x 3 pools; the two share 0 barcodes, so the onlist decides.
13
+ #
14
+ # The layout (reads/elements) is inherited verbatim from the family and is what the round-trip and the
15
+ # adapter-anchored `compose` derivation read; see the family node for the annotated read structure.
16
+ # ============================================================================================
17
+
18
+ identity:
19
+ id: bd-rhapsody-wta-enhanced-v1
20
+ version: enhanced-v1
21
+ name: "BD Rhapsody WTA — Enhanced bead v1 (96-plex cell labels)"
22
+ aliases: ["BD Rhapsody Enhanced 96", "Rhapsody Enhanced 96x3", "BD Rhapsody Enhanced v1"]
23
+ assay_ontology: ["EFO:0700003"]
24
+ modality: rna
25
+
26
+ parent: bd-rhapsody-wta-enhanced
27
+
28
+ reads:
29
+ - id: cdna
30
+ seqspec_read_id: R2
31
+ file_hint: "_R2_"
32
+ strand: pos
33
+ min_len: 25
34
+ max_len: null
35
+ elements:
36
+ - {type: cdna, name: cdna, start: 0, end: null, seqspec_region_type: cdna}
37
+ - id: bc
38
+ seqspec_read_id: R1
39
+ file_hint: "_R1_"
40
+ strand: pos
41
+ min_len: 43
42
+ max_len: null
43
+ elements:
44
+ - {type: diversity, name: vb, min_len: 0, max_len: 3, seqspec_region_type: linker}
45
+ - {type: barcode, name: cls1, min_len: 9, max_len: 9, onlist: cls1, seqspec_region_type: barcode,
46
+ anchor: {relative_to: element, ref_element: vb, ref_side: end, offset: 0}}
47
+ - {type: linker, name: linker1, sequence: "GTGA", seqspec_region_type: linker,
48
+ anchor: {relative_to: element, ref_element: cls1, ref_side: end, offset: 0, max_mismatch: 1}}
49
+ - {type: barcode, name: cls2, min_len: 9, max_len: 9, onlist: cls2, seqspec_region_type: barcode,
50
+ anchor: {relative_to: element, ref_element: linker1, ref_side: end, offset: 0}}
51
+ - {type: linker, name: linker2, sequence: "GACA", seqspec_region_type: linker,
52
+ anchor: {relative_to: element, ref_element: cls2, ref_side: end, offset: 0, max_mismatch: 1}}
53
+ - {type: barcode, name: cls3, min_len: 9, max_len: 9, onlist: cls3, seqspec_region_type: barcode,
54
+ anchor: {relative_to: element, ref_element: linker2, ref_side: end, offset: 0}}
55
+ - {type: umi, name: UMI, min_len: 8, max_len: 8, seqspec_region_type: umi,
56
+ anchor: {relative_to: element, ref_element: cls3, ref_side: end, offset: 0}}
57
+
58
+ onlists:
59
+ cls1: {registry: bd-rhapsody-cls1, role: cell_barcode, expected_orientation: forward} # 97 x 9 bp
60
+ cls2: {registry: bd-rhapsody-cls2, role: cell_barcode, expected_orientation: forward}
61
+ cls3: {registry: bd-rhapsody-cls3, role: cell_barcode, expected_orientation: forward}
62
+
63
+ signature:
64
+ requires: # the GTGA...GACA frame is the byte signature (stagger-tolerant)
65
+ - {test: read_count, roles: 2}
66
+ - {test: motif_present, read: bc, motif: "GTGANNNNNNNNNGACA", where: window,
67
+ search_start: 8, search_end: 13, max_mismatch: 2, min_rate: 0.5}
68
+ supports: # each CLS block hits its own 97 x 9 bp pool, resolved per-read
69
+ - {when: {test: onlist_hit_rate, read: bc, element: cls1, onlist: cls1, orientation: forward, min: 0.5}, weight: 3.0}
70
+ - {when: {test: onlist_hit_rate, read: bc, element: cls2, onlist: cls2, orientation: forward, min: 0.5}, weight: 3.0}
71
+ - {when: {test: onlist_hit_rate, read: bc, element: cls3, onlist: cls3, orientation: forward, min: 0.5}, weight: 3.0}
72
+ - {when: {test: distinct_ratio, read: bc, element: UMI, expect: high}, weight: 1.0}
73
+ - {when: {test: distinct_ratio, read: cdna, start: 0, end: 20, expect: high}, weight: 1.0}
74
+ excludes: []
75
+
76
+ backend:
77
+ module: map/starsolo
78
+ params:
79
+ soloType: CB_UMI_Complex
80
+ soloCBwhitelist: ["{onlist:cls1}", "{onlist:cls2}", "{onlist:cls3}"] # order == CB-position order
81
+ # soloStrand Forward: the bead oligo ends in poly(dT) priming off the mRNA poly(A), so R2 is sense to
82
+ # the transcript — a 3' poly-dT capture library like the original bead (STAR #1607 sets Forward).
83
+ soloStrand: Forward
84
+ # soloAdapterSequence, soloCBposition, soloUMIposition are DERIVED from the anchored element model at
85
+ # compose time (compose/params.py) -> "NNNNNNNNNGTGANNNNNNNNNGACA" + "2_0_2_8 2_13_2_21 3_1_3_9" +
86
+ # "3_10_3_17", byte-identical to the maintainer-endorsed STARsolo string on issue #1607. Never
87
+ # hand-entered: the adapter anchor is where STARsolo absorbs the variable diversity insert.
88
+
89
+ # Siblings under the family are decided by onlist (parent.children_decided_by), so no per-sibling edge.
90
+ # Nothing else is confusable: the GTGA...GACA `requires` motif rejects the original bead, 10x and bulk
91
+ # outright, and the >43 bp bc read is length-infeasible for 10x. (The generic bulk fallback accepts THIS
92
+ # on read-count alone, so the edge is declared on `bulk-rnaseq-pe`, mirroring the original bead.)
93
+ confusable_with: []
@@ -0,0 +1,81 @@
1
+ schema_version: 1
2
+
3
+ # ============================================================================================
4
+ # BD RHAPSODY WTA — ENHANCED bead, 384 x 3 cell-label lists (rhapsodist class `enhanced_v2`).
5
+ #
6
+ # A leaf of `bd-rhapsody-wta-enhanced`, identical to `-96` in every byte-decided way (0-3 bp diversity
7
+ # insert, GTGA/GACA linkers, 9/9/9 CLS + 8 bp UMI). The ONLY difference is the whitelist: 384 x 9 bp per
8
+ # CLS block instead of 97 — the higher-plexity "Enhanced v2" bead. The 384 and 97 pools are DISJOINT
9
+ # (0 shared 9-mers), so a real dataset hits exactly one set at rung 3 and the sibling tie resolves.
10
+ #
11
+ # The 384 x 3 lists ship pre-packed as `bd-rhapsody-cls{1,2,3}-384` (source: imallona/rhapsodist
12
+ # workflow/data/whitelist_384x3, Moro & Mallona 2025).
13
+ # ============================================================================================
14
+
15
+ identity:
16
+ id: bd-rhapsody-wta-enhanced-v2
17
+ version: enhanced-384
18
+ name: "BD Rhapsody WTA — Enhanced bead v2 (384-plex cell labels)"
19
+ aliases: ["BD Rhapsody Enhanced v2", "Rhapsody Enhanced 384x3", "BD Rhapsody Enhanced 384"]
20
+ assay_ontology: ["EFO:0700003"]
21
+ modality: rna
22
+
23
+ parent: bd-rhapsody-wta-enhanced
24
+
25
+ reads:
26
+ - id: cdna
27
+ seqspec_read_id: R2
28
+ file_hint: "_R2_"
29
+ strand: pos
30
+ min_len: 25
31
+ max_len: null
32
+ elements:
33
+ - {type: cdna, name: cdna, start: 0, end: null, seqspec_region_type: cdna}
34
+ - id: bc
35
+ seqspec_read_id: R1
36
+ file_hint: "_R1_"
37
+ strand: pos
38
+ min_len: 43
39
+ max_len: null
40
+ elements:
41
+ - {type: diversity, name: vb, min_len: 0, max_len: 3, seqspec_region_type: linker}
42
+ - {type: barcode, name: cls1, min_len: 9, max_len: 9, onlist: cls1, seqspec_region_type: barcode,
43
+ anchor: {relative_to: element, ref_element: vb, ref_side: end, offset: 0}}
44
+ - {type: linker, name: linker1, sequence: "GTGA", seqspec_region_type: linker,
45
+ anchor: {relative_to: element, ref_element: cls1, ref_side: end, offset: 0, max_mismatch: 1}}
46
+ - {type: barcode, name: cls2, min_len: 9, max_len: 9, onlist: cls2, seqspec_region_type: barcode,
47
+ anchor: {relative_to: element, ref_element: linker1, ref_side: end, offset: 0}}
48
+ - {type: linker, name: linker2, sequence: "GACA", seqspec_region_type: linker,
49
+ anchor: {relative_to: element, ref_element: cls2, ref_side: end, offset: 0, max_mismatch: 1}}
50
+ - {type: barcode, name: cls3, min_len: 9, max_len: 9, onlist: cls3, seqspec_region_type: barcode,
51
+ anchor: {relative_to: element, ref_element: linker2, ref_side: end, offset: 0}}
52
+ - {type: umi, name: UMI, min_len: 8, max_len: 8, seqspec_region_type: umi,
53
+ anchor: {relative_to: element, ref_element: cls3, ref_side: end, offset: 0}}
54
+
55
+ onlists:
56
+ cls1: {registry: bd-rhapsody-cls1-384, role: cell_barcode, expected_orientation: forward} # 384 x 9 bp
57
+ cls2: {registry: bd-rhapsody-cls2-384, role: cell_barcode, expected_orientation: forward}
58
+ cls3: {registry: bd-rhapsody-cls3-384, role: cell_barcode, expected_orientation: forward}
59
+
60
+ signature:
61
+ requires:
62
+ - {test: read_count, roles: 2}
63
+ - {test: motif_present, read: bc, motif: "GTGANNNNNNNNNGACA", where: window,
64
+ search_start: 8, search_end: 13, max_mismatch: 2, min_rate: 0.5}
65
+ supports:
66
+ - {when: {test: onlist_hit_rate, read: bc, element: cls1, onlist: cls1, orientation: forward, min: 0.5}, weight: 3.0}
67
+ - {when: {test: onlist_hit_rate, read: bc, element: cls2, onlist: cls2, orientation: forward, min: 0.5}, weight: 3.0}
68
+ - {when: {test: onlist_hit_rate, read: bc, element: cls3, onlist: cls3, orientation: forward, min: 0.5}, weight: 3.0}
69
+ - {when: {test: distinct_ratio, read: bc, element: UMI, expect: high}, weight: 1.0}
70
+ - {when: {test: distinct_ratio, read: cdna, start: 0, end: 20, expect: high}, weight: 1.0}
71
+ excludes: []
72
+
73
+ backend:
74
+ module: map/starsolo
75
+ params:
76
+ soloType: CB_UMI_Complex
77
+ soloCBwhitelist: ["{onlist:cls1}", "{onlist:cls2}", "{onlist:cls3}"]
78
+ soloStrand: Forward
79
+ # soloAdapterSequence / soloCBposition / soloUMIposition DERIVED from the anchored elements (see -96).
80
+
81
+ confusable_with: []
@@ -0,0 +1,35 @@
1
+ # Bulk paired-end RNA-seq
2
+
3
+ Standard bulk RNA-seq on an Illumina sequencer: two paired-end cDNA reads, **no cell barcode and no
4
+ UMI**. Every base is transcript sequence — there's nothing to demultiplex into cells. seqforge aligns
5
+ these with plain STAR and counts genes, rather than STARsolo.
6
+
7
+ ## How it's read
8
+
9
+ - **R1** and **R2** are the two ends of the same cDNA fragment (a mate pair). Both are transcript
10
+ sequence, typically 75–150 bp.
11
+ - No barcode, no UMI, no whitelist.
12
+
13
+ ## How seqforge tells it apart from single-cell
14
+
15
+ It's the **absence** of a barcode that identifies bulk. seqforge looks for a short, low-diversity
16
+ technical read — the tell-tale of a cell barcode — and finds none, just two long, near-unique cDNA
17
+ mates. A single-cell barcode read is short (26–28 bp) and repeats the same 16 bp prefix across reads;
18
+ a bulk mate is long and near-unique from the first base, so a barcode read can never be mistaken for
19
+ bulk cDNA.
20
+
21
+ Because this entry demands so little, it also serves as the **fallback** for any paired-end data.
22
+ That's deliberate: when a real single-cell library (BD Rhapsody, SPLiT-seq) happens to share this
23
+ loose shape, seqforge falls back to the barcode-list check to make sure it never quietly treats a
24
+ single-cell library as bulk.
25
+
26
+ ## Coverage note
27
+
28
+ This is the paired-end, poly-A branch. Single-end bulk and explicit strand-protocol handling aren't
29
+ modeled yet.
30
+
31
+ ## References
32
+
33
+ The exact, machine-readable definition seqforge uses lives in this entry's `spec.yaml`.
34
+ (scg_lib_structs documents single-cell library structures; plain bulk RNA-seq isn't one of them, so
35
+ there's no page to link.)
@@ -0,0 +1,97 @@
1
+ schema_version: 1
2
+
3
+ identity:
4
+ id: bulk-rnaseq-pe
5
+ version: illumina-pe
6
+ name: "Bulk Illumina paired-end RNA-seq (no cell barcode)"
7
+ aliases: ["bulk RNA-seq", "paired-end RNA-seq", "Illumina PE RNA-seq", "polyA RNA-seq PE", "RNA-seq PE"]
8
+ assay_ontology: ["EFO:0008896"] # "RNA-Seq" (generic bulk) — verified against live EFO (EBI OLS)
9
+ modality: rna
10
+
11
+ reads:
12
+ # Two symmetric cDNA mates, no cell barcode / UMI. The min_len gate (>=40) is what excludes a short
13
+ # single-cell barcode read (26/28 bp) from ever filling a bulk role.
14
+ - id: R1
15
+ seqspec_read_id: R1
16
+ file_hint: "_R1_"
17
+ strand: pos
18
+ min_len: 40
19
+ max_len: null
20
+ elements:
21
+ - {type: cdna, name: cdna_r1, start: 0, end: null, seqspec_region_type: cdna}
22
+ - id: R2
23
+ seqspec_read_id: R2
24
+ file_hint: "_R2_"
25
+ strand: neg # the reverse mate
26
+ min_len: 40
27
+ max_len: null
28
+ elements:
29
+ - {type: cdna, name: cdna_r2, start: 0, end: null, seqspec_region_type: cdna}
30
+
31
+ onlists: {}
32
+
33
+ signature:
34
+ requires:
35
+ - {test: read_count, roles: 2} # two reads, both biological; no barcode role to fill
36
+ supports: # diverse cDNA => high distinct-ratio (not a repeated barcode)
37
+ - when: {test: distinct_ratio, read: R1, start: 0, end: 25, expect: high}
38
+ weight: 2.0
39
+ - when: {test: distinct_ratio, read: R2, start: 0, end: 25, expect: high}
40
+ weight: 2.0
41
+ - when: {test: header_index, present: true} # real Illumina headers carry the sample index
42
+ weight: 0.5
43
+ excludes: []
44
+
45
+ backend:
46
+ module: map/star # plain STAR (no solo): bulk has no barcode/UMI demultiplex
47
+ params: {}
48
+ # EMPTY, and meaningfully so — not degenerate. Bulk paired-end RNA-seq has nothing byte-decided to
49
+ # pass: no barcode, no UMI, no whitelist, no offsets. Every knob that used to live here was on the
50
+ # wrong side of the parse/count line:
51
+ # - quantMode: GeneCounts -> COUNTING. Now processing.quantification (BulkQuant.mode).
52
+ # - outSAMtype: [BAM, Sorted..] -> an OUTPUT FORMAT: neither parse nor count. It is a module
53
+ # implementation detail, and the proof was already in this repo — starsolo.smk hardcoded
54
+ # `--outSAMtype BAM Unsorted` in its shell block while star.smk read the same knob from config.
55
+ # Two hand-written modules, two answers, one detail. star.smk now hardcodes it too.
56
+ #
57
+ # There is also no strandedness knob, and none is needed. This file used to promise that "library
58
+ # strandedness is resolved at compose from processing policy" — no code ever implemented it, because
59
+ # there was never a decision to make: `--quantMode GeneCounts` already emits ALL THREE strand columns
60
+ # in ReadsPerGene.out.tab. Same law as soloFeatures: when the aligner computes every alternative in
61
+ # one pass and the outputs are small, compute them all and let the consumer choose.
62
+
63
+ confusable_with:
64
+ - id: splitseq
65
+ relationship: processing_divergent
66
+ distinguishable_by: [onlist]
67
+ note: >
68
+ This entry is the GENERIC PAIRED-END FALLBACK: it requires two reads and forbids very little,
69
+ so at rungs 0-2 it accepts SPLiT-seq's cdna+bc pair as readily as real bulk data. Computed, not
70
+ assumed — `rung02_separable(bulk, splitseq)` is False, which is what forced this declaration.
71
+ The two are maximally divergent downstream (map/star vs map/starsolo: no barcodes at all vs
72
+ three rounds of them), so silently preferring the alphabetically-luckier entry would emit a
73
+ bulk gene-count matrix for a single-cell library and exit 0. Only the onlist separates them:
74
+ SPLiT-seq's round1/2/3 barcodes hit their whitelists at rung 3 and bulk has no whitelist to
75
+ hit, which is why the resolver reaches rung 3 here rather than deciding at rung 2.
76
+ - id: bd-rhapsody-wta
77
+ relationship: processing_divergent
78
+ distinguishable_by: [onlist]
79
+ note: >
80
+ Same story as SPLiT-seq: the generic paired-end fallback accepts BD Rhapsody's cdna+bc pair on
81
+ geometry alone (`rung02_separable(bulk, bd-rhapsody-wta)` is False), so silently preferring bulk
82
+ would emit a gene-count matrix for a single-cell library and exit 0. Only the onlist separates
83
+ them — BD's three CLS whitelists hit at rung 3, and bulk has none.
84
+ - id: bd-rhapsody-wta-enhanced-v1
85
+ relationship: processing_divergent
86
+ distinguishable_by: [onlist]
87
+ note: >
88
+ The BD Rhapsody Enhanced bead (v1, 96-plex). Identical story to the original bead: this fallback
89
+ requires only two reads, so it accepts the Enhanced cdna+bc pair on read-count alone. Enhanced
90
+ REJECTS bulk (its GTGA...GACA `requires` motif is absent from bulk cDNA), but the acceptance is
91
+ one-directional, so the edge is declared here. The three CLS whitelists hit at rung 3; bulk has none.
92
+ - id: bd-rhapsody-wta-enhanced-v2
93
+ relationship: processing_divergent
94
+ distinguishable_by: [onlist]
95
+ note: >
96
+ The BD Rhapsody Enhanced bead (v2, 384-plex) — same reasoning as `-enhanced-v1`. Separated at rung
97
+ 3 by the 384 x 3 CLS whitelists (disjoint from both the 96 pools and from anything bulk could hit).
@@ -0,0 +1,51 @@
1
+ # SPLiT-seq (split-pool combinatorial barcoding)
2
+
3
+ A single-cell RNA-seq method that needs no special instrument. Instead of one droplet per cell, cells
4
+ are barcoded by **splitting and pooling** them through several rounds of labeling; after three rounds
5
+ each cell carries a unique combination of three barcodes.
6
+
7
+ This entry covers the **original published SPLiT-seq** (Rosenberg et al., *Science* 2018). Parse
8
+ Biosciences' **Evercode** is a separate commercial descendant with different linkers and barcodes — a
9
+ different chemistry, not covered here.
10
+
11
+ ## How it's read
12
+
13
+ - **Read 1 = the cDNA** (the transcript).
14
+ - **Read 2 = the barcode read**: a 10 bp UMI, then three 8 bp round-barcodes separated by two fixed
15
+ 30 bp linkers.
16
+
17
+ ```text
18
+ [10 bp UMI] [8 bp round-3] --linker1-- [8 bp round-2] --linker2-- [8 bp round-1]
19
+ ```
20
+
21
+ A cell's identity is the combination of its round-1, round-2, and round-3 barcodes; each round draws
22
+ from a list of ~96 barcodes.
23
+
24
+ ## How seqforge tells it apart
25
+
26
+ Like BD Rhapsody, the **two fixed linker sequences at known positions** are the signature, and each
27
+ round-barcode matches its own list. The exact linker lengths matter: the original 2018 chemistry puts
28
+ round-1 at a different position than the later Parse chemistry, so a position copied from the wrong
29
+ source silently mismatches. seqforge pins the original layout from the paper's own oligos.
30
+
31
+ ## Status: not yet ready for real data
32
+
33
+ SPLiT-seq is the pilot's generalization test — it exercises machinery the 10x entries don't (8 bp
34
+ barcodes instead of 16, a barcode split combinatorially across a read). Two things must land before
35
+ it can process real data:
36
+
37
+ - **The barcode lists must ship.** The entry names three round-barcode lists that aren't bundled yet.
38
+ - **The strand must be confirmed on real data.** It's derived from the paper's oligos and the authors'
39
+ own pipeline (both point the same way), but the honest state is that most pipelines never explicitly
40
+ chose the strand — they inherited a default. The decisive check is to run the paper's own data both
41
+ ways; the correct strand assigns far more reads.
42
+
43
+ Until then this entry is intentionally inert.
44
+
45
+ ## References
46
+
47
+ Read structure and linker sequences pinned from
48
+ **[scg_lib_structs — SPLiT-seq](https://teichlab.github.io/scg_lib_structs/methods_html/SPLiT-seq.html)**
49
+ (Teichmann Lab, CC-BY) and independently reconstructed from the paper's oligo tables
50
+ ([Rosenberg et al., *Science* 2018](https://doi.org/10.1126/science.aam8999)). The exact,
51
+ machine-readable definition seqforge uses lives in this entry's `spec.yaml`.