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,62 @@
1
+ schema_version: 1
2
+
3
+ # An ABSTRACT FAMILY NODE, not a runnable chemistry. It has no `backend`: v2 and v3 parse reads
4
+ # differently (10 vs 12 bp UMI, different whitelists), so there is no single recipe to compile — descent
5
+ # always resolves to a leaf child. Its job is threefold: (1) group v2/v3/v3.1 so their pairwise
6
+ # `processing_divergent` confusable edges collapse to one `parent` link; (2) carry `children_decided_by`
7
+ # so the divergent-tie question knows the whitelist separates them; (3) be the family a prose/metadata
8
+ # prior names ("10x 3' gene expression") when it orders the descent. It is NEVER scored as a candidate
9
+ # (the scorer skips backend-less nodes) and is validated by the recognition self-test, not `kb roundtrip`.
10
+
11
+ identity:
12
+ id: 10x-3p-gex
13
+ version: family
14
+ name: "10x Chromium Single Cell 3' Gene Expression (family)"
15
+ aliases: ["10x 3'", "10x 3-prime", "Chromium 3' GEX", "10x Chromium 3' GEX", "SC3P", "single cell 3-prime gene expression"]
16
+ assay_ontology: ["EFO:0009922"] # 10x 3' GEX (the assay the whole family instantiates)
17
+ modality: rna
18
+
19
+ node_kind: family
20
+ # The children (v2 / v3 / v3.1) share 16 bp CB geometry and are told apart ONLY by their whitelist —
21
+ # so a divergent tie among them is decidable_by onlist, sourced here instead of a per-sibling edge.
22
+ children_decided_by: [onlist]
23
+
24
+ reads:
25
+ # The loose 10x 3' barcode read: 16 bp CB + a 10-12 bp UMI (26-28 bp total), covering v2 (26) and
26
+ # v3/v3.1 (28). min/max_len 26..28 is what keeps the family SEPARABLE from bulk: a long cDNA read
27
+ # (>= 40 bp) fails this gate, so the family never claims bulk/SPLiT-seq data.
28
+ - id: R1
29
+ seqspec_read_id: R1
30
+ file_hint: "_R1_"
31
+ strand: pos
32
+ min_len: 26
33
+ max_len: 28
34
+ elements:
35
+ - {type: barcode, name: CB, start: 0, end: 16, seqspec_region_type: barcode}
36
+ - {type: umi, name: UMI, start: 16, end: 28, seqspec_region_type: umi}
37
+ - id: R2
38
+ seqspec_read_id: R2
39
+ file_hint: "_R2_"
40
+ strand: pos
41
+ min_len: 25
42
+ max_len: null
43
+ elements:
44
+ - {type: cdna, name: cdna, start: 0, end: null, seqspec_region_type: cdna}
45
+
46
+ onlists: {}
47
+
48
+ signature:
49
+ # Coarse recognition only — enough to admit any 10x 3' child at rungs 0-2 and to reject bulk (whose
50
+ # R1 is a long cDNA, not a 16 bp CB in a 26-28 bp read). NO exact segment_length: 26 and 28 both pass.
51
+ requires:
52
+ - {test: read_count, roles: 2}
53
+ - {test: has_segment, read: R1, start: 0, end: 16, kind: random}
54
+ supports:
55
+ - when: {test: distinct_ratio, read: R1, start: 0, end: 16, expect: low}
56
+ weight: 2.0 # the CB recurs across reads
57
+ - when: {test: distinct_ratio, read: R2, start: 0, end: 20, expect: high}
58
+ weight: 1.0 # cDNA is diverse
59
+ excludes: []
60
+
61
+ # no backend: abstract classifier. no parent: a root family.
62
+ confusable_with: []
@@ -0,0 +1,41 @@
1
+ # 10x Chromium Single Cell 3' Gene Expression v2
2
+
3
+ Droplet single-cell RNA-seq — the generation before v3. Every droplet holds one gel bead, which tags
4
+ all of a cell's transcripts with the same **cell barcode** and each individual mRNA molecule with a
5
+ unique **UMI**. That lets you count molecules per gene per cell.
6
+
7
+ ## How it's read
8
+
9
+ Two reads come off the sequencer:
10
+
11
+ | read | length | what it holds |
12
+ | --- | --- | --- |
13
+ | **R1** | 26 bp | a 16 bp **cell barcode** + a 10 bp **UMI** |
14
+ | **R2** | open-ended | the **cDNA** (the transcript itself), read sense to the mRNA |
15
+
16
+ The cell barcode is drawn from 10x's published `737K-august-2016` list (~737,000 barcodes).
17
+
18
+ ## How seqforge tells v2 apart from v3
19
+
20
+ The only difference visible in the reads is **R1 length**: v2 is 16 + **10** = **26 bp**; v3 is
21
+ 16 + **12** = **28 bp**. That 2 bp gap is decisive on its own, so seqforge separates v2 from v3 by
22
+ read geometry alone — before it ever consults a barcode list. The two are never confused. (Their
23
+ whitelists differ too — `737K-august-2016` vs `3M-february-2018` — but length settles it first.)
24
+
25
+ If a paper or database says "v2" while the reads are actually 28 bp, seqforge does **not** quietly
26
+ pick one. It surfaces the disagreement (metadata says 26 bp, the bytes say 28 bp) and hands it to a
27
+ human — the bytes decide what the data is.
28
+
29
+ ## Gotchas
30
+
31
+ - **The barcode read can go missing on SRA.** `fasterq-dump` without `--include-technical` drops R1
32
+ entirely; seqforge blocks rather than guessing which file is which. Re-fetch with
33
+ `--include-technical`, or pull the submitter's original files.
34
+ - SRA rewrites read-name headers, so seqforge never trusts them when grouping files into samples.
35
+
36
+ ## References
37
+
38
+ Read structure cross-checked against
39
+ **[scg_lib_structs — 10x Chromium 3'](https://teichlab.github.io/scg_lib_structs/methods_html/10xChromium3.html)**
40
+ (Teichmann Lab, CC-BY), which lays out the v2–v4 kits side by side. The exact, machine-readable
41
+ definition seqforge uses lives in this entry's `spec.yaml`.
@@ -0,0 +1,83 @@
1
+ schema_version: 1
2
+
3
+ identity:
4
+ id: 10x-3p-gex-v2
5
+ version: "2"
6
+ name: "10x Chromium Single Cell 3' Gene Expression v2"
7
+ aliases: ["10x 3' v2", "Chromium 3' v2", "SC3Pv2", "single cell 3-prime v2"]
8
+ assay_ontology: ["EFO:0009899"] # 10x 3' v2 — verified against live EFO (EBI OLS), not memory
9
+ modality: rna
10
+
11
+ reads:
12
+ # R1 = the technical/barcode read. 26 bp = 16 CB + 10 UMI (the 2 bp shorter UMI vs v3's 28 bp).
13
+ # For a CANONICAL read the segment_length gate below separates v2 from v3 by that 26-vs-28. But
14
+ # max_len is null (an over-sequenced R1 is still v2), and when both accept an over-length read on
15
+ # geometry, the WHITELIST decides instead (v2 = 737K-august-2016) — see confusable_with.
16
+ - id: R1
17
+ seqspec_read_id: R1
18
+ file_hint: "_R1_"
19
+ strand: pos
20
+ min_len: 26
21
+ max_len: null
22
+ elements:
23
+ - {type: barcode, name: CB, start: 0, end: 16, onlist: cb_whitelist, seqspec_region_type: barcode}
24
+ - {type: umi, name: UMI, start: 16, end: 26, seqspec_region_type: umi}
25
+ - id: R2
26
+ seqspec_read_id: R2
27
+ file_hint: "_R2_"
28
+ strand: pos
29
+ min_len: 25
30
+ max_len: null
31
+ elements:
32
+ - {type: cdna, name: cdna, start: 0, end: null, seqspec_region_type: cdna}
33
+
34
+ onlists:
35
+ cb_whitelist:
36
+ registry: 737K-august-2016 # ~737,280 x 16 bp, forward orientation (v2 whitelist)
37
+ role: cell_barcode
38
+ expected_orientation: forward
39
+
40
+ signature:
41
+ requires: # length separates v2 (26) from v3 (28) for canonical reads
42
+ - {test: read_count, roles: 2}
43
+ # length 26 exact for canonical reads; over_length_min 100 admits a full-length over-sequenced R1
44
+ # without loosening 26-vs-28 (a 28 bp read is < 100 -> not over-length -> still FAILs v2's exact
45
+ # 26), and a 60-94 bp cDNA/split-pool read stays exact-checked and rejected.
46
+ - {test: segment_length, read: R1, length: 26, tolerance: 0, over_length_min: 100}
47
+ - {test: has_segment, read: R1, start: 0, end: 16, kind: random}
48
+ - {test: has_segment, read: R1, start: 16, end: 26, kind: random}
49
+ supports:
50
+ - when: {test: onlist_hit_rate, read: R1, start: 0, end: 16, onlist: cb_whitelist, orientation: forward, min: 0.6}
51
+ weight: 5.0
52
+ - when: {test: distinct_ratio, read: R1, start: 0, end: 16, expect: low}
53
+ weight: 2.0
54
+ - when: {test: distinct_ratio, read: R1, start: 16, end: 26, expect: high}
55
+ weight: 1.0
56
+ - when: {test: distinct_ratio, read: R2, start: 0, end: 20, expect: high}
57
+ weight: 1.0 # cDNA is diverse -> the biological role is scored, not left at 0
58
+ excludes: []
59
+
60
+ backend:
61
+ module: map/starsolo
62
+ params:
63
+ soloType: CB_UMI_Simple
64
+ soloCBstart: 1
65
+ soloCBlen: 16
66
+ soloUMIstart: 17
67
+ soloUMIlen: 10 # 10 bp UMI (vs v3's 12) -> 26 bp R1
68
+ soloCBwhitelist: "{onlist:cb_whitelist}"
69
+ soloStrand: Forward
70
+ # 0 disables STARsolo's barcode-read-length check (default 1 FATALs on an over-length barcode
71
+ # read); CB/UMI still read from the fixed offsets above.
72
+ soloBarcodeReadLength: 0
73
+ # soloFeatures is NOT here, and that is the point. It says what to COUNT, and 10x 3' v3.1
74
+ # chemistry is byte-identical for cells and nuclei — what differs is the RNA population, a
75
+ # property of SAMPLE PREP. Filing it as chemistry cost a measured 40.7% of a nuclear library.
76
+ # It lives in the processing manifest now, where a user may instruct it.
77
+
78
+ # A child of the 10x-3p-gex family. Both former edges (to v3 and its byte-identical twin v3.1) were the
79
+ # OVER-LENGTH-R1 whitelist split among siblings — now declared once, in the parent's
80
+ # `children_decided_by: [onlist]`. So v2 declares no confusable_with of its own.
81
+ parent: 10x-3p-gex
82
+
83
+ confusable_with: []
@@ -0,0 +1,56 @@
1
+ # 10x Chromium Single Cell 3' Gene Expression v3 (and v3.1)
2
+
3
+ Droplet single-cell RNA-seq — the current-generation 3' kit. Every droplet holds one gel bead, which
4
+ tags all of a cell's transcripts with the same **cell barcode** and each mRNA molecule with a unique
5
+ **UMI**.
6
+
7
+ This page covers **both v3 and v3.1**. To a sequencer they are the same library, and seqforge
8
+ processes them identically — see [v3 vs v3.1](#v3-vs-v31) below.
9
+
10
+ ## How it's read
11
+
12
+ | read | length | what it holds |
13
+ | --- | --- | --- |
14
+ | **R1** | 28 bp | a 16 bp **cell barcode** + a 12 bp **UMI** |
15
+ | **R2** | open-ended | the **cDNA** (the transcript itself), read sense to the mRNA |
16
+
17
+ The cell barcode is drawn from 10x's `3M-february-2018` list (~6.8 million barcodes). Index reads
18
+ (I1/I2) may or may not be present; seqforge doesn't need them.
19
+
20
+ Names you'll see in the wild: "10x 3' v3", "Chromium 3' v3", "SC3Pv3", "single cell 3-prime v3",
21
+ "Next GEM 3' v3.1". Papers often write only "10x 3' v3" for a v3.1 run, or name the kit and not the
22
+ version — which changes nothing, as the next section explains.
23
+
24
+ ## v3 vs v3.1
25
+
26
+ They are **identical to seqforge**: same read layout, same barcode list, same alignment settings. No
27
+ probe can tell them apart from the reads, and none needs to — so seqforge records both names and asks
28
+ you nothing. v3.1 exists as its own knowledge-base entry only so that "these two really are
29
+ identical" is something the test suite re-checks on every build, rather than a claim sitting in a
30
+ comment.
31
+
32
+ ## How seqforge tells it apart from other chemistries
33
+
34
+ - **v2** — a 16 bp barcode + a **10** bp UMI makes a **26 bp** R1 (vs v3's 28 bp), on a different
35
+ list. The 2 bp length difference alone separates them.
36
+ - **Multiome (ARC) and GEM-X 3' v4** produce the same 28 bp / 16+12 layout — here **only the barcode
37
+ list** tells them apart, because each uses a different one. Geometry narrows to a family; the list
38
+ picks the exact member.
39
+ - **5' kits** share the barcode/UMI geometry but read the cDNA in the opposite direction. The reads
40
+ can't reveal that, so it takes metadata or a trial alignment, not geometry.
41
+
42
+ ## Gotchas
43
+
44
+ - **The barcode read can go missing on SRA.** `fasterq-dump` without `--include-technical` drops the
45
+ 28 bp R1; seqforge blocks rather than inventing a barcode read from a filename. Re-fetch with
46
+ `--include-technical`, or pull the submitter's original files.
47
+ - **A pre-trimmed upload** (R1 no longer a single fixed length) shifts the barcode offsets — seqforge
48
+ refuses rather than reading the barcode from the wrong place.
49
+ - SRA rewrites read-name headers, so seqforge never trusts them when grouping files into samples.
50
+
51
+ ## References
52
+
53
+ Read structure cross-checked against
54
+ **[scg_lib_structs — 10x Chromium 3'](https://teichlab.github.io/scg_lib_structs/methods_html/10xChromium3.html)**
55
+ (Teichmann Lab, CC-BY). The exact, machine-readable definition seqforge uses lives in each entry's
56
+ `spec.yaml`.
@@ -0,0 +1,118 @@
1
+ schema_version: 1
2
+
3
+ identity:
4
+ id: 10x-3p-gex-v3
5
+ version: "3"
6
+ name: "10x Chromium Single Cell 3' Gene Expression v3"
7
+ aliases: ["10x 3' v3", "Chromium 3' v3", "SC3Pv3", "single cell 3-prime v3"]
8
+ assay_ontology: ["EFO:0009922"] # 10x 3' v3 — verified against live EFO (EBI OLS)
9
+ modality: rna
10
+
11
+ reads:
12
+ # R1 = the technical/barcode read. 28 bp = 16 CB + 12 UMI. max_len is null, not 28: an
13
+ # over-sequenced R1 (e.g. 150 bp, CB+UMI in bp0-28, the rest junk) is a real 10x layout, and the
14
+ # segment_length over_length_min below is what still keeps 28 separable from v2's 26.
15
+ - id: R1
16
+ seqspec_read_id: R1
17
+ file_hint: "_R1_"
18
+ strand: pos
19
+ min_len: 28
20
+ max_len: null
21
+ elements:
22
+ - {type: barcode, name: CB, start: 0, end: 16, onlist: cb_whitelist, seqspec_region_type: barcode}
23
+ - {type: umi, name: UMI, start: 16, end: 28, seqspec_region_type: umi}
24
+ # R2 = cDNA (open-ended). soloStrand Forward: R2 is sense to mRNA for 3' kits.
25
+ - id: R2
26
+ seqspec_read_id: R2
27
+ file_hint: "_R2_"
28
+ strand: pos
29
+ min_len: 25
30
+ max_len: null
31
+ elements:
32
+ - {type: cdna, name: cdna, start: 0, end: null, seqspec_region_type: cdna}
33
+
34
+ onlists:
35
+ cb_whitelist: # used by backend + a supports test
36
+ registry: 3M-february-2018 # ~6,794,880 x 16 bp, forward orientation
37
+ role: cell_barcode
38
+ expected_orientation: forward
39
+ arc_v1_probe: # probe-only, used ONLY by the exclude anti-gate
40
+ registry: 737K-arc-v1 # Multiome (ARC) GEX list
41
+ role: cell_barcode
42
+ expected_orientation: forward
43
+
44
+ signature:
45
+ requires: # hard structural gates, rungs 0-2 friendly (NO distinct_ratio, NO onlist)
46
+ - {test: read_count, roles: 2}
47
+ # length 28 stays exact for canonical reads; over_length_min 100 admits a full-length
48
+ # over-sequenced R1 (>=100 bp: the run read length) without loosening 26-vs-28. A shorter
49
+ # non-canonical read (a 60-94 bp cDNA or split-pool barcode) is below it, so it is still
50
+ # exact-checked and rejected -> v3 stays un-confusable with cDNA-only chemistries at rungs 0-2.
51
+ - {test: segment_length, read: R1, length: 28, tolerance: 0, over_length_min: 100}
52
+ - {test: has_segment, read: R1, start: 0, end: 16, kind: random}
53
+ - {test: has_segment, read: R1, start: 16, end: 28, kind: random}
54
+ supports: # additive evidence (rung 3+)
55
+ - when: {test: onlist_hit_rate, read: R1, start: 0, end: 16, onlist: cb_whitelist, orientation: forward, min: 0.6}
56
+ weight: 5.0
57
+ - when: {test: distinct_ratio, read: R1, start: 0, end: 16, expect: low}
58
+ weight: 2.0 # CB recurs -> low distinct ratio
59
+ - when: {test: distinct_ratio, read: R1, start: 16, end: 28, expect: high}
60
+ weight: 1.0 # UMI ~ unique -> high distinct ratio
61
+ - when: {test: distinct_ratio, read: R2, start: 0, end: 20, expect: high}
62
+ weight: 1.0 # cDNA is diverse -> the biological role is scored, not left at 0
63
+ - when: {test: header_index, present: true}
64
+ weight: 0.2
65
+ excludes: # anti-gate: if the Multiome list hits, it's not 3' GEX
66
+ - {test: onlist_hit_rate, read: R1, start: 0, end: 16, onlist: arc_v1_probe, orientation: forward, min: 0.6}
67
+
68
+ backend:
69
+ module: map/starsolo
70
+ params: # chemistry-defining MINIMUM (CellRanger-parity knobs come from processing policy)
71
+ soloType: CB_UMI_Simple
72
+ soloCBstart: 1
73
+ soloCBlen: 16
74
+ soloUMIstart: 17
75
+ soloUMIlen: 12
76
+ soloCBwhitelist: "{onlist:cb_whitelist}"
77
+ soloStrand: Forward
78
+ # 0 disables STARsolo's barcode-read-length check, which by default (1) FATALs when the barcode
79
+ # read is longer than CB+UMI. An over-sequenced R1 (150 bp) needs this; CB/UMI are still read from
80
+ # the fixed offsets above. Kept identical in v3/v3.1 so `backend_identical` holds.
81
+ soloBarcodeReadLength: 0
82
+ # soloFeatures is NOT here, and that is the point. It says what to COUNT, and 10x 3' v3.1
83
+ # chemistry is byte-identical for cells and nuclei — what differs is the RNA population, a
84
+ # property of SAMPLE PREP. Filing it as chemistry cost a measured 40.7% of a nuclear library.
85
+ # It lives in the processing manifest now, where a user may instruct it.
86
+
87
+ # A child of the 10x-3p-gex family. The v2 sibling edge (over-length whitelist split) now lives in the
88
+ # parent's `children_decided_by: [onlist]`, so it is deleted here — the tree declares it once.
89
+ parent: 10x-3p-gex
90
+
91
+ confusable_with:
92
+ - id: 10x-3p-gex-v3.1
93
+ relationship: processing_equivalent
94
+ distinguishable_by: [none]
95
+ note: >
96
+ Identical 16+12=28 bp geometry, identical 3M-february-2018 whitelist, byte-identical STARsolo
97
+ params -> §12 benign: the resolver records both ids and asks 0 questions. KEPT as an explicit
98
+ edge (not collapsed into the parent): backend-identity is a byte fact the §2.4 biconditional
99
+ verifies pairwise, distinct from the divergent sibling relationship the tree carries.
100
+ - id: 10x-multiome-gex
101
+ relationship: processing_divergent
102
+ distinguishable_by: [onlist]
103
+ note: >
104
+ Same 28 bp geometry, but Multiome (ARC) GEX uses 737K-arc-v1, not 3M-february-2018. Rung-3
105
+ onlist check separates them; backend params differ only in soloCBwhitelist.
106
+ - id: 10x-gemx-3p-v4
107
+ relationship: processing_divergent
108
+ distinguishable_by: [onlist]
109
+ note: >
110
+ REQUIRED declaration: GEM-X 3' v4 shares the 28 bp / 16+12 geometry and is separable only by
111
+ its newer whitelist. Without this entry the flagship fails the CI under-declaration check.
112
+ - id: 10x-5p-gex
113
+ relationship: processing_divergent
114
+ distinguishable_by: [metadata, alignment]
115
+ note: >
116
+ 5' chemistry reads antisense cDNA -> soloStrand Reverse. Read-undecidable when geometry and
117
+ whitelist coincide; metadata (rung 0) or alignment (rung 6) resolves it.
118
+
@@ -0,0 +1,56 @@
1
+ # 10x Chromium Single Cell 3' Gene Expression v3 (and v3.1)
2
+
3
+ Droplet single-cell RNA-seq — the current-generation 3' kit. Every droplet holds one gel bead, which
4
+ tags all of a cell's transcripts with the same **cell barcode** and each mRNA molecule with a unique
5
+ **UMI**.
6
+
7
+ This page covers **both v3 and v3.1**. To a sequencer they are the same library, and seqforge
8
+ processes them identically — see [v3 vs v3.1](#v3-vs-v31) below.
9
+
10
+ ## How it's read
11
+
12
+ | read | length | what it holds |
13
+ | --- | --- | --- |
14
+ | **R1** | 28 bp | a 16 bp **cell barcode** + a 12 bp **UMI** |
15
+ | **R2** | open-ended | the **cDNA** (the transcript itself), read sense to the mRNA |
16
+
17
+ The cell barcode is drawn from 10x's `3M-february-2018` list (~6.8 million barcodes). Index reads
18
+ (I1/I2) may or may not be present; seqforge doesn't need them.
19
+
20
+ Names you'll see in the wild: "10x 3' v3", "Chromium 3' v3", "SC3Pv3", "single cell 3-prime v3",
21
+ "Next GEM 3' v3.1". Papers often write only "10x 3' v3" for a v3.1 run, or name the kit and not the
22
+ version — which changes nothing, as the next section explains.
23
+
24
+ ## v3 vs v3.1
25
+
26
+ They are **identical to seqforge**: same read layout, same barcode list, same alignment settings. No
27
+ probe can tell them apart from the reads, and none needs to — so seqforge records both names and asks
28
+ you nothing. v3.1 exists as its own knowledge-base entry only so that "these two really are
29
+ identical" is something the test suite re-checks on every build, rather than a claim sitting in a
30
+ comment.
31
+
32
+ ## How seqforge tells it apart from other chemistries
33
+
34
+ - **v2** — a 16 bp barcode + a **10** bp UMI makes a **26 bp** R1 (vs v3's 28 bp), on a different
35
+ list. The 2 bp length difference alone separates them.
36
+ - **Multiome (ARC) and GEM-X 3' v4** produce the same 28 bp / 16+12 layout — here **only the barcode
37
+ list** tells them apart, because each uses a different one. Geometry narrows to a family; the list
38
+ picks the exact member.
39
+ - **5' kits** share the barcode/UMI geometry but read the cDNA in the opposite direction. The reads
40
+ can't reveal that, so it takes metadata or a trial alignment, not geometry.
41
+
42
+ ## Gotchas
43
+
44
+ - **The barcode read can go missing on SRA.** `fasterq-dump` without `--include-technical` drops the
45
+ 28 bp R1; seqforge blocks rather than inventing a barcode read from a filename. Re-fetch with
46
+ `--include-technical`, or pull the submitter's original files.
47
+ - **A pre-trimmed upload** (R1 no longer a single fixed length) shifts the barcode offsets — seqforge
48
+ refuses rather than reading the barcode from the wrong place.
49
+ - SRA rewrites read-name headers, so seqforge never trusts them when grouping files into samples.
50
+
51
+ ## References
52
+
53
+ Read structure cross-checked against
54
+ **[scg_lib_structs — 10x Chromium 3'](https://teichlab.github.io/scg_lib_structs/methods_html/10xChromium3.html)**
55
+ (Teichmann Lab, CC-BY). The exact, machine-readable definition seqforge uses lives in each entry's
56
+ `spec.yaml`.
@@ -0,0 +1,124 @@
1
+ schema_version: 1
2
+
3
+ # The §12 benign twin of 10x-3p-gex-v3, and the ONLY reason it is a separate file: `backend_identical`
4
+ # is a computed predicate, and a predicate cannot be computed about a spec that does not exist. v3
5
+ # declared `processing_equivalent: 10x-3p-gex-v3.1` from the start, but the twin was never written, so
6
+ # the flagship example of the rule the whole resolver is built around was the one pair CI could not
7
+ # check. Everything below except `identity` is byte-for-byte v3 — that IS the assertion.
8
+ #
9
+ # Do not "simplify" this by generating it from v3 at load time. The point is that two independently
10
+ # authored specs are compared and found identical; deriving one from the other would make
11
+ # `backend_identical(v3, v3.1)` true by construction and prove nothing.
12
+
13
+ identity:
14
+ id: 10x-3p-gex-v3.1
15
+ version: "3.1"
16
+ name: "10x Chromium Single Cell 3' Gene Expression v3.1"
17
+ aliases: ["10x 3' v3.1", "Chromium 3' v3.1", "SC3Pv3.1", "single cell 3-prime v3.1", "Next GEM 3' v3.1"]
18
+ assay_ontology: ["EFO:0022980"] # 10x 3' v3.1 — verified against live EFO (EBI OLS), FLAG-1
19
+ modality: rna
20
+
21
+ reads:
22
+ # Identical to v3: 28 bp = 16 CB + 12 UMI (max_len null: an over-sequenced R1 is still v3.1). This
23
+ # identity is the whole point — the probe cannot separate v3 from v3.1, and §12 says it does not
24
+ # need to; the over_length_min below keeps 28 separable from v2's 26 either way.
25
+ - id: R1
26
+ seqspec_read_id: R1
27
+ file_hint: "_R1_"
28
+ strand: pos
29
+ min_len: 28
30
+ max_len: null
31
+ elements:
32
+ - {type: barcode, name: CB, start: 0, end: 16, onlist: cb_whitelist, seqspec_region_type: barcode}
33
+ - {type: umi, name: UMI, start: 16, end: 28, seqspec_region_type: umi}
34
+ - id: R2
35
+ seqspec_read_id: R2
36
+ file_hint: "_R2_"
37
+ strand: pos
38
+ min_len: 25
39
+ max_len: null
40
+ elements:
41
+ - {type: cdna, name: cdna, start: 0, end: null, seqspec_region_type: cdna}
42
+
43
+ onlists:
44
+ cb_whitelist: # the SAME list as v3 — this is why no onlist check can separate them
45
+ registry: 3M-february-2018 # ~6,794,880 x 16 bp, forward orientation
46
+ role: cell_barcode
47
+ expected_orientation: forward
48
+ arc_v1_probe: # probe-only, used ONLY by the exclude anti-gate
49
+ registry: 737K-arc-v1 # Multiome (ARC) GEX list
50
+ role: cell_barcode
51
+ expected_orientation: forward
52
+
53
+ signature:
54
+ requires:
55
+ - {test: read_count, roles: 2}
56
+ # length 28 exact for canonical reads; over_length_min 100 admits a full-length over-sequenced
57
+ # R1 (>=100 bp) while a 60-94 bp cDNA/split-pool read stays exact-checked and rejected (see v3).
58
+ - {test: segment_length, read: R1, length: 28, tolerance: 0, over_length_min: 100}
59
+ - {test: has_segment, read: R1, start: 0, end: 16, kind: random}
60
+ - {test: has_segment, read: R1, start: 16, end: 28, kind: random}
61
+ supports:
62
+ - when: {test: onlist_hit_rate, read: R1, start: 0, end: 16, onlist: cb_whitelist, orientation: forward, min: 0.6}
63
+ weight: 5.0
64
+ - when: {test: distinct_ratio, read: R1, start: 0, end: 16, expect: low}
65
+ weight: 2.0
66
+ - when: {test: distinct_ratio, read: R1, start: 16, end: 28, expect: high}
67
+ weight: 1.0
68
+ - when: {test: distinct_ratio, read: R2, start: 0, end: 20, expect: high}
69
+ weight: 1.0
70
+ - when: {test: header_index, present: true}
71
+ weight: 0.2
72
+ excludes:
73
+ - {test: onlist_hit_rate, read: R1, start: 0, end: 16, onlist: arc_v1_probe, orientation: forward, min: 0.6}
74
+
75
+ backend:
76
+ module: map/starsolo
77
+ params: # byte-identical to v3 -> backend_identical -> §12 benign
78
+ soloType: CB_UMI_Simple
79
+ soloCBstart: 1
80
+ soloCBlen: 16
81
+ soloUMIstart: 17
82
+ soloUMIlen: 12
83
+ soloCBwhitelist: "{onlist:cb_whitelist}"
84
+ soloStrand: Forward
85
+ # 0 disables STARsolo's barcode-read-length check (default 1 FATALs on an over-length barcode
86
+ # read); byte-identical to v3's, which is what keeps `backend_identical` true.
87
+ soloBarcodeReadLength: 0
88
+ # soloFeatures is NOT here, and that is the point. It says what to COUNT, and 10x 3' v3.1
89
+ # chemistry is byte-identical for cells and nuclei — what differs is the RNA population, a
90
+ # property of SAMPLE PREP. Filing it as chemistry cost a measured 40.7% of a nuclear library.
91
+ # It lives in the processing manifest now, where a user may instruct it.
92
+
93
+ # A child of the 10x-3p-gex family; the v2 sibling edge now lives in the parent's children_decided_by.
94
+ parent: 10x-3p-gex
95
+
96
+ confusable_with:
97
+ - id: 10x-3p-gex-v3
98
+ relationship: processing_equivalent
99
+ distinguishable_by: [none]
100
+ note: >
101
+ The back-edge of v3's declaration. Identical 16+12=28 bp geometry, identical 3M-february-2018
102
+ whitelist, byte-identical STARsolo params -> §12 benign: the resolver records both ids and asks
103
+ 0 questions. Declared on both sides so the relation reads as symmetric in the file, which is
104
+ what a human checks; the CI biconditional takes the union of both directions regardless.
105
+ - id: 10x-multiome-gex
106
+ relationship: processing_divergent
107
+ distinguishable_by: [onlist]
108
+ note: >
109
+ Same 28 bp geometry, but Multiome (ARC) GEX uses 737K-arc-v1, not 3M-february-2018. Rung-3
110
+ onlist check separates them; backend params differ only in soloCBwhitelist.
111
+ - id: 10x-gemx-3p-v4
112
+ relationship: processing_divergent
113
+ distinguishable_by: [onlist]
114
+ note: >
115
+ REQUIRED declaration: GEM-X 3' v4 shares the 28 bp / 16+12 geometry and is separable only by
116
+ its newer whitelist. v3.1 inherits every one of v3's divergent confusables — it is the same
117
+ chemistry to the probe, so it is confusable with exactly the same things.
118
+ - id: 10x-5p-gex
119
+ relationship: processing_divergent
120
+ distinguishable_by: [metadata, alignment]
121
+ note: >
122
+ 5' chemistry reads antisense cDNA -> soloStrand Reverse. Read-undecidable when geometry and
123
+ whitelist coincide; metadata (rung 0) or alignment (rung 6) resolves it.
124
+
@@ -0,0 +1,103 @@
1
+ # BD Rhapsody WTA (Whole Transcriptome Analysis)
2
+
3
+ BD Biosciences' droplet-free single-cell 3' RNA-seq. Cells are captured in a microwell plate and
4
+ tagged with a **cell label** built from three barcode blocks on the capture bead, plus a **UMI** on
5
+ each molecule.
6
+
7
+ BD ships this chemistry in two bead generations, and seqforge recognizes and compiles **both**
8
+ automatically, from the reads. The **original fixed-position bead** is described first; the 2022
9
+ **[Enhanced bead](#enhanced-beads-2022)** — with its variable-length diversity insert — is described
10
+ below. seqforge tells the two apart from the bytes (their linker structure differs), so you never have
11
+ to declare which bead a dataset used.
12
+
13
+ ## How it's read
14
+
15
+ Two reads:
16
+
17
+ - **R2 = the cDNA** (the transcript), read sense to the mRNA.
18
+ - **R1 = the bead read**: three 9 bp cell-label blocks separated by two fixed linker sequences, then
19
+ an 8 bp UMI.
20
+
21
+ | segment on R1 | length | position |
22
+ | --- | --- | --- |
23
+ | cell label 1 | 9 bp | 0–9 |
24
+ | linker 1 (`ACTGGCCTGCGA`) | 12 bp | 9–21 |
25
+ | cell label 2 | 9 bp | 21–30 |
26
+ | linker 2 (`GGTAGCGGTGACA`) | 13 bp | 30–43 |
27
+ | cell label 3 | 9 bp | 43–52 |
28
+ | UMI | 8 bp | 52–60 |
29
+
30
+ Each cell-label block is drawn from its own published list of **97** 9-mers, so there are 97³ ≈
31
+ 913,000 possible cell labels. R1 is often sequenced past position 60 into poly(T)/cDNA; those extra
32
+ bases are ignored.
33
+
34
+ ## How seqforge tells it apart
35
+
36
+ The **two fixed linker sequences at known positions** are the signature — no other supported
37
+ chemistry has them. Each cell-label block also matches its own barcode list, which both confirms the
38
+ call and separates BD Rhapsody from a plain bulk paired-end library (bulk has no barcode list to
39
+ match).
40
+
41
+ ## Strand
42
+
43
+ The bead's capture oligo ends in poly(dT), which primes off the mRNA poly(A) tail, so R2 reads the
44
+ cDNA **sense** to the transcript — a standard 3' poly-dT library, like 10x 3'. Getting this backwards
45
+ would leave most reads uncounted while the aligner still exits cleanly, so seqforge derives it from
46
+ the bead chemistry rather than guessing.
47
+
48
+ ## Enhanced beads (2022)
49
+
50
+ In 2022 BD introduced **Enhanced Cell Capture Beads**, the current chemistry. The cell labels work the
51
+ same way — three 9 bp blocks read off the bead — but two things change:
52
+
53
+ - A short **diversity insert** of 0–3 bp (one of *nothing*, `A`, `GT`, or `TCA`) is added at the very
54
+ start of Read 1, to stagger the reads and cut the amount of PhiX needed.
55
+ - The two linkers shrink to **`GTGA`** and **`GACA`** (4 bp each), from v1's 12 and 13 bp.
56
+
57
+ So Enhanced Read 1 reads:
58
+
59
+ ```text
60
+ [0–3 bp insert] [CLS1 · 9] GTGA [CLS2 · 9] GACA [CLS3 · 9] [UMI · 8]
61
+ ```
62
+
63
+ There are two sub-versions, differing only in the cell-label lists: **96** or **384** sequences per
64
+ block (the 384-list "Enhanced v2" allows more cell labels). The read layout is otherwise identical.
65
+
66
+ ### How seqforge handles the Enhanced bead
67
+
68
+ seqforge recognizes and compiles the Enhanced bead **automatically** — you do not tell it the bead
69
+ generation, and you do not pre-trim the diversity insert.
70
+
71
+ - **Recognition.** The diversity insert shifts every barcode by a different amount from one read to the
72
+ next, so the positions are not fixed. seqforge finds the `GTGA…GACA` linker frame in each read
73
+ (tolerating the 0–3 bp stagger and sequencing error), then reads the cell-label and UMI blocks
74
+ relative to it. The presence of that frame is what separates Enhanced from the original bead, whose
75
+ longer `ACTGGCCTGCGA`/`GGTAGCGGTGACA` linkers sit at fixed positions instead.
76
+ - **96 vs 384.** The two Enhanced sub-versions have identical read layout and differ only in which
77
+ cell-label list their blocks match, so seqforge picks the one the barcodes actually hit — exactly how
78
+ it tells 10x v2 from v3. The 96-plex bead reuses the original bead's cell-label lists; the 384-plex
79
+ lists ship with seqforge too.
80
+ - **Compilation.** seqforge maps Enhanced reads with STARsolo's adapter anchor — no pre-trimming — and
81
+ derives the exact settings STAR's author endorsed in
82
+ [issue #1607](https://github.com/alexdobin/STAR/issues/1607) straight from the read layout:
83
+
84
+ ```bash
85
+ STAR --soloType CB_UMI_Complex \
86
+ --soloAdapterSequence NNNNNNNNNGTGANNNNNNNNNGACA \
87
+ --soloCBposition 2_0_2_8 2_13_2_21 3_1_3_9 \
88
+ --soloUMIposition 3_10_3_17 \
89
+ --soloCBwhitelist BD_CLS1.txt BD_CLS2.txt BD_CLS3.txt
90
+ ```
91
+
92
+ The adapter `NNNNNNNNNGTGANNNNNNNNNGACA` is `CLS1(9) + GTGA + CLS2(9) + GACA`; STARsolo finds it in
93
+ each read and reads the barcodes relative to it, so the variable insert at the front takes care of
94
+ itself. For a reproducible reference workflow spanning every bead generation, see
95
+ **[rhapsodist](https://github.com/imallona/rhapsodist)**.
96
+
97
+ ## References
98
+
99
+ Read structure, linker sequences, and barcode lists pinned from
100
+ **[scg_lib_structs — BD Rhapsody](https://teichlab.github.io/scg_lib_structs/methods_html/BD_Rhapsody.html)**
101
+ (Teichmann Lab, CC-BY), cross-checked against the seqspec `bd_rhapsody_v1` example and the STARsolo
102
+ maintainer's endorsed settings. The exact, machine-readable definition seqforge uses lives in this
103
+ entry's `spec.yaml`.