feat: Update placeholder metadata and nutrition metrics
All checks were successful
Deploy Development / deploy (push) Successful in 54s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 1s
Build Test / build-frontend (push) Successful in 15s

- Adjusted the total number of placeholders from 116 to 114 across various documentation and code files to reflect the current state of the system.
- Enhanced TDEE calculation logic in `nutrition_metrics.py` to prioritize Mifflin–St Jeor BMR with PAL when demographic data is available, with a fallback to a weight-based estimate.
- Updated placeholder registrations to ensure consistency with the new metadata structure and improved data handling.
- Revised documentation to clarify the authoritative source of placeholder metadata and the implications of the changes on existing functionalities.

These updates improve the accuracy and consistency of the placeholder system and enhance the nutritional assessment capabilities within the application.
This commit is contained in:
Lars 2026-04-11 21:11:05 +02:00
parent 2ea5f905c4
commit 052ba195cc
11 changed files with 159 additions and 77 deletions

View File

@ -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

View File

@ -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

View File

@ -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 **MifflinSt 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)

View File

@ -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 MifflinSt 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: MifflinSt 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 (0100) 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:
# Moderate deficit/surplus = good
if 200 <= abs_balance <= 500:
return 100 return 100
elif 100 <= abs_balance <= 700: if x > 450:
return 85 return 38
elif abs_balance <= 900: 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
def _surplus_friendly(x: float) -> int:
if 80 <= x <= 480:
return 100
if -120 <= x < 80 or 480 < x <= 700:
return 86
if -380 <= x < -120:
return 68
if x > 850:
return 54
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 return 70
elif abs_balance <= 1200: if a <= 900:
return 55 return 55
else:
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)"""

View File

@ -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.
] ]

View File

@ -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
) )

View File

@ -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: MifflinSt 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) MifflinSt 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

View File

@ -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)",

View File

@ -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

View File

@ -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": {

View File

@ -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