All files / src/analysis registry.ts

100% Statements 88/88
100% Branches 9/9
100% Functions 4/4
100% Lines 88/88

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219                                                2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x           2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x     2x                                                 2x 2x 2x 2x             2x       2x 2x 2x 2x 2x 2x       2x 2x       2x 2x 2x 2x 2x       2x 2x 2x           2x       2x         2x         2x 2x 2x 2x 2x     2x 2x     2x     2x 2x 2x 2x 2x                   2x 2081x 2081x                 2x 145x 145x     2x 2x 2x               5292x 5292x 5292x                 2x 2x 2x 2x 2x 2x    
/**
 * Every task the panel can offer, in the order it offers them.
 *
 * Main-thread only, and free of `MetaCooler` by construction: it imports
 * descriptors from `analysis/tasks/` and nothing from `analysis/run/`, which is
 * the split `analysis/job.ts` argues for and the reason the analysis worker's
 * bundle does not contain the panel's half of this subtree.
 *
 * **A task that means nothing on this file is absent, not disabled.** That is
 * §8.5's doctrine applied a second time and for the same reason §18.1 gives: a
 * disabled row is a promise the file cannot keep, and the reader spends their
 * attention discovering why rather than reading what is there. {@link tasksFor}
 * is the whole of the mechanism — a row that survives it is a row that will run
 * — and there is deliberately no second list of tasks that exist but are greyed
 * out. A `.metacool` carries no gene models, so it offers no metaplot; a `.cool`
 * has no genomes, so it offers no MAG tasks; and both offer P(s), because both
 * have a diagonal.
 *
 * Registry order is the catalogue order of §18.8 and is not sorted here. It is a
 * reading order — the tasks somebody runs first come first, and the composites
 * come after the things they are built from — and sorting the list by title
 * would replace an argued order with an alphabetical one.
 */
 
import { CHROMOSIGHT_TASK } from "./tasks/chromosight";
import { HICCUPS_TASK } from "./tasks/hiccups";
import { devFeature } from '../state/devFeatures';
import { CELL_QC_TASK } from "./tasks/cellqc";
import { HICREP_TASK } from "./tasks/hicrep";
import { CIS_TRANS_TASK } from "./tasks/cistrans";
import { COVERAGE_TASK } from "./tasks/coverage";
import { FOURC_TASK } from "./tasks/fourc";
import { APA_TASK } from "./tasks/apa";
import { COMPARTMENTS_TASK } from "./tasks/compartments";
import { INTERARM_TASK } from "./tasks/interarm";
import {
  MAG_NETWORK_TASK,
  MAG_PURITY_TASK,
  MAG_QC_TASK,
  MAG_SPLIT_TASK,
} from "./tasks/mags";
import { FRIP_TASK } from "./tasks/frip";
import { OVERLAP_TASK } from "./tasks/overlap";
import { TRACK_CORRELATION_TASK } from "./tasks/trackcorr";
import { METAPLOT_TASK } from "./tasks/metaplot";
import { MOTIF_TASK } from "./tasks/motif";
import { RESTRICTION_TASK } from "./tasks/restriction";
import { PAIRS_QC_TASK } from "./tasks/pairsqc";
import { MAP_REPORT_TASK } from "./tasks/mapreport";
import { PEAKS_TASK } from "./tasks/peaks";
import { COMPOSITION_TASK } from "./tasks/composition";
import { DOTPLOT_TASK } from "./tasks/dotplot";
import { GROUP_SIGNAL_TASK } from "./tasks/groupsignal";
import { ORITER_TASK } from "./tasks/oriter";
import { INSULATION_TASK } from "./tasks/insulation";
import { ARROWHEAD_TASK } from "./tasks/arrowhead";
import { DI_TASK } from "./tasks/di";
import { HIC_SCORE_TASK } from "./tasks/hicscore";
import { SCALOGRAM_TASK } from "./tasks/scalogram";
import { EXPECTED_TASK } from "./tasks/ps";
import { RECONSTRUCT_TASK, SPATIAL_TRACKS_TASK, STRUCTURE_MAP_TASK } from "./tasks/structure";
import type { AnalysisContext, AnalysisTask, TaskGroup } from "./types";
 
