Skip to content

Commit 33b08ca

Browse files
authored
Merge pull request #147 from algolia/feature/explain-decompounding
Feature/explain decompounding
2 parents 3cc735a + cb41f92 commit 33b08ca

25 files changed

+545
-87
lines changed

src/commonMain/kotlin/com/algolia/search/dsl/DSLQuery.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.algolia.search.dsl
22

3+
import com.algolia.search.dsl.advanced.DSLExplainModules
34
import com.algolia.search.dsl.advanced.DSLResponseFields
45
import com.algolia.search.dsl.attributes.DSLAttributes
56
import com.algolia.search.dsl.attributes.DSLAttributesSet
@@ -170,4 +171,11 @@ public fun Query.analyticsTags(block: DSLStrings.() -> Unit) {
170171
*/
171172
public fun Query.responseFields(block: DSLResponseFields.() -> Unit) {
172173
responseFields = DSLResponseFields(block)
174+
}
175+
176+
/**
177+
* Assign the output of [block] to [Query.explainModules].
178+
*/
179+
public fun Query.explainModules(block: DSLExplainModules.() -> Unit) {
180+
explainModules = DSLExplainModules(block)
173181
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.algolia.search.dsl.advanced
2+
3+
import com.algolia.search.dsl.DSL
4+
import com.algolia.search.dsl.DSLParameters
5+
import com.algolia.search.model.search.ExplainModule
6+
7+
8+
/**
9+
* DSL for building a [List] of [ExplainModule].
10+
*/
11+
@Suppress("PropertyName")
12+
@DSLParameters
13+
public class DSLExplainModules(
14+
private val explainModules: MutableList<ExplainModule> = mutableListOf()
15+
) {
16+
17+
public val MatchAlternatives = ExplainModule.MatchAlternatives
18+
/**
19+
* Add [this] to [explainModules].
20+
*/
21+
public operator fun ExplainModule.unaryPlus() {
22+
explainModules += this
23+
}
24+
25+
public companion object : DSL<DSLExplainModules, List<ExplainModule>> {
26+
27+
override operator fun invoke(block: DSLExplainModules.() -> Unit): List<ExplainModule> {
28+
return DSLExplainModules().apply(block).explainModules
29+
}
30+
}
31+
}

src/commonMain/kotlin/com/algolia/search/model/response/ResponseSearch.kt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.algolia.search.model.Attribute
55
import com.algolia.search.model.IndexName
66
import com.algolia.search.model.ObjectID
77
import com.algolia.search.model.QueryID
8+
import com.algolia.search.model.filter.FilterGroup
89
import com.algolia.search.model.insights.InsightsEvent
910
import com.algolia.search.model.search.*
1011
import com.algolia.search.model.settings.Settings
@@ -132,7 +133,7 @@ public data class ResponseSearch(
132133
@SerialName(KeyFacets) @Serializable(KSerializerFacetMap::class) val facetsOrNull: Map<Attribute, List<Facet>>? = null,
133134
/**
134135
* A mapping of each facet name to the corresponding facet counts for disjunctive facets.
135-
* Returned only by the [EndpointSearch.searchDisjunctiveFacets] method.
136+
* Returned only by the [EndpointSearch.advancedSearch] method.
136137
* [Documentation][https://www.algolia.com/doc/guides/building-search-ui/going-further/backend-search/how-to/faceting/?language=kotlin#conjunctive-and-disjunctive-faceting]
137138
*/
138139
@SerialName(KeyDisjunctiveFacets) @Serializable(KSerializerFacetMap::class) val disjunctiveFacetsOrNull: Map<Attribute, List<Facet>>? = null,
@@ -152,9 +153,14 @@ public data class ResponseSearch(
152153
*/
153154
@SerialName(KeyQueryID) val queryIDOrNull: QueryID? = null,
154155
/**
155-
*
156+
* A mapping of each facet name to the corresponding facet counts for hierarchical facets.
157+
* Returned only by the [EndpointSearch.advancedSearch] method, only if a [FilterGroup.And.Hierarchical] is used.
156158
*/
157-
@SerialName(KeyHierarchicalFacets) val hierarchicalFacetsOrNull: Map<Attribute, List<Facet>>? = null
159+
@SerialName(KeyHierarchicalFacets) val hierarchicalFacetsOrNull: Map<Attribute, List<Facet>>? = null,
160+
/**
161+
* Meta-information as to how the query was processed.
162+
*/
163+
@SerialName(KeyExplain) val explainOrNull: Explain? = null
158164
) {
159165

160166
public val hits: List<Hit>
@@ -244,6 +250,9 @@ public data class ResponseSearch(
244250
public val hierarchicalFacets: Map<Attribute, List<Facet>>
245251
get() = hierarchicalFacetsOrNull!!
246252

253+
public val explain: Explain
254+
get() = explainOrNull!!
255+
247256
@Deprecated(
248257
message = "Use getObjectPosition instead.",
249258
replaceWith = ReplaceWith("getObjectPosition(objectID)"),
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.algolia.search.model.search
2+
3+
import com.algolia.search.serialize.*
4+
import kotlinx.serialization.SerialName
5+
import kotlinx.serialization.Serializable
6+
7+
8+
@Serializable
9+
public data class Alternative(
10+
@SerialName(KeyTypes) val types: List<AlternativeType>,
11+
@SerialName(KeyWords) val words: List<String>,
12+
@SerialName(KeyTypos) val typos: Int,
13+
@SerialName(KeyOffset) val offset: Int,
14+
@SerialName(KeyLength) val length: Int
15+
)
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package com.algolia.search.model.search
2+
3+
import com.algolia.search.model.Raw
4+
import com.algolia.search.serialize.*
5+
import kotlinx.serialization.Decoder
6+
import kotlinx.serialization.Encoder
7+
import kotlinx.serialization.KSerializer
8+
import kotlinx.serialization.Serializable
9+
import kotlinx.serialization.internal.StringSerializer
10+
11+
/**
12+
* Type for [Alternative.type]
13+
*/
14+
@Serializable(AlternativeType.Companion::class)
15+
public sealed class AlternativeType(override val raw: String) : Raw<String> {
16+
17+
/**
18+
* Literal word from the query
19+
*/
20+
public object Original : AlternativeType(KeyOriginal)
21+
22+
/**
23+
* Original keywords that should not appear in the results because it had a “minus” sign at the beginning and
24+
* [Query.advancedSyntax] enabled.
25+
*/
26+
public object Excluded : AlternativeType(KeyExcluded)
27+
28+
/**
29+
* Original keywords that was declared in [Query.optionalWords].
30+
*/
31+
public object Optional : AlternativeType(KeyOptional)
32+
33+
/**
34+
* Original keywords that was discarded by [Query.removeStopWords].
35+
*/
36+
public object StopWord : AlternativeType(KeyStopWord)
37+
38+
/**
39+
* Alternative that mostly looks like another original keyword. “Mostly looks like” is defined by the
40+
* Damerau-Levenshtein distance between the two words, which counts 1 typo as any insertion, deletion, substitution,
41+
* or transposition of a single character. The field typos contains this number.
42+
* Because it would make no sense to display every combination of typos possible, the response only contains typos
43+
* that were found in the documents.
44+
*/
45+
public object Typo : AlternativeType(KeyTypo)
46+
47+
/**
48+
* Alternative that tries to split every original keyword into 2 valid sub-keywords.
49+
* As for typo, only sub-keywords that were found in the documents may return a split.
50+
* There is always 0 or 1 split per original keyword.
51+
*/
52+
public object Split : AlternativeType(KeySplit)
53+
54+
/**
55+
* Alternative that tries to build bigrams out of every adjacent pair of keywords (up to the 5th keyword),
56+
* and to build an n-gram out of all the words of the query (if it contains at least 3 words).
57+
*/
58+
public object Concat : AlternativeType(KeyConcat)
59+
60+
/**
61+
* Any of the following kinds of alternatives: one-way synonym, two-way synonym, n-way synonym.
62+
*/
63+
public object Synonym : AlternativeType(KeySynonym)
64+
65+
/**
66+
* Any of the following kinds of alternatives: one-way alternative correction, two-way alternative correction,
67+
* n-way alternative correction. The difference between a synonym and an alternative correction can also be seen
68+
* in the field typos, which will always be 0 in the case of a synonym and 1 or 2 in the case of an alternative
69+
* correction.
70+
*/
71+
public object AlternativeCorrection : AlternativeType(KeyAltcorrection)
72+
73+
/**
74+
* Any declension of the original keywords, including singular and plural, case
75+
* (nominative, accusative, genitive etc.), gender, and others depending on the language.
76+
* Every possible combination is returned, regardless of what the documents contain.
77+
*/
78+
public object Plural : AlternativeType(KeyPlural)
79+
80+
/**
81+
* Word made of several consecutive original query words.
82+
* It is like a concatenation,but based on a decompounding dictionary.
83+
*/
84+
public object Compound : AlternativeType(KeyCompound)
85+
86+
public data class Other(override val raw: String) : AlternativeType(raw)
87+
88+
companion object : KSerializer<AlternativeType> {
89+
90+
private val serializer = StringSerializer
91+
92+
override val descriptor = serializer.descriptor
93+
94+
override fun serialize(encoder: Encoder, obj: AlternativeType) {
95+
serializer.serialize(encoder, obj.raw)
96+
}
97+
98+
override fun deserialize(decoder: Decoder): AlternativeType {
99+
return when (val string = serializer.deserialize(decoder)) {
100+
KeyOriginal -> Original
101+
KeyExcluded -> Excluded
102+
KeyOptional -> Optional
103+
KeyStopWord -> StopWord
104+
KeyTypo -> Typo
105+
KeySplit -> Split
106+
KeyConcat -> Concat
107+
KeySynonym -> Synonym
108+
KeyAltcorrection -> AlternativeCorrection
109+
KeyPlural -> Plural
110+
KeyCompound -> Compound
111+
else -> Other(string)
112+
}
113+
}
114+
}
115+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.algolia.search.model.search
2+
3+
import com.algolia.search.serialize.KeyMatch
4+
import kotlinx.serialization.SerialName
5+
import kotlinx.serialization.Serializable
6+
7+
8+
@Serializable
9+
public data class Explain(
10+
@SerialName(KeyMatch) val match: Match
11+
)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.algolia.search.model.search
2+
3+
import com.algolia.search.model.Raw
4+
import com.algolia.search.serialize.KeyMatchAlternatives
5+
import kotlinx.serialization.Decoder
6+
import kotlinx.serialization.Encoder
7+
import kotlinx.serialization.KSerializer
8+
import kotlinx.serialization.Serializable
9+
import kotlinx.serialization.internal.StringSerializer
10+
11+
12+
@Serializable(ExplainModule.Companion::class)
13+
public sealed class ExplainModule(override val raw: String) : Raw<String> {
14+
15+
public object MatchAlternatives : ExplainModule(KeyMatchAlternatives)
16+
17+
public data class Other(override val raw: String) : ExplainModule(raw)
18+
19+
companion object : KSerializer<ExplainModule> {
20+
21+
private val serializer = StringSerializer
22+
23+
override val descriptor = serializer.descriptor
24+
25+
override fun serialize(encoder: Encoder, obj: ExplainModule) {
26+
serializer.serialize(encoder, obj.raw)
27+
}
28+
29+
override fun deserialize(decoder: Decoder): ExplainModule {
30+
return when (val string = serializer.deserialize(decoder)) {
31+
KeyMatchAlternatives -> MatchAlternatives
32+
else -> Other(string)
33+
}
34+
}
35+
}
36+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.algolia.search.model.search
2+
3+
import com.algolia.search.serialize.KeyAlternatives
4+
import kotlinx.serialization.SerialName
5+
import kotlinx.serialization.Serializable
6+
7+
8+
@Serializable
9+
public data class Match(
10+
@SerialName(KeyAlternatives) val alternatives: List<Alternative>
11+
)

src/commonMain/kotlin/com/algolia/search/model/search/Query.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,16 +456,26 @@ public data class Query(
456456
* [Documentation][https://www.algolia.com/doc/api-reference/api-parameters/percentileComputation/?language=kotlin]
457457
*/
458458
@SerialName(KeyPercentileComputation) var percentileComputation: Boolean? = null,
459+
459460
/**
460461
* Overrides the query parameter and performs a more generic search that can be used to find "similar" results.
461462
* Engine default: ""
462463
* [Documentation][https://www.algolia.com/doc/api-reference/api-parameters/similarQuery/?language=kotlin]
463464
*/
464465
@SerialName(KeySimilarQuery) var similarQuery: String? = null,
466+
465467
/**
466468
* Whether this query should be taken into consideration by currently active ABTests.
467469
* Engine default: true
468470
* [Documentation][https://www.algolia.com/doc/api-reference/api-parameters/enableABTest/?language=kotlin]
469471
*/
470-
@SerialName(KeyEnableABTest) var enableABTest: Boolean? = null
472+
@SerialName(KeyEnableABTest) var enableABTest: Boolean? = null,
473+
474+
/**
475+
* Enriches the API’s response with meta-information as to how the query was processed.
476+
* It is possible to enable several [ExplainModule] independently.
477+
* Engine default: null
478+
* [Documentation][https://www.algolia.com/doc/api-reference/api-parameters/decompoundedAttributes/?language=kotlin]
479+
*/
480+
@SerialName(KeyExplain) var explainModules: List<ExplainModule>? = null
471481
)

src/commonMain/kotlin/com/algolia/search/model/settings/DecompoundedAttributes.kt

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package com.algolia.search.model.settings
33
import com.algolia.search.model.Attribute
44
import com.algolia.search.model.search.Language
55
import kotlinx.serialization.*
6-
import kotlinx.serialization.internal.HashMapSerializer
6+
import kotlinx.serialization.internal.PairSerializer
77

88

99
/**
@@ -14,16 +14,12 @@ import kotlinx.serialization.internal.HashMapSerializer
1414
* The goal of decompounding, regarding the previous example, is to index both Baum and Haus separately,
1515
* nstead of as a single word.
1616
*/
17-
@Serializable(DecompoundedAttributes.Companion::class)
17+
@Serializable
1818
public data class DecompoundedAttributes internal constructor(
19-
val map: Map<Language, List<Attribute>>
19+
val language: Language,
20+
val attributes: List<Attribute>
2021
) {
2122

22-
internal constructor(
23-
language: Language,
24-
attributes: List<Attribute>
25-
) : this(mapOf(language to attributes.toList()))
26-
2723
public constructor(
2824
language: Language.German,
2925
vararg attributes: Attribute
@@ -38,19 +34,4 @@ public data class DecompoundedAttributes internal constructor(
3834
language: Language.Dutch,
3935
vararg attributes: Attribute
4036
) : this(language, attributes.toList())
41-
42-
companion object : KSerializer<DecompoundedAttributes> {
43-
44-
private val serializer = HashMapSerializer(Language, Attribute.list)
45-
46-
override val descriptor: SerialDescriptor = serializer.descriptor
47-
48-
override fun serialize(encoder: Encoder, obj: DecompoundedAttributes) {
49-
serializer.serialize(encoder, obj.map)
50-
}
51-
52-
override fun deserialize(decoder: Decoder): DecompoundedAttributes {
53-
return DecompoundedAttributes(serializer.deserialize(decoder))
54-
}
55-
}
5637
}

0 commit comments

Comments
 (0)