Chapters: 
The Flask recipe viewer reads recipes_normalized.jsonl, displays preserved ingredient lines
alongside a session-only editable override copy, and shows a plain-text preview plus a 
mailto: link for emailing the current edits. 

Original data remains untouched; overrides are stored only in the Flask session and are not yet persisted.

 

Flask it is


Recipe Viewer: Flask-Based Override Flow

We ingest recipes/recipes.csv through rag_setup/import_recipes.py, which generates rag_setup/recipes_normalized.jsonl. Each recipe record includes both the older structured fields and the newer raw ingredient layer:

  • ingredient_lines_raw
  • source / status / flags metadata

The Flask viewer in tools/recipe_viewer/app.py reads that JSONL file, filters for recipe_full and recipe_partial, and renders the results.

Flask App Structure

The app has two main routes:

  • / lists recipes with title, classification, and stage
  • /recipe/<idx> shows a single recipe and supports both GET and POST

The recipe detail page displays:

  • classification, stage, and servings
  • preserved ingredient lines
  • recipe body
  • herb/aromatic mentions
  • effective recipe JSON
  • raw JSON dump

Ingredient Line Handling

The ingredient display is split into two layers:

Original

This comes directly from ingredient_lines_raw and is never modified.

After Overrides (Editable)

This is a textarea backed by Flask session storage using a key like recipe_lines_{idx}.

Anything typed there:

  • affects only the current browser session
  • does not change the source record
  • is not written to disk

Preview and Email

Under the editable section, the UI now includes:

  • a plain-text preview of the current edited lines
  • an Email this link using mailto:, which opens the user’s local mail client with the current edits prefilled in the message body

Current Scope

This is a Flask session-based debugging workflow only.

At this stage:

  • original ingredient lines remain untouched
  • edited lines exist only in session
  • nothing is persisted to disk yet

> - Layers + data flow: We ingest recipes/recipes.csv via rag_setup/import_recipes.py, producing
 rag_setup/recipes_normalized.jsonl. Each recipe carries both the legacy structured fields and the
 new raw ingredient layer (ingredient_lines_raw, source/status/flags). The Flask viewer (tools/
 recipe_viewer/app.py) simply reads that JSONL, filters to recipe_full/recipe_partial, and renders
 each record.
 - Flask app structure:
   • index route lists recipes (title/classification/stage).
   • recipe/<idx> route displays one recipe and now accepts GET+POST.
   • Within the detail view we show classification/stage/servings, the preserved ingredient lines,
 body, herb/aromatic mentions, effective recipe JSON, and the raw JSON dump.
   • The “Ingredient Lines” section has two layers:
   – “Original”: straight from ingredient_lines_raw (never touched).
   – “After Overrides (Editable)”: a textarea populated from per-session overrides (stored under
 recipe_lines_{idx} via Flask session). Whatever you type here is kept for that browser session
 only.
 - Tools in play: Flask (with its session store + debug server), standard JSON/CSV parsing, and
 the existing multi_agent_chatbot/effective_recipe.py helper for the “Effective Recipe” preview.
 - UI additions: Under the editable section we now show a live “Preview” of the current lines
 (plain text) plus a mailto link (Email this) that pre-fills an email body with the current edits.
 No data is persisted to disk yet—this is all session-scope debugging.
 

 

 

 

For displaying this kind of data, I’d keep it simple and give yourself two lanes:

Best practical choice

Flask

Run a small Flask app that reads your JSONL and renders:

  • recipe list
  • recipe detail page
  • classification
  • servings
  • herb/aromatic mentions
  • later: effective recipe view and override testing

Why Flask fits:

  • you already know Python
  • dead simple to host
  • perfect for “read data, render page”
  • easy to bolt onto your existing pipeline

That’s the straight road.

If you want something a bit more “app-like”

FastAPI

Good if you want:

  • API endpoints
  • JSON-first design
  • maybe a separate frontend later

But for your current situation, Flask is probably less friction.

What I would run on the public site

If your external web site already runs Python, then I’d do:

  • Flask
  • Jinja templates
  • read from recipes_normalized.jsonl
  • maybe later cache into SQLite

