feat: Enhance activity session metrics handling and frontend display
- Updated the `ACTIVITY_LOG_PATCHABLE_COLUMNS` and `ACTIVITY_LOG_PATCH_FORBIDDEN` sets to improve validation of CSV imports, ensuring only allowed fields are patched. - Refactored the `_coerce_raw_value_for_parameter` function to handle string inputs for integer and float types, enhancing data coercion accuracy. - Modified the `SessionMetricsFields` component to display orphan metrics that do not match the current schema, improving user visibility of imported data discrepancies. - Enhanced the frontend to handle and display additional metrics, ensuring a more comprehensive representation of session data.
This commit is contained in:
parent
574af61349
commit
c570e67a09
|
|
@ -11,10 +11,21 @@ from typing import Any, Dict, List, Mapping, Optional, Sequence
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# activity_log-Spalten (ohne Kernfelder aus CSV-Minimal-Insert), die über source_field beschrieben werden können.
|
||||
# activity_log-Spalten, die per training_parameters.source_field aus CSV (Parameter-Key) befüllt werden dürfen.
|
||||
# Muss mit sync_column_backed_session_metrics übereinstimmen (inkl. Kernmetriken wie hr_avg).
|
||||
ACTIVITY_LOG_PATCHABLE_COLUMNS = frozenset(
|
||||
{
|
||||
"start_time",
|
||||
"end_time",
|
||||
"activity_type",
|
||||
"duration_min",
|
||||
"kcal_active",
|
||||
"kcal_resting",
|
||||
"hr_avg",
|
||||
"hr_max",
|
||||
"hr_min",
|
||||
"distance_km",
|
||||
"rpe",
|
||||
"pace_min_per_km",
|
||||
"cadence",
|
||||
"avg_power",
|
||||
|
|
@ -23,11 +34,24 @@ ACTIVITY_LOG_PATCHABLE_COLUMNS = frozenset(
|
|||
"humidity_percent",
|
||||
"avg_hr_percent",
|
||||
"kcal_per_km",
|
||||
"rpe",
|
||||
"notes",
|
||||
}
|
||||
)
|
||||
|
||||
# Diese Spalten nicht aus CSV-Parameter-Zuordnung überschreiben (kommen aus Typ-Mapping / System).
|
||||
ACTIVITY_LOG_PATCH_FORBIDDEN = frozenset(
|
||||
{
|
||||
"id",
|
||||
"profile_id",
|
||||
"date",
|
||||
"created",
|
||||
"training_type_id",
|
||||
"training_category",
|
||||
"training_subcategory",
|
||||
"source",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ActivitySessionMetricsError(Exception):
|
||||
"""Raised by Layer 1; routers map to HTTP (404/400)."""
|
||||
|
|
@ -218,8 +242,14 @@ def _coerce_raw_value_for_parameter(data_type: str, raw: Any) -> Any:
|
|||
if data_type == "integer":
|
||||
if isinstance(raw, bool):
|
||||
raise TypeError("boolean nicht als integer erlaubt")
|
||||
if isinstance(raw, str):
|
||||
s = raw.strip().replace(",", ".")
|
||||
return int(round(float(s)))
|
||||
return int(round(float(raw)))
|
||||
if data_type == "float":
|
||||
if isinstance(raw, str):
|
||||
s = raw.strip().replace(",", ".")
|
||||
return float(s)
|
||||
return float(raw)
|
||||
if data_type == "string":
|
||||
return str(raw) if raw is not None else ""
|
||||
|
|
@ -248,7 +278,9 @@ def resolve_activity_log_column_patch_from_csv(
|
|||
patch: Dict[str, Any] = {}
|
||||
for spec in schema:
|
||||
src_col = (spec.get("source_field") or "").strip()
|
||||
if not src_col or src_col not in ACTIVITY_LOG_PATCHABLE_COLUMNS:
|
||||
if not src_col or src_col in ACTIVITY_LOG_PATCH_FORBIDDEN:
|
||||
continue
|
||||
if src_col not in ACTIVITY_LOG_PATCHABLE_COLUMNS:
|
||||
continue
|
||||
pkey = spec["key"]
|
||||
if pkey not in mapped:
|
||||
|
|
|
|||
|
|
@ -118,13 +118,18 @@ function buildMetricsPayload(schema, draft) {
|
|||
return out
|
||||
}
|
||||
|
||||
function SessionMetricsFields({ schema, values, setValues }) {
|
||||
if (!schema || schema.length === 0) return null
|
||||
function SessionMetricsFields({ schema, values, setValues, metrics }) {
|
||||
const schemaList = Array.isArray(schema) ? schema : []
|
||||
const metricRows = Array.isArray(metrics) ? metrics : []
|
||||
const schemaKeys = new Set(schemaList.map((s) => s.key))
|
||||
const orphanMetrics = metricRows.filter((row) => row && row.key && !schemaKeys.has(row.key))
|
||||
|
||||
if (schemaList.length === 0 && orphanMetrics.length === 0) return null
|
||||
const set = (k, v) => setValues((prev) => ({ ...prev, [k]: v }))
|
||||
return (
|
||||
<div style={{ marginTop: 12, paddingTop: 12, borderTop: '1px solid var(--border)' }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 8 }}>Weitere Kennwerte (Profil)</div>
|
||||
{schema.map((s) => (
|
||||
{schemaList.map((s) => (
|
||||
<div key={s.key} className="form-row">
|
||||
<label className="form-label">
|
||||
{s.name_de}
|
||||
|
|
@ -157,6 +162,44 @@ function SessionMetricsFields({ schema, values, setValues }) {
|
|||
<span className="form-unit" />
|
||||
</div>
|
||||
))}
|
||||
{orphanMetrics.length > 0 && (
|
||||
<div style={{ marginTop: 14 }}>
|
||||
<div style={{ fontSize: 12, color: 'var(--text3)', marginBottom: 8, lineHeight: 1.45 }}>
|
||||
Werte aus Import/älteren Daten, die zum <strong>aktuellen</strong> Trainingsprofil dieser Session (Kategorie/Typ
|
||||
in activity_log) nicht ins Schema passen — nur Anzeige. Sichtbar nach erneutem Laden, wenn die Daten in der
|
||||
Datenbank stehen.
|
||||
</div>
|
||||
{orphanMetrics.map((row) => {
|
||||
const disp =
|
||||
values[row.key] === null || values[row.key] === undefined || values[row.key] === ''
|
||||
? '—'
|
||||
: String(values[row.key])
|
||||
return (
|
||||
<div key={row.key} className="form-row">
|
||||
<label className="form-label">
|
||||
{row.key}
|
||||
{row.unit ? ` (${row.unit})` : ''}
|
||||
</label>
|
||||
{row.data_type === 'boolean' ? (
|
||||
<input type="checkbox" style={{ width: 'auto', marginRight: 'auto' }} checked={!!values[row.key]} readOnly disabled />
|
||||
) : (
|
||||
<div
|
||||
className="form-input"
|
||||
style={{
|
||||
background: 'var(--surface2)',
|
||||
cursor: 'default',
|
||||
color: 'var(--text1)',
|
||||
}}
|
||||
>
|
||||
{disp}
|
||||
</div>
|
||||
)}
|
||||
<span className="form-unit" />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -699,6 +742,7 @@ export default function ActivityPage() {
|
|||
)}
|
||||
<SessionMetricsFields
|
||||
schema={sessionDetail?.schema}
|
||||
metrics={sessionDetail?.metrics}
|
||||
values={metricDraft}
|
||||
setValues={setMetricDraft}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user