Keyword search matches strings. Semantic search matches meanings. Codes, names, and part numbers want strings. Concepts and paraphrases want vectors. Neither one understands negation, which is why production search is almost always a blend.
The keyword side is real BM25 over the 24 docs: term frequency, inverse document frequency, and length normalization (k1 = 1.2, b = 0.75). Codes like SKU-4417 stay whole, the way a product index keeps them.
The semantic side is a toy embedding, labeled as one. About 120 hand-mapped words load onto 12 named concept axes. A doc vector is the idf-weighted mean of its word vectors, and results rank by cosine. Real embedding models learn hundreds or thousands of unnamed dimensions from data, so treat the axes here as a readable stand-in, not a real model.
The blend scales each engine's scores so its best result is 1, then mixes them with weight w. REL tags mark the docs we labeled as the right answers for each preset, and the verdict compares where each engine ranks them.