Skip to content

Commit

Permalink
feat: add feature isSingleSegmentedSequence
Browse files Browse the repository at this point in the history
- if enabled the returned value of the insertions can be in the format ins_<position>:<insertion>
- if not enabled the returned value is in the format ins_<segment>:<position>:<insertion>
  • Loading branch information
JonasKellerer committed Sep 18, 2023
1 parent bb3358e commit a90e331
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ package org.genspectrum.lapis.config

typealias SequenceFilterFieldName = String

const val SARS_COV2_VARIANT_QUERY_FEATURE = "sarsCoV2VariantQuery"

val FEATURES_FOR_SEQUENCE_FILTERS = listOf(
SARS_COV2_VARIANT_QUERY_FEATURE,
)

data class SequenceFilterFields(val fields: Map<SequenceFilterFieldName, SequenceFilterFieldType>) {
companion object {
fun fromDatabaseConfig(databaseConfig: DatabaseConfig): SequenceFilterFields {
Expand All @@ -13,7 +19,9 @@ data class SequenceFilterFields(val fields: Map<SequenceFilterFieldName, Sequenc
val featuresFields = if (databaseConfig.schema.features.isEmpty()) {
emptyMap<SequenceFilterFieldName, SequenceFilterFieldType>()
} else {
databaseConfig.schema.features.associate(::mapToSequenceFilterFieldsFromFeatures)
databaseConfig.schema.features
.filter { it.name in FEATURES_FOR_SEQUENCE_FILTERS }
.associate(::mapToSequenceFilterFieldsFromFeatures)
}

return SequenceFilterFields(fields = metadataFields + featuresFields)
Expand All @@ -29,22 +37,25 @@ private fun mapToSequenceFilterFields(databaseMetadata: DatabaseMetadata) = when
"${databaseMetadata.name}From" to SequenceFilterFieldType.DateFrom(databaseMetadata.name),
"${databaseMetadata.name}To" to SequenceFilterFieldType.DateTo(databaseMetadata.name),
)

MetadataType.INT -> listOf(
databaseMetadata.name to SequenceFilterFieldType.Int,
"${databaseMetadata.name}From" to SequenceFilterFieldType.IntFrom(databaseMetadata.name),
"${databaseMetadata.name}To" to SequenceFilterFieldType.IntTo(databaseMetadata.name),
)

MetadataType.FLOAT -> listOf(
databaseMetadata.name to SequenceFilterFieldType.Float,
"${databaseMetadata.name}From" to SequenceFilterFieldType.FloatFrom(databaseMetadata.name),
"${databaseMetadata.name}To" to SequenceFilterFieldType.FloatTo(databaseMetadata.name),
)

MetadataType.NUCLEOTIDE_INSERTION -> emptyList()
MetadataType.AMINO_ACID_INSERTION -> emptyList()
}

private fun mapToSequenceFilterFieldsFromFeatures(databaseFeature: DatabaseFeature) = when (databaseFeature.name) {
"sarsCoV2VariantQuery" -> "variantQuery" to SequenceFilterFieldType.VariantQuery
SARS_COV2_VARIANT_QUERY_FEATURE -> "variantQuery" to SequenceFilterFieldType.VariantQuery
else -> throw IllegalArgumentException(
"Unknown feature '${databaseFeature.name}'",
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.genspectrum.lapis.config

import org.springframework.stereotype.Component

const val IS_SEQUENCE_SINGLE_SEGMENTED_FEATURE = "isSingleSegmentedSequence"

@Component
class SingleSegmentedSequenceFeature(private val databaseConfig: DatabaseConfig) {
fun isEnabled() =
databaseConfig.schema.features.any { it.name == IS_SEQUENCE_SINGLE_SEGMENTED_FEATURE }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.genspectrum.lapis.model

import org.genspectrum.lapis.config.SingleSegmentedSequenceFeature
import org.genspectrum.lapis.request.InsertionsRequest
import org.genspectrum.lapis.request.MutationProportionsRequest
import org.genspectrum.lapis.request.SequenceFiltersRequestWithFields
Expand All @@ -17,6 +18,7 @@ import org.springframework.stereotype.Component
class SiloQueryModel(
private val siloClient: SiloClient,
private val siloFilterExpressionMapper: SiloFilterExpressionMapper,
private val singleSegmentedSequenceFeature: SingleSegmentedSequenceFeature,
) {

fun getAggregated(sequenceFilters: SequenceFiltersRequestWithFields) = siloClient.sendQuery(
Expand Down Expand Up @@ -46,14 +48,11 @@ class SiloQueryModel(
),
)
return data.map { it ->
val sequenceName =
if (singleSegmentedSequenceFeature.isEnabled()) it.mutation else "${it.sequenceName}:${it.mutation}"

NucleotideMutationResponse(
if (
it.sequenceName == "main"
) {
it.mutation
} else {
it.sequenceName + ":" + it.mutation
},
sequenceName,
it.count,
it.proportion,
)
Expand All @@ -76,7 +75,7 @@ class SiloQueryModel(
)
return data.map { it ->
AminoAcidMutationResponse(
it.sequenceName + ":" + it.mutation,
"${it.sequenceName}:${it.mutation}",
it.count,
it.proportion,
)
Expand Down Expand Up @@ -109,10 +108,7 @@ class SiloQueryModel(
)

return data.map { it ->
val sequenceName = when (it.sequenceName) {
"main" -> ""
else -> "${it.sequenceName}:"
}
val sequenceName = if (singleSegmentedSequenceFeature.isEnabled()) "" else "${it.sequenceName}:"

NucleotideInsertionResponse(
"ins_${sequenceName}${it.position}:${it.insertions}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ class DatabaseConfigTest {
assertThat(
underTest.schema.features,
containsInAnyOrder(
DatabaseFeature(name = "sarsCoV2VariantQuery"),
DatabaseFeature(name = SARS_COV2_VARIANT_QUERY_FEATURE),
DatabaseFeature(name = IS_SEQUENCE_SINGLE_SEGMENTED_FEATURE),
),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class SequenceFilterFieldsTest {

@Test
fun `given database config with a feature of 'sarsCoV2VariantQuery' then contains variantQuery`() {
val input = databaseConfigWithFields(emptyList(), listOf(DatabaseFeature("sarsCoV2VariantQuery")))
val input = databaseConfigWithFields(emptyList(), listOf(DatabaseFeature(SARS_COV2_VARIANT_QUERY_FEATURE)))

val underTest = SequenceFilterFields.fromDatabaseConfig(input)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.genspectrum.lapis.config

import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class SingleSegmentedSequenceFeatureTest {
@Test
fun `given a databaseConfig with a feature named isSingleSegmentedSequence then isEnabled returns true`() {
val input = databaseConfigWithFeatures(listOf(DatabaseFeature(IS_SEQUENCE_SINGLE_SEGMENTED_FEATURE)))

val underTest = SingleSegmentedSequenceFeature(input)

assertTrue(underTest.isEnabled())
}

@Test
fun `given a databaseConfig without a feature named isSingleSegmentedSequence then isEnabled returns false`() {
val input = databaseConfigWithFeatures(listOf(DatabaseFeature("notTheRightFeature")))

val underTest = SingleSegmentedSequenceFeature(input)

assertFalse(underTest.isEnabled())
}

private fun databaseConfigWithFeatures(
databaseFeatures: List<DatabaseFeature> = emptyList(),
) = DatabaseConfig(
DatabaseSchema(
"test config",
OpennessLevel.OPEN,
emptyList(),
"test primary key",
databaseFeatures,
),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.verify
import org.genspectrum.lapis.config.SingleSegmentedSequenceFeature
import org.genspectrum.lapis.request.CommonSequenceFilters
import org.genspectrum.lapis.request.InsertionsRequest
import org.genspectrum.lapis.request.MutationProportionsRequest
Expand All @@ -28,20 +29,25 @@ class SiloQueryModelTest {
@MockK
lateinit var siloClientMock: SiloClient

@MockK
lateinit var singleSegmentedSequenceFeatureMock: SingleSegmentedSequenceFeature

@MockK
lateinit var siloFilterExpressionMapperMock: SiloFilterExpressionMapper

private lateinit var underTest: SiloQueryModel

@BeforeEach
fun setup() {
MockKAnnotations.init(this)
underTest = SiloQueryModel(siloClientMock, siloFilterExpressionMapperMock)
underTest = SiloQueryModel(siloClientMock, siloFilterExpressionMapperMock, singleSegmentedSequenceFeatureMock)
}

@Test
fun `aggregate calls the SILO client with an aggregated action`() {
every { siloClientMock.sendQuery(any<SiloQuery<List<AggregationData>>>()) } returns emptyList()
every { siloFilterExpressionMapperMock.map(any<CommonSequenceFilters>()) } returns True
every { singleSegmentedSequenceFeatureMock.isEnabled() } returns true

underTest.getAggregated(
SequenceFiltersRequestWithFields(
Expand All @@ -65,6 +71,7 @@ class SiloQueryModelTest {
fun `computeNucleotideMutationProportions calls the SILO client with a mutations action`() {
every { siloClientMock.sendQuery(any<SiloQuery<List<MutationData>>>()) } returns emptyList()
every { siloFilterExpressionMapperMock.map(any<CommonSequenceFilters>()) } returns True
every { singleSegmentedSequenceFeatureMock.isEnabled() } returns true

underTest.computeNucleotideMutationProportions(
MutationProportionsRequest(emptyMap(), emptyList(), emptyList(), emptyList(), emptyList(), 0.5),
Expand All @@ -78,11 +85,12 @@ class SiloQueryModelTest {
}

@Test
fun `computeNucleotideMutationProportions ignores the field sequenceName if it is called main`() {
fun `computeNucleotideMutationProportions ignores the segmentName if singleSegmentedSequenceFeature is enabled`() {
every { siloClientMock.sendQuery(any<SiloQuery<List<MutationData>>>()) } returns listOf(
MutationData("A1234B", 1234, 0.1234, "main"),
MutationData("A1234B", 1234, 0.1234, "someSequenceName"),
)
every { siloFilterExpressionMapperMock.map(any<CommonSequenceFilters>()) } returns True
every { singleSegmentedSequenceFeatureMock.isEnabled() } returns true

val result = underTest.computeNucleotideMutationProportions(
MutationProportionsRequest(emptyMap(), emptyList(), emptyList(), emptyList(), emptyList()),
Expand All @@ -92,17 +100,18 @@ class SiloQueryModelTest {
}

@Test
fun `computeNucleotideMutationProportions include the field sequenceName if it is not called main`() {
fun `computeNucleotideMutationProportions includes segmentName if singleSegmentedSequenceFeature is not enabled`() {
every { siloClientMock.sendQuery(any<SiloQuery<List<MutationData>>>()) } returns listOf(
MutationData("A1234B", 1234, 0.1234, "NotMain"),
MutationData("A1234B", 1234, 0.1234, "someSegmentName"),
)
every { siloFilterExpressionMapperMock.map(any<CommonSequenceFilters>()) } returns True
every { singleSegmentedSequenceFeatureMock.isEnabled() } returns false

val result = underTest.computeNucleotideMutationProportions(
MutationProportionsRequest(emptyMap(), emptyList(), emptyList(), emptyList(), emptyList()),
)

assertThat(result, equalTo(listOf(NucleotideMutationResponse("NotMain:A1234B", 1234, 0.1234))))
assertThat(result, equalTo(listOf(NucleotideMutationResponse("someSegmentName:A1234B", 1234, 0.1234))))
}

@Test
Expand All @@ -120,11 +129,12 @@ class SiloQueryModelTest {
}

@Test
fun `getNucleotideInsertions ignores the field sequenceName if it is called main`() {
fun `getNucleotideInsertions ignores the field sequenceName if if singleSegmentedSequenceFeature is enabled`() {
every { siloClientMock.sendQuery(any<SiloQuery<List<InsertionData>>>()) } returns listOf(
InsertionData(42, "ABCD", 1234, "main"),
InsertionData(42, "ABCD", 1234, "someSequenceName"),
)
every { siloFilterExpressionMapperMock.map(any<CommonSequenceFilters>()) } returns True
every { singleSegmentedSequenceFeatureMock.isEnabled() } returns true

val result = underTest.getNucleotideInsertions(
InsertionsRequest(
Expand All @@ -141,11 +151,12 @@ class SiloQueryModelTest {
}

@Test
fun `getNucleotideInsertions includes the field sequenceName if it is not called main`() {
fun `getNucleotideInsertions includes the field sequenceName if singleSegmentedSequenceFeature is not enabled`() {
every { siloClientMock.sendQuery(any<SiloQuery<List<InsertionData>>>()) } returns listOf(
InsertionData(42, "ABCD", 1234, "notMain"),
InsertionData(42, "ABCD", 1234, "someSequenceName"),
)
every { siloFilterExpressionMapperMock.map(any<CommonSequenceFilters>()) } returns True
every { singleSegmentedSequenceFeatureMock.isEnabled() } returns false

val result = underTest.getNucleotideInsertions(
InsertionsRequest(
Expand All @@ -158,7 +169,7 @@ class SiloQueryModelTest {
),
)

assertThat(result, equalTo(listOf(NucleotideInsertionResponse("ins_notMain:1234:ABCD", 42))))
assertThat(result, equalTo(listOf(NucleotideInsertionResponse("ins_someSequenceName:1234:ABCD", 42))))
}

@Test
Expand Down
1 change: 1 addition & 0 deletions lapis2/src/test/resources/config/testDatabaseConfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ schema:
type: aaInsertion
features:
- name: sarsCoV2VariantQuery
- name: isSingleSegmentedSequence
primaryKey: gisaid_epi_isl
3 changes: 1 addition & 2 deletions siloLapisTests/test/aggregated.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,9 @@ describe('The /aggregated endpoint', () => {

expect(result.status).equals(200);
const resultJson = await result.json();
expect(resultJson.data[0]).to.have.property('count', 1);
expect(resultJson.data[0]).to.have.property('count', 0);
});


it('should correctly handle nucleotide insertion requests in GET requests', async () => {
const urlParams = new URLSearchParams({
nucleotideInsertions: 'ins_25701:CC?,ins_5959:?AT',
Expand Down
1 change: 1 addition & 0 deletions siloLapisTests/testData/testDatabaseConfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ schema:
type: aaInsertion
features:
- name: sarsCoV2VariantQuery
- name: isSingleSegmentedSequence
primaryKey: gisaid_epi_isl
dateToSortBy: date
partitionBy: pango_lineage

0 comments on commit a90e331

Please sign in to comment.