← back to Wine Finder Next

lib/services/realHistoryService.ts

110 lines

/**
 * Real Historical Price Data Service
 * Reads actual price tracking data from JSONL file
 */

import fs from 'fs/promises';
import path from 'path';

export interface RealPricePoint {
  timestamp: string;
  name: string;
  price: number;
  source: string;
  winery?: string;
  region?: string;
  url?: string;
}

export interface LastSaleInfo {
  price: number;
  source: string;
  date: string;
  daysAgo: number;
}

export async function getRealPriceHistory(wineName: string): Promise<RealPricePoint[]> {
  try {
    const dataDir = path.join(process.cwd(), 'data');
    const priceHistoryFile = path.join(dataDir, 'price-history.jsonl');

    const fileContent = await fs.readFile(priceHistoryFile, 'utf-8');
    const lines = fileContent.trim().split('\n');

    const history: RealPricePoint[] = [];

    for (const line of lines) {
      try {
        const entry = JSON.parse(line);

        // Fuzzy match wine name
        const nameMatch = entry.name.toLowerCase().includes(wineName.toLowerCase()) ||
                         wineName.toLowerCase().includes(entry.name.toLowerCase());

        if (nameMatch) {
          history.push({
            timestamp: entry.timestamp,
            name: entry.name,
            price: entry.price,
            source: entry.source || 'Unknown',
            winery: entry.winery,
            region: entry.region,
            url: entry.url,
          });
        }
      } catch (e) {
        // Skip invalid JSON lines
        continue;
      }
    }

    // Sort by timestamp (oldest first)
    return history.sort((a, b) =>
      new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
    );
  } catch (error) {
    console.error('Error reading real price history:', error);
    return [];
  }
}

export async function getLastSaleInfo(wineName: string): Promise<LastSaleInfo | null> {
  const history = await getRealPriceHistory(wineName);

  if (history.length === 0) return null;

  // Get most recent entry
  const lastSale = history[history.length - 1];
  const lastSaleDate = new Date(lastSale.timestamp);
  const now = new Date();
  const daysAgo = Math.floor((now.getTime() - lastSaleDate.getTime()) / (1000 * 60 * 60 * 24));

  return {
    price: lastSale.price,
    source: lastSale.source,
    date: lastSale.timestamp,
    daysAgo,
  };
}

export async function getAveragePriceBySource(wineName: string): Promise<Record<string, number>> {
  const history = await getRealPriceHistory(wineName);

  const pricesBySource: Record<string, number[]> = {};

  history.forEach(point => {
    if (!pricesBySource[point.source]) {
      pricesBySource[point.source] = [];
    }
    pricesBySource[point.source].push(point.price);
  });

  const averages: Record<string, number> = {};
  for (const [source, prices] of Object.entries(pricesBySource)) {
    const avg = prices.reduce((sum, p) => sum + p, 0) / prices.length;
    averages[source] = parseFloat(avg.toFixed(2));
  }

  return averages;
}