CROP
ProjectsParts ServicesCatalog

Manufacturer Aliases Integration Guide

This document explains how to integrate the manufacturer alias normalization system into the search autocomplete pipeline. The alias system enables queries...

Manufacturer Aliases Integration Guide

Overview

This document explains how to integrate the manufacturer alias normalization system into the search autocomplete pipeline. The alias system enables queries like "newh" to return "New Holland" results, fixing the gap in partial manufacturer name matching.

Problem Statement

Query "newh" returns empty results because:

  • Elasticsearch prefix/term matching looks for exact field values
  • "newh" doesn't match "New Holland" in the index
  • The autocomplete query (lines 1061-1130 in autocomplete-suggestions.ts) has no alias expansion

Solution Architecture

Components

  1. Alias Mapping (packages/shared-catalog/src/config/manufacturers.ts)

    • DEFAULT_ALIASES: Record mapping aliases to manufacturer info
    • normalizeManufacturerQuery(query): Convert alias to canonical name
    • asManufacturer(key): Get manufacturer info from alias or key
  2. Normalization Helpers (same module)

    • normalizeManufacturerQuery(query): Resolve aliases to canonical names
    • asManufacturer(key): Reverse lookup from alias/code/name to canonical object
  3. Tests

    • services/search/src/__tests__/manufacturer-aliases.test.ts: Unit tests
    • services/search/src/__tests__/routes/autocomplete/manufacturer-aliases-integration.test.ts (Optional): Integration tests

Alias Mapping Reference

New Holland Aliases

nh → New Holland
newh → New Holland
new-holland → New Holland
newholland → New Holland
new holland → New Holland

Other Manufacturers

mch, mchale → McHale
briggs, bgs, briggs-stratton → Briggs & Stratton
gp, great-plains → Great Plains
cc, clubcar, club-car → Club Car
kws, kawasaki → Kawasaki
ven, ventrac → Ventrac
ezt, ez-trail → EZ-Trail
hsy, hotsy → Hotsy
dio, dion, dion ag → Dion AG
amc, amco → AMCO
ht, harvesttec, harvest-tec → Harvest Tec
agb, ag-bag → Ag-Bag
hla → HLA

Integration into Autocomplete

Current Manufacturer Query (Lines 1061-1130)

// 4) Manufacturers aggregation with multi-field search
if (!skipExpensiveQueries && caps.manufacturer > 0) {
  const lowerQuery = query.toLowerCase();
  searches.push({ index: indexName, preference });
  searches.push({
    size: 0,
    _source: false,
    query: {
      bool: {
        should: [
          {
            term: {
              'manufacturer.code': {
                value: lowerQuery,
                boost: 10,
              },
            },
          },
          // ... other matches
        ],
      },
    },
    aggs: {
      manufacturers: {
        terms: {
          field: 'manufacturer.name.keyword',
          size: caps.manufacturer,
        },
      },
    },
  });
}

Enhanced Implementation

Add alias expansion to the manufacturer query:

import { normalizeManufacturerQuery } from '@crop/shared-catalog';

// 4) Manufacturers aggregation with multi-field search + alias expansion
if (!skipExpensiveQueries && caps.manufacturer > 0) {
  const lowerQuery = query.toLowerCase();

  // NEW: Check for manufacturer aliases
  const normalizedMfgNames = normalizeManufacturerQuery(lowerQuery);

  searches.push({ index: indexName, preference });
  searches.push({
    size: 0,
    _source: false,
    track_total_hits: false,
    timeout: '100ms',
    query: {
      bool: {
        should: [
          // 1. Exact manufacturer code match (highest priority)
          {
            term: {
              'manufacturer.code': {
                value: lowerQuery,
                boost: 10,
              },
            },
          },
          // 2. Exact name match
          {
            term: {
              'manufacturer.name.keyword': {
                value: query,
                boost: 5,
                case_insensitive: true,
              },
            },
          },
          // NEW: 2b. Alias expansion match (normalized manufacturer names)
          ...(normalizedMfgNames.length > 0
            ? [
                {
                  terms: {
                    'manufacturer.name.keyword': normalizedMfgNames,
                    boost: 4.5,
                  },
                },
              ]
            : []),
          // 3. Suggest analyzer match
          {
            match: {
              'manufacturer.name.suggest': {
                query: lowerQuery,
                minimum_should_match: '75%',
                boost: 3,
              },
            },
          },
          // 4. Ngram match
          {
            match: {
              'manufacturer.name.ngram': {
                query: lowerQuery,
                minimum_should_match: '50%',
                boost: 1,
              },
            },
          },
        ],
        minimum_should_match: 1,
        filter: lockFilters.length > 0 ? lockFilters : undefined,
      },
    },
    aggs: {
      manufacturers: {
        terms: {
          field: 'manufacturer.name.keyword',
          size: caps.manufacturer,
          shard_size: caps.manufacturer * 3,
          min_doc_count: 1,
          order: { _count: 'desc' },
        },
      },
    },
  });
}

Usage Examples

Example 1: Query "newh"

Before Integration:

Query: "newh"
Results: [] (empty)

After Integration:

