fix: vitals baseline parameter sync + goal utils transaction rollback
Bug 1 Fix (Ruhepuls):
- Completely rewrote vitals_baseline POST endpoint
- Clear separation: param_values array contains ALL values (pid, date, ...)
- Synchronized insert_cols, insert_placeholders, and param_values
- Added debug logging
- Simplified UPDATE logic (EXCLUDED.col instead of COALESCE)
Bug 2 Fix (Custom Goal Type Transaction Error):
- Added transaction rollback in goal_utils._fetch_by_aggregation_method()
- When SQL query fails (e.g., invalid column name), rollback transaction
- Prevents 'InFailedSqlTransaction' errors on subsequent queries
- Enhanced error logging (shows filter conditions, SQL, params)
- Returns None gracefully so goal creation can continue
User Action Required for Bug 2:
- Edit goal type 'Trainingshäufigkeit Krafttraining'
- Change filter from {"training_type": "strength"}
to {"training_category": "strength"}
- activity_log has training_category, NOT training_type column
This commit is contained in:
parent
ce4cd7daf1
commit
e4a2b63a48
|
|
@ -278,7 +278,7 @@ def _fetch_by_aggregation_method(
|
|||
- max_30d: Maximum value in last 30 days
|
||||
|
||||
Args:
|
||||
filter_conditions: Optional JSON filters (e.g., {"training_type": "strength"})
|
||||
filter_conditions: Optional JSON filters (e.g., {"training_category": "strength"})
|
||||
"""
|
||||
# Guard: source_table/column required for simple aggregation
|
||||
if not table or not column:
|
||||
|
|
@ -412,7 +412,21 @@ def _fetch_by_aggregation_method(
|
|||
return None
|
||||
|
||||
except Exception as e:
|
||||
# Log detailed error for debugging
|
||||
print(f"[ERROR] Failed to fetch value from {table}.{column} using {method}: {e}")
|
||||
print(f"[ERROR] Filter conditions: {filter_conditions}")
|
||||
print(f"[ERROR] Filter SQL: {filter_sql}")
|
||||
print(f"[ERROR] Filter params: {filter_params}")
|
||||
|
||||
# CRITICAL: Rollback transaction to avoid InFailedSqlTransaction errors
|
||||
try:
|
||||
conn.rollback()
|
||||
print(f"[INFO] Transaction rolled back after query error")
|
||||
except Exception as rollback_err:
|
||||
print(f"[WARNING] Rollback failed: {rollback_err}")
|
||||
|
||||
# Return None so goal creation can continue without current_value
|
||||
# (current_value will be NULL in the goal record)
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -99,67 +99,72 @@ def create_or_update_baseline(
|
|||
"""Create or update baseline entry (upsert on date)."""
|
||||
pid = get_pid(x_profile_id)
|
||||
|
||||
# Build dynamic INSERT columns and UPDATE fields
|
||||
# Build dynamic INSERT columns, placeholders, UPDATE fields, and values list
|
||||
# All arrays must stay synchronized
|
||||
insert_cols = []
|
||||
insert_placeholders = []
|
||||
update_fields = []
|
||||
values = [pid, entry.date]
|
||||
param_idx = 3 # Start after $1 (pid) and $2 (date)
|
||||
param_values = [] # Will contain ALL values including pid and date
|
||||
|
||||
# Always include profile_id and date
|
||||
param_values.append(pid)
|
||||
param_values.append(entry.date)
|
||||
param_idx = 3 # Next parameter starts at $3
|
||||
|
||||
if entry.resting_hr is not None:
|
||||
insert_cols.append("resting_hr")
|
||||
insert_placeholders.append(f"${param_idx}")
|
||||
update_fields.append(f"resting_hr = COALESCE(EXCLUDED.resting_hr, vitals_baseline.resting_hr)")
|
||||
values.append(entry.resting_hr)
|
||||
update_fields.append(f"resting_hr = EXCLUDED.resting_hr")
|
||||
param_values.append(entry.resting_hr)
|
||||
param_idx += 1
|
||||
|
||||
if entry.hrv is not None:
|
||||
insert_cols.append("hrv")
|
||||
insert_placeholders.append(f"${param_idx}")
|
||||
update_fields.append(f"hrv = COALESCE(EXCLUDED.hrv, vitals_baseline.hrv)")
|
||||
values.append(entry.hrv)
|
||||
update_fields.append(f"hrv = EXCLUDED.hrv")
|
||||
param_values.append(entry.hrv)
|
||||
param_idx += 1
|
||||
|
||||
if entry.vo2_max is not None:
|
||||
insert_cols.append("vo2_max")
|
||||
insert_placeholders.append(f"${param_idx}")
|
||||
update_fields.append(f"vo2_max = COALESCE(EXCLUDED.vo2_max, vitals_baseline.vo2_max)")
|
||||
values.append(entry.vo2_max)
|
||||
update_fields.append(f"vo2_max = EXCLUDED.vo2_max")
|
||||
param_values.append(entry.vo2_max)
|
||||
param_idx += 1
|
||||
|
||||
if entry.spo2 is not None:
|
||||
insert_cols.append("spo2")
|
||||
insert_placeholders.append(f"${param_idx}")
|
||||
update_fields.append(f"spo2 = COALESCE(EXCLUDED.spo2, vitals_baseline.spo2)")
|
||||
values.append(entry.spo2)
|
||||
update_fields.append(f"spo2 = EXCLUDED.spo2")
|
||||
param_values.append(entry.spo2)
|
||||
param_idx += 1
|
||||
|
||||
if entry.respiratory_rate is not None:
|
||||
insert_cols.append("respiratory_rate")
|
||||
insert_placeholders.append(f"${param_idx}")
|
||||
update_fields.append(f"respiratory_rate = COALESCE(EXCLUDED.respiratory_rate, vitals_baseline.respiratory_rate)")
|
||||
values.append(entry.respiratory_rate)
|
||||
update_fields.append(f"respiratory_rate = EXCLUDED.respiratory_rate")
|
||||
param_values.append(entry.respiratory_rate)
|
||||
param_idx += 1
|
||||
|
||||
if entry.body_temperature is not None:
|
||||
insert_cols.append("body_temperature")
|
||||
insert_placeholders.append(f"${param_idx}")
|
||||
update_fields.append(f"body_temperature = COALESCE(EXCLUDED.body_temperature, vitals_baseline.body_temperature)")
|
||||
values.append(entry.body_temperature)
|
||||
update_fields.append(f"body_temperature = EXCLUDED.body_temperature")
|
||||
param_values.append(entry.body_temperature)
|
||||
param_idx += 1
|
||||
|
||||
if entry.resting_metabolic_rate is not None:
|
||||
insert_cols.append("resting_metabolic_rate")
|
||||
insert_placeholders.append(f"${param_idx}")
|
||||
update_fields.append(f"resting_metabolic_rate = COALESCE(EXCLUDED.resting_metabolic_rate, vitals_baseline.resting_metabolic_rate)")
|
||||
values.append(entry.resting_metabolic_rate)
|
||||
update_fields.append(f"resting_metabolic_rate = EXCLUDED.resting_metabolic_rate")
|
||||
param_values.append(entry.resting_metabolic_rate)
|
||||
param_idx += 1
|
||||
|
||||
if entry.note:
|
||||
insert_cols.append("note")
|
||||
insert_placeholders.append(f"${param_idx}")
|
||||
update_fields.append(f"note = COALESCE(EXCLUDED.note, vitals_baseline.note)")
|
||||
values.append(entry.note)
|
||||
update_fields.append(f"note = EXCLUDED.note")
|
||||
param_values.append(entry.note)
|
||||
param_idx += 1
|
||||
|
||||
# At least one field must be provided
|
||||
|
|
@ -168,14 +173,24 @@ def create_or_update_baseline(
|
|||
|
||||
with get_db() as conn:
|
||||
cur = get_cursor(conn)
|
||||
|
||||
# Build complete column list and placeholder list
|
||||
all_cols = f"profile_id, date, {', '.join(insert_cols)}"
|
||||
all_placeholders = f"$1, $2, {', '.join(insert_placeholders)}"
|
||||
|
||||
query = f"""
|
||||
INSERT INTO vitals_baseline (profile_id, date, {', '.join(insert_cols)})
|
||||
VALUES ($1, $2, {', '.join(insert_placeholders)})
|
||||
INSERT INTO vitals_baseline ({all_cols})
|
||||
VALUES ({all_placeholders})
|
||||
ON CONFLICT (profile_id, date)
|
||||
DO UPDATE SET {', '.join(update_fields)}, updated_at = NOW()
|
||||
RETURNING *
|
||||
"""
|
||||
cur.execute(query, tuple(values))
|
||||
|
||||
# Debug logging
|
||||
print(f"[DEBUG] Vitals baseline query: {query}")
|
||||
print(f"[DEBUG] Param values ({len(param_values)}): {param_values}")
|
||||
|
||||
cur.execute(query, tuple(param_values))
|
||||
return r2d(cur.fetchone())
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user