mitai-jinkendo/backend/scripts/inspect_activity_eav.py
Lars 2a6c437a08
All checks were successful
Deploy Development / deploy (push) Successful in 48s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 1s
Build Test / build-frontend (push) Successful in 17s
feat: Introduce activity schema headline binding for improved metrics handling
- Added a new function `activitySchemaHeadlineBinding` to streamline the binding of profile parameters to their corresponding headline columns, enhancing clarity in the metrics display logic.
- Refactored the `SessionMetricsFields` component to utilize the new binding function, simplifying the filtering of schema entries and improving maintainability.
- Updated the logic in `ActivityPage` to leverage the binding function for determining the appropriate column for metrics, ensuring consistent data handling across the application.
2026-04-16 13:15:41 +02:00

180 lines
6.5 KiB
Python

"""
Diagnose: Was liegt in activity_session_metrics (EAV) vs. activity_log?
Ausführung (mit gesetzten DB_*-Variablen wie die App, z. B. aus .env):
cd backend
python scripts/inspect_activity_eav.py
Lokal ohne Docker-Hostname: z. B. ``set DB_HOST=127.0.0.1`` (Windows) / ``export DB_HOST=127.0.0.1``,
Port/User/Pass wie in der laufenden Postgres-Instanz.
Im Backend-Container (Compose-Service meist ``backend``, Arbeitsverzeichnis ``/app``):
docker compose exec backend python /app/scripts/inspect_activity_eav.py
Optional:
python scripts/inspect_activity_eav.py --limit 30
python scripts/inspect_activity_eav.py --profile <uuid>
python scripts/inspect_activity_eav.py --activity <activity_log uuid>
Keine Schreibzugriffe — nur SELECT.
"""
from __future__ import annotations
import argparse
import os
import sys
# backend/ als Import-Root
_BACKEND_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if _BACKEND_ROOT not in sys.path:
sys.path.insert(0, _BACKEND_ROOT)
def _val_row(r: dict) -> str | None:
dt = r.get("data_type")
if dt == "integer":
v = r.get("value_int")
return str(v) if v is not None else None
if dt == "float":
v = r.get("value_num")
return str(v) if v is not None else None
if dt == "string":
v = r.get("value_text")
return repr(v) if v is not None else None
if dt == "boolean":
v = r.get("value_bool")
return str(v) if v is not None else None
return None
def main() -> None:
parser = argparse.ArgumentParser(description="EAV activity_session_metrics inspizieren")
parser.add_argument("--limit", type=int, default=40, help="Zeilen Report A/B")
parser.add_argument("--profile", type=str, default=None, help="profile_id filtern")
parser.add_argument("--activity", type=str, default=None, help="activity_log.id (einzelne Session)")
args = parser.parse_args()
from db import get_db, get_cursor
if args.activity:
with get_db() as conn:
with get_cursor(conn) as cur:
cur.execute(
"""
SELECT al.id, al.profile_id, al.date, al.start_time, al.source,
al.training_category, al.training_type_id, al.activity_type,
al.duration_min, al.kcal_active, al.hr_avg, al.hr_max, al.distance_km
FROM activity_log al
WHERE al.id = %s::uuid
""",
(args.activity,),
)
h = cur.fetchone()
if not h:
print("activity_log: keine Zeile für diese id")
return
print("=== activity_log (Kopfzeile) ===")
for k, v in dict(h).items():
print(f" {k}: {v}")
cur.execute(
"""
SELECT m.id AS metric_id, tp.key, tp.data_type, tp.source_field,
m.value_num, m.value_int, m.value_text, m.value_bool, m.updated_at
FROM activity_session_metrics m
JOIN training_parameters tp ON tp.id = m.training_parameter_id
WHERE m.activity_log_id = %s::uuid
ORDER BY tp.key
""",
(args.activity,),
)
rows = cur.fetchall()
print(f"\n=== activity_session_metrics ({len(rows)} Zeilen) ===")
for r in rows:
d = dict(r)
print(
f" {d['key']} ({d['data_type']}) "
f"value={_val_row(d)!r} source_field={d.get('source_field')!r} "
f"updated_at={d.get('updated_at')}"
)
if not rows:
print(" (keine EAV-Zeilen)")
return
prof_filter = ""
if args.profile:
prof_filter = " AND al.profile_id = %s::uuid "
params_a: tuple = (args.profile, args.limit) if args.profile else (args.limit,)
params_b: tuple = (args.profile, args.limit) if args.profile else (args.limit,)
q_recent_eav = f"""
SELECT
al.id AS activity_id,
al.profile_id,
al.date,
al.start_time,
al.source,
al.training_type_id,
al.training_category,
tp.key AS parameter_key,
tp.data_type,
tp.source_field AS tp_source_field,
m.value_num,
m.value_int,
m.value_text,
m.value_bool,
m.updated_at
FROM activity_session_metrics m
JOIN activity_log al ON al.id = m.activity_log_id
JOIN training_parameters tp ON tp.id = m.training_parameter_id
WHERE 1=1 {prof_filter}
ORDER BY m.updated_at DESC NULLS LAST, al.date DESC, al.start_time DESC
LIMIT %s
"""
q_csv_no_eav = f"""
SELECT
al.id AS activity_id,
al.profile_id,
al.date,
al.start_time,
al.source,
al.training_type_id,
al.training_category,
(SELECT COUNT(*) FROM activity_session_metrics m WHERE m.activity_log_id = al.id) AS eav_count
FROM activity_log al
WHERE al.source = 'csv' {prof_filter}
ORDER BY al.date DESC, al.start_time DESC
LIMIT %s
"""
with get_db() as conn:
with get_cursor(conn) as cur:
print("=== A) Neueste EAV-Zeilen (join activity_log + training_parameters) ===\n")
cur.execute(q_recent_eav, params_a)
for r in cur.fetchall():
d = dict(r)
v = _val_row(d)
print(
f"{d['date']} {d['start_time']} | {d['activity_id']} | src={d['source']!r} | "
f"type={d['training_type_id']} cat={d['training_category']!r} | "
f"{d['parameter_key']}={v!r} (tp.source_field={d.get('tp_source_field')!r})"
)
print("\n=== B) Neueste CSV-importierte Sessions: EAV-Anzahl pro Zeile ===\n")
cur.execute(q_csv_no_eav, params_b)
for r in cur.fetchall():
d = dict(r)
print(
f"{d['date']} {d['start_time']} | {d['activity_id']} | "
f"type={d['training_type_id']} | eav_count={d['eav_count']}"
)
print("\nFertig. Für eine Session im Detail: --activity <uuid>")
if __name__ == "__main__":
main()