feat: Add Flight Recorder, Timeline, ABV Curve and Offline Bottle Caching with Draft Notes support

This commit is contained in:
2025-12-19 20:45:20 +01:00
parent 24e243fff8
commit e8c3032954
13 changed files with 864 additions and 337 deletions

View File

@@ -1,69 +1,46 @@
1. Feature: Tasting Notes Auto-Fill (Die "Tag-Matching" Strategie)
3. Timeline & "Flight Recorder" (Reihenfolge-Logik)
Du hast völlig recht: Wenn Gemini einfach wild Text generiert (z.B. "Grüner Granny Smith Apfel"), und deine Datenbank nur "Apfel" kennt, hast du Chaos.
Ziel: Die Geschichte des Abends rekonstruieren. Analyse des Konsumverhaltens.
Feature: Die Timeline-Ansicht
Die Lösung: "Constrained Generation" (Gezwungene Auswahl)
Statt einer einfachen Liste, eine vertikale Zeitstrahl-Ansicht.
Du fütterst Gemini nicht nur mit dem Bild, sondern auch mit deiner Master-Liste an Tags im Prompt.
Der Workflow:
14:00 Uhr: Start der Session "Whisky Herbst".
Input: Bild vom Label + Deine Liste der System Tags (als JSON-String).
14:15 Uhr: Glenfiddich 12 (Mild, Start).
15:30 Uhr: Laphroaig Cask Strength (Der Gaumen-Killer).
Frontend:
16:00 Uhr: Auchentoshan (Schmeckt nach nichts mehr, weil Laphroaig davor war).
Die App empfängt die IDs.
Analyse & Warnungen (Smart Features):
In der UI werden diese Tags aktiviert/vorausgewählt angezeigt (z.B. farbig hinterlegt).
Der "Palette-Checker":
Der User sieht: "Vorschlag: Rauch, Vanille".
Wenn der User einen extrem rauchigen Whisky (80ppm) loggt und 10 Minuten später einen milden Lowlander eintragen will.
Wichtig: Der User kann sie abwählen (wenn er es nicht schmeckt) oder andere aus der Liste hinzufügen.
Warnung (lustig): "Achtung! Du hast gerade eine Torfbombe getrunken. Warte lieber noch 10 Min oder trink Wasser, sonst schmeckst du den nächsten nicht!"
Das ist der "Sweet Spot". Wir kombinieren die harte Fakten-Extraktion (Metadata) mit der "halluzinierten" aber kontrollierten Sensorik (Tags).
ABV-Kurve:
Hier ist dein "Master Prompt", der beides erledigt.
Das Konzept der "Constrained Generation"
Ein Liniendiagramm am Ende der Session: Wie hat sich der Alkoholgehalt entwickelt?
Wichtig: Damit Gemini nicht irgendwelche Wörter erfindet, müssen wir ihm deine Tag-Liste im Prompt mitgeben. Ich habe im Prompt einen Platzhalter {AVAILABLE_TAGS_JSON} eingefügt. Diesen musst du in deinem Code (Next.js API Route oder Edge Function) mit deiner echten Tag-Liste ersetzen, bevor du den String an Gemini schickst.
Der Prompt (Copy & Paste)
Ideal: Langsamer Anstieg.
You are a master sommelier and strict database clerk.
Your task is to analyze the whisky bottle image provided.
Gefährlich: Zick-Zack.
PART 1: METADATA EXTRACTION
Extract precise metadata from the visible label text.
- If the image is NOT a whisky bottle or if you are very unsure, set "is_whisky" to false and provide a low "confidence" score.
- If a value is not visible, use null.
- Infer the 'Category' (e.g., Islay Single Malt, Bourbon, Rye) based on the Distillery if possible.
- Search specifically for a "Whiskybase ID" or "WB ID" on the label (often handwritten or small print).
- Search for "Bottle Codes" (Laser codes often on the glass).
Time-Stamping:
PART 2: SENSORY ANALYSIS (AUTO-FILL)
Based on the identified bottle (using your internal knowledge about this specific release/distillery), select the most appropriate flavor tags.
CONSTRAINT: You must ONLY select tags from the following provided list. Do NOT invent new tags.
If you recognize the whisky, try to select 3-6 tags that best describe its character.
Nutze nicht nur created_at (Upload Zeit), sondern speichere explizit tasted_at.
AVAILABLE TAGS LIST:
{AVAILABLE_TAGS_JSON}
Warum? Wenn du 3 Stunden offline warst und dann online gehst, haben alle 5 Whiskys das gleiche created_at (Upload-Zeitpunkt). Du brauchst den Zeitpunkt, an dem der Button gedrückt wurde (lokale Handy-Zeit).
PART 3: OUTPUT
Output strictly raw JSON matching the following schema (no markdown, no code blocks):
Zusammenfassung für die Session-Logik:
{
"name": string | null,
"distillery": string | null,
"category": string | null,
"abv": number | null,
"age": number | null,
"vintage": string | null,
"bottleCode": string | null,
"whiskybaseId": string | null,
"distilled_at": string | null,
"bottled_at": string | null,
"batch_info": string | null,
"is_whisky": boolean,
"confidence": number,
"suggested_tags": string[]
}
Das Datenmodell muss wissen:
session_start (Zeitstempel)
session_end (Zeitstempel)
Innerhalb der Session: Relative Zeit ("Dram Nr. 3, +45min nach Start").