Compare commits
2 Commits
8be87bfdfb
...
b3cc588293
| Author | SHA1 | Date | |
|---|---|---|---|
| b3cc588293 | |||
| c9e4b6aa02 |
181
backend/check_migration_024.py
Normal file
181
backend/check_migration_024.py
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Quick diagnostic: Check Migration 024 state
|
||||
|
||||
Run this inside the backend container:
|
||||
docker exec bodytrack-dev-backend-1 python check_migration_024.py
|
||||
"""
|
||||
|
||||
import psycopg2
|
||||
import os
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
# Database connection
|
||||
DB_HOST = os.getenv('DB_HOST', 'db')
|
||||
DB_PORT = os.getenv('DB_PORT', '5432')
|
||||
DB_NAME = os.getenv('DB_NAME', 'bodytrack')
|
||||
DB_USER = os.getenv('DB_USER', 'bodytrack')
|
||||
DB_PASS = os.getenv('DB_PASSWORD', '')
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print("Migration 024 Diagnostic")
|
||||
print("=" * 70)
|
||||
|
||||
# Connect to database
|
||||
conn = psycopg2.connect(
|
||||
host=DB_HOST,
|
||||
port=DB_PORT,
|
||||
dbname=DB_NAME,
|
||||
user=DB_USER,
|
||||
password=DB_PASS
|
||||
)
|
||||
cur = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
# 1. Check if table exists
|
||||
print("\n1. Checking if goal_type_definitions table exists...")
|
||||
cur.execute("""
|
||||
SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_name = 'goal_type_definitions'
|
||||
)
|
||||
""")
|
||||
exists = cur.fetchone()['exists']
|
||||
print(f" ✓ Table exists: {exists}")
|
||||
|
||||
if not exists:
|
||||
print("\n❌ TABLE DOES NOT EXIST - Migration 024 did not run!")
|
||||
print("\nRECOMMENDED ACTION:")
|
||||
print(" 1. Restart backend container: docker restart bodytrack-dev-backend-1")
|
||||
print(" 2. Check logs: docker logs bodytrack-dev-backend-1 | grep 'Migration'")
|
||||
cur.close()
|
||||
conn.close()
|
||||
return
|
||||
|
||||
# 2. Check row count
|
||||
print("\n2. Checking row count...")
|
||||
cur.execute("SELECT COUNT(*) as count FROM goal_type_definitions")
|
||||
count = cur.fetchone()['count']
|
||||
print(f" Row count: {count}")
|
||||
|
||||
if count == 0:
|
||||
print("\n❌ TABLE IS EMPTY - Seed data was not inserted!")
|
||||
print("\nPOSSIBLE CAUSES:")
|
||||
print(" - INSERT statements failed (constraint violation?)")
|
||||
print(" - Migration ran partially")
|
||||
print("\nRECOMMENDED ACTION:")
|
||||
print(" Run the seed statements manually (see below)")
|
||||
else:
|
||||
print(f" ✓ Table has {count} entries")
|
||||
|
||||
# 3. Show all entries
|
||||
print("\n3. Current goal type definitions:")
|
||||
cur.execute("""
|
||||
SELECT type_key, label_de, unit, is_system, is_active, created_at
|
||||
FROM goal_type_definitions
|
||||
ORDER BY is_system DESC, type_key
|
||||
""")
|
||||
|
||||
entries = cur.fetchall()
|
||||
if entries:
|
||||
print(f"\n {'Type Key':<20} {'Label':<20} {'Unit':<10} {'System':<8} {'Active':<8}")
|
||||
print(" " + "-" * 70)
|
||||
for row in entries:
|
||||
status = "SYSTEM" if row['is_system'] else "CUSTOM"
|
||||
active = "YES" if row['is_active'] else "NO"
|
||||
print(f" {row['type_key']:<20} {row['label_de']:<20} {row['unit']:<10} {status:<8} {active:<8}")
|
||||
else:
|
||||
print(" (empty)")
|
||||
|
||||
# 4. Check schema_migrations
|
||||
print("\n4. Checking schema_migrations tracking...")
|
||||
cur.execute("""
|
||||
SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_name = 'schema_migrations'
|
||||
)
|
||||
""")
|
||||
sm_exists = cur.fetchone()['exists']
|
||||
|
||||
if sm_exists:
|
||||
cur.execute("""
|
||||
SELECT filename, executed_at
|
||||
FROM schema_migrations
|
||||
WHERE filename = '024_goal_type_registry.sql'
|
||||
""")
|
||||
tracked = cur.fetchone()
|
||||
if tracked:
|
||||
print(f" ✓ Migration 024 is tracked (executed: {tracked['executed_at']})")
|
||||
else:
|
||||
print(" ❌ Migration 024 is NOT tracked in schema_migrations")
|
||||
else:
|
||||
print(" ⚠️ schema_migrations table does not exist")
|
||||
|
||||
# 5. Check for errors
|
||||
print("\n5. Potential issues:")
|
||||
issues = []
|
||||
|
||||
if count == 0:
|
||||
issues.append("No seed data - INSERTs failed")
|
||||
|
||||
if count > 0 and count < 6:
|
||||
issues.append(f"Only {count} types (expected 8) - partial seed")
|
||||
|
||||
cur.execute("""
|
||||
SELECT COUNT(*) as inactive_count
|
||||
FROM goal_type_definitions
|
||||
WHERE is_active = false
|
||||
""")
|
||||
inactive = cur.fetchone()['inactive_count']
|
||||
if inactive > 2:
|
||||
issues.append(f"{inactive} inactive types (expected 2)")
|
||||
|
||||
if not issues:
|
||||
print(" ✓ No issues detected")
|
||||
else:
|
||||
for issue in issues:
|
||||
print(f" ❌ {issue}")
|
||||
|
||||
# 6. Test query that frontend uses
|
||||
print("\n6. Testing frontend query (WHERE is_active = true)...")
|
||||
cur.execute("""
|
||||
SELECT COUNT(*) as active_count
|
||||
FROM goal_type_definitions
|
||||
WHERE is_active = true
|
||||
""")
|
||||
active_count = cur.fetchone()['active_count']
|
||||
print(f" Active types returned: {active_count}")
|
||||
|
||||
if active_count == 0:
|
||||
print(" ❌ This is why frontend shows empty list!")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("SUMMARY")
|
||||
print("=" * 70)
|
||||
|
||||
if count == 0:
|
||||
print("\n🔴 PROBLEM: Table exists but has no data")
|
||||
print("\nQUICK FIX: Run these SQL commands manually:")
|
||||
print("\n```sql")
|
||||
print("-- Connect to database:")
|
||||
print("docker exec -it bodytrack-dev-db-1 psql -U bodytrack -d bodytrack")
|
||||
print("\n-- Then paste migration content:")
|
||||
print("-- (copy from backend/migrations/024_goal_type_registry.sql)")
|
||||
print("-- Skip CREATE TABLE (already exists), run INSERT statements only")
|
||||
print("```")
|
||||
elif active_count >= 6:
|
||||
print("\n🟢 EVERYTHING LOOKS GOOD")
|
||||
print(f" {active_count} active goal types available")
|
||||
print("\nIf frontend still shows error, check:")
|
||||
print(" 1. Backend logs: docker logs bodytrack-dev-backend-1 -f")
|
||||
print(" 2. Network tab in browser DevTools")
|
||||
print(" 3. API endpoint: curl -H 'X-Auth-Token: YOUR_TOKEN' http://localhost:8099/api/goals/goal-types")
|
||||
else:
|
||||
print(f"\n🟡 PARTIAL DATA: {active_count} active types (expected 6)")
|
||||
print(" Some INSERTs might have failed")
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
215
backend/fix_seed_goal_types.py
Normal file
215
backend/fix_seed_goal_types.py
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Quick Fix: Insert seed data for goal_type_definitions
|
||||
|
||||
This script ONLY inserts the 8 standard goal types.
|
||||
Safe to run multiple times (uses ON CONFLICT DO NOTHING).
|
||||
|
||||
Run inside backend container:
|
||||
docker exec bodytrack-dev-backend-1 python fix_seed_goal_types.py
|
||||
"""
|
||||
|
||||
import psycopg2
|
||||
import os
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
# Database connection
|
||||
DB_HOST = os.getenv('DB_HOST', 'db')
|
||||
DB_PORT = os.getenv('DB_PORT', '5432')
|
||||
DB_NAME = os.getenv('DB_NAME', 'bodytrack')
|
||||
DB_USER = os.getenv('DB_USER', 'bodytrack')
|
||||
DB_PASS = os.getenv('DB_PASSWORD', '')
|
||||
|
||||
SEED_DATA = [
|
||||
{
|
||||
'type_key': 'weight',
|
||||
'label_de': 'Gewicht',
|
||||
'label_en': 'Weight',
|
||||
'unit': 'kg',
|
||||
'icon': '⚖️',
|
||||
'category': 'body',
|
||||
'source_table': 'weight_log',
|
||||
'source_column': 'weight',
|
||||
'aggregation_method': 'latest',
|
||||
'description': 'Aktuelles Körpergewicht',
|
||||
'is_system': True
|
||||
},
|
||||
{
|
||||
'type_key': 'body_fat',
|
||||
'label_de': 'Körperfett',
|
||||
'label_en': 'Body Fat',
|
||||
'unit': '%',
|
||||
'icon': '📊',
|
||||
'category': 'body',
|
||||
'source_table': 'caliper_log',
|
||||
'source_column': 'body_fat_pct',
|
||||
'aggregation_method': 'latest',
|
||||
'description': 'Körperfettanteil aus Caliper-Messung',
|
||||
'is_system': True
|
||||
},
|
||||
{
|
||||
'type_key': 'lean_mass',
|
||||
'label_de': 'Muskelmasse',
|
||||
'label_en': 'Lean Mass',
|
||||
'unit': 'kg',
|
||||
'icon': '💪',
|
||||
'category': 'body',
|
||||
'calculation_formula': '{"type": "lean_mass", "dependencies": ["weight_log.weight", "caliper_log.body_fat_pct"], "formula": "weight - (weight * body_fat_pct / 100)"}',
|
||||
'description': 'Fettfreie Körpermasse (berechnet aus Gewicht und Körperfett)',
|
||||
'is_system': True
|
||||
},
|
||||
{
|
||||
'type_key': 'vo2max',
|
||||
'label_de': 'VO2Max',
|
||||
'label_en': 'VO2Max',
|
||||
'unit': 'ml/kg/min',
|
||||
'icon': '🫁',
|
||||
'category': 'recovery',
|
||||
'source_table': 'vitals_baseline',
|
||||
'source_column': 'vo2_max',
|
||||
'aggregation_method': 'latest',
|
||||
'description': 'Maximale Sauerstoffaufnahme (geschätzt oder gemessen)',
|
||||
'is_system': True
|
||||
},
|
||||
{
|
||||
'type_key': 'rhr',
|
||||
'label_de': 'Ruhepuls',
|
||||
'label_en': 'Resting Heart Rate',
|
||||
'unit': 'bpm',
|
||||
'icon': '💓',
|
||||
'category': 'recovery',
|
||||
'source_table': 'vitals_baseline',
|
||||
'source_column': 'resting_hr',
|
||||
'aggregation_method': 'latest',
|
||||
'description': 'Ruhepuls morgens vor dem Aufstehen',
|
||||
'is_system': True
|
||||
},
|
||||
{
|
||||
'type_key': 'bp',
|
||||
'label_de': 'Blutdruck',
|
||||
'label_en': 'Blood Pressure',
|
||||
'unit': 'mmHg',
|
||||
'icon': '❤️',
|
||||
'category': 'recovery',
|
||||
'source_table': 'blood_pressure_log',
|
||||
'source_column': 'systolic',
|
||||
'aggregation_method': 'latest',
|
||||
'description': 'Blutdruck (aktuell nur systolisch, v2.0: beide Werte)',
|
||||
'is_system': True
|
||||
},
|
||||
{
|
||||
'type_key': 'strength',
|
||||
'label_de': 'Kraft',
|
||||
'label_en': 'Strength',
|
||||
'unit': 'kg',
|
||||
'icon': '🏋️',
|
||||
'category': 'activity',
|
||||
'description': 'Maximalkraft (Platzhalter, Datenquelle in v2.0)',
|
||||
'is_system': True,
|
||||
'is_active': False
|
||||
},
|
||||
{
|
||||
'type_key': 'flexibility',
|
||||
'label_de': 'Beweglichkeit',
|
||||
'label_en': 'Flexibility',
|
||||
'unit': 'cm',
|
||||
'icon': '🤸',
|
||||
'category': 'activity',
|
||||
'description': 'Beweglichkeit (Platzhalter, Datenquelle in v2.0)',
|
||||
'is_system': True,
|
||||
'is_active': False
|
||||
}
|
||||
]
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print("Goal Type Definitions - Seed Data Fix")
|
||||
print("=" * 70)
|
||||
|
||||
# Connect to database
|
||||
conn = psycopg2.connect(
|
||||
host=DB_HOST,
|
||||
port=DB_PORT,
|
||||
dbname=DB_NAME,
|
||||
user=DB_USER,
|
||||
password=DB_PASS
|
||||
)
|
||||
conn.autocommit = False
|
||||
cur = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
try:
|
||||
# Check current state
|
||||
cur.execute("SELECT COUNT(*) as count FROM goal_type_definitions")
|
||||
before_count = cur.fetchone()['count']
|
||||
print(f"\nBefore: {before_count} goal types in database")
|
||||
|
||||
# Insert seed data
|
||||
print(f"\nInserting {len(SEED_DATA)} standard goal types...")
|
||||
inserted = 0
|
||||
skipped = 0
|
||||
|
||||
for data in SEED_DATA:
|
||||
columns = list(data.keys())
|
||||
values = [data[col] for col in columns]
|
||||
placeholders = ', '.join(['%s'] * len(values))
|
||||
cols_str = ', '.join(columns)
|
||||
|
||||
sql = f"""
|
||||
INSERT INTO goal_type_definitions ({cols_str})
|
||||
VALUES ({placeholders})
|
||||
ON CONFLICT (type_key) DO NOTHING
|
||||
RETURNING id
|
||||
"""
|
||||
|
||||
cur.execute(sql, values)
|
||||
result = cur.fetchone()
|
||||
|
||||
if result:
|
||||
inserted += 1
|
||||
print(f" ✓ {data['type_key']}: {data['label_de']}")
|
||||
else:
|
||||
skipped += 1
|
||||
print(f" - {data['type_key']}: already exists (skipped)")
|
||||
|
||||
conn.commit()
|
||||
|
||||
# Check final state
|
||||
cur.execute("SELECT COUNT(*) as count FROM goal_type_definitions")
|
||||
after_count = cur.fetchone()['count']
|
||||
|
||||
print(f"\nAfter: {after_count} goal types in database")
|
||||
print(f" Inserted: {inserted}")
|
||||
print(f" Skipped: {skipped}")
|
||||
|
||||
# Show summary
|
||||
cur.execute("""
|
||||
SELECT type_key, label_de, is_active, is_system
|
||||
FROM goal_type_definitions
|
||||
ORDER BY is_system DESC, type_key
|
||||
""")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("Current Goal Types:")
|
||||
print("=" * 70)
|
||||
print(f"\n{'Type Key':<20} {'Label':<20} {'System':<8} {'Active':<8}")
|
||||
print("-" * 70)
|
||||
|
||||
for row in cur.fetchall():
|
||||
status = "YES" if row['is_system'] else "NO"
|
||||
active = "YES" if row['is_active'] else "NO"
|
||||
print(f"{row['type_key']:<20} {row['label_de']:<20} {status:<8} {active:<8}")
|
||||
|
||||
print("\n✅ DONE! Goal types seeded successfully.")
|
||||
print("\nNext step: Reload frontend to see the changes.")
|
||||
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
print(f"\n❌ Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -63,7 +63,8 @@ INSERT INTO goal_type_definitions (
|
|||
'weight', 'Gewicht', 'Weight', 'kg', '⚖️', 'body',
|
||||
'weight_log', 'weight', 'latest',
|
||||
'Aktuelles Körpergewicht', true
|
||||
);
|
||||
)
|
||||
ON CONFLICT (type_key) DO NOTHING;
|
||||
|
||||
-- 2. Body Fat (simple - latest value)
|
||||
INSERT INTO goal_type_definitions (
|
||||
|
|
@ -74,7 +75,8 @@ INSERT INTO goal_type_definitions (
|
|||
'body_fat', 'Körperfett', 'Body Fat', '%', '📊', 'body',
|
||||
'caliper_log', 'body_fat_pct', 'latest',
|
||||
'Körperfettanteil aus Caliper-Messung', true
|
||||
);
|
||||
)
|
||||
ON CONFLICT (type_key) DO NOTHING;
|
||||
|
||||
-- 3. Lean Mass (complex - calculation formula)
|
||||
INSERT INTO goal_type_definitions (
|
||||
|
|
@ -85,7 +87,8 @@ INSERT INTO goal_type_definitions (
|
|||
'lean_mass', 'Muskelmasse', 'Lean Mass', 'kg', '💪', 'body',
|
||||
'{"type": "lean_mass", "dependencies": ["weight_log.weight", "caliper_log.body_fat_pct"], "formula": "weight - (weight * body_fat_pct / 100)"}',
|
||||
'Fettfreie Körpermasse (berechnet aus Gewicht und Körperfett)', true
|
||||
);
|
||||
)
|
||||
ON CONFLICT (type_key) DO NOTHING;
|
||||
|
||||
-- 4. VO2 Max (simple - latest value)
|
||||
INSERT INTO goal_type_definitions (
|
||||
|
|
@ -96,7 +99,8 @@ INSERT INTO goal_type_definitions (
|
|||
'vo2max', 'VO2Max', 'VO2Max', 'ml/kg/min', '🫁', 'recovery',
|
||||
'vitals_baseline', 'vo2_max', 'latest',
|
||||
'Maximale Sauerstoffaufnahme (geschätzt oder gemessen)', true
|
||||
);
|
||||
)
|
||||
ON CONFLICT (type_key) DO NOTHING;
|
||||
|
||||
-- 5. Resting Heart Rate (simple - latest value)
|
||||
INSERT INTO goal_type_definitions (
|
||||
|
|
@ -107,7 +111,8 @@ INSERT INTO goal_type_definitions (
|
|||
'rhr', 'Ruhepuls', 'Resting Heart Rate', 'bpm', '💓', 'recovery',
|
||||
'vitals_baseline', 'resting_hr', 'latest',
|
||||
'Ruhepuls morgens vor dem Aufstehen', true
|
||||
);
|
||||
)
|
||||
ON CONFLICT (type_key) DO NOTHING;
|
||||
|
||||
-- 6. Blood Pressure (placeholder - compound goal for v2.0)
|
||||
-- Currently limited to single value, v2.0 will support systolic/diastolic
|
||||
|
|
@ -119,7 +124,8 @@ INSERT INTO goal_type_definitions (
|
|||
'bp', 'Blutdruck', 'Blood Pressure', 'mmHg', '❤️', 'recovery',
|
||||
'blood_pressure_log', 'systolic', 'latest',
|
||||
'Blutdruck (aktuell nur systolisch, v2.0: beide Werte)', true
|
||||
);
|
||||
)
|
||||
ON CONFLICT (type_key) DO NOTHING;
|
||||
|
||||
-- 7. Strength (placeholder - no data source yet)
|
||||
INSERT INTO goal_type_definitions (
|
||||
|
|
@ -128,7 +134,8 @@ INSERT INTO goal_type_definitions (
|
|||
) VALUES (
|
||||
'strength', 'Kraft', 'Strength', 'kg', '🏋️', 'activity',
|
||||
'Maximalkraft (Platzhalter, Datenquelle in v2.0)', true, false
|
||||
);
|
||||
)
|
||||
ON CONFLICT (type_key) DO NOTHING;
|
||||
|
||||
-- 8. Flexibility (placeholder - no data source yet)
|
||||
INSERT INTO goal_type_definitions (
|
||||
|
|
@ -137,7 +144,8 @@ INSERT INTO goal_type_definitions (
|
|||
) VALUES (
|
||||
'flexibility', 'Beweglichkeit', 'Flexibility', 'cm', '🤸', 'activity',
|
||||
'Beweglichkeit (Platzhalter, Datenquelle in v2.0)', true, false
|
||||
);
|
||||
)
|
||||
ON CONFLICT (type_key) DO NOTHING;
|
||||
|
||||
-- ============================================================================
|
||||
-- Example: Future custom goal types (commented out, for reference)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user