CombinationSearchMatcher

Pure, UI-free matcher for the combination / cross-reference search feature.

A query may combine a CONTAINER token-group (a building — or, at a higher layer, a category) with one or more CONTAINED-ENTITY tokens. This class answers the sdk-core half of the problem:

  1. tokenize a raw query (lowercase, NFKD diacritic-strip, split on whitespace/punctuation) into Tokens, classifying each as numeric or text.

  2. scoreContainer a candidate container token-group against a building's CMS NAME WORDS, returning the best of four GENERIC tiers (plus exact) — derived from the name at call time.

  3. partition a tokenized query against a building-name map into token-order-independent {container tokens} + {remaining entity tokens} candidates, one per matching building.

GENERIC, NO maintained list. The biz/bizz -> Business case is reached by the phonetic-PREFIX tier (Metaphone.isPrefixCode) — there is deliberately NO {biz,bizz}->business dictionary in this class. The only explicit-alias path in the product is the existing CMS keyword mechanism (folded into a SearchEntity's keywords), which is a data entry, not code here.

The entity-scoping leg (matching the remaining tokens to entities in the matched building, on name AND short-name) is performed by the UI layer using the existing SearchFilterHelpers.filterSearchableByMatchType / SearchFilterHelpers.filterSearchableByMatchTypeOnMetadata ordering; this class only decides WHICH building(s) are containers and WHICH tokens remain for that entity leg.

Types

Link copied to clipboard
data class Candidate<K>(val key: K, val nameWords: List<String>, val aliasWords: List<String> = emptyList())

A candidate container = a stable key (buildingId or category name) + its CMS name words.

Link copied to clipboard
data class CombinationMatch(val buildingId: Int, val containerTokens: List<String>, val entityTokens: List<String>, val tier: CombinationSearchMatcher.ContainerTier, val containerScore: Double)

One container/entity partition: a building whose name matched containerTokens, with the remaining entityTokens to be scoped to that building's entities by the UI leg.

Link copied to clipboard

Result of scoring a container token-group against a building's name words.

Link copied to clipboard

Container match tiers, highest tier-weight first.

Link copied to clipboard
data class Match<K>(val key: K, val score: Double, val coverage: Double, val entityTokens: List<String>)

One engaged candidate from match: its key, the normalized container score, the residual entityTokens to scope to it in the UI leg, and the coverage (fraction of the candidate's name words the container tokens consumed) used as the secondary sort key.

Link copied to clipboard
data class Token(val text: String)

A classified query token.

Properties

Link copied to clipboard

Threshold for the consolidated match entry point: a token whose best tier score is at or above this counts as a container token for that candidate. Equals the lowest fuzzy tier score (PHONETIC_PREFIX), so any tier firing engages.

Link copied to clipboard
const val JARO_THRESHOLD: Double = 0.82

Minimum Jaro for the bounded-JARO tier. Rejects biznes(0.686)/biz(0.639)/bizz(0.583).

Functions

Link copied to clipboard

Engagement guard: the combination leg may only run when the query has at least two tokens AND at least one of them is a container candidate (a text token). A lone token, or a query of purely numeric tokens, never engages — those fall through to plain unified search.

Link copied to clipboard

Derives the GENERIC name-words for a candidate set: a (lowercased) name-word that is SHARED by at least two candidates AND appears in MORE THAN HALF of the candidate names is generic (e.g. "center"/"centre" = 5/5 across the VERIFIED M-University buildings). A name-word's contribution is counted ONCE per candidate (presence, not multiplicity), so a word repeated within one name does not inflate its share.

Link copied to clipboard

THE single consolidated entry point both lanes agree on. Tokenizes query, computes the genericWords across candidates, scores each candidate by best-tier-per-token with the length floors AND the DISTINCTIVE-ANCHOR requirement, partitions the tokens into container vs entity, and returns the engaged Matches ordered by score DESC then coverage DESC.

Link copied to clipboard

Partitions a tokenized query into combination candidates against buildingNameWords (buildingId -> lowercased CMS name words). For each building that is a container match, the tokens consumed by the container group are removed and the REMAINING text/numeric tokens become that match's CombinationMatch.entityTokens.

Link copied to clipboard

Scores a candidate container token-group containerTokens (already lowercased) against a building's nameWords (the CMS long name — and any folded short-name words — split into lowercased words). Returns the best ContainerMatch, or null if the group does not clear any tier.

Link copied to clipboard

Tokenizes query: lowercases, strips diacritics (NFKD), and splits on any run of whitespace or punctuation. Empty tokens are dropped. Result preserves left-to-right order for stability, but downstream partitioning is token-order independent.