import sys import os import math from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker import logging sys.path.append('/app/backend') from src.services.discovery import SegmentDiscoveryService from src.models.activity import Activity from src.utils.geo import calculate_bearing, haversine_distance from src.services.parsers import extract_activity_data logging.basicConfig(level=logging.INFO) def trace_activity(activity_id): DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:password@localhost:5433/fitbit_garmin_sync") engine = create_engine(DATABASE_URL) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) db = SessionLocal() # 1. Fetch Activity act = db.query(Activity).filter(Activity.garmin_activity_id == str(activity_id)).first() if not act: print(f"Activity {activity_id} NOT FOUND.") return print(f"Tracing Activity {act.id} ({act.garmin_activity_id})") # 2. Extract Data # Parser returns a dict: {'points': [], 'timestamps': [], ...} result = extract_activity_data(act.file_content, act.file_type) points = result['points'] timestamps = result['timestamps'] # Filter points (same logic as service) clean_points = [] clean_ts = [] for i in range(len(points)): if points[i][0] is not None and points[i][1] is not None: clean_points.append(points[i]) clean_ts.append(timestamps[i]) print(f"Clean points: {len(clean_points)}") # 3. Simulate Analysis Loop current_segment = [clean_points[0]] last_bearing = None segments_found = 0 skipped_dist = 0 max_dist = 0 max_time_diff = 0 max_bearing_diff = 0 for i in range(1, len(clean_points)): p1 = clean_points[i-1] p2 = clean_points[i] # dist dist = haversine_distance(p1[1], p1[0], p2[1], p2[0]) max_dist = max(max_dist, dist) if dist < 2.0: skipped_dist += 1 continue bearing = calculate_bearing(p1[1], p1[0], p2[1], p2[0]) is_turn = False diff = 0 if last_bearing is not None: diff = abs(bearing - last_bearing) if diff > 180: diff = 360 - diff max_bearing_diff = max(max_bearing_diff, diff) if diff > 60: is_turn = True print(f"TURN DETECTED at index {i}: Bearing {last_bearing:.1f} -> {bearing:.1f} (Diff: {diff:.1f})") elif diff > 10: # Log even smaller turns to see noise print(f" Minor Turn at index {i}: Bearing {last_bearing:.1f} -> {bearing:.1f} (Diff: {diff:.1f})") last_bearing = bearing t1 = clean_ts[i-1] t2 = clean_ts[i] time_diff = (t2 - t1).total_seconds() max_time_diff = max(max_time_diff, time_diff) is_pause = time_diff > 10 if is_pause: print(f"PAUSE DETECTED at index {i}: {time_diff}s") if is_pause or is_turn: if len(current_segment) > 10: segments_found += 1 print(f" -> Segment Created (Points: {len(current_segment)})") current_segment = [p2] else: current_segment.append(p2) print(f"\nStats:") print(f" Total Points: {len(clean_points)}") print(f" Skipped (dist < 5m): {skipped_dist}") print(f" Max Point-to-Point Dist: {max_dist:.2f}m") print(f" Max Time Diff: {max_time_diff}s") print(f" Max Bearing Diff: {max_bearing_diff:.1f}") print(f" Segments Found (from splits): {segments_found}") from src.utils.geo import ramer_douglas_peucker for eps in [2.0, 5.0, 10.0, 15.0]: print(f"\n--- Riper RDP Trace (epsilon={eps}m) ---") simplified = ramer_douglas_peucker(clean_points, eps) print(f"Simplified points: {len(simplified)} (from {len(clean_points)})") last_bearing = None turns_found = 0 for i in range(1, len(simplified)): p1 = simplified[i-1] p2 = simplified[i] bearing = calculate_bearing(p1[1], p1[0], p2[1], p2[0]) if last_bearing is not None: diff = abs(bearing - last_bearing) if diff > 180: diff = 360 - diff if diff > 60: print(f"TURN: {last_bearing:.1f} -> {bearing:.1f} (Diff: {diff:.1f})") turns_found += 1 elif diff > 45: print(f" Minor Turn (>45): {last_bearing:.1f} -> {bearing:.1f} (Diff: {diff:.1f})") last_bearing = bearing print(f"Total Turns > 60: {turns_found}") if __name__ == "__main__": trace_activity(21465710074)