@genspectrum/dashboard-components 0.1.1
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/LICENSE +661 -0
- package/README.md +109 -0
- package/custom-elements.json +1587 -0
- package/dist/dashboard-components.js +7322 -0
- package/dist/dashboard-components.js.map +1 -0
- package/dist/genspectrum-components.d.ts +298 -0
- package/dist/style.css +2930 -0
- package/package.json +109 -0
- package/src/constants.ts +6 -0
- package/src/index.ts +1 -0
- package/src/lapisApi/ReferenceGenome.ts +30 -0
- package/src/lapisApi/__mockData__/referenceGenome.json +58 -0
- package/src/lapisApi/lapisApi.ts +99 -0
- package/src/lapisApi/lapisTypes.ts +51 -0
- package/src/operator/Dataset.ts +3 -0
- package/src/operator/DivisionOperator.spec.ts +27 -0
- package/src/operator/DivisionOperator.ts +60 -0
- package/src/operator/FetchAggregatedOperator.ts +44 -0
- package/src/operator/FetchInsertionsOperator.ts +24 -0
- package/src/operator/FetchSubstitutionsOrDeletionsOperator.ts +49 -0
- package/src/operator/FillMissingOperator.spec.ts +26 -0
- package/src/operator/FillMissingOperator.ts +30 -0
- package/src/operator/GroupByAndSumOperator.spec.ts +26 -0
- package/src/operator/GroupByAndSumOperator.ts +26 -0
- package/src/operator/GroupByOperator.spec.ts +43 -0
- package/src/operator/GroupByOperator.ts +32 -0
- package/src/operator/MapOperator.spec.ts +13 -0
- package/src/operator/MapOperator.ts +16 -0
- package/src/operator/MockOperator.spec.ts +11 -0
- package/src/operator/MockOperator.ts +12 -0
- package/src/operator/Operator.ts +5 -0
- package/src/operator/SlidingOperator.spec.ts +52 -0
- package/src/operator/SlidingOperator.ts +23 -0
- package/src/operator/SortOperator.spec.ts +13 -0
- package/src/operator/SortOperator.ts +16 -0
- package/src/preact/LapisUrlContext.ts +3 -0
- package/src/preact/ReferenceGenomeContext.ts +5 -0
- package/src/preact/components/SegmentSelector.tsx +62 -0
- package/src/preact/components/chart.stories.tsx +42 -0
- package/src/preact/components/chart.tsx +32 -0
- package/src/preact/components/checkbox-selector.stories.tsx +56 -0
- package/src/preact/components/checkbox-selector.tsx +46 -0
- package/src/preact/components/confidence-interval-selector.tsx +45 -0
- package/src/preact/components/csv-download-button.stories.tsx +25 -0
- package/src/preact/components/csv-download-button.tsx +51 -0
- package/src/preact/components/error-display.stories.tsx +22 -0
- package/src/preact/components/error-display.tsx +5 -0
- package/src/preact/components/headline.stories.tsx +29 -0
- package/src/preact/components/headline.tsx +16 -0
- package/src/preact/components/info.stories.tsx +22 -0
- package/src/preact/components/info.tsx +16 -0
- package/src/preact/components/loading-display.stories.tsx +20 -0
- package/src/preact/components/loading-display.tsx +5 -0
- package/src/preact/components/min-max-percent-slider.css +40 -0
- package/src/preact/components/min-max-range-slider.tsx +95 -0
- package/src/preact/components/mutation-type-selector.tsx +30 -0
- package/src/preact/components/no-data-display.stories.tsx +20 -0
- package/src/preact/components/no-data-display.tsx +5 -0
- package/src/preact/components/percent-intput.tsx +49 -0
- package/src/preact/components/proportion-selector-dropdown.stories.tsx +66 -0
- package/src/preact/components/proportion-selector-dropdown.tsx +33 -0
- package/src/preact/components/proportion-selector.stories.tsx +81 -0
- package/src/preact/components/proportion-selector.tsx +43 -0
- package/src/preact/components/scaling-selector.stories.tsx +25 -0
- package/src/preact/components/scaling-selector.tsx +36 -0
- package/src/preact/components/select.stories.tsx +42 -0
- package/src/preact/components/select.tsx +21 -0
- package/src/preact/components/table.stories.tsx +24 -0
- package/src/preact/components/table.tsx +51 -0
- package/src/preact/components/tabs.stories.tsx +60 -0
- package/src/preact/components/tabs.tsx +49 -0
- package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +32 -0
- package/src/preact/dateRangeSelector/date-range-selector.tsx +228 -0
- package/src/preact/dateRangeSelector/dateConversion.ts +8 -0
- package/src/preact/locationFilter/__mockData__/aggregated.json +775 -0
- package/src/preact/locationFilter/fetchAutocompletionList.spec.ts +36 -0
- package/src/preact/locationFilter/fetchAutocompletionList.ts +43 -0
- package/src/preact/locationFilter/location-filter.stories.tsx +50 -0
- package/src/preact/locationFilter/location-filter.tsx +112 -0
- package/src/preact/mutationComparison/__mockData__/nucleotideMutationsOtherVariant.json +295 -0
- package/src/preact/mutationComparison/__mockData__/nucleotideMutationsSomeVariant.json +304 -0
- package/src/preact/mutationComparison/fetchMutationData.spec.ts +118 -0
- package/src/preact/mutationComparison/getMutationComparisonTableData.spec.ts +125 -0
- package/src/preact/mutationComparison/getMutationComparisonTableData.ts +40 -0
- package/src/preact/mutationComparison/mutation-comparison-table.tsx +43 -0
- package/src/preact/mutationComparison/mutation-comparison-venn.tsx +122 -0
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +152 -0
- package/src/preact/mutationComparison/mutation-comparison.tsx +179 -0
- package/src/preact/mutationComparison/queryMutationData.ts +53 -0
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +164 -0
- package/src/preact/mutationFilter/mutation-filter.tsx +268 -0
- package/src/preact/mutationFilter/parseAndValidateMutation.ts +54 -0
- package/src/preact/mutationFilter/parseMutation.spec.ts +150 -0
- package/src/preact/mutationFilter/sequenceTypeFromSegment.spec.ts +66 -0
- package/src/preact/mutationFilter/sequenceTypeFromSegment.ts +20 -0
- package/src/preact/mutations/__mockData__/nucleotideInsertions.json +252 -0
- package/src/preact/mutations/__mockData__/nucleotideMutations.json +880 -0
- package/src/preact/mutations/getInsertionsTableData.spec.ts +36 -0
- package/src/preact/mutations/getInsertionsTableData.ts +10 -0
- package/src/preact/mutations/getMutationsGridData.spec.ts +135 -0
- package/src/preact/mutations/getMutationsGridData.ts +92 -0
- package/src/preact/mutations/getMutationsTableData.spec.ts +94 -0
- package/src/preact/mutations/getMutationsTableData.ts +17 -0
- package/src/preact/mutations/mutations-grid.tsx +84 -0
- package/src/preact/mutations/mutations-insertions-table.tsx +33 -0
- package/src/preact/mutations/mutations-table.tsx +47 -0
- package/src/preact/mutations/mutations.stories.tsx +95 -0
- package/src/preact/mutations/mutations.tsx +192 -0
- package/src/preact/mutations/queryMutations.ts +55 -0
- package/src/preact/prevalenceOverTime/__mockData__/denominator.json +1700 -0
- package/src/preact/prevalenceOverTime/__mockData__/denominatorOneVariant.json +608 -0
- package/src/preact/prevalenceOverTime/__mockData__/numeratorEG.json +1560 -0
- package/src/preact/prevalenceOverTime/__mockData__/numeratorJN1.json +592 -0
- package/src/preact/prevalenceOverTime/__mockData__/numeratorOneVariant.json +604 -0
- package/src/preact/prevalenceOverTime/getPrevalenceOverTimeTableData.spec.ts +67 -0
- package/src/preact/prevalenceOverTime/getPrevalenceOverTimeTableData.ts +18 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +105 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +86 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +141 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time-table.tsx +46 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +165 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +202 -0
- package/src/preact/relativeGrowthAdvantage/__mockData__/denominator.json +376 -0
- package/src/preact/relativeGrowthAdvantage/__mockData__/numerator.json +332 -0
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +138 -0
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +71 -0
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +136 -0
- package/src/preact/shared/charts/LogitScale.ts +48 -0
- package/src/preact/shared/charts/colors.ts +26 -0
- package/src/preact/shared/charts/confideceInterval.ts +29 -0
- package/src/preact/shared/charts/getYAxisScale.ts +16 -0
- package/src/preact/shared/charts/scales.ts +16 -0
- package/src/preact/shared/icons/DeleteIcon.tsx +17 -0
- package/src/preact/shared/sort/sortInsertions.spec.ts +47 -0
- package/src/preact/shared/sort/sortInsertions.ts +21 -0
- package/src/preact/shared/sort/sortMutationPositions.spec.ts +31 -0
- package/src/preact/shared/sort/sortMutationPositions.ts +14 -0
- package/src/preact/shared/sort/sortSubstitutionsAndDeletions.spec.ts +47 -0
- package/src/preact/shared/sort/sortSubstitutionsAndDeletions.ts +17 -0
- package/src/preact/shared/table/formatProportion.ts +3 -0
- package/src/preact/textInput/__mockData__/aggregated_hosts.json +24 -0
- package/src/preact/textInput/fetchAutocompleteList.ts +9 -0
- package/src/preact/textInput/text-input.stories.tsx +49 -0
- package/src/preact/textInput/text-input.tsx +73 -0
- package/src/preact/useQuery.ts +27 -0
- package/src/query/queryInsertions.ts +14 -0
- package/src/query/queryPrevalenceOverTime.ts +126 -0
- package/src/query/queryRelativeGrowthAdvantage.ts +131 -0
- package/src/query/querySubstitutionsOrDeletions.ts +19 -0
- package/src/styles/tailwind.css +3 -0
- package/src/styles/tailwind.d.ts +3 -0
- package/src/types.ts +23 -0
- package/src/utils/mutations.spec.ts +64 -0
- package/src/utils/mutations.ts +165 -0
- package/src/utils/temporal.spec.ts +97 -0
- package/src/utils/temporal.ts +348 -0
- package/src/utils/test-utils.ts +5 -0
- package/src/utils/type-utils.ts +15 -0
- package/src/utils/utils.spec.ts +16 -0
- package/src/utils/utils.ts +38 -0
- package/src/web-components/PreactLitAdapter.tsx +62 -0
- package/src/web-components/PreactLitAdapterWithGridJsStyles.tsx +12 -0
- package/src/web-components/app.ts +51 -0
- package/src/web-components/display/index.ts +4 -0
- package/src/web-components/display/mutation-comparison-component.stories.ts +138 -0
- package/src/web-components/display/mutation-comparison-component.tsx +31 -0
- package/src/web-components/display/mutations-component.stories.ts +107 -0
- package/src/web-components/display/mutations-component.tsx +27 -0
- package/src/web-components/display/prevalence-over-time-component.stories.ts +205 -0
- package/src/web-components/display/prevalence-over-time-component.tsx +46 -0
- package/src/web-components/display/relative-growth-advantage-component.stories.ts +89 -0
- package/src/web-components/display/relative-growth-advantage-component.tsx +37 -0
- package/src/web-components/index.ts +3 -0
- package/src/web-components/input/date-range-selector-component.stories.ts +53 -0
- package/src/web-components/input/date-range-selector-component.tsx +33 -0
- package/src/web-components/input/index.ts +4 -0
- package/src/web-components/input/location-filter-component.stories.ts +184 -0
- package/src/web-components/input/location-filter-component.tsx +68 -0
- package/src/web-components/input/location-filter.mdx +25 -0
- package/src/web-components/input/mutation-filter-component.stories.ts +97 -0
- package/src/web-components/input/mutation-filter-component.tsx +27 -0
- package/src/web-components/input/text-input-component.stories.ts +92 -0
- package/src/web-components/input/text-input-component.tsx +30 -0
- package/src/web-components/lapis-context.ts +3 -0
- package/src/web-components/reference-genome-context.ts +5 -0
- package/src/web-components/withinShadowRoot.story.ts +34 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { FetchAggregatedOperator } from '../operator/FetchAggregatedOperator';
|
|
2
|
+
import { MapOperator } from '../operator/MapOperator';
|
|
3
|
+
import { type LapisFilter } from '../types';
|
|
4
|
+
import { getMinMaxTemporal, TemporalCache, type YearMonthDay } from '../utils/temporal';
|
|
5
|
+
|
|
6
|
+
export type RelativeGrowthAdvantageData = Awaited<ReturnType<typeof queryRelativeGrowthAdvantage>>;
|
|
7
|
+
|
|
8
|
+
export async function queryRelativeGrowthAdvantage(
|
|
9
|
+
numerator: LapisFilter,
|
|
10
|
+
denominator: LapisFilter,
|
|
11
|
+
generationTime: number,
|
|
12
|
+
lapis: string,
|
|
13
|
+
signal?: AbortSignal,
|
|
14
|
+
) {
|
|
15
|
+
const fetchNumerator = new FetchAggregatedOperator<{
|
|
16
|
+
date: string | null;
|
|
17
|
+
}>(numerator, ['date']);
|
|
18
|
+
const fetchDenominator = new FetchAggregatedOperator<{
|
|
19
|
+
date: string | null;
|
|
20
|
+
}>(denominator, ['date']);
|
|
21
|
+
const mapNumerator = new MapOperator(fetchNumerator, toYearMonthDay);
|
|
22
|
+
const mapDenominator = new MapOperator(fetchDenominator, toYearMonthDay);
|
|
23
|
+
const [numeratorData, denominatorData] = await Promise.all([
|
|
24
|
+
mapNumerator.evaluate(lapis, signal),
|
|
25
|
+
mapDenominator.evaluate(lapis, signal),
|
|
26
|
+
]);
|
|
27
|
+
const minMaxDate = getMinMaxTemporal(denominatorData.content.map((d) => d.date));
|
|
28
|
+
if (!minMaxDate) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const [minDate, maxDate] = minMaxDate as [YearMonthDay, YearMonthDay];
|
|
32
|
+
const numeratorCounts = new Map<YearMonthDay, number>();
|
|
33
|
+
numeratorData.content.forEach((d) => {
|
|
34
|
+
if (d.date) {
|
|
35
|
+
numeratorCounts.set(d.date, d.count);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
const denominatorCounts = new Map<YearMonthDay, number>();
|
|
39
|
+
const requestData = {
|
|
40
|
+
t: [] as number[],
|
|
41
|
+
n: [] as number[],
|
|
42
|
+
k: [] as number[],
|
|
43
|
+
};
|
|
44
|
+
denominatorData.content.forEach((d) => {
|
|
45
|
+
if (d.date) {
|
|
46
|
+
denominatorCounts.set(d.date, d.count);
|
|
47
|
+
const t = d.date.minus(minDate);
|
|
48
|
+
requestData.t.push(t);
|
|
49
|
+
requestData.n.push(d.count);
|
|
50
|
+
requestData.k.push(numeratorCounts.get(d.date) ?? 0);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
const requestPayload = {
|
|
54
|
+
config: {
|
|
55
|
+
alpha: 0.95,
|
|
56
|
+
generationTime,
|
|
57
|
+
initialCasesVariant: 1,
|
|
58
|
+
initialCasesWildtype: 1,
|
|
59
|
+
reproductionNumberWildtype: 1,
|
|
60
|
+
tStart: 0,
|
|
61
|
+
tEnd: maxDate.minus(minDate),
|
|
62
|
+
},
|
|
63
|
+
data: requestData,
|
|
64
|
+
};
|
|
65
|
+
const response = await fetch('https://cov-spectrum.org/api/v2/computed/model/chen2021Fitness', {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: {
|
|
68
|
+
'Content-Type': 'application/json',
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify(requestPayload),
|
|
71
|
+
signal,
|
|
72
|
+
});
|
|
73
|
+
const responseData = (await response.json()) as {
|
|
74
|
+
estimatedAbsoluteNumbers: {
|
|
75
|
+
t: number[];
|
|
76
|
+
variantCases: number[];
|
|
77
|
+
wildtypeCases: number[];
|
|
78
|
+
};
|
|
79
|
+
estimatedProportions: {
|
|
80
|
+
t: number[];
|
|
81
|
+
proportion: number[];
|
|
82
|
+
ciLower: number[];
|
|
83
|
+
ciUpper: number[];
|
|
84
|
+
};
|
|
85
|
+
params: {
|
|
86
|
+
a: {
|
|
87
|
+
value: number;
|
|
88
|
+
ciLower: number;
|
|
89
|
+
ciUpper: number;
|
|
90
|
+
};
|
|
91
|
+
fc: {
|
|
92
|
+
value: number;
|
|
93
|
+
ciLower: number;
|
|
94
|
+
ciUpper: number;
|
|
95
|
+
};
|
|
96
|
+
fd: {
|
|
97
|
+
value: number;
|
|
98
|
+
ciLower: number;
|
|
99
|
+
ciUpper: number;
|
|
100
|
+
};
|
|
101
|
+
t0: {
|
|
102
|
+
value: number;
|
|
103
|
+
ciLower: number;
|
|
104
|
+
ciUpper: number;
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
const transformed = {
|
|
109
|
+
...responseData,
|
|
110
|
+
estimatedProportions: {
|
|
111
|
+
...responseData.estimatedProportions,
|
|
112
|
+
t: responseData.estimatedProportions.t.map((t) => minDate.addDays(t)),
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
const observedProportions = transformed.estimatedProportions.t.map(
|
|
116
|
+
(t) => (numeratorCounts.get(t) ?? 0) / (denominatorCounts.get(t) ?? 0),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
...transformed,
|
|
121
|
+
observedProportions,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function toYearMonthDay(d: { date: string | null; count: number }) {
|
|
126
|
+
const temporalCache = TemporalCache.getInstance();
|
|
127
|
+
return {
|
|
128
|
+
date: d.date ? temporalCache.getYearMonthDay(d.date) : null,
|
|
129
|
+
count: d.count,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FetchSubstitutionsOrDeletionsOperator } from '../operator/FetchSubstitutionsOrDeletionsOperator';
|
|
2
|
+
import { SortOperator } from '../operator/SortOperator';
|
|
3
|
+
import { type LapisFilter, type SequenceType } from '../types';
|
|
4
|
+
|
|
5
|
+
export function querySubstitutionsOrDeletions(
|
|
6
|
+
variant: LapisFilter,
|
|
7
|
+
sequenceType: SequenceType,
|
|
8
|
+
lapis: string,
|
|
9
|
+
signal?: AbortSignal,
|
|
10
|
+
) {
|
|
11
|
+
const fetchData = new FetchSubstitutionsOrDeletionsOperator(variant, sequenceType, 0);
|
|
12
|
+
const sortData = new SortOperator(fetchData, (a, b) => {
|
|
13
|
+
if (a.mutation.segment !== b.mutation.segment) {
|
|
14
|
+
return (a.mutation.segment ?? '').localeCompare(b.mutation.segment ?? '');
|
|
15
|
+
}
|
|
16
|
+
return a.mutation.position - b.mutation.position;
|
|
17
|
+
});
|
|
18
|
+
return sortData.evaluate(lapis, signal);
|
|
19
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Deletion, type Insertion, type Substitution } from './utils/mutations';
|
|
2
|
+
|
|
3
|
+
export type LapisFilter = Record<string, string | number | null | boolean>;
|
|
4
|
+
|
|
5
|
+
export type NamedLapisFilter = LapisFilter & { displayName: string };
|
|
6
|
+
|
|
7
|
+
export type TemporalGranularity = 'day' | 'week' | 'month' | 'year';
|
|
8
|
+
|
|
9
|
+
export type SequenceType = 'nucleotide' | 'amino acid';
|
|
10
|
+
|
|
11
|
+
export type SubstitutionOrDeletion = 'substitution' | 'deletion';
|
|
12
|
+
|
|
13
|
+
export type MutationType = SubstitutionOrDeletion | 'insertion';
|
|
14
|
+
|
|
15
|
+
export type SubstitutionEntry = { type: 'substitution'; mutation: Substitution; count: number; proportion: number };
|
|
16
|
+
|
|
17
|
+
export type DeletionEntry = { type: 'deletion'; mutation: Deletion; count: number; proportion: number };
|
|
18
|
+
|
|
19
|
+
export type InsertionEntry = { type: 'insertion'; mutation: Insertion; count: number };
|
|
20
|
+
|
|
21
|
+
export type SubstitutionOrDeletionEntry = SubstitutionEntry | DeletionEntry;
|
|
22
|
+
|
|
23
|
+
export type MutationEntry = SubstitutionEntry | DeletionEntry | InsertionEntry;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { Deletion, Insertion, Substitution } from './mutations';
|
|
4
|
+
|
|
5
|
+
describe('Substitution', () => {
|
|
6
|
+
it('should be parsed from string', () => {
|
|
7
|
+
expect(Substitution.parse('A1T')).deep.equal(new Substitution(undefined, 'A', 'T', 1));
|
|
8
|
+
expect(Substitution.parse('seg1:A1T')).deep.equal(new Substitution('seg1', 'A', 'T', 1));
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should render to string correctly', () => {
|
|
12
|
+
const substitutions = [
|
|
13
|
+
{
|
|
14
|
+
substitution: new Substitution(undefined, 'A', 'T', 1),
|
|
15
|
+
expected: 'A1T',
|
|
16
|
+
},
|
|
17
|
+
{ substitution: new Substitution('segment', 'A', 'T', 1), expected: 'segment:A1T' },
|
|
18
|
+
{ substitution: new Substitution(undefined, undefined, undefined, 1), expected: '1' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
for (const { substitution, expected } of substitutions) {
|
|
22
|
+
expect(substitution.toString()).to.equal(expected);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('Deletion', () => {
|
|
28
|
+
it('should be parsed from string', () => {
|
|
29
|
+
expect(Deletion.parse('A1-')).deep.equal(new Deletion(undefined, 'A', 1));
|
|
30
|
+
expect(Deletion.parse('seg1:A1-')).deep.equal(new Deletion('seg1', 'A', 1));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should render to string correctly', () => {
|
|
34
|
+
const substitutions = [
|
|
35
|
+
{
|
|
36
|
+
deletion: new Deletion(undefined, 'A', 1),
|
|
37
|
+
expected: 'A1-',
|
|
38
|
+
},
|
|
39
|
+
{ deletion: new Deletion('segment', 'A', 1), expected: 'segment:A1-' },
|
|
40
|
+
{ deletion: new Deletion(undefined, undefined, 1), expected: '1-' },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
for (const { deletion, expected } of substitutions) {
|
|
44
|
+
expect(deletion.toString()).to.equal(expected);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('Insertion', () => {
|
|
50
|
+
it('should be parsed from string', () => {
|
|
51
|
+
expect(Insertion.parse('ins_1:A')).deep.equal(new Insertion(undefined, 1, 'A'));
|
|
52
|
+
expect(Insertion.parse('ins_seg1:1:A')).deep.equal(new Insertion('seg1', 1, 'A'));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should be parsed with case insensitive ins prefix', () => {
|
|
56
|
+
expect(Insertion.parse('INS_1:A')).deep.equal(new Insertion(undefined, 1, 'A'));
|
|
57
|
+
expect(Insertion.parse('iNs_1:A')).deep.equal(new Insertion(undefined, 1, 'A'));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should be parsed with the other parts not case insensitive', () => {
|
|
61
|
+
expect(Insertion.parse('ins_geNe1:1:A')).deep.equal(new Insertion('geNe1', 1, 'A'));
|
|
62
|
+
expect(Insertion.parse('ins_1:aA')).deep.equal(new Insertion(undefined, 1, 'aA'));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { type SequenceType } from '../types';
|
|
2
|
+
|
|
3
|
+
export interface Mutation {
|
|
4
|
+
readonly segment: string | undefined;
|
|
5
|
+
readonly position: number;
|
|
6
|
+
readonly code: string;
|
|
7
|
+
|
|
8
|
+
equals(other: Mutation): boolean;
|
|
9
|
+
|
|
10
|
+
toString(): string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const substitutionRegex =
|
|
14
|
+
/^((?<segment>[A-Za-z0-9_-]+)(?=:):)?(?<valueAtReference>[A-Za-z])?(?<position>\d+)(?<substitutionValue>[A-Za-z.])?$/;
|
|
15
|
+
|
|
16
|
+
export class Substitution implements Mutation {
|
|
17
|
+
readonly code;
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
readonly segment: string | undefined,
|
|
21
|
+
readonly valueAtReference: string | undefined,
|
|
22
|
+
readonly substitutionValue: string | undefined,
|
|
23
|
+
readonly position: number,
|
|
24
|
+
) {
|
|
25
|
+
const segmentString = this.segment ? `${this.segment}:` : '';
|
|
26
|
+
const valueAtReferenceString = this.valueAtReference ? `${this.valueAtReference}` : '';
|
|
27
|
+
const substitutionValueString = this.substitutionValue ? `${this.substitutionValue}` : '';
|
|
28
|
+
this.code = `${segmentString}${valueAtReferenceString}${this.position}${substitutionValueString}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
equals(other: Mutation): boolean {
|
|
32
|
+
if (!(other instanceof Substitution)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return (
|
|
36
|
+
this.segment === other.segment &&
|
|
37
|
+
this.valueAtReference === other.valueAtReference &&
|
|
38
|
+
this.substitutionValue === other.substitutionValue &&
|
|
39
|
+
this.position === other.position
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
toString() {
|
|
44
|
+
return this.code;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static parse(mutationStr: string): Substitution | null {
|
|
48
|
+
const match = mutationStr.match(substitutionRegex);
|
|
49
|
+
if (match === null || match.groups === undefined) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return new Substitution(
|
|
53
|
+
match.groups.segment,
|
|
54
|
+
match.groups.valueAtReference,
|
|
55
|
+
match.groups.substitutionValue,
|
|
56
|
+
parseInt(match.groups.position, 10),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const deletionRegex = /^((?<segment>[A-Za-z0-9_-]+)(?=:):)?(?<valueAtReference>[A-Za-z])?(?<position>\d+)(-)$/;
|
|
62
|
+
|
|
63
|
+
export class Deletion implements Mutation {
|
|
64
|
+
readonly code;
|
|
65
|
+
|
|
66
|
+
constructor(
|
|
67
|
+
readonly segment: string | undefined,
|
|
68
|
+
readonly valueAtReference: string | undefined,
|
|
69
|
+
readonly position: number,
|
|
70
|
+
) {
|
|
71
|
+
const segmentString = this.segment ? `${this.segment}:` : '';
|
|
72
|
+
const valueAtReferenceString = this.valueAtReference ? `${this.valueAtReference}` : '';
|
|
73
|
+
this.code = `${segmentString}${valueAtReferenceString}${this.position}-`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
equals(other: Mutation): boolean {
|
|
77
|
+
if (!(other instanceof Deletion)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
return (
|
|
81
|
+
this.segment === other.segment &&
|
|
82
|
+
this.valueAtReference === other.valueAtReference &&
|
|
83
|
+
this.position === other.position
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
toString() {
|
|
88
|
+
return this.code;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
static parse(mutationStr: string): Deletion | null {
|
|
92
|
+
const match = mutationStr.match(deletionRegex);
|
|
93
|
+
if (match === null || match.groups === undefined) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return new Deletion(match.groups.segment, match.groups.valueAtReference, parseInt(match.groups.position, 10));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const insertionRegexp =
|
|
102
|
+
/^ins_((?<segment>[A-Za-z0-9_-]+)(?=:):)?(?<position>\d+):(?<insertedSymbols>(([A-Za-z?]|(\.\*))+))$/i;
|
|
103
|
+
|
|
104
|
+
export class Insertion implements Mutation {
|
|
105
|
+
readonly code;
|
|
106
|
+
|
|
107
|
+
constructor(
|
|
108
|
+
readonly segment: string | undefined,
|
|
109
|
+
readonly position: number,
|
|
110
|
+
readonly insertedSymbols: string,
|
|
111
|
+
) {
|
|
112
|
+
this.code = `ins_${this.segment ? `${this.segment}:` : ''}${this.position}:${this.insertedSymbols}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
equals(other: Mutation): boolean {
|
|
116
|
+
if (!(other instanceof Insertion)) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return (
|
|
120
|
+
this.segment === other.segment &&
|
|
121
|
+
this.insertedSymbols === other.insertedSymbols &&
|
|
122
|
+
this.position === other.position
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
toString() {
|
|
127
|
+
return this.code;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static parse(mutationStr: string): Insertion | null {
|
|
131
|
+
const match = mutationStr.match(insertionRegexp);
|
|
132
|
+
if (match === null || match.groups === undefined) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return new Insertion(match.groups.segment, parseInt(match.groups.position, 10), match.groups.insertedSymbols);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export const bases: { [P in SequenceType]: string[] } = {
|
|
141
|
+
nucleotide: ['A', 'C', 'G', 'T', '-'],
|
|
142
|
+
'amino acid': [
|
|
143
|
+
'I',
|
|
144
|
+
'L',
|
|
145
|
+
'V',
|
|
146
|
+
'F',
|
|
147
|
+
'M',
|
|
148
|
+
'C',
|
|
149
|
+
'A',
|
|
150
|
+
'G',
|
|
151
|
+
'P',
|
|
152
|
+
'T',
|
|
153
|
+
'S',
|
|
154
|
+
'Y',
|
|
155
|
+
'W',
|
|
156
|
+
'Q',
|
|
157
|
+
'N',
|
|
158
|
+
'H',
|
|
159
|
+
'E',
|
|
160
|
+
'D',
|
|
161
|
+
'K',
|
|
162
|
+
'R',
|
|
163
|
+
'-',
|
|
164
|
+
],
|
|
165
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
generateAllDaysInRange,
|
|
5
|
+
generateAllMonthsInRange,
|
|
6
|
+
generateAllYearsInRange,
|
|
7
|
+
TemporalCache,
|
|
8
|
+
Year,
|
|
9
|
+
YearMonth,
|
|
10
|
+
YearMonthDay,
|
|
11
|
+
YearWeek,
|
|
12
|
+
} from './temporal';
|
|
13
|
+
|
|
14
|
+
const cache = TemporalCache.getInstance();
|
|
15
|
+
|
|
16
|
+
describe('generateAllDaysInRange', () => {
|
|
17
|
+
it('should return all days in range', () => {
|
|
18
|
+
expect(
|
|
19
|
+
generateAllDaysInRange(YearMonthDay.parse('2020-01-01', cache), YearMonthDay.parse('2020-01-04', cache)),
|
|
20
|
+
).deep.equal([
|
|
21
|
+
YearMonthDay.parse('2020-01-01', cache),
|
|
22
|
+
YearMonthDay.parse('2020-01-02', cache),
|
|
23
|
+
YearMonthDay.parse('2020-01-03', cache),
|
|
24
|
+
YearMonthDay.parse('2020-01-04', cache),
|
|
25
|
+
]);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('generateAllMonthsInRange', () => {
|
|
30
|
+
it('should return all months in range', () => {
|
|
31
|
+
expect(
|
|
32
|
+
generateAllMonthsInRange(YearMonth.parse('2020-01', cache), YearMonth.parse('2020-04', cache)),
|
|
33
|
+
).deep.equal([
|
|
34
|
+
YearMonth.parse('2020-01', cache),
|
|
35
|
+
YearMonth.parse('2020-02', cache),
|
|
36
|
+
YearMonth.parse('2020-03', cache),
|
|
37
|
+
YearMonth.parse('2020-04', cache),
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('generateAllYearsInRange', () => {
|
|
43
|
+
it('should return all years in range', () => {
|
|
44
|
+
expect(generateAllYearsInRange(Year.parse('2020-01', cache), Year.parse('2023', cache))).deep.equal([
|
|
45
|
+
Year.parse('2020', cache),
|
|
46
|
+
Year.parse('2021', cache),
|
|
47
|
+
Year.parse('2022', cache),
|
|
48
|
+
Year.parse('2023', cache),
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('YearMonthDay', () => {
|
|
54
|
+
it('should parse from string', () => {
|
|
55
|
+
const underTest = YearMonthDay.parse('2020-01-01', cache);
|
|
56
|
+
|
|
57
|
+
expect(underTest.yearNumber).equal(2020);
|
|
58
|
+
expect(underTest.monthNumber).equal(1);
|
|
59
|
+
expect(underTest.dayNumber).equal(1);
|
|
60
|
+
// seems to be a bug in dayjs: https://github.com/iamkun/dayjs/issues/2620
|
|
61
|
+
expect(underTest.week.text).equal('2019-01');
|
|
62
|
+
expect(underTest.text).equal('2020-01-01');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('YearWeek', () => {
|
|
67
|
+
it('should parse from string', () => {
|
|
68
|
+
const underTest = YearWeek.parse('2020-02', cache);
|
|
69
|
+
|
|
70
|
+
expect(underTest.isoYearNumber).equal(2020);
|
|
71
|
+
expect(underTest.isoWeekNumber).equal(2);
|
|
72
|
+
expect(underTest.firstDay.text).equal('2020-01-06');
|
|
73
|
+
expect(underTest.text).equal('2020-02');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('YearMonth', () => {
|
|
78
|
+
it('should parse from string', () => {
|
|
79
|
+
const underTest = YearMonth.parse('2020-01', cache);
|
|
80
|
+
|
|
81
|
+
expect(underTest.yearNumber).equal(2020);
|
|
82
|
+
expect(underTest.monthNumber).equal(1);
|
|
83
|
+
expect(underTest.text).equal('2020-01');
|
|
84
|
+
expect(underTest.firstDay.text).equal('2020-01-01');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('Year', () => {
|
|
89
|
+
it('should parse from string', () => {
|
|
90
|
+
const underTest = Year.parse('2020', cache);
|
|
91
|
+
|
|
92
|
+
expect(underTest.year).equal(2020);
|
|
93
|
+
expect(underTest.text).equal('2020');
|
|
94
|
+
expect(underTest.firstDay.text).equal('2020-01-01');
|
|
95
|
+
expect(underTest.firstMonth.text).equal('2020-01');
|
|
96
|
+
});
|
|
97
|
+
});
|