CROP
ProjectsCROP Frontend

Equipment Fitment API Enhancement Task

Document Version: 1.0 Created: 2025-11-10 Status: Draft for Backend Team Review Related Frontend Work: Phase 4 - Equipment Fitment Integration

Equipment Fitment API Enhancement Task

Document Version: 1.0 Created: 2025-11-10 Status: Draft for Backend Team Review Related Frontend Work: Phase 4 - Equipment Fitment Integration


Overview

This document specifies the API enhancements needed to support interactive equipment fitment search, filtering, and sorting on product detail pages. Currently, equipment fitment data is displayed statically on the frontend. We need backend support for searching within fitment data, sorting by various criteria, filtering by category and year range, and paginating large result sets.


Current State

The Search Service API (v1.3.0) already has foundational equipment support:

1. Equipment Filter Parameters (Search Endpoint)

Endpoint: GET/POST /api/search

Existing parameters:

  • equipmentFitment (string | string[]): Filter by equipment fitment strings
  • hasEquipmentFitment (boolean): Filter to parts that have fitment data
  • equipmentModelKey (string | string[]): Filter by equipment model keys

2. Equipment Facets (Filters Endpoint)

Endpoint: GET /api/filters

Returns EquipmentFacet structure:

interface EquipmentFacet {
  afterKey?: Record<string, unknown> | null;
  buckets: EquipmentFacetBucket[];
}

interface EquipmentFacetBucket {
  brandCode: string;
  model: string;
  docCount: number;
  variants: number;
  productLine?: string | null;
  yearFrom?: number | null;
  yearTo?: number | null;
}

Pagination parameters:

  • equipmentAfterKey (string | object): Cursor for pagination
  • equipmentLimit (number): Page size for equipment facets
  • equipmentPageSize (number): Alias for equipmentLimit

3. Part Response Structure

Endpoint: GET /api/parts/:id

Returns Part object with equipment fields:

  • equipmentFitment (string[] | null): Raw fitment strings (e.g., "T4040 DELUXE TRACTOR 12/07 12/14 - TRACTORS UTILITY")
  • equipmentModelKey (string[] | null): Equipment model keys for filtering
  • equipmentKey (string[] | null): Additional equipment keys
  • yearFrom (number[] | null): Year range start values
  • yearTo (number[] | null): Year range end values

4. Pagination Infrastructure

The API already supports two pagination modes:

  1. Offset-based: page and pageSize parameters
  2. Cursor-based: searchAfter, pitId, pitKeepAlive parameters

Requirements

Problem Statement

On product detail pages, parts can have 50-500+ equipment fitment records. Users need to:

  1. Search within fitment data (by model name, year, identifier)
  2. Filter by equipment category and year range
  3. Sort by different criteria (alphabetical, year, category)
  4. Paginate through large fitment lists

Current frontend implementation parses raw equipmentFitment strings client-side, which is inefficient and lacks search/filter capability.

Use Cases

  1. User searches for "T4040" within a part's fitment → API returns matching fitment records
  2. User filters by "TRACTORS UTILITY" category → API returns only that category's records
  3. User filters by year range 2010-2015 → API returns fitment records within that range
  4. User sorts by year descending → API returns newest equipment first
  5. User scrolls through 200+ fitment records → API paginates efficiently

API Specifications

Endpoint: Search Equipment Fitment for a Part

Method: GET Path: /api/parts/:partId/equipment-fitment

Returns paginated, searchable, and sortable equipment fitment data for a specific part.

Path Parameters

ParameterTypeRequiredDescription
partIdstringYesPart ID or slug

Query Parameters

ParameterTypeRequiredDefaultDescription
qstringNo-Search query (searches model name, identifier, raw string)
categorystringNo-Filter by exact category name (e.g., "TRACTORS UTILITY")
yearFromnumberNo-Filter by minimum year (inclusive)
yearTonumberNo-Filter by maximum year (inclusive)
sortByenumNocategorySort criteria (see Sort Options below)
sortOrderenumNoascSort order: asc or desc
pagenumberNo1Page number (1-based)
pageSizenumberNo50Items per page (max: 100)

Sort Options

