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.
- seqforge/__init__.py +16 -0
- seqforge/cli/__init__.py +38 -0
- seqforge/cli/__main__.py +8 -0
- seqforge/cli/_common.py +105 -0
- seqforge/cli/compose.py +119 -0
- seqforge/cli/eval.py +103 -0
- seqforge/cli/harvest.py +417 -0
- seqforge/cli/hook.py +247 -0
- seqforge/cli/io.py +502 -0
- seqforge/cli/kb.py +348 -0
- seqforge/cli/manifest.py +536 -0
- seqforge/cli/probe.py +43 -0
- seqforge/cli/processing.py +192 -0
- seqforge/cli/project.py +52 -0
- seqforge/cli/resolve.py +55 -0
- seqforge/cli/root.py +66 -0
- seqforge/cli/run.py +463 -0
- seqforge/cli/schema.py +41 -0
- seqforge/compose/__init__.py +28 -0
- seqforge/compose/core.py +515 -0
- seqforge/compose/gates.py +113 -0
- seqforge/compose/params.py +447 -0
- seqforge/e2e.py +1926 -0
- seqforge/evals/__init__.py +78 -0
- seqforge/evals/case.py +382 -0
- seqforge/evals/grade.py +300 -0
- seqforge/evals/run.py +420 -0
- seqforge/harvest/__init__.py +121 -0
- seqforge/harvest/extract.py +319 -0
- seqforge/harvest/fields.py +212 -0
- seqforge/harvest/normalize.py +537 -0
- seqforge/harvest/prep.py +41 -0
- seqforge/harvest/providers.py +321 -0
- seqforge/harvest/verify.py +251 -0
- seqforge/hooks/__init__.py +33 -0
- seqforge/hooks/guards.py +214 -0
- seqforge/io/__init__.py +61 -0
- seqforge/io/archive.py +450 -0
- seqforge/io/attributes.py +190 -0
- seqforge/io/biosample/attributes.json +6341 -0
- seqforge/io/efo/labels.json +55 -0
- seqforge/io/efo.py +138 -0
- seqforge/io/onlist.py +661 -0
- seqforge/io/onlists/3M-february-2018.codes.gz +0 -0
- seqforge/io/onlists/737K-arc-v1.codes.gz +0 -0
- seqforge/io/onlists/737K-august-2016.codes.gz +0 -0
- seqforge/io/onlists/bd-rhapsody-cls1-384.codes.gz +0 -0
- seqforge/io/onlists/bd-rhapsody-cls1.codes.gz +0 -0
- seqforge/io/onlists/bd-rhapsody-cls2-384.codes.gz +0 -0
- seqforge/io/onlists/bd-rhapsody-cls2.codes.gz +0 -0
- seqforge/io/onlists/bd-rhapsody-cls3-384.codes.gz +0 -0
- seqforge/io/onlists/bd-rhapsody-cls3.codes.gz +0 -0
- seqforge/io/onlists/index.json +74 -0
- seqforge/io/remote.py +659 -0
- seqforge/io/taxonomy.py +194 -0
- seqforge/kb/__init__.py +62 -0
- seqforge/kb/anchor.py +169 -0
- seqforge/kb/generate.py +147 -0
- seqforge/kb/loader.py +152 -0
- seqforge/kb/roundtrip.py +112 -0
- seqforge/kb/schema.py +422 -0
- seqforge/kb/specs/10x-3p-gex/spec.yaml +62 -0
- seqforge/kb/specs/10x-3p-gex-v2/README.md +41 -0
- seqforge/kb/specs/10x-3p-gex-v2/spec.yaml +83 -0
- seqforge/kb/specs/10x-3p-gex-v3/README.md +56 -0
- seqforge/kb/specs/10x-3p-gex-v3/spec.yaml +118 -0
- seqforge/kb/specs/10x-3p-gex-v3.1/README.md +56 -0
- seqforge/kb/specs/10x-3p-gex-v3.1/spec.yaml +124 -0
- seqforge/kb/specs/bd-rhapsody-wta/README.md +103 -0
- seqforge/kb/specs/bd-rhapsody-wta/spec.yaml +130 -0
- seqforge/kb/specs/bd-rhapsody-wta-enhanced/spec.yaml +99 -0
- seqforge/kb/specs/bd-rhapsody-wta-enhanced-v1/spec.yaml +93 -0
- seqforge/kb/specs/bd-rhapsody-wta-enhanced-v2/spec.yaml +81 -0
- seqforge/kb/specs/bulk-rnaseq-pe/README.md +35 -0
- seqforge/kb/specs/bulk-rnaseq-pe/spec.yaml +97 -0
- seqforge/kb/specs/splitseq/README.md +51 -0
- seqforge/kb/specs/splitseq/spec.yaml +157 -0
- seqforge/manifest/__init__.py +61 -0
- seqforge/manifest/fill.py +531 -0
- seqforge/manifest/hash.py +77 -0
- seqforge/manifest/instruct.py +114 -0
- seqforge/manifest/policy.py +409 -0
- seqforge/manifest/validate.py +274 -0
- seqforge/models/__init__.py +268 -0
- seqforge/models/assertion.py +68 -0
- seqforge/models/base.py +100 -0
- seqforge/models/blocker.py +71 -0
- seqforge/models/conflict.py +47 -0
- seqforge/models/dataset.py +320 -0
- seqforge/models/evidenced.py +54 -0
- seqforge/models/observation.py +157 -0
- seqforge/models/processing.py +231 -0
- seqforge/models/records.py +145 -0
- seqforge/models/resolve.py +216 -0
- seqforge/probe/__init__.py +46 -0
- seqforge/probe/core.py +232 -0
- seqforge/probe/signals.py +250 -0
- seqforge/probe/streaming.py +118 -0
- seqforge/project.py +177 -0
- seqforge/py.typed +0 -0
- seqforge/resolve/__init__.py +98 -0
- seqforge/resolve/assign.py +204 -0
- seqforge/resolve/cache.py +119 -0
- seqforge/resolve/confuse.py +215 -0
- seqforge/resolve/engine.py +646 -0
- seqforge/resolve/escalate.py +668 -0
- seqforge/resolve/evaluators.py +306 -0
- seqforge/resolve/geometry.py +89 -0
- seqforge/resolve/group.py +85 -0
- seqforge/resolve/records.py +550 -0
- seqforge/resolve/scoring.py +373 -0
- seqforge/resolve/window.py +206 -0
- seqforge/workflows/__init__.py +234 -0
- seqforge/workflows/cram.py +117 -0
- seqforge/workflows/h5ad.py +368 -0
- seqforge/workflows/map/star.smk +101 -0
- seqforge/workflows/map/starsolo.smk +360 -0
- seqforge/workflows/qc.py +157 -0
- seqforge/workspace.py +125 -0
- seqforge-2026.7.1.dist-info/METADATA +125 -0
- seqforge-2026.7.1.dist-info/RECORD +124 -0
- seqforge-2026.7.1.dist-info/WHEEL +4 -0
- seqforge-2026.7.1.dist-info/entry_points.txt +2 -0
- 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
|
+
]
|