From a90e3312dd742e789885495c74aef5643683f4dc Mon Sep 17 00:00:00 2001 From: Jonas Kellerer Date: Fri, 15 Sep 2023 11:15:58 +0200 Subject: [PATCH] feat: add feature isSingleSegmentedSequence - if enabled the returned value of the insertions can be in the format ins_: - if not enabled the returned value is in the format ins_:: --- .../lapis/config/SequenceFilterFields.kt | 15 +++++++- .../config/SingleSegmentedSequenceFeature.kt | 11 ++++++ .../genspectrum/lapis/model/SiloQueryModel.kt | 20 ++++------ .../lapis/config/DatabaseConfigTest.kt | 3 +- .../lapis/config/SequenceFilterFieldsTest.kt | 2 +- .../SingleSegmentedSequenceFeatureTest.kt | 37 +++++++++++++++++++ .../lapis/model/SiloQueryModelTest.kt | 33 +++++++++++------ .../resources/config/testDatabaseConfig.yaml | 1 + siloLapisTests/test/aggregated.spec.ts | 3 +- .../testData/testDatabaseConfig.yaml | 1 + 10 files changed, 97 insertions(+), 29 deletions(-) create mode 100644 lapis2/src/main/kotlin/org/genspectrum/lapis/config/SingleSegmentedSequenceFeature.kt create mode 100644 lapis2/src/test/kotlin/org/genspectrum/lapis/config/SingleSegmentedSequenceFeatureTest.kt diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/config/SequenceFilterFields.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/config/SequenceFilterFields.kt index 6d4c5b54..74e5dfa6 100644 --- a/lapis2/src/main/kotlin/org/genspectrum/lapis/config/SequenceFilterFields.kt +++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/config/SequenceFilterFields.kt @@ -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) { companion object { fun fromDatabaseConfig(databaseConfig: DatabaseConfig): SequenceFilterFields { @@ -13,7 +19,9 @@ data class SequenceFilterFields(val fields: Map() } else { - databaseConfig.schema.features.associate(::mapToSequenceFilterFieldsFromFeatures) + databaseConfig.schema.features + .filter { it.name in FEATURES_FOR_SEQUENCE_FILTERS } + .associate(::mapToSequenceFilterFieldsFromFeatures) } return SequenceFilterFields(fields = metadataFields + featuresFields) @@ -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}'", ) diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/config/SingleSegmentedSequenceFeature.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/config/SingleSegmentedSequenceFeature.kt new file mode 100644 index 00000000..a02427bf --- /dev/null +++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/config/SingleSegmentedSequenceFeature.kt @@ -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 } +} diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/model/SiloQueryModel.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/model/SiloQueryModel.kt index e23a3b96..1abb1249 100644 --- a/lapis2/src/main/kotlin/org/genspectrum/lapis/model/SiloQueryModel.kt +++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/model/SiloQueryModel.kt @@ -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 @@ -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( @@ -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, ) @@ -76,7 +75,7 @@ class SiloQueryModel( ) return data.map { it -> AminoAcidMutationResponse( - it.sequenceName + ":" + it.mutation, + "${it.sequenceName}:${it.mutation}", it.count, it.proportion, ) @@ -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}", diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/config/DatabaseConfigTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/config/DatabaseConfigTest.kt index 5f73e36c..24c5baeb 100644 --- a/lapis2/src/test/kotlin/org/genspectrum/lapis/config/DatabaseConfigTest.kt +++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/config/DatabaseConfigTest.kt @@ -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), ), ) } diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/config/SequenceFilterFieldsTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/config/SequenceFilterFieldsTest.kt index e2943b19..9e15f52a 100644 --- a/lapis2/src/test/kotlin/org/genspectrum/lapis/config/SequenceFilterFieldsTest.kt +++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/config/SequenceFilterFieldsTest.kt @@ -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) diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/config/SingleSegmentedSequenceFeatureTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/config/SingleSegmentedSequenceFeatureTest.kt new file mode 100644 index 00000000..c7448293 --- /dev/null +++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/config/SingleSegmentedSequenceFeatureTest.kt @@ -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 = emptyList(), + ) = DatabaseConfig( + DatabaseSchema( + "test config", + OpennessLevel.OPEN, + emptyList(), + "test primary key", + databaseFeatures, + ), + ) +} diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/model/SiloQueryModelTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/model/SiloQueryModelTest.kt index dcb4404e..6b58cfc9 100644 --- a/lapis2/src/test/kotlin/org/genspectrum/lapis/model/SiloQueryModelTest.kt +++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/model/SiloQueryModelTest.kt @@ -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 @@ -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>>()) } returns emptyList() every { siloFilterExpressionMapperMock.map(any()) } returns True + every { singleSegmentedSequenceFeatureMock.isEnabled() } returns true underTest.getAggregated( SequenceFiltersRequestWithFields( @@ -65,6 +71,7 @@ class SiloQueryModelTest { fun `computeNucleotideMutationProportions calls the SILO client with a mutations action`() { every { siloClientMock.sendQuery(any>>()) } returns emptyList() every { siloFilterExpressionMapperMock.map(any()) } returns True + every { singleSegmentedSequenceFeatureMock.isEnabled() } returns true underTest.computeNucleotideMutationProportions( MutationProportionsRequest(emptyMap(), emptyList(), emptyList(), emptyList(), emptyList(), 0.5), @@ -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>>()) } returns listOf( - MutationData("A1234B", 1234, 0.1234, "main"), + MutationData("A1234B", 1234, 0.1234, "someSequenceName"), ) every { siloFilterExpressionMapperMock.map(any()) } returns True + every { singleSegmentedSequenceFeatureMock.isEnabled() } returns true val result = underTest.computeNucleotideMutationProportions( MutationProportionsRequest(emptyMap(), emptyList(), emptyList(), emptyList(), emptyList()), @@ -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>>()) } returns listOf( - MutationData("A1234B", 1234, 0.1234, "NotMain"), + MutationData("A1234B", 1234, 0.1234, "someSegmentName"), ) every { siloFilterExpressionMapperMock.map(any()) } 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 @@ -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>>()) } returns listOf( - InsertionData(42, "ABCD", 1234, "main"), + InsertionData(42, "ABCD", 1234, "someSequenceName"), ) every { siloFilterExpressionMapperMock.map(any()) } returns True + every { singleSegmentedSequenceFeatureMock.isEnabled() } returns true val result = underTest.getNucleotideInsertions( InsertionsRequest( @@ -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>>()) } returns listOf( - InsertionData(42, "ABCD", 1234, "notMain"), + InsertionData(42, "ABCD", 1234, "someSequenceName"), ) every { siloFilterExpressionMapperMock.map(any()) } returns True + every { singleSegmentedSequenceFeatureMock.isEnabled() } returns false val result = underTest.getNucleotideInsertions( InsertionsRequest( @@ -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 diff --git a/lapis2/src/test/resources/config/testDatabaseConfig.yaml b/lapis2/src/test/resources/config/testDatabaseConfig.yaml index aa56983f..7cd74046 100644 --- a/lapis2/src/test/resources/config/testDatabaseConfig.yaml +++ b/lapis2/src/test/resources/config/testDatabaseConfig.yaml @@ -18,4 +18,5 @@ schema: type: aaInsertion features: - name: sarsCoV2VariantQuery + - name: isSingleSegmentedSequence primaryKey: gisaid_epi_isl diff --git a/siloLapisTests/test/aggregated.spec.ts b/siloLapisTests/test/aggregated.spec.ts index 7bb97368..6491d0e1 100644 --- a/siloLapisTests/test/aggregated.spec.ts +++ b/siloLapisTests/test/aggregated.spec.ts @@ -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', diff --git a/siloLapisTests/testData/testDatabaseConfig.yaml b/siloLapisTests/testData/testDatabaseConfig.yaml index dd23ce92..c9729d21 100644 --- a/siloLapisTests/testData/testDatabaseConfig.yaml +++ b/siloLapisTests/testData/testDatabaseConfig.yaml @@ -27,6 +27,7 @@ schema: type: aaInsertion features: - name: sarsCoV2VariantQuery + - name: isSingleSegmentedSequence primaryKey: gisaid_epi_isl dateToSortBy: date partitionBy: pango_lineage