feat: Add distillery enrichment cache
Caches AI enrichment results per distillery to save API calls: - New table: enrichment_cache (distillery, tags, hit_count) - New service: cache-enrichment.ts (get, save, increment, stats) - enrich-data.ts checks cache before AI query - Saves to cache after successful AI response - Returns cached: true/false flag for transparency Benefits: - 0 API cost for repeated distillery scans - Near-instant response for cached distilleries - Shared across all users
This commit is contained in:
@@ -6,6 +6,7 @@ import { trackApiUsage } from '@/services/track-api-usage';
|
||||
import { deductCredits } from '@/services/credit-service';
|
||||
import { getAllSystemTags } from '@/services/tags';
|
||||
import { getAIProvider, getOpenRouterClient, OPENROUTER_PROVIDER_PREFERENCES } from '@/lib/openrouter';
|
||||
import { getEnrichmentCache, saveEnrichmentCache, incrementCacheHit } from '@/services/cache-enrichment';
|
||||
|
||||
// Native Schema Definition for Enrichment Data
|
||||
const enrichmentSchema = {
|
||||
@@ -146,6 +147,31 @@ export async function enrichData(name: string, distillery: string, availableTags
|
||||
return { success: false, error: 'OPENROUTER_API_KEY is not configured.' };
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CACHE CHECK - Return cached data if available
|
||||
// ========================================
|
||||
if (distillery?.trim()) {
|
||||
try {
|
||||
const cached = await getEnrichmentCache(distillery);
|
||||
if (cached) {
|
||||
console.log(`[EnrichData] ✅ CACHE HIT for: ${distillery}`);
|
||||
// Fire and forget: increment hit counter
|
||||
incrementCacheHit(distillery).catch(() => { });
|
||||
return {
|
||||
success: true,
|
||||
data: cached,
|
||||
cached: true,
|
||||
provider: 'cache',
|
||||
perf: { apiDuration: 0 }
|
||||
};
|
||||
}
|
||||
console.log(`[EnrichData] ❌ Cache MISS for: ${distillery}`);
|
||||
} catch (cacheError) {
|
||||
console.warn('[EnrichData] Cache lookup failed:', cacheError);
|
||||
// Continue with AI query
|
||||
}
|
||||
}
|
||||
|
||||
let supabase;
|
||||
try {
|
||||
let tagsToUse = availableTags;
|
||||
@@ -184,6 +210,15 @@ Instructions:
|
||||
|
||||
console.log('[EnrichData] Response:', result.data);
|
||||
|
||||
// ========================================
|
||||
// SAVE TO CACHE - Store for future lookups
|
||||
// ========================================
|
||||
if (distillery?.trim()) {
|
||||
saveEnrichmentCache(distillery, result.data).catch((err) => {
|
||||
console.warn('[EnrichData] Cache save failed:', err);
|
||||
});
|
||||
}
|
||||
|
||||
// Track usage
|
||||
await trackApiUsage({
|
||||
userId: userId,
|
||||
@@ -197,6 +232,7 @@ Instructions:
|
||||
return {
|
||||
success: true,
|
||||
data: result.data,
|
||||
cached: false,
|
||||
provider,
|
||||
perf: {
|
||||
apiDuration: result.apiTime
|
||||
|
||||
108
src/services/cache-enrichment.ts
Normal file
108
src/services/cache-enrichment.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
'use server';
|
||||
|
||||
import { createClient } from '@/lib/supabase/server';
|
||||
|
||||
interface EnrichmentCacheData {
|
||||
suggested_tags: string[] | null;
|
||||
suggested_custom_tags: string[] | null;
|
||||
search_string: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached enrichment data for a distillery
|
||||
*/
|
||||
export async function getEnrichmentCache(distillery: string): Promise<EnrichmentCacheData | null> {
|
||||
if (!distillery?.trim()) return null;
|
||||
|
||||
const supabase = await createClient();
|
||||
const normalizedDistillery = distillery.trim().toLowerCase();
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('enrichment_cache')
|
||||
.select('suggested_tags, suggested_custom_tags, search_string')
|
||||
.eq('distillery', normalizedDistillery)
|
||||
.single();
|
||||
|
||||
if (error || !data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save enrichment data to cache
|
||||
*/
|
||||
export async function saveEnrichmentCache(
|
||||
distillery: string,
|
||||
enrichmentData: EnrichmentCacheData
|
||||
): Promise<boolean> {
|
||||
if (!distillery?.trim()) return false;
|
||||
|
||||
const supabase = await createClient();
|
||||
const normalizedDistillery = distillery.trim().toLowerCase();
|
||||
|
||||
const { error } = await supabase
|
||||
.from('enrichment_cache')
|
||||
.upsert({
|
||||
distillery: normalizedDistillery,
|
||||
suggested_tags: enrichmentData.suggested_tags,
|
||||
suggested_custom_tags: enrichmentData.suggested_custom_tags,
|
||||
search_string: enrichmentData.search_string,
|
||||
updated_at: new Date().toISOString(),
|
||||
}, {
|
||||
onConflict: 'distillery'
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('[EnrichmentCache] Save error:', error);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log(`[EnrichmentCache] Saved cache for: ${normalizedDistillery}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment cache hit counter
|
||||
*/
|
||||
export async function incrementCacheHit(distillery: string): Promise<void> {
|
||||
if (!distillery?.trim()) return;
|
||||
|
||||
const supabase = await createClient();
|
||||
const normalizedDistillery = distillery.trim().toLowerCase();
|
||||
|
||||
// Simple increment via update - no RPC needed
|
||||
const { data: current } = await supabase
|
||||
.from('enrichment_cache')
|
||||
.select('hit_count')
|
||||
.eq('distillery', normalizedDistillery)
|
||||
.single();
|
||||
|
||||
if (current) {
|
||||
await supabase
|
||||
.from('enrichment_cache')
|
||||
.update({ hit_count: (current.hit_count || 0) + 1 })
|
||||
.eq('distillery', normalizedDistillery);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache statistics
|
||||
*/
|
||||
export async function getEnrichmentCacheStats() {
|
||||
const supabase = await createClient();
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('enrichment_cache')
|
||||
.select('distillery, hit_count, created_at')
|
||||
.order('hit_count', { ascending: false })
|
||||
.limit(20);
|
||||
|
||||
if (error) {
|
||||
console.error('[EnrichmentCache] Stats error:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return data || [];
|
||||
}
|
||||
Reference in New Issue
Block a user