From e479627f0f9ce8d1eabe71cf1961c8546940a4c2 Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 28 Mar 2026 13:41:35 +0100 Subject: [PATCH] feat: Auto-adjust start_date to first available measurement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **User Feedback:** "Macht es nicht Sinn, den nächsten verfügbaren Wert am oder nach dem Startdatum automatisch zu ermitteln und auch das Startdatum dann automatisch auf den Wert zu setzen?" **New Logic:** 1. User sets start_date: 2026-01-01 2. System finds FIRST measurement >= 2026-01-01 (e.g., 2026-01-15: 88 kg) 3. System auto-adjusts: - start_date → 2026-01-15 - start_value → 88 kg 4. User sees: "Start: 88 kg (15.01.26)" ✓ **Benefits:** - User doesn't need to know exact date of first measurement - More user-friendly UX - Automatically finds closest available data **Implementation:** - Changed query from "BETWEEN date ±7 days" to "WHERE date >= target_date" - Returns dict with {'value': float, 'date': date} - Both create_goal() and update_goal() now adjust start_date automatically Co-Authored-By: Claude Opus 4.6 --- backend/routers/goals.py | 77 +++++++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/backend/routers/goals.py b/backend/routers/goals.py index c17a089..5611d6e 100644 --- a/backend/routers/goals.py +++ b/backend/routers/goals.py @@ -395,11 +395,16 @@ def create_goal(data: GoalCreate, session: dict = Depends(require_auth)): start_value = data.start_value elif start_date < date.today(): # Historical start date - try to get historical value - start_value = _get_historical_value_for_goal_type(conn, pid, data.goal_type, start_date) - if start_value is None: - # No data on that date, fall back to current value + historical_data = _get_historical_value_for_goal_type(conn, pid, data.goal_type, start_date) + if historical_data is not None: + # Use the actual measurement date and value + start_date = historical_data['date'] + start_value = historical_data['value'] + print(f"[INFO] Auto-adjusted start_date to {start_date} (first measurement)") + else: + # No data found, fall back to current value and keep original date start_value = current_value - print(f"[WARN] No historical data for {data.goal_type} on {start_date}, using current value") + print(f"[WARN] No historical data for {data.goal_type} on or after {start_date}, using current value") else: # Start date is today, use current value start_value = current_value @@ -514,15 +519,23 @@ def update_goal(goal_id: str, data: GoalUpdate, session: dict = Depends(require_ goal_row = cur.fetchone() if goal_row: goal_type = goal_row['goal_type'] - print(f"[DEBUG] Looking up historical value for {goal_type} on {data.start_date}") - historical_value = _get_historical_value_for_goal_type(conn, pid, goal_type, data.start_date) - print(f"[DEBUG] Historical value result: {historical_value}") - if historical_value is not None: + print(f"[DEBUG] Looking up historical value for {goal_type} on or after {data.start_date}") + historical_data = _get_historical_value_for_goal_type(conn, pid, goal_type, data.start_date) + print(f"[DEBUG] Historical data result: {historical_data}") + if historical_data is not None: + # Update both start_date and start_value with actual measurement + actual_date = historical_data['date'] + actual_value = historical_data['value'] + + # Replace the start_date in updates with the actual measurement date + updates[-1] = "start_date = %s" # Update the last added start_date + params[-1] = actual_date + updates.append("start_value = %s") - params.append(historical_value) - print(f"[INFO] Auto-populated start_value from {data.start_date}: {historical_value}") + params.append(actual_value) + print(f"[INFO] Auto-adjusted to first measurement: {actual_date} = {actual_value}") else: - print(f"[WARN] No historical data found for {goal_type} around {data.start_date}") + print(f"[WARN] No historical data found for {goal_type} on or after {data.start_date}") else: print(f"[ERROR] Could not find goal with id {goal_id}") @@ -683,19 +696,19 @@ def _get_current_value_for_goal_type(conn, profile_id: str, goal_type: str) -> O # Delegate to universal fetcher (Phase 1.5) return get_current_value_for_goal(conn, profile_id, goal_type) -def _get_historical_value_for_goal_type(conn, profile_id: str, goal_type: str, target_date: date) -> Optional[float]: +def _get_historical_value_for_goal_type(conn, profile_id: str, goal_type: str, target_date: date) -> Optional[dict]: """ - Get historical value for a goal type on a specific date. - Looks for closest value within ±7 days window. + Get historical value for a goal type on or after a specific date. + Finds the FIRST available measurement >= target_date. Args: conn: Database connection profile_id: User's profile ID goal_type: Goal type key (e.g., 'weight', 'body_fat') - target_date: Date to query (can be historical) + target_date: Desired start date (will find first measurement on or after this date) Returns: - Historical value or None if not found + Dict with {'value': float, 'date': date} or None if not found """ from goal_utils import get_goal_type_config, get_cursor @@ -726,20 +739,16 @@ def _get_historical_value_for_goal_type(conn, profile_id: str, goal_type: str, t else: date_col = 'date' + # Find first measurement on or after target_date query = f""" - SELECT {source_column} + SELECT {source_column}, {date_col} as measurement_date FROM {source_table} WHERE profile_id = %s - AND {date_col} BETWEEN %s AND %s - ORDER BY ABS({date_col} - %s::date) + AND {date_col} >= %s + ORDER BY {date_col} ASC LIMIT 1 """ - params = ( - profile_id, - target_date - timedelta(days=7), - target_date + timedelta(days=7), - target_date - ) + params = (profile_id, target_date) print(f"[DEBUG] Query: {query}") print(f"[DEBUG] Params: {params}") @@ -751,12 +760,24 @@ def _get_historical_value_for_goal_type(conn, profile_id: str, goal_type: str, t if row: value = row[source_column] + measurement_date = row['measurement_date'] + # Convert Decimal to float - result = float(value) if value is not None else None - print(f"[DEBUG] Returning value: {result}") + result_value = float(value) if value is not None else None + + # Handle different date types (date vs datetime) + if hasattr(measurement_date, 'date'): + # It's a datetime, extract date + result_date = measurement_date.date() + else: + # It's already a date + result_date = measurement_date + + result = {'value': result_value, 'date': result_date} + print(f"[DEBUG] Returning: {result}") return result - print(f"[DEBUG] No data found in date range") + print(f"[DEBUG] No data found on or after {target_date}") return None except Exception as e: print(f"[ERROR] Failed to get historical value for {goal_type} on {target_date}: {e}")