feat: Auto-adjust start_date to first available measurement
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 13s

**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 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-03-28 13:41:35 +01:00
parent 169dbba092
commit e479627f0f

View File

@ -395,11 +395,16 @@ def create_goal(data: GoalCreate, session: dict = Depends(require_auth)):
start_value = data.start_value start_value = data.start_value
elif start_date < date.today(): elif start_date < date.today():
# Historical start date - try to get historical value # Historical start date - try to get historical value
start_value = _get_historical_value_for_goal_type(conn, pid, data.goal_type, start_date) historical_data = _get_historical_value_for_goal_type(conn, pid, data.goal_type, start_date)
if start_value is None: if historical_data is not None:
# No data on that date, fall back to current value # 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 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: else:
# Start date is today, use current value # Start date is today, use current value
start_value = 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() goal_row = cur.fetchone()
if goal_row: if goal_row:
goal_type = goal_row['goal_type'] goal_type = goal_row['goal_type']
print(f"[DEBUG] Looking up historical value for {goal_type} on {data.start_date}") print(f"[DEBUG] Looking up historical value for {goal_type} on or after {data.start_date}")
historical_value = _get_historical_value_for_goal_type(conn, pid, goal_type, data.start_date) historical_data = _get_historical_value_for_goal_type(conn, pid, goal_type, data.start_date)
print(f"[DEBUG] Historical value result: {historical_value}") print(f"[DEBUG] Historical data result: {historical_data}")
if historical_value is not None: 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") updates.append("start_value = %s")
params.append(historical_value) params.append(actual_value)
print(f"[INFO] Auto-populated start_value from {data.start_date}: {historical_value}") print(f"[INFO] Auto-adjusted to first measurement: {actual_date} = {actual_value}")
else: 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: else:
print(f"[ERROR] Could not find goal with id {goal_id}") 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) # Delegate to universal fetcher (Phase 1.5)
return get_current_value_for_goal(conn, profile_id, goal_type) 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. Get historical value for a goal type on or after a specific date.
Looks for closest value within ±7 days window. Finds the FIRST available measurement >= target_date.
Args: Args:
conn: Database connection conn: Database connection
profile_id: User's profile ID profile_id: User's profile ID
goal_type: Goal type key (e.g., 'weight', 'body_fat') 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: 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 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: else:
date_col = 'date' date_col = 'date'
# Find first measurement on or after target_date
query = f""" query = f"""
SELECT {source_column} SELECT {source_column}, {date_col} as measurement_date
FROM {source_table} FROM {source_table}
WHERE profile_id = %s WHERE profile_id = %s
AND {date_col} BETWEEN %s AND %s AND {date_col} >= %s
ORDER BY ABS({date_col} - %s::date) ORDER BY {date_col} ASC
LIMIT 1 LIMIT 1
""" """
params = ( params = (profile_id, target_date)
profile_id,
target_date - timedelta(days=7),
target_date + timedelta(days=7),
target_date
)
print(f"[DEBUG] Query: {query}") print(f"[DEBUG] Query: {query}")
print(f"[DEBUG] Params: {params}") 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: if row:
value = row[source_column] value = row[source_column]
measurement_date = row['measurement_date']
# Convert Decimal to float # Convert Decimal to float
result = float(value) if value is not None else None result_value = float(value) if value is not None else None
print(f"[DEBUG] Returning value: {result}")
# 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 return result
print(f"[DEBUG] No data found in date range") print(f"[DEBUG] No data found on or after {target_date}")
return None return None
except Exception as e: except Exception as e:
print(f"[ERROR] Failed to get historical value for {goal_type} on {target_date}: {e}") print(f"[ERROR] Failed to get historical value for {goal_type} on {target_date}: {e}")