p2s_mapper 1.0.0

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.
Files changed (172) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +148 -0
  3. package/dist/alignmentQuality.d.ts +58 -0
  4. package/dist/alignmentQuality.js +108 -0
  5. package/dist/alignmentQuality.js.map +1 -0
  6. package/dist/alphaFoldModels.d.ts +29 -0
  7. package/dist/alphaFoldModels.js +60 -0
  8. package/dist/alphaFoldModels.js.map +1 -0
  9. package/dist/caCoordsToPdb.d.ts +18 -0
  10. package/dist/caCoordsToPdb.js +95 -0
  11. package/dist/caCoordsToPdb.js.map +1 -0
  12. package/dist/chooseMappedEntity.d.ts +55 -0
  13. package/dist/chooseMappedEntity.js +140 -0
  14. package/dist/chooseMappedEntity.js.map +1 -0
  15. package/dist/coordinates.d.ts +66 -0
  16. package/dist/coordinates.js +66 -0
  17. package/dist/coordinates.js.map +1 -0
  18. package/dist/dbIds.d.ts +16 -0
  19. package/dist/dbIds.js +42 -0
  20. package/dist/dbIds.js.map +1 -0
  21. package/dist/extractPerResidueConfidence.d.ts +49 -0
  22. package/dist/extractPerResidueConfidence.js +42 -0
  23. package/dist/extractPerResidueConfidence.js.map +1 -0
  24. package/dist/extractStructureSequences.d.ts +142 -0
  25. package/dist/extractStructureSequences.js +217 -0
  26. package/dist/extractStructureSequences.js.map +1 -0
  27. package/dist/fetchUtils.d.ts +34 -0
  28. package/dist/fetchUtils.js +91 -0
  29. package/dist/fetchUtils.js.map +1 -0
  30. package/dist/index.d.ts +23 -0
  31. package/dist/index.js +46 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/invertMap.d.ts +2 -0
  34. package/dist/invertMap.js +8 -0
  35. package/dist/invertMap.js.map +1 -0
  36. package/dist/isoformRanking.d.ts +47 -0
  37. package/dist/isoformRanking.js +91 -0
  38. package/dist/isoformRanking.js.map +1 -0
  39. package/dist/mappings.d.ts +58 -0
  40. package/dist/mappings.js +193 -0
  41. package/dist/mappings.js.map +1 -0
  42. package/dist/package.json +1 -0
  43. package/dist/pairwiseAlignment.d.ts +35 -0
  44. package/dist/pairwiseAlignment.js +337 -0
  45. package/dist/pairwiseAlignment.js.map +1 -0
  46. package/dist/pdbUniProtMapping.d.ts +75 -0
  47. package/dist/pdbUniProtMapping.js +241 -0
  48. package/dist/pdbUniProtMapping.js.map +1 -0
  49. package/dist/pdbeBestStructures.d.ts +23 -0
  50. package/dist/pdbeBestStructures.js +79 -0
  51. package/dist/pdbeBestStructures.js.map +1 -0
  52. package/dist/pdbeEntryMolecules.d.ts +7 -0
  53. package/dist/pdbeEntryMolecules.js +39 -0
  54. package/dist/pdbeEntryMolecules.js.map +1 -0
  55. package/dist/siftsNumbering.d.ts +23 -0
  56. package/dist/siftsNumbering.js +44 -0
  57. package/dist/siftsNumbering.js.map +1 -0
  58. package/dist/stripStopCodon.d.ts +15 -0
  59. package/dist/stripStopCodon.js +24 -0
  60. package/dist/stripStopCodon.js.map +1 -0
  61. package/dist/structureFormat.d.ts +40 -0
  62. package/dist/structureFormat.js +54 -0
  63. package/dist/structureFormat.js.map +1 -0
  64. package/dist/structureSources.d.ts +28 -0
  65. package/dist/structureSources.js +76 -0
  66. package/dist/structureSources.js.map +1 -0
  67. package/dist/structureUrls.d.ts +54 -0
  68. package/dist/structureUrls.js +147 -0
  69. package/dist/structureUrls.js.map +1 -0
  70. package/dist/types.d.ts +14 -0
  71. package/dist/types.js +27 -0
  72. package/dist/types.js.map +1 -0
  73. package/dist/uniProtSearch.d.ts +32 -0
  74. package/dist/uniProtSearch.js +114 -0
  75. package/dist/uniProtSearch.js.map +1 -0
  76. package/esm/alignmentQuality.d.ts +58 -0
  77. package/esm/alignmentQuality.js +100 -0
  78. package/esm/alignmentQuality.js.map +1 -0
  79. package/esm/alphaFoldModels.d.ts +29 -0
  80. package/esm/alphaFoldModels.js +55 -0
  81. package/esm/alphaFoldModels.js.map +1 -0
  82. package/esm/caCoordsToPdb.d.ts +18 -0
  83. package/esm/caCoordsToPdb.js +91 -0
  84. package/esm/caCoordsToPdb.js.map +1 -0
  85. package/esm/chooseMappedEntity.d.ts +55 -0
  86. package/esm/chooseMappedEntity.js +134 -0
  87. package/esm/chooseMappedEntity.js.map +1 -0
  88. package/esm/coordinates.d.ts +66 -0
  89. package/esm/coordinates.js +58 -0
  90. package/esm/coordinates.js.map +1 -0
  91. package/esm/dbIds.d.ts +16 -0
  92. package/esm/dbIds.js +35 -0
  93. package/esm/dbIds.js.map +1 -0
  94. package/esm/extractPerResidueConfidence.d.ts +49 -0
  95. package/esm/extractPerResidueConfidence.js +38 -0
  96. package/esm/extractPerResidueConfidence.js.map +1 -0
  97. package/esm/extractStructureSequences.d.ts +142 -0
  98. package/esm/extractStructureSequences.js +205 -0
  99. package/esm/extractStructureSequences.js.map +1 -0
  100. package/esm/fetchUtils.d.ts +34 -0
  101. package/esm/fetchUtils.js +82 -0
  102. package/esm/fetchUtils.js.map +1 -0
  103. package/esm/index.d.ts +23 -0
  104. package/esm/index.js +30 -0
  105. package/esm/index.js.map +1 -0
  106. package/esm/invertMap.d.ts +2 -0
  107. package/esm/invertMap.js +5 -0
  108. package/esm/invertMap.js.map +1 -0
  109. package/esm/isoformRanking.d.ts +47 -0
  110. package/esm/isoformRanking.js +86 -0
  111. package/esm/isoformRanking.js.map +1 -0
  112. package/esm/mappings.d.ts +58 -0
  113. package/esm/mappings.js +179 -0
  114. package/esm/mappings.js.map +1 -0
  115. package/esm/pairwiseAlignment.d.ts +35 -0
  116. package/esm/pairwiseAlignment.js +328 -0
  117. package/esm/pairwiseAlignment.js.map +1 -0
  118. package/esm/pdbUniProtMapping.d.ts +75 -0
  119. package/esm/pdbUniProtMapping.js +231 -0
  120. package/esm/pdbUniProtMapping.js.map +1 -0
  121. package/esm/pdbeBestStructures.d.ts +23 -0
  122. package/esm/pdbeBestStructures.js +73 -0
  123. package/esm/pdbeBestStructures.js.map +1 -0
  124. package/esm/pdbeEntryMolecules.d.ts +7 -0
  125. package/esm/pdbeEntryMolecules.js +35 -0
  126. package/esm/pdbeEntryMolecules.js.map +1 -0
  127. package/esm/siftsNumbering.d.ts +23 -0
  128. package/esm/siftsNumbering.js +40 -0
  129. package/esm/siftsNumbering.js.map +1 -0
  130. package/esm/stripStopCodon.d.ts +15 -0
  131. package/esm/stripStopCodon.js +20 -0
  132. package/esm/stripStopCodon.js.map +1 -0
  133. package/esm/structureFormat.d.ts +40 -0
  134. package/esm/structureFormat.js +48 -0
  135. package/esm/structureFormat.js.map +1 -0
  136. package/esm/structureSources.d.ts +28 -0
  137. package/esm/structureSources.js +71 -0
  138. package/esm/structureSources.js.map +1 -0
  139. package/esm/structureUrls.d.ts +54 -0
  140. package/esm/structureUrls.js +134 -0
  141. package/esm/structureUrls.js.map +1 -0
  142. package/esm/types.d.ts +14 -0
  143. package/esm/types.js +23 -0
  144. package/esm/types.js.map +1 -0
  145. package/esm/uniProtSearch.d.ts +32 -0
  146. package/esm/uniProtSearch.js +110 -0
  147. package/esm/uniProtSearch.js.map +1 -0
  148. package/package.json +63 -0
  149. package/src/alignmentQuality.ts +132 -0
  150. package/src/alphaFoldModels.ts +91 -0
  151. package/src/caCoordsToPdb.ts +111 -0
  152. package/src/chooseMappedEntity.ts +198 -0
  153. package/src/coordinates.ts +119 -0
  154. package/src/dbIds.ts +46 -0
  155. package/src/extractPerResidueConfidence.ts +88 -0
  156. package/src/extractStructureSequences.ts +342 -0
  157. package/src/fetchUtils.ts +110 -0
  158. package/src/index.ts +34 -0
  159. package/src/invertMap.ts +4 -0
  160. package/src/isoformRanking.ts +139 -0
  161. package/src/mappings.ts +230 -0
  162. package/src/pairwiseAlignment.ts +423 -0
  163. package/src/pdbUniProtMapping.ts +320 -0
  164. package/src/pdbeBestStructures.ts +94 -0
  165. package/src/pdbeEntryMolecules.ts +37 -0
  166. package/src/siftsNumbering.ts +61 -0
  167. package/src/stripStopCodon.ts +20 -0
  168. package/src/structureFormat.ts +70 -0
  169. package/src/structureSources.ts +101 -0
  170. package/src/structureUrls.ts +171 -0
  171. package/src/types.ts +30 -0
  172. package/src/uniProtSearch.ts +212 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Colin Diesh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # p2s_mapper
