match

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.

DISTINCTIVE-ANCHOR requirement (the GO-blocking generic-word guard): a candidate engages ONLY when at least one query container-token matches a NON-generic (distinctive) name-word of that candidate via some tier. Generic-word hits (e.g. a token that only prefixes the shared "Center") still count toward the container token set and the score, but cannot by THEMSELVES engage a candidate. Combined with the FUZZY_MIN_TOKEN_LEN floor this kills "s"/"b"/"ce"/"me"/"mu"/"cen"/"center"/"centre" while preserving distinctive anchors (business/medical/eng/uni/bc/mac).

The ViewModel calls this ONCE with buildings (key = buildingId) and ONCE with categories (key = category name); the entity-scoping leg (matching Match.entityTokens to entities in the engaged container, on name + folded short name) stays in the ViewModel, which also applies the intersection guard (drop a match whose scoped entity result is empty). This method is deliberately Android/JNI-free.