Skip to content

Commit 85d0d82

Browse files
feat: implement amino acid mutations in advanced variant queries
issue: #287 This also fixed nucleotide mutations: When no "second symbol" is given, it should result in a "has mutation" filter
1 parent f33c0ff commit 85d0d82

File tree

3 files changed

+92
-40
lines changed

3 files changed

+92
-40
lines changed

lapis2/src/main/antlr/org/genspectrum/lapis/model/variantqueryparser/VariantQuery.g4

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ nOfExprs: expr (',' expr)*;
4848
nucleotideInsertionQuery: insertionKeyword position ':' (possibleAmbiguousNucleotideSymbol | '?')+;
4949
insertionKeyword: 'ins_' | 'INS_';
5050

51-
aaMutationQuery: gene ':' aaSymbol? position possibleAmbiguousAaSymbol?;
51+
aaMutationQuery: gene ':' aaSymbol? position possiblyAmbiguousAaSymbol?;
5252
aaSymbol: A | R | N | D | C | E | Q | G | H | I | L | K | M | F | P | S | T | W | Y | V | ASTERISK;
5353
ambiguousAaSymbol: X | MINUS | DOT;
54-
possibleAmbiguousAaSymbol: aaSymbol | ambiguousAaSymbol;
54+
possiblyAmbiguousAaSymbol: aaSymbol | ambiguousAaSymbol;
5555
gene: covidGene;
5656
covidGene : E | M | N | S | ORF;
5757

58-
aaInsertionQuery: insertionKeyword gene ':' position ':' (possibleAmbiguousAaSymbol | '?')+;
58+
aaInsertionQuery: insertionKeyword gene ':' position ':' (possiblyAmbiguousAaSymbol | '?')+;
5959

6060
nextcladePangolineageQuery: nextcladePangoLineagePrefix pangolineageQuery;
6161
nextcladePangoLineagePrefix: 'nextcladePangoLineage:' | 'NEXTCLADEPANGOLINEAGE:';

lapis2/src/main/kotlin/org/genspectrum/lapis/model/VariantQueryCustomListener.kt

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import VariantQueryParser.NucleotideMutationQueryContext
1515
import VariantQueryParser.OrContext
1616
import VariantQueryParser.PangolineageQueryContext
1717
import org.antlr.v4.runtime.tree.ParseTreeListener
18+
import org.genspectrum.lapis.silo.AminoAcidSymbolEquals
1819
import org.genspectrum.lapis.silo.And
20+
import org.genspectrum.lapis.silo.HasAminoAcidMutation
21+
import org.genspectrum.lapis.silo.HasNucleotideMutation
1922
import org.genspectrum.lapis.silo.Maybe
2023
import org.genspectrum.lapis.silo.NOf
2124
import org.genspectrum.lapis.silo.Not
@@ -36,10 +39,13 @@ class VariantQueryCustomListener : VariantQueryBaseListener(), ParseTreeListener
3639
return
3740
}
3841
val position = ctx.position().text.toInt()
39-
val secondSymbol = ctx.nucleotideMutationQuerySecondSymbol()?.text ?: "-"
4042

41-
val expr = NucleotideSymbolEquals(null, position, secondSymbol)
42-
expressionStack.addLast(expr)
43+
val expression = when (val secondSymbol = ctx.nucleotideMutationQuerySecondSymbol()) {
44+
null -> HasNucleotideMutation(null, position)
45+
else -> NucleotideSymbolEquals(null, position, secondSymbol.text)
46+
}
47+
48+
expressionStack.addLast(expression)
4349
}
4450

4551
override fun enterPangolineageQuery(ctx: PangolineageQueryContext?) {
@@ -94,7 +100,17 @@ class VariantQueryCustomListener : VariantQueryBaseListener(), ParseTreeListener
94100
}
95101

96102
override fun enterAaMutationQuery(ctx: AaMutationQueryContext?) {
97-
throw SiloNotImplementedError("Amino acid mutations are not supported yet.", NotImplementedError())
103+
if (ctx == null) {
104+
return
105+
}
106+
val position = ctx.position().text.toInt()
107+
108+
val expression = when (val aaSymbol = ctx.possiblyAmbiguousAaSymbol()) {
109+
null -> HasAminoAcidMutation(ctx.gene().text, position)
110+
else -> AminoAcidSymbolEquals(ctx.gene().text, position, aaSymbol.text)
111+
}
112+
113+
expressionStack.addLast(expression)
98114
}
99115