Query: "newh"
Normalized: ["New Holland"]
Elasticsearch Query: terms { manufacturer.name: ["New Holland"] }
Results: [New Holland manufacturers]

Example 2: Query "nh"

Query: "nh"
Normalized: ["New Holland"]
Results: [New Holland manufacturers]

Example 3: Query "briggs"

Query: "briggs"
Normalized: ["Briggs & Stratton"]
Results: [Briggs & Stratton manufacturers]

Example 4: Query "hydraulic" (no manufacturer)

Query: "hydraulic"
Normalized: [] (no match)
Results: [proceed with normal matching]

Performance Considerations

Memory

  • DEFAULT_ALIASES map: ~2KB (50+ aliases)
  • Single query normalization: O(1) lookup time
  • No additional memory allocation needed

CPU

  • Alias lookup: < 0.05ms per query (1000 queries in < 50ms)
  • Negligible overhead compared to Elasticsearch round-trip

Network

  • No additional ES requests (alias expansion happens client-side)
  • Potentially fewer ES queries if alias match prevents fallback queries

Testing

Unit Tests

bun test src/__tests__/manufacturer-aliases.test.ts

Coverage:

  • ✅ New Holland aliases: newh, nh, new-holland, newholland, new holland
  • ✅ Other manufacturer aliases
  • ✅ Case insensitivity
  • ✅ Edge cases (empty, whitespace, unknown)
  • ✅ Performance (1000 queries < 50ms)

Manual Testing

Test Query 1: Partial alias

curl "http://localhost:3001/api/autocomplete?q=newh"

Expected: New Holland should appear in suggestions

Test Query 2: Code alias

curl "http://localhost:3001/api/autocomplete?q=nh"

Expected: New Holland should appear in suggestions

Test Query 3: Spaced alias

curl "http://localhost:3001/api/autocomplete?q=new%20holland"

Expected: New Holland should appear in suggestions

Test Query 4: Non-manufacturer word

curl "http://localhost:3001/api/autocomplete?q=hydraulic"

Expected: Normal search behavior (no alias expansion, but hydraulic results)

Implementation Checklist

  • Review manufacturer aliases in DEFAULT_ALIASES map
  • Add aliases for any missing manufacturers
  • Update autocomplete-suggestions.ts with alias expansion logic
  • Add import: import { normalizeManufacturerQuery } from '@crop/shared-catalog';
  • Update manufacturer query section (lines 1061-1130)
  • Run unit tests: bun test src/__tests__/manufacturer-aliases.test.ts
  • Run full test suite: bun test
  • Manual testing with real autocomplete queries
  • Deploy and monitor autocomplete suggestions

Configuration

Disabling Alias Expansion (if needed)

To temporarily disable alias expansion:

import { normalizeManufacturerQuery } from '@crop/shared-catalog';

const normalized = normalizeManufacturerQuery(query);
// Temporarily skip using `normalized` if you want to disable alias expansion

Adjusting Boost Values

The terms query boost (4.5 in the example) controls how much the alias match contributes to relevance:

{
  terms: {
    'manufacturer.name.keyword': normalizedMfgNames,
    boost: 4.5,  // Adjust this value
  },
}
  • Higher boost = more weight for alias matches
  • 4.5 balances alias matches with code/name matches
  • Adjust based on autocomplete quality metrics

Monitoring & Debugging

Enable Query Logging

export ES_LOG_QUERIES=true
bun run dev

Look for manufacturer aggregation queries to verify alias expansion is working.

Check Alias Resolution

import { DEFAULT_ALIASES, normalizeManufacturerQuery } from '@crop/shared-catalog';

// Check what aliases exist for a manufacturer
const aliases = Object.entries(DEFAULT_ALIASES)
	.filter(([_, info]) => info.name === "New Holland")
	.map(([alias]) => alias);
console.log(aliases); // ["nh", "newh", "new-holland", ...]

// Check how a query normalizes
const canonical = normalizeManufacturerQuery("newh");
console.log(canonical[0]); // "New Holland"

Future Enhancements

1. User-Defined Aliases

Allow API admins to add custom manufacturer aliases:

POST /api/admin/manufacturer-aliases
{ "alias": "holland", "manufacturerName": "New Holland" }

2. Prefix Matching for Short Queries

Expand prefix matching for very short queries (< 3 chars):

// "ne" → should it match "New Holland"?
// Current: No (security/performance reason)
// Future: Configurable threshold

3. Fuzzy Matching Enhancement

Combine alias system with fuzzy matching:

// "neww" (typo) → should it match "newh"?
// Current: Uses existing fuzzy in Elasticsearch
// Future: Could apply here too

4. Metrics & Analytics

Track which aliases are used:

aliasUsageMetrics.inc({
  alias: "newh",
  manufacturerName: "New Holland",
});
  • packages/shared-catalog/src/config/manufacturers.ts - Alias definitions
  • services/search/src/utils/autocomplete-suggestions.ts - Integration point (lines 1061-1130)
  • services/search/src/__tests__/manufacturer-aliases.test.ts - Unit tests
  • services/search/src/__tests__/manufacturer-detector.test.ts - Existing manufacturer detection tests

See Also

On this page