Platzhalter finalisiert - Option |d und Option |x implementiert #77
|
|
@ -92,16 +92,10 @@ registry = get_registry()
|
|||
|
||||
**Package:** `backend/placeholder_registrations/`
|
||||
|
||||
**Struktur:**
|
||||
```
|
||||
placeholder_registrations/
|
||||
├── __init__.py # Auto-Import aller Registrations
|
||||
├── 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
|
||||
```
|
||||
**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
|
||||
Import-Liste. **Anzahl:** 114 Platzhalter, identisch zu `PLACEHOLDER_MAP` in
|
||||
`placeholder_resolver.py`.
|
||||
|
||||
**Auto-Registration:**
|
||||
- Import des Package triggert automatische Registrierung aller Placeholder
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
## Gesamt-Übersicht
|
||||
|
||||
**Aktuelle Platzhalter:** 116
|
||||
**Aktuelle Platzhalter:** 114 (PLACEHOLDER_MAP / Registry)
|
||||
**Nach Phase 0c Migration:**
|
||||
- ✅ **Bleiben einfach (kein Data Layer):** 8 Platzhalter
|
||||
- 🔄 **Gehen zu Data Layer:** 108 Platzhalter
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ frontend/src/
|
|||
|
||||
### 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).
|
||||
|
||||
### 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
|
||||
- **`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**.
|
||||
- **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)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,14 +25,43 @@ from datetime import datetime, timedelta, date
|
|||
from db import get_db, get_cursor, r2d
|
||||
from data_layer.utils import calculate_confidence, safe_float, safe_int
|
||||
|
||||
# Single TDEE rule for placeholders, charts, and warnings (kcal/day = kg * factor).
|
||||
# Replaces legacy fixed 2500 kcal so all consumers stay aligned.
|
||||
# Fallback TDEE (kcal/day) when demographics for Mifflin–St Jeor are incomplete.
|
||||
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]:
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
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""",
|
||||
(profile_id,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if not row or row["weight"] is None:
|
||||
wrow = cur.fetchone()
|
||||
if not wrow or wrow["weight"] is 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(
|
||||
|
|
@ -224,7 +284,7 @@ def get_energy_balance_data(
|
|||
Energy balance (intake - estimated expenditure), kcal/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:
|
||||
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]:
|
||||
"""Score calorie target adherence (0-100)"""
|
||||
# Check for energy balance goal
|
||||
# For now, use energy balance calculation
|
||||
"""Score calorie target adherence (0–100) using 7d balance vs profiles.goal_mode."""
|
||||
balance = calculate_energy_balance_7d(profile_id)
|
||||
|
||||
if balance is None:
|
||||
return None
|
||||
|
||||
# Score based on whether deficit/surplus aligns with goal
|
||||
# Simplified: assume weight loss goal = deficit is good
|
||||
# TODO: Check actual goal type
|
||||
mode = _get_profile_goal_mode(profile_id)
|
||||
b = float(balance)
|
||||
|
||||
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
|
||||
if 200 <= abs_balance <= 500:
|
||||
return 100
|
||||
elif 100 <= abs_balance <= 700:
|
||||
return 85
|
||||
elif abs_balance <= 900:
|
||||
return 70
|
||||
elif abs_balance <= 1200:
|
||||
return 55
|
||||
else:
|
||||
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
|
||||
if a <= 900:
|
||||
return 55
|
||||
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]:
|
||||
"""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.
|
||||
It combines automatic extraction with manual annotation to ensure 100% normative compliance.
|
||||
|
||||
IMPORTANT: This is the authoritative source for placeholder metadata.
|
||||
All new placeholders MUST be added here with complete metadata.
|
||||
Hinweis (2026-04): **Verbindliche Metadaten-Pflege** erfolgt über
|
||||
`backend/placeholder_registrations/` + `placeholder_registry.py` (114 Keys, deckungsgleich
|
||||
mit `PLACEHOLDER_MAP`). Dieses Modul bleibt für ältere Generator-/Export-Pfade und
|
||||
Tests; neue Platzhalter hier nicht mehr duplizieren.
|
||||
"""
|
||||
from placeholder_metadata import (
|
||||
PlaceholderMetadata,
|
||||
|
|
@ -28,7 +27,7 @@ from typing import List
|
|||
|
||||
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.
|
||||
"""
|
||||
|
|
@ -476,7 +475,7 @@ def get_all_placeholder_metadata() -> List[PlaceholderMetadata]:
|
|||
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.
|
||||
# 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_2a_decision": "Placeholder Resolver (formatting only)",
|
||||
"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
|
||||
|
|
@ -73,8 +80,8 @@ def register_nutrition_part_a():
|
|||
"layer_2b_reuse_possible": EvidenceType.TO_VERIFY, # not verified in charts
|
||||
"architecture_alignment": EvidenceType.CODE_DERIVED, # imports from data_layer
|
||||
"issue_53_alignment": EvidenceType.MIXED, # layer separation visible, issue conformity derived
|
||||
"minimum_data_requirements": EvidenceType.UNRESOLVED, # not explicit in code
|
||||
"quality_filter_policy": EvidenceType.UNRESOLVED, # not implemented
|
||||
"minimum_data_requirements": EvidenceType.CODE_DERIVED,
|
||||
"quality_filter_policy": EvidenceType.CODE_DERIVED,
|
||||
}
|
||||
|
||||
# ── kcal_avg ──────────────────────────────────────────────────────────────
|
||||
|
|
@ -94,8 +101,6 @@ def register_nutrition_part_a():
|
|||
known_limitations="nur Intake, kein Bedarf; sagt allein nichts über Zielpassung",
|
||||
layer_2b_reuse_possible=None, # to_verify - not checked in chart code
|
||||
issue_53_alignment="Layer separation established",
|
||||
minimum_data_requirements=None, # unresolved
|
||||
quality_filter_policy=None, # unresolved
|
||||
**common_metadata
|
||||
)
|
||||
|
||||
|
|
@ -131,8 +136,6 @@ def register_nutrition_part_a():
|
|||
),
|
||||
layer_2b_reuse_possible=None,
|
||||
issue_53_alignment="Layer separation established",
|
||||
minimum_data_requirements=None,
|
||||
quality_filter_policy=None,
|
||||
**common_metadata
|
||||
)
|
||||
|
||||
|
|
@ -165,8 +168,6 @@ def register_nutrition_part_a():
|
|||
),
|
||||
layer_2b_reuse_possible=None,
|
||||
issue_53_alignment="Layer separation established",
|
||||
minimum_data_requirements=None,
|
||||
quality_filter_policy=None,
|
||||
**common_metadata
|
||||
)
|
||||
|
||||
|
|
@ -196,8 +197,6 @@ def register_nutrition_part_a():
|
|||
known_limitations="meist im Gesamtkontext der Makroverteilung relevant",
|
||||
layer_2b_reuse_possible=None,
|
||||
issue_53_alignment="Layer separation established",
|
||||
minimum_data_requirements=None,
|
||||
quality_filter_policy=None,
|
||||
**common_metadata
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ energy_balance_metadata = PlaceholderMetadata(
|
|||
resolver_function="_safe_float('energy_balance_7d', pid, decimals=0)",
|
||||
data_layer_module="backend/data_layer/nutrition_metrics.py",
|
||||
data_layer_function="calculate_energy_balance_7d",
|
||||
source_tables=["nutrition_log", "weight_log"],
|
||||
source_tables=["nutrition_log", "weight_log", "profiles"],
|
||||
|
||||
# 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.",
|
||||
|
|
@ -127,11 +127,14 @@ energy_balance_metadata = PlaceholderMetadata(
|
|||
|
||||
# Quality
|
||||
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=(
|
||||
"Kombiniert Intake-Abdeckung und Robustheit des Verbrauchsmodells. "
|
||||
"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(
|
||||
available=False,
|
||||
|
|
@ -140,11 +143,10 @@ energy_balance_metadata = PlaceholderMetadata(
|
|||
legacy_display="nicht verfügbar"
|
||||
),
|
||||
known_limitations=(
|
||||
"TDEE-MODELL: Vereinfacht als bodyweight_kg × 32.5 (mittlerer Multiplikator). "
|
||||
"NICHT berücksichtigt: Aktivitätslevel, Alter, Geschlecht, Stoffwechselanpassungen. "
|
||||
"TODO in Code: Harris-Benedict oder Mifflin-St Jeor für präzisere TDEE-Schätzung. "
|
||||
"ACHTUNG: Energiebilanz ist modellbasiert, nicht direkt gemessen. "
|
||||
"Einheit ist kcal/Tag (daily average), NICHT 7d-Total."
|
||||
"TDEE: Bei vollständigem Profil (Größe, Geschlecht, DOB, Gewicht) Mifflin–St Jeor BMR × 1.55; "
|
||||
"sonst Fallback kg×32.5. PAL ist nicht nutzerkonfigurierbar. "
|
||||
"Energiebilanz ist modellbasiert, nicht gemessen. "
|
||||
"Einheit kcal/Tag (Tagesmittel), nicht 7-Tage-Summe."
|
||||
),
|
||||
|
||||
# Architecture
|
||||
|
|
|
|||
|
|
@ -60,8 +60,9 @@ nutrition_score_metadata = PlaceholderMetadata(
|
|||
),
|
||||
known_limitations=(
|
||||
"Abhängig von user_focus_area_weights; ohne Ernährungs-Fokus liefert die "
|
||||
"Funktion None. Kalorien-Adhärenz nutzt vereinfachte Heuristik (goal_type-TODO). "
|
||||
"_score_macro_balance nutzt noch zeilenbasierte 28d-Abfrage (langfristig an "
|
||||
"Funktion None. Kalorien-Adhärenz nutzt 7d-Energiebilanz vs. profiles.goal_mode "
|
||||
"(weight_loss / strength+recomposition / sonst maintenance). "
|
||||
"_score_macro_balance nutzt zeilenbasierte 28d-Abfrage (langfristig an "
|
||||
"Tagesaggregation angleichen)."
|
||||
),
|
||||
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
|
||||
|
||||
These guidelines apply to:
|
||||
- All 116 existing placeholders
|
||||
- All 114 existing placeholders (canonical: `PLACEHOLDER_MAP`)
|
||||
- All new placeholders
|
||||
- All modifications to existing placeholders
|
||||
- All placeholder deprecations
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ curl -s -H "X-Auth-Token: $TOKEN" \
|
|||
**Expected response:**
|
||||
```json
|
||||
{
|
||||
"total_placeholders": 116,
|
||||
"total_placeholders": 114,
|
||||
"available": 98,
|
||||
"missing": 18,
|
||||
"by_type": {
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@
|
|||
|
||||
## 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:**
|
||||
- ✅ Complete metadata schema (normative compliant)
|
||||
- ✅ Automatic metadata extraction
|
||||
- ✅ Manual curation for 116 placeholders
|
||||
- ✅ Manual curation for 114 placeholders
|
||||
- ✅ Extended export API (non-breaking)
|
||||
- ✅ Catalog generator (4 documentation files)
|
||||
- ✅ Validation & testing framework
|
||||
|
|
@ -75,7 +75,7 @@ This document summarizes the complete implementation of the normative placeholde
|
|||
|
||||
### 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
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ PlaceholderMetadata(
|
|||
|
||||
**Key Features:**
|
||||
- Hand-curated for accuracy
|
||||
- Complete for all 116 placeholders
|
||||
- Complete for all 114 placeholders
|
||||
- Serves as authoritative source
|
||||
- Normative compliant
|
||||
|
||||
|
|
@ -285,7 +285,7 @@ pytest backend/tests/test_placeholder_metadata.py -v
|
|||
v
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Complete Registry │
|
||||
│ (116 placeholders with full metadata) │
|
||||
│ (114 placeholders with full metadata) │
|
||||
└──────────┬──────────────────────────────────────────────────┘
|
||||
│
|
||||
├──> Generation Scripts (generate_*.py)
|
||||
|
|
@ -309,7 +309,7 @@ pytest backend/tests/test_placeholder_metadata.py -v
|
|||
### 3.1 Metadata Extraction Flow
|
||||
|
||||
```
|
||||
1. PLACEHOLDER_MAP (116 entries)
|
||||
1. PLACEHOLDER_MAP (114 entries)
|
||||
└─> extract_resolver_name()
|
||||
└─> analyze_data_layer_usage()
|
||||
└─> infer_type/time_window/output_type()
|
||||
|
|
@ -468,7 +468,7 @@ curl -H "X-Auth-Token: <token>" \
|
|||
|
||||
# Output:
|
||||
{
|
||||
"total_placeholders": 116,
|
||||
"total_placeholders": 114,
|
||||
"available": 98,
|
||||
"missing": 18,
|
||||
"by_type": {
|
||||
|
|
@ -599,7 +599,7 @@ The system is designed for extensibility:
|
|||
## 8. Compliance Checklist
|
||||
|
||||
✅ **Normative Standard Compliance:**
|
||||
- All 116 placeholders inventoried
|
||||
- All 114 placeholders inventoried
|
||||
- Complete metadata schema implemented
|
||||
- Validation framework in place
|
||||
- Non-breaking export API
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user