feat: Add Spotify-style backdrop, Cascade OCR, Smart Scan Flow & OCR Dashboard
- BottleGrid: Implement blurred backdrop effect for bottle cards - Cascade OCR: TextDetector → RegEx → Fuzzy Match → window.ai pipeline - Smart Scan: Native OCR for Android, Live Text fallback for iOS - OCR Dashboard: Admin page at /admin/ocr-logs with stats and scan history - Features: Add feature flags in src/config/features.ts - SQL: Add ocr_logs table migration - Services: Update analyze-bottle to use OpenRouter, add save-ocr-log
This commit is contained in:
78
sql/create_ocr_logs.sql
Normal file
78
sql/create_ocr_logs.sql
Normal file
@@ -0,0 +1,78 @@
|
||||
-- OCR Logs Table for storing cascade OCR results
|
||||
-- This allows admins to view OCR recognition results from mobile devices
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ocr_logs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
||||
bottle_id UUID REFERENCES bottles(id) ON DELETE SET NULL,
|
||||
|
||||
-- Image data
|
||||
image_url TEXT, -- URL to the scanned image
|
||||
image_thumbnail TEXT, -- Base64 thumbnail for quick preview
|
||||
|
||||
-- Detected fields
|
||||
raw_text TEXT, -- All detected text joined
|
||||
detected_texts JSONB, -- Array of individual text detections
|
||||
|
||||
-- Extracted data
|
||||
distillery TEXT,
|
||||
distillery_source TEXT, -- 'fuzzy', 'ai', 'manual'
|
||||
bottle_name TEXT,
|
||||
abv DECIMAL(5,2),
|
||||
age INTEGER,
|
||||
vintage TEXT,
|
||||
volume TEXT,
|
||||
category TEXT,
|
||||
|
||||
-- Meta
|
||||
confidence INTEGER, -- 0-100
|
||||
device_info TEXT, -- User agent or device type
|
||||
ocr_method TEXT, -- 'text_detector', 'fallback', etc.
|
||||
processing_time_ms INTEGER,
|
||||
|
||||
-- Timestamps
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Index for efficient queries
|
||||
CREATE INDEX IF NOT EXISTS idx_ocr_logs_user_id ON ocr_logs(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ocr_logs_created_at ON ocr_logs(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_ocr_logs_distillery ON ocr_logs(distillery);
|
||||
|
||||
-- RLS Policies
|
||||
ALTER TABLE ocr_logs ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Users can view their own logs
|
||||
CREATE POLICY "Users can view own ocr_logs"
|
||||
ON ocr_logs FOR SELECT
|
||||
USING (auth.uid() = user_id);
|
||||
|
||||
-- Users can insert their own logs
|
||||
CREATE POLICY "Users can insert own ocr_logs"
|
||||
ON ocr_logs FOR INSERT
|
||||
WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- Admins can view all logs
|
||||
CREATE POLICY "Admins can view all ocr_logs"
|
||||
ON ocr_logs FOR SELECT
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM admin_users
|
||||
WHERE admin_users.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Trigger for updated_at
|
||||
CREATE OR REPLACE FUNCTION update_ocr_logs_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = now();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trigger_ocr_logs_updated_at
|
||||
BEFORE UPDATE ON ocr_logs
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_ocr_logs_updated_at();
|
||||
36
sql/migrate_blind_tasting.sql
Normal file
36
sql/migrate_blind_tasting.sql
Normal file
@@ -0,0 +1,36 @@
|
||||
-- Add Blind Tasting support to Sessions
|
||||
ALTER TABLE public.tasting_sessions
|
||||
ADD COLUMN IF NOT EXISTS is_blind BOOLEAN DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS is_revealed BOOLEAN DEFAULT false;
|
||||
|
||||
-- Add Guessing fields to Tastings
|
||||
ALTER TABLE public.tastings
|
||||
ADD COLUMN IF NOT EXISTS blind_label TEXT,
|
||||
ADD COLUMN IF NOT EXISTS guess_abv DECIMAL,
|
||||
ADD COLUMN IF NOT EXISTS guess_age INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS guess_region TEXT,
|
||||
ADD COLUMN IF NOT EXISTS guess_points INTEGER;
|
||||
|
||||
-- Update RLS Policies for blind sessions
|
||||
-- Guests should only see bottle details if NOT blind OR revealed
|
||||
-- This is a complex policy update, we'll refine the existing tastings_select_policy
|
||||
|
||||
DROP POLICY IF EXISTS "tastings_select_policy" ON public.tastings;
|
||||
CREATE POLICY "tastings_select_policy" ON public.tastings FOR SELECT USING (
|
||||
-- You can see your own tastings
|
||||
auth.uid() = user_id
|
||||
OR
|
||||
-- You can see tastings in a session you participate in
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM public.session_participants sp
|
||||
JOIN public.buddies b ON b.id = sp.buddy_id
|
||||
WHERE sp.session_id = public.tastings.session_id
|
||||
AND b.buddy_profile_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Note: The logic for hiding bottle details will be handled in the UI/API layer
|
||||
-- as the RLS here still needs to allow access to the tasting record itself.
|
||||
-- Hiding 'bottle_id' content for blind tastings will be done in the frontend
|
||||
-- based on session.is_blind and session.is_revealed.
|
||||
Reference in New Issue
Block a user