2
+
3
+ Map a transcript's translation onto a protein structure: the pairwise alignment
4
+ between them, the coordinate maps that alignment implies, which chain of the
5
+ structure is the gene's product, SIFTS residue numbering, and the AlphaFold,
6
+ PDBe and UniProt lookups that find a structure to map in the first place.
7
+
8
+ Extracted from
9
+ [jbrowse-plugin-protein3d](https://github.com/GMOD/jbrowse-plugin-protein3d),
10
+ which is where the measurements behind the scoring rules were made. The package
11
+ has no React, no JBrowse and no Mol\* dependency: a loaded Mol\* model reaches
12
+ it through narrow structural interfaces, and `g2p_mapper` is the only runtime
13
+ dependency.
14
+
15
+ ## Install
16
+
17
+ ```
18
+ npm install p2s_mapper
19
+ ```
20
+
21
+ ## Coordinate conventions
22
+
23
+ One residue has up to four numbers, and mixing them is the off-by-one class of
24
+ bug this package exists to prevent. **Every stored or computed coordinate is a
25
+ 0-based position**: an index into the ungapped structure sequence or the
26
+ ungapped transcript sequence, which is the one numbering that is dense and
27
+ zero-based whatever the file. `coordinates.ts` brands the three internal spaces
28
+ — structure position, transcript position, alignment column — so the compiler
29
+ rejects mixing them. Two conversions leave that space, and each has one door.
30
+ **Mol\* is addressed by `label_seq_id`**, which is `position + 1` only for a
31
+ file carrying `entity_poly_seq` or SEQRES; a SEQRES-less PDB numbers its
32
+ observed residues by author numbering and leaves holes at unobserved loops, so
33
+ `Entity.seqIds` carries the real ids and every crossing goes through
34
+ `toLabelSeqIds`, `rangeToLabelSeqIds` or `makeLabelSeqIdIndex`. **The user is
35
+ shown `auth_seq_id`**, the depositors' numbering, which is what papers and
36
+ UniProt cite (1TUP position 154 reads 248) and what Mol\*'s own hover shows;
37
+ `Entity.authSeqIds` carries it and it is display-only. UniProt positions are
38
+ 1-based, and `pdbUniProtMapping` converts them with SIFTS rather than assuming
39
+ any offset.
40
+
41
+ ## Exports
42
+
43
+ ### Alignment
44
+
45
+ `runLocalAlignment`, `scoredAlignment`, `needlemanWunsch`, `smithWaterman`,
46
+ `selfScore`, `alignmentTooLarge`, `MAX_ALIGNMENT_CELLS` — Smith-Waterman and
47
+ Needleman-Wunsch over BLOSUM62, with affine gaps and a packed traceback.
48
+
49
+ `PairwiseAlignment`, `transcriptAlignedSeq`, `structureAlignedSeq`,
50
+ `alignmentLength`, `pairwiseAlignmentProblem`,
51
+ `pairwiseAlignmentSequenceProblem`, `mappedStructureIdentity`,
52
+ `unmapStructurePositions` — the two-row alignment, whose **row order is a
53
+ contract**: row 0 is the transcript, row 1 is the structure. Read it through the
54
+ accessors, not by index.
55
+
56
+ `alignmentQuality`, `isLowSimilarity`, `describeAlignmentQuality`,
57
+ `describeTranscriptCoverage`, `describeCoveredRange` — identity over the shorter
58
+ sequence is what separates a real match from a chance local alignment; local
59
+ identity alone does not.
60
+
61
+ `AlignmentAlgorithm`, `coerceAlignmentAlgorithm`, `ALIGNMENT_ALGORITHM_LABELS`.
62
+
63
+ ### Coordinates
64
+
65
+ `makeCoordinateMapper`, `CoordinateMapper`, `transcriptRangeToStructureRange`,
66
+ `structurePos`, `transcriptPos`, `alignmentCol` — every conversion built once
67
+ from an alignment, branded by space.
68
+
69
+ `structureSeqVsTranscriptSeqMap`, `structurePositionToAlignmentMap`,
70
+ `transcriptPositionToAlignmentMap`, `invertMap`, `codonGenomeSpan`,
71
+ `stripStopCodon`, `stripAllStopCodons`.
72
+
73
+ ### Chain choice
74
+
75
+ `chooseMappedEntity`, `alignTranscriptToEntity`, `explainedFraction`,
76
+ `interactionMatchesMappedEntity` — which polymer entity is the transcript's
77
+ product, ranked by identical residues over the **shorter** of the two sequences.
78
+ A raw match count picks the large partner of every bound peptide, and the
79
+ alignment score does not separate them either.
80
+
81
+ `extractEntities`, `extractStructureSequences`, `entityLabel`, `residueNumber`,
82
+ `residueRangeToPositions`, `fillAuthSeqIds`, `oneLetterSequence`,
83
+ `toLabelSeqIds`, `rangeToLabelSeqIds`, `makeLabelSeqIdIndex` — read a loaded
84
+ Mol\* model through a structural interface. Read `sequence.code`, never
85
+ `sequence.label`: `label` spells MSE, TPO and ACE by component id, so the string
86
+ outgrows `seqId` and every later position addresses the wrong residue.
87
+
88
+ `extractPerResidueConfidence`, `looksLikePlddt` — AlphaFold's pLDDT out of the
89
+ B-factor column, keyed by `label_seq_id` because the hierarchy holds only
90
+ observed residues.
91
+
92
+ `classifyIsoforms`, `selectBestTranscript`, `pickStructureSequence` — rank plain
93
+ `{ id, seq }` isoform records against a chain.
94
+
95
+ ### SIFTS
96
+
97
+ `fetchUniProtStructureMappings`, `parseUniProtStructureMappings`,
98
+ `pdbeSiftsUrl`, `chooseUniProtMappingForEntity`, `makeUniProtPositionMap`,
99
+ `identityUniProtPositionMap`, `fusionPartnerPositions` — the authoritative
100
+ UniProt ↔ structure alignment. `fusionPartnerPositions` unmaps the residues
101
+ SIFTS gives another protein on a fusion construct, which local alignment
102
+ otherwise bridges (2RH1 scatters 33 receptor residues onto T4 lysozyme).
103
+
104
+ `toAuthorRange`, `segmentsForAccession` — the author-numbered range a UniProt
105
+ range is cited by. Haemoglobin numbers from the mature protein, so author
106
+ residue 1 is UniProt residue 2.
107
+
108
+ ### Structure sources
109
+
110
+ `fetchAlphaFoldModels`, `parseAlphaFoldModels`, `pickAlphaFoldModel` — asked
111
+ rather than derived: a protein past AlphaFold's length cap has no F1 model at
112
+ all, and the model version moves.
113
+
114
+ `fetchExperimentalStructures`, `parseExperimentalStructures` — 3D-Beacons'
115
+ experimental entries, PDBe only, because SASBDB's scattering fits file under the
116
+ same category with near-full coverage and no residue mapping.
117
+
118
+ `pdbeBestStructuresUrl`, `parseBestStructures`, `isPdbId`,
119
+ `pdbeEntryMoleculesUrl`, `parseEntryMolecules`, `searchUniProtEntries`,
120
+ `buildGeneNameQuery`, `isRecognizedDatabaseId`, `buildUniProtXrefQuery`,
121
+ `getDbIdLabel`, `stripTrailingVersion`.
122
+
123
+ Every request takes its `fetch` from the call —
124
+ `{ fetch?: typeof fetch, signal? }`, defaulting to `globalThis.fetch` — so a
125
+ host can hand in an instrumented one and a test can hand in a double.
126
+
127
+ ### Urls and formats
128
+
129
+ `getPdbStructureUrl`, `getAlphaFoldStructureUrl`, `resolveStructureUrl`,
130
+ `getPdbIdFromUrl`, `getUniprotIdFromAlphaFoldTarget`,
131
+ `getStructureUrlFromTarget`, `getConfidenceUrlFromTarget`,
132
+ `structureDisplayLabel`, `uniprotGffUrl`, `uniprotFastaUrl`, `uniprotEntryUrl`,
133
+ `rcsbEntryUrl`.
134
+
135
+ `structureFormatFromContent`, `structureFormatFromName`,
136
+ `structureFileExtension`, `isBinaryStructureUrl` — which parser a structure
137
+ needs, sniffed from **content** rather than a filename. Getting it wrong fails
138
+ asymmetrically: PDB read as mmCIF throws, while mmCIF read as PDB succeeds and
139
+ yields a model with thousands of misread atoms and zero polymer entities.
140
+
141
+ `caCoordsToPdb`, `hasValidCaCoords` — a PDB file from Foldseek Cα coordinates.
142
+
143
+ `rawfetch`, `myfetch`, `jsonfetch`, `httpError`, `networkError`, `abortError`,
144
+ `timeout`, `FetchOptions`.
145
+
146
+ ## License
147
+
148
+ MIT
@@ -0,0 +1,58 @@
1
+ import type { PairwiseAlignment } from './mappings.ts';
2
+ export interface AlignmentQuality {
3
+ /** columns carrying a residue on both rows, i.e. mapped positions */
4
+ aligned: number;
5
+ identical: number;
6
+ transcriptLength: number;
7
+ structureLength: number;
8
+ /** identical over aligned; 0 when nothing aligned */
9
+ identity: number;
10
+ /** identical over the shorter of the two sequences: the statistic that
11
+ * separates a real match from a chance local alignment, see below */
12
+ identityOverShorter: number;
13
+ /** aligned over the structure's length */
14
+ structureCoverage: number;
15
+ /** first and last transcript residue the alignment covers, 1-based
16
+ * inclusive; 0 when nothing aligned. A structure is usually a fragment, and
17
+ * which part of the protein it holds is the reader's first question. */
18
+ transcriptStart: number;
19
+ transcriptEnd: number;
20
+ }
21
+ /**
22
+ * Below either floor the mapped positions are as likely to be chance as
23
+ * homology. Local alignment picks the best-scoring stretch of two sequences,
24
+ * so identity over the aligned columns is inflated whatever the inputs:
25
+ * measured 2026-09-11 on random sequences, Smith-Waterman with BLOSUM62 gave
26
+ * 37% identity over 27 columns at 300 × 300 and 30% over 100 columns at
27
+ * 500 × 150, two thirds of the shorter chain, and p53 against a ribosomal
28
+ * protein (7K00 RPS11) gives 31 identities at a third identity. Neither local
29
+ * identity nor coverage alone separates those from a real match. Identical
30
+ * residues over the shorter sequence does: 0.03, 0.20 and 0.24 for the chance
31
+ * cases against 0.9 or better for any structure of the transcript's protein
32
+ * and about 0.6 for an ortholog.
33
+ *
34
+ * A short alignment has to be nearly perfect instead: a 10-residue decoy with
35
+ * 3 identities sits at the ratio floor, while a real bound peptide (1YCR's 15
36
+ * p53 residues, 4ZZJ's 7) is identical or one off. So under
37
+ * SHORT_ALIGNMENT_RESIDUES identical residues the ratio floor rises to
38
+ * SHORT_ALIGNMENT_IDENTITY, and under MIN_IDENTICAL_RESIDUES nothing passes,
39
+ * since a tetrapeptide is never the gene's product.
40
+ */
41
+ export declare const LOW_IDENTITY_OVER_SHORTER = 0.3;
42
+ export declare const SHORT_ALIGNMENT_RESIDUES = 20;
43
+ export declare const SHORT_ALIGNMENT_IDENTITY = 0.8;
44
+ export declare const MIN_IDENTICAL_RESIDUES = 5;
45
+ export declare function alignmentQuality(pa: PairwiseAlignment): AlignmentQuality;
46
+ export declare function isLowSimilarity(q: AlignmentQuality): boolean;
47
+ /** One line for the alignment header: "87% identity over 219 of 393
48
+ * structure residues". */
49
+ export declare function describeAlignmentQuality(q: AlignmentQuality): string;
50
+ /**
51
+ * The same counted from the transcript's side, for the always-visible header:
52
+ * how much of the gene's protein this structure speaks for is what decides
53
+ * whether a codon has a residue at all.
54
+ */
55
+ export declare function describeTranscriptCoverage(q: AlignmentQuality): string;
56
+ /** Which residues of the transcript a fragment holds, or undefined when it
57
+ * covers the whole translation. */
58
+ export declare function describeCoveredRange(q: AlignmentQuality): string | undefined;
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MIN_IDENTICAL_RESIDUES = exports.SHORT_ALIGNMENT_IDENTITY = exports.SHORT_ALIGNMENT_RESIDUES = exports.LOW_IDENTITY_OVER_SHORTER = void 0;
4
+ exports.alignmentQuality = alignmentQuality;
5
+ exports.isLowSimilarity = isLowSimilarity;
6
+ exports.describeAlignmentQuality = describeAlignmentQuality;
7
+ exports.describeTranscriptCoverage = describeTranscriptCoverage;
8
+ exports.describeCoveredRange = describeCoveredRange;
9
+ const mappings_ts_1 = require("./mappings.js");
10
+ /**
11
+ * Below either floor the mapped positions are as likely to be chance as
12
+ * homology. Local alignment picks the best-scoring stretch of two sequences,
13
+ * so identity over the aligned columns is inflated whatever the inputs:
14
+ * measured 2026-09-11 on random sequences, Smith-Waterman with BLOSUM62 gave
15
+ * 37% identity over 27 columns at 300 × 300 and 30% over 100 columns at
16
+ * 500 × 150, two thirds of the shorter chain, and p53 against a ribosomal
17
+ * protein (7K00 RPS11) gives 31 identities at a third identity. Neither local
18
+ * identity nor coverage alone separates those from a real match. Identical
19
+ * residues over the shorter sequence does: 0.03, 0.20 and 0.24 for the chance
20
+ * cases against 0.9 or better for any structure of the transcript's protein
21
+ * and about 0.6 for an ortholog.
22
+ *
23
+ * A short alignment has to be nearly perfect instead: a 10-residue decoy with
24
+ * 3 identities sits at the ratio floor, while a real bound peptide (1YCR's 15
25
+ * p53 residues, 4ZZJ's 7) is identical or one off. So under
26
+ * SHORT_ALIGNMENT_RESIDUES identical residues the ratio floor rises to
27
+ * SHORT_ALIGNMENT_IDENTITY, and under MIN_IDENTICAL_RESIDUES nothing passes,
28
+ * since a tetrapeptide is never the gene's product.
29
+ */
30
+ exports.LOW_IDENTITY_OVER_SHORTER = 0.3;
31
+ exports.SHORT_ALIGNMENT_RESIDUES = 20;
32
+ exports.SHORT_ALIGNMENT_IDENTITY = 0.8;
33
+ exports.MIN_IDENTICAL_RESIDUES = 5;
34
+ function alignmentQuality(pa) {
35
+ const t = (0, mappings_ts_1.transcriptAlignedSeq)(pa);
36
+ const s = (0, mappings_ts_1.structureAlignedSeq)(pa);
37
+ let aligned = 0;
38
+ let identical = 0;
39
+ let transcriptLength = 0;
40
+ let structureLength = 0;
41
+ let transcriptStart = 0;
42
+ let transcriptEnd = 0;
43
+ for (let i = 0; i < (0, mappings_ts_1.alignmentLength)(pa); i++) {
44
+ const a = t[i];
45
+ const b = s[i];
46
+ const ta = a !== '-';
47
+ const sb = b !== '-';
48
+ if (ta) {
49
+ transcriptLength++;
50
+ }
51
+ if (sb) {
52
+ structureLength++;
53
+ }
54
+ if (ta && sb) {
55
+ aligned++;
56
+ transcriptStart = transcriptStart || transcriptLength;
57
+ transcriptEnd = transcriptLength;
58
+ if (a.toUpperCase() === b.toUpperCase()) {
59
+ identical++;
60
+ }
61
+ }
62
+ }
63
+ const shorter = Math.min(transcriptLength, structureLength);
64
+ return {
65
+ aligned,
66
+ identical,
67
+ transcriptLength,
68
+ structureLength,
69
+ identity: aligned ? identical / aligned : 0,
70
+ identityOverShorter: shorter ? identical / shorter : 0,
71
+ structureCoverage: structureLength ? aligned / structureLength : 0,
72
+ transcriptStart,
73
+ transcriptEnd,
74
+ };
75
+ }
76
+ function isLowSimilarity(q) {
77
+ const floor = q.identical < exports.SHORT_ALIGNMENT_RESIDUES
78
+ ? exports.SHORT_ALIGNMENT_IDENTITY
79
+ : exports.LOW_IDENTITY_OVER_SHORTER;
80
+ return q.identical < exports.MIN_IDENTICAL_RESIDUES || q.identityOverShorter < floor;
81
+ }
82
+ /** One line for the alignment header: "87% identity over 219 of 393
83
+ * structure residues". */
84
+ function describeAlignmentQuality(q) {
85
+ if (q.aligned === 0) {
86
+ return 'no residues aligned';
87
+ }
88
+ return `${Math.round(q.identity * 100)}% identity over ${q.aligned} of ${q.structureLength} structure residues`;
89
+ }
90
+ /**
91
+ * The same counted from the transcript's side, for the always-visible header:
92
+ * how much of the gene's protein this structure speaks for is what decides
93
+ * whether a codon has a residue at all.
94
+ */
95
+ function describeTranscriptCoverage(q) {
96
+ if (q.aligned === 0) {
97
+ return 'no residues aligned';
98
+ }
99
+ return `${Math.round(q.identity * 100)}% identity, ${q.aligned} of ${q.transcriptLength} transcript residues covered`;
100
+ }
101
+ /** Which residues of the transcript a fragment holds, or undefined when it
102
+ * covers the whole translation. */
103
+ function describeCoveredRange(q) {
104
+ return q.aligned === 0 || q.aligned === q.transcriptLength
105
+ ? undefined
106
+ : `covers transcript residues ${q.transcriptStart}–${q.transcriptEnd}`;
107
+ }
108
+ //# sourceMappingURL=alignmentQuality.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alignmentQuality.js","sourceRoot":"","sources":["../src/alignmentQuality.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { FetchOptions } from './fetchUtils.ts';
2
+ export interface AlphaFoldModel {
3
+ /** UniProt accession, with an isoform suffix on an isoform model */
4
+ accession: string;
5
+ url: string;
6
+ confidenceUrl?: string;
7
+ sequence: string;
8
+ }
9
+ export declare function parseAlphaFoldModels(json: unknown): AlphaFoldModel[];
10
+ /**
11
+ * Every model AlphaFold DB has for an accession: the canonical one and any
12
+ * isoform models, each with its file url and sequence. Asked rather than
13
+ * derived from the accession, because a derived `AF-<acc>-F1-model_v6` does
14
+ * not exist past the length cap (dystrophin has fourteen isoform models and no
15
+ * F1) and moves with every model version. An accession AlphaFold has never
16
+ * folded answers 400 or 404, which is no models rather than an error.
17
+ */
18
+ export declare function fetchAlphaFoldModels(uniprotId: string, opts?: FetchOptions): Promise<AlphaFoldModel[]>;
19
+ /**
20
+ * The model to open for a gene's transcripts: one folded from exactly a
21
+ * transcript's translation, canonical first, so the view maps it as an
22
+ * identity; else the canonical model; else the longest isoform model.
23
+ *
24
+ * Takes any record of sequences, since the structure loader knows one
25
+ * translation rather than a gene's isoforms.
26
+ */
27
+ export declare function pickAlphaFoldModel(models: AlphaFoldModel[], isoformSequences: Record<string, {
28
+ seq: string;
29
+ }> | undefined): AlphaFoldModel | undefined;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseAlphaFoldModels = parseAlphaFoldModels;
4
+ exports.fetchAlphaFoldModels = fetchAlphaFoldModels;
5
+ exports.pickAlphaFoldModel = pickAlphaFoldModel;
6
+ const fetchUtils_ts_1 = require("./fetchUtils.js");
7
+ const stripStopCodon_ts_1 = require("./stripStopCodon.js");
8
+ function isPredictionEntry(value) {
9
+ return typeof value === 'object' && value !== null;
10
+ }
11
+ function parseAlphaFoldModels(json) {
12
+ return (Array.isArray(json) ? json : []).flatMap(e => isPredictionEntry(e) &&
13
+ typeof e.uniprotAccession === 'string' &&
14
+ typeof e.cifUrl === 'string' &&
15
+ typeof e.sequence === 'string'
16
+ ? [
17
+ {
18
+ accession: e.uniprotAccession,
19
+ url: e.cifUrl,
20
+ confidenceUrl: typeof e.plddtDocUrl === 'string' ? e.plddtDocUrl : undefined,
21
+ sequence: e.sequence,
22
+ },
23
+ ]
24
+ : []);
25
+ }
26
+ /**
27
+ * Every model AlphaFold DB has for an accession: the canonical one and any
28
+ * isoform models, each with its file url and sequence. Asked rather than
29
+ * derived from the accession, because a derived `AF-<acc>-F1-model_v6` does
30
+ * not exist past the length cap (dystrophin has fourteen isoform models and no
31
+ * F1) and moves with every model version. An accession AlphaFold has never
32
+ * folded answers 400 or 404, which is no models rather than an error.
33
+ */
34
+ async function fetchAlphaFoldModels(uniprotId, opts) {
35
+ const url = `https://alphafold.ebi.ac.uk/api/prediction/${encodeURIComponent(uniprotId)}`;
36
+ const res = await (0, fetchUtils_ts_1.rawfetch)(url, opts);
37
+ if (res.status === 400 || res.status === 404) {
38
+ return [];
39
+ }
40
+ if (!res.ok) {
41
+ throw await (0, fetchUtils_ts_1.httpError)(res, url);
42
+ }
43
+ return parseAlphaFoldModels(await res.json());
44
+ }
45
+ /**
46
+ * The model to open for a gene's transcripts: one folded from exactly a
47
+ * transcript's translation, canonical first, so the view maps it as an
48
+ * identity; else the canonical model; else the longest isoform model.
49
+ *
50
+ * Takes any record of sequences, since the structure loader knows one
51
+ * translation rather than a gene's isoforms.
52
+ */
53
+ function pickAlphaFoldModel(models, isoformSequences) {
54
+ const translations = new Set(Object.values(isoformSequences ?? {}).map(v => (0, stripStopCodon_ts_1.stripStopCodon)(v.seq)));
55
+ const canonicalFirst = [...models].sort((a, b) => Number(a.accession.includes('-')) - Number(b.accession.includes('-')));
56
+ return (canonicalFirst.find(m => translations.has(m.sequence)) ??
57
+ canonicalFirst.find(m => !m.accession.includes('-')) ??
58
+ [...models].sort((a, b) => b.sequence.length - a.sequence.length)[0]);
59
+ }
60
+ //# sourceMappingURL=alphaFoldModels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alphaFoldModels.js","sourceRoot":"","sources":["../src/alphaFoldModels.ts"],"names":[],"mappings":";;AAwBA,oDAiBC;AAUD,oDAaC;AAUD,gDAgBC;AA1FD,mDAAqD;AAGrD,2DAAoD;AAiBpD,SAAS,iBAAiB,CAAC,KAAc;IACvC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAA;AACpD,CAAC;AAED,SAAgB,oBAAoB,CAAC,IAAa;IAChD,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CACnD,iBAAiB,CAAC,CAAC,CAAC;QACpB,OAAO,CAAC,CAAC,gBAAgB,KAAK,QAAQ;QACtC,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ;QAC5B,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ;QAC5B,CAAC,CAAC;YACE;gBACE,SAAS,EAAE,CAAC,CAAC,gBAAgB;gBAC7B,GAAG,EAAE,CAAC,CAAC,MAAM;gBACb,aAAa,EACX,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;gBAC/D,QAAQ,EAAE,CAAC,CAAC,QAAQ;aACrB;SACF;QACH,CAAC,CAAC,EAAE,CACP,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,oBAAoB,CACxC,SAAiB,EACjB,IAAmB;IAEnB,MAAM,GAAG,GAAG,8CAA8C,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAA;IACzF,MAAM,GAAG,GAAG,MAAM,IAAA,wBAAQ,EAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IACrC,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC7C,OAAO,EAAE,CAAA;IACX,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,MAAM,IAAA,yBAAS,EAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACjC,CAAC;IACD,OAAO,oBAAoB,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;AAC/C,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,kBAAkB,CAChC,MAAwB,EACxB,gBAA6D;IAE7D,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,MAAM,CAAC,MAAM,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAA,kCAAc,EAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CACtE,CAAA;IACD,MAAM,cAAc,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CACrC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CACxE,CAAA;IACD,OAAO,CACL,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACtD,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACpD,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CACrE,CAAA;AACH,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Convert Foldseek tCa coordinates and tSeq to PDB format
3
+ * tCa is a comma-separated string of x,y,z triplets
4
+ * tSeq is the amino acid sequence
5
+ */
6
+ export declare function caCoordsToPdb(tCa: string, tSeq: string, chainId?: string, title?: string): string;
7
+ /**
8
+ * Whether a Foldseek hit carries tCa data that can be converted to PDB. A type
9
+ * predicate rather than a boolean so a guarded caller can pass `hit.tCa`/
10
+ * `hit.tSeq` straight to caCoordsToPdb without re-asserting they're defined.
11
+ */
12
+ export declare function hasValidCaCoords<T extends {
13
+ tCa?: string;
14
+ tSeq?: string;
15
+ }>(hit: T): hit is T & {
16
+ tCa: string;
17
+ tSeq: string;
18
+ };
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.caCoordsToPdb = caCoordsToPdb;
4
+ exports.hasValidCaCoords = hasValidCaCoords;
5
+ // 3-letter amino acid codes
6
+ const AA_3LETTER = {
7
+ A: 'ALA',
8
+ C: 'CYS',
9
+ D: 'ASP',
10
+ E: 'GLU',
11
+ F: 'PHE',
12
+ G: 'GLY',
13
+ H: 'HIS',
14
+ I: 'ILE',
15
+ K: 'LYS',
16
+ L: 'LEU',
17
+ M: 'MET',
18
+ N: 'ASN',
19
+ P: 'PRO',
20
+ Q: 'GLN',
21
+ R: 'ARG',
22
+ S: 'SER',
23
+ T: 'THR',
24
+ V: 'VAL',
25
+ W: 'TRP',
26
+ Y: 'TYR',
27
+ X: 'UNK', // Unknown
28
+ };
29
+ function padLeft(str, len) {
30
+ return str.padStart(len, ' ');
31
+ }
32
+ function padRight(str, len) {
33
+ return str.padEnd(len, ' ');
34
+ }
35
+ function formatCoord(val) {
36
+ return val.toFixed(3).padStart(8, ' ');
37
+ }
38
+ /**
39
+ * Convert Foldseek tCa coordinates and tSeq to PDB format
40
+ * tCa is a comma-separated string of x,y,z triplets
41
+ * tSeq is the amino acid sequence
42
+ */
43
+ function caCoordsToPdb(tCa, tSeq, chainId = 'A', title) {
44
+ const coords = tCa.split(',').map(Number);
45
+ const lines = [];
46
+ // Add header
47
+ if (title) {
48
+ lines.push(`TITLE ${title}`);
49
+ }
50
+ lines.push('REMARK 1 Generated from Foldseek Cα coordinates');
51
+ let atomNum = 1;
52
+ let resNum = 1;
53
+ for (let i = 0; i < coords.length - 2; i += 3) {
54
+ const x = coords[i];
55
+ const y = coords[i + 1];
56
+ const z = coords[i + 2];
57
+ const aa = tSeq[resNum - 1] ?? 'X';
58
+ const resName = AA_3LETTER[aa] ?? 'UNK';
59
+ if (x === undefined || y === undefined || z === undefined) {
60
+ break;
61
+ }
62
+ // PDB ATOM record format
63
+ // Columns: 1-6 "ATOM ", 7-11 serial, 13-16 name, 17 altLoc, 18-20 resName,
64
+ // 22 chainID, 23-26 resSeq, 27 iCode, 31-38 x, 39-46 y, 47-54 z,
65
+ // 55-60 occupancy, 61-66 tempFactor, 77-78 element
66
+ const line = 'ATOM ' +
67
+ padLeft(String(atomNum), 5) +
68
+ ' CA ' +
69
+ padRight(resName, 3) +
70
+ ' ' +
71
+ chainId +
72
+ padLeft(String(resNum), 4) +
73
+ ' ' +
74
+ formatCoord(x) +
75
+ formatCoord(y) +
76
+ formatCoord(z) +
77
+ ' 1.00 0.00 C';
78
+ lines.push(line);
79
+ atomNum++;
80
+ resNum++;
81
+ }
82
+ lines.push('END');
83
+ return lines.join('\n');
84
+ }
85
+ /**
86
+ * Whether a Foldseek hit carries tCa data that can be converted to PDB. A type
87
+ * predicate rather than a boolean so a guarded caller can pass `hit.tCa`/
88
+ * `hit.tSeq` straight to caCoordsToPdb without re-asserting they're defined.
89
+ */
90
+ function hasValidCaCoords(hit) {
91
+ const { tCa, tSeq } = hit;
92
+ // Need at least 3 coordinates (x,y,z for one residue) and a sequence
93
+ return !!tCa && !!tSeq && tCa.split(',').length >= 3;
94
+ }
95
+ //# sourceMappingURL=caCoordsToPdb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caCoordsToPdb.js","sourceRoot":"","sources":["../src/caCoordsToPdb.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import type { PairwiseAlignment } from './mappings.ts';
2
+ import type { AlignmentAlgorithm } from './types.ts';
3
+ /**
4
+ * Whether a structure interaction (hover/click) should drive genome navigation.
5
+ * Only the transcript's mapped entity may — a hover on any other chain carries
6
+ * that chain's own label_seq_id and would mis-map through the wrong alignment.
7
+ * A structure with no resolved mapping (`mappedEntityId` undefined, e.g. a
8
+ * standalone viewer with no transcript) stays fully interactive.
9
+ */
10
+ export declare function interactionMatchesMappedEntity(entityId: string, mappedEntityId: string | undefined): boolean;
11
+ export interface EntitySelection extends ScoredAlignment {
12
+ /** index into the candidate array of the entity that is the transcript's */
13
+ index: number;
14
+ }
15
+ export interface ScoredAlignment {
16
+ /** alignment of the transcript against the entity (stop codons stripped on
17
+ * both sides, matching the rest of the mapping pipeline) */
18
+ alignment: PairwiseAlignment;
19
+ /** identical aligned residues */
20
+ matches: number;
21
+ /** identical residues over the shorter sequence, the score that picks the
22
+ * entity; see `explainedFraction` */
23
+ explained: number;
24
+ /** the alignment's BLOSUM62 score, gap penalties included */
25
+ score: number;
26
+ }
27
+ /** A candidate entity: its sequence, and whether it is DNA/RNA, which is never
28
+ * the transcript's product however its letters happen to align. */
29
+ export interface EntityCandidate {
30
+ seq: string;
31
+ nucleicAcid?: boolean;
32
+ }
33
+ export declare function explainedFraction(matches: number, transcriptLength: number, entityLength: number): number;
34
+ /**
35
+ * Align one transcript to one entity, stop codons stripped on both sides. An
36
+ * exact match skips the DP entirely; an oversized pair returns undefined rather
37
+ * than locking up the tab. Used both to score every entity of a structure and
38
+ * to honour a user's explicit chain choice.
39
+ */
40
+ export declare function alignTranscriptToEntity(transcript: string, entitySeq: string, algorithm: AlignmentAlgorithm): ScoredAlignment | undefined;
41
+ /**
42
+ * Pick which polymer entity of a structure corresponds to the transcript.
43
+ *
44
+ * The plugin historically hardcoded entity `[0]`, which silently mis-maps every
45
+ * heteromeric / protein-DNA / processed-peptide structure where the protein of
46
+ * interest is some other chain. Selecting by alignment makes the structure self-
47
+ * describe which entity is the gene's protein: an exact sequence match wins
48
+ * outright, otherwise the entity with the highest identity over the shorter
49
+ * sequence (see `explainedFraction`). Nucleic-acid entities are never
50
+ * candidates.
51
+ *
52
+ * Returns `undefined` only when there is nothing to map (no transcript or no
53
+ * protein entities) — never a silent fallback to the wrong entity.
54
+ */
55
+ export declare function chooseMappedEntity(transcript: string, entities: readonly (string | EntityCandidate)[], algorithm: AlignmentAlgorithm): EntitySelection | undefined;