@inflexa-ai/harness 0.48.1 → 0.48.2
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.
- package/dist/contracts/chat-events.d.ts +40 -0
- package/dist/contracts/chat-frame.js +11 -0
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/message.d.ts +7 -1
- package/dist/contracts/schemas/chat-events.d.ts +319 -0
- package/dist/contracts/schemas/chat-events.js +29 -0
- package/dist/execution/run-synthesis.js +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/input-sanitization.d.ts +30 -2
- package/dist/input-sanitization.js +187 -2
- package/dist/loop/run-loop.js +50 -5
- package/dist/loop/tool-adapter.d.ts +8 -0
- package/dist/loop/tool-adapter.js +75 -4
- package/dist/loop/tool-detail.d.ts +5 -1
- package/dist/loop/tool-detail.js +7 -3
- package/dist/loop/tool-value.d.ts +57 -0
- package/dist/loop/tool-value.js +249 -0
- package/dist/loop/types.d.ts +9 -2
- package/dist/memory/conversation-display-storage.js +4 -1
- package/dist/memory/count-tokens.js +29 -2
- package/dist/sandbox/sandbox-step-translate.d.ts +5 -9
- package/dist/sandbox/sandbox-step-translate.js +5 -9
- package/dist/tasks/data-profile-activity.d.ts +3 -7
- package/dist/tasks/data-profile-activity.js +1 -1
- package/dist/tasks/data-profile.js +3 -6
- package/dist/tools/ad-hoc-router.js +1 -0
- package/dist/tools/bio/alphafold-prediction.js +1 -0
- package/dist/tools/bio/chembl.js +1 -0
- package/dist/tools/bio/comptox.js +1 -0
- package/dist/tools/bio/drug-gene-interactions.js +1 -0
- package/dist/tools/bio/gene-disease-evidence.js +1 -0
- package/dist/tools/bio/gene-preclinical-profile.js +1 -0
- package/dist/tools/bio/lookup-annotation.js +1 -0
- package/dist/tools/bio/opentargets.js +1 -0
- package/dist/tools/bio/pubchem.js +1 -0
- package/dist/tools/bio/pubmed.js +1 -0
- package/dist/tools/bio/search-clinical-trials.js +1 -0
- package/dist/tools/bio/search-faers.js +1 -0
- package/dist/tools/bio/search-gene.js +1 -0
- package/dist/tools/bio/search-geo-datasets.js +1 -0
- package/dist/tools/bio/search-interactions.js +1 -0
- package/dist/tools/bio/search-patents.js +1 -0
- package/dist/tools/bio/search-protein.js +1 -0
- package/dist/tools/bio/search-regulatory-approvals.js +1 -0
- package/dist/tools/bio/target-safety.js +1 -0
- package/dist/tools/capability-data.js +5 -0
- package/dist/tools/define-tool.d.ts +18 -0
- package/dist/tools/define-tool.js +2 -0
- package/dist/tools/display/show-user.js +1 -0
- package/dist/tools/execute-analysis.js +4 -0
- package/dist/tools/knowledge/check.d.ts +47 -1
- package/dist/tools/knowledge/check.js +10 -1
- package/dist/tools/knowledge/client.d.ts +1 -1
- package/dist/tools/knowledge/recommend.js +4 -3
- package/dist/tools/knowledge/situation.d.ts +39 -1
- package/dist/tools/knowledge/situation.js +80 -8
- package/dist/tools/knowledge/skeleton.d.ts +7 -0
- package/dist/tools/knowledge/skeleton.js +88 -2
- package/dist/tools/knowledge/template.js +1 -0
- package/dist/tools/memory/update-working-memory.js +5 -0
- package/dist/tools/read-tool-output.js +4 -0
- package/dist/tools/report-authoring/authoring-tools.js +8 -0
- package/dist/tools/report-session/examine-page.js +5 -0
- package/dist/tools/report-session/list-artifacts.js +5 -0
- package/dist/tools/report-session/preview-report.js +1 -0
- package/dist/tools/report-session/record-version.js +1 -0
- package/dist/tools/report-session/script-tools.js +2 -0
- package/dist/tools/research/context7-docs.js +2 -0
- package/dist/tools/research/generate-analogy-report.js +3 -0
- package/dist/tools/research/generate-plan.js +6 -0
- package/dist/tools/research/inspect-data-profile.js +5 -0
- package/dist/tools/research/inspect-run.js +5 -0
- package/dist/tools/research/resolve-citation.js +1 -0
- package/dist/tools/research/search-arxiv.js +1 -0
- package/dist/tools/research/search-github-repos.js +1 -0
- package/dist/tools/research/search-semantic-scholar.js +1 -0
- package/dist/tools/research/web-fetch.js +1 -0
- package/dist/tools/research/web-search.js +1 -0
- package/dist/tools/sandbox/lease-tools.js +2 -0
- package/dist/tools/sandbox/link-packages.js +1 -0
- package/dist/tools/sandbox/list-available-packages.js +5 -0
- package/dist/tools/sandbox/list-available-refs.js +5 -0
- package/dist/tools/sandbox/report-blocker.js +1 -0
- package/dist/tools/sandbox/skills.js +2 -0
- package/dist/tools/sandbox/submit-file-metadata.js +1 -0
- package/dist/tools/script/file-tools.js +6 -0
- package/dist/tools/script/run-script.js +20 -1
- package/dist/tools/start-report-session.js +1 -0
- package/dist/tools/tool-view.d.ts +63 -0
- package/dist/tools/tool-view.js +68 -0
- package/dist/tools/workspace/edit-file.js +5 -0
- package/dist/tools/workspace/execute-command.js +5 -2
- package/dist/tools/workspace/file-stat.js +5 -0
- package/dist/tools/workspace/grep.js +4 -0
- package/dist/tools/workspace/list-files.js +5 -0
- package/dist/tools/workspace/read-file.js +4 -0
- package/dist/tools/workspace/scan-inputs.js +5 -0
- package/dist/tools/workspace/show-file.js +1 -0
- package/dist/tools/workspace/show-plan.js +1 -0
- package/dist/tools/workspace/workspace-search.js +5 -0
- package/dist/tools/workspace/write-file.js +7 -2
- package/dist/workflows/execute-analysis.js +2 -1
- package/dist/workflows/sandbox-step.js +3 -7
- package/package.json +1 -1
- package/dist/tools/detail-resolver.d.ts +0 -40
- package/dist/tools/detail-resolver.js +0 -44
|
@@ -113,6 +113,7 @@ export const searchGeoDatasetsTool = defineTool({
|
|
|
113
113
|
.default(10)
|
|
114
114
|
.describe("Max datasets to return (default 10, max 50). totalFound reports the true match count regardless."),
|
|
115
115
|
}),
|
|
116
|
+
view: "none",
|
|
116
117
|
describeCall: "none",
|
|
117
118
|
execute: async ({ query, organism, datasetType, limit }) => {
|
|
118
119
|
let searchQuery = query;
|
|
@@ -43,6 +43,7 @@ export const searchInteractionsTool = defineTool({
|
|
|
43
43
|
.describe(`Max records returned — partners PER INPUT PROTEIN, network edges, or enriched terms depending on action (default ${DEFAULT_LIMIT}, max 500). ` +
|
|
44
44
|
"Rows are ordered best-first (score descending for interactions, FDR ascending for enrichment), so the default keeps the strongest."),
|
|
45
45
|
}),
|
|
46
|
+
view: "none",
|
|
46
47
|
describeCall: "none",
|
|
47
48
|
execute: async ({ identifiers, species, action, minScore, limit }) => {
|
|
48
49
|
if (action === "enrichment") {
|
|
@@ -88,6 +88,7 @@ export function createSearchPatentsTool(deps) {
|
|
|
88
88
|
"Requires AMASS_API_KEY — a missing key fails terminally: do NOT retry, tell the user the key needs configuring and proceed without " +
|
|
89
89
|
"Amass patent data. Every call spends the user's Amass credits.",
|
|
90
90
|
inputSchema,
|
|
91
|
+
view: "none",
|
|
91
92
|
describeCall: "none",
|
|
92
93
|
execute: async (input) => {
|
|
93
94
|
const headers = getAmassHeaders(deps.apiKey);
|
|
@@ -49,6 +49,7 @@ export const searchProteinTool = defineTool({
|
|
|
49
49
|
.describe(`Max proteins returned (default ${DEFAULT_LIMIT}, max ${MAX_LIMIT}). An exact symbol under reviewedOnly resolves to one entry, so raise ` +
|
|
50
50
|
"this only for a reviewedOnly=false survey, where a gene carries many TrEMBL rows."),
|
|
51
51
|
}),
|
|
52
|
+
view: "none",
|
|
52
53
|
describeCall: ({ query }) => query,
|
|
53
54
|
execute: async ({ query, organismId, reviewedOnly, limit }) => {
|
|
54
55
|
const result = await searchProteins(query, {
|
|
@@ -108,6 +108,7 @@ export function createSearchRegulatoryApprovalsTool(deps) {
|
|
|
108
108
|
"Requires AMASS_API_KEY — a missing key fails terminally: do NOT retry; state that the key is not configured, and proceed without " +
|
|
109
109
|
"Amass regulatory data. Every call spends the user's Amass credits.",
|
|
110
110
|
inputSchema,
|
|
111
|
+
view: "none",
|
|
111
112
|
describeCall: "none",
|
|
112
113
|
execute: async (input) => {
|
|
113
114
|
const headers = getAmassHeaders(deps.apiKey);
|
|
@@ -107,6 +107,7 @@ export const targetSafetyTool = defineTool({
|
|
|
107
107
|
"means no CURATED liability, not that none exists. Both are valid no-data — do not retry. Read `perSource` and each row's `opentargetsNote`: a " +
|
|
108
108
|
"ChEMBL or UniProt identifier absent from the panel cannot be resolved to an Ensembl id, so its Open Targets half is skipped rather than empty.",
|
|
109
109
|
inputSchema,
|
|
110
|
+
view: "none",
|
|
110
111
|
describeCall: "none",
|
|
111
112
|
execute: async ({ identifiers, identifierType, sources, filterOrgan, minSeverity, liabilityLimit }) => {
|
|
112
113
|
const selected = sources ?? [...SOURCES];
|
|
@@ -52,6 +52,11 @@ export function createInspectCapabilityDataTool(deps = {}) {
|
|
|
52
52
|
"It takes no arguments and the whole set is small, so one call is the complete answer. " +
|
|
53
53
|
"An unreadable store answers UNKNOWN rather than absent, so a store that cannot be read never reads as a store that holds nothing.",
|
|
54
54
|
inputSchema: CapabilityDataInputSchema,
|
|
55
|
+
view: {
|
|
56
|
+
input: (input) => input,
|
|
57
|
+
result: (_input, result) => result,
|
|
58
|
+
error: true,
|
|
59
|
+
},
|
|
55
60
|
describeCall: () => "declared capability data",
|
|
56
61
|
execute: async () => {
|
|
57
62
|
const declared = declaredDataRequirements();
|
|
@@ -25,6 +25,7 @@ import type { AgentSession } from "../auth/types.js";
|
|
|
25
25
|
import type { AgentRunUsage } from "../loop/metrics.js";
|
|
26
26
|
import type { EmitFn } from "../loop/types.js";
|
|
27
27
|
import type { AskRequest } from "./approval/contract.js";
|
|
28
|
+
import type { ToolCallView } from "./tool-view.js";
|
|
28
29
|
export type { EmitFn } from "../loop/types.js";
|
|
29
30
|
/**
|
|
30
31
|
* The error channel of a tool's `Result`. `error` is the message surfaced to
|
|
@@ -158,6 +159,8 @@ export type Tool<Input = unknown, Output = unknown> = {
|
|
|
158
159
|
describeCall?(input: Input): string | undefined;
|
|
159
160
|
/** See {@link ToolDefinition.describeResult}. Absent when the tool declares none. */
|
|
160
161
|
describeResult?(input: Input, result: Output): string | undefined;
|
|
162
|
+
/** See {@link ToolDefinition.view}. Absent when the tool shows no value. */
|
|
163
|
+
view?: ToolCallView<Input, Output>;
|
|
161
164
|
/**
|
|
162
165
|
* See {@link ToolDefinition.foldCallRecord}. The record arrives untyped: the
|
|
163
166
|
* loop passes back only what the `execute` of the same tool attached.
|
|
@@ -243,6 +246,21 @@ export type ToolDefinition<Schema extends z.ZodType<z.output<Schema>>, Output, O
|
|
|
243
246
|
* The hook never reaches the model.
|
|
244
247
|
*/
|
|
245
248
|
readonly describeResult?: (input: z.infer<Schema>, result: Output) => string | undefined;
|
|
249
|
+
/**
|
|
250
|
+
* What a call of this tool shows to a host: its input, its result, and its error as values with a kind. A tool
|
|
251
|
+
* shows only what it declares, thus the default of a tool is to show nothing.
|
|
252
|
+
*
|
|
253
|
+
* The decision is REQUIRED. Give a `ToolCallView`, or give the literal `"none"`. `"none"` declares that no call of
|
|
254
|
+
* the tool shows a value. `defineTool` consumes the sentinel at construction, and the sentinel never reaches a
|
|
255
|
+
* packaged tool.
|
|
256
|
+
*
|
|
257
|
+
* A member is synchronous and pure, like a `describeCall` hook, and it never reaches the model. A member gives a
|
|
258
|
+
* string for a `text` value, any other plain value for a `json` value, or the result of `viewText`, `viewJson`,
|
|
259
|
+
* `viewCode`, or `viewFields` for a value of a kind that the author chooses. The emit site then removes the control
|
|
260
|
+
* characters and the listed secret forms, and it cuts a long text. The view is the control for a secret in a form
|
|
261
|
+
* that no rule matches: a tool that holds such data declares a view that leaves it out.
|
|
262
|
+
*/
|
|
263
|
+
view: ToolCallView<z.infer<Schema>, Output> | "none";
|
|
246
264
|
/**
|
|
247
265
|
* Fold the call record of one settled call into process-local state. The
|
|
248
266
|
* record is the one that `execute` attached with `withToolCallRecord`.
|
|
@@ -135,6 +135,8 @@ export function defineTool(def) {
|
|
|
135
135
|
// property check on either side.
|
|
136
136
|
if (def.describeResult !== undefined)
|
|
137
137
|
packaged.describeResult = def.describeResult;
|
|
138
|
+
if (def.view !== "none")
|
|
139
|
+
packaged.view = def.view;
|
|
138
140
|
if (def.ownStep === true)
|
|
139
141
|
packaged = { ...packaged, ownStep: true };
|
|
140
142
|
if (def.startsWorkflow === true)
|
|
@@ -71,6 +71,7 @@ export const showUserTool = defineTool({
|
|
|
71
71
|
"One card per call, rendered in call order; an identical repeat call resolves to the same card, so compose the " +
|
|
72
72
|
"finished content before calling rather than re-rendering a draft.",
|
|
73
73
|
inputSchema: ShowUserInputSchema,
|
|
74
|
+
view: "none",
|
|
74
75
|
describeCall: ({ kind, title }) => (title === undefined ? kind : `${kind}: ${title}`),
|
|
75
76
|
execute: async (input, ctx) => {
|
|
76
77
|
// `dataPath` is a reference the host resolves at render time; validate its
|
|
@@ -625,6 +625,10 @@ export function createExecuteAnalysisTool(deps) {
|
|
|
625
625
|
"A plan that already has an active run returns that run, so a second run of one plan does not start. " +
|
|
626
626
|
"The launch fails with an error, and no run starts, when the plan is not found or fails validation, when a package of the plan cannot link, or when the run is not authorized.",
|
|
627
627
|
inputSchema,
|
|
628
|
+
view: {
|
|
629
|
+
input: (input) => input,
|
|
630
|
+
error: true,
|
|
631
|
+
},
|
|
628
632
|
// The mode is what a user most needs to see here — an approved plan
|
|
629
633
|
// running is a different event from an ad hoc request being routed.
|
|
630
634
|
describeCall: (input) => (input.mode === "plan" ? `plan ${input.planId ?? ""}` : `ad hoc: ${input.request ?? ""}`),
|
|
@@ -24,6 +24,7 @@ declare const KnowledgeCheckInputSchema: z.ZodObject<{
|
|
|
24
24
|
coexpression: "coexpression";
|
|
25
25
|
deconvolution: "deconvolution";
|
|
26
26
|
differential_expression: "differential_expression";
|
|
27
|
+
dose_response: "dose_response";
|
|
27
28
|
enrichment: "enrichment";
|
|
28
29
|
full_plan: "full_plan";
|
|
29
30
|
qc: "qc";
|
|
@@ -31,9 +32,15 @@ declare const KnowledgeCheckInputSchema: z.ZodObject<{
|
|
|
31
32
|
survival: "survival";
|
|
32
33
|
tf_activity: "tf_activity";
|
|
33
34
|
}>;
|
|
34
|
-
modality: z.
|
|
35
|
+
modality: z.ZodEnum<{
|
|
36
|
+
bulk_rna_seq: "bulk_rna_seq";
|
|
37
|
+
microarray: "microarray";
|
|
38
|
+
tempo_seq: "tempo_seq";
|
|
39
|
+
}>;
|
|
35
40
|
data_state: z.ZodEnum<{
|
|
36
41
|
counts: "counts";
|
|
42
|
+
de_results: "de_results";
|
|
43
|
+
effect_sizes_only: "effect_sizes_only";
|
|
37
44
|
fastq: "fastq";
|
|
38
45
|
log_normalized: "log_normalized";
|
|
39
46
|
tpm_or_fpkm: "tpm_or_fpkm";
|
|
@@ -53,9 +60,12 @@ declare const KnowledgeCheckInputSchema: z.ZodObject<{
|
|
|
53
60
|
unknown: "unknown";
|
|
54
61
|
}>>;
|
|
55
62
|
organism: z.ZodEnum<{
|
|
63
|
+
dog: "dog";
|
|
56
64
|
human: "human";
|
|
65
|
+
monkey: "monkey";
|
|
57
66
|
mouse: "mouse";
|
|
58
67
|
other: "other";
|
|
68
|
+
rat: "rat";
|
|
59
69
|
}>;
|
|
60
70
|
n_groups: z.ZodNumber;
|
|
61
71
|
n_per_group_min: z.ZodNumber;
|
|
@@ -69,6 +79,7 @@ declare const KnowledgeCheckInputSchema: z.ZodObject<{
|
|
|
69
79
|
litter: "litter";
|
|
70
80
|
other: "other";
|
|
71
81
|
pair: "pair";
|
|
82
|
+
plate: "plate";
|
|
72
83
|
site: "site";
|
|
73
84
|
}>>>;
|
|
74
85
|
block_structure: z.ZodOptional<z.ZodEnum<{
|
|
@@ -119,10 +130,12 @@ declare const KnowledgeCheckInputSchema: z.ZodObject<{
|
|
|
119
130
|
classifier: z.ZodOptional<z.ZodBoolean>;
|
|
120
131
|
extra_analyses: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
121
132
|
annotation: "annotation";
|
|
133
|
+
classifier_call: "classifier_call";
|
|
122
134
|
clustering: "clustering";
|
|
123
135
|
coexpression: "coexpression";
|
|
124
136
|
deconvolution: "deconvolution";
|
|
125
137
|
pathway_activity: "pathway_activity";
|
|
138
|
+
reference_module_score: "reference_module_score";
|
|
126
139
|
signature_scoring: "signature_scoring";
|
|
127
140
|
survival: "survival";
|
|
128
141
|
tf_activity: "tf_activity";
|
|
@@ -145,26 +158,59 @@ declare const KnowledgeCheckInputSchema: z.ZodObject<{
|
|
|
145
158
|
outlier_sample: "outlier_sample";
|
|
146
159
|
sample_identity_doubt: "sample_identity_doubt";
|
|
147
160
|
}>>>;
|
|
161
|
+
tissue: z.ZodOptional<z.ZodEnum<{
|
|
162
|
+
adipose: "adipose";
|
|
163
|
+
blood: "blood";
|
|
164
|
+
brain: "brain";
|
|
165
|
+
heart: "heart";
|
|
166
|
+
kidney: "kidney";
|
|
167
|
+
liver: "liver";
|
|
168
|
+
muscle: "muscle";
|
|
169
|
+
other: "other";
|
|
170
|
+
}>>;
|
|
171
|
+
exposure_axis: z.ZodOptional<z.ZodEnum<{
|
|
172
|
+
administered_dose: "administered_dose";
|
|
173
|
+
in_vitro_concentration: "in_vitro_concentration";
|
|
174
|
+
systemic_exposure: "systemic_exposure";
|
|
175
|
+
}>>;
|
|
176
|
+
n_dose_levels: z.ZodOptional<z.ZodNumber>;
|
|
177
|
+
n_compounds: z.ZodOptional<z.ZodNumber>;
|
|
178
|
+
control_placement: z.ZodOptional<z.ZodEnum<{
|
|
179
|
+
per_block: "per_block";
|
|
180
|
+
per_time: "per_time";
|
|
181
|
+
per_time_and_block: "per_time_and_block";
|
|
182
|
+
single_baseline: "single_baseline";
|
|
183
|
+
}>>;
|
|
184
|
+
grid_complete: z.ZodOptional<z.ZodBoolean>;
|
|
185
|
+
recovery_group: z.ZodOptional<z.ZodBoolean>;
|
|
148
186
|
steps: z.ZodArray<z.ZodObject<{
|
|
149
187
|
step_type: z.ZodEnum<{
|
|
150
188
|
annotation: "annotation";
|
|
189
|
+
benchmark_dose_sets: "benchmark_dose_sets";
|
|
190
|
+
classifier_call: "classifier_call";
|
|
151
191
|
clustering: "clustering";
|
|
152
192
|
coexpression: "coexpression";
|
|
153
193
|
cohort_assembly: "cohort_assembly";
|
|
194
|
+
compendium_connectivity: "compendium_connectivity";
|
|
154
195
|
deconvolution: "deconvolution";
|
|
155
196
|
differential_expression: "differential_expression";
|
|
197
|
+
dose_response_fit: "dose_response_fit";
|
|
156
198
|
enrichment: "enrichment";
|
|
199
|
+
exposure_margin: "exposure_margin";
|
|
157
200
|
filter_low_counts: "filter_low_counts";
|
|
158
201
|
model_design: "model_design";
|
|
159
202
|
multiple_testing: "multiple_testing";
|
|
160
203
|
normalize: "normalize";
|
|
161
204
|
pathway_activity: "pathway_activity";
|
|
205
|
+
point_of_departure: "point_of_departure";
|
|
162
206
|
qc_sample_structure: "qc_sample_structure";
|
|
207
|
+
reference_module_score: "reference_module_score";
|
|
163
208
|
report: "report";
|
|
164
209
|
shrink_lfc: "shrink_lfc";
|
|
165
210
|
signature_scoring: "signature_scoring";
|
|
166
211
|
survival: "survival";
|
|
167
212
|
tf_activity: "tf_activity";
|
|
213
|
+
trajectory_classification: "trajectory_classification";
|
|
168
214
|
transcript_level: "transcript_level";
|
|
169
215
|
variance_partition: "variance_partition";
|
|
170
216
|
}>;
|
|
@@ -23,6 +23,14 @@ const DraftedStepSchema = z.object({
|
|
|
23
23
|
"differential_expression",
|
|
24
24
|
"shrink_lfc",
|
|
25
25
|
"multiple_testing",
|
|
26
|
+
"dose_response_fit",
|
|
27
|
+
"benchmark_dose_sets",
|
|
28
|
+
"point_of_departure",
|
|
29
|
+
"exposure_margin",
|
|
30
|
+
"trajectory_classification",
|
|
31
|
+
"reference_module_score",
|
|
32
|
+
"classifier_call",
|
|
33
|
+
"compendium_connectivity",
|
|
26
34
|
"enrichment",
|
|
27
35
|
"variance_partition",
|
|
28
36
|
"tf_activity",
|
|
@@ -67,7 +75,7 @@ export function createKnowledgeCheckTool(deps) {
|
|
|
67
75
|
let calls = 0;
|
|
68
76
|
return defineTool({
|
|
69
77
|
id: "knowledge_check",
|
|
70
|
-
description: "Check the method steps you drafted for
|
|
78
|
+
description: "Check the method steps you drafted for an expression analysis against the rules of the Inflexa knowledge service, once, after the draft and before `submit_plan`. " +
|
|
71
79
|
"Send the same situation you sent to `knowledge_recommend` and the drafted steps: the step type, the method as the step names it, its package, and its key parameters. " +
|
|
72
80
|
"The answer lists `violations` (a forbidden method, or an inferential test on a design that a flag limits) and `warnings` (a method outside the set the rules name, a parameter that differs from a sourced default, or a required parameter that the step does not state), each with the rule id and the permitted alternatives. " +
|
|
73
81
|
"Revise a violated step once, then submit. A warning is advice: keep the step when the data gives a reason, and state the reason in the step. `ok: true` means no finding on the assessed steps. " +
|
|
@@ -75,6 +83,7 @@ export function createKnowledgeCheckTool(deps) {
|
|
|
75
83
|
"`match: unavailable` means the service did not answer, and `match: snapshot_mismatch` means the service moved to another release than the one your recommend answer came from; in both cases submit the draft as it is. " +
|
|
76
84
|
`The host accepts ${CHECK_CALL_LIMIT} checks per plan; after that the tool refuses and you submit with the findings you have.`,
|
|
77
85
|
inputSchema: KnowledgeCheckInputSchema,
|
|
86
|
+
view: "none",
|
|
78
87
|
describeCall: ({ steps }) => `${steps.length} drafted step(s)`,
|
|
79
88
|
describeResult: (_input, result) => ("ok" in result ? describeFindings(result) : result.match),
|
|
80
89
|
execute: async (input) => {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { z } from "zod";
|
|
21
21
|
import type { SituationFieldsSchema } from "./situation.js";
|
|
22
22
|
/** The role of the blocking factor. A role and never a column name, because the situation leaves the machine. */
|
|
23
|
-
export type BlockingRole = "individual" | "cell_line" | "litter" | "site" | "pair" | "other";
|
|
23
|
+
export type BlockingRole = "individual" | "cell_line" | "litter" | "site" | "pair" | "plate" | "other";
|
|
24
24
|
/** The role of a covariate of the design. A role and never a column name. */
|
|
25
25
|
export type CovariateRole = "sex" | "age" | "clinical" | "technical" | "other";
|
|
26
26
|
/** The role of the numeric variable of interest when the question is a trend, not a comparison of groups. */
|
|
@@ -49,10 +49,10 @@ export function toPlannerAnswer(joined) {
|
|
|
49
49
|
export function createKnowledgeRecommendTool(deps) {
|
|
50
50
|
return defineTool({
|
|
51
51
|
id: "knowledge_recommend",
|
|
52
|
-
description: "Get the cited, versioned procedure for
|
|
53
|
-
"which method for each step (QC, filter, normalize, model, test, shrinkage, multiple testing, enrichment, report), " +
|
|
52
|
+
description: "Get the cited, versioned procedure for an expression analysis situation (bulk RNA-seq, a microarray, or a TempO-Seq panel) from the Inflexa knowledge service, folded into plan steps: " +
|
|
53
|
+
"which method for each step (for example QC, filter, normalize, model, test, shrinkage, multiple testing, enrichment, dose-response fit, point of departure, report), " +
|
|
54
54
|
"its settings with their sources, a tested script template per step, the rules that select them, and the evidence. " +
|
|
55
|
-
"Call it BEFORE you draft a method step for
|
|
55
|
+
"Call it BEFORE you draft a method step for a differential expression, an enrichment, a QC, a dose response, or any other question that `question` names. " +
|
|
56
56
|
"Fill the situation from the Data Context (the design, the replicate counts, the batch structure, the data state, the quality concerns); never send a sample name or a file path. " +
|
|
57
57
|
`Example input: ${SITUATION_EXAMPLE}. ` +
|
|
58
58
|
"The answer holds one representation: `plan_skeleton`, the plan steps with the id, the name, the track, the agent, the packages, the dependencies, " +
|
|
@@ -80,6 +80,7 @@ export function createKnowledgeRecommendTool(deps) {
|
|
|
80
80
|
"That is the environment answer; do not call the listing tools for a package or a collection the answer already reports. " +
|
|
81
81
|
"Call it once per situation; a second call with the same situation gives the same answer.",
|
|
82
82
|
inputSchema: SituationFieldsSchema,
|
|
83
|
+
view: "none",
|
|
83
84
|
describeCall: ({ question, n_per_group_min, n_per_group_max, batch }) => `${question}: ${n_per_group_min}-${n_per_group_max} per group, batch ${batch}`,
|
|
84
85
|
describeResult: (_input, result) => result.match === "applicable" ? `${result.plan_skeleton.length} steps, ${result.claims.length} claims` : result.match,
|
|
85
86
|
execute: async (input, ctx) => {
|
|
@@ -12,6 +12,7 @@ export declare const SituationFieldsSchema: z.ZodObject<{
|
|
|
12
12
|
coexpression: "coexpression";
|
|
13
13
|
deconvolution: "deconvolution";
|
|
14
14
|
differential_expression: "differential_expression";
|
|
15
|
+
dose_response: "dose_response";
|
|
15
16
|
enrichment: "enrichment";
|
|
16
17
|
full_plan: "full_plan";
|
|
17
18
|
qc: "qc";
|
|
@@ -19,9 +20,15 @@ export declare const SituationFieldsSchema: z.ZodObject<{
|
|
|
19
20
|
survival: "survival";
|
|
20
21
|
tf_activity: "tf_activity";
|
|
21
22
|
}>;
|
|
22
|
-
modality: z.
|
|
23
|
+
modality: z.ZodEnum<{
|
|
24
|
+
bulk_rna_seq: "bulk_rna_seq";
|
|
25
|
+
microarray: "microarray";
|
|
26
|
+
tempo_seq: "tempo_seq";
|
|
27
|
+
}>;
|
|
23
28
|
data_state: z.ZodEnum<{
|
|
24
29
|
counts: "counts";
|
|
30
|
+
de_results: "de_results";
|
|
31
|
+
effect_sizes_only: "effect_sizes_only";
|
|
25
32
|
fastq: "fastq";
|
|
26
33
|
log_normalized: "log_normalized";
|
|
27
34
|
tpm_or_fpkm: "tpm_or_fpkm";
|
|
@@ -41,9 +48,12 @@ export declare const SituationFieldsSchema: z.ZodObject<{
|
|
|
41
48
|
unknown: "unknown";
|
|
42
49
|
}>>;
|
|
43
50
|
organism: z.ZodEnum<{
|
|
51
|
+
dog: "dog";
|
|
44
52
|
human: "human";
|
|
53
|
+
monkey: "monkey";
|
|
45
54
|
mouse: "mouse";
|
|
46
55
|
other: "other";
|
|
56
|
+
rat: "rat";
|
|
47
57
|
}>;
|
|
48
58
|
n_groups: z.ZodNumber;
|
|
49
59
|
n_per_group_min: z.ZodNumber;
|
|
@@ -57,6 +67,7 @@ export declare const SituationFieldsSchema: z.ZodObject<{
|
|
|
57
67
|
litter: "litter";
|
|
58
68
|
other: "other";
|
|
59
69
|
pair: "pair";
|
|
70
|
+
plate: "plate";
|
|
60
71
|
site: "site";
|
|
61
72
|
}>>>;
|
|
62
73
|
block_structure: z.ZodOptional<z.ZodEnum<{
|
|
@@ -107,10 +118,12 @@ export declare const SituationFieldsSchema: z.ZodObject<{
|
|
|
107
118
|
classifier: z.ZodOptional<z.ZodBoolean>;
|
|
108
119
|
extra_analyses: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
109
120
|
annotation: "annotation";
|
|
121
|
+
classifier_call: "classifier_call";
|
|
110
122
|
clustering: "clustering";
|
|
111
123
|
coexpression: "coexpression";
|
|
112
124
|
deconvolution: "deconvolution";
|
|
113
125
|
pathway_activity: "pathway_activity";
|
|
126
|
+
reference_module_score: "reference_module_score";
|
|
114
127
|
signature_scoring: "signature_scoring";
|
|
115
128
|
survival: "survival";
|
|
116
129
|
tf_activity: "tf_activity";
|
|
@@ -133,6 +146,31 @@ export declare const SituationFieldsSchema: z.ZodObject<{
|
|
|
133
146
|
outlier_sample: "outlier_sample";
|
|
134
147
|
sample_identity_doubt: "sample_identity_doubt";
|
|
135
148
|
}>>>;
|
|
149
|
+
tissue: z.ZodOptional<z.ZodEnum<{
|
|
150
|
+
adipose: "adipose";
|
|
151
|
+
blood: "blood";
|
|
152
|
+
brain: "brain";
|
|
153
|
+
heart: "heart";
|
|
154
|
+
kidney: "kidney";
|
|
155
|
+
liver: "liver";
|
|
156
|
+
muscle: "muscle";
|
|
157
|
+
other: "other";
|
|
158
|
+
}>>;
|
|
159
|
+
exposure_axis: z.ZodOptional<z.ZodEnum<{
|
|
160
|
+
administered_dose: "administered_dose";
|
|
161
|
+
in_vitro_concentration: "in_vitro_concentration";
|
|
162
|
+
systemic_exposure: "systemic_exposure";
|
|
163
|
+
}>>;
|
|
164
|
+
n_dose_levels: z.ZodOptional<z.ZodNumber>;
|
|
165
|
+
n_compounds: z.ZodOptional<z.ZodNumber>;
|
|
166
|
+
control_placement: z.ZodOptional<z.ZodEnum<{
|
|
167
|
+
per_block: "per_block";
|
|
168
|
+
per_time: "per_time";
|
|
169
|
+
per_time_and_block: "per_time_and_block";
|
|
170
|
+
single_baseline: "single_baseline";
|
|
171
|
+
}>>;
|
|
172
|
+
grid_complete: z.ZodOptional<z.ZodBoolean>;
|
|
173
|
+
recovery_group: z.ZodOptional<z.ZodBoolean>;
|
|
136
174
|
}, z.core.$strip>;
|
|
137
175
|
export type SituationFields = z.infer<typeof SituationFieldsSchema>;
|
|
138
176
|
/** The situation as the client sends it: the fields as given, with the caller preference held out of the situation fields. */
|
|
@@ -18,16 +18,28 @@ export const SituationFieldsSchema = z.object({
|
|
|
18
18
|
"clustering",
|
|
19
19
|
"survival",
|
|
20
20
|
"signature_scoring",
|
|
21
|
+
"dose_response",
|
|
21
22
|
])
|
|
22
23
|
.describe("What the plan needs a procedure for. `full_plan` returns QC, filtering, the model, the test, shrinkage, multiple testing, enrichment, and the report in one answer. " +
|
|
23
24
|
"Use `differential_expression` when the research question does not ask for pathways or gene sets; an enrichment step the user did not ask for is unasked scope. " +
|
|
24
25
|
"Use `enrichment` when a results table is the input and no new test is fitted, and `qc` when the question stops at the sample structure. " +
|
|
25
26
|
"Use `tf_activity` for regulator or pathway activity, `deconvolution` for cell type proportions, `coexpression` for modules, `clustering` for sample subgroups, `survival` for an outcome association, " +
|
|
26
|
-
"and `signature_scoring` for a per-sample score, or with `classifier: true` for a classifier of the samples."
|
|
27
|
-
|
|
27
|
+
"and `signature_scoring` for a per-sample score, or with `classifier: true` for a classifier of the samples. " +
|
|
28
|
+
"Use `dose_response` when the question asks at which exposure the expression leaves the control: the walk fits a curve per gene over the dose, takes the benchmark dose of each gene set, " +
|
|
29
|
+
"reads the transcriptomic point of departure from the lowest set median, and divides it by an efficacious exposure for the exposure margin. " +
|
|
30
|
+
"A `dose_response` situation carries `n_dose_levels`, `tissue`, and `exposure_axis`, because the fit, the point of departure, and the margin each stop without one of them, " +
|
|
31
|
+
"and it carries `control_placement`, `grid_complete`, `recovery_group`, and `n_compounds` when the design holds them."),
|
|
32
|
+
modality: z
|
|
33
|
+
.enum(["bulk_rna_seq", "microarray", "tempo_seq"])
|
|
34
|
+
.describe("The platform that measured the expression: `bulk_rna_seq` for a sequencing run over the whole transcriptome; `microarray` for the probe intensities of a hybridization array, the platform of the legacy toxicogenomics compendia; " +
|
|
35
|
+
"`tempo_seq` for the targeted read counts of a TempO-Seq probe panel, where the panel and not the transcriptome is the gene universe. " +
|
|
36
|
+
"An array reads no sequence, thus it has no read count and no library size: a `microarray` takes `data_state` `log_normalized`, `de_results`, or `effect_sizes_only`, and `fastq`, `counts`, or `tpm_or_fpkm` stops the plan as a contradiction. " +
|
|
37
|
+
"A probe panel is a sequencing assay and it keeps the read count, thus `tempo_seq` takes `counts`; a panel gives no transcript length, thus never `tpm_or_fpkm`."),
|
|
28
38
|
data_state: z
|
|
29
|
-
.enum(["fastq", "counts", "tpm_or_fpkm", "log_normalized"])
|
|
30
|
-
.describe("The state of the expression data in the inputs: raw reads, a raw integer count matrix, TPM/FPKM/RPKM abundances, or log-scale normalized values (log-CPM, microarray)."
|
|
39
|
+
.enum(["fastq", "counts", "tpm_or_fpkm", "log_normalized", "de_results", "effect_sizes_only"])
|
|
40
|
+
.describe("The state of the expression data in the inputs: raw reads, a raw integer count matrix, TPM/FPKM/RPKM abundances, or log-scale normalized values (log-CPM, microarray). " +
|
|
41
|
+
"`de_results` is a table of contrasts with an effect size and a p-value per gene, and `effect_sizes_only` the same table with no p-value. " +
|
|
42
|
+
"A table of contrasts holds no value of a sample and no variance inside a dose group: without the p-values no step counts calls at a false discovery rate, and either table stops a benchmark dose fit."),
|
|
31
43
|
count_source: z
|
|
32
44
|
.enum(["salmon", "kallisto", "star_featurecounts", "rsem", "unknown"])
|
|
33
45
|
.optional()
|
|
@@ -41,7 +53,9 @@ export const SituationFieldsSchema = z.object({
|
|
|
41
53
|
"`corrected_counts`: a table described as lengthScaledTPM or scaledTPM counts. " +
|
|
42
54
|
"`integer_counts`: integer counts from an aligner and a counter (STAR, featureCounts) or from a 3' protocol, with no length data. " +
|
|
43
55
|
"`unknown`: a count table from a quantifier with no statement of the correction. Omit when `data_state` is not `counts`."),
|
|
44
|
-
organism: z
|
|
56
|
+
organism: z
|
|
57
|
+
.enum(["human", "mouse", "rat", "dog", "monkey", "other"])
|
|
58
|
+
.describe("The organism of the samples. `rat`, `dog`, and `monkey` are the species of a regulatory toxicology study; use `other` only for a species this list does not name."),
|
|
45
59
|
n_groups: z
|
|
46
60
|
.number()
|
|
47
61
|
.int()
|
|
@@ -71,10 +85,10 @@ export const SituationFieldsSchema = z.object({
|
|
|
71
85
|
.describe("For a survival question, the number of samples with an observed event (event indicator 1) in the outcome column. Omit when the question is not survival or the count is not in the profile."),
|
|
72
86
|
paired: z.boolean().describe("True when each subject contributes a sample to more than one condition (paired or repeated measures)."),
|
|
73
87
|
blocking_factor: z
|
|
74
|
-
.enum(["individual", "cell_line", "litter", "site", "pair", "other"])
|
|
88
|
+
.enum(["individual", "cell_line", "litter", "site", "pair", "plate", "other"])
|
|
75
89
|
.nullable()
|
|
76
90
|
.optional()
|
|
77
|
-
.describe("The role of the blocking factor that is not the condition, never its column name: `individual` when the same subject, donor, or animal gives more than one sample; `cell_line`; `litter`; `site` for a center or a lab of a multi-site study; `pair` for matched pairs of different individuals; `other`. Omit when there is none. The column name stays in the step."),
|
|
91
|
+
.describe("The role of the blocking factor that is not the condition, never its column name: `individual` when the same subject, donor, or animal gives more than one sample; `cell_line`; `litter`; `site` for a center or a lab of a multi-site study; `pair` for matched pairs of different individuals; `plate` when the samples sit on culture or assay plates and one plate holds a set of them; `other`. Omit when there is none. The column name stays in the step."),
|
|
78
92
|
block_structure: z
|
|
79
93
|
.enum(["crossed", "nested"])
|
|
80
94
|
.optional()
|
|
@@ -126,10 +140,14 @@ export const SituationFieldsSchema = z.object({
|
|
|
126
140
|
"survival",
|
|
127
141
|
"transcript_level",
|
|
128
142
|
"annotation",
|
|
143
|
+
"reference_module_score",
|
|
144
|
+
"classifier_call",
|
|
129
145
|
]))
|
|
130
146
|
.optional()
|
|
131
147
|
.describe("Analyses the research question asks for beside the question kind, for example a regulator activity beside a differential expression. Each joins the procedure at its place. " +
|
|
132
|
-
"Add `annotation` when the rows of the count table are not gene symbols (Ensembl or Entrez identifiers) and a later step needs symbols: the identifier map is then a vetted step with its own template, and no other step rebuilds it.
|
|
148
|
+
"Add `annotation` when the rows of the count table are not gene symbols (Ensembl or Entrez identifiers) and a later step needs symbols: the identifier map is then a vetted step with its own template, and no other step rebuilds it. " +
|
|
149
|
+
"Add `reference_module_score` for a score of each sample against a reference co-expression module set, and `classifier_call` for the call of a validated classifier on each sample. " +
|
|
150
|
+
"Each of the two contrasts two levels of the group column, thus either one needs two or more groups, a time course, or a numeric predictor. Omit when none."),
|
|
133
151
|
preferred_language: z
|
|
134
152
|
.enum(["R", "python"])
|
|
135
153
|
.optional()
|
|
@@ -142,6 +160,60 @@ export const SituationFieldsSchema = z.object({
|
|
|
142
160
|
.array(z.enum(["low_depth_sample", "outlier_sample", "sample_identity_doubt", "high_duplication", "degraded_rna"]))
|
|
143
161
|
.optional()
|
|
144
162
|
.describe("The quality concerns of the profile as typed flags: a sample with a library size far below the median; a sample that does not cluster with its group; a doubt about the identity of a sample; high duplication; degraded RNA (a low RNA integrity number, a 3' coverage bias, or formalin-fixed material)."),
|
|
163
|
+
tissue: z
|
|
164
|
+
.enum(["liver", "kidney", "heart", "muscle", "brain", "adipose", "blood", "other"])
|
|
165
|
+
.optional()
|
|
166
|
+
.describe("The tissue the samples come from: `liver`, `kidney`, `heart`, `muscle` for skeletal muscle, `brain` whole or one region, `adipose`, `blood` whole or a fraction, " +
|
|
167
|
+
"or `other` for a tissue this list does not name, for example a cell line. " +
|
|
168
|
+
"A transcriptomic point of departure belongs to the tissue of its samples, and the point of departure of one tissue says nothing about another, thus one call covers one tissue and the exposure margin stops without this field. " +
|
|
169
|
+
"Read it from the sample description of the profile or from the user request. Omit it when the question is not `dose_response`, or when the samples are not from one tissue."),
|
|
170
|
+
exposure_axis: z
|
|
171
|
+
.enum(["administered_dose", "in_vitro_concentration", "systemic_exposure"])
|
|
172
|
+
.optional()
|
|
173
|
+
.describe("The axis on which the exposure values of the study lie: `administered_dose` for the dose the study gives to the animal or to the culture, for example in mg/kg; " +
|
|
174
|
+
"`in_vitro_concentration` for the concentration of the compound in the medium of a cell culture; `systemic_exposure` for a measured level in the plasma, for example a Cmax or an AUC. " +
|
|
175
|
+
"The exposure margin divides the point of departure by an efficacious exposure, and that quotient means something only when both values lie on one axis and in one unit. " +
|
|
176
|
+
"Thus the margin stops without this field, and `in_vitro_concentration` stops it too, because a concentration in a medium is no dose of an intact organism and the extrapolation from in vitro to in vivo comes first. " +
|
|
177
|
+
"Read it from the dose column of the profile or from the user request. Omit it when the question is not `dose_response`."),
|
|
178
|
+
n_dose_levels: z
|
|
179
|
+
.number()
|
|
180
|
+
.int()
|
|
181
|
+
.min(1)
|
|
182
|
+
.optional()
|
|
183
|
+
.describe("The number of dose levels of the design, the control level included. The dose levels are groups of the design, thus this count is at most `n_groups`, " +
|
|
184
|
+
"unless `continuous_predictor` is `dose` and the dose itself, not a group, is the factor of interest. " +
|
|
185
|
+
"Three levels is the floor of the walk: the model families of the average hold three or more parameters, thus below three levels no gene and no gene set has a benchmark dose and the point of departure stops. " +
|
|
186
|
+
"Read it from the dose column of the profile. Omit it when the question is not `dose_response`."),
|
|
187
|
+
n_compounds: z
|
|
188
|
+
.number()
|
|
189
|
+
.int()
|
|
190
|
+
.min(1)
|
|
191
|
+
.optional()
|
|
192
|
+
.describe("The number of compounds the study exposes the samples to. One call covers one compound: no design term names a compound as a factor, and the point of departure and the exposure margin each give one value for one compound. " +
|
|
193
|
+
"Thus 2 or more says that the plan runs the walk one time for each compound, each run over the samples of that compound and the control samples of that compound, and it pools no two compounds in one fit. " +
|
|
194
|
+
"Read it from the sample table of the profile. Omit it when the question is not `dose_response`."),
|
|
195
|
+
control_placement: z
|
|
196
|
+
.enum(["single_baseline", "per_time", "per_block", "per_time_and_block"])
|
|
197
|
+
.optional()
|
|
198
|
+
.describe("Where the control samples sit in the design, which decides the samples that form the contrast of each treated sample: `single_baseline` when one control group serves every treated group; " +
|
|
199
|
+
"`per_time` when each time point holds its own control group; `per_block` when each block, for example a plate, holds its own control group; `per_time_and_block` for both. " +
|
|
200
|
+
"With a control at each time the design takes the dose and the time as fixed factors and the control of the same time as the reference level, so that a time effect all dose levels share leaves the dose contrast. " +
|
|
201
|
+
"`per_time` and `per_time_and_block` need `n_timepoints` or `continuous_predictor: time`, and `per_block` and `per_time_and_block` need `blocking_factor`. " +
|
|
202
|
+
"Read it from the sample table of the profile. Omit it when the question is not `dose_response`."),
|
|
203
|
+
grid_complete: z
|
|
204
|
+
.boolean()
|
|
205
|
+
.optional()
|
|
206
|
+
.describe("Whether every cell of the grid of the dose levels, the times, and the blocks holds at least one sample. Set false when a cell holds none: a model that assumes every cell, " +
|
|
207
|
+
"for example a cell-means model with one coefficient per combination or a full interaction of the factors, then carries a coefficient with no data and the design matrix loses rank. " +
|
|
208
|
+
"The plan then fits the terms the design can estimate and states the estimable contrasts only. " +
|
|
209
|
+
"Read it from the sample table of the profile. Omit it when every cell holds a sample."),
|
|
210
|
+
recovery_group: z
|
|
211
|
+
.boolean()
|
|
212
|
+
.optional()
|
|
213
|
+
.describe("Set true when the study holds a group that it observes after the exposure ends, for example animals that rest after the last dose. " +
|
|
214
|
+
"Such a group carries no dose at the time of its sample, thus it lies at no point of the dose-response curve and the fit leaves it out; " +
|
|
215
|
+
"a recovery sample placed at the dose of its exposure makes the response of that dose lower than the exposure gives. " +
|
|
216
|
+
"Read it from the time column or the group labels of the sample table. Omit it when the study samples every group during the exposure."),
|
|
145
217
|
});
|
|
146
218
|
/** The situation as the client sends it: the fields as given, with the caller preference held out of the situation fields. */
|
|
147
219
|
export function toSituation(fields) {
|
|
@@ -19,6 +19,13 @@
|
|
|
19
19
|
* one enrichment step on its own track, and one report step. A group with
|
|
20
20
|
* no step in the procedure is absent from the skeleton.
|
|
21
21
|
*
|
|
22
|
+
* A step whose own template reads the output of another template gets its
|
|
23
|
+
* own group, because a fold keeps the template of the central step alone and
|
|
24
|
+
* it would drop the others. Thus the four steps of the dose-response chain
|
|
25
|
+
* (the benchmark dose of each gene, the benchmark dose of each gene set, the
|
|
26
|
+
* point of departure, and the exposure margin) are four groups of one track,
|
|
27
|
+
* in that order, and each of the other four new steps is a group of its own.
|
|
28
|
+
*
|
|
22
29
|
* The central step of a group names the method of the skeleton step, and it
|
|
23
30
|
* gives the alternatives, the template, and the environment: an alternative
|
|
24
31
|
* of a folded step (a normalization in the differential expression group)
|