Skip to the content

Capsid menu

app/kb/procedures/taxonomy.mjs

89 lines and 6 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.

Functions

The functions of taxonomy.mjs
FunctionLinesComplexityCoveredCRAPMutation
statusLabel49 to 512100%OkCRAP 2No data: no mutants in this code
<anonymous:71>71 to 711100%OkCRAP 1No data: no mutants in this code
methodLabel79 to 812100%OkCRAP 2No data: no mutants in this code
rememberOrganisms88 to 902100%OkCRAP 2No data: no mutants in this code
organismLabel91 to 933100%OkCRAP 3No data: no mutants in this code
courseLabel94 to 962100%OkCRAP 2No data: no mutants in this code

Imports

It imports no other module of this repo. Imported by app/kb/form-fields.mjs, app/kb/libraries.mjs, app/kb/procedures/cite.mjs, app/kb/procedures/compile.mjs, app/kb/procedures/library.mjs, app/kb/procedures/render.mjs, app/kb/procedures/validate.mjs, app/routes.ts, app/routes/kb-library.tsx, app/routes/protocols.tsx.

Source

// The closed lists a procedure's `method`, `organism` and `course` are chosen from, so the protocol library can
// filter by them (docs/PROCEDURES.md): a value is an id from here or the save is refused, which keeps "PCR" from
// being "pcr", "PCR" and "polymerase chain reaction" on three pages. A procedure's `target` (the gene or region it
// amplifies, the sample it works on) is free text, because targets are as many as the experiments.
//
// The organism ids are the phages' host keys (app/lib/phages/compile.mjs HOSTS) where a host exists, so a protocol's
// organism and a phage's host are the same word. When the lab registry holds host strains, these become records
// and this list is read from them.

/** What a procedure is mostly doing: the library's "method" facet. */
export const METHODS = Object.freeze({
  pcr: "PCR",
  plating: "Plating and titering",
  culture: "Culture",
  extraction: "Nucleic acid extraction",
  sequencing: "Sequencing",
  annotation: "Genome annotation",
  media: "Media and reagents",
  microscopy: "Microscopy",
});

/**
 * The organisms a procedure works with or on that are not a cultured strain. The rest of the list is the lab registry's strains
 * (docs/REGISTRY.md): a strain's id is an organism id, and its organism is the words, so a strain added to the registry is an
 * organism a protocol can name with no edit here.
 */
export const NON_STRAIN_ORGANISMS = Object.freeze({
  avian: "Birds",
});

/** The courses a procedure is taught in, each with the page that describes it. */
export const COURSES = Object.freeze({
  "phage-discovery": { label: "Phage Discovery Program", path: "/teaching/phage-discovery" },
  "virus-isolation": { label: "Virus Isolation Course", path: "/teaching/virus-isolation" },
  "phage-bioinformatics": { label: "Phage Bioinformatics Course", path: "/teaching/phage-bioinformatics" },
});

/**
 * Where a protocol stands in the lab, a closed list so the editor offers a choice and the page says it in one set of words
 * (Dustin, 2026-10-09, the form editor). Each is the protocol's `status`, or "MISSING: <why>" until someone sets it.
 */
export const PROTOCOL_STATUSES = Object.freeze({
  "in-use": "In use",
  "in-development": "In development",
  retired: "Retired",
});

/** A status id's words, or the id itself. @param {string} id */
export function statusLabel(id) {
  return /** @type {Record<string, string>} */ (PROTOCOL_STATUSES)[id] ?? id;
}

/**
 * The phage workflow the library draws as a strip, in the order a phage takes through the lab: each stage is the
 * methods that carry it out. A stage with no protocol yet is drawn without a link, so the strip is the lab's own
 * path and a protocol appears under its stage the moment one is saved. `tools` are the ids of the calculators
 * (TOOLS in phage-tools.mjs) a person uses at that stage, so the strip links them from the one place that says so.
 */
export const PHAGE_PIPELINE = Object.freeze([
  { id: "isolate", label: "Isolate and purify", methods: ["plating", "culture"], tools: ["titer", "dilution", "webbed-plate", "lysate-volume"] },
  { id: "extract", label: "Extract DNA", methods: ["extraction"], tools: [] },
  { id: "sequence", label: "Sequence", methods: ["sequencing"], tools: [] },
  { id: "annotate", label: "Annotate", methods: ["annotation"], tools: [] },
]);

/**
 * The tabs above the library: the lab's two kinds of work, each the methods that make it up. "All" is implied. A tab
 * is a link to the library narrowed to its methods, so it is also what the method facet would give.
 */
export const LIBRARY_TABS = Object.freeze([
  { id: "phage", label: "Phage work", methods: PHAGE_PIPELINE.flatMap((stage) => stage.methods) },
  { id: "pcr", label: "PCR and primers", methods: ["pcr"] },
]);

/** The page that lists the phage calculators (docs/PHAGE-TOOLS.md): the library's Calculators tab goes there. */
export const CALCULATORS_PATH = "/research/tools";

/** The words for an id in a closed list, or the id itself when the list does not know it. */
export function methodLabel(/** @type {string} */ id) {
  return /** @type {Record<string, string>} */ (METHODS)[id] ?? id;
}
/**
 * The words for an organism id: a non-strain organism's own, else the registry strain's organism, as the protocols' records carry
 * it (compile.mjs reads it from the registry; `libraryItems` hands it here), else the id. The facet's label function is given a
 * value and nothing else, so the words read so far are kept by id; each request's `libraryItems` refreshes them from its records.
 */
const organismWords = /** @type {Map<string, string>} */ (new Map());
export function rememberOrganisms(/** @type {Record<string, string>} */ names) {
  for (const [id, words] of Object.entries(names)) organismWords.set(id, words);
}
export function organismLabel(/** @type {string} */ id) {
  return /** @type {Record<string, string>} */ (NON_STRAIN_ORGANISMS)[id] ?? organismWords.get(id) ?? id;
}
export function courseLabel(/** @type {string} */ id) {
  return /** @type {Record<string, { label: string }>} */ (COURSES)[id]?.label ?? id;
}

/** The protocol library's address; its filters are query parameters on it, so the canonical stays this path. */
export const LIBRARY_PATH = "/research/protocols";