100116
override fun enterAaInsertionQuery(ctx: AaInsertionQueryContext?) {

lapis2/src/test/kotlin/org/genspectrum/lapis/model/VariantQueryFacadeTest.kt

Lines changed: 69 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package org.genspectrum.lapis.model
22

3+
import org.genspectrum.lapis.silo.AminoAcidSymbolEquals
34
import org.genspectrum.lapis.silo.And
5+
import org.genspectrum.lapis.silo.HasAminoAcidMutation
6+
import org.genspectrum.lapis.silo.HasNucleotideMutation
47
import org.genspectrum.lapis.silo.Maybe
58
import org.genspectrum.lapis.silo.NOf
69
import org.genspectrum.lapis.silo.Not
710
import org.genspectrum.lapis.silo.NucleotideSymbolEquals
811
import org.genspectrum.lapis.silo.Or
912
import org.genspectrum.lapis.silo.PangoLineageEquals
10-
import org.hamcrest.MatcherAssert
11-
import org.hamcrest.Matchers
13+
import org.hamcrest.MatcherAssert.assertThat
14+
import org.hamcrest.Matchers.equalTo
1215
import org.junit.jupiter.api.BeforeEach
1316
import org.junit.jupiter.api.Test
1417
import org.junit.jupiter.api.assertThrows
@@ -47,7 +50,7 @@ class VariantQueryFacadeTest {
4750
),
4851
),
4952
),
50-
Not(NucleotideSymbolEquals(null, 600, "-")),
53+
Not(HasNucleotideMutation(null, 600)),
5154
),
5255
),
5356
Maybe(
@@ -75,7 +78,7 @@ class VariantQueryFacadeTest {
7578
),
7679
)
7780

78-
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
81+
assertThat(result, equalTo(expectedResult))
7982
}
8083

8184
@Test
@@ -85,12 +88,21 @@ class VariantQueryFacadeTest {
8588
val result = underTest.map(variantQuery)
8689

8790
val expectedResult = NucleotideSymbolEquals(null, 300, "G")
88-
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
91+
assertThat(result, equalTo(expectedResult))
92+
}
93+
94+
@Test
95+
fun `given a variantQuery with mutation with position only then should return HasNucleotideMutation filter`() {
96+
val variantQuery = "400"
97+
98+
val result = underTest.map(variantQuery)
99+
100+
assertThat(result, equalTo(HasNucleotideMutation(null, 400)))
89101
}
90102

91103
@Test
92104
fun `given a variantQuery with an 'And' expression then map should return the corresponding SiloQuery`() {
93-
val variantQuery = "300G & 400"
105+
val variantQuery = "300G & 400-"
94106

95107
val result = underTest.map(variantQuery)
96108

@@ -100,7 +112,7 @@ class VariantQueryFacadeTest {
100112
NucleotideSymbolEquals(null, 400, "-"),
101113
),
102114
)
103-
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
115+
assertThat(result, equalTo(expectedResult))
104116
}
105117

106118
@Test
@@ -120,7 +132,7 @@ class VariantQueryFacadeTest {
120132
NucleotideSymbolEquals(null, 500, "B"),
121133
),
122134
)
123-
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
135+
assertThat(result, equalTo(expectedResult))
124136
}
125137

126138
@Test
@@ -130,12 +142,12 @@ class VariantQueryFacadeTest {
130142
val result = underTest.map(variantQuery)
131143

132144
val expectedResult = Not(NucleotideSymbolEquals(null, 300, "G"))
133-
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
145+
assertThat(result, equalTo(expectedResult))
134146
}
135147

136148
@Test
137149
fun `given a variant variantQuery with an 'Or' expression then map should return the corresponding SiloQuery`() {
138-
val variantQuery = "300G | 400"
150+
val variantQuery = "300G | 400-"
139151

140152
val result = underTest.map(variantQuery)
141153

@@ -145,7 +157,7 @@ class VariantQueryFacadeTest {
145157
NucleotideSymbolEquals(null, 400, "-"),
146158
),
147159
)
148-
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
160+
assertThat(result, equalTo(expectedResult))
149161
}
150162

151163
@Test
@@ -165,7 +177,7 @@ class VariantQueryFacadeTest {
165177
),
166178
),
167179
)
168-
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
180+
assertThat(result, equalTo(expectedResult))
169181
}
170182

171183
@Test
@@ -175,7 +187,7 @@ class VariantQueryFacadeTest {
175187
val result = underTest.map(variantQuery)
176188

177189
val expectedResult = Maybe(NucleotideSymbolEquals(null, 300, "G"))
178-
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
190+
assertThat(result, equalTo(expectedResult))
179191
}
180192

181193
@Test
@@ -185,7 +197,7 @@ class VariantQueryFacadeTest {
185197
val result = underTest.map(variantQuery)
186198

187199
val expectedResult = PangoLineageEquals("pango_lineage", "A.1.2.3", false)
188-
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
200+
assertThat(result, equalTo(expectedResult))
189201
}
190202

