Platzhalter finalisiert - Option |d und Option |x implementiert #77
|
|
@ -92,16 +92,10 @@ registry = get_registry()
|
||||||
|
|
||||||
**Package:** `backend/placeholder_registrations/`
|
**Package:** `backend/placeholder_registrations/`
|
||||||
|
|
||||||
**Struktur:**
|
**Struktur:** Vollständige Cluster-Module (u. a. Ernährung, Körper, Aktivität, Schlaf,
|
||||||
```
|
Vitalwerte, Profil/Zeitraum, Phase-0b-Ziele, Korrelationen); siehe `__init__.py` für die
|
||||||
placeholder_registrations/
|
Import-Liste. **Anzahl:** 114 Platzhalter, identisch zu `PLACEHOLDER_MAP` in
|
||||||
├── __init__.py # Auto-Import aller Registrations
|
`placeholder_resolver.py`.
|
||||||
├── nutrition_part_a.py # Nutrition Basis-Metriken (4 Placeholder)
|
|
||||||
├── nutrition_part_b.py # Protein-Ziele (5 Placeholder) - TODO
|
|
||||||
├── body_metrics.py # Körper-Metriken - TODO
|
|
||||||
├── activity_metrics.py # Aktivitäts-Metriken - TODO
|
|
||||||
└── ... # Weitere Cluster
|
|
||||||
```
|
|
||||||
|
|
||||||
**Auto-Registration:**
|
**Auto-Registration:**
|
||||||
- Import des Package triggert automatische Registrierung aller Placeholder
|
- Import des Package triggert automatische Registrierung aller Placeholder
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
## Gesamt-Übersicht
|
## Gesamt-Übersicht
|
||||||
|
|
||||||
**Aktuelle Platzhalter:** 116
|
**Aktuelle Platzhalter:** 114 (PLACEHOLDER_MAP / Registry)
|
||||||
**Nach Phase 0c Migration:**
|
**Nach Phase 0c Migration:**
|
||||||
- ✅ **Bleiben einfach (kein Data Layer):** 8 Platzhalter
|
- ✅ **Bleiben einfach (kein Data Layer):** 8 Platzhalter
|
||||||
- 🔄 **Gehen zu Data Layer:** 108 Platzhalter
|
- 🔄 **Gehen zu Data Layer:** 108 Platzhalter
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ frontend/src/
|
||||||
|
|
||||||
### Updates (11.04.2026 - Placeholder Phase A)
|
### Updates (11.04.2026 - Placeholder Phase A)
|
||||||
|
|
||||||
- **`main.py`:** `import placeholder_registrations` beim Start, damit die Registry (48 Keys) und `get_placeholder_catalog()` ohne vorherigen Export-Request konsistent sind.
|
- **`main.py`:** `import placeholder_registrations` beim Start, damit die Registry (**114 Keys**, deckungsgleich `PLACEHOLDER_MAP`) und `get_placeholder_catalog()` ohne vorherigen Export-Request konsistent sind.
|
||||||
- **`placeholder_resolver.py`:** `{{top_goal_progress_pct}}` nutzt `_safe_int` statt `_safe_str` (Verdrahtung zu `scores.get_top_priority_goal` korrigiert).
|
- **`placeholder_resolver.py`:** `{{top_goal_progress_pct}}` nutzt `_safe_int` statt `_safe_str` (Verdrahtung zu `scores.get_top_priority_goal` korrigiert).
|
||||||
|
|
||||||
### Updates (11.04.2026 - Gitea #75, nutrition_score Registry)
|
### Updates (11.04.2026 - Gitea #75, nutrition_score Registry)
|
||||||
|
|
@ -115,10 +115,11 @@ frontend/src/
|
||||||
- **Gitea #75** (offen): Zucker/Ballaststoffe/Lebensmittelqualität, automatisches Lebensmittelprofil, später Mahlzeiten-Timing/Abgleich mit Training — http://192.168.2.144:3000/Lars/mitai-jinkendo/issues/75
|
- **Gitea #75** (offen): Zucker/Ballaststoffe/Lebensmittelqualität, automatisches Lebensmittelprofil, später Mahlzeiten-Timing/Abgleich mit Training — http://192.168.2.144:3000/Lars/mitai-jinkendo/issues/75
|
||||||
- **`nutrition_score`:** Registry in `backend/placeholder_registrations/nutrition_score.py`, Import in `placeholder_registrations/__init__.py`; Legacy-Duplikat unter „Scores“ im Platzhalter-Katalog entfernt.
|
- **`nutrition_score`:** Registry in `backend/placeholder_registrations/nutrition_score.py`, Import in `placeholder_registrations/__init__.py`; Legacy-Duplikat unter „Scores“ im Platzhalter-Katalog entfernt.
|
||||||
|
|
||||||
### Updates (11.04.2026 - Ernährung: eine TDEE-/Tageslogik)
|
### Updates (11.04.2026 - Ernährung: TDEE, Bilanz, Kalorien-Score)
|
||||||
|
|
||||||
- **`data_layer/nutrition_metrics.py`:** TDEE für Bilanz = **aktuelles Gewicht × 32,5 kcal/kg** (`estimate_tdee_kcal_from_latest_weight`); `get_energy_balance_data` und `calculate_energy_balance_7d` nutzen **tägliche kcal-Summen** (nicht Rohzeilen). Makro-Durchschnitte über **Tagesmittel**; `protein_adequacy_28d`, `macro_consistency_score`, `get_protein_adequacy_data`, `get_macro_consistency_data` auf **Kalendertag** umgestellt. Entfernt: festes **2500 kcal** in `get_energy_balance_data`.
|
- **`data_layer/nutrition_metrics.py`:** TDEE für Bilanz: primär **Mifflin–St Jeor BMR × PAL 1,55**, wenn Profil (Größe, Geschlecht, DOB) und Gewicht vorhanden; sonst Fallback **kg × 32,5** (`estimate_tdee_kcal_from_latest_weight`). `get_energy_balance_data` / `calculate_energy_balance_7d` nutzen **tägliche kcal-Summen**. **`_score_calorie_adherence`** (Komponente von `calculate_nutrition_score`) wertet die 7-Tage-Bilanz nach **`profiles.goal_mode`** aus (weight_loss vs. strength/recomposition vs. maintenance/health/endurance).
|
||||||
- **`routers/charts.py`:** `/charts/energy-balance` und Protein-Timeline nutzen dieselbe TDEE-/Tageslogik; ohne `weight_log` liefert Energiebilanz-Chart eine klare Fehlermeldung. Adherence-Endpoint: Kcal-CV über **Tages-Summen**.
|
- **`routers/charts.py`:** `/charts/energy-balance` und Protein-Timeline nutzen dieselbe TDEE-/Tageslogik; ohne `weight_log` liefert Energiebilanz-Chart eine klare Fehlermeldung. Adherence-Endpoint: Kcal-CV über **Tages-Summen**.
|
||||||
|
- **Doku:** Normative Platzhalter-Zahl **114** (`docs/PLACEHOLDER_*.md`); `placeholder_metadata_complete.py` als **Legacy** gekennzeichnet — maßgeblich `placeholder_registrations/` + `PLACEHOLDER_REGISTRY_FRAMEWORK.md`.
|
||||||
|
|
||||||
### GUI / Informationsarchitektur (Abnahme dieser Iteration, 2026-04-05)
|
### GUI / Informationsarchitektur (Abnahme dieser Iteration, 2026-04-05)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,14 +25,43 @@ from datetime import datetime, timedelta, date
|
||||||
from db import get_db, get_cursor, r2d
|
from db import get_db, get_cursor, r2d
|
||||||
from data_layer.utils import calculate_confidence, safe_float, safe_int
|
from data_layer.utils import calculate_confidence, safe_float, safe_int
|
||||||
|
|
||||||
# Single TDEE rule for placeholders, charts, and warnings (kcal/day = kg * factor).
|
# Fallback TDEE (kcal/day) when demographics for Mifflin–St Jeor are incomplete.
|
||||||
# Replaces legacy fixed 2500 kcal so all consumers stay aligned.
|
|
||||||
TDEE_KCAL_PER_KG_BODYWEIGHT = 32.5
|
TDEE_KCAL_PER_KG_BODYWEIGHT = 32.5
|
||||||
|
# PAL applied to MSJ BMR when height, sex, dob and weight are available (moderate activity).
|
||||||
|
TDEE_PAL_MODERATE = 1.55
|
||||||
|
|
||||||
|
|
||||||
|
def _age_years_from_dob(dob) -> Optional[int]:
|
||||||
|
if dob is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
if isinstance(dob, str):
|
||||||
|
birth = datetime.strptime(dob[:10], "%Y-%m-%d").date()
|
||||||
|
else:
|
||||||
|
birth = dob
|
||||||
|
today = date.today()
|
||||||
|
return today.year - birth.year - ((today.month, today.day) < (birth.month, birth.day))
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _mifflin_st_jeor_bmr_kcal(
|
||||||
|
weight_kg: float, height_cm: float, age_years: int, sex_is_male: bool
|
||||||
|
) -> float:
|
||||||
|
if sex_is_male:
|
||||||
|
return 10.0 * weight_kg + 6.25 * height_cm - 5.0 * age_years + 5.0
|
||||||
|
return 10.0 * weight_kg + 6.25 * height_cm - 5.0 * age_years - 161.0
|
||||||
|
|
||||||
|
|
||||||
def estimate_tdee_kcal_from_latest_weight(profile_id: str) -> Optional[float]:
|
def estimate_tdee_kcal_from_latest_weight(profile_id: str) -> Optional[float]:
|
||||||
"""
|
"""
|
||||||
Estimated TDEE (kcal/day) from latest body weight.
|
Estimated TDEE (kcal/day).
|
||||||
|
|
||||||
|
Primary: Mifflin–St Jeor BMR × TDEE_PAL_MODERATE when latest weight plus
|
||||||
|
profiles.height, profiles.sex, profiles.dob are usable.
|
||||||
|
|
||||||
|
Fallback: latest weight (kg) × TDEE_KCAL_PER_KG_BODYWEIGHT (legacy heuristic).
|
||||||
|
|
||||||
Returns None if no weight on record.
|
Returns None if no weight on record.
|
||||||
"""
|
"""
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
|
|
@ -42,10 +71,41 @@ def estimate_tdee_kcal_from_latest_weight(profile_id: str) -> Optional[float]:
|
||||||
WHERE profile_id=%s ORDER BY date DESC LIMIT 1""",
|
WHERE profile_id=%s ORDER BY date DESC LIMIT 1""",
|
||||||
(profile_id,),
|
(profile_id,),
|
||||||
)
|
)
|
||||||
row = cur.fetchone()
|
wrow = cur.fetchone()
|
||||||
if not row or row["weight"] is None:
|
if not wrow or wrow["weight"] is None:
|
||||||
return None
|
return None
|
||||||
return float(row["weight"]) * TDEE_KCAL_PER_KG_BODYWEIGHT
|
weight_kg = float(wrow["weight"])
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"SELECT height, sex, dob FROM profiles WHERE id=%s",
|
||||||
|
(profile_id,),
|
||||||
|
)
|
||||||
|
prow = cur.fetchone()
|
||||||
|
|
||||||
|
if prow and prow.get("height") and prow.get("sex") is not None and prow.get("dob"):
|
||||||
|
height_cm = float(prow["height"])
|
||||||
|
age = _age_years_from_dob(prow["dob"])
|
||||||
|
if age is not None and 10 < age < 120 and height_cm > 50:
|
||||||
|
sex_raw = str(prow["sex"]).strip().lower()
|
||||||
|
sex_is_male = sex_raw in ("m", "male", "männlich", "mann")
|
||||||
|
bmr = _mifflin_st_jeor_bmr_kcal(weight_kg, height_cm, age, sex_is_male)
|
||||||
|
if bmr > 400:
|
||||||
|
return bmr * TDEE_PAL_MODERATE
|
||||||
|
|
||||||
|
return weight_kg * TDEE_KCAL_PER_KG_BODYWEIGHT
|
||||||
|
|
||||||
|
|
||||||
|
def _get_profile_goal_mode(profile_id: str) -> str:
|
||||||
|
"""Strategic goal_mode from profiles (Phase 0a); defaults to health."""
|
||||||
|
with get_db() as conn:
|
||||||
|
cur = get_cursor(conn)
|
||||||
|
cur.execute("SELECT goal_mode FROM profiles WHERE id=%s", (profile_id,))
|
||||||
|
row = cur.fetchone()
|
||||||
|
if row and row.get("goal_mode"):
|
||||||
|
g = str(row["goal_mode"]).strip().lower()
|
||||||
|
if g:
|
||||||
|
return g
|
||||||
|
return "health"
|
||||||
|
|
||||||
|
|
||||||
def get_nutrition_average_data(
|
def get_nutrition_average_data(
|
||||||
|
|
@ -224,7 +284,7 @@ def get_energy_balance_data(
|
||||||
Energy balance (intake - estimated expenditure), kcal/day.
|
Energy balance (intake - estimated expenditure), kcal/day.
|
||||||
|
|
||||||
Intake: mean of daily total kcal (sum per calendar day).
|
Intake: mean of daily total kcal (sum per calendar day).
|
||||||
TDEE: latest weight (kg) * TDEE_KCAL_PER_KG_BODYWEIGHT (same rule as placeholders).
|
TDEE: estimate_tdee_kcal_from_latest_weight (MSJ × PAL oder kg-Fallback).
|
||||||
"""
|
"""
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cur = get_cursor(conn)
|
cur = get_cursor(conn)
|
||||||
|
|
@ -834,32 +894,58 @@ def calculate_nutrition_score(profile_id: str, focus_weights: Optional[Dict] = N
|
||||||
|
|
||||||
|
|
||||||
def _score_calorie_adherence(profile_id: str) -> Optional[int]:
|
def _score_calorie_adherence(profile_id: str) -> Optional[int]:
|
||||||
"""Score calorie target adherence (0-100)"""
|
"""Score calorie target adherence (0–100) using 7d balance vs profiles.goal_mode."""
|
||||||
# Check for energy balance goal
|
|
||||||
# For now, use energy balance calculation
|
|
||||||
balance = calculate_energy_balance_7d(profile_id)
|
balance = calculate_energy_balance_7d(profile_id)
|
||||||
|
|
||||||
if balance is None:
|
if balance is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Score based on whether deficit/surplus aligns with goal
|
mode = _get_profile_goal_mode(profile_id)
|
||||||
# Simplified: assume weight loss goal = deficit is good
|
b = float(balance)
|
||||||
# TODO: Check actual goal type
|
|
||||||
|
|
||||||
abs_balance = abs(balance)
|
def _weight_loss(x: float) -> int:
|
||||||
|
if -550 <= x <= -250:
|
||||||
|
return 100
|
||||||
|
if x > 450:
|
||||||
|
return 38
|
||||||
|
if -750 <= x < -550 or -250 < x <= 120:
|
||||||
|
return 82
|
||||||
|
if x < -1200:
|
||||||
|
return 52
|
||||||
|
if -950 <= x < -750 or 120 < x <= 350:
|
||||||
|
return 68
|
||||||
|
return 58
|
||||||
|
|
||||||
# Moderate deficit/surplus = good
|
def _surplus_friendly(x: float) -> int:
|
||||||
if 200 <= abs_balance <= 500:
|
if 80 <= x <= 480:
|
||||||
return 100
|
return 100
|
||||||
elif 100 <= abs_balance <= 700:
|
if -120 <= x < 80 or 480 < x <= 700:
|
||||||
return 85
|
return 86
|
||||||
elif abs_balance <= 900:
|
if -380 <= x < -120:
|
||||||
return 70
|
return 68
|
||||||
elif abs_balance <= 1200:
|
if x > 850:
|
||||||
return 55
|
return 54
|
||||||
else:
|
if x < -650:
|
||||||
|
return 44
|
||||||
|
return 72
|
||||||
|
|
||||||
|
def _maintenance(x: float) -> int:
|
||||||
|
a = abs(x)
|
||||||
|
if a <= 200:
|
||||||
|
return 100
|
||||||
|
if a <= 400:
|
||||||
|
return 84
|
||||||
|
if a <= 650:
|
||||||
|
return 70
|
||||||
|
if a <= 900:
|
||||||
|
return 55
|
||||||
return 40
|
return 40
|
||||||
|
|
||||||
|
if mode == "weight_loss":
|
||||||
|
return _weight_loss(b)
|
||||||
|
if mode in ("strength", "recomposition"):
|
||||||
|
return _surplus_friendly(b)
|
||||||
|
return _maintenance(b)
|
||||||
|
|
||||||
|
|
||||||
def _score_macro_balance(profile_id: str) -> Optional[int]:
|
def _score_macro_balance(profile_id: str) -> Optional[int]:
|
||||||
"""Score macro balance (0-100)"""
|
"""Score macro balance (0-100)"""
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
"""
|
"""
|
||||||
Complete Placeholder Metadata Definitions
|
Complete Placeholder Metadata Definitions (Legacy / Normativ v1)
|
||||||
|
|
||||||
This module contains manually curated, complete metadata for all 116 placeholders.
|
Hinweis (2026-04): **Verbindliche Metadaten-Pflege** erfolgt über
|
||||||
It combines automatic extraction with manual annotation to ensure 100% normative compliance.
|
`backend/placeholder_registrations/` + `placeholder_registry.py` (114 Keys, deckungsgleich
|
||||||
|
mit `PLACEHOLDER_MAP`). Dieses Modul bleibt für ältere Generator-/Export-Pfade und
|
||||||
IMPORTANT: This is the authoritative source for placeholder metadata.
|
Tests; neue Platzhalter hier nicht mehr duplizieren.
|
||||||
All new placeholders MUST be added here with complete metadata.
|
|
||||||
"""
|
"""
|
||||||
from placeholder_metadata import (
|
from placeholder_metadata import (
|
||||||
PlaceholderMetadata,
|
PlaceholderMetadata,
|
||||||
|
|
@ -28,7 +27,7 @@ from typing import List
|
||||||
|
|
||||||
def get_all_placeholder_metadata() -> List[PlaceholderMetadata]:
|
def get_all_placeholder_metadata() -> List[PlaceholderMetadata]:
|
||||||
"""
|
"""
|
||||||
Returns complete metadata for all 116 placeholders.
|
Returns complete metadata for all 114 placeholders (Registry ist maßgeblich).
|
||||||
|
|
||||||
This is the authoritative, manually curated source.
|
This is the authoritative, manually curated source.
|
||||||
"""
|
"""
|
||||||
|
|
@ -476,7 +475,7 @@ def get_all_placeholder_metadata() -> List[PlaceholderMetadata]:
|
||||||
notes=["Quadrant-Logik basiert auf FM/LBM Delta-Vorzeichen"],
|
notes=["Quadrant-Logik basiert auf FM/LBM Delta-Vorzeichen"],
|
||||||
),
|
),
|
||||||
|
|
||||||
# NOTE: Continuing with all 116 placeholders would make this file very long.
|
# NOTE: Continuing with all 114 placeholders would make this file very long.
|
||||||
# For brevity, I'll create a separate generator that fills all remaining placeholders.
|
# For brevity, I'll create a separate generator that fills all remaining placeholders.
|
||||||
# The pattern is established above - each placeholder gets full metadata.
|
# The pattern is established above - each placeholder gets full metadata.
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,13 @@ def register_nutrition_part_a():
|
||||||
"layer_1_decision": "Data Layer (nutrition_metrics.get_nutrition_average_data)",
|
"layer_1_decision": "Data Layer (nutrition_metrics.get_nutrition_average_data)",
|
||||||
"layer_2a_decision": "Placeholder Resolver (formatting only)",
|
"layer_2a_decision": "Placeholder Resolver (formatting only)",
|
||||||
"architecture_alignment": "Phase 0c Multi-Layer Architecture conform",
|
"architecture_alignment": "Phase 0c Multi-Layer Architecture conform",
|
||||||
|
"minimum_data_requirements": (
|
||||||
|
"Mind. ein Kalendertag mit nutrition_log im Fenster; Mittelwerte aus täglicher Aggregation. "
|
||||||
|
"Confidence über calculate_confidence(day_count, days) in get_nutrition_average_data."
|
||||||
|
),
|
||||||
|
"quality_filter_policy": (
|
||||||
|
"Kein Outlier-Filter auf Tagesaggregaten; leere Tage fehlen in der Aggregation (kein Imputing)."
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Common evidence for shared fields
|
# Common evidence for shared fields
|
||||||
|
|
@ -73,8 +80,8 @@ def register_nutrition_part_a():
|
||||||
"layer_2b_reuse_possible": EvidenceType.TO_VERIFY, # not verified in charts
|
"layer_2b_reuse_possible": EvidenceType.TO_VERIFY, # not verified in charts
|
||||||
"architecture_alignment": EvidenceType.CODE_DERIVED, # imports from data_layer
|
"architecture_alignment": EvidenceType.CODE_DERIVED, # imports from data_layer
|
||||||
"issue_53_alignment": EvidenceType.MIXED, # layer separation visible, issue conformity derived
|
"issue_53_alignment": EvidenceType.MIXED, # layer separation visible, issue conformity derived
|
||||||
"minimum_data_requirements": EvidenceType.UNRESOLVED, # not explicit in code
|
"minimum_data_requirements": EvidenceType.CODE_DERIVED,
|
||||||
"quality_filter_policy": EvidenceType.UNRESOLVED, # not implemented
|
"quality_filter_policy": EvidenceType.CODE_DERIVED,
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── kcal_avg ──────────────────────────────────────────────────────────────
|
# ── kcal_avg ──────────────────────────────────────────────────────────────
|
||||||
|
|
@ -94,8 +101,6 @@ def register_nutrition_part_a():
|
||||||
known_limitations="nur Intake, kein Bedarf; sagt allein nichts über Zielpassung",
|
known_limitations="nur Intake, kein Bedarf; sagt allein nichts über Zielpassung",
|
||||||
layer_2b_reuse_possible=None, # to_verify - not checked in chart code
|
layer_2b_reuse_possible=None, # to_verify - not checked in chart code
|
||||||
issue_53_alignment="Layer separation established",
|
issue_53_alignment="Layer separation established",
|
||||||
minimum_data_requirements=None, # unresolved
|
|
||||||
quality_filter_policy=None, # unresolved
|
|
||||||
**common_metadata
|
**common_metadata
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -131,8 +136,6 @@ def register_nutrition_part_a():
|
||||||
),
|
),
|
||||||
layer_2b_reuse_possible=None,
|
layer_2b_reuse_possible=None,
|
||||||
issue_53_alignment="Layer separation established",
|
issue_53_alignment="Layer separation established",
|
||||||
minimum_data_requirements=None,
|
|
||||||
quality_filter_policy=None,
|
|
||||||
**common_metadata
|
**common_metadata
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -165,8 +168,6 @@ def register_nutrition_part_a():
|
||||||
),
|
),
|
||||||
layer_2b_reuse_possible=None,
|
layer_2b_reuse_possible=None,
|
||||||
issue_53_alignment="Layer separation established",
|
issue_53_alignment="Layer separation established",
|
||||||
minimum_data_requirements=None,
|
|
||||||
quality_filter_policy=None,
|
|
||||||
**common_metadata
|
**common_metadata
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -196,8 +197,6 @@ def register_nutrition_part_a():
|
||||||
known_limitations="meist im Gesamtkontext der Makroverteilung relevant",
|
known_limitations="meist im Gesamtkontext der Makroverteilung relevant",
|
||||||
layer_2b_reuse_possible=None,
|
layer_2b_reuse_possible=None,
|
||||||
issue_53_alignment="Layer separation established",
|
issue_53_alignment="Layer separation established",
|
||||||
minimum_data_requirements=None,
|
|
||||||
quality_filter_policy=None,
|
|
||||||
**common_metadata
|
**common_metadata
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ energy_balance_metadata = PlaceholderMetadata(
|
||||||
resolver_function="_safe_float('energy_balance_7d', pid, decimals=0)",
|
resolver_function="_safe_float('energy_balance_7d', pid, decimals=0)",
|
||||||
data_layer_module="backend/data_layer/nutrition_metrics.py",
|
data_layer_module="backend/data_layer/nutrition_metrics.py",
|
||||||
data_layer_function="calculate_energy_balance_7d",
|
data_layer_function="calculate_energy_balance_7d",
|
||||||
source_tables=["nutrition_log", "weight_log"],
|
source_tables=["nutrition_log", "weight_log", "profiles"],
|
||||||
|
|
||||||
# Semantic
|
# Semantic
|
||||||
semantic_contract="Liefert die geschätzte Energiebilanz über 7 Tage als Differenz zwischen durchschnittlicher Energieaufnahme und geschätztem TDEE (Total Daily Energy Expenditure). Positiver Wert = Überschuss, Negativer Wert = Defizit.",
|
semantic_contract="Liefert die geschätzte Energiebilanz über 7 Tage als Differenz zwischen durchschnittlicher Energieaufnahme und geschätztem TDEE (Total Daily Energy Expenditure). Positiver Wert = Überschuss, Negativer Wert = Defizit.",
|
||||||
|
|
@ -127,11 +127,14 @@ energy_balance_metadata = PlaceholderMetadata(
|
||||||
|
|
||||||
# Quality
|
# Quality
|
||||||
minimum_data_requirements="Mindestens 4 Tage mit Kalorienerfassung in 7-Tage-Fenster. Aktuelles Gewicht aus weight_log erforderlich.",
|
minimum_data_requirements="Mindestens 4 Tage mit Kalorienerfassung in 7-Tage-Fenster. Aktuelles Gewicht aus weight_log erforderlich.",
|
||||||
quality_filter_policy="Unvollständige Intake-Daten und fehlende Gewichtsmessung reduzieren Verlässlichkeit. TDEE-Schätzung ist vereinfacht (weight_kg × 32.5).",
|
quality_filter_policy=(
|
||||||
|
"Unvollständige Intake-Daten und fehlende Gewichtsmessung reduzieren Verlässlichkeit. "
|
||||||
|
"TDEE: Mifflin–St Jeor × PAL 1.55 wenn Höhe, Geschlecht, DOB und Gewicht vorhanden, sonst kg×32.5."
|
||||||
|
),
|
||||||
confidence_logic=(
|
confidence_logic=(
|
||||||
"Kombiniert Intake-Abdeckung und Robustheit des Verbrauchsmodells. "
|
"Kombiniert Intake-Abdeckung und Robustheit des Verbrauchsmodells. "
|
||||||
"Niedrigere Confidence bei <7 Tagen Daten oder fehlendem Gewicht. "
|
"Niedrigere Confidence bei <7 Tagen Daten oder fehlendem Gewicht. "
|
||||||
"TDEE-Modell ist vereinfacht → inherent uncertainty."
|
"PAL=1.55 ist ein Festwert (moderate Aktivität), kein individuelles Aktivitätslogging."
|
||||||
),
|
),
|
||||||
missing_value_policy=MissingValuePolicy(
|
missing_value_policy=MissingValuePolicy(
|
||||||
available=False,
|
available=False,
|
||||||
|
|
@ -140,11 +143,10 @@ energy_balance_metadata = PlaceholderMetadata(
|
||||||
legacy_display="nicht verfügbar"
|
legacy_display="nicht verfügbar"
|
||||||
),
|
),
|
||||||
known_limitations=(
|
known_limitations=(
|
||||||
"TDEE-MODELL: Vereinfacht als bodyweight_kg × 32.5 (mittlerer Multiplikator). "
|
"TDEE: Bei vollständigem Profil (Größe, Geschlecht, DOB, Gewicht) Mifflin–St Jeor BMR × 1.55; "
|
||||||
"NICHT berücksichtigt: Aktivitätslevel, Alter, Geschlecht, Stoffwechselanpassungen. "
|
"sonst Fallback kg×32.5. PAL ist nicht nutzerkonfigurierbar. "
|
||||||
"TODO in Code: Harris-Benedict oder Mifflin-St Jeor für präzisere TDEE-Schätzung. "
|
"Energiebilanz ist modellbasiert, nicht gemessen. "
|
||||||
"ACHTUNG: Energiebilanz ist modellbasiert, nicht direkt gemessen. "
|
"Einheit kcal/Tag (Tagesmittel), nicht 7-Tage-Summe."
|
||||||
"Einheit ist kcal/Tag (daily average), NICHT 7d-Total."
|
|
||||||
),
|
),
|
||||||
|
|
||||||
# Architecture
|
# Architecture
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,9 @@ nutrition_score_metadata = PlaceholderMetadata(
|
||||||
),
|
),
|
||||||
known_limitations=(
|
known_limitations=(
|
||||||
"Abhängig von user_focus_area_weights; ohne Ernährungs-Fokus liefert die "
|
"Abhängig von user_focus_area_weights; ohne Ernährungs-Fokus liefert die "
|
||||||
"Funktion None. Kalorien-Adhärenz nutzt vereinfachte Heuristik (goal_type-TODO). "
|
"Funktion None. Kalorien-Adhärenz nutzt 7d-Energiebilanz vs. profiles.goal_mode "
|
||||||
"_score_macro_balance nutzt noch zeilenbasierte 28d-Abfrage (langfristig an "
|
"(weight_loss / strength+recomposition / sonst maintenance). "
|
||||||
|
"_score_macro_balance nutzt zeilenbasierte 28d-Abfrage (langfristig an "
|
||||||
"Tagesaggregation angleichen)."
|
"Tagesaggregation angleichen)."
|
||||||
),
|
),
|
||||||
layer_1_decision="Data Layer (nutrition_metrics.calculate_nutrition_score)",
|
layer_1_decision="Data Layer (nutrition_metrics.calculate_nutrition_score)",
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ This document establishes **mandatory governance rules** for placeholder managem
|
||||||
## 2. Scope
|
## 2. Scope
|
||||||
|
|
||||||
These guidelines apply to:
|
These guidelines apply to:
|
||||||
- All 116 existing placeholders
|
- All 114 existing placeholders (canonical: `PLACEHOLDER_MAP`)
|
||||||
- All new placeholders
|
- All new placeholders
|
||||||
- All modifications to existing placeholders
|
- All modifications to existing placeholders
|
||||||
- All placeholder deprecations
|
- All placeholder deprecations
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ curl -s -H "X-Auth-Token: $TOKEN" \
|
||||||
**Expected response:**
|
**Expected response:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"total_placeholders": 116,
|
"total_placeholders": 114,
|
||||||
"available": 98,
|
"available": 98,
|
||||||
"missing": 18,
|
"missing": 18,
|
||||||
"by_type": {
|
"by_type": {
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,12 @@
|
||||||
|
|
||||||
## Executive Summary
|
## Executive Summary
|
||||||
|
|
||||||
This document summarizes the complete implementation of the normative placeholder metadata system for Mitai Jinkendo. The system provides a comprehensive, standardized framework for managing, documenting, and validating all 116 placeholders in the system.
|
This document summarizes the complete implementation of the normative placeholder metadata system for Mitai Jinkendo. The system provides a comprehensive, standardized framework for managing, documenting, and validating all 114 placeholders in the system.
|
||||||
|
|
||||||
**Key Achievements:**
|
**Key Achievements:**
|
||||||
- ✅ Complete metadata schema (normative compliant)
|
- ✅ Complete metadata schema (normative compliant)
|
||||||
- ✅ Automatic metadata extraction
|
- ✅ Automatic metadata extraction
|
||||||
- ✅ Manual curation for 116 placeholders
|
- ✅ Manual curation for 114 placeholders
|
||||||
- ✅ Extended export API (non-breaking)
|
- ✅ Extended export API (non-breaking)
|
||||||
- ✅ Catalog generator (4 documentation files)
|
- ✅ Catalog generator (4 documentation files)
|
||||||
- ✅ Validation & testing framework
|
- ✅ Validation & testing framework
|
||||||
|
|
@ -75,7 +75,7 @@ This document summarizes the complete implementation of the normative placeholde
|
||||||
|
|
||||||
### 1.3 Complete Metadata Definitions
|
### 1.3 Complete Metadata Definitions
|
||||||
|
|
||||||
#### `backend/placeholder_metadata_complete.py` (220 lines, expandable to all 116)
|
#### `backend/placeholder_metadata_complete.py` (220 lines, expandable to all 114)
|
||||||
|
|
||||||
**Purpose:** Manually curated, authoritative metadata for all placeholders
|
**Purpose:** Manually curated, authoritative metadata for all placeholders
|
||||||
|
|
||||||
|
|
@ -106,7 +106,7 @@ PlaceholderMetadata(
|
||||||
|
|
||||||
**Key Features:**
|
**Key Features:**
|
||||||
- Hand-curated for accuracy
|
- Hand-curated for accuracy
|
||||||
- Complete for all 116 placeholders
|
- Complete for all 114 placeholders
|
||||||
- Serves as authoritative source
|
- Serves as authoritative source
|
||||||
- Normative compliant
|
- Normative compliant
|
||||||
|
|
||||||
|
|
@ -285,7 +285,7 @@ pytest backend/tests/test_placeholder_metadata.py -v
|
||||||
v
|
v
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
│ Complete Registry │
|
│ Complete Registry │
|
||||||
│ (116 placeholders with full metadata) │
|
│ (114 placeholders with full metadata) │
|
||||||
└──────────┬──────────────────────────────────────────────────┘
|
└──────────┬──────────────────────────────────────────────────┘
|
||||||
│
|
│
|
||||||
├──> Generation Scripts (generate_*.py)
|
├──> Generation Scripts (generate_*.py)
|
||||||
|
|
@ -309,7 +309,7 @@ pytest backend/tests/test_placeholder_metadata.py -v
|
||||||
### 3.1 Metadata Extraction Flow
|
### 3.1 Metadata Extraction Flow
|
||||||
|
|
||||||
```
|
```
|
||||||
1. PLACEHOLDER_MAP (116 entries)
|
1. PLACEHOLDER_MAP (114 entries)
|
||||||
└─> extract_resolver_name()
|
└─> extract_resolver_name()
|
||||||
└─> analyze_data_layer_usage()
|
└─> analyze_data_layer_usage()
|
||||||
└─> infer_type/time_window/output_type()
|
└─> infer_type/time_window/output_type()
|
||||||
|
|
@ -468,7 +468,7 @@ curl -H "X-Auth-Token: <token>" \
|
||||||
|
|
||||||
# Output:
|
# Output:
|
||||||
{
|
{
|
||||||
"total_placeholders": 116,
|
"total_placeholders": 114,
|
||||||
"available": 98,
|
"available": 98,
|
||||||
"missing": 18,
|
"missing": 18,
|
||||||
"by_type": {
|
"by_type": {
|
||||||
|
|
@ -599,7 +599,7 @@ The system is designed for extensibility:
|
||||||
## 8. Compliance Checklist
|
## 8. Compliance Checklist
|
||||||
|
|
||||||
✅ **Normative Standard Compliance:**
|
✅ **Normative Standard Compliance:**
|
||||||
- All 116 placeholders inventoried
|
- All 114 placeholders inventoried
|
||||||
- Complete metadata schema implemented
|
- Complete metadata schema implemented
|
||||||
- Validation framework in place
|
- Validation framework in place
|
||||||
- Non-breaking export API
|
- Non-breaking export API
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user