p2s_mapper 1.2.2 → 1.3.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.
- package/README.md +36 -129
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/sharedStretches.d.ts +17 -0
- package/dist/sharedStretches.js +99 -0
- package/dist/sharedStretches.js.map +1 -0
- package/dist/structureSources.js +14 -3
- package/dist/structureSources.js.map +1 -1
- package/esm/index.d.ts +1 -0
- package/esm/index.js +1 -0
- package/esm/index.js.map +1 -1
- package/esm/sharedStretches.d.ts +17 -0
- package/esm/sharedStretches.js +94 -0
- package/esm/sharedStretches.js.map +1 -0
- package/esm/structureSources.js +14 -3
- package/esm/structureSources.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/sharedStretches.ts +109 -0
- package/src/structureSources.ts +16 -5
package/README.md
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
# p2s_mapper
|
|
2
2
|
|
|
3
|
-
Map a transcript's translation onto a protein structure
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Map a transcript's translation onto a protein structure.
|
|
4
|
+
|
|
5
|
+
- **Align** the transcript to the structure's sequence (Smith-Waterman or
|
|
6
|
+
Needleman-Wunsch, BLOSUM62)
|
|
7
|
+
- **Convert coordinates** both ways between transcript, alignment, structure,
|
|
8
|
+
Mol\* and UniProt numbering
|
|
9
|
+
- **Choose the chain** that best matches the gene's protein
|
|
10
|
+
- **Use SIFTS** to place UniProt annotations and drop fusion-partner residues
|
|
11
|
+
- **Find structures** through AlphaFold, 3D-Beacons, PDBe and UniProt
|
|
12
|
+
|
|
13
|
+
## Works with g2p_mapper
|
|
14
|
+
|
|
15
|
+
p2s_mapper covers **protein ↔ structure**. The other half, **genome ↔ protein**,
|
|
16
|
+
is [g2p_mapper](https://github.com/GMOD/g2p_mapper): it turns a transcript's CDS
|
|
17
|
+
into per-codon genome positions, on either strand and across exon boundaries. It
|
|
18
|
+
is p2s_mapper's only runtime dependency, and chaining the two takes you from a
|
|
19
|
+
genome coordinate to a residue in 3D and back.
|
|
7
20
|
|
|
8
21
|
Extracted from
|
|
9
22
|
[jbrowse-plugin-protein3d](https://github.com/GMOD/jbrowse-plugin-protein3d),
|
|
10
23
|
where we made the measurements behind the scoring rules. The package has no
|
|
11
|
-
React,
|
|
12
|
-
|
|
13
|
-
dependency.
|
|
24
|
+
React, JBrowse or Mol\* dependency: a loaded Mol\* model reaches it through
|
|
25
|
+
narrow structural interfaces.
|
|
14
26
|
|
|
15
27
|
## Install
|
|
16
28
|
|
|
@@ -18,133 +30,28 @@ dependency.
|
|
|
18
30
|
npm install p2s_mapper
|
|
19
31
|
```
|
|
20
32
|
|
|
21
|
-
##
|
|
22
|
-
|
|
23
|
-
One residue has up to four numbers, and mixing them causes the off-by-one bugs
|
|
24
|
-
this package's types prevent. **Every stored or computed coordinate is a 0-based
|
|
25
|
-
position**: an index into the ungapped structure sequence or the ungapped
|
|
26
|
-
transcript sequence, the one numbering that is dense and zero-based whatever the
|
|
27
|
-
file. `coordinates.ts` brands the three internal spaces — structure position,
|
|
28
|
-
transcript position, alignment column — so the compiler rejects mixing them. Two
|
|
29
|
-
conversions leave that space, and each has exactly one correct route. **Mol\* is
|
|
30
|
-
addressed by `label_seq_id`**, which is `position + 1` only for a file carrying
|
|
31
|
-
`entity_poly_seq` or SEQRES; a SEQRES-less PDB numbers its observed residues by
|
|
32
|
-
author numbering and leaves holes at unobserved loops, so `Entity.seqIds`
|
|
33
|
-
carries the real ids and every crossing goes through `toLabelSeqIds`,
|
|
34
|
-
`rangeToLabelSeqIds` or `makeLabelSeqIdIndex`. **The user sees `auth_seq_id`**,
|
|
35
|
-
the depositors' numbering that papers and UniProt cite (1TUP position 154
|
|
36
|
-
reads 248) and that Mol\*'s own hover shows; `Entity.authSeqIds` carries it, and
|
|
37
|
-
it is display-only. UniProt positions are 1-based, and `pdbUniProtMapping`
|
|
38
|
-
converts them with SIFTS rather than assuming any offset.
|
|
39
|
-
|
|
40
|
-
## Exports
|
|
41
|
-
|
|
42
|
-
### Alignment
|
|
43
|
-
|
|
44
|
-
`runLocalAlignment`, `scoredAlignment`, `needlemanWunsch`, `smithWaterman`,
|
|
45
|
-
`selfScore`, `alignmentTooLarge`, `MAX_ALIGNMENT_CELLS` — Smith-Waterman and
|
|
46
|
-
Needleman-Wunsch over BLOSUM62, with affine gaps and a packed traceback.
|
|
47
|
-
|
|
48
|
-
`PairwiseAlignment`, `transcriptAlignedSeq`, `structureAlignedSeq`,
|
|
49
|
-
`alignmentLength`, `pairwiseAlignmentProblem`,
|
|
50
|
-
`pairwiseAlignmentSequenceProblem`, `mappedStructureIdentity`,
|
|
51
|
-
`unmapStructurePositions` — the two-row alignment, whose **row order is a
|
|
52
|
-
contract**: row 0 is the transcript, row 1 is the structure. Read it through the
|
|
53
|
-
accessors, not by index.
|
|
54
|
-
|
|
55
|
-
`alignmentQuality`, `isLowSimilarity`, `describeAlignmentQuality`,
|
|
56
|
-
`describeTranscriptCoverage`, `describeCoveredRange` — identity over the shorter
|
|
57
|
-
sequence separates a real match from a chance local alignment; local identity
|
|
58
|
-
alone does not.
|
|
59
|
-
|
|
60
|
-
`AlignmentAlgorithm`, `coerceAlignmentAlgorithm`, `ALIGNMENT_ALGORITHM_LABELS`.
|
|
61
|
-
|
|
62
|
-
### Coordinates
|
|
63
|
-
|
|
64
|
-
`makeCoordinateMapper`, `CoordinateMapper`, `transcriptRangeToStructureRange`,
|
|
65
|
-
`structurePos`, `transcriptPos`, `alignmentCol` — every conversion built once
|
|
66
|
-
from an alignment, branded by space.
|
|
67
|
-
|
|
68
|
-
`structureSeqVsTranscriptSeqMap`, `structurePositionToAlignmentMap`,
|
|
69
|
-
`transcriptPositionToAlignmentMap`, `invertMap`, `codonGenomeSpan`,
|
|
70
|
-
`stripStopCodon`, `stripAllStopCodons`.
|
|
71
|
-
|
|
72
|
-
### Chain choice
|
|
73
|
-
|
|
74
|
-
`chooseMappedEntity`, `alignTranscriptToEntity`, `explainedFraction`,
|
|
75
|
-
`interactionMatchesMappedEntity` — which polymer entity is the transcript's
|
|
76
|
-
product, ranked by identical residues over the **shorter** of the two sequences.
|
|
77
|
-
A raw match count picks the large partner of every bound peptide, and the
|
|
78
|
-
alignment score does not separate them either.
|
|
79
|
-
|
|
80
|
-
`extractEntities`, `extractStructureSequences`, `entityLabel`, `residueNumber`,
|
|
81
|
-
`residueRangeToPositions`, `fillAuthSeqIds`, `oneLetterSequence`,
|
|
82
|
-
`toLabelSeqIds`, `rangeToLabelSeqIds`, `makeLabelSeqIdIndex` — read a loaded
|
|
83
|
-
Mol\* model through a structural interface. Read `sequence.code`, never
|
|
84
|
-
`sequence.label`: `label` spells MSE, TPO and ACE by component id, so the string
|
|
85
|
-
outgrows `seqId` and every later position addresses the wrong residue.
|
|
86
|
-
|
|
87
|
-
`extractPerResidueConfidence`, `looksLikePlddt` — AlphaFold's pLDDT out of the
|
|
88
|
-
B-factor column, keyed by `label_seq_id` because the hierarchy holds only
|
|
89
|
-
observed residues.
|
|
90
|
-
|
|
91
|
-
`classifyIsoforms`, `selectBestTranscript`, `pickStructureSequence` — rank plain
|
|
92
|
-
`{ id, seq }` isoform records against a chain.
|
|
93
|
-
|
|
94
|
-
### SIFTS
|
|
95
|
-
|
|
96
|
-
`fetchUniProtStructureMappings`, `parseUniProtStructureMappings`,
|
|
97
|
-
`pdbeSiftsUrl`, `chooseUniProtMappingForEntity`, `makeUniProtPositionMap`,
|
|
98
|
-
`identityUniProtPositionMap`, `fusionPartnerPositions` — the authoritative
|
|
99
|
-
UniProt ↔ structure alignment. `fusionPartnerPositions` unmaps the residues
|
|
100
|
-
SIFTS gives another protein on a fusion construct, which local alignment
|
|
101
|
-
otherwise bridges (2RH1 scatters 33 receptor residues onto T4 lysozyme).
|
|
102
|
-
|
|
103
|
-
`toAuthorRange`, `segmentsForAccession` — the author-numbered range a UniProt
|
|
104
|
-
range is cited by. Haemoglobin numbers from the mature protein, so author
|
|
105
|
-
residue 1 is UniProt residue 2.
|
|
106
|
-
|
|
107
|
-
### Structure sources
|
|
108
|
-
|
|
109
|
-
`fetchAlphaFoldModels`, `parseAlphaFoldModels`, `pickAlphaFoldModel` — asked
|
|
110
|
-
rather than derived: a protein past AlphaFold's length cap has no F1 model at
|
|
111
|
-
all, and the model version moves.
|
|
112
|
-
|
|
113
|
-
`fetchExperimentalStructures`, `parseExperimentalStructures` — 3D-Beacons'
|
|
114
|
-
experimental entries, PDBe only, because SASBDB's scattering fits file under the
|
|
115
|
-
same category with near-full coverage and no residue mapping.
|
|
116
|
-
|
|
117
|
-
`pdbeBestStructuresUrl`, `parseBestStructures`, `isPdbId`,
|
|
118
|
-
`pdbeEntryMoleculesUrl`, `parseEntryMolecules`, `searchUniProtEntries`,
|
|
119
|
-
`buildGeneNameQuery`, `isRecognizedDatabaseId`, `buildUniProtXrefQuery`,
|
|
120
|
-
`getDbIdLabel`, `stripTrailingVersion`.
|
|
121
|
-
|
|
122
|
-
Every request takes its `fetch` from the call —
|
|
123
|
-
`{ fetch?: typeof fetch, signal? }`, defaulting to `globalThis.fetch` — so a
|
|
124
|
-
host can hand in an instrumented one and a test can hand in a double.
|
|
33
|
+
## How it fits together
|
|
125
34
|
|
|
126
|
-
|
|
35
|
+

|
|
127
36
|
|
|
128
|
-
|
|
129
|
-
`getPdbIdFromUrl`, `getUniprotIdFromAlphaFoldTarget`,
|
|
130
|
-
`getStructureUrlFromTarget`, `getConfidenceUrlFromTarget`,
|
|
131
|
-
`structureDisplayLabel`, `uniprotGffUrl`, `uniprotFastaUrl`, `uniprotEntryUrl`,
|
|
132
|
-
`rcsbEntryUrl`.
|
|
37
|
+
## Docs
|
|
133
38
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
39
|
+
- [faq.md](docs/faq.md) — can I trust the mapping? What the package checks, and
|
|
40
|
+
what it leaves to you
|
|
41
|
+
- [pipeline.md](docs/pipeline.md) — the flow above, step by step, and why chain
|
|
42
|
+
choice divides by the shorter sequence
|
|
43
|
+
- [coordinates.md](docs/coordinates.md) — the four numberings of a residue and
|
|
44
|
+
the one route between each
|
|
45
|
+
- [api.md](docs/api.md) — every export, grouped by job
|
|
139
46
|
|
|
140
|
-
|
|
47
|
+
## Things to know first
|
|
141
48
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
A
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
49
|
+
- Every coordinate in the API is **0-based**, branded by space so the compiler
|
|
50
|
+
rejects mixing them. See [coordinates.md](docs/coordinates.md).
|
|
51
|
+
- A `PairwiseAlignment` has **row 0 = transcript, row 1 = structure**. Use the
|
|
52
|
+
accessors, not indexes.
|
|
53
|
+
- Network functions take `{ fetch?, signal? }` and default to
|
|
54
|
+
`globalThis.fetch`.
|
|
148
55
|
|
|
149
56
|
## License
|
|
150
57
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from './types.ts';
|
|
|
2
2
|
export * from './mappings.ts';
|
|
3
3
|
export * from './pairwiseAlignment.ts';
|
|
4
4
|
export * from './alignmentQuality.ts';
|
|
5
|
+
export * from './sharedStretches.ts';
|
|
5
6
|
export * from './coordinates.ts';
|
|
6
7
|
export * from './invertMap.ts';
|
|
7
8
|
export * from './stripStopCodon.ts';
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,7 @@ __exportStar(require("./types.js"), exports);
|
|
|
19
19
|
__exportStar(require("./mappings.js"), exports);
|
|
20
20
|
__exportStar(require("./pairwiseAlignment.js"), exports);
|
|
21
21
|
__exportStar(require("./alignmentQuality.js"), exports);
|
|
22
|
+
__exportStar(require("./sharedStretches.js"), exports);
|
|
22
23
|
// Coordinates
|
|
23
24
|
__exportStar(require("./coordinates.js"), exports);
|
|
24
25
|
__exportStar(require("./invertMap.js"), exports);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,YAAY;AACZ,6CAA0B;AAC1B,gDAA6B;AAC7B,yDAAsC;AACtC,wDAAqC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,YAAY;AACZ,6CAA0B;AAC1B,gDAA6B;AAC7B,yDAAsC;AACtC,wDAAqC;AACrC,uDAAoC;AAEpC,cAAc;AACd,mDAAgC;AAChC,iDAA8B;AAC9B,sDAAmC;AAEnC,eAAe;AACf,0DAAuC;AACvC,iEAA8C;AAC9C,mEAAgD;AAChD,sDAAmC;AAEnC,QAAQ;AACR,yDAAsC;AACtC,sDAAmC;AAEnC,oBAAoB;AACpB,uDAAoC;AACpC,0DAAuC;AACvC,0DAAuC;AACvC,wDAAqC;AACrC,qDAAkC;AAClC,6CAA0B;AAE1B,mBAAmB;AACnB,qDAAkC;AAClC,uDAAoC;AACpC,qDAAkC;AAClC,kDAA+B"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PairwiseAlignment } from './mappings.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Each residue of row `a` that row `b` shares, as 0-based index in `a` to
|
|
4
|
+
* 0-based index in `b`. A lone substitution between two kept stretches carries
|
|
5
|
+
* too: it is one codon two databases read differently, as MUC1's repeats are.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sharedResidueMap(a: string, b: string): Map<number, number>;
|
|
8
|
+
/** Each residue of isoform `from` that isoform `to` shares, through a global
|
|
9
|
+
* alignment of the two. */
|
|
10
|
+
export declare function sharedIsoformResidues(from: string, to: string): Map<number, number>;
|
|
11
|
+
/**
|
|
12
|
+
* The alignment of a transcript to another isoform of its protein, with every
|
|
13
|
+
* residue outside a shared stretch unmapped. For two isoforms, not for a
|
|
14
|
+
* structure: a mismatch against a structure's chain is usually a point
|
|
15
|
+
* mutation in the construct, at the right place in the fold.
|
|
16
|
+
*/
|
|
17
|
+
export declare function keepSharedStretches(pa: PairwiseAlignment): PairwiseAlignment;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Which residues two isoforms of one protein share. An alignment column pairs
|
|
3
|
+
// two residues whether or not one codon encodes both: a mutually exclusive exon
|
|
4
|
+
// sits opposite its partner (PKM's exons 9 and 10, FGFR2's IIIb and IIIc), and
|
|
5
|
+
// its residues align, mismatches and chance identities included. A residue
|
|
6
|
+
// carries across only inside a stretch the two spell letter for letter: a long
|
|
7
|
+
// one, or a short one bounded on both sides by a gap or a sequence end, the
|
|
8
|
+
// shape a short shared exon takes (VEGFA's six-residue exon 8a).
|
|
9
|
+
//
|
|
10
|
+
// Measured 2026-09-25 against codon identity on the genome, over every isoform
|
|
11
|
+
// of 13 genes (TP53, PKM, CDKN2A, FGFR2, TPM1, BRAF, EGFR, SCN8A, MAPT, BIN1,
|
|
12
|
+
// VEGFA, TPM3, CD44; 89,927 truly shared residues), on global alignments: 335
|
|
13
|
+
// placed wrongly and 28 missed, against 1,885 and 28 for every identical
|
|
14
|
+
// residue and 328 and 64 for long stretches alone. The misses are single
|
|
15
|
+
// residues at exon junctions, where the gap fits either side.
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.sharedResidueMap = sharedResidueMap;
|
|
18
|
+
exports.sharedIsoformResidues = sharedIsoformResidues;
|
|
19
|
+
exports.keepSharedStretches = keepSharedStretches;
|
|
20
|
+
const mappings_ts_1 = require("./mappings.js");
|
|
21
|
+
const pairwiseAlignment_ts_1 = require("./pairwiseAlignment.js");
|
|
22
|
+
const LONG_STRETCH = 10;
|
|
23
|
+
const SHORT_EXON = 3;
|
|
24
|
+
/**
|
|
25
|
+
* Each residue of row `a` that row `b` shares, as 0-based index in `a` to
|
|
26
|
+
* 0-based index in `b`. A lone substitution between two kept stretches carries
|
|
27
|
+
* too: it is one codon two databases read differently, as MUC1's repeats are.
|
|
28
|
+
*/
|
|
29
|
+
function sharedResidueMap(a, b) {
|
|
30
|
+
const cols = [];
|
|
31
|
+
for (let c = 0, x = 0, y = 0; c < a.length; c++) {
|
|
32
|
+
const inA = a[c] !== '-';
|
|
33
|
+
const inB = b[c] !== '-';
|
|
34
|
+
cols.push({
|
|
35
|
+
x,
|
|
36
|
+
y,
|
|
37
|
+
kind: !inA || !inB
|
|
38
|
+
? 'gap'
|
|
39
|
+
: a[c].toUpperCase() === b[c].toUpperCase()
|
|
40
|
+
? 'same'
|
|
41
|
+
: 'mismatch',
|
|
42
|
+
});
|
|
43
|
+
if (inA) {
|
|
44
|
+
x++;
|
|
45
|
+
}
|
|
46
|
+
if (inB) {
|
|
47
|
+
y++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const bounded = (c) => cols[c]?.kind !== 'mismatch';
|
|
51
|
+
const kept = cols.map(() => false);
|
|
52
|
+
for (let start = 0; start < cols.length;) {
|
|
53
|
+
let end = start;
|
|
54
|
+
while (cols[end]?.kind === 'same') {
|
|
55
|
+
end++;
|
|
56
|
+
}
|
|
57
|
+
const length = end - start;
|
|
58
|
+
if (length >= LONG_STRETCH ||
|
|
59
|
+
(length >= SHORT_EXON && bounded(start - 1) && bounded(end))) {
|
|
60
|
+
kept.fill(true, start, end);
|
|
61
|
+
}
|
|
62
|
+
start = Math.max(end, start + 1);
|
|
63
|
+
}
|
|
64
|
+
const shared = new Map();
|
|
65
|
+
cols.forEach((col, c) => {
|
|
66
|
+
if (kept[c] || (col.kind === 'mismatch' && kept[c - 1] && kept[c + 1])) {
|
|
67
|
+
shared.set(col.x, col.y);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return shared;
|
|
71
|
+
}
|
|
72
|
+
/** Each residue of isoform `from` that isoform `to` shares, through a global
|
|
73
|
+
* alignment of the two. */
|
|
74
|
+
function sharedIsoformResidues(from, to) {
|
|
75
|
+
const { alignedSeq1, alignedSeq2 } = (0, pairwiseAlignment_ts_1.needlemanWunsch)(from, to);
|
|
76
|
+
return sharedResidueMap(alignedSeq1, alignedSeq2);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The alignment of a transcript to another isoform of its protein, with every
|
|
80
|
+
* residue outside a shared stretch unmapped. For two isoforms, not for a
|
|
81
|
+
* structure: a mismatch against a structure's chain is usually a point
|
|
82
|
+
* mutation in the construct, at the right place in the fold.
|
|
83
|
+
*/
|
|
84
|
+
function keepSharedStretches(pa) {
|
|
85
|
+
const structure = (0, mappings_ts_1.structureAlignedSeq)(pa);
|
|
86
|
+
const transcript = (0, mappings_ts_1.transcriptAlignedSeq)(pa);
|
|
87
|
+
const shared = sharedResidueMap(structure, transcript);
|
|
88
|
+
const unshared = new Set();
|
|
89
|
+
for (let i = 0, j = 0; i < structure.length; i++) {
|
|
90
|
+
if (structure[i] !== '-') {
|
|
91
|
+
if (transcript[i] !== '-' && !shared.has(j)) {
|
|
92
|
+
unshared.add(j);
|
|
93
|
+
}
|
|
94
|
+
j++;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return (0, mappings_ts_1.unmapStructurePositions)(pa, unshared);
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=sharedStretches.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sharedStretches.js","sourceRoot":"","sources":["../src/sharedStretches.ts"],"names":[],"mappings":";AAAA,8EAA8E;AAC9E,gFAAgF;AAChF,+EAA+E;AAC/E,2EAA2E;AAC3E,+EAA+E;AAC/E,4EAA4E;AAC5E,iEAAiE;AACjE,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,8EAA8E;AAC9E,yEAAyE;AACzE,yEAAyE;AACzE,8DAA8D;;AAqB9D,4CA6CC;AAID,sDAGC;AAQD,kDAcC;AA7FD,+CAIsB;AACtB,iEAAwD;AAIxD,MAAM,YAAY,GAAG,EAAE,CAAA;AACvB,MAAM,UAAU,GAAG,CAAC,CAAA;AAIpB;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,CAAS,EAAE,CAAS;IACnD,MAAM,IAAI,GAAiD,EAAE,CAAA;IAC7D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChD,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAA;QACxB,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAA;QACxB,IAAI,CAAC,IAAI,CAAC;YACR,CAAC;YACD,CAAC;YACD,IAAI,EACF,CAAC,GAAG,IAAI,CAAC,GAAG;gBACV,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE;oBAC3C,CAAC,CAAC,MAAM;oBACR,CAAC,CAAC,UAAU;SACnB,CAAC,CAAA;QACF,IAAI,GAAG,EAAE,CAAC;YACR,CAAC,EAAE,CAAA;QACL,CAAC;QACD,IAAI,GAAG,EAAE,CAAC;YACR,CAAC,EAAE,CAAA;QACL,CAAC;IACH,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,UAAU,CAAA;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAA;IAClC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC;QACzC,IAAI,GAAG,GAAG,KAAK,CAAA;QACf,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;YAClC,GAAG,EAAE,CAAA;QACP,CAAC;QACD,MAAM,MAAM,GAAG,GAAG,GAAG,KAAK,CAAA;QAC1B,IACE,MAAM,IAAI,YAAY;YACtB,CAAC,MAAM,IAAI,UAAU,IAAI,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,EAC5D,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAA;QAC7B,CAAC;QACD,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAA;IAClC,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IACxC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;QACtB,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACvE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;QAC1B,CAAC;IACH,CAAC,CAAC,CAAA;IACF,OAAO,MAAM,CAAA;AACf,CAAC;AAED;2BAC2B;AAC3B,SAAgB,qBAAqB,CAAC,IAAY,EAAE,EAAU;IAC5D,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,IAAA,sCAAe,EAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IAC9D,OAAO,gBAAgB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;AACnD,CAAC;AAED;;;;;GAKG;AACH,SAAgB,mBAAmB,CAAC,EAAqB;IACvD,MAAM,SAAS,GAAG,IAAA,iCAAmB,EAAC,EAAE,CAAC,CAAA;IACzC,MAAM,UAAU,GAAG,IAAA,kCAAoB,EAAC,EAAE,CAAC,CAAA;IAC3C,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,EAAE,UAAU,CAAC,CAAA;IACtD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAA;IAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACjD,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACzB,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC5C,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,CAAC,EAAE,CAAA;QACL,CAAC;IACH,CAAC;IACD,OAAO,IAAA,qCAAuB,EAAC,EAAE,EAAE,QAAQ,CAAC,CAAA;AAC9C,CAAC"}
|
package/dist/structureSources.js
CHANGED
|
@@ -10,8 +10,11 @@ exports.beaconsSummaryUrl = beaconsSummaryUrl;
|
|
|
10
10
|
exports.parseExperimentalStructures = parseExperimentalStructures;
|
|
11
11
|
exports.fetchExperimentalStructures = fetchExperimentalStructures;
|
|
12
12
|
const fetchUtils_ts_1 = require("./fetchUtils.js");
|
|
13
|
+
// Unfiltered: the `?provider=pdbe` form answered BRAF, HBB and BRCA2 with a 404
|
|
14
|
+
// about one time in three (2026-10-09), and the parser keeps PDBe's entries
|
|
15
|
+
// anyway.
|
|
13
16
|
function beaconsSummaryUrl(uniprotId) {
|
|
14
|
-
return `https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api/uniprot/summary/${encodeURIComponent(uniprotId)}.json
|
|
17
|
+
return `https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api/uniprot/summary/${encodeURIComponent(uniprotId)}.json`;
|
|
15
18
|
}
|
|
16
19
|
function isRecord(value) {
|
|
17
20
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -70,7 +73,15 @@ function parseExperimentalStructures(json) {
|
|
|
70
73
|
/** Best-effort: an unreachable API reads as "no entries", which costs a
|
|
71
74
|
* structure suggestion and nothing else. */
|
|
72
75
|
async function fetchExperimentalStructures(uniprotId, opts) {
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
77
|
+
const res = await (0, fetchUtils_ts_1.rawfetch)(beaconsSummaryUrl(uniprotId), opts).catch(() => undefined);
|
|
78
|
+
if (res?.ok) {
|
|
79
|
+
return parseExperimentalStructures(await res.json());
|
|
80
|
+
}
|
|
81
|
+
if (res?.status === 404 || opts?.signal?.aborted) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return [];
|
|
75
86
|
}
|
|
76
87
|
//# sourceMappingURL=structureSources.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structureSources.js","sourceRoot":"","sources":["../src/structureSources.ts"],"names":[],"mappings":";AAAA,+EAA+E;AAC/E,0EAA0E;AAC1E,yEAAyE;AACzE,8EAA8E;AAC9E,yEAAyE;AACzE,mBAAmB;;
|
|
1
|
+
{"version":3,"file":"structureSources.js","sourceRoot":"","sources":["../src/structureSources.ts"],"names":[],"mappings":"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"}
|
package/esm/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from './types.ts';
|
|
|
2
2
|
export * from './mappings.ts';
|
|
3
3
|
export * from './pairwiseAlignment.ts';
|
|
4
4
|
export * from './alignmentQuality.ts';
|
|
5
|
+
export * from './sharedStretches.ts';
|
|
5
6
|
export * from './coordinates.ts';
|
|
6
7
|
export * from './invertMap.ts';
|
|
7
8
|
export * from './stripStopCodon.ts';
|
package/esm/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./types.js";
|
|
|
3
3
|
export * from "./mappings.js";
|
|
4
4
|
export * from "./pairwiseAlignment.js";
|
|
5
5
|
export * from "./alignmentQuality.js";
|
|
6
|
+
export * from "./sharedStretches.js";
|
|
6
7
|
// Coordinates
|
|
7
8
|
export * from "./coordinates.js";
|
|
8
9
|
export * from "./invertMap.js";
|
package/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY;AACZ,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY;AACZ,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AAEpC,cAAc;AACd,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,qBAAqB,CAAA;AAEnC,eAAe;AACf,cAAc,yBAAyB,CAAA;AACvC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,kCAAkC,CAAA;AAChD,cAAc,qBAAqB,CAAA;AAEnC,QAAQ;AACR,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AAEnC,oBAAoB;AACpB,cAAc,sBAAsB,CAAA;AACpC,cAAc,yBAAyB,CAAA;AACvC,cAAc,yBAAyB,CAAA;AACvC,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAE1B,mBAAmB;AACnB,cAAc,oBAAoB,CAAA;AAClC,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PairwiseAlignment } from './mappings.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Each residue of row `a` that row `b` shares, as 0-based index in `a` to
|
|
4
|
+
* 0-based index in `b`. A lone substitution between two kept stretches carries
|
|
5
|
+
* too: it is one codon two databases read differently, as MUC1's repeats are.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sharedResidueMap(a: string, b: string): Map<number, number>;
|
|
8
|
+
/** Each residue of isoform `from` that isoform `to` shares, through a global
|
|
9
|
+
* alignment of the two. */
|
|
10
|
+
export declare function sharedIsoformResidues(from: string, to: string): Map<number, number>;
|
|
11
|
+
/**
|
|
12
|
+
* The alignment of a transcript to another isoform of its protein, with every
|
|
13
|
+
* residue outside a shared stretch unmapped. For two isoforms, not for a
|
|
14
|
+
* structure: a mismatch against a structure's chain is usually a point
|
|
15
|
+
* mutation in the construct, at the right place in the fold.
|
|
16
|
+
*/
|
|
17
|
+
export declare function keepSharedStretches(pa: PairwiseAlignment): PairwiseAlignment;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Which residues two isoforms of one protein share. An alignment column pairs
|
|
2
|
+
// two residues whether or not one codon encodes both: a mutually exclusive exon
|
|
3
|
+
// sits opposite its partner (PKM's exons 9 and 10, FGFR2's IIIb and IIIc), and
|
|
4
|
+
// its residues align, mismatches and chance identities included. A residue
|
|
5
|
+
// carries across only inside a stretch the two spell letter for letter: a long
|
|
6
|
+
// one, or a short one bounded on both sides by a gap or a sequence end, the
|
|
7
|
+
// shape a short shared exon takes (VEGFA's six-residue exon 8a).
|
|
8
|
+
//
|
|
9
|
+
// Measured 2026-09-25 against codon identity on the genome, over every isoform
|
|
10
|
+
// of 13 genes (TP53, PKM, CDKN2A, FGFR2, TPM1, BRAF, EGFR, SCN8A, MAPT, BIN1,
|
|
11
|
+
// VEGFA, TPM3, CD44; 89,927 truly shared residues), on global alignments: 335
|
|
12
|
+
// placed wrongly and 28 missed, against 1,885 and 28 for every identical
|
|
13
|
+
// residue and 328 and 64 for long stretches alone. The misses are single
|
|
14
|
+
// residues at exon junctions, where the gap fits either side.
|
|
15
|
+
import { structureAlignedSeq, transcriptAlignedSeq, unmapStructurePositions, } from "./mappings.js";
|
|
16
|
+
import { needlemanWunsch } from "./pairwiseAlignment.js";
|
|
17
|
+
const LONG_STRETCH = 10;
|
|
18
|
+
const SHORT_EXON = 3;
|
|
19
|
+
/**
|
|
20
|
+
* Each residue of row `a` that row `b` shares, as 0-based index in `a` to
|
|
21
|
+
* 0-based index in `b`. A lone substitution between two kept stretches carries
|
|
22
|
+
* too: it is one codon two databases read differently, as MUC1's repeats are.
|
|
23
|
+
*/
|
|
24
|
+
export function sharedResidueMap(a, b) {
|
|
25
|
+
const cols = [];
|
|
26
|
+
for (let c = 0, x = 0, y = 0; c < a.length; c++) {
|
|
27
|
+
const inA = a[c] !== '-';
|
|
28
|
+
const inB = b[c] !== '-';
|
|
29
|
+
cols.push({
|
|
30
|
+
x,
|
|
31
|
+
y,
|
|
32
|
+
kind: !inA || !inB
|
|
33
|
+
? 'gap'
|
|
34
|
+
: a[c].toUpperCase() === b[c].toUpperCase()
|
|
35
|
+
? 'same'
|
|
36
|
+
: 'mismatch',
|
|
37
|
+
});
|
|
38
|
+
if (inA) {
|
|
39
|
+
x++;
|
|
40
|
+
}
|
|
41
|
+
if (inB) {
|
|
42
|
+
y++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const bounded = (c) => cols[c]?.kind !== 'mismatch';
|
|
46
|
+
const kept = cols.map(() => false);
|
|
47
|
+
for (let start = 0; start < cols.length;) {
|
|
48
|
+
let end = start;
|
|
49
|
+
while (cols[end]?.kind === 'same') {
|
|
50
|
+
end++;
|
|
51
|
+
}
|
|
52
|
+
const length = end - start;
|
|
53
|
+
if (length >= LONG_STRETCH ||
|
|
54
|
+
(length >= SHORT_EXON && bounded(start - 1) && bounded(end))) {
|
|
55
|
+
kept.fill(true, start, end);
|
|
56
|
+
}
|
|
57
|
+
start = Math.max(end, start + 1);
|
|
58
|
+
}
|
|
59
|
+
const shared = new Map();
|
|
60
|
+
cols.forEach((col, c) => {
|
|
61
|
+
if (kept[c] || (col.kind === 'mismatch' && kept[c - 1] && kept[c + 1])) {
|
|
62
|
+
shared.set(col.x, col.y);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return shared;
|
|
66
|
+
}
|
|
67
|
+
/** Each residue of isoform `from` that isoform `to` shares, through a global
|
|
68
|
+
* alignment of the two. */
|
|
69
|
+
export function sharedIsoformResidues(from, to) {
|
|
70
|
+
const { alignedSeq1, alignedSeq2 } = needlemanWunsch(from, to);
|
|
71
|
+
return sharedResidueMap(alignedSeq1, alignedSeq2);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The alignment of a transcript to another isoform of its protein, with every
|
|
75
|
+
* residue outside a shared stretch unmapped. For two isoforms, not for a
|
|
76
|
+
* structure: a mismatch against a structure's chain is usually a point
|
|
77
|
+
* mutation in the construct, at the right place in the fold.
|
|
78
|
+
*/
|
|
79
|
+
export function keepSharedStretches(pa) {
|
|
80
|
+
const structure = structureAlignedSeq(pa);
|
|
81
|
+
const transcript = transcriptAlignedSeq(pa);
|
|
82
|
+
const shared = sharedResidueMap(structure, transcript);
|
|
83
|
+
const unshared = new Set();
|
|
84
|
+
for (let i = 0, j = 0; i < structure.length; i++) {
|
|
85
|
+
if (structure[i] !== '-') {
|
|
86
|
+
if (transcript[i] !== '-' && !shared.has(j)) {
|
|
87
|
+
unshared.add(j);
|
|
88
|
+
}
|
|
89
|
+
j++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return unmapStructurePositions(pa, unshared);
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=sharedStretches.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sharedStretches.js","sourceRoot":"","sources":["../src/sharedStretches.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,gFAAgF;AAChF,+EAA+E;AAC/E,2EAA2E;AAC3E,+EAA+E;AAC/E,4EAA4E;AAC5E,iEAAiE;AACjE,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,8EAA8E;AAC9E,yEAAyE;AACzE,yEAAyE;AACzE,8DAA8D;AAE9D,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAIxD,MAAM,YAAY,GAAG,EAAE,CAAA;AACvB,MAAM,UAAU,GAAG,CAAC,CAAA;AAIpB;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAS,EAAE,CAAS;IACnD,MAAM,IAAI,GAAiD,EAAE,CAAA;IAC7D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChD,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAA;QACxB,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAA;QACxB,IAAI,CAAC,IAAI,CAAC;YACR,CAAC;YACD,CAAC;YACD,IAAI,EACF,CAAC,GAAG,IAAI,CAAC,GAAG;gBACV,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE;oBAC3C,CAAC,CAAC,MAAM;oBACR,CAAC,CAAC,UAAU;SACnB,CAAC,CAAA;QACF,IAAI,GAAG,EAAE,CAAC;YACR,CAAC,EAAE,CAAA;QACL,CAAC;QACD,IAAI,GAAG,EAAE,CAAC;YACR,CAAC,EAAE,CAAA;QACL,CAAC;IACH,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,UAAU,CAAA;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAA;IAClC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC;QACzC,IAAI,GAAG,GAAG,KAAK,CAAA;QACf,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;YAClC,GAAG,EAAE,CAAA;QACP,CAAC;QACD,MAAM,MAAM,GAAG,GAAG,GAAG,KAAK,CAAA;QAC1B,IACE,MAAM,IAAI,YAAY;YACtB,CAAC,MAAM,IAAI,UAAU,IAAI,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,EAC5D,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAA;QAC7B,CAAC;QACD,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAA;IAClC,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IACxC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;QACtB,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACvE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;QAC1B,CAAC;IACH,CAAC,CAAC,CAAA;IACF,OAAO,MAAM,CAAA;AACf,CAAC;AAED;2BAC2B;AAC3B,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,EAAU;IAC5D,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IAC9D,OAAO,gBAAgB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;AACnD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAqB;IACvD,MAAM,SAAS,GAAG,mBAAmB,CAAC,EAAE,CAAC,CAAA;IACzC,MAAM,UAAU,GAAG,oBAAoB,CAAC,EAAE,CAAC,CAAA;IAC3C,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,EAAE,UAAU,CAAC,CAAA;IACtD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAA;IAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACjD,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACzB,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC5C,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,CAAC,EAAE,CAAA;QACL,CAAC;IACH,CAAC;IACD,OAAO,uBAAuB,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAA;AAC9C,CAAC"}
|
package/esm/structureSources.js
CHANGED
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
// the entry id from here and ask `fetchUniProtStructureMappings` for its
|
|
6
6
|
// residue mapping.
|
|
7
7
|
import { rawfetch } from "./fetchUtils.js";
|
|
8
|
+
// Unfiltered: the `?provider=pdbe` form answered BRAF, HBB and BRCA2 with a 404
|
|
9
|
+
// about one time in three (2026-10-09), and the parser keeps PDBe's entries
|
|
10
|
+
// anyway.
|
|
8
11
|
export function beaconsSummaryUrl(uniprotId) {
|
|
9
|
-
return `https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api/uniprot/summary/${encodeURIComponent(uniprotId)}.json
|
|
12
|
+
return `https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api/uniprot/summary/${encodeURIComponent(uniprotId)}.json`;
|
|
10
13
|
}
|
|
11
14
|
function isRecord(value) {
|
|
12
15
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -65,7 +68,15 @@ export function parseExperimentalStructures(json) {
|
|
|
65
68
|
/** Best-effort: an unreachable API reads as "no entries", which costs a
|
|
66
69
|
* structure suggestion and nothing else. */
|
|
67
70
|
export async function fetchExperimentalStructures(uniprotId, opts) {
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
72
|
+
const res = await rawfetch(beaconsSummaryUrl(uniprotId), opts).catch(() => undefined);
|
|
73
|
+
if (res?.ok) {
|
|
74
|
+
return parseExperimentalStructures(await res.json());
|
|
75
|
+
}
|
|
76
|
+
if (res?.status === 404 || opts?.signal?.aborted) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return [];
|
|
70
81
|
}
|
|
71
82
|
//# sourceMappingURL=structureSources.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structureSources.js","sourceRoot":"","sources":["../src/structureSources.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,0EAA0E;AAC1E,yEAAyE;AACzE,8EAA8E;AAC9E,yEAAyE;AACzE,mBAAmB;AAEnB,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAgB1C,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,OAAO,oEAAoE,kBAAkB,CAAC,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"structureSources.js","sourceRoot":"","sources":["../src/structureSources.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "p2s_mapper",
|
|
3
3
|
"description": "Map a transcript's translation onto a protein structure: alignment, coordinates, chain choice, SIFTS, and the AlphaFold and PDBe lookups that find the structure",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.0",
|
|
5
5
|
"packageManager": "pnpm@11.15.1",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Which residues two isoforms of one protein share. An alignment column pairs
|
|
2
|
+
// two residues whether or not one codon encodes both: a mutually exclusive exon
|
|
3
|
+
// sits opposite its partner (PKM's exons 9 and 10, FGFR2's IIIb and IIIc), and
|
|
4
|
+
// its residues align, mismatches and chance identities included. A residue
|
|
5
|
+
// carries across only inside a stretch the two spell letter for letter: a long
|
|
6
|
+
// one, or a short one bounded on both sides by a gap or a sequence end, the
|
|
7
|
+
// shape a short shared exon takes (VEGFA's six-residue exon 8a).
|
|
8
|
+
//
|
|
9
|
+
// Measured 2026-09-25 against codon identity on the genome, over every isoform
|
|
10
|
+
// of 13 genes (TP53, PKM, CDKN2A, FGFR2, TPM1, BRAF, EGFR, SCN8A, MAPT, BIN1,
|
|
11
|
+
// VEGFA, TPM3, CD44; 89,927 truly shared residues), on global alignments: 335
|
|
12
|
+
// placed wrongly and 28 missed, against 1,885 and 28 for every identical
|
|
13
|
+
// residue and 328 and 64 for long stretches alone. The misses are single
|
|
14
|
+
// residues at exon junctions, where the gap fits either side.
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
structureAlignedSeq,
|
|
18
|
+
transcriptAlignedSeq,
|
|
19
|
+
unmapStructurePositions,
|
|
20
|
+
} from './mappings.ts'
|
|
21
|
+
import { needlemanWunsch } from './pairwiseAlignment.ts'
|
|
22
|
+
|
|
23
|
+
import type { PairwiseAlignment } from './mappings.ts'
|
|
24
|
+
|
|
25
|
+
const LONG_STRETCH = 10
|
|
26
|
+
const SHORT_EXON = 3
|
|
27
|
+
|
|
28
|
+
type ColumnKind = 'same' | 'mismatch' | 'gap'
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Each residue of row `a` that row `b` shares, as 0-based index in `a` to
|
|
32
|
+
* 0-based index in `b`. A lone substitution between two kept stretches carries
|
|
33
|
+
* too: it is one codon two databases read differently, as MUC1's repeats are.
|
|
34
|
+
*/
|
|
35
|
+
export function sharedResidueMap(a: string, b: string) {
|
|
36
|
+
const cols: { x: number; y: number; kind: ColumnKind }[] = []
|
|
37
|
+
for (let c = 0, x = 0, y = 0; c < a.length; c++) {
|
|
38
|
+
const inA = a[c] !== '-'
|
|
39
|
+
const inB = b[c] !== '-'
|
|
40
|
+
cols.push({
|
|
41
|
+
x,
|
|
42
|
+
y,
|
|
43
|
+
kind:
|
|
44
|
+
!inA || !inB
|
|
45
|
+
? 'gap'
|
|
46
|
+
: a[c]!.toUpperCase() === b[c]!.toUpperCase()
|
|
47
|
+
? 'same'
|
|
48
|
+
: 'mismatch',
|
|
49
|
+
})
|
|
50
|
+
if (inA) {
|
|
51
|
+
x++
|
|
52
|
+
}
|
|
53
|
+
if (inB) {
|
|
54
|
+
y++
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const bounded = (c: number) => cols[c]?.kind !== 'mismatch'
|
|
58
|
+
const kept = cols.map(() => false)
|
|
59
|
+
for (let start = 0; start < cols.length;) {
|
|
60
|
+
let end = start
|
|
61
|
+
while (cols[end]?.kind === 'same') {
|
|
62
|
+
end++
|
|
63
|
+
}
|
|
64
|
+
const length = end - start
|
|
65
|
+
if (
|
|
66
|
+
length >= LONG_STRETCH ||
|
|
67
|
+
(length >= SHORT_EXON && bounded(start - 1) && bounded(end))
|
|
68
|
+
) {
|
|
69
|
+
kept.fill(true, start, end)
|
|
70
|
+
}
|
|
71
|
+
start = Math.max(end, start + 1)
|
|
72
|
+
}
|
|
73
|
+
const shared = new Map<number, number>()
|
|
74
|
+
cols.forEach((col, c) => {
|
|
75
|
+
if (kept[c] || (col.kind === 'mismatch' && kept[c - 1] && kept[c + 1])) {
|
|
76
|
+
shared.set(col.x, col.y)
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
return shared
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Each residue of isoform `from` that isoform `to` shares, through a global
|
|
83
|
+
* alignment of the two. */
|
|
84
|
+
export function sharedIsoformResidues(from: string, to: string) {
|
|
85
|
+
const { alignedSeq1, alignedSeq2 } = needlemanWunsch(from, to)
|
|
86
|
+
return sharedResidueMap(alignedSeq1, alignedSeq2)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The alignment of a transcript to another isoform of its protein, with every
|
|
91
|
+
* residue outside a shared stretch unmapped. For two isoforms, not for a
|
|
92
|
+
* structure: a mismatch against a structure's chain is usually a point
|
|
93
|
+
* mutation in the construct, at the right place in the fold.
|
|
94
|
+
*/
|
|
95
|
+
export function keepSharedStretches(pa: PairwiseAlignment) {
|
|
96
|
+
const structure = structureAlignedSeq(pa)
|
|
97
|
+
const transcript = transcriptAlignedSeq(pa)
|
|
98
|
+
const shared = sharedResidueMap(structure, transcript)
|
|
99
|
+
const unshared = new Set<number>()
|
|
100
|
+
for (let i = 0, j = 0; i < structure.length; i++) {
|
|
101
|
+
if (structure[i] !== '-') {
|
|
102
|
+
if (transcript[i] !== '-' && !shared.has(j)) {
|
|
103
|
+
unshared.add(j)
|
|
104
|
+
}
|
|
105
|
+
j++
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return unmapStructurePositions(pa, unshared)
|
|
109
|
+
}
|
package/src/structureSources.ts
CHANGED
|
@@ -21,8 +21,11 @@ export interface ExperimentalStructure {
|
|
|
21
21
|
coverage: number
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// Unfiltered: the `?provider=pdbe` form answered BRAF, HBB and BRCA2 with a 404
|
|
25
|
+
// about one time in three (2026-10-09), and the parser keeps PDBe's entries
|
|
26
|
+
// anyway.
|
|
24
27
|
export function beaconsSummaryUrl(uniprotId: string) {
|
|
25
|
-
return `https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api/uniprot/summary/${encodeURIComponent(uniprotId)}.json
|
|
28
|
+
return `https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api/uniprot/summary/${encodeURIComponent(uniprotId)}.json`
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -94,8 +97,16 @@ export async function fetchExperimentalStructures(
|
|
|
94
97
|
uniprotId: string,
|
|
95
98
|
opts?: FetchOptions,
|
|
96
99
|
): Promise<ExperimentalStructure[]> {
|
|
97
|
-
|
|
98
|
-
()
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
101
|
+
const res = await rawfetch(beaconsSummaryUrl(uniprotId), opts).catch(
|
|
102
|
+
() => undefined,
|
|
103
|
+
)
|
|
104
|
+
if (res?.ok) {
|
|
105
|
+
return parseExperimentalStructures(await res.json())
|
|
106
|
+
}
|
|
107
|
+
if (res?.status === 404 || opts?.signal?.aborted) {
|
|
108
|
+
return []
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return []
|
|
101
112
|
}
|