1import { NextRequest, NextResponse } from 'next/server';
2import { fieldpineServerApi } from '@/lib/server/fieldpineApi';
3import { getStoredAuth } from '@/lib/server/auth';
6 f100: number; // Product ID
7 f101: string; // Description
8 f105: string | null; // SKU
9 f150?: number; // SellFlags
10 f151?: number; // NoSell (0=can sell, 1=cannot sell)
11 f155?: number; // Hidden (0=visible, 1=hidden)
14interface DuplicateGroup {
25// Levenshtein distance algorithm for string similarity
26function levenshteinDistance(str1: string, str2: string): number {
27 const len1 = str1.length;
28 const len2 = str2.length;
29 const matrix: number[][] = [];
31 for (let i = 0; i <= len1; i++) {
34 for (let j = 0; j <= len2; j++) {
38 for (let i = 1; i <= len1; i++) {
39 for (let j = 1; j <= len2; j++) {
40 const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
41 matrix[i][j] = Math.min(
44 matrix[i - 1][j - 1] + cost
49 return matrix[len1][len2];
52// Calculate similarity ratio between two strings (0-1, where 1 is identical)
53function similarityRatio(str1: string, str2: string): number {
54 const maxLength = Math.max(str1.length, str2.length);
55 if (maxLength === 0) return 1.0;
56 const distance = levenshteinDistance(str1, str2);
57 return 1 - distance / maxLength;
60export async function GET(request: NextRequest) {
62 console.log('[Duplicate Descriptions API] Starting request');
64 // Verify authentication
65 const authData = await getStoredAuth();
66 if (!authData || !authData.authenticated) {
67 console.log('[Duplicate Descriptions API] Authentication failed');
68 return NextResponse.json(
69 { error: 'Authentication required' },
74 console.log('[Duplicate Descriptions API] Making BUCK call');
76 // Fetch all products with required fields including status fields
77 const result = await fieldpineServerApi.buckApiCall({
78 "3": "retailmax.elink.products",
79 "10": "113,349,100,101,105,103,113s,1101,1102,150,151,155", // Fields: ID, description, SKU, price, status fields
80 "18": "1101,1102", // Additional fields
81 "9": "f503,0,a6", // Filter settings (a6 = all products including inactive/hidden)
82 "8": "150000" // Limit
85 console.log('[Duplicate Descriptions API] BUCK result:', {
87 hasDats: !!(result as any)?.DATS,
88 datsLength: (result as any)?.DATS?.length || 0,
89 resultKeys: Object.keys(result || {})
92 if (!result || !(result as any).DATS || !Array.isArray((result as any).DATS)) {
93 console.error('[Duplicate Descriptions API] Invalid result structure');
94 return NextResponse.json(
95 { success: false, error: 'Invalid API response structure' },
100 const products: Product[] = (result as any).DATS;
101 console.log('[Duplicate Descriptions API] Processing', products.length, 'products');
103 // Filter products with descriptions
104 const validProducts = products.filter(p => p.f101 && p.f101.trim().length > 0);
105 console.log('[Duplicate Descriptions API] Valid products with descriptions:', validProducts.length);
107 // Group products by similarity (75%+ match threshold)
108 const SIMILARITY_THRESHOLD = 0.75;
109 const duplicateGroups: Product[][] = [];
110 const processed = new Set<number>();
112 for (let i = 0; i < validProducts.length; i++) {
113 if (processed.has(i)) continue;
115 const product1 = validProducts[i];
116 const desc1 = product1.f101.trim().toLowerCase();
117 const group: Product[] = [product1];
120 // Compare with all remaining products
121 for (let j = i + 1; j < validProducts.length; j++) {
122 if (processed.has(j)) continue;
124 const product2 = validProducts[j];
125 const desc2 = product2.f101.trim().toLowerCase();
127 // Calculate similarity
128 const similarity = similarityRatio(desc1, desc2);
130 if (similarity >= SIMILARITY_THRESHOLD) {
131 group.push(product2);
136 // Only keep groups with 2+ products
137 if (group.length >= 2) {
138 duplicateGroups.push(group);
142 console.log('[Duplicate Descriptions API] Found', duplicateGroups.length, 'duplicate groups using similarity matching');
144 // Convert to response format with status information
145 const duplicates: DuplicateGroup[] = duplicateGroups.map(group => ({
146 description: group[0].f101, // Use original description from first product
147 products: group.map(p => ({
149 sku: p.f105 || `PROD-${p.f100}`,
150 noSell: p.f151 === 1, // true if product cannot be sold
151 hidden: p.f155 === 1 // true if product is hidden
156 // Sort by count (most duplicates first)
157 duplicates.sort((a, b) => b.count - a.count);
159 console.log('[Duplicate Descriptions API] Found', duplicates.length, 'duplicate groups');
161 return NextResponse.json({
165 totalGroups: duplicates.length,
166 totalProducts: duplicates.reduce((sum, d) => sum + d.count, 0)
169 } catch (error: any) {
170 console.error('[Duplicate Descriptions API] Error:', error);
171 return NextResponse.json(
172 { success: false, error: error.message || 'Failed to fetch duplicate descriptions' },