export const TASKS: readonly AnalysisTask[] = [
  // Registry order is catalogue order (§18.8), and the cards come out in the
  // order their first task does — so this list is read as four blocks.
  //
  // **2D.** Outward from the axis: what every bin *has* first (coverage and the
  // marginals), then what every *sequence* has — cis, trans and which pairs of
  // sequences touch, which is the same question one level up and the one that
  // says whether the assembly is the karyotype — then how contact falls away
  // from the diagonal (P(s)), then how far each *bin* reaches within that decay
  // (the scalogram, which is P(s) kept per bin instead of averaged over the
  // sequence), then one locus's slice of that decay (4C), then the domains it
  // resolves into — insulation, the directionality index that reads the same
  // edge from its asymmetry, and the HiC score that says how much contact is
  // there at all rather than how it is shared out — then the compartments those
  // domains sit in. Each is a question the one before it makes it sensible to
  // ask. Detection comes next and is the only one that *calls* anything: it
  // needs the decay the tasks above it describe, and what it produces — a loop
  // set — is what the pileup under it takes as input. Reproducibility and the
  // pileup close the card: both read the matrix against something else, another
  // map in one case and a set of places in the other.
  //
  // Detection is two rows, the narrower question first: *loop calling*
  // (HiCCUPS) asks of every pixel whether it is a dot, and only that, where
  // *pattern detection* (chromosight) matches any of five shapes, a loop among
  // them — so a reader looking for loops meets the caller built for them first.
  COVERAGE_TASK,
  CIS_TRANS_TASK,
  CELL_QC_TASK,
  EXPECTED_TASK,
  // The library under the map, beside the decay it takes its first figure
  // from: P(s) by orientation is the four curves the matrix sums together, and
  // it moved here from P(s)'s own form along with the rest of what a `.pairs`
  // says about itself — pairtools' counts and, with an enzyme, its fragments.
  // Read after P(s) because it answers the question P(s) raises first: how
  // short a separation the curve above can be believed at.
  PAIRS_QC_TASK,
  // The library on one page: after the four it gathers from — coverage, cis and
  // trans, P(s) and the records under them — since it is their composite, and
  // before the tasks that read structure out of a map it has said can bear it.
  MAP_REPORT_TASK,
  SCALOGRAM_TASK,
  FOURC_TASK,
  INSULATION_TASK,
  DI_TASK,
  HIC_SCORE_TASK,
  // The domains read off the map's corners rather than off a track of minima:
  // after the three that score each bin, because what it calls — nested, and
  // with gaps — is the answer those three cannot give by construction.
  ARROWHEAD_TASK,
  COMPARTMENTS_TASK,
  // The arms of a bacterial chromosome, after the compartments that are read
  // with their secondary diagonal divided out: how strongly the two arms touch
  // is the question that makes the arm-frame expectation necessary at all.
  INTERARM_TASK,
  HICCUPS_TASK,
  CHROMOSIGHT_TASK,
  HICREP_TASK,
  APA_TASK,
  // **3D**, and the one that *makes* a model comes first, because the other two
  // read one: a reader with no structure loaded has exactly one thing to do in
  // this card, and it should be the first row rather than the third.
  RECONSTRUCT_TASK,
  STRUCTURE_MAP_TASK,
  SPATIAL_TRACKS_TASK,
  // **1D**, opening with the one task that *makes* an interval set out of the
  // file rather than reading one somebody loaded: a motif search answers where a
  // sequence occurs, and what it produces is exactly the input the four below it
  // take. Same reading order as the 2D card's — the thing that comes first is
  // the thing the next one is built on.
  MOTIF_TASK,
  // Beside it, the search for one particular kind of site: where an enzyme
  // cuts, to the base, rather than where its recognition sequence lies — the
  // digest a Hi-C library is read against, which Pairs QC requires.
  RESTRICTION_TASK,
  // And the other that makes one: peaks called from a track, which is the input
  // FRiP, the metaplot and the overlap below were always handed from elsewhere.
  // A motif search reads the sequence and this reads a track, so the two open
  // the card side by side, before the tasks that read what they make.
  PEAKS_TASK,
  // Then the two that read a bacterial chromosome off its own sequence: its
  // composition, whose GC skew changes sign at the ends of replication, and the
  // origin and terminus that skew and the sites around it place — which the 2D
  // tasks that read the chromosome's arms then take as their reference point.
  COMPOSITION_TASK,
  ORITER_TASK,
  METAPLOT_TASK,
  TRACK_CORRELATION_TASK,
  FRIP_TASK,
  // Beside FRiP, which asks how much of a track sits inside one set: this asks
  // how its values differ between the groups of one — A against B.
  GROUP_SIGNAL_TASK,
  OVERLAP_TASK,
  // Last of the 1D card, and of a kind of its own: two sequence sets against
  // each other rather than one read along its length (§28) — a dev feature.
  DOTPLOT_TASK,
  // **Metagenome**, absent unless the file has genomes, so the card is there
  // for a metacool and nowhere else.
  MAG_QC_TASK,
  MAG_NETWORK_TASK,
  MAG_PURITY_TASK,
  MAG_SPLIT_TASK,
];
 
/**
 * The descriptor for an id, or `undefined` where there is none.
 *
 * `undefined` rather than a throw because the callers are a stored session, a
 * shared URL and a result held over from an earlier build, and every one of
 * those can legitimately name a task this build does not have. The row that
 * cannot be rebuilt is dropped; the app is not.
 */
export function taskById(id: string): AnalysisTask | undefined {
  return TASKS.find((task) => task.id === id);
}
 
/**
 * The tasks worth listing, in registry order.
 *
 * Everything that is not `absent` — which includes the ones that cannot run yet.
 * A row that says *load a loop set* is the menu; a row that is missing teaches a
 * reader the feature does not exist. See {@link TaskAvailability}.
 */
export function tasksFor(context: AnalysisContext): AnalysisTask[] {
  return TASKS.filter((task) => shown(task) && task.availability(context).state !== 'absent');
}
 
/** The tasks that could be run right now — what the panel counts as usable. */
export function runnableTasks(context: AnalysisContext): AnalysisTask[] {
  return TASKS.filter((task) => shown(task) && task.availability(context).state === 'ready');
}
 
/**
 * Whether a task is offered at all: always, unless it belongs to a dev feature
 * that is off (§29) — which is *absent*, the strongest answer, since a greyed
 * row with *load a metagenome* on it is exactly the sentence the switch exists
 * to keep off the screen.
 */
function shown(task: AnalysisTask): boolean {
  return task.dev === undefined || devFeature(task.dev);
}
 
/**
 * The four headings of §18.8's catalogue, one per card.
 *
 * Written out rather than derived from the group name, because `2d` is an
 * identifier and "2D tasks" is a heading, and a heading generated from an
 * identifier is a heading nobody chose.
 */
export const TASK_GROUP_LABELS: Record<TaskGroup, string> = {
  "2d": "2D tasks",
  "3d": "3D tasks",
  "1d": "1D tasks",
  metagenome: "Metagenome",
};