Spaces:
Sleeping
Sleeping
| import type { SpecDoc } from "./types"; | |
| /** | |
| * Resolve a finding's `evidence[].spec_ref` (a heterogeneous dotted path with an | |
| * optional [selector]) to a DOM anchor id in the spec viewer, and check whether | |
| * it actually resolves. The spec-viewer components render matching `id`s. | |
| * Keyed on the run's snapshot spec — NEVER on spec_id (which drifts). | |
| */ | |
| // Anchor-id helpers — shared by the resolver and the spec viewer so they agree. | |
| export const anchor = { | |
| read: (read: string) => `spec-read-${read}`, | |
| chain: (read: string, name: string) => `spec-chain-${read}-${name}`, | |
| oligo: (id: string) => `spec-oligo-${id}`, | |
| whitelist: (key: string) => `spec-whitelist-${key}`, | |
| platformParam: (field: string) => `spec-pp-${field}`, | |
| libStep: (step: number) => `spec-libstep-${step}`, | |
| }; | |
| export interface SpecRefTarget { | |
| anchorId: string | null; | |
| label: string; // human-friendly label for the chip | |
| found: boolean; | |
| } | |
| function parseSelector(seg: string): { key: string; selector?: string } { | |
| const m = seg.match(/^([^[]+)(?:\[([^\]]+)\])?$/); | |
| if (!m) return { key: seg }; | |
| return { key: m[1], selector: m[2] }; | |
| } | |
| export function resolveSpecRef(spec: SpecDoc | null, ref: string): SpecRefTarget { | |
| const fail = (label: string): SpecRefTarget => ({ anchorId: null, label, found: false }); | |
| if (!ref) return fail(ref); | |
| if (!spec) return fail(ref); | |
| const parts = ref.split("."); | |
| const root = parts[0]; | |
| if (root === "read_structure") { | |
| // read_structure.R2 OR read_structure.R2.readthrough_chain[tso_5prime] | |
| const readName = parts[1]; | |
| const reads = spec.read_structure?.reads ?? []; | |
| const read = reads.find((r) => r.read === readName); | |
| if (parts[2]) { | |
| const { key, selector } = parseSelector(parts[2]); | |
| if (key === "readthrough_chain" && selector) { | |
| const el = read?.readthrough_chain?.find((c) => c.name === selector); | |
| return el | |
| ? { anchorId: anchor.chain(readName, selector), label: `${readName} · ${selector}`, found: true } | |
| : fail(ref); | |
| } | |
| } | |
| return read | |
| ? { anchorId: anchor.read(readName), label: `read ${readName}`, found: true } | |
| : fail(ref); | |
| } | |
| if (root === "oligos") { | |
| const id = parts.slice(1).join("."); | |
| const o = spec.oligos?.find((x) => x.oligo_id === id); | |
| return o ? { anchorId: anchor.oligo(id), label: id, found: true } : fail(ref); | |
| } | |
| if (root === "whitelists") { | |
| const key = parts.slice(1).join("."); | |
| return spec.whitelists && key in spec.whitelists | |
| ? { anchorId: anchor.whitelist(key), label: key, found: true } | |
| : fail(ref); | |
| } | |
| if (root === "platform_params") { | |
| const field = parts.slice(1).join("."); | |
| return spec.platform_params && field in spec.platform_params | |
| ? { anchorId: anchor.platformParam(field), label: `platform · ${field}`, found: true } | |
| : fail(ref); | |
| } | |
| return fail(ref); | |
| } | |