ValueDescription
categorySort by category name alphabetically
modelSort by model name alphabetically
yearSort by year range (uses yearFrom, then yearTo)
relevanceSort by search relevance (only when q is provided)

Response Schema

interface EquipmentFitmentResponse {
  // Grouped fitment data
  fitmentGroups: EquipmentFitmentGroup[];

  // Pagination metadata
  pagination: {
    page: number;          // Current page (1-based)
    pageSize: number;      // Items per page
    total: number;         // Total items matching filters
    totalPages: number;    // Total pages
    hasNextPage: boolean;  // Whether there's a next page
    hasPreviousPage: boolean; // Whether there's a previous page
  };

  // Search metadata
  query?: string;          // Echo back search query if provided
  filters: {
    category?: string;     // Active category filter
    yearFrom?: number;     // Active year range filter
    yearTo?: number;       // Active year range filter
  };

  // Performance metrics
  took: number;            // Query execution time in ms
}

interface EquipmentFitmentGroup {
  category: string;        // Equipment category (e.g., "TRACTORS UTILITY")
  items: EquipmentFitmentItem[];
  itemCount: number;       // Number of items in this group
}

interface EquipmentFitmentItem {
  // Core data
  raw: string;             // Original fitment string from database
  summary: string;         // Parsed model name/description
  category: string;        // Equipment category

  // Optional parsed fields
  identifier?: string;     // Model identifier/code (extracted from parentheses)
  yearRange?: string;      // Formatted year range (e.g., "12/07 → 12/14")
  yearFrom?: number;       // Parsed start year (for filtering/sorting)
  yearTo?: number;         // Parsed end year (for filtering/sorting)

  // Search relevance (only when q is provided)
  score?: number;          // Search relevance score
}

Success Response Example

