Engine Architecture & Automated Pipelines

System Architecture

To support our meal-planning e-commerce app, we designed a backend that transforms unstructured text input (such as copying a food recipe URL or raw text ingredients list) into a list of purchasable organic products.

The service is built on top of a modular NestJS framework, chosen for its strong TypeScript architecture, dependency injection patterns, and decoupled service layers. The architecture is split into three main modules:

  • Recipe Parser Gateway: Communicates with OpenAI's API (`gpt-4o-mini`) using strict JSON schema validation to extract ingredients, quantities, and units from plain text.
  • Normalizer Engine: Translates arbitrary culinary descriptions (like "half a cup", "a bunch", or "a pinch") into standardized SI units (grams, milliliters) or unit counts using conversion coefficients.
  • Catalog Matching Service: Interfaces with a PostgreSQL database via Prisma ORM, executing fuzzy string searches and word-similarity matches to align standardized ingredients with real, available inventory SKUs.

Database Schema & Prisma

Our entity relations map weekly schedules, user plans, parsed recipes, catalog items, and supplier details. We use Prisma to enforce type safety and write clean relational queries.

In alignment with our business strategy, our sourcing system prioritizes local family-farms practicing agroecology. If a product is unavailable, it falls back to wholesale backup supplies (Assaí Atacadista). The relational schema enforces this by linking each SKU to a specific supplier type:

enum SupplierType {
  LOCAL_AGROECOLOGICAL   // Local agroecological family farms
  WHOLESALE_BACKUP       // Assaí Atacadista (Fulfillment backup)
}

model Supplier {
  id          String             @id @default(uuid())
  name        String             // e.g. "Cooperativa Agroecológica de Irecê"
  type        SupplierType
  skus        Sku[]
  createdAt   DateTime           @default(now())
}

model Sku {
  id             String          @id @default(uuid())
  name           String          // e.g. "Organic Hass Avocado (Pack of 4)"
  price          Decimal
  stock          Int
  supplierId     String
  supplier       Supplier        @relation(fields: [supplierId], references: [id])
  ingredients    RecipeIngredient[]
}

model Recipe {
  id          String             @id @default(uuid())
  title       String
  sourceUrl   String?
  ingredients RecipeIngredient[]
  createdAt   DateTime           @default(now())
}

model RecipeIngredient {
  id             String          @id @default(uuid())
  recipeId       String
  recipe         Recipe          @relation(fields: [recipeId], references: [id])
  rawText        String          // e.g. "3 ripe Hass avocados, cubed"
  standardName   String          // e.g. "Organic Hass Avocado"
  quantity       Float           // e.g. 3.0
  unit           String          // e.g. "pieces"
  matchedSkuId   String?
  matchedSku     Sku?            @relation(fields: [matchedSkuId], references: [id])
}

LLM Recipe Scraper

To parse raw recipe details, our service uses the OpenAI API. Ingesting unstructured text relies on structured prompt engineering to coerce the model to output strict JSON schemas.

The service passes the raw text to `gpt-4o-mini` with a system prompt detailing the output template and forcing a strict JSON format structure:

const systemPrompt = `
  You are an expert culinary parser. Given an unstructured list of ingredients, extract them into a valid JSON array.
  
  JSON Schema format:
  [
    {
      "ingredient": "string (lowercase, singular, standard name)",
      "quantity": "number (float)",
      "unit": "string (pieces, grams, ml, cup, pinch, tablespoon, teaspoon)"
    }
  ]
  
  Only output JSON. Do not add markdown blocks or notes.
`;

We perform secondary runtime validation on the returned payload using Zod schemas. If the model fails validation (e.g. returning strings instead of floats for quantities), NestJS captures the exception and runs a fallback Regex parser.

Ingredient Matching Pipeline

Once ingredients are standardized, we need to map them to active store items. Standardizing quantities is crucial: we convert cups, tablespoons, and ounces to SI units using localized coefficients. For example:

1 Cup of Water / Oil = 240 ml
1 Cup of Flour = 120 grams
1 Tablespoon = 15 grams / ml

To query SKUs matching the parsed ingredients, we use PostgreSQL's pg_trgm trigram word-similarity matching, but filter results based on supplier strategy.

Our query prioritization order is:

  1. Local Agroecological: Find the best fuzzy match supplied by local chemical-free family farms with stock > 0.
  2. Wholesale Backup: If no local supplier has stock, fall back to the backup supplier (Assaí Atacadista) to avoid order failure.
// Query mapping that prioritizes local agroecological suppliers
async findSkuBySimilarity(name: string, threshold = 0.4) {
  // 1. Try local agroecological suppliers first
  const localMatch = await this.prisma.$queryRaw<Sku[]>`
    SELECT s.id, s.name, s.price, s.stock,
           similarity(s.name, ${name}) AS score
    FROM "Sku" s
    JOIN "Supplier" sup ON s."supplierId" = sup.id
    WHERE similarity(s.name, ${name}) > ${threshold}
      AND s.stock > 0
      AND sup.type = 'LOCAL_AGROECOLOGICAL'
    ORDER BY score DESC
    LIMIT 1
  `;

  if (localMatch.length > 0) return localMatch[0];

  // 2. Fallback to wholesale backup (e.g. Assaí Atacadista)
  const backupMatch = await this.prisma.$queryRaw<Sku[]>`
    SELECT s.id, s.name, s.price, s.stock,
           similarity(s.name, ${name}) AS score
    FROM "Sku" s
    JOIN "Supplier" sup ON s."supplierId" = sup.id
    WHERE similarity(s.name, ${name}) > ${threshold}
      AND s.stock > 0
      AND sup.type = 'WHOLESALE_BACKUP'
    ORDER BY score DESC
    LIMIT 1
  `;

  return backupMatch[0] || null;
}

Interactive Pipeline Sandbox

Test out the interactive pipeline below to see how a raw avocado salad recipe text block progresses through our parsing, normalizer, trigram database matching, and cart-loading operations:

Interactive Ingestion Pipeline SimulationSimulate how unstructured recipe ingredients map to standard organic SKUs.
1. LLM ParserGPT-4o-mini extraction
2. NormalizerQty & Unit standardization
3. Trigram LookupFuzzy similarity matching
4. Cart PopulateSKU basket delivery
Pipeline InputStatus: IDLE
Pasted recipe text:
Healthy Avocado Salad
- 3 ripe Hass avocados, cubed
- 1 cup fresh organic cherry tomatoes, halved
- 1/2 cup organic extra virgin olive oil
- 1 pinch of sea salt
Live Checkout Cart
Cart is currently empty. Run the ingestion pipeline to map and populate matched items.