Erste Version - Universal CSV Importer für EAV und activity_log #85

Merged
Lars merged 17 commits from develop into main 2026-04-15 11:46:31 +02:00
4 changed files with 67 additions and 7 deletions
Showing only changes of commit 766b64cd64 - Show all commits

View File

@ -391,8 +391,8 @@ def replace_activity_session_metrics(
(activity_log_id, spec["training_parameter_id"], vn, vi, vt, vb),
)
# Übergang: Spalten in activity_log sind maßgeblich — EAV für alle source_field-Parameter angleichen
sync_column_backed_session_metrics(cur, profile_id, activity_log_id)
# Kein sync_column_backed nach PUT /metrics: der Request ist maßgeblich für EAV. Ein Spalten-Sync würde
# Werte aus nicht mitgeschriebenen activity_log-Spalten wieder verwerfen.
return fetch_activity_session_metrics(cur, activity_log_id)
@ -408,10 +408,40 @@ def get_activity_session_logical_unit(cur, profile_id: str, activity_log_id: str
cur, header.get("training_category"), header.get("training_type_id")
)
metrics = fetch_activity_session_metrics(cur, activity_log_id)
by_key = {m["key"]: m for m in metrics}
merged_metrics: List[Dict[str, Any]] = list(metrics)
for s in schema:
k = s["key"]
if k in by_key:
continue
sf = s.get("source_field")
if not sf or (isinstance(sf, str) and not str(sf).strip()):
continue
col = str(sf).strip()
if col not in header:
continue
raw = header.get(col)
if raw is None:
continue
dt = s["data_type"]
try:
val = _coerce_raw_value_for_parameter(dt, raw)
except (TypeError, ValueError):
continue
merged_metrics.append(
{
"training_parameter_id": s["training_parameter_id"],
"key": k,
"data_type": dt,
"unit": s.get("unit"),
"value": val,
}
)
merged_metrics.sort(key=lambda x: x["key"])
return {
"header": header,
"schema": schema,
"metrics": metrics,
"metrics": merged_metrics,
}

View File

@ -83,8 +83,17 @@ class ActivityEntry(BaseModel):
kcal_resting: Optional[float] = None
hr_avg: Optional[float] = None
hr_max: Optional[float] = None
hr_min: Optional[int] = None # DB-Spalte hr_min (Parameter min_hr)
distance_km: Optional[float] = None
rpe: Optional[int] = None
pace_min_per_km: Optional[float] = None
cadence: Optional[int] = None
avg_power: Optional[int] = None
elevation_gain: Optional[int] = None
temperature_celsius: Optional[float] = None
humidity_percent: Optional[int] = None
avg_hr_percent: Optional[float] = None
kcal_per_km: Optional[float] = None
source: Optional[str] = 'manual'
notes: Optional[str] = None
training_type_id: Optional[int] = None # v9d: Training type categorization

View File

@ -102,9 +102,10 @@ def create_activity(e: ActivityEntry, x_profile_id: Optional[str]=Header(default
cur.execute(
"""INSERT INTO activity_log
(id,profile_id,date,start_time,end_time,activity_type,duration_min,kcal_active,kcal_resting,
hr_avg,hr_max,distance_km,rpe,source,notes,
hr_avg,hr_max,hr_min,distance_km,pace_min_per_km,cadence,avg_power,elevation_gain,
temperature_celsius,humidity_percent,avg_hr_percent,kcal_per_km,rpe,source,notes,
training_type_id,training_category,training_subcategory,created)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,CURRENT_TIMESTAMP)""",
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,CURRENT_TIMESTAMP)""",
(
eid,
pid,
@ -117,7 +118,16 @@ def create_activity(e: ActivityEntry, x_profile_id: Optional[str]=Header(default
d["kcal_resting"],
d["hr_avg"],
d["hr_max"],
d.get("hr_min"),
d["distance_km"],
d.get("pace_min_per_km"),
d.get("cadence"),
d.get("avg_power"),
d.get("elevation_gain"),
d.get("temperature_celsius"),
d.get("humidity_percent"),
d.get("avg_hr_percent"),
d.get("kcal_per_km"),
d["rpe"],
d["source"],
d["notes"],

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from 'react'
import { useState, useEffect, useRef, startTransition } from 'react'
import { Upload, Pencil, Trash2, Check, X, CheckCircle } from 'lucide-react'
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid } from 'recharts'
import { api } from '../utils/api'
@ -26,7 +26,16 @@ const ACTIVITY_LOG_PAYLOAD_KEYS = new Set([
'kcal_resting',
'hr_avg',
'hr_max',
'hr_min',
'distance_km',
'pace_min_per_km',
'cadence',
'avg_power',
'elevation_gain',
'temperature_celsius',
'humidity_percent',
'avg_hr_percent',
'kcal_per_km',
'rpe',
'source',
'notes',
@ -412,7 +421,9 @@ export default function ActivityPage() {
}
setEditing(null)
setSessionDetail(null)
await load()
startTransition(() => {
void load()
})
} catch (err) {
setError(err.message || 'Speichern fehlgeschlagen')
setTimeout(() => setError(null), 6000)