{
  "fitmentGroups": [
    {
      "category": "TRACTORS UTILITY",
      "itemCount": 3,
      "items": [
        {
          "raw": "T4040 DELUXE TRACTOR 12/07 12/14 - TRACTORS UTILITY",
          "summary": "T4040 DELUXE TRACTOR",
          "category": "TRACTORS UTILITY",
          "yearRange": "12/07 → 12/14",
          "yearFrom": 2007,
          "yearTo": 2014,
          "identifier": null
        },
        {
          "raw": "T4050 TRACTOR 01/09 12/15 (12345) - TRACTORS UTILITY",
          "summary": "T4050 TRACTOR",
          "category": "TRACTORS UTILITY",
          "yearRange": "01/09 → 12/15",
          "yearFrom": 2009,
          "yearTo": 2015,
          "identifier": "12345"
        }
      ]
    },
    {
      "category": "COMBINES",
      "itemCount": 1,
      "items": [
        {
          "raw": "CR9080 COMBINE 01/11 12/16 - COMBINES",
          "summary": "CR9080 COMBINE",
          "category": "COMBINES",
          "yearRange": "01/11 → 12/16",
          "yearFrom": 2011,
          "yearTo": 2016,
          "identifier": null
        }
      ]
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 50,
    "total": 4,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPreviousPage": false
  },
  "filters": {},
  "took": 12
}

Error Responses

404 Not Found - Part doesn't exist

{
  "success": false,
  "error": "Part not found",
  "code": "PART_NOT_FOUND"
}

400 Bad Request - Invalid parameters

{
  "success": false,
  "error": "Invalid year range: yearFrom must be <= yearTo",
  "code": "INVALID_PARAMETERS",
  "issues": [
    {
      "code": "invalid_range",
      "path": ["yearFrom"],
      "message": "yearFrom must be less than or equal to yearTo"
    }
  ]
}

Endpoint: Get Available Equipment Categories

Method: GET Path: /api/parts/:partId/equipment-fitment/categories

Returns list of unique equipment categories for a part (for filter dropdown UI).

Path Parameters

ParameterTypeRequiredDescription
partIdstringYesPart ID or slug

Response Schema

interface CategoriesResponse {
  categories: CategoryBucket[];
  total: number;  // Total number of categories
  took: number;   // Query time in ms
}

interface CategoryBucket {
  name: string;   // Category name
  count: number;  // Number of fitment items in this category
}

Success Response Example

{
  "categories": [
    {
      "name": "TRACTORS UTILITY",
      "count": 45
    },
    {
      "name": "COMBINES",
      "count": 12
    },
    {
      "name": "BALERS",
      "count": 8
    }
  ],
  "total": 3,
  "took": 5
}

Data Requirements

Indexing Requirements

For optimal performance, the following fields should be indexed:

  1. Full-text search index on equipmentFitment array field

    • Enables fast searching within fitment strings
    • Should support partial matches and fuzzy search
  2. Keyword index on parsed category field

    • For exact category filtering
    • Consider extracting category during indexing (parse " - CATEGORY" suffix)
  3. Range index on year fields

    • Parse MM/YY dates from fitment strings
    • Index both yearFrom and yearTo for range queries
    • Format: "12/07" → 2007, "01/15" → 2015

Data Parsing Strategy

The backend should parse raw equipmentFitment strings during indexing:

Input format: "{MODEL} {MM/YY_START} {MM/YY_END} ({IDENTIFIER}) - {CATEGORY}"

Examples:

  • "T4040 DELUXE TRACTOR 12/07 12/14 - TRACTORS UTILITY"
  • "CR9080 COMBINE 01/11 12/16 (12345) - COMBINES"
  • "SOME MODEL - OTHER EQUIPMENT" (no years or identifier)

Parsing rules:

  1. Category: Extract text after " - " separator (required)
  2. Identifier: Extract text within parentheses at end (optional)
  3. Years: Extract MM/YY patterns, take last 2 as start/end range (optional)
    • Convert "12/07" → 2007
    • Handle missing years gracefully
  4. Summary: Everything before " - " after removing years and identifier

Edge cases:

  • Missing years (some old equipment)
  • Missing identifier (most entries)
  • Missing category (fallback to "Other equipment")
  • Multiple parentheses (take last one as identifier)

Implementation Notes

Performance Considerations

  1. Caching: Consider caching parsed fitment data per part

    • Cache TTL: 24 hours (fitment data rarely changes)
    • Cache key: part:{partId}:equipment-fitment
  2. Pagination: Use offset-based pagination for simplicity

    • Max page size: 100 items
    • For parts with 500+ fitment items, pagination is essential
  3. Search:

    • ElasticSearch/OpenSearch match query on equipmentFitment array
    • Minimum 2 characters for search query
    • Use fuzzy matching for typo tolerance
  4. Grouping:

    • Group by category after filtering/searching
    • Return groups sorted by sortBy parameter
    • Within each group, items sorted by sortBy parameter

Database Considerations

Option A: Parse on Index

  • Pros: Fast queries, no runtime parsing overhead
  • Cons: Requires re-indexing existing data, larger index size
  • Recommendation: Preferred for production

Add these fields to the ElasticSearch/OpenSearch document:

{
  "equipmentFitment": ["T4040 DELUXE TRACTOR 12/07 12/14 - TRACTORS UTILITY"],
  "equipmentFitmentParsed": [
    {
      "raw": "T4040 DELUXE TRACTOR 12/07 12/14 - TRACTORS UTILITY",
      "summary": "T4040 DELUXE TRACTOR",
      "category": "TRACTORS UTILITY",
      "identifier": null,
      "yearFrom": 2007,
      "yearTo": 2014
    }
  ]
}

Option B: Parse on Query

  • Pros: No re-indexing needed, smaller index
  • Cons: Slower queries, parsing overhead on every request
  • Recommendation: Acceptable for MVP, migrate to Option A later

Security Considerations

  1. Rate limiting: 100 requests/minute per IP
  2. Parameter validation:
    • Sanitize q parameter (max 100 chars)
    • Validate year range (1900-2100)
    • Validate pageSize (max 100)
  3. Part existence check: Verify part exists before querying fitment

Backward Compatibility

This is a new endpoint, so no backward compatibility concerns. The existing /api/parts/:id endpoint continues to return raw equipmentFitment strings unchanged.


Priority Classification

P0 (Must Have) - MVP Requirements

  • /api/parts/:partId/equipment-fitment endpoint
  • ✅ Search parameter (q)
  • ✅ Category filter parameter (category)
  • ✅ Pagination (page, pageSize)
  • ✅ Basic sorting (sortBy: category | model | year)
  • ✅ Parsed fitment data structure (category, summary, yearRange)

P1 (Should Have) - Post-MVP Enhancements

  • ✅ Year range filtering (yearFrom, yearTo)
  • ✅ Sort order parameter (sortOrder: asc | desc)
  • ✅ Relevance sorting (when search query provided)
  • /api/parts/:partId/equipment-fitment/categories endpoint
  • ✅ Search relevance scores in response

P2 (Nice to Have) - Future Enhancements

  • 🔄 Fuzzy search with typo tolerance
  • 🔄 Highlighting matched terms in response
  • 🔄 Equipment brand/manufacturer filtering
  • 🔄 Export fitment data (CSV/Excel format)
  • 🔄 Bulk fitment updates API (admin)
  • 🔄 Equipment model suggestions API (autocomplete)

Testing Requirements

Unit Tests

  1. Parsing logic:

    • Parse fitment strings correctly (with/without years, identifiers)
    • Handle edge cases (missing category, malformed strings)
    • Convert MM/YY to full year correctly
  2. Query building:

    • Build correct ElasticSearch query for search
    • Build correct filters for category/year range
    • Build correct sort clauses

Integration Tests

  1. Endpoint behavior:

    • Returns 404 for non-existent part
    • Returns 400 for invalid parameters
    • Returns 200 with correct data structure
    • Pagination works correctly (page 1, 2, 3, etc.)
  2. Search functionality:

    • Search matches model names
    • Search matches identifiers
    • Search matches partial strings
    • Search returns empty results gracefully
  3. Filtering:

    • Category filter works correctly
    • Year range filter works correctly
    • Combined filters work correctly (AND logic)
  4. Sorting:

    • Sort by category works (alphabetical)
    • Sort by model works (alphabetical)
    • Sort by year works (numerical, with null handling)
    • Sort order (asc/desc) works correctly

Performance Tests

  1. Load testing:

    • Handle 100 concurrent requests
    • Response time < 200ms for typical query (50 items)
    • Response time < 500ms for large dataset (500+ items)
  2. Edge cases:

    • Part with 0 fitment items
    • Part with 1000+ fitment items
    • Search with no matches
    • Invalid sort parameter

Timeline Estimate

PhaseTasksEstimated Time
Phase 1: Setup• Review requirements• Design database schema changes• Set up test fixtures2-3 days
Phase 2: Parsing• Implement fitment string parser• Write parser unit tests• Validate against production data3-4 days
Phase 3: Indexing• Update ElasticSearch mapping• Implement re-indexing script• Run re-indexing on staging2-3 days
Phase 4: API Implementation• Implement fitment endpoint• Implement categories endpoint• Add parameter validation• Write integration tests4-5 days
Phase 5: Testing & QA• Performance testing• Edge case testing• Security testing• Code review2-3 days
Phase 6: Deployment• Deploy to staging• Frontend integration testing• Deploy to production• Monitor & iterate2-3 days

Total Estimated Time: 15-21 working days (3-4 weeks)

Critical Path: Parsing logic → Re-indexing → API implementation → Frontend integration


Questions for Backend Team

Technical Questions

  1. Indexing strategy: Should we parse fitment strings during indexing (Option A) or at query time (Option B)?

    • Recommendation: Option A for production performance
  2. Re-indexing: Can we run a re-indexing job on production with zero downtime?

    • Expected volume: ~X parts with equipment fitment data
    • Average fitment items per part: ~Y
  3. Search technology: Are we using ElasticSearch or OpenSearch?

    • Affects query syntax and available features
  4. Caching layer: Do we have Redis or similar for caching parsed fitment data?

    • Would significantly improve performance for frequently accessed parts

Data Questions

  1. Fitment string format: Are there other format variations besides the documented pattern?

    • Need sample data for edge cases
  2. Data quality: What percentage of equipmentFitment strings have:

    • Year information? (estimate: 80-90%)
    • Identifier codes? (estimate: 30-40%)
    • Valid category suffix? (estimate: 95%+)
  3. Update frequency: How often does equipment fitment data change?

    • Affects cache TTL strategy

Business Questions

  1. Feature flag: Should this endpoint be behind a feature flag initially?

    • Allows gradual rollout and A/B testing
  2. Analytics: What metrics should we track?

    • Search usage, popular categories, average result count, etc.
  3. Rate limiting: What are appropriate rate limits for this endpoint?

    • Suggestion: 100 req/min per IP, 1000 req/min per API key

TypeScript Type Definitions

For frontend integration, these types should be added to /lib/search-service/types.ts:

/**
 * Equipment fitment item parsed from raw fitment string.
 * Returned by /api/parts/:partId/equipment-fitment endpoint.
 */
export interface EquipmentFitmentItem {
  /** Original fitment string from database */
  raw: string;
  /** Parsed model name/description (without years/identifier/category) */
  summary: string;
  /** Equipment category (e.g., "TRACTORS UTILITY", "COMBINES") */
  category: string;
  /** Model identifier/code extracted from parentheses (optional) */
  identifier?: string | null;
  /** Formatted year range (e.g., "12/07 → 12/14") for display */
  yearRange?: string | null;
  /** Parsed start year (numeric, for filtering/sorting) */
  yearFrom?: number | null;
  /** Parsed end year (numeric, for filtering/sorting) */
  yearTo?: number | null;
  /** Search relevance score (only present when q parameter provided) */
  score?: number;
}

/**
 * Group of equipment fitment items by category.
 */
export interface EquipmentFitmentGroup {
  /** Category name */
  category: string;
  /** Fitment items in this category */
  items: EquipmentFitmentItem[];
  /** Number of items in this group */
  itemCount: number;
}

/**
 * Pagination metadata for equipment fitment results.
 */
export interface EquipmentFitmentPagination {
  /** Current page number (1-based) */
  page: number;
  /** Items per page */
  pageSize: number;
  /** Total items matching filters */
  total: number;
  /** Total pages */
  totalPages: number;
  /** Whether there's a next page */
  hasNextPage: boolean;
  /** Whether there's a previous page */
  hasPreviousPage: boolean;
}

/**
 * Active filters for equipment fitment query.
 */
export interface EquipmentFitmentFilters {
  /** Active category filter */
  category?: string;
  /** Active year range filter (minimum) */
  yearFrom?: number;
  /** Active year range filter (maximum) */
  yearTo?: number;
}

/**
 * Response from /api/parts/:partId/equipment-fitment endpoint.
 */
export interface EquipmentFitmentResponse {
  /** Grouped fitment data by category */
  fitmentGroups: EquipmentFitmentGroup[];
  /** Pagination metadata */
  pagination: EquipmentFitmentPagination;
  /** Echo back search query if provided */
  query?: string;
  /** Active filters */
  filters: EquipmentFitmentFilters;
  /** Query execution time in milliseconds */
  took: number;
}

/**
 * Sort options for equipment fitment results.
 */
export type EquipmentFitmentSortBy = "category" | "model" | "year" | "relevance";

/**
 * Sort order for equipment fitment results.
 */
export type EquipmentFitmentSortOrder = "asc" | "desc";

/**
 * Parameters for equipment fitment search.
 */
export interface EquipmentFitmentParams {
  /** Search query (searches model name, identifier, raw string) */
  q?: string;
  /** Filter by exact category name */
  category?: string;
  /** Filter by minimum year (inclusive) */
  yearFrom?: number;
  /** Filter by maximum year (inclusive) */
  yearTo?: number;
  /** Sort criteria */
  sortBy?: EquipmentFitmentSortBy;
  /** Sort order */
  sortOrder?: EquipmentFitmentSortOrder;
  /** Page number (1-based) */
  page?: number;
  /** Items per page (max: 100) */
  pageSize?: number;
}

/**
 * Category bucket for equipment fitment categories aggregation.
 */
export interface EquipmentCategoryBucket {
  /** Category name */
  name: string;
  /** Number of fitment items in this category */
  count: number;
}

/**
 * Response from /api/parts/:partId/equipment-fitment/categories endpoint.
 */
export interface EquipmentCategoriesResponse {
  /** List of categories with counts */
  categories: EquipmentCategoryBucket[];
  /** Total number of categories */
  total: number;
  /** Query execution time in milliseconds */
  took: number;
}

Example API Requests

Example 1: Basic Request (No Filters)

GET /api/parts/12345/equipment-fitment?page=1&pageSize=50

Returns first 50 fitment items grouped by category, sorted by category name.

Example 2: Search Query

GET /api/parts/12345/equipment-fitment?q=T4040&page=1&pageSize=50

Returns fitment items matching "T4040" search query, sorted by relevance.

Example 3: Category Filter

GET /api/parts/12345/equipment-fitment?category=TRACTORS+UTILITY&page=1&pageSize=50

Returns only fitment items in "TRACTORS UTILITY" category.

Example 4: Year Range Filter

GET /api/parts/12345/equipment-fitment?yearFrom=2010&yearTo=2015&page=1&pageSize=50

Returns fitment items with year range overlapping 2010-2015.

Example 5: Combined Filters with Sorting

GET /api/parts/12345/equipment-fitment?q=tractor&category=TRACTORS+UTILITY&yearFrom=2010&sortBy=year&sortOrder=desc&page=1&pageSize=50

Returns tractors in "TRACTORS UTILITY" category from 2010 onwards, sorted by year descending.

Example 6: Get Categories

GET /api/parts/12345/equipment-fitment/categories

Returns list of all categories available for this part.


Success Metrics

Performance Metrics

  • Response time: < 200ms (p95) for typical query
  • Search accuracy: > 95% relevant results for common queries
  • Uptime: 99.9% availability

Usage Metrics

  • Adoption: > 50% of product detail page visitors use search/filter
  • Search success: > 80% of searches return results
  • Category filter usage: Track most popular categories

Business Metrics

  • Conversion impact: Measure impact on add-to-cart rate
  • Support reduction: Fewer "compatibility" support tickets
  • User satisfaction: Improved product detail page engagement time

Appendix: Sample Data

Sample Raw Fitment Strings

T4040 DELUXE TRACTOR 12/07 12/14 - TRACTORS UTILITY
T4050 TRACTOR 01/09 12/15 (NH12345) - TRACTORS UTILITY
T4060 CAB TRACTOR 06/10 - TRACTORS UTILITY
CR9080 COMBINE 01/11 12/16 - COMBINES
CR9090 ELEVATION COMBINE 03/13 12/18 (CR9090ELEV) - COMBINES
BC5070 BALER 05/12 - BALERS
T5050 - TRACTORS COMPACT
LEGACY MODEL - OTHER EQUIPMENT

Sample Parsed Output

[
  {
    "raw": "T4040 DELUXE TRACTOR 12/07 12/14 - TRACTORS UTILITY",
    "summary": "T4040 DELUXE TRACTOR",
    "category": "TRACTORS UTILITY",
    "identifier": null,
    "yearRange": "12/07 → 12/14",
    "yearFrom": 2007,
    "yearTo": 2014
  },
  {
    "raw": "T4050 TRACTOR 01/09 12/15 (NH12345) - TRACTORS UTILITY",
    "summary": "T4050 TRACTOR",
    "category": "TRACTORS UTILITY",
    "identifier": "NH12345",
    "yearRange": "01/09 → 12/15",
    "yearFrom": 2009,
    "yearTo": 2015
  },
  {
    "raw": "T4060 CAB TRACTOR 06/10 - TRACTORS UTILITY",
    "summary": "T4060 CAB TRACTOR",
    "category": "TRACTORS UTILITY",
    "identifier": null,
    "yearRange": "06/10",
    "yearFrom": 2010,
    "yearTo": null
  },
  {
    "raw": "LEGACY MODEL - OTHER EQUIPMENT",
    "summary": "LEGACY MODEL",
    "category": "OTHER EQUIPMENT",
    "identifier": null,
    "yearRange": null,
    "yearFrom": null,
    "yearTo": null
  }
]


Document Status: Ready for backend team review Next Steps: Backend team to review and provide feedback on feasibility, timeline, and technical approach Contact: Frontend team lead for questions or clarifications

On this page