many updates

This commit is contained in:
2026-01-11 06:06:43 -08:00
parent 67357b5038
commit 4bb86b603e
73 changed files with 2881 additions and 59 deletions

View File

@@ -141,6 +141,7 @@ async def query_activities(
start_date: Optional[str] = Query(None),
end_date: Optional[str] = Query(None),
download_status: Optional[str] = Query(None),
bike_setup_id: Optional[int] = Query(None),
db: Session = Depends(get_db)
):
"""
@@ -154,7 +155,21 @@ async def query_activities(
# Apply filters based on parameters
if activity_type:
query = query.filter(Activity.activity_type == activity_type)
if activity_type == 'cycling':
# Match outdoor cycling types
# Using OR filtering for various sub-types
from sqlalchemy import or_
query = query.filter(or_(
Activity.activity_type == 'cycling',
Activity.activity_type == 'road_biking',
Activity.activity_type == 'mountain_biking',
Activity.activity_type == 'gravel_cycling',
Activity.activity_type == 'cyclocross',
Activity.activity_type == 'track_cycling',
Activity.activity_type == 'commuting'
))
else:
query = query.filter(Activity.activity_type == activity_type)
if start_date:
from datetime import datetime
@@ -168,6 +183,9 @@ async def query_activities(
if download_status:
query = query.filter(Activity.download_status == download_status)
if bike_setup_id:
query = query.filter(Activity.bike_setup_id == bike_setup_id)
# Execute the query
activities = query.all()
@@ -376,7 +394,20 @@ async def redownload_activity_endpoint(activity_id: str, db: Session = Depends(g
success = sync_app.redownload_activity(activity_id)
if success:
return {"message": f"Successfully redownloaded activity {activity_id}", "status": "success"}
# Trigger bike matching
try:
from ..services.bike_matching import process_activity_matching
# Fetch fresh activity object using new session logic or flush/commit handled by sync_app
# Just query by garmin_id
act_obj = db.query(Activity).filter(Activity.garmin_activity_id == activity_id).first()
if act_obj:
process_activity_matching(db, act_obj.id)
logger.info(f"Retriggered bike match for {activity_id} after redownload")
except Exception as match_err:
logger.error(f"Error matching bike after redownload: {match_err}")
return {"message": f"Successfully redownloaded and matched activity {activity_id}", "status": "success"}
else:
raise HTTPException(status_code=500, detail="Failed to redownload activity. Check logs for details.")
@@ -389,6 +420,48 @@ async def redownload_activity_endpoint(activity_id: str, db: Session = Depends(g
# New Sync Endpoints
class BikeMatchUpdate(BaseModel):
bike_setup_id: Optional[int] = None
manual_override: bool = True
@router.put("/activities/{activity_id}/bike")
async def update_activity_bike(activity_id: str, update: BikeMatchUpdate, db: Session = Depends(get_db)):
"""
Manually update the bike setup for an activity.
Sets bike_match_confidence to 2.0 to indicate manual override.
"""
try:
activity = db.query(Activity).filter(Activity.garmin_activity_id == activity_id).first()
if not activity:
raise HTTPException(status_code=404, detail="Activity not found")
# Verify bike setup exists if provided
if update.bike_setup_id:
from ..models.bike_setup import BikeSetup
setup = db.query(BikeSetup).filter(BikeSetup.id == update.bike_setup_id).first()
if not setup:
raise HTTPException(status_code=404, detail="Bike Setup not found")
activity.bike_setup_id = setup.id
activity.bike_match_confidence = 2.0 # Manual Override
logger.info(f"Manual bike override for {activity_id} to setup {setup.id}")
else:
# Clear setup
activity.bike_setup_id = None
activity.bike_match_confidence = 2.0 # Manual Clear
logger.info(f"Manual bike override for {activity_id} to cleared")
db.commit()
return {"message": "Bike setup updated successfully", "status": "success"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error updating activity bike: {e}")
db.rollback()
raise HTTPException(status_code=500, detail=str(e))
def run_scan_job(job_id: str, days_back: int, db_session_factory):
"""Background task wrapper for scan"""
try:
@@ -685,6 +758,23 @@ async def get_activity_streams(activity_id: str, db: Session = Depends(get_db)):
logger.error(f"Error getting streams: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/activities/{activity_id}/estimate_power")
async def estimate_activity_power(activity_id: int, db: Session = Depends(get_db)):
"""
Trigger physics-based power estimation.
"""
from ..services.power_estimator import PowerEstimatorService
try:
service = PowerEstimatorService(db)
result = service.estimate_power_for_activity(activity_id)
return {"message": "Power estimated successfully", "stats": result}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Error estimating power: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/activities/{activity_id}/navigation")
async def get_activity_navigation(activity_id: str, db: Session = Depends(get_db)):
"""