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 { 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 { 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(null); const [isLoading, setIsLoading] = useState(false); const [result, setResult] = useState(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 (

Importera flyer

setFile(e.target.files?.[0] || null)} required />
{result?.success && (

Importerade produkter ({result.products.length})

{result.products.map((product: any, index: number) => ( ))}
Namn Pris JÀmförpris Kategori Erbjudande
{product.name} {product.price} {product.unit} {product.comparisonPrice} {product.unit} {product.category} {product.offer.join(', ')}
)}
); } export default FlyerImportForm; 📌 Miljövariabler (.env) env Copy # Mistral API-nyckel MISTRAL_API_KEY=din_api_nyckel_hĂ€r