Skip to content

Recipe Service

Purpose

The recipe service manages prep recipe documentation — creating, editing, approving, and publishing recipe BOMs (Bills of Materials) along with auto-generated Google Docs for kitchen use. It powers the Recipe Docs tab in OK Manager, which automates BOM updates and Google Doc generation for prep recipes. The service handles the full draft lifecycle from creation through approval, syncing approved BOMs to the Snowflake data warehouse and managing Google Docs across Drafts, Production, and Archive folders.

Note: The Recipes tab in OK Manager also has a bulk operations feature for execution recipe updates (SKU switchouts, modifier changes, quantity updates, packaging changes, and recipe cloning). The backend for bulk operations lives in the legacy C# codebase (/api/BulkOperations/) because it relies on shared .NET methods already built for the recipe tool. This document focuses on the Python recipe service backend.

Development

The recipe service lives in the recipe_service directory. It can be started via:

uvicorn recipe_service.app.main:app --host 0.0.0.0 --port 8003 --reload

Architecture Overview

The recipe service is composed of three main layers — the drafts service (business logic and workflow), the Google Docs service (document generation and management), and the Snowflake merger (production BOM sync). It uses PostgreSQL (Supabase) for draft storage and Snowflake for production BOM data.

Recipe Drafts Service

The drafts service (services/recipe_drafts_service.py) manages the core draft lifecycle. It handles creation, updates, status transitions, starting weight calculation, diff generation against production BOMs, and metadata inheritance from previous drafts. It coordinates between PostgreSQL (draft storage), Snowflake (production BOM reads and writes), and Google Docs (document generation). Snowflake and Google Docs dependencies are lazily initialized so the service can still operate in a degraded state if either is unavailable.

Google Docs Service

The Google Docs service (services/google_docs_service.py) handles all interaction with the Google Docs and Drive APIs using a service account. It generates professionally formatted two-column recipe documents with styled metadata, monospaced ingredient tables with color-coded highlight groups, embedded photos, bulleted equipment/handling notes, and preparation steps with inline ingredient highlighting. It manages documents across three Drive folders — Drafts, Production, and Archive — handling moves, overwrites, and archival with timestamps.

Snowflake Merger

The Snowflake layer (sql/snowflake_sql/merge_sql.py and query_sql.py) handles reading production subrecipe data and writing approved BOMs. On approval, it syncs the draft's ingredients to the SUBRECIPES and SUBRECIPE_DETAILS tables in Snowflake, handling inserts, updates, and deletes to match the approved draft. It auto-generates master_id values for new recipes.

PostgreSQL Layer

The PostgreSQL layer (sql/postgres_sql/recipe_drafts_sql.py) manages all draft metadata and ingredients in Supabase. This includes recipe fields not stored in Snowflake — steps, equipment, handling notes, critical control points, photos, highlight groups, and hidden sections. It uses asyncpg connection pooling.

Draft Workflow

Drafts follow a status-gated workflow: - DRAFT — Editable. Can be updated, deleted, or submitted. - PENDING — Submitted for approval. Can be approved or rejected. Google Doc is generated on submit. - PUBLISHED — Approved. BOM merged to Snowflake, Google Doc moved to Production. - REJECTED — Rejected. Can be edited and resubmitted.

Users can create drafts from scratch or from an existing production subrecipe. When creating from an existing subrecipe, the service pulls the current BOM from Snowflake and inherits PostgreSQL-only metadata (steps, equipment, CCPs, etc.) from the most recent published draft for that subrecipe.

Key Points

  • All operations are synchronous — there are no background workers or job queues. Google Doc generation and Snowflake merges happen inline during requests.
  • Authentication uses JWT tokens issued by the .NET auth service (HS256, orbital.com issuer). Two permission levels: require_mdt_read (MasterItem.Read) and require_mdt_write (MasterItem.Write).
  • Photo uploads go to Azure Blob Storage (recipe-docs container), with a 10 MB limit and JPEG/PNG/WebP/GIF support.
  • The diff endpoint compares a draft's BOM against the current production subrecipe in Snowflake, categorizing ingredients as added, removed, changed, or unchanged.
  • When approving a draft that overwrites an existing production doc, the old doc is archived (copied to the Archive folder with a timestamp) before being replaced.
  • Starting weight auto-calculation sums ingredient quantities converted to a common weight UOM (supports g, kg, oz, lb).
  • Google Docs use 8 hardcoded highlight colors for ingredient groups, which visually link ingredients in the BOM table to their mentions in preparation steps.

External Integrations

Service Purpose Auth
Google Docs API v1 Create, update, and style recipe documents Service account (JSON key from Azure Key Vault)
Google Drive API v3 Manage folders (Drafts/Production/Archive), move and archive docs Service account
Azure Blob Storage Store recipe and step photos Connection string from Azure Key Vault
Snowflake Read/write production BOMs (ORBITAL_KITCHENS_DW.RECIPES) Snowflake credentials
PostgreSQL (Supabase) Store draft metadata, ingredients, doc URLs asyncpg connection pool

Draft Lifecycle Diagram

This diagram shows the flow from draft creation through approval, including BOM sync and Google Doc management.

sequenceDiagram
    participant User as User (OK Manager)
    participant API as Recipe Service API
    participant Svc as RecipeDraftsService
    participant PG as PostgreSQL (Supabase)
    participant SF as Snowflake
    participant GDoc as GoogleDocsService
    participant Drive as Google Drive

    User->>API: POST /recipe-drafts/ (or /from-existing/{uuid})
    API->>Svc: create_draft() or create_from_existing()
    alt From Existing
        Svc->>SF: fetch subrecipe BOM
        Svc->>PG: fetch latest published draft metadata
    end
    Svc->>PG: insert draft + ingredients
    Svc-->>User: draft (status: DRAFT)

    Note over User: Edit draft, add ingredients, upload photos...

    User->>API: POST /{draft_id}/submit
    API->>Svc: submit_for_approval()
    Svc->>PG: update status DRAFT → PENDING
    Svc->>GDoc: generate or update Google Doc
    GDoc->>Drive: create doc in Drafts folder
    Svc-->>User: draft (status: PENDING, google_doc_url)

    User->>API: GET /{draft_id}/diff
    API->>Svc: get_diff()
    Svc->>PG: fetch draft ingredients
    Svc->>SF: fetch production subrecipe
    Svc-->>User: diff (added, removed, changed, unchanged)

    User->>API: POST /{draft_id}/approve
    API->>Svc: approve_draft()
    Svc->>SF: merge BOM (insert/update/delete)
    Svc->>PG: update status PENDING → PUBLISHED
    alt Overwrite Existing Production Doc
        GDoc->>Drive: archive old doc (copy to Archive)
        GDoc->>Drive: update production doc in-place
        GDoc->>Drive: trash draft doc
    else New Production Doc
        GDoc->>Drive: move doc from Drafts to Production
    end
    Svc-->>User: draft (status: PUBLISHED, production_doc_url)

Frontend Notes

Recipe Docs Tab

The Recipe Docs tab in OK Manager (/recipe_docs route) provides three views: - List view — Filterable table of all drafts with status chips, search, and per-row actions (edit, review, open doc, delete). - Form view — Full recipe editor with metadata fields, an interactive ingredient table with cost calculations and color-coded highlight groups, step editor with photo support and ingredient highlighting, and Google Doc generation. - Approval view — Side-by-side layout showing recipe summary with change highlights on the left and a BOM diff table on the right, with approve/reject actions.

Recipes Tab — Bulk Operations

The Recipes tab (/boms route) includes a bulk operations modal with a 4-step wizard (Select Operation, Configure, Preview, Results). Six operations are available:

Operation Purpose
SKU Switchout Replace one ingredient with another across recipes
Add/Update Modifier Add or update modifier ingredients on target recipes
Remove Modifier Remove all ingredients for a modifier from target recipes
Quantity Change Update quantity/UOM for an ingredient across recipes
Packaging Update Add or remove packaging items/modifiers across recipes
Clone Recipe Copy a recipe with all ingredients and packaging

These operations hit the C# backend at POST /api/BulkOperations/Preview and POST /api/BulkOperations/Execute.

Future Direction

The long-term goal is to evolve this into an all-in-one documentation and BOM update tool that replaces the current Google Sheets workflow entirely. With current resources, the prep recipe drafts and doc generation described here made sense as a phase 1.