**Backend (insights.py):**
- Extended _get_profile_data() to fetch sleep, rest_days, vitals
- Added template variables for Sleep Module:
{{sleep_summary}}, {{sleep_detail}}, {{sleep_avg_duration}}, {{sleep_avg_quality}}
- Added template variables for Rest Days:
{{rest_days_summary}}, {{rest_days_count}}, {{rest_days_types}}
- Added template variables for Vitals:
{{vitals_summary}}, {{vitals_detail}}, {{vitals_avg_hr}}, {{vitals_avg_hrv}},
{{vitals_avg_bp}}, {{vitals_vo2_max}}
**Frontend (Analysis.jsx):**
- Added 12 new template variables to VARS list in PromptEditor
- Enables AI prompt creation for Sleep, Rest Days, and Vitals analysis
All modules now have AI evaluation support for future prompt creation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Import endpoints for Omron blood pressure CSV (German date format)
- Import endpoints for Apple Health vitals CSV
- Import UI tab in VitalsPage with drag & drop for both sources
- German month mapping for Omron date parsing ("13 März 2026")
- Upsert logic preserves manual entries (source != 'manual')
- Import result feedback (inserted/updated/skipped/errors)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Avg blood pressure (systolic/diastolic) 7d and 30d
- Latest VO2 Max value
- Avg SpO2 7d and 30d
- Backend now provides all metrics expected by frontend
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migration 014:
- blood_pressure_systolic/diastolic (mmHg)
- pulse (bpm) - during BP measurement
- vo2_max (ml/kg/min) - from Apple Watch
- spo2 (%) - blood oxygen saturation
- respiratory_rate (breaths/min)
- irregular_heartbeat, possible_afib (boolean flags from Omron)
- Added 'omron' to source enum
Backend:
- Updated Pydantic models (VitalsEntry, VitalsUpdate)
- Updated all SELECT queries to include new fields
- Updated INSERT/UPDATE with COALESCE for partial updates
- Validation: at least one vital must be provided
Preparation for Omron + Apple Health imports
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend:
- New router: vitals.py with CRUD endpoints
- GET /api/vitals (list)
- GET /api/vitals/by-date/{date}
- POST /api/vitals (upsert)
- PUT /api/vitals/{id}
- DELETE /api/vitals/{id}
- GET /api/vitals/stats (7d/30d averages, trends)
- Registered in main.py
Frontend:
- VitalsPage.jsx with manual entry form
- List with inline editing
- Stats overview (averages, trend indicators)
- Added to CaptureHub (❤️ icon)
- Route /vitals in App.jsx
API:
- Added vitals methods to api.js
v9d Phase 2d - Vitals tracking complete
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- PostgreSQL returns numeric values as Decimal objects
- psycopg2.Json() cannot serialize Decimal to JSON
- Added convert_decimals() helper function
- Converts activity_data, context, and evaluation_result before saving
Fixes: Batch evaluation errors (31 errors 'Decimal is not JSON serializable')
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Shows first 10 errors with activity_id, training_type_id, and error message
- Helps debug evaluation failures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Admin endpoints for profile configuration:
- Extended TrainingTypeCreate/Update models with profile field
- Added profile column to all SELECT queries
- Profile templates for Running, Meditation, Strength Training
- Template endpoints: list, get, apply
- Profile stats endpoint (configured/unconfigured count)
New file: profile_templates.py
- TEMPLATE_RUNNING: Endurance-focused with HR zones
- TEMPLATE_MEDITATION: Mental-focused (low HR ≤ instead of ≥)
- TEMPLATE_STRENGTH: Strength-focused
API Endpoints:
- GET /api/admin/training-types/profiles/templates
- GET /api/admin/training-types/profiles/templates/{key}
- POST /api/admin/training-types/{id}/profile/apply-template
- GET /api/admin/training-types/profiles/stats
Next: Frontend Admin-UI (ProfileEditor component)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SQL Error: VALUES lists must all be the same length (line 130)
Cause: kcal_per_km row was missing validation_rules JSONB value
Fixed: Added validation_rules '{"min": 0, "max": 1000}'::jsonb
All 16 parameter rows now have correct 10 columns:
key, name_de, name_en, category, data_type, unit, source_field,
validation_rules, description_de, description_en
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Problem: Backend crashed on startup due to evaluation import failure
Solution: Wrap evaluation_helper import in try/except
Changes:
- Import evaluation_helper with error handling
- Add EVALUATION_AVAILABLE flag
- All evaluation calls now check flag before executing
- System remains functional even if evaluation system unavailable
This prevents backend crashes if:
- Migrations haven't run yet
- Dependencies are missing
- Import errors occur
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Automatic evaluation on activity INSERT/UPDATE:
- create_activity(): Evaluate after manual creation
- update_activity(): Re-evaluate after manual update
- import_activity_csv(): Evaluate after CSV import (INSERT + UPDATE)
- bulk_categorize_activities(): Evaluate after bulk training type assignment
All evaluation calls wrapped in try/except to prevent activity operations
from failing if evaluation encounters an error. Only activities with
training_type_id assigned are evaluated.
Phase 1.2 complete ✅
## Next Steps (Phase 2):
Admin-UI for training type profile configuration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Problem: Creating new training types via Admin UI resulted in
'Internal Server Error' because abilities dict was passed directly
to PostgreSQL JSONB column without Json() wrapper.
Solution:
- Import Json from psycopg2.extras
- Wrap abilities_json with Json() in INSERT
- Wrap data.abilities with Json() in UPDATE
Same issue as rest_days JSONB fix (commit 7d627cf).
Closes#13
Problem: User can create multiple rest days of same type per date
(e.g., 2x Mental Rest on 2026-03-23) - makes no sense.
Solution: UNIQUE constraint on (profile_id, date, focus)
## Migration 012:
- Add focus column (extracted from rest_config JSONB)
- Populate from existing data
- Add NOT NULL constraint
- Add CHECK constraint (valid focus values)
- Add UNIQUE constraint (profile_id, date, focus)
- Add index for performance
## Backend:
- Insert focus column alongside rest_config
- Handle UniqueViolation gracefully
- User-friendly error: "Du hast bereits einen Ruhetag 'Muskelregeneration' für 23.03."
## Benefits:
- DB-level enforcement (clean)
- Fast queries (no JSONB scan)
- Clear error messages
- Prevents: 2x muscle_recovery same day
- Allows: muscle_recovery + mental_rest same day ✓
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migration 011 removed UNIQUE constraint (profile_id, date) to allow
multiple rest days per date, but INSERT still used ON CONFLICT.
Error: psycopg2.errors.InvalidColumnReference: there is no unique or
exclusion constraint matching the ON CONFLICT specification
Solution: Remove ON CONFLICT clause, use plain INSERT.
Multiple entries per date now allowed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Error: psycopg2.ProgrammingError: can't adapt type 'dict'
Solution: Import psycopg2.extras.Json and wrap config_dict
Changes:
- Import Json from psycopg2.extras
- Wrap config_dict with Json() in INSERT
- Wrap config_dict with Json() in UPDATE
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Problem: Photos were always getting NULL date instead of form date,
causing frontend to fallback to created timestamp (today).
Root cause: FastAPI requires Form() wrapper for form fields when
mixing with File() parameters. Without it, the date parameter was
treated as query parameter and always received empty string.
Solution:
- Import Form from fastapi
- Change date parameter from str="" to str=Form("")
- Return photo_date instead of date in response (consistency)
Now photos correctly use the date from the upload form and can be
backdated when uploading later.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Problem:
- Photo upload with empty date parameter (date='')
- PostgreSQL rejects empty string for DATE field
- Error: "invalid input syntax for type date: ''"
- Occurred when saving circumference entry with only photo
Fix:
- Convert empty string to NULL before INSERT
- Check: date if date and date.strip() else None
- NULL is valid for optional date field
Test case:
- Circumference entry with only photo → should work now
- Photo without date → stored with date=NULL ✓
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Conceptual change: duration_minutes = actual sleep time (not time in bed)
Backend:
- Plausibility check: deep + rem + light = duration (awake separate)
- Import: duration = deep + rem + light (without awake)
- Updated error message: clarifies awake not counted
Frontend:
- Label: "Schlafdauer (reine Schlafzeit, Minuten)"
- Auto-calculate: bedtime-waketime minus awake_minutes
- Plausibility check: only validates sleep phases (not awake)
- Both NewEntry and Edit mode updated
Rationale:
- Standard in sleep tracking (Apple Health shows "Sleep", not "Time in Bed")
- Clearer semantics: duration = how long you slept
- awake_minutes tracked separately for analysis
- More intuitive for users
Example:
- Time in bed: 22:00 - 06:00 = 480 min (8h)
- Awake phases: 30 min
- Sleep duration: 450 min (7h 30min) ✓
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Problem: Segments crossing midnight were split into different nights
- 22:30-23:15 (21.03) → assigned to 21.03
- 00:30-02:45 (22.03) → assigned to 22.03
But both belong to the same night (21/22.03)!
Solution: Gap-based grouping
- Sort segments chronologically
- Group segments with gap < 2 hours
- Night date = wake_time.date() (last segment's end date)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend:
- New endpoint POST /api/sleep/import/apple-health
- Parses Apple Health sleep CSV format
- Maps German phase names (Kern→light, REM→rem, Tief→deep, Wach→awake)
- Aggregates segments by night (wake date)
- Stores raw segments in JSONB (sleep_segments)
- Does NOT overwrite manual entries (source='manual')
Frontend:
- Import button in SleepPage with file picker
- Progress indicator during import
- Success/error messages
- Auto-refresh after import
Documentation:
- Added architecture rules reference to CLAUDE.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Creates rest_days table for rest day tracking
- Creates vitals_log table for resting HR + HRV
- Creates weekly_goals table for training planning
- Extends profiles with hf_max and sleep_goal_minutes columns
- Extends activity_log with avg_hr and max_hr columns
- Fixes sleep_goal_minutes missing column error in stats endpoint
- Includes stats error handling in SleepWidget
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PostgreSQL TIME type doesn't accept empty strings.
Converting empty bedtime/wake_time to None before INSERT/UPDATE.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Profile IDs are UUID type in the profiles table, not VARCHAR.
This was causing foreign key constraint error on migration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add sleep_log table with JSONB sleep_segments (Migration 009)
- Add sleep router with CRUD + stats endpoints (7d avg, 14d debt, trend, phases)
- Add SleepPage with quick/detail entry forms and inline edit
- Add SleepWidget to Dashboard showing last night + 7d average
- Add sleep navigation entry with Moon icon
- Register sleep router in main.py
- Add 9 new API methods in api.js
Phase 2b complete - ready for testing on dev
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Issue 1: Automatic training type mapping didn't work
- Root cause: Only English workout names were mapped
- Solution: Added 20+ German workout type mappings:
- "Traditionelles Krafttraining" → hypertrophy
- "Outdoor Spaziergang" → walk
- "Innenräume Spaziergang" → walk
- "Matrial Arts" → technique (handles typo)
- "Cardio Dance" → dance
- "Geist & Körper" → yoga
- Plus: Laufen, Gehen, Radfahren, Schwimmen, etc.
Issue 2: Reimporting CSV created duplicates without training types
- Root cause: Import always did INSERT with new UUID, no duplicate check
- Solution: Check if entry exists (profile_id + date + start_time)
- If exists: UPDATE with new data + training type mapping
- If new: INSERT as before
- Handles multiple workouts per day (different start times)
- "Skipped" count now includes updated entries
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend (v9d Phase 1b):
- Migration 006: Add abilities JSONB column + descriptions
- admin_training_types.py: Full CRUD endpoints for training types
- List, Get, Create, Update, Delete
- Abilities taxonomy endpoint (5 dimensions: koordinativ, konditionell, kognitiv, psychisch, taktisch)
- Validation: Cannot delete types in use
- Register admin_training_types router in main.py
Frontend:
- AdminTrainingTypesPage: Full CRUD UI
- Create/edit form with all fields (category, subcategory, names, icon, descriptions, sort_order)
- List grouped by category with color coding
- Delete with usage check
- Note about abilities mapping coming in v9f
- Add TrainingTypeDistribution to ActivityPage stats tab
- Add admin link in AdminPanel (v9d section)
- Update api.js with admin training types methods
Notes:
- Abilities mapping UI deferred to v9f (flexible prompt system)
- Placeholders (abilities column) in place for future AI analysis
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Migration 005: Add cardio subcategories (Gehen, Tanzen)
- Migration 005: Add new category "Geist & Meditation" with 4 subcategories
(Meditation, Atemarbeit, Achtsamkeit, Visualisierung)
- Update categories endpoint with mind category metadata
- Update Apple Health mapping: dance → dance, add meditation/mindfulness
- 6 new training types total
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add get_training_type_for_apple_health() mapping function (23 workout types)
- CSV import now automatically assigns training_type_id/category/subcategory
- New endpoint: GET /activity/uncategorized (grouped by activity_type)
- New endpoint: POST /activity/bulk-categorize (bulk update training types)
- New component: BulkCategorize with two-level dropdown selection
- ActivityPage: new "Kategorisieren" tab for existing activities
- Update CLAUDE.md: v9d Phase 1b progress
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 1: Training Types Basis
=============================
Backend:
- Migration 004: training_types table + seed data (24 types)
- New router: /api/training-types (grouped, flat, categories)
- Extend activity_log: training_type_id, training_category, training_subcategory
- Extend ActivityEntry model: support training type fields
Frontend:
- TrainingTypeSelect component (two-level dropdown)
- TrainingTypeDistribution component (pie chart)
- API functions: listTrainingTypes, listTrainingTypesFlat, getTrainingCategories
Quick Win: Logout Button
========================
- Add LogOut icon button in app header
- Confirm dialog before logout
- Redirect to / after logout
- Hover effect: red color on hover
Not yet integrated:
- TrainingTypeSelect not yet in ActivityPage form
- TrainingTypeDistribution not yet in Dashboard
(will be added in next commit)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Use window.location.href instead of navigate() for reliable redirect
2. Improve backend error message for already-used verification tokens
3. Show user-friendly message when token was already verified
4. Reduce redirect delay from 2s to 1.5s for better UX
Fixes:
- Empty page after email verification
- Generic error when clicking verification link twice
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend fixes:
- Fixed timezone-aware datetime comparison in verify_email endpoint
- Added trial_ends_at (14 days) for new registrations
- All datetime.now() calls now use timezone.utc
Frontend additions:
- Added EmailVerificationBanner component for unverified users
- Banner shows warning before trial banner in Dashboard
- Clear messaging about verification requirement
This fixes the 500 error on email verification and ensures new users
see both verification and trial status correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Modified run_migrations() to only process files matching pattern: \d{3}_*.sql
This prevents utility scripts (check_features.sql) and manually applied
migrations (v9c_*.sql) from being executed.
Only properly numbered migrations like 003_add_email_verification.sql
will be processed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added migration tracking and execution to db_init.py:
- Created schema_migrations table to track applied migrations
- Added run_migrations() to automatically apply pending SQL files
- Migrations from backend/migrations/*.sql are now applied on startup
This fixes the missing email verification columns (migration 003).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend:
- New endpoint: POST /api/auth/register
- New endpoint: GET /api/auth/verify/{token}
- Migration: Add email_verified, verification_token, verification_expires
- Helper: send_email() for reusable SMTP
- Validation: email format, password length (min 8), name
- Auto-login after verification (returns session token)
- Rate limit: 3 registrations per hour per IP
Features:
- Verification token valid for 24h
- Existing users marked as verified (grandfather clause)
- SMTP configured via .env (SMTP_HOST, SMTP_USER, SMTP_PASS)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Features:
- Manual entry form above data list
- Date picker with auto-load existing entries
- Upsert logic: creates new or updates existing entry
- Smart button text: "Hinzufügen" vs "Aktualisieren"
- Prevents duplicate entries per day
- Feature enforcement for nutrition_entries
Backend:
- POST /nutrition - Create or update entry (upsert)
- GET /nutrition/by-date/{date} - Load entry by date
- Auto-detects existing entry and switches to UPDATE mode
- Increments usage counter only on INSERT
Frontend:
- EntryForm component with date picker + macros inputs
- Auto-loads data when date changes
- Shows info message when entry exists
- Success/error feedback
- Disabled state while loading/saving
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Features:
- Import history panel showing all CSV imports with date, count, and range
- Edit/delete functionality for nutrition entries (inline editing)
- New backend endpoints: GET /import-history, PUT /{id}, DELETE /{id}
UI Changes:
- Import history displayed under import panel
- "Daten" tab now has edit/delete buttons per entry
- Inline form for editing macros (kcal, protein, fat, carbs)
- Confirmation dialog for deletion
Backend:
- nutrition.py: Added import_history, update_nutrition, delete_nutrition endpoints
- Groups imports by created date to show history
Frontend:
- NutritionPage: New DataTab and ImportHistory components
- api.js: Added nutritionImportHistory, updateNutrition, deleteNutrition
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Problem:
- /api/nutrition/weekly crashed with 500 Internal Server Error
- TypeError: strptime() argument 1 must be str, not datetime.date
Root Cause:
- d['date'] from PostgreSQL is already datetime.date object
- datetime.strptime() expects string input
- Line 156: wk=datetime.strptime(d['date'],'%Y-%m-%d').strftime('%Y-W%V')
Solution:
- Added type check before strptime()
- If date already has strftime method → use directly
- Else → parse as string first
- Works with both datetime.date objects and strings
Tested:
- /nutrition page loads without error
- Weekly aggregation works correctly
- Chart displays nutrition data
Closes: BUG-001
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Behebt IndentationError in Zeile 204 der _check_impl() Funktion.
Die Funktion wurde beim Connection-Pool-Fix erstellt, hatte aber
inkonsistente Einrückungen (8 statt 4 Spaces nach der ersten Zeile).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add user-facing usage overview endpoint
- Returns all features with usage, limits, reset info
- Fully dynamic - automatically includes new features
- Phase 3: Frontend Display preparation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create feature_logger.py with JSON logging infrastructure
- Add log_feature_usage() calls to all 9 routers after check_feature_access()
- Logs written to /app/logs/feature-usage.log
- Tracks all usage (not just violations) for future analysis
- Phase 2: Non-blocking monitoring complete
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes two critical bugs in AI analysis storage:
1. History now works - analyses are saved, not overwritten
- Removed DELETE statements before INSERT in insights.py
- All analyses are now preserved per scope
- Displayed in descending order by creation date
2. Pipeline saves under correct scope 'pipeline' instead of 'gesamt'
- Changed scope from 'gesamt' to 'pipeline' in pipeline endpoint
- Pipeline results now appear under correct category in history
3. Fixed pipeline appearing twice in UI
- Filter now excludes both 'pipeline_*' and 'pipeline' from individual list
- Pipeline only appears in dedicated section at top
Changes:
- backend/routers/insights.py: Removed DELETE, changed scope to 'pipeline'
- frontend/src/pages/Analysis.jsx: Fixed filter to exclude 'pipeline'
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reverts all feature enforcement changes (commits 3745ebd, cbad50a, cd4d912, 8415509)
to restore original working functionality.
Issues caused by feature enforcement implementation:
- Export buttons disappeared and never reappeared
- KI analysis counter not incrementing
- New analyses not saving
- Pipeline appearing twice
- Many core features broken
Restored files to working state before enforcement implementation (commit 0210844):
- Backend: auth.py, insights.py, exportdata.py, importdata.py, nutrition.py, activity.py
- Frontend: Analysis.jsx, SettingsPage.jsx, api.js
- Removed: FeatureGate.jsx, useFeatureAccess.js
The original simple AI limit system (ai_enabled, ai_limit_day) is now active again.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical bug: usage limits were never resetting after first month because
reset_at timestamp was not updated during ON CONFLICT UPDATE.
This caused users to stay permanently blocked after reaching monthly limit once.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical fixes for feature enforcement:
- Add GET /api/features/{feature_id}/check-access endpoint (was missing!)
- Add migration for missing features: data_export, csv_import
- These features were used in frontend but didn't exist in DB
This fixes:
- "No analysis available" when setting KI limit
- Export features not working
- Frontend calling non-existent API endpoint
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove active=true filter - admins need to configure all tiers
- Add reset_period to features query for frontend display
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New router: routers/coupons.py
Admin endpoints:
- GET /api/coupons - List all coupons with stats
- POST /api/coupons - Create new coupon
- PUT /api/coupons/{id} - Update coupon
- DELETE /api/coupons/{id} - Soft-delete (set active=false)
- GET /api/coupons/{id}/redemptions - Redemption history
User endpoints:
- POST /api/coupons/redeem - Redeem coupon code
Features:
- Three coupon types: single_use, period, wellpass
- Wellpass logic: Pauses existing personal grants, resumes after expiry
- Max redemptions limit (NULL = unlimited)
- Validity period checks
- Activity logging
- Duplicate redemption prevention
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New router: routers/subscription.py
Endpoints:
- GET /api/subscription/me - Own subscription info (tier, trial, grants)
- GET /api/subscription/usage - Feature usage with limits
- GET /api/subscription/limits - All feature limits for current tier
Features:
- Shows effective tier (considers access_grants)
- Lists active access grants (from coupons, trials)
- Per-feature usage tracking
- Email verification status
Uses new middleware: get_effective_tier(), check_feature_access()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changed all profile_id columns from TEXT to UUID to match profiles.id type.
Changed all auto-generated IDs from gen_random_uuid() to uuid_generate_v4()
to match existing schema.sql convention.
Fixed tables:
- tier_limits: id TEXT → UUID
- user_feature_restrictions: id, profile_id, created_by TEXT → UUID
- user_feature_usage: id, profile_id TEXT → UUID
- coupons: id, created_by TEXT → UUID
- coupon_redemptions: id, coupon_id, profile_id, access_grant_id TEXT → UUID
- access_grants: id, profile_id, coupon_id, paused_by TEXT → UUID
- user_activity_log: id, profile_id TEXT → UUID
- user_stats: profile_id TEXT → UUID
- profiles.invited_by: TEXT → UUID
This fixes: foreign key constraint "user_feature_restrictions_profile_id_fkey"
cannot be implemented - Key columns "profile_id" and "id" are of
incompatible types: text and uuid
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PROBLEM:
- Backend crasht beim Start auf Prod
- Migration schlägt fehl: column 'meas_id' does not exist
- SQLite ai_insights hat Legacy-Spalte meas_id
- PostgreSQL schema hat diese Spalte nicht mehr
FIX:
- COLUMN_WHITELIST für ai_insights hinzugefügt
- Nur erlaubte Spalten werden migriert:
id, profile_id, scope, content, created
- meas_id wird beim Import gefiltert
DATEIEN:
- backend/migrate_to_postgres.py
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PROBLEM:
- Backend crasht beim Start auf Prod
- Login zeigt HTML statt JSON (Backend nicht erreichbar)
- Ursache: init_db() wirft Exception beim Startup
FIX:
1. startup_event() wrapped in try-except (non-fatal)
2. init_db() prüft ob ai_prompts Tabelle existiert
3. init_db() hat eigenen try-except
4. Bessere Fehlermeldungen in stdout
ERGEBNIS:
- Backend startet auch wenn init_db() fehlschlägt
- Pipeline-Prompt kann manuell angelegt werden falls nötig
- Login funktioniert wieder
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- /api/prompts checkt nun ob User admin ist
- Admins sehen ALLE Prompts (inkl. pipeline_ und inaktive)
- Normale User sehen nur aktive Einzelanalysen (wie bisher)
- Frontend (Analysis.jsx) zeigt Pipeline-Prompts bereits korrekt:
* Gruppiert nach "Einzelanalysen" und "Mehrstufige Pipeline"
* JSON-Prompts (Stage 1) mit oranger Border und Badge
* Warnung über JSON-Format bereits vorhanden
- CSS-Variablen --warn, --warn-bg, --warn-text bereits definiert
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Backend: POST /api/import/zip endpoint with validation and rollback
- CSV import with ON CONFLICT DO NOTHING for duplicate detection
- Photo import with existence check
- AI insights import
- Frontend: file upload UI in SettingsPage
- Import summary showing count per category
- Full transaction rollback on error
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PostgreSQL returns dob as datetime.date object, not string.
Changed from prof['dob'][:4] to prof['dob'].year
Error was: TypeError: 'datetime.date' object is not subscriptable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixed TypeError when preparing AI prompt template variables.
PostgreSQL returns NUMERIC columns as decimal.Decimal, not float.
**Fixed in _prepare_template_vars:**
- Weight calculations (protein targets, delta)
- Nutrition averages (kcal, protein, fat, carbs)
- Activity totals (kcal_active)
All Decimal values now converted to float before math operations.
Error was: "TypeError: unsupported operand type(s) for *: 'decimal.Decimal' and 'float'"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixed 11 critical endpoint mismatches found during codebase audit.
**Renamed Endpoints (consistency):**
- /api/ai/analyze/{slug} → /api/insights/run/{slug}
- /api/ai/analyze-pipeline → /api/insights/pipeline
- /api/auth/password-reset-request → /api/auth/forgot-password
- /api/auth/password-reset-confirm → /api/auth/reset-password
- /api/admin/test-email → /api/admin/email/test
**Added Missing Endpoints:**
- POST /api/auth/pin (change PIN/password for current user)
- PUT /api/admin/profiles/{id}/permissions (set permissions)
- PUT /api/admin/profiles/{id}/email (set email)
- PUT /api/admin/profiles/{id}/pin (admin set PIN)
- GET /api/admin/email/status (check SMTP config)
- PUT /api/prompts/{id} (edit prompt templates, admin only)
- GET /api/export/json (export all data as JSON)
- GET /api/export/zip (export data + photos as ZIP)
**Updated:**
- Added imports: json, zipfile, Response
- Fixed admin email test endpoint to accept dict body
All frontend API calls now have matching backend implementations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add GET /api/insights (returns all insights for profile)
- Add DELETE /api/insights/{id} (delete by ID, not scope)
- Frontend Analysis.jsx needs these endpoints to load/delete insights
Fixes 404 error preventing prompts from displaying.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Change WHERE active=1 to WHERE active=true (PostgreSQL uses boolean)
- Change endpoint from /api/ai/prompts to /api/prompts (simpler path)
- Fixed 5 occurrences across prompt-related queries
This fixes the issue where no prompts were returned, causing empty
prompt list in Admin and no AI analysis options.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add GET /api/insights/latest (returns latest 10 insights)
- Add GET /api/auth/status (health check endpoint)
These endpoints were called by frontend but returned 404,
causing uncaught promise errors that blocked page loading.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
RealDictCursor returns dicts, not tuples. Cannot use [0] for index access.
Changed all COUNT(*) to COUNT(*) as count and access via ['count'].
Fixes: KeyError: 0 on cur.fetchone()[0]
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All conn.cursor() calls replaced with get_cursor(conn) to enable
dict-like row access (prof['pin_hash'] instead of prof[column_index]).
This fixes KeyError when accessing PostgreSQL query results.
Fixes: 'tuple' object has no attribute '__getitem__' with string keys
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PostgreSQL DATE type doesn't accept empty strings ('').
Convert empty/whitespace date values to NULL during migration.
Fixes: invalid input syntax for type date: ""
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SQLite schema (v9a) has meas_id in photos table, but PostgreSQL
schema (v9b) was missing it. This caused migration to fail.
Added meas_id as nullable UUID column for backward compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bug: db_init.py was importing migrate_to_postgres but not calling main().
Result: Migration appeared successful but no data was migrated (0 users).
Fix: Import and call migrate_to_postgres.main()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PostgreSQL does not support IF NOT EXISTS for CREATE TRIGGER.
Use DROP TRIGGER IF EXISTS before CREATE TRIGGER instead.
Fixes: Backend crash loop due to schema.sql syntax error on line 231
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove postgresql-client installation (causes 150s+ hangs due to network)
- Add db_init.py: Pure Python PostgreSQL checks using psycopg2-binary
- Simplify startup.sh: Call Python script instead of psql commands
- Build should now complete in <30s instead of hanging
This fixes the deployment timeout issue by avoiding APT network problems entirely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Force IPv4 (IPv6 shows 33% packet loss)
- Increase retries to 5
- Add 10s timeouts to fail fast and retry
- Previous fix improved from 1630s to 78s but still hangs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Switch from deb.debian.org to ftp.de.debian.org (33% packet loss observed)
- Add APT retry logic (3 attempts) for flaky connections
- Fixes deployment timeout on backend build (postgresql-client install)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>