191203
@Test
@@ -195,7 +207,7 @@ class VariantQueryFacadeTest {
195207
val result = underTest.map(variantQuery)
196208

197209
val expectedResult = PangoLineageEquals("pango_lineage", "A.1.2.3", true)
198-
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
210+
assertThat(result, equalTo(expectedResult))
199211
}
200212

201213
@Test
@@ -213,7 +225,7 @@ class VariantQueryFacadeTest {
213225
NucleotideSymbolEquals(null, 345, "G"),
214226
),
215227
)
216-
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
228+
assertThat(result, equalTo(expectedResult))
217229
}
218230

219231
@Test
@@ -231,7 +243,7 @@ class VariantQueryFacadeTest {
231243
NucleotideSymbolEquals(null, 345, "G"),
232244
),
233245
)
234-
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
246+
assertThat(result, equalTo(expectedResult))
235247
}
236248

237249
@Test
@@ -240,22 +252,46 @@ class VariantQueryFacadeTest {
240252

241253
val exception = assertThrows<SiloNotImplementedError> { underTest.map(variantQuery) }
242254

243-
MatcherAssert.assertThat(
255+
assertThat(
244256
exception.message,
245-
Matchers.equalTo("Nucleotide insertions are not supported yet."),
257+
equalTo("Nucleotide insertions are not supported yet."),
246258
)
247259
}
248260

249261
@Test
250-
fun `given a variant variantQuery with a 'AA mutation' expression then map should throw an error`() {
262+
fun `given amino acidAA mutation expression then should map to AminoAcidSymbolEquals`() {
251263
val variantQuery = "S:N501Y"
252264

253-
val exception = assertThrows<SiloNotImplementedError> { underTest.map(variantQuery) }
265+
val result = underTest.map(variantQuery)
254266

255-
MatcherAssert.assertThat(
256-
exception.message,
257-
Matchers.equalTo("Amino acid mutations are not supported yet."),
258-
)
267+
assertThat(result, equalTo(AminoAcidSymbolEquals("S", 501, "Y")))
268+
}
269+
270+
@Test
271+
fun `given amino acid mutation expression without first symbol then should map to AminoAcidSymbolEquals`() {
272+
val variantQuery = "S:501Y"
273+
274+
val result = underTest.map(variantQuery)
275+
276+
assertThat(result, equalTo(AminoAcidSymbolEquals("S", 501, "Y")))
277+
}
278+
279+
@Test
280+
fun `given amino acid mutation expression without second symbol then should return HasAminoAcidMutation`() {
281+
val variantQuery = "S:N501"
282+
283+
val result = underTest.map(variantQuery)
284+
285+
assertThat(result, equalTo(HasAminoAcidMutation("S", 501)))
286+
}
287+
288+
@Test
289+
fun `given amino acid mutation expression without any symbol then should return HasAminoAcidMutation`() {
290+
val variantQuery = "S:501"
291+
292+
val result = underTest.map(variantQuery)
293+
294+
assertThat(result, equalTo(HasAminoAcidMutation("S", 501)))
259295
}
260296

261297
@Test
@@ -264,9 +300,9 @@ class VariantQueryFacadeTest {
264300

265301
val exception = assertThrows<SiloNotImplementedError> { underTest.map(variantQuery) }
266302

267-
MatcherAssert.assertThat(
303+
assertThat(
268304
exception.message,
269-
Matchers.equalTo("Amino acid insertions are not supported yet."),
305+
equalTo("Amino acid insertions are not supported yet."),
270306
)
271307
}
272308

@@ -276,9 +312,9 @@ class VariantQueryFacadeTest {
276312

277313
val exception = assertThrows<SiloNotImplementedError> { underTest.map(variantQuery) }
278314

279-
MatcherAssert.assertThat(
315+
assertThat(
280316
exception.message,
281-
Matchers.equalTo("Nextclade pango lineages are not supported yet."),
317+
equalTo("Nextclade pango lineages are not supported yet."),
282318
)
283319
}
284320

@@ -288,9 +324,9 @@ class VariantQueryFacadeTest {
288324

289325
val exception = assertThrows<SiloNotImplementedError> { underTest.map(variantQuery) }
290326

291-
MatcherAssert.assertThat(
327+
assertThat(
292328
exception.message,
293-
Matchers.equalTo("Nextstrain clade lineages are not supported yet."),
329+
equalTo("Nextstrain clade lineages are not supported yet."),
294330
)
295331
}
296332

@@ -300,9 +336,9 @@ class VariantQueryFacadeTest {
300336

301337
val exception = assertThrows<SiloNotImplementedError> { underTest.map(variantQuery) }
302338

303-
MatcherAssert.assertThat(
339+
assertThat(
304340
exception.message,
305-
Matchers.equalTo("Gisaid clade lineages are not supported yet."),
341+
equalTo("Gisaid clade lineages are not supported yet."),
306342
)
307343
}
308344
}

0 commit comments

Comments
 (0)