That gives you a tidy little inspection UI in the Internet World.

Suggested shape

Pages

  • /recipes
    list recipes with title, classification, stage
  • /recipes/<id-or-checksum>
    show full normalized record
  • /recipes?classification=recipe_partial
    filter view
  • /recipes/<id>/effective
    later, show base_recipe, overrides, effective_recipe

That last one is where this gets fun.

Data backend

Start with:

  • JSONL on disk

Move to later if needed:

  • SQLite

Why:

  • JSONL is great for pipeline output
  • SQLite is better for filtering, sorting, searching, editing

So the likely evolution is:

Backdrop CSV
→ JSONL normalize
→ Flask reads JSONL
→ later import JSONL into SQLite

If we ignore “reality limits”

If you want the nicest interactive local/public UI without much ceremony:

Flask + SQLite + HTMX

That combo gives you:

  • server-rendered pages
  • no heavy frontend build mess
  • easy filtering/search
  • dynamic-ish feel without SPA nonsense

Very pleasant stack.

If you want pure quick-and-dirty

You could even make:

  • a single Flask page
  • table of recipes
  • click through to detail
  • pretty-print JSON

That would already be hugely useful for debugging.

Perfect. This is the right energy. ⚡

Let’s build the smallest useful thing that gives you visibility.

🎯 Goal

One page:

  • list recipes
  • click → detail view
  • see raw + normalized fields

No ceremony. No framework gymnastics.


🧱 Minimal Flask app

Create app.py:

from flask import Flask, render_template_string
import json

app = Flask(__name__)

DATA_FILE = "rag_setup/recipes_normalized.jsonl"

def load_rows():
    rows = []
    with open(DATA_FILE, encoding="utf-8") as f:
        for line in f:
            rows.append(json.loads(line))
    return rows

@app.route("/")
def index():
    rows = load_rows()
    return render_template_string("""
    <h1>Recipes</h1>
    <table border="1" cellpadding="5">
        <tr>
            <th>Title</th>
            <th>Classification</th>
            <th>Stage</th>
        </tr>
        {% for r in rows %}
        <tr>
            <td><a href="/recipe/{{ loop.index0 }}">{{ r.title }}</a></td>
            <td>{{ r.classification }}</td>
            <td>{{ r.stage }}</td>
        </tr>
        {% endfor %}
    </table>
    """, rows=rows)

@app.route("/recipe/<int:idx>")
def recipe(idx):
    rows = load_rows()
    r = rows[idx]

    return render_template_string("""
    <h1>{{ r.title }}</h1>

    <p><b>Classification:</b> {{ r.classification }}</p>
    <p><b>Stage:</b> {{ r.stage }}</p>
    <p><b>Servings:</b> {{ r.servings }}</p>

    <h2>Body</h2>
    <pre>{{ r.body }}</pre>

    <h2>Herbs</h2>
    <pre>{{ r.herb_mentions }}</pre>

    <h2>Aromatics</h2>
    <pre>{{ r.aromatic_mentions }}</pre>

    <h2>Raw JSON</h2>
    <pre>{{ r | tojson(indent=2) }}</pre>

    <p><a href="/">← Back</a></p>
    """, r=r)

if __name__ == "__main__":
    app.run(debug=True)

▶️ Run it

python app.py

Then open:

http://127.0.0.1:5000

💡 Why this is perfect right now

  • zero setup
  • no templates folder
  • no database
  • reads your JSONL directly
  • instant visibility into:
    • classification
    • servings
    • body
    • extracted fields

🔥 What this unlocks immediately

You can now:

  • eyeball classification errors
  • inspect ingredient text easily
  • verify herb/aromatic detection
  • confirm servings logic
  • debug your pipeline visually

🧠 Tiny upgrade (optional, 2 min)

Add a filter:

@app.route("/recipes/<cls>")
def by_class(cls):
    rows = [r for r in load_rows() if r["classification"] == cls]
    return {"count": len(rows), "titles": [r["title"] for r in rows]}

Now hit:

/recipes/recipe_partial

🏁 Bottom line

This is not your final UI.

This is your debug console with a heartbeat.

And honestly… it’s one of the most valuable tools you’ll build in this whole project.