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,157 @@
1
+ schema_version: 1
2
+
3
+ # ============================================================================================
4
+ # ORIGINAL SPLiT-seq (Rosenberg et al., Science 2018, doi:10.1126/science.aam8999).
5
+ # Read structure + linker sequences pinned from scg_lib_structs (CC-BY):
6
+ # https://teichlab.github.io/scg_lib_structs/methods_html/SPLiT-seq.html
7
+ # https://github.com/Teichlab/scg_lib_structs/issues/13 (the "Published Manuscript" read-2 variant)
8
+ #
9
+ # This entry models the PUBLISHED-MANUSCRIPT SPLiT-seq only. Parse Biosciences Evercode is a separate,
10
+ # updated commercial descendant with different linkers/whitelists and multiple kit versions — it is
11
+ # deliberately OUT OF SCOPE for this pilot and must get its own KB entry, never conflated with this one.
12
+ #
13
+ # Read 2 (94 cycles) reads, 5'->3':
14
+ # [10 bp UMI][8 bp Round3][30 bp linker1][8 bp Round2][30 bp linker2][8 bp Round1]
15
+ # Read 1 (66 cycles) reads the cDNA. Round1 is the RT/first-round (oligo-dT/randN) barcode; Round3 is
16
+ # nearest the sequencing primer. Whitelists: 96 x 8 bp per round (register the actual barcode files
17
+ # from scg_lib_structs before a real run — the KB declares the registry name, never vendors the list).
18
+ #
19
+ # INDEPENDENTLY RECONSTRUCTED from the paper's own Table S12 oligos (PMC7643870): assembling the RT
20
+ # primer + round-2/round-3 ligation oligos 5'->3' reproduces both linkers base-for-base and lands
21
+ # Read 2 on exactly 10+8+30+8+30+8 = 94 cycles. The structure above is therefore derived from primary
22
+ # oligos, not copied from a diagram. See `backend.params.soloStrand` for the full derivation.
23
+ #
24
+ # THE v1/v2 DISCRIMINATOR (cheap, from Read 2 alone, and the thing most likely to be silently
25
+ # mismatched): this entry is v1 == the Science-2018 original, identified by a 30 bp linker2 with
26
+ # Round1 at offset 86-93. Parse/v2 uses a 22 bp linker2 (ATCCACGTGCTTGAGACTGTGG) with Round1 at
27
+ # 78-85. That single 8 bp shift is why published SPLiT-seq soloCBposition quadruples disagree in the
28
+ # wild (`0_86_0_93` here vs `0_78_0_85` elsewhere) — they are different chemistries, not a typo.
29
+ #
30
+ # NB on demultiplexing (Table S12 footnote): there are 96 Round1 barcodes but only 48 RT WELLS — the
31
+ # oligo-dT and random-hexamer primers in one well carry DIFFERENT barcodes, paired i <-> i+48. A
32
+ # demultiplexer that treats all 96 as distinct doubles the apparent cell count at half the depth.
33
+ # ============================================================================================
34
+
35
+ identity:
36
+ id: splitseq
37
+ version: science-2018 # the published-manuscript variant (not preprint, not Parse)
38
+ name: "SPLiT-seq (combinatorial split-pool barcoding, Rosenberg et al. 2018)"
39
+ aliases: ["SPLiT-seq", "split-pool", "combinatorial indexing", "split-pool ligation"]
40
+ assay_ontology: ["EFO:0009919"] # SPLiT-seq — verified against live EFO (EBI OLS), not memory.
41
+ # NB: Parse Evercode has its own terms (EFO:0022600/1/2) — a
42
+ # separate future entry, never this one.
43
+ modality: rna
44
+
45
+ reads:
46
+ - id: cdna
47
+ seqspec_read_id: R1 # Read 1 (66 cycles) = cDNA
48
+ file_hint: "_R1_"
49
+ strand: pos
50
+ min_len: 25
51
+ max_len: null
52
+ elements:
53
+ - {type: cdna, name: cdna, start: 0, end: null, seqspec_region_type: cdna}
54
+ # Read 2 (94 cycles) = UMI + three round barcodes separated by two fixed linkers.
55
+ - id: bc
56
+ seqspec_read_id: R2
57
+ file_hint: "_R2_"
58
+ strand: pos
59
+ min_len: 94
60
+ max_len: 94
61
+ elements:
62
+ - {type: umi, name: UMI, start: 0, end: 10, seqspec_region_type: umi}
63
+ - {type: barcode, name: bc3, start: 10, end: 18, onlist: round3, seqspec_region_type: barcode}
64
+ # linker1 = the Round3->Round2 spacer (Science-2018 sequence, verbatim from scg_lib_structs)
65
+ - {type: linker, name: linker1, start: 18, end: 48, seqspec_region_type: linker,
66
+ sequence: "GTGGCCGATGTTTCGCATCGGCGTACGACT"}
67
+ - {type: barcode, name: bc2, start: 48, end: 56, onlist: round2, seqspec_region_type: barcode}
68
+ # linker2 = the Round2->Round1 spacer (Science-2018 sequence, verbatim from scg_lib_structs)
69
+ - {type: linker, name: linker2, start: 56, end: 86, seqspec_region_type: linker,
70
+ sequence: "ATCCACGTGCTTGAGAGGCCAGAGCATTCG"}
71
+ - {type: barcode, name: bc1, start: 86, end: 94, onlist: round1, seqspec_region_type: barcode}
72
+
73
+ onlists:
74
+ round1: {registry: splitseq-round1, role: cell_barcode, expected_orientation: forward} # 96 x 8 bp
75
+ round2: {registry: splitseq-round2, role: cell_barcode, expected_orientation: forward}
76
+ round3: {registry: splitseq-round3, role: cell_barcode, expected_orientation: forward}
77
+ tenx_probe: {registry: 3M-february-2018, role: cell_barcode, expected_orientation: forward} # exclude only
78
+
79
+ signature:
80
+ requires: # the two FIXED linkers are the structural signature
81
+ - {test: read_count, roles: 2}
82
+ - {test: has_segment, read: bc, start: 18, end: 48, kind: constant} # linker1
83
+ - {test: has_segment, read: bc, start: 56, end: 86, kind: constant} # linker2
84
+ supports: # each round barcode hits its own small whitelist (width-generic 8 bp)
85
+ - when: {test: onlist_hit_rate, read: bc, element: bc1, onlist: round1, orientation: forward, min: 0.5}
86
+ weight: 3.0
87
+ - when: {test: onlist_hit_rate, read: bc, element: bc2, onlist: round2, orientation: forward, min: 0.5}
88
+ weight: 3.0
89
+ - when: {test: onlist_hit_rate, read: bc, element: bc3, onlist: round3, orientation: forward, min: 0.5}
90
+ weight: 3.0
91
+ - when: {test: distinct_ratio, read: bc, start: 0, end: 10, expect: high} # UMI ~unique
92
+ weight: 1.0
93
+ - when: {test: distinct_ratio, read: cdna, start: 0, end: 20, expect: high} # cDNA role is scored too
94
+ weight: 1.0
95
+ excludes: # a 16 bp 10x CB at read start => not SPLiT-seq
96
+ - {test: onlist_hit_rate, read: bc, start: 0, end: 16, onlist: tenx_probe, orientation: forward, min: 0.6}
97
+
98
+ backend:
99
+ module: map/starsolo
100
+ params:
101
+ soloType: CB_UMI_Complex
102
+ soloCBwhitelist: ["{onlist:round1}", "{onlist:round2}", "{onlist:round3}"]
103
+ # soloStrand: DERIVED (FLAG closed). Was the most dangerous value in this file — a wrong strand
104
+ # leaves most reads unassigned while STARsolo exits 0 and emits a matrix that merely looks like a
105
+ # thin dataset. Neither gate can catch it: the params gate only proves the value survives compose
106
+ # intact, and a simulated e2e would test our own assumed orientation against itself (circular).
107
+ # So it is derived from the primary oligos, then corroborated by the authors' own code.
108
+ #
109
+ # DERIVATION (Rosenberg et al., Science 2018, doi:10.1126/science.aam8999; Supp. Table S12 oligos
110
+ # via PMC7643870 — note the Methods miscite the oligo table as "Table S1"; it is Table S12):
111
+ # 1. RT primer = /5Phos/AGGCCAGAGCATTCG + [bc1] + dT(15)VN (or N6). It anneals to the mRNA and
112
+ # RT extends its 3' end, so first-strand cDNA is ANTISENSE with bc1 at its 5' end. Both
113
+ # primer types share that 5' architecture and differ only in the 3' priming tail, which is
114
+ # why random hexamer priming does NOT destroy strandedness here (the trap: random-primed
115
+ # bulk RNA-seq genuinely is unstranded; this is not that).
116
+ # 2. Rounds 2/3 ligate onto that same 5' end. Assembling the Table S12 oligos 5'->3' gives
117
+ # [PCR handle][UMI10][bc3][linker1][bc2][linker2][bc1][dT15VN]--antisense cDNA--, and the
118
+ # ligation junctions reconstruct THIS FILE'S linker1/linker2 base-for-base, with Read2
119
+ # landing on 10+8+30+8+30+8 = 94 cycles. The spec below is reproduced from primary oligos.
120
+ # 3. Read2 therefore reads the first strand = antisense; Read1 is its mate = SENSE to the mRNA.
121
+ # STARsolo defines Forward as "read strand same as the original RNA molecule" -> Forward.
122
+ # CORROBORATION (independent of the derivation):
123
+ # - The authors' own pipeline (github.com/Alex-Rosenberg/split-seq-pipeline, processv2.py)
124
+ # compares the read strand to the gene's GTF strand with NO flip ("The current implementation
125
+ # is stranded and will not return a gene on the opposite strand") == featureCounts -s 1.
126
+ # - It finds the TSO (AAGCAGTGGTATCAACGCAGAGTGAATGGG) at the START of read1 and trims it, with
127
+ # no reverse-complement on the counting path -> R1 reads sense.
128
+ # - scg_lib_structs states it outright and runs --soloStrand Forward on SRR6750042, i.e. on
129
+ # GSE110823 itself: "Read 1 comes from the coding strand. Therefore, use Forward".
130
+ # - An adversarial search found NO source claiming Reverse. NB salmon's `-l ISR` is NOT a
131
+ # Reverse signal: in alevin's convention (-1 barcode, -2 cDNA) ISR is the same library
132
+ # STARsolo calls Forward.
133
+ # HONEST CAVEAT: the field's practice is weaker than its claim. Nobody chose Reverse, but most
134
+ # pipelines never chose at all — they inherited STARsolo's Forward default. The two affirmative
135
+ # sources are strong (one is the authors' code); the rest is silence, and silence here means
136
+ # Forward by accident, not by verification. The remaining non-circular confirmation is rung 6:
137
+ # run GSE110823 both ways (SRR6750042 is the scg_lib_structs control; SRR6750041 is cheapest at
138
+ # 77.6M reads) — the correct strand assigns ~an order of magnitude more reads. Bounded.
139
+ soloStrand: Forward
140
+ # soloFeatures is NOT here, and that is the point. It says what to COUNT, and 10x 3' v3.1
141
+ # chemistry is byte-identical for cells and nuclei — what differs is the RNA population, a
142
+ # property of SAMPLE PREP. Filing it as chemistry cost a measured 40.7% of a nuclear library.
143
+ # It lives in the processing manifest now, where a user may instruct it.
144
+ # soloCBposition / soloUMIposition are DERIVED from the element coordinates at compose time
145
+ # (FLAG-3: never hand-enter a position quadruple from memory — generate it from the element model).
146
+
147
+ # The combinatorial geometry + onlists separate this from 10x / inDrop. What it does NOT separate
148
+ # itself from is the generic paired-end fallback, which forbids so little that it accepts these two
149
+ # files at rungs 0-2 — the confusion runs in that direction, and the relation is symmetric, so it is
150
+ # declared on both sides. See bulk-rnaseq-pe's note.
151
+ confusable_with:
152
+ - id: bulk-rnaseq-pe
153
+ relationship: processing_divergent
154
+ distinguishable_by: [onlist]
155
+ note: >
156
+ The generic paired-end fallback accepts cdna+bc on geometry alone. Rung 3 decides it: the
157
+ round1/2/3 whitelists hit, and bulk has no whitelist to hit.
@@ -0,0 +1,61 @@
1
+ """``manifest`` — assemble, validate, and hash the two artifacts.
2
+
3
+ Operations live here; the *schemas* are :mod:`seqforge.models.dataset` (the IR: what the data is) and
4
+ :mod:`seqforge.models.processing` (the flags: what to do with it). ``fill`` assembles a Decision into
5
+ a dataset manifest — each section keeping its own authority — and ``fill_processing`` builds one of
6
+ the many processing manifests a dataset may be paired with. ``validate`` is the refusal contract
7
+ (structured ``Blocker``s + a nonzero exit), and ``hash`` gives each artifact a content-addressed
8
+ identity plus the ``run_id`` that records their pairing.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from .fill import (
14
+ ExperimentInputs,
15
+ FillError,
16
+ ProcessingInputs,
17
+ dataset_uris,
18
+ experiment_from_metadata,
19
+ fill_manifest,
20
+ fill_processing,
21
+ )
22
+ from .hash import dataset_content_hash, processing_content_hash, run_id
23
+ from .instruct import INSTRUCTABLE_FIELDS, Instruction, instructions_from_assertions
24
+ from .policy import (
25
+ DEFAULT_SOLO_FEATURES,
26
+ PolicyError,
27
+ ProcessingDefaults,
28
+ ProcessingOverrides,
29
+ prep_type_from_assertions,
30
+ processing_defaults,
31
+ resolve_features,
32
+ resolve_processing,
33
+ )
34
+ from .validate import exit_code_for_report, validate_manifest, validate_processing
35
+
36
+ __all__ = [
37
+ "dataset_uris",
38
+ "fill_manifest",
39
+ "fill_processing",
40
+ "ExperimentInputs",
41
+ "experiment_from_metadata",
42
+ "ProcessingInputs",
43
+ "FillError",
44
+ "validate_manifest",
45
+ "validate_processing",
46
+ "exit_code_for_report",
47
+ "dataset_content_hash",
48
+ "processing_content_hash",
49
+ "run_id",
50
+ "processing_defaults",
51
+ "ProcessingDefaults",
52
+ "ProcessingOverrides",
53
+ "resolve_processing",
54
+ "resolve_features",
55
+ "prep_type_from_assertions",
56
+ "DEFAULT_SOLO_FEATURES",
57
+ "PolicyError",
58
+ "Instruction",
59
+ "INSTRUCTABLE_FIELDS",
60
+ "instructions_from_assertions",
61
+ ]