File size: 2,898 Bytes
ff34739
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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);
}