flyerimporter.md
📌 Steg 1: Skapa en funktion för att extrahera text från PDF:en
Använd pdf-parse som primär metod och Tesseract.js som fallback för OCR.
Kod: extractFlyerText.ts
typescript
Copy

import * as fs from 'fs';
import * as pdf from 'pdf-parse';
import Tesseract from 'tesseract.js';

/**
 * Extraherar text från en PDF-fil (flyer), med fallback till OCR.
 * @param pdfPath Sökväg till PDF-filen.
 * @returns Extraherad text.
 */
export async function extractFlyerText(pdfPath: string): Promise<string> {
  try {
    // Försök med pdf-parse först
    const dataBuffer = fs.readFileSync(pdfPath);
    const data = await pdf(dataBuffer);
    if (data.text.trim()) {
      return data.text;
    }
  } catch (error) {
    console.warn('pdf-parse misslyckades, försöker med OCR...');
  }

  // Fallback till Tesseract.js för OCR
  try {
    const { data: { text } } = await Tesseract.recognize(pdfPath, 'swe', {
      logger: (m) => console.log(m),
    });
    return text;
  } catch (error) {
    console.error('OCR misslyckades:', error);
    throw new Error('Kunde inte extrahera text från PDF:en.');
  }
}




📌 Steg 2: Skapa en funktion för att skicka texten till Mistral Tiny
Använd Mistral Tiny för att extrahera och strukturera all produktinformation från flyern.
Kod: importFlyerWithAI.ts
typescript
Copy

import { MistralClient } from '@mistralai/mistralai';

const mistral = new MistralClient({
  apiKey: process.env.MISTRAL_API_KEY,
});

/**
 * Skickar flyer-texten till Mistral Tiny för att extrahera strukturerad data.
 * @param text Texten från flyern.
 * @returns Strukturerad data (JSON-array).
 */
export async function importFlyerWithAI(text: string): Promise<any[]> {
  const prompt = `
    Du är en expert på att tolka svenska matvaruflyers (t.ex. från Willys).
    Extrahera ALL produktinformation från följande text och returnera den som en JSON-array.

    För varje produkt, inkludera:
    - name: Produktnamn (fullständigt namn)
    - weight: Vikt (om tillgänglig, t.ex. "150g", "Ca 1kg")
    - origin: Ursprung/land/märke (om tillgänglig, t.ex. "FALKENBERG", "NYBERGS DELI • Sverige")
    - price: Pris (som ett nummer, t.ex. 39.90)
    - comparisonPrice: Jämförpris (som ett nummer, t.ex. 266.00)
    - unit: Enhet (kg, st, förp, l, etc.)
    - offer: Erbjudande (t.ex. ["Max 3 köp/hushåll", "Lägsta 30-dgrspris 125:00 kr"])
    - category: Kategori (t.ex. "Fisk", "Kött", "Mejeri", "Grönsaker", "Frukt", "Dryck")
    - validFrom: Giltig från (datum i formatet YYYY-MM-DD, om tillgängligt)
    - validTo: Giltig till (datum i formatet YYYY-MM-DD, om tillgängligt)

    Texten att tolka:
    ${text}

    Returnera ENDAST en JSON-array. Inga andra kommentarer.
    Exempel på utdata:
    [
      {
        "name": "KALLRÖKT LAX, GRAVAD LAX",
        "weight": "150g",
        "origin": "FALKENBERG",
        "price": 39.90,
        "comparisonPrice": 266.00,
        "unit": "kg",
        "offer": ["Max 3 köp/hushåll"],
        "category": "Fisk",
        "validFrom": "2026-05-18",
        "validTo": "2026-05-24"
      }
    ]
  `;

  try {
    const response = await mistral.chat({
      model: 'mistral-tiny', // Använder den enklaste modellen
      messages: [{ role: 'user', content: prompt }],
      temperature: 0.1, // Låg temperatur för mer deterministiska svar
    });

    // Rensa upp JSON-strängen
    const jsonString = response.choices[0].message.content
      .replace(/```json|```/g, '')
      .trim();

    // Parsa JSON:en
    return JSON.parse(jsonString);
  } catch (error) {
    console.error('Fel vid AI-import:', error);
    throw new Error('Kunde inte importera flyern med AI.');
  }
}




📌 Steg 3: Fullständigt importflöde
Kombinera text-extrahering och AI-import i ett fullständigt flöde.
Kod: flyerImportService.ts
typescript
Copy

import { extractFlyerText } from './extractFlyerText';
import { importFlyerWithAI } from './importFlyerWithAI';

/**
 * Importerar en flyer (PDF) och returnerar strukturerad data.
 * @param pdfPath Sökväg till PDF-filen.
 * @returns Strukturerad data från flyern.
 */
export async function importFlyer(pdfPath: string) {
  try {
    // 1. Extrahera text från PDF:en
    console.log('Extraherar text från flyern...');
    const text = await extractFlyerText(pdfPath);

    // 2. Skicka texten till Mistral Tiny för att extrahera data
    console.log('Skickar text till Mistral Tiny för extrahering...');
    const products = await importFlyerWithAI(text);

    return {
      success: true,
      products,
      text,
    };
  } catch (error) {
    console.error('Fel vid import:', error);
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Okänt fel',
    };
  }
}




📌 Steg 4: API-Endpoint för flyer-import
Skapa en Express-endpoint för att hantera uppladdning och import av flyers.
Kod: flyerImportRouter.ts
typescript
Copy

import express from 'express';
import multer from 'multer';
import { importFlyer } from '../services/flyerImportService';
import * as fs from 'fs';

const router = express.Router();
const upload = multer({ dest: 'uploads/' });

// Endpoint för att ladda upp och importera en flyer
router.post('/import/flyer', upload.single('flyer'), async (req, res) => {
  try {
    if (!req.file) {
      return res.status(400).json({ error: 'Ingen flyer uppladdad.' });
    }

    const result = await importFlyer(req.file.path);

    // Rensa upp uppladdad fil
    fs.unlinkSync(req.file.path);

    if (!result.success) {
      return res.status(500).json({ error: result.error });
    }

    res.json(result);
  } catch (error) {
    console.error('Fel vid flyer-import:', error);
    res.status(500).json({ error: 'Kunde inte importera flyern.' });
  }
});

export default router;




📌 Steg 5: Exempel på utdata
När du skickar texten från Willys flyer till Mistral Tiny, kommer du att få tillbaka en JSON-array med alla produkter, t.ex.:
json
Copy

[
  {
    "name": "KALLRÖKT LAX, GRAVAD LAX",
    "weight": "150g",
    "origin": "FALKENBERG",
    "price": 39.9,
    "comparisonPrice": 266.0,
    "unit": "kg",
    "offer": ["Max 3 köp/hushåll"],
    "category": "Fisk",
    "validFrom": "2026-05-18",
    "validTo": "2026-05-24"
  },
  {
    "name": "FLÄSKYTTERFILÉ",
    "weight": "Ca 1kg",
    "origin": "NYBERGS DELI • Sverige",
    "price": 64.9,
    "comparisonPrice": 64.9,
    "unit": "kg",
    "offer": ["Max 3 förp/hushåll"],
    "category": "Kött",
    "validFrom": "2026-05-18",
    "validTo": "2026-05-24"
  }
]




📌 Steg 6: Använda den extraherade datan
När du har den strukturerade datan, kan du:

Lagra den i din databas (t.ex. för att jämföra med inventory).
Visa den för användaren (t.ex. i en tabell).
Använda den för att generera recept (med eller utan AI).
Exempel: Lagra i databasen
typescript
Copy

// Antas att du har en Prisma-modell för flyer-produkter
await prisma.flyerProduct.createMany({
  data: products.map((product) => ({
    name: product.name,
    weight: product.weight,
    origin: product.origin,
    price: product.price,
    comparisonPrice: product.comparisonPrice,
    unit: product.unit,
    offer: JSON.stringify(product.offer),
    category: product.category,
    validFrom: product.validFrom ? new Date(product.validFrom) : null,
    validTo: product.validTo ? new Date(product.validTo) : null,
  })),
});




📌 Steg 7: Frontend-Integrering (Exempel: React)
Här är hur du kan integrera flyer-importen i din frontend:
Kod: FlyerImportForm.tsx
tsx
Copy

import { useState } from 'react';
import axios from 'axios';

function FlyerImportForm() {
  const [file, setFile] = useState<File | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [result, setResult] = useState<any>(null);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!file) return;

    setIsLoading(true);
    const formData = new FormData();
    formData.append('flyer', file);

    try {
      const response = await axios.post('/api/import/flyer', formData, {
        headers: { 'Content-Type': 'multipart/form-data' },
      });
      setResult(response.data);
    } catch (error) {
      console.error('Fel vid uppladdning:', error);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div>
      <h2>Importera flyer</h2>
      <form onSubmit={handleSubmit}>
        <input
          type="file"
          accept=".pdf"
          onChange={(e) => setFile(e.target.files?.[0] || null)}
          required
        />
        <button type="submit" disabled={isLoading}>
          {isLoading ? 'Importerar...' : 'Importera flyer'}
        </button>
      </form>

      {result?.success && (
        <div>
          <h3>Importerade produkter ({result.products.length})</h3>
          <table>
            <thead>
              <tr>
                <th>Namn</th>
                <th>Pris</th>
                <th>Jämförpris</th>
                <th>Kategori</th>
                <th>Erbjudande</th>
              </tr>
            </thead>
            <tbody>
              {result.products.map((product: any, index: number) => (
                <tr key={index}>
                  <td>{product.name}</td>
                  <td>{product.price} {product.unit}</td>
                  <td>{product.comparisonPrice} {product.unit}</td>
                  <td>{product.category}</td>
                  <td>{product.offer.join(', ')}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}

export default FlyerImportForm;




📌 Miljövariabler (.env)
env
Copy

# Mistral API-nyckel
MISTRAL_API_KEY=din_api_nyckel_här