diff --git a/mcp-server/dashboard.html b/mcp-server/dashboard.html
index 927b224..f69a0b5 100644
--- a/mcp-server/dashboard.html
+++ b/mcp-server/dashboard.html
@@ -134,15 +134,20 @@ async function doSearch() {
const btn = document.getElementById('search-btn');
btn.disabled = true;
btn.textContent = 'Searching...';
- document.getElementById('results').innerHTML = '
Searching ' + (mode === 'crm' ? 'by keyword' : 'with AI') + '...
';
+ const searchLabel = effectiveMode === 'crm' ? 'by keyword' : effectiveMode === 'hybrid' ? 'with AI + filters' : 'with AI';
+ document.getElementById('results').innerHTML = 'Searching ' + searchLabel + '...
';
const t0 = Date.now();
+ // If ANY filter is set, always use hybrid — filters only work through SQL
+ const hasFilters = state || role || rel > 0.5;
+ const effectiveMode = (mode === 'crm') ? 'crm' : (hasFilters ? 'hybrid' : mode);
+
try {
let data;
- if (mode === 'crm') {
- // CRM keyword search — LIKE match
+ if (effectiveMode === 'crm') {
+ // CRM keyword search — exact LIKE match
let where = "resume_text LIKE '%" + query.replace(/'/g, "''") + "%'";
if (state) where += " AND state = '" + state + "'";
if (role) where += " AND role = '" + role + "'";
@@ -154,8 +159,8 @@ async function doSearch() {
data = await r.json();
renderCRMResults(data, query, Date.now() - t0);
- } else if (mode === 'ai') {
- // Pure AI vector search
+ } else if (effectiveMode === 'ai') {
+ // Pure AI vector search — no filters, just meaning
const r = await fetch(GW + '/api/vectors/hnsw/search', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({index_name: 'workers_500k_v1', query: query, top_k: 10})
@@ -164,7 +169,7 @@ async function doSearch() {
renderAIResults(data, query, Date.now() - t0);
} else {
- // Hybrid — SQL filter + AI ranking
+ // Hybrid — SQL filters enforce structure, AI ranks by relevance
let filter = "CAST(reliability AS DOUBLE) >= " + rel;
if (state) filter += " AND state = '" + state + "'";
if (role) filter += " AND role = '" + role + "'";