Compare commits
No commits in common. "5bbea036c279a60b3a9cf4af59a92c81ca7c80fa" and "c762495b6f7679f2eb91e023b0c42a83da282471" have entirely different histories.
5bbea036c2
...
c762495b6f
|
|
@ -27,14 +27,6 @@
|
|||
- Production Deploy nur nach expliziter Freigabe
|
||||
- Migration 001-999 Pattern einhalten
|
||||
|
||||
**Placeholder/Metrics (VERBINDLICH ab 2026-04-02):**
|
||||
- ✅ **ALLE** neuen Placeholder/Metrics MÜSSEN über Registry Framework registriert werden
|
||||
- ✅ Registrierung in `backend/placeholder_registrations/`
|
||||
- ✅ Evidence-basiertes Tagging für alle Metadatenfelder (CODE_DERIVED, DRAFT_DERIVED, etc.)
|
||||
- ✅ Single Source of Truth für Prompt-Injektion, GUI, Export, Validierung
|
||||
- 📚 **Verbindliche Dokumentation:** `.claude/docs/technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md`
|
||||
- ❌ **VERBOTEN:** Hardcoded Metadaten, duplizierte Definitionen, Placeholder ohne Registry
|
||||
|
||||
## Projekt-Übersicht
|
||||
**Mitai Jinkendo** (身体 Jinkendo) – selbst-gehostete PWA für Körper-Tracking mit KI-Auswertung.
|
||||
Teil der **Jinkendo**-App-Familie (人拳道). Domains: jinkendo.de / .com / .life
|
||||
|
|
|
|||
|
|
@ -575,23 +575,22 @@ def calculate_protein_g_per_kg(profile_id: str) -> Optional[float]:
|
|||
|
||||
weight = float(weight_row['weight'])
|
||||
|
||||
# Get protein intake aggregated by day (SUM per day)
|
||||
# Get protein intake
|
||||
cur.execute("""
|
||||
SELECT date, SUM(protein_g) as daily_protein
|
||||
SELECT protein_g
|
||||
FROM nutrition_log
|
||||
WHERE profile_id = %s
|
||||
AND date >= CURRENT_DATE - INTERVAL '7 days'
|
||||
AND protein_g IS NOT NULL
|
||||
GROUP BY date
|
||||
ORDER BY date DESC
|
||||
""", (profile_id,))
|
||||
|
||||
daily_protein = [float(row['daily_protein']) for row in cur.fetchall()]
|
||||
protein_values = [row['protein_g'] for row in cur.fetchall()]
|
||||
|
||||
if len(daily_protein) < 4: # At least 4 days with data
|
||||
if len(protein_values) < 4:
|
||||
return None
|
||||
|
||||
avg_protein = sum(daily_protein) / len(daily_protein)
|
||||
avg_protein = float(sum(protein_values) / len(protein_values))
|
||||
protein_per_kg = avg_protein / weight
|
||||
|
||||
return round(protein_per_kg, 2)
|
||||
|
|
@ -620,33 +619,28 @@ def calculate_protein_days_in_target(profile_id: str, target_low: float = 1.6, t
|
|||
|
||||
weight = float(weight_row['weight'])
|
||||
|
||||
# Calculate protein target range (absolute values)
|
||||
target_low_g = target_low * weight
|
||||
target_high_g = target_high * weight
|
||||
|
||||
# Get protein intake aggregated by day (SUM per day)
|
||||
# Get protein intake last 7 days
|
||||
cur.execute("""
|
||||
SELECT date, SUM(protein_g) as daily_protein
|
||||
SELECT protein_g, date
|
||||
FROM nutrition_log
|
||||
WHERE profile_id = %s
|
||||
AND date >= CURRENT_DATE - INTERVAL '7 days'
|
||||
AND protein_g IS NOT NULL
|
||||
GROUP BY date
|
||||
ORDER BY date DESC
|
||||
""", (profile_id,))
|
||||
|
||||
daily_data = cur.fetchall()
|
||||
protein_data = cur.fetchall()
|
||||
|
||||
if len(daily_data) < 4: # At least 4 days with data
|
||||
if len(protein_data) < 4:
|
||||
return None
|
||||
|
||||
# Count days in target range
|
||||
days_in_target = 0
|
||||
total_days = len(daily_data)
|
||||
total_days = len(protein_data)
|
||||
|
||||
for row in daily_data:
|
||||
daily_protein = float(row['daily_protein'])
|
||||
if target_low_g <= daily_protein <= target_high_g:
|
||||
for row in protein_data:
|
||||
protein_per_kg = float(row['protein_g']) / weight
|
||||
if target_low <= protein_per_kg <= target_high:
|
||||
days_in_target += 1
|
||||
|
||||
return f"{days_in_target}/{total_days}"
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
"""
|
||||
Placeholder Registrations Package
|
||||
|
||||
Auto-imports all placeholder registrations to populate the global registry.
|
||||
"""
|
||||
|
||||
# Import all registration modules to trigger auto-registration
|
||||
from . import nutrition_part_a
|
||||
from . import nutrition_part_b
|
||||
from . import nutrition_part_c
|
||||
from . import body_metrics
|
||||
from . import activity_metrics
|
||||
|
||||
__all__ = ['nutrition_part_a', 'nutrition_part_b', 'nutrition_part_c', 'body_metrics', 'activity_metrics']
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,216 +0,0 @@
|
|||
"""
|
||||
Nutrition Part A Placeholder Registrations
|
||||
|
||||
Registers the 4 basis nutrition metrics in the central placeholder registry:
|
||||
- kcal_avg
|
||||
- protein_avg
|
||||
- carb_avg
|
||||
- fat_avg
|
||||
|
||||
Evidence-based metadata with clear tagging of source.
|
||||
"""
|
||||
|
||||
from placeholder_registry import (
|
||||
PlaceholderMetadata,
|
||||
MissingValuePolicy,
|
||||
EvidenceType,
|
||||
OutputType,
|
||||
PlaceholderType,
|
||||
register_placeholder
|
||||
)
|
||||
|
||||
|
||||
def register_nutrition_part_a():
|
||||
"""
|
||||
Register Part A nutrition placeholders.
|
||||
|
||||
Metadata sources:
|
||||
- code-derived: extracted from actual code
|
||||
- draft-derived: from canonical requirements draft
|
||||
- mixed: combination of code and draft
|
||||
- unresolved: not explicitly documented
|
||||
- to_verify: claimed but not verified
|
||||
"""
|
||||
|
||||
# Common metadata for all 4 placeholders
|
||||
common_metadata = {
|
||||
"category": "Ernährung",
|
||||
"resolver_module": "backend/placeholder_resolver.py",
|
||||
"resolver_function": "get_nutrition_avg",
|
||||
"data_layer_module": "backend/data_layer/nutrition_metrics.py",
|
||||
"data_layer_function": "get_nutrition_average_data",
|
||||
"source_tables": ["nutrition_log"],
|
||||
"time_window": "30d",
|
||||
"output_type": OutputType.NUMERIC,
|
||||
"placeholder_type": PlaceholderType.INTERPRETED,
|
||||
"confidence_logic": "datenpunktbasierte Coverage-Logik (calculate_confidence)",
|
||||
"missing_value_policy": MissingValuePolicy(
|
||||
available=False,
|
||||
value_raw=None,
|
||||
missing_reason="insufficient_data",
|
||||
legacy_display="nicht genug Daten"
|
||||
),
|
||||
"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",
|
||||
}
|
||||
|
||||
# Common evidence for shared fields
|
||||
common_evidence = {
|
||||
"category": EvidenceType.CODE_DERIVED, # from placeholder_resolver.py:1380
|
||||
"resolver_module": EvidenceType.CODE_DERIVED,
|
||||
"resolver_function": EvidenceType.CODE_DERIVED,
|
||||
"data_layer_module": EvidenceType.CODE_DERIVED, # from import statement
|
||||
"data_layer_function": EvidenceType.CODE_DERIVED, # from resolver code
|
||||
"source_tables": EvidenceType.CODE_DERIVED, # from SQL query
|
||||
"time_window": EvidenceType.CODE_DERIVED, # from PLACEHOLDER_MAP lambda
|
||||
"output_type": EvidenceType.CODE_DERIVED, # from resolver return type
|
||||
"placeholder_type": EvidenceType.MIXED, # draft classification + code shows aggregation
|
||||
"confidence_logic": EvidenceType.CODE_DERIVED, # from data layer
|
||||
"missing_value_policy": EvidenceType.CODE_DERIVED, # from resolver code
|
||||
"layer_1_decision": EvidenceType.CODE_DERIVED,
|
||||
"layer_2a_decision": EvidenceType.CODE_DERIVED,
|
||||
"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
|
||||
}
|
||||
|
||||
# ── kcal_avg ──────────────────────────────────────────────────────────────
|
||||
|
||||
kcal_metadata = PlaceholderMetadata(
|
||||
key="kcal_avg",
|
||||
description="Durchschn. Kalorien (30d)",
|
||||
semantic_contract=(
|
||||
"Liefert den Durchschnitt der dokumentierten täglichen Kalorienaufnahme "
|
||||
"über das definierte Auswertungsfenster. Der Wert ist als Intake-Mittelwert "
|
||||
"zu interpretieren, nicht als Energiebedarf oder Energiebilanz."
|
||||
),
|
||||
business_meaning="Kernwert für Ernährungsstatus, Defizit-/Überschussbewertung und Zielabgleich",
|
||||
unit="kcal/day",
|
||||
format_hint="Ganzzahl",
|
||||
example_output="2140",
|
||||
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
|
||||
)
|
||||
|
||||
kcal_metadata.evidence.update(common_evidence)
|
||||
kcal_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
|
||||
kcal_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
kcal_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED) # from resolver: no " g" suffix
|
||||
kcal_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED) # int(value)
|
||||
kcal_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED) # runtime testable
|
||||
kcal_metadata.set_evidence("known_limitations", EvidenceType.DRAFT_DERIVED)
|
||||
|
||||
register_placeholder(kcal_metadata)
|
||||
|
||||
# ── protein_avg ───────────────────────────────────────────────────────────
|
||||
|
||||
protein_metadata = PlaceholderMetadata(
|
||||
key="protein_avg",
|
||||
description="Durchschn. Protein in g (30d)",
|
||||
semantic_contract=(
|
||||
"Liefert den Durchschnitt der dokumentierten täglichen Proteinzufuhr "
|
||||
"über das definierte Auswertungsfenster."
|
||||
),
|
||||
business_meaning=(
|
||||
"Zentraler Placeholder für Muskelerhalt, Muskelaufbau, Recomposition "
|
||||
"und Absicherung im Defizit"
|
||||
),
|
||||
unit="g/day",
|
||||
format_hint="Ganzzahl in g/day",
|
||||
example_output="156",
|
||||
known_limitations=(
|
||||
"absoluter Wert allein reicht nicht immer; sollte oft relativ zum "
|
||||
"Körpergewicht interpretiert werden"
|
||||
),
|
||||
layer_2b_reuse_possible=None,
|
||||
issue_53_alignment="Layer separation established",
|
||||
minimum_data_requirements=None,
|
||||
quality_filter_policy=None,
|
||||
**common_metadata
|
||||
)
|
||||
|
||||
protein_metadata.evidence.update(common_evidence)
|
||||
protein_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
|
||||
protein_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
protein_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED) # from resolver: " g" suffix
|
||||
protein_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
|
||||
protein_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
|
||||
protein_metadata.set_evidence("known_limitations", EvidenceType.DRAFT_DERIVED)
|
||||
|
||||
register_placeholder(protein_metadata)
|
||||
|
||||
# ── carb_avg ──────────────────────────────────────────────────────────────
|
||||
|
||||
carb_metadata = PlaceholderMetadata(
|
||||
key="carb_avg",
|
||||
description="Durchschn. Kohlenhydrate in g (30d)",
|
||||
semantic_contract=(
|
||||
"Liefert den Durchschnitt der dokumentierten täglichen Kohlenhydratzufuhr "
|
||||
"über das definierte Auswertungsfenster."
|
||||
),
|
||||
business_meaning="Relevanter Makroindikator für Leistungs-, Energie- und Belastungskontext",
|
||||
unit="g/day",
|
||||
format_hint="Ganzzahl in g/day",
|
||||
example_output="210",
|
||||
known_limitations=(
|
||||
"allein selten aussagekräftig; meist im Kontext von Ziel, Energie und "
|
||||
"Belastung relevant"
|
||||
),
|
||||
layer_2b_reuse_possible=None,
|
||||
issue_53_alignment="Layer separation established",
|
||||
minimum_data_requirements=None,
|
||||
quality_filter_policy=None,
|
||||
**common_metadata
|
||||
)
|
||||
|
||||
carb_metadata.evidence.update(common_evidence)
|
||||
carb_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
|
||||
carb_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
carb_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
|
||||
carb_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
|
||||
carb_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
|
||||
carb_metadata.set_evidence("known_limitations", EvidenceType.DRAFT_DERIVED)
|
||||
|
||||
register_placeholder(carb_metadata)
|
||||
|
||||
# ── fat_avg ───────────────────────────────────────────────────────────────
|
||||
|
||||
fat_metadata = PlaceholderMetadata(
|
||||
key="fat_avg",
|
||||
description="Durchschn. Fett in g (30d)",
|
||||
semantic_contract=(
|
||||
"Liefert den Durchschnitt der dokumentierten täglichen Fettzufuhr "
|
||||
"über das definierte Auswertungsfenster."
|
||||
),
|
||||
business_meaning="Relevanter Makroindikator für Ernährungsstruktur und Zielpassung",
|
||||
unit="g/day",
|
||||
format_hint="Ganzzahl in g/day",
|
||||
example_output="72",
|
||||
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
|
||||
)
|
||||
|
||||
fat_metadata.evidence.update(common_evidence)
|
||||
fat_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
|
||||
fat_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
fat_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
|
||||
fat_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
|
||||
fat_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
|
||||
fat_metadata.set_evidence("known_limitations", EvidenceType.DRAFT_DERIVED)
|
||||
|
||||
register_placeholder(fat_metadata)
|
||||
|
||||
|
||||
# Auto-register on import
|
||||
register_nutrition_part_a()
|
||||
|
|
@ -1,429 +0,0 @@
|
|||
"""
|
||||
Nutrition Part B Placeholder Registrations
|
||||
|
||||
Registers the 5 protein-specific metrics in the central placeholder registry:
|
||||
- protein_ziel_low
|
||||
- protein_ziel_high
|
||||
- protein_g_per_kg
|
||||
- protein_days_in_target
|
||||
- protein_adequacy_28d
|
||||
|
||||
Evidence-based metadata with clear tagging of source.
|
||||
Includes documentation of open points (weight basis inconsistency, score logic).
|
||||
"""
|
||||
|
||||
from placeholder_registry import (
|
||||
PlaceholderMetadata,
|
||||
MissingValuePolicy,
|
||||
EvidenceType,
|
||||
OutputType,
|
||||
PlaceholderType,
|
||||
register_placeholder
|
||||
)
|
||||
|
||||
|
||||
def register_nutrition_part_b():
|
||||
"""
|
||||
Register Part B protein placeholders.
|
||||
|
||||
Metadata sources:
|
||||
- code-derived: extracted from actual code
|
||||
- draft-derived: from canonical requirements draft
|
||||
- mixed: combination of code and draft
|
||||
- unresolved: not explicitly documented
|
||||
- to_verify: claimed but not verified
|
||||
"""
|
||||
|
||||
# ── protein_ziel_low ──────────────────────────────────────────────────────
|
||||
|
||||
low_metadata = PlaceholderMetadata(
|
||||
key="protein_ziel_low",
|
||||
category="Ernährung",
|
||||
description="Unteres Proteinziel (1.6 g/kg)",
|
||||
|
||||
# Technical
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="get_protein_ziel_low",
|
||||
data_layer_module="backend/data_layer/nutrition_metrics.py",
|
||||
data_layer_function="get_protein_targets_data",
|
||||
source_tables=["weight_log"],
|
||||
|
||||
# Semantic
|
||||
semantic_contract=(
|
||||
"Liefert die untere Proteinziel-Grenze basierend auf aktuellem "
|
||||
"Körpergewicht (1.6 g/kg). Ziel für Muskelerhalt in Maintenance-Phasen."
|
||||
),
|
||||
business_meaning="Maintenance-Ziel für Muskelerhalt",
|
||||
unit="g/day",
|
||||
time_window="snapshot",
|
||||
output_type=OutputType.NUMERIC,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="Ganzzahl",
|
||||
example_output="128",
|
||||
|
||||
# Quality
|
||||
confidence_logic="Binary: weight vorhanden/nicht vorhanden",
|
||||
missing_value_policy=MissingValuePolicy(
|
||||
available=False,
|
||||
value_raw=None,
|
||||
missing_reason="no_data",
|
||||
legacy_display="nicht verfügbar"
|
||||
),
|
||||
known_limitations=(
|
||||
"Basiert auf single-point weight (latest entry); "
|
||||
"anfällig für Gewichts-Outlier (z.B. nach Refeed-Tag)"
|
||||
),
|
||||
|
||||
# Architecture
|
||||
layer_1_decision="Data Layer (nutrition_metrics.get_protein_targets_data)",
|
||||
layer_2a_decision="Placeholder Resolver (formatting only)",
|
||||
layer_2b_reuse_possible=None, # to_verify
|
||||
architecture_alignment="Phase 0c Multi-Layer Architecture conform",
|
||||
issue_53_alignment="Layer separation established"
|
||||
)
|
||||
|
||||
# Evidence
|
||||
low_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("description", EvidenceType.MIXED)
|
||||
low_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
|
||||
low_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
low_metadata.set_evidence("unit", EvidenceType.MIXED) # implicit in code, confirmed by draft
|
||||
low_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
|
||||
low_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("confidence_logic", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("known_limitations", EvidenceType.MIXED)
|
||||
low_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
|
||||
low_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
|
||||
low_metadata.set_evidence("issue_53_alignment", EvidenceType.MIXED)
|
||||
|
||||
register_placeholder(low_metadata)
|
||||
|
||||
# ── protein_ziel_high ─────────────────────────────────────────────────────
|
||||
|
||||
high_metadata = PlaceholderMetadata(
|
||||
key="protein_ziel_high",
|
||||
category="Ernährung",
|
||||
description="Oberes Proteinziel (2.2 g/kg)",
|
||||
|
||||
# Technical (same as protein_ziel_low)
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="get_protein_ziel_high",
|
||||
data_layer_module="backend/data_layer/nutrition_metrics.py",
|
||||
data_layer_function="get_protein_targets_data",
|
||||
source_tables=["weight_log"],
|
||||
|
||||
# Semantic
|
||||
semantic_contract=(
|
||||
"Liefert die obere Proteinziel-Grenze basierend auf aktuellem "
|
||||
"Körpergewicht (2.2 g/kg). Ziel für Muskelaufbau in hypertrophen Phasen."
|
||||
),
|
||||
business_meaning="Muskelaufbau-Ziel für hypertrophe Phasen",
|
||||
unit="g/day",
|
||||
time_window="snapshot",
|
||||
output_type=OutputType.NUMERIC,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="Ganzzahl",
|
||||
example_output="176",
|
||||
|
||||
# Quality
|
||||
confidence_logic="Binary: weight vorhanden/nicht vorhanden",
|
||||
missing_value_policy=MissingValuePolicy(
|
||||
available=False,
|
||||
value_raw=None,
|
||||
missing_reason="no_data",
|
||||
legacy_display="nicht verfügbar"
|
||||
),
|
||||
known_limitations=(
|
||||
"Basiert auf single-point weight (latest entry); "
|
||||
"anfällig für Gewichts-Outlier"
|
||||
),
|
||||
|
||||
# Architecture
|
||||
layer_1_decision="Data Layer (nutrition_metrics.get_protein_targets_data)",
|
||||
layer_2a_decision="Placeholder Resolver (formatting only)",
|
||||
layer_2b_reuse_possible=None,
|
||||
architecture_alignment="Phase 0c Multi-Layer Architecture conform",
|
||||
issue_53_alignment="Layer separation established"
|
||||
)
|
||||
|
||||
# Evidence (identical to protein_ziel_low)
|
||||
high_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("description", EvidenceType.MIXED)
|
||||
high_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
|
||||
high_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
high_metadata.set_evidence("unit", EvidenceType.MIXED)
|
||||
high_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
|
||||
high_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("confidence_logic", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("known_limitations", EvidenceType.MIXED)
|
||||
high_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
|
||||
high_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
|
||||
high_metadata.set_evidence("issue_53_alignment", EvidenceType.MIXED)
|
||||
|
||||
register_placeholder(high_metadata)
|
||||
|
||||
# ── protein_g_per_kg ──────────────────────────────────────────────────────
|
||||
|
||||
gpk_metadata = PlaceholderMetadata(
|
||||
key="protein_g_per_kg",
|
||||
category="Ernährung",
|
||||
description="Protein g/kg Körpergewicht",
|
||||
|
||||
# Technical
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_float",
|
||||
data_layer_module="backend/data_layer/nutrition_metrics.py",
|
||||
data_layer_function="calculate_protein_g_per_kg",
|
||||
source_tables=["nutrition_log", "weight_log"],
|
||||
|
||||
# Semantic
|
||||
semantic_contract=(
|
||||
"Liefert die durchschnittliche Proteinzufuhr relativ zum Körpergewicht. "
|
||||
"Berechnung: protein_7d_avg / latest_weight. "
|
||||
"WICHTIG: Protein ist geglättet (7d), Gewicht ist single-point."
|
||||
),
|
||||
business_meaning="Zentraler Zielindikator für Muskelerhalt und Aufbau",
|
||||
unit="g/kg/day",
|
||||
time_window="7d", # dominante Komponente (protein); weight ist snapshot, aber secondary
|
||||
output_type=OutputType.NUMERIC,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="Dezimalzahl (1-2 Stellen)",
|
||||
example_output="1.95",
|
||||
|
||||
# Quality
|
||||
confidence_logic="Minimum von protein_confidence und weight_availability",
|
||||
missing_value_policy=MissingValuePolicy(
|
||||
available=False,
|
||||
value_raw=None,
|
||||
missing_reason="insufficient_data",
|
||||
legacy_display="nicht verfügbar"
|
||||
),
|
||||
known_limitations=(
|
||||
"KRITISCHE INKONSISTENZ: Protein ist geglättet (7d average), "
|
||||
"Gewicht ist single-point (latest). Anfällig für Gewichts-Outlier. "
|
||||
"Ein Refeed-Tag kann den Wert stark verfälschen, obwohl Protein-Intake stabil ist."
|
||||
),
|
||||
|
||||
# Architecture
|
||||
layer_1_decision="Data Layer (nutrition_metrics.calculate_protein_g_per_kg)",
|
||||
layer_2a_decision="Placeholder Resolver (_safe_float wrapper)",
|
||||
layer_2b_reuse_possible=None,
|
||||
architecture_alignment="Phase 0c Multi-Layer Architecture conform",
|
||||
issue_53_alignment="Layer separation established"
|
||||
)
|
||||
|
||||
# Evidence
|
||||
gpk_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("description", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("semantic_contract", EvidenceType.MIXED) # code + explicit documentation
|
||||
gpk_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
gpk_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED) # explicitly documented as mixed
|
||||
gpk_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
|
||||
gpk_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("confidence_logic", EvidenceType.UNRESOLVED) # not explicitly documented
|
||||
gpk_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED) # identified from code analysis
|
||||
gpk_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
|
||||
gpk_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
|
||||
gpk_metadata.set_evidence("issue_53_alignment", EvidenceType.MIXED)
|
||||
|
||||
register_placeholder(gpk_metadata)
|
||||
|
||||
# ── protein_days_in_target ────────────────────────────────────────────────
|
||||
|
||||
days_metadata = PlaceholderMetadata(
|
||||
key="protein_days_in_target",
|
||||
category="Ernährung",
|
||||
description="Tage im Protein-Zielbereich (7d)",
|
||||
|
||||
# Technical
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_str",
|
||||
data_layer_module="backend/data_layer/nutrition_metrics.py",
|
||||
data_layer_function="calculate_protein_days_in_target",
|
||||
source_tables=["nutrition_log", "weight_log"],
|
||||
|
||||
# Semantic
|
||||
semantic_contract=(
|
||||
"Liefert Anzahl Tage im Protein-Zielbereich relativ zu Gesamttagen. "
|
||||
"Target-Range: 1.6-2.2 g/kg (hardcoded). "
|
||||
"Format: 'X/Y' (z.B. '5/7' = 5 von 7 Tagen im Ziel)."
|
||||
),
|
||||
business_meaning="Adhärenz-Indikator für Proteinversorgung",
|
||||
unit="days_ratio",
|
||||
time_window="7d",
|
||||
output_type=OutputType.STRING,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="String format 'X/Y' (e.g. '5/7')",
|
||||
example_output="5/7",
|
||||
|
||||
# Quality
|
||||
confidence_logic="Abhängig von nutrition_log Datenabdeckung",
|
||||
missing_value_policy=MissingValuePolicy(
|
||||
available=False,
|
||||
value_raw=None,
|
||||
missing_reason="no_data",
|
||||
legacy_display="nicht verfügbar"
|
||||
),
|
||||
known_limitations=(
|
||||
"Target-Range 1.6-2.2 g/kg fest kodiert (default parameters), "
|
||||
"nicht konfigurierbar. Keine Integration mit Goal-System."
|
||||
),
|
||||
|
||||
# Architecture
|
||||
layer_1_decision="Data Layer (nutrition_metrics.calculate_protein_days_in_target)",
|
||||
layer_2a_decision="Placeholder Resolver (_safe_str wrapper)",
|
||||
layer_2b_reuse_possible=None,
|
||||
architecture_alignment="Phase 0c Multi-Layer Architecture conform",
|
||||
issue_53_alignment="Layer separation established"
|
||||
)
|
||||
|
||||
# Evidence
|
||||
days_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("description", EvidenceType.MIXED)
|
||||
days_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("semantic_contract", EvidenceType.MIXED)
|
||||
days_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
days_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
|
||||
days_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("confidence_logic", EvidenceType.UNRESOLVED)
|
||||
days_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
|
||||
days_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
|
||||
days_metadata.set_evidence("issue_53_alignment", EvidenceType.MIXED)
|
||||
|
||||
register_placeholder(days_metadata)
|
||||
|
||||
# ── protein_adequacy_28d ──────────────────────────────────────────────────
|
||||
|
||||
adequacy_metadata = PlaceholderMetadata(
|
||||
key="protein_adequacy_28d",
|
||||
category="Ernährung",
|
||||
description="Protein Adequacy Score (0-100)",
|
||||
|
||||
# Technical
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_int",
|
||||
data_layer_module="backend/data_layer/nutrition_metrics.py",
|
||||
data_layer_function="calculate_protein_adequacy_28d",
|
||||
source_tables=["nutrition_log", "weight_log"],
|
||||
|
||||
# Semantic
|
||||
semantic_contract=(
|
||||
"Liefert standardisierten Angemessenheitswert der Proteinversorgung "
|
||||
"über 28 Tage relativ zu definierten Protein-Zielbereichen (1.6-2.2 g/kg). "
|
||||
"Score-Logik: "
|
||||
"- Days in target [1.6-2.2]: 100 points; "
|
||||
"- Days slightly below [1.4-1.6]: partial points (linear interpolation); "
|
||||
"- Days far below (<1.4): 0 points; "
|
||||
"- Days above (>2.2): 100 points (no penalty). "
|
||||
"Final score: average over 28d."
|
||||
),
|
||||
business_meaning="Verdichteter Zielerreichungsindikator für Proteinversorgung",
|
||||
unit="score (0-100)",
|
||||
time_window="28d",
|
||||
output_type=OutputType.NUMERIC,
|
||||
placeholder_type=PlaceholderType.SCORE,
|
||||
format_hint="Integer 0-100, höher = besser",
|
||||
example_output="82",
|
||||
|
||||
# Quality
|
||||
confidence_logic="Abgeleitet aus Datenabdeckung über 28d",
|
||||
missing_value_policy=MissingValuePolicy(
|
||||
available=False,
|
||||
value_raw=None,
|
||||
missing_reason="insufficient_data",
|
||||
legacy_display="nicht verfügbar"
|
||||
),
|
||||
known_limitations=(
|
||||
"Score muss transparent erklärt werden; ohne Skalen-Dokumentation "
|
||||
"interpretationsanfällig. Scoring-Schwellen [1.4, 1.6, 2.2] nicht explizit "
|
||||
"im Code dokumentiert, nur in Logik implementiert."
|
||||
),
|
||||
|
||||
# Architecture
|
||||
layer_1_decision="Data Layer (nutrition_metrics.calculate_protein_adequacy_28d)",
|
||||
layer_2a_decision="Placeholder Resolver (_safe_int wrapper)",
|
||||
layer_2b_reuse_possible=None,
|
||||
architecture_alignment="Phase 0c Multi-Layer Architecture conform",
|
||||
issue_53_alignment="Layer separation established"
|
||||
)
|
||||
|
||||
# Evidence
|
||||
adequacy_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("description", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("semantic_contract", EvidenceType.MIXED) # code + explicit documentation
|
||||
adequacy_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
adequacy_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
|
||||
adequacy_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("confidence_logic", EvidenceType.UNRESOLVED)
|
||||
adequacy_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("known_limitations", EvidenceType.MIXED) # code analysis + draft
|
||||
adequacy_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
|
||||
adequacy_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
|
||||
adequacy_metadata.set_evidence("issue_53_alignment", EvidenceType.MIXED)
|
||||
|
||||
register_placeholder(adequacy_metadata)
|
||||
|
||||
|
||||
# Auto-register on import
|
||||
register_nutrition_part_b()
|
||||
|
|
@ -1,446 +0,0 @@
|
|||
"""
|
||||
Placeholder Registrations - Nutrition Part C
|
||||
|
||||
Registers 5 nutrition-related placeholders with complete metadata:
|
||||
- macro_consistency_score
|
||||
- energy_balance_7d
|
||||
- energy_deficit_surplus
|
||||
- intake_volatility
|
||||
- nutrition_days
|
||||
|
||||
All placeholders follow Phase 0c Multi-Layer Architecture.
|
||||
"""
|
||||
|
||||
from placeholder_registry import (
|
||||
PlaceholderMetadata,
|
||||
MissingValuePolicy,
|
||||
EvidenceType,
|
||||
OutputType,
|
||||
PlaceholderType,
|
||||
register_placeholder
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 1. macro_consistency_score
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
macro_consistency_metadata = PlaceholderMetadata(
|
||||
key="macro_consistency_score",
|
||||
category="Ernährung",
|
||||
description="Makro-Konsistenz Score (0-100)",
|
||||
|
||||
# Technical
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_int('macro_consistency_score', pid)",
|
||||
data_layer_module="backend/data_layer/nutrition_metrics.py",
|
||||
data_layer_function="calculate_macro_consistency_score",
|
||||
source_tables=["nutrition_log"],
|
||||
|
||||
# Semantic
|
||||
semantic_contract="Liefert einen standardisierten Score (0-100), der die Stabilität bzw. Varianz der Makronährstoffzufuhr über 28 Tage bewertet. Niedriger CV (Coefficient of Variation) = höherer Score.",
|
||||
business_meaning="Verdichteter Konsistenzindikator für Ernährungsumsetzung. Score basiert auf durchschnittlicher Variabilität der Makros (kcal, protein, fat, carbs).",
|
||||
unit="score (0-100)",
|
||||
time_window="28d",
|
||||
output_type=OutputType.NUMERIC,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="Ganzzahl 0-100",
|
||||
example_output="74",
|
||||
|
||||
# Quality
|
||||
minimum_data_requirements="Mindestens 18 Einträge in 28 Tagen (60% coverage) für verlässliche Varianzberechnung.",
|
||||
quality_filter_policy="Unvollständige oder stark lückenhafte Tage schwächen Aussagekraft. NULL-Werte bei einzelnen Makros werden für CV-Berechnung übersprungen.",
|
||||
confidence_logic="Aus Datenabdeckung ableiten: 18+ Einträge = ausreichend für CV-Berechnung. Score selbst ist bereits ein Konsistenzmaß.",
|
||||
missing_value_policy=MissingValuePolicy(
|
||||
available=False,
|
||||
value_raw=None,
|
||||
missing_reason="insufficient_data",
|
||||
legacy_display="nicht verfügbar"
|
||||
),
|
||||
known_limitations=(
|
||||
"Score-Formel: CV (Coefficient of Variation) = std_dev / mean für jeden Makro. "
|
||||
"Durchschnittlicher CV über alle 4 Makros. "
|
||||
"Thresholds: CV<0.2=100, CV<0.3=85, CV<0.4=70, CV<0.5=55, CV>=0.5=max(30,100-CV*100). "
|
||||
"WICHTIG: Niedrige Konsistenz ist nicht automatisch schlecht (bewusste Zyklen, Refeed-Tage). "
|
||||
"Interpretation hängt vom Zielkontext ab."
|
||||
),
|
||||
|
||||
# Architecture
|
||||
layer_1_decision="Data Layer (nutrition_metrics.calculate_macro_consistency_score)",
|
||||
layer_2a_decision="Placeholder Resolver (_safe_int, keine zusätzliche Logik)",
|
||||
layer_2b_reuse_possible="Ja - Chart für Konsistenz-Verlauf oder Score-Trend möglich",
|
||||
architecture_alignment="Phase 0c conform - Data Layer liefert Score, Resolver formatiert nur",
|
||||
issue_53_alignment="Konform - Data Layer berechnet, Resolver wraps",
|
||||
|
||||
# Evidence (not exported, internal tracking)
|
||||
evidence={}
|
||||
)
|
||||
|
||||
# Evidence tagging
|
||||
macro_consistency_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
|
||||
macro_consistency_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
|
||||
macro_consistency_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
|
||||
macro_consistency_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
|
||||
macro_consistency_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
|
||||
macro_consistency_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
|
||||
macro_consistency_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
|
||||
macro_consistency_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
|
||||
macro_consistency_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
|
||||
macro_consistency_metadata.set_evidence("minimum_data_requirements", EvidenceType.CODE_DERIVED)
|
||||
macro_consistency_metadata.set_evidence("quality_filter_policy", EvidenceType.CODE_DERIVED)
|
||||
macro_consistency_metadata.set_evidence("confidence_logic", EvidenceType.CODE_DERIVED)
|
||||
macro_consistency_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
|
||||
macro_consistency_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
macro_consistency_metadata.set_evidence("known_limitations", EvidenceType.MIXED) # Formula from code, interpretation from draft
|
||||
macro_consistency_metadata.set_evidence("layer_1_decision", EvidenceType.TO_VERIFY)
|
||||
macro_consistency_metadata.set_evidence("layer_2a_decision", EvidenceType.TO_VERIFY)
|
||||
macro_consistency_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
|
||||
|
||||
register_placeholder(macro_consistency_metadata)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 2. energy_balance_7d
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
energy_balance_metadata = PlaceholderMetadata(
|
||||
key="energy_balance_7d",
|
||||
category="Ernährung",
|
||||
description="Energiebilanz 7-Tage (kcal/Tag Durchschnitt)",
|
||||
|
||||
# Technical
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
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"],
|
||||
|
||||
# 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.",
|
||||
business_meaning="Kernindikator für Defizit-/Überschussrichtung im Kurzfristfenster. Zeigt, ob aktuelle Ernährung auf Gewichtsverlust, Erhaltung oder Aufbau ausgerichtet ist.",
|
||||
unit="kcal/day (Durchschnitt)",
|
||||
time_window="7d",
|
||||
output_type=OutputType.NUMERIC,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="Ganzzahl, gerundet auf 0 Dezimalstellen",
|
||||
example_output="-380",
|
||||
|
||||
# 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).",
|
||||
confidence_logic=(
|
||||
"Kombiniert Intake-Abdeckung und Robustheit des Verbrauchsmodells. "
|
||||
"Niedrigere Confidence bei <7 Tagen Daten oder fehlendem Gewicht. "
|
||||
"TDEE-Modell ist vereinfacht → inherent uncertainty."
|
||||
),
|
||||
missing_value_policy=MissingValuePolicy(
|
||||
available=False,
|
||||
value_raw=None,
|
||||
missing_reason="insufficient_data",
|
||||
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."
|
||||
),
|
||||
|
||||
# Architecture
|
||||
layer_1_decision="Data Layer (nutrition_metrics.calculate_energy_balance_7d) - berechnet Balance aus Intake und TDEE",
|
||||
layer_2a_decision="Placeholder Resolver (_safe_float, rundet auf 0 Dezimalstellen)",
|
||||
layer_2b_reuse_possible="Ja - Chart für Energiebilanz-Verlauf oder Defizit-Trend",
|
||||
architecture_alignment="Phase 0c conform - Data Layer berechnet Balance, Resolver formatiert",
|
||||
issue_53_alignment="Konform - Berechnung in Data Layer",
|
||||
|
||||
# Evidence
|
||||
evidence={}
|
||||
)
|
||||
|
||||
# Evidence tagging
|
||||
energy_balance_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
|
||||
energy_balance_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
|
||||
energy_balance_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
|
||||
energy_balance_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
|
||||
energy_balance_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
|
||||
energy_balance_metadata.set_evidence("unit", EvidenceType.MIXED) # Code says kcal/day, canonical was ambiguous
|
||||
energy_balance_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
|
||||
energy_balance_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
|
||||
energy_balance_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
|
||||
energy_balance_metadata.set_evidence("minimum_data_requirements", EvidenceType.CODE_DERIVED)
|
||||
energy_balance_metadata.set_evidence("quality_filter_policy", EvidenceType.CODE_DERIVED)
|
||||
energy_balance_metadata.set_evidence("confidence_logic", EvidenceType.MIXED)
|
||||
energy_balance_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
|
||||
energy_balance_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
energy_balance_metadata.set_evidence("known_limitations", EvidenceType.MIXED) # TDEE formula from code, limitations from both
|
||||
energy_balance_metadata.set_evidence("layer_1_decision", EvidenceType.TO_VERIFY)
|
||||
energy_balance_metadata.set_evidence("layer_2a_decision", EvidenceType.TO_VERIFY)
|
||||
energy_balance_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
|
||||
|
||||
register_placeholder(energy_balance_metadata)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 3. energy_deficit_surplus
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
energy_deficit_surplus_metadata = PlaceholderMetadata(
|
||||
key="energy_deficit_surplus",
|
||||
category="Ernährung",
|
||||
description="Energie Defizit/Überschuss Status",
|
||||
|
||||
# Technical
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_str('energy_deficit_surplus', pid)",
|
||||
data_layer_module="backend/data_layer/nutrition_metrics.py",
|
||||
data_layer_function="calculate_energy_deficit_surplus",
|
||||
source_tables=["nutrition_log", "weight_log"], # Indirect via energy_balance_7d
|
||||
|
||||
# Semantic
|
||||
semantic_contract="Liefert qualitative Einordnung, ob aktuelle Energiezufuhr relativ zum geschätzten Bedarf in einem Defizit ('deficit'), auf Erhaltung ('maintenance') oder im Überschuss ('surplus') liegt.",
|
||||
business_meaning="Leicht interpretierbarer Energie-Statusindikator. Vereinfacht Energiebilanz zu verständlichen Kategorien.",
|
||||
unit="state (string)",
|
||||
time_window="7d",
|
||||
output_type=OutputType.STRING,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="Einer von drei Statuswerten: 'deficit', 'maintenance', 'surplus'",
|
||||
example_output="deficit",
|
||||
|
||||
# Quality
|
||||
minimum_data_requirements="Wie energy_balance_7d: mindestens 4 Tage mit Kalorienerfassung + aktuelles Gewicht.",
|
||||
quality_filter_policy="Wie energy_balance_7d: unvollständige Intake-Daten und vereinfachte TDEE-Schätzung reduzieren Verlässlichkeit.",
|
||||
confidence_logic="Abgeleitet von energy_balance_7d. Confidence der Balance überträgt sich auf Status.",
|
||||
missing_value_policy=MissingValuePolicy(
|
||||
available=False,
|
||||
value_raw=None,
|
||||
missing_reason="insufficient_data",
|
||||
legacy_display="nicht verfügbar"
|
||||
),
|
||||
known_limitations=(
|
||||
"Status-Schwellen: balance < -200 kcal/day = 'deficit', "
|
||||
"balance > +200 kcal/day = 'surplus', "
|
||||
"-200 bis +200 = 'maintenance'. "
|
||||
"WICHTIG: Nur so gut wie zugrunde liegende TDEE-Schätzung (siehe energy_balance_7d). "
|
||||
"Minimale Abweichungen nahe Maintenance-Schwelle können zu Statuswechsel führen. "
|
||||
"200 kcal Schwelle ist willkürlich gewählt - physiologisch könnten auch 100-300 kcal sinnvoll sein."
|
||||
),
|
||||
|
||||
# Architecture
|
||||
layer_1_decision="Data Layer (nutrition_metrics.calculate_energy_deficit_surplus) - mapped Balance zu Status",
|
||||
layer_2a_decision="Placeholder Resolver (_safe_str, keine zusätzliche Logik)",
|
||||
layer_2b_reuse_possible="Ja - Status-Anzeige oder Kategorien-Chart",
|
||||
architecture_alignment="Phase 0c conform - Status-Mapping in Data Layer",
|
||||
issue_53_alignment="Konform - Kategorisierung in Data Layer",
|
||||
|
||||
# Evidence
|
||||
evidence={}
|
||||
)
|
||||
|
||||
# Evidence tagging
|
||||
energy_deficit_surplus_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
|
||||
energy_deficit_surplus_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
|
||||
energy_deficit_surplus_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
|
||||
energy_deficit_surplus_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
|
||||
energy_deficit_surplus_metadata.set_evidence("source_tables", EvidenceType.MIXED) # Indirect via energy_balance_7d
|
||||
energy_deficit_surplus_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
|
||||
energy_deficit_surplus_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
|
||||
energy_deficit_surplus_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
|
||||
energy_deficit_surplus_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
|
||||
energy_deficit_surplus_metadata.set_evidence("minimum_data_requirements", EvidenceType.MIXED)
|
||||
energy_deficit_surplus_metadata.set_evidence("quality_filter_policy", EvidenceType.MIXED)
|
||||
energy_deficit_surplus_metadata.set_evidence("confidence_logic", EvidenceType.MIXED)
|
||||
energy_deficit_surplus_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
|
||||
energy_deficit_surplus_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
energy_deficit_surplus_metadata.set_evidence("known_limitations", EvidenceType.MIXED) # Thresholds from code, interpretation mixed
|
||||
energy_deficit_surplus_metadata.set_evidence("layer_1_decision", EvidenceType.TO_VERIFY)
|
||||
energy_deficit_surplus_metadata.set_evidence("layer_2a_decision", EvidenceType.TO_VERIFY)
|
||||
energy_deficit_surplus_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
|
||||
|
||||
register_placeholder(energy_deficit_surplus_metadata)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 4. intake_volatility
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
intake_volatility_metadata = PlaceholderMetadata(
|
||||
key="intake_volatility",
|
||||
category="Ernährung",
|
||||
description="Intake-Volatilität (Klassifikation)",
|
||||
|
||||
# Technical
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_str('intake_volatility', pid)",
|
||||
data_layer_module="backend/data_layer/nutrition_metrics.py",
|
||||
data_layer_function="calculate_intake_volatility",
|
||||
source_tables=["nutrition_log"],
|
||||
|
||||
# Semantic
|
||||
semantic_contract="Liefert qualitative Klassifikation der Variabilität der täglichen Kalorienaufnahme über 28 Tage. 'stable' = hohe Konstanz, 'moderate' = mittlere Schwankung, 'high' = starke Variabilität.",
|
||||
business_meaning="Konsistenz- und Adhärenzindikator für Ernährungsumsetzung. Zeigt, wie gleichmäßig die Kalorienaufnahme über die Zeit ist.",
|
||||
unit="category (string)",
|
||||
time_window="28d",
|
||||
output_type=OutputType.STRING,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="Einer von drei Werten: 'stable', 'moderate', 'high'",
|
||||
example_output="moderate",
|
||||
|
||||
# Quality
|
||||
minimum_data_requirements="Wie macro_consistency_score: mindestens 18 Einträge in 28 Tagen (60% coverage).",
|
||||
quality_filter_policy="Ausreißer, lückenhafte Tage und unvollständige Logs reduzieren Verlässlichkeit. Abgeleitet von macro_consistency_score.",
|
||||
confidence_logic="Aus Datenabdeckung und Vollständigkeit ableiten. Abhängig von macro_consistency_score Confidence.",
|
||||
missing_value_policy=MissingValuePolicy(
|
||||
available=False,
|
||||
value_raw=None,
|
||||
missing_reason="insufficient_data",
|
||||
legacy_display="nicht verfügbar"
|
||||
),
|
||||
known_limitations=(
|
||||
"Klassifikation basiert auf macro_consistency_score: "
|
||||
"score >= 80: 'stable', score >= 60: 'moderate', score < 60: 'high'. "
|
||||
"WICHTIG: Hohe Volatilität ist nicht automatisch schlecht (bewusste Refeed-/Diet-Break-Tage, unregelmäßige Wochenenden). "
|
||||
"Interpretation hängt von Zielkontext und Trainingslogik ab. "
|
||||
"Vereinfacht komplexes Konsistenzmuster zu drei Kategorien."
|
||||
),
|
||||
|
||||
# Architecture
|
||||
layer_1_decision="Data Layer (nutrition_metrics.calculate_intake_volatility) - mapped macro_consistency_score zu Kategorie",
|
||||
layer_2a_decision="Placeholder Resolver (_safe_str, keine zusätzliche Logik)",
|
||||
layer_2b_reuse_possible="Ja - Kategorie-Anzeige oder Trend-Chart",
|
||||
architecture_alignment="Phase 0c conform - Kategorisierung in Data Layer",
|
||||
issue_53_alignment="Konform - Mapping in Data Layer",
|
||||
|
||||
# Evidence
|
||||
evidence={}
|
||||
)
|
||||
|
||||
# Evidence tagging
|
||||
intake_volatility_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
|
||||
intake_volatility_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
|
||||
intake_volatility_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
|
||||
intake_volatility_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
|
||||
intake_volatility_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
|
||||
intake_volatility_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
|
||||
intake_volatility_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
|
||||
intake_volatility_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
|
||||
intake_volatility_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
|
||||
intake_volatility_metadata.set_evidence("minimum_data_requirements", EvidenceType.MIXED)
|
||||
intake_volatility_metadata.set_evidence("quality_filter_policy", EvidenceType.MIXED)
|
||||
intake_volatility_metadata.set_evidence("confidence_logic", EvidenceType.MIXED)
|
||||
intake_volatility_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
|
||||
intake_volatility_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
intake_volatility_metadata.set_evidence("known_limitations", EvidenceType.MIXED) # Thresholds from code, interpretation mixed
|
||||
intake_volatility_metadata.set_evidence("layer_1_decision", EvidenceType.TO_VERIFY)
|
||||
intake_volatility_metadata.set_evidence("layer_2a_decision", EvidenceType.TO_VERIFY)
|
||||
intake_volatility_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
|
||||
|
||||
register_placeholder(intake_volatility_metadata)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 5. nutrition_days
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
nutrition_days_metadata = PlaceholderMetadata(
|
||||
key="nutrition_days",
|
||||
category="Ernährung",
|
||||
description="Anzahl valider Ernährungstage (30d)",
|
||||
|
||||
# Technical
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="get_nutrition_days(pid, 30)",
|
||||
data_layer_module="backend/data_layer/nutrition_metrics.py",
|
||||
data_layer_function="get_nutrition_days_data",
|
||||
source_tables=["nutrition_log"],
|
||||
|
||||
# Semantic
|
||||
semantic_contract="Liefert die Anzahl der Tage mit valider Ernährungserfassung im 30-Tage-Fenster. Zählt alle unique Datums-Einträge in nutrition_log.",
|
||||
business_meaning="Direktes Maß für Datenabdeckung und Aussagekraft der Ernährungsplaceholder. Zeigt, an wie vielen Tagen im Zeitfenster Ernährungsdaten erfasst wurden.",
|
||||
unit="days",
|
||||
time_window="30d",
|
||||
output_type=OutputType.NUMERIC,
|
||||
placeholder_type=PlaceholderType.META,
|
||||
format_hint="Ganzzahl 0-30",
|
||||
example_output="22",
|
||||
|
||||
# Quality
|
||||
minimum_data_requirements="Keine Mindestmenge für Existenz des Placeholders selbst. Wert kann 0 sein.",
|
||||
quality_filter_policy=(
|
||||
"Definition 'valider Tag': Jeder Tag mit mindestens einem Eintrag in nutrition_log gilt als valide. "
|
||||
"WICHTIG: Sagt NICHTS über Qualität oder Vollständigkeit des einzelnen Tages. "
|
||||
"Auch Teil-Tage (z.B. nur Frühstück erfasst) zählen als valider Tag. "
|
||||
"Keine Prüfung auf Mindest-Kalorienanzahl oder vollständige Makros."
|
||||
),
|
||||
confidence_logic="Nicht klassisch nötig - der Wert selbst dient als Verlässlichkeitsindikator für andere Ernährungsplaceholder.",
|
||||
missing_value_policy=MissingValuePolicy(
|
||||
available=True, # Always available, even if 0 days
|
||||
value_raw=0,
|
||||
missing_reason=None,
|
||||
legacy_display="0"
|
||||
),
|
||||
known_limitations=(
|
||||
"Zählt nur UNIQUE dates mit Einträgen, nicht die Anzahl der Einträge. "
|
||||
"Sagt nichts über Qualität der einzelnen Tage (z.B. Vollständigkeit, Plausibilität). "
|
||||
"Nur Abdeckungsmaß, kein Qualitätsmaß. "
|
||||
"Bei mehreren Einträgen pro Tag wird Tag nur einmal gezählt."
|
||||
),
|
||||
|
||||
# Architecture
|
||||
layer_1_decision="Data Layer (nutrition_metrics.get_nutrition_days_data) - zählt unique dates",
|
||||
layer_2a_decision="Placeholder Resolver (get_nutrition_days, formatiert zu String)",
|
||||
layer_2b_reuse_possible="Ja - Coverage-Chart oder Datenqualitäts-Dashboard",
|
||||
architecture_alignment="Phase 0c conform - Count in Data Layer, Formatting in Resolver",
|
||||
issue_53_alignment="Konform - Zählung in Data Layer",
|
||||
|
||||
# Evidence
|
||||
evidence={}
|
||||
)
|
||||
|
||||
# Evidence tagging
|
||||
nutrition_days_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
|
||||
nutrition_days_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
|
||||
nutrition_days_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
|
||||
nutrition_days_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
|
||||
nutrition_days_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
|
||||
nutrition_days_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
|
||||
nutrition_days_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
|
||||
nutrition_days_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
|
||||
nutrition_days_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
|
||||
nutrition_days_metadata.set_evidence("minimum_data_requirements", EvidenceType.CODE_DERIVED)
|
||||
nutrition_days_metadata.set_evidence("quality_filter_policy", EvidenceType.MIXED) # Logic from code, definition from inspection
|
||||
nutrition_days_metadata.set_evidence("confidence_logic", EvidenceType.DRAFT_DERIVED)
|
||||
nutrition_days_metadata.set_evidence("semantic_contract", EvidenceType.MIXED) # Count logic from code, interpretation from draft
|
||||
nutrition_days_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
nutrition_days_metadata.set_evidence("known_limitations", EvidenceType.MIXED)
|
||||
nutrition_days_metadata.set_evidence("layer_1_decision", EvidenceType.TO_VERIFY)
|
||||
nutrition_days_metadata.set_evidence("layer_2a_decision", EvidenceType.TO_VERIFY)
|
||||
nutrition_days_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
|
||||
|
||||
register_placeholder(nutrition_days_metadata)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Registration Summary
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
"""
|
||||
Part C Registration Complete:
|
||||
- macro_consistency_score: Score-based consistency indicator (CV-based)
|
||||
- energy_balance_7d: kcal/day average balance (intake - estimated TDEE)
|
||||
- energy_deficit_surplus: Status classification (deficit/maintenance/surplus)
|
||||
- intake_volatility: Category classification (stable/moderate/high)
|
||||
- nutrition_days: Count of valid nutrition days (meta indicator)
|
||||
|
||||
Total Nutrition Cluster:
|
||||
- Part A: 4 placeholders (kcal_avg, protein_avg, carb_avg, fat_avg)
|
||||
- Part B: 5 placeholders (protein targets + adequacy)
|
||||
- Part C: 5 placeholders (consistency + balance + meta)
|
||||
→ 14 nutrition placeholders total
|
||||
|
||||
All registrations follow Phase 0c Multi-Layer Architecture:
|
||||
- Layer 1 (Data Layer): Calculations
|
||||
- Layer 2a (Placeholder Resolver): Formatting only
|
||||
- No logic changes from existing implementations
|
||||
- Evidence-based metadata tagging
|
||||
"""
|
||||
|
|
@ -1,281 +0,0 @@
|
|||
"""
|
||||
Placeholder/Metric Registry Framework
|
||||
|
||||
Central registry for all placeholders/metrics ensuring consistent metadata across:
|
||||
- Backend prompt resolution (Layer 2a)
|
||||
- GUI selection lists
|
||||
- Extended export
|
||||
- Validation
|
||||
- Chart assignment (Layer 2b)
|
||||
|
||||
Version: 1.0 (Part A - Nutrition Basis Metrics)
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from typing import Callable, Dict, List, Optional, Any
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class EvidenceType(str, Enum):
|
||||
"""Evidence type for metadata fields."""
|
||||
CODE_DERIVED = "code-derived"
|
||||
DRAFT_DERIVED = "draft-derived"
|
||||
MIXED = "mixed"
|
||||
UNRESOLVED = "unresolved"
|
||||
TO_VERIFY = "to_verify"
|
||||
|
||||
|
||||
class OutputType(str, Enum):
|
||||
"""Placeholder output types."""
|
||||
NUMERIC = "numeric"
|
||||
STRING = "string"
|
||||
BOOLEAN = "boolean"
|
||||
JSON = "json"
|
||||
LIST = "list"
|
||||
TEXT_SUMMARY = "text_summary"
|
||||
|
||||
|
||||
class PlaceholderType(str, Enum):
|
||||
"""Placeholder semantic types."""
|
||||
ATOMIC = "atomic"
|
||||
RAW_DATA = "raw_data"
|
||||
INTERPRETED = "interpreted"
|
||||
SCORE = "score"
|
||||
META = "meta"
|
||||
|
||||
|
||||
@dataclass
|
||||
class MissingValuePolicy:
|
||||
"""Structured missing value handling."""
|
||||
available: bool
|
||||
value_raw: Optional[Any]
|
||||
missing_reason: str # no_data, insufficient_data, resolver_error, calculation_error, not_applicable
|
||||
legacy_display: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlaceholderMetadata:
|
||||
"""
|
||||
Complete metadata for a placeholder/metric.
|
||||
|
||||
All fields track their evidence type to maintain transparency
|
||||
about what is code-derived vs. draft-derived.
|
||||
"""
|
||||
# Core identification
|
||||
key: str
|
||||
category: str
|
||||
description: str
|
||||
|
||||
# Technical (typically code-derived)
|
||||
resolver_module: str
|
||||
resolver_function: str
|
||||
data_layer_module: Optional[str] = None
|
||||
data_layer_function: Optional[str] = None
|
||||
source_tables: List[str] = field(default_factory=list)
|
||||
|
||||
# Semantic (typically draft-derived or mixed)
|
||||
semantic_contract: str = ""
|
||||
business_meaning: str = ""
|
||||
unit: str = ""
|
||||
time_window: str = ""
|
||||
output_type: OutputType = OutputType.STRING
|
||||
placeholder_type: PlaceholderType = PlaceholderType.INTERPRETED
|
||||
format_hint: str = ""
|
||||
example_output: str = ""
|
||||
|
||||
# Quality (mixed sources)
|
||||
minimum_data_requirements: Optional[str] = None
|
||||
quality_filter_policy: Optional[str] = None
|
||||
confidence_logic: Optional[str] = None
|
||||
missing_value_policy: Optional[MissingValuePolicy] = None
|
||||
known_limitations: Optional[str] = None
|
||||
|
||||
# Architecture (code-derived)
|
||||
layer_1_decision: Optional[str] = None
|
||||
layer_2a_decision: Optional[str] = None
|
||||
layer_2b_reuse_possible: Optional[bool] = None
|
||||
architecture_alignment: Optional[str] = None
|
||||
issue_53_alignment: Optional[str] = None
|
||||
|
||||
# Evidence tracking
|
||||
evidence: Dict[str, EvidenceType] = field(default_factory=dict)
|
||||
|
||||
# Runtime resolver (not serialized to export)
|
||||
_resolver_func: Optional[Callable] = field(default=None, repr=False, compare=False)
|
||||
|
||||
def to_dict(self, include_resolver: bool = False) -> Dict:
|
||||
"""Convert to dictionary for export."""
|
||||
data = asdict(self)
|
||||
|
||||
# Remove private fields
|
||||
if not include_resolver:
|
||||
data.pop('_resolver_func', None)
|
||||
|
||||
# Convert enums to strings
|
||||
data['output_type'] = self.output_type.value
|
||||
data['placeholder_type'] = self.placeholder_type.value
|
||||
|
||||
# Convert evidence dict
|
||||
data['evidence'] = {k: v.value for k, v in self.evidence.items()}
|
||||
|
||||
# Convert missing_value_policy
|
||||
if self.missing_value_policy:
|
||||
data['missing_value_policy'] = asdict(self.missing_value_policy)
|
||||
|
||||
return data
|
||||
|
||||
def get_evidence(self, field_name: str) -> Optional[EvidenceType]:
|
||||
"""Get evidence type for a field."""
|
||||
return self.evidence.get(field_name)
|
||||
|
||||
def set_evidence(self, field_name: str, evidence_type: EvidenceType):
|
||||
"""Set evidence type for a field."""
|
||||
self.evidence[field_name] = evidence_type
|
||||
|
||||
def validate(self) -> List[str]:
|
||||
"""Validate metadata completeness."""
|
||||
issues = []
|
||||
|
||||
if not self.key:
|
||||
issues.append("Missing key")
|
||||
if not self.category:
|
||||
issues.append("Missing category")
|
||||
if not self.description:
|
||||
issues.append("Missing description")
|
||||
if not self.resolver_module:
|
||||
issues.append("Missing resolver_module")
|
||||
if not self.resolver_function:
|
||||
issues.append("Missing resolver_function")
|
||||
if not self.semantic_contract:
|
||||
issues.append("Missing semantic_contract")
|
||||
if not self.unit:
|
||||
issues.append("Missing unit")
|
||||
if not self.time_window:
|
||||
issues.append("Missing time_window")
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
class PlaceholderRegistry:
|
||||
"""
|
||||
Central registry for all placeholders/metrics.
|
||||
|
||||
Ensures single source of truth for metadata across all consumers.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._registry: Dict[str, PlaceholderMetadata] = {}
|
||||
|
||||
def register(
|
||||
self,
|
||||
metadata: PlaceholderMetadata,
|
||||
resolver_func: Optional[Callable] = None
|
||||
):
|
||||
"""
|
||||
Register a placeholder with complete metadata.
|
||||
|
||||
Args:
|
||||
metadata: Complete placeholder metadata
|
||||
resolver_func: Optional resolver function (for runtime resolution)
|
||||
"""
|
||||
if metadata.key in self._registry:
|
||||
raise ValueError(f"Placeholder {metadata.key} already registered")
|
||||
|
||||
if resolver_func:
|
||||
metadata._resolver_func = resolver_func
|
||||
|
||||
self._registry[metadata.key] = metadata
|
||||
|
||||
def get(self, key: str) -> Optional[PlaceholderMetadata]:
|
||||
"""Get metadata for a placeholder."""
|
||||
return self._registry.get(key)
|
||||
|
||||
def get_all(self) -> Dict[str, PlaceholderMetadata]:
|
||||
"""Get all registered placeholders."""
|
||||
return self._registry.copy()
|
||||
|
||||
def get_by_category(self, category: str) -> List[PlaceholderMetadata]:
|
||||
"""Get placeholders by category (for GUI selection lists)."""
|
||||
return [
|
||||
m for m in self._registry.values()
|
||||
if m.category == category
|
||||
]
|
||||
|
||||
def get_all_for_export(self) -> List[Dict]:
|
||||
"""Get all metadata for extended export."""
|
||||
return [m.to_dict() for m in self._registry.values()]
|
||||
|
||||
def get_by_evidence_type(self, evidence_type: EvidenceType) -> Dict[str, List[str]]:
|
||||
"""
|
||||
Get fields by evidence type (for quality assurance).
|
||||
|
||||
Returns:
|
||||
Dict mapping placeholder_key to list of field_names with that evidence type
|
||||
"""
|
||||
result = {}
|
||||
for key, metadata in self._registry.items():
|
||||
fields = [
|
||||
field_name
|
||||
for field_name, ev_type in metadata.evidence.items()
|
||||
if ev_type == evidence_type
|
||||
]
|
||||
if fields:
|
||||
result[key] = fields
|
||||
return result
|
||||
|
||||
def validate_all(self) -> Dict[str, List[str]]:
|
||||
"""
|
||||
Validate all registered placeholders.
|
||||
|
||||
Returns:
|
||||
Dict mapping placeholder_key to list of validation issues
|
||||
"""
|
||||
issues = {}
|
||||
for key, metadata in self._registry.items():
|
||||
validation_issues = metadata.validate()
|
||||
if validation_issues:
|
||||
issues[key] = validation_issues
|
||||
return issues
|
||||
|
||||
def resolve(self, key: str, profile_id: str) -> str:
|
||||
"""
|
||||
Resolve a placeholder value for a profile.
|
||||
|
||||
Args:
|
||||
key: Placeholder key
|
||||
profile_id: User profile ID
|
||||
|
||||
Returns:
|
||||
Resolved value as string
|
||||
"""
|
||||
metadata = self.get(key)
|
||||
if not metadata:
|
||||
raise ValueError(f"Placeholder {key} not registered")
|
||||
|
||||
if not metadata._resolver_func:
|
||||
raise ValueError(f"Placeholder {key} has no resolver function")
|
||||
|
||||
return metadata._resolver_func(profile_id)
|
||||
|
||||
|
||||
# Global registry instance
|
||||
_global_registry = PlaceholderRegistry()
|
||||
|
||||
|
||||
def get_registry() -> PlaceholderRegistry:
|
||||
"""Get the global placeholder registry."""
|
||||
return _global_registry
|
||||
|
||||
|
||||
def register_placeholder(
|
||||
metadata: PlaceholderMetadata,
|
||||
resolver_func: Optional[Callable] = None
|
||||
):
|
||||
"""
|
||||
Register a placeholder in the global registry.
|
||||
|
||||
Args:
|
||||
metadata: Complete placeholder metadata
|
||||
resolver_func: Optional resolver function
|
||||
"""
|
||||
_global_registry.register(metadata, resolver_func)
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
"""
|
||||
Placeholder Registry Export Integration
|
||||
|
||||
Integrates the new placeholder registry with the existing export system.
|
||||
Provides backward-compatible export with enhanced metadata from registry.
|
||||
"""
|
||||
|
||||
from typing import Dict, List
|
||||
from placeholder_registry import get_registry, EvidenceType
|
||||
|
||||
|
||||
def get_registry_metadata_for_export(profile_id: str) -> Dict:
|
||||
"""
|
||||
Get metadata from registry formatted for export.
|
||||
|
||||
Returns:
|
||||
Dict with:
|
||||
- flat: List of all metadata dicts
|
||||
- by_category: Dict mapping category to list of metadata
|
||||
- evidence_report: Statistics about evidence types
|
||||
- validation_report: Validation issues
|
||||
"""
|
||||
registry = get_registry()
|
||||
|
||||
# Get all metadata
|
||||
all_metadata = registry.get_all()
|
||||
|
||||
# Build flat export
|
||||
flat = []
|
||||
for key, metadata in sorted(all_metadata.items()):
|
||||
meta_dict = metadata.to_dict()
|
||||
flat.append(meta_dict)
|
||||
|
||||
# Build by_category
|
||||
by_category = {}
|
||||
for metadata in all_metadata.values():
|
||||
cat = metadata.category
|
||||
if cat not in by_category:
|
||||
by_category[cat] = []
|
||||
by_category[cat].append(metadata.to_dict())
|
||||
|
||||
# Evidence report
|
||||
evidence_stats = {
|
||||
"code_derived": len(registry.get_by_evidence_type(EvidenceType.CODE_DERIVED)),
|
||||
"draft_derived": len(registry.get_by_evidence_type(EvidenceType.DRAFT_DERIVED)),
|
||||
"mixed": len(registry.get_by_evidence_type(EvidenceType.MIXED)),
|
||||
"unresolved": len(registry.get_by_evidence_type(EvidenceType.UNRESOLVED)),
|
||||
"to_verify": len(registry.get_by_evidence_type(EvidenceType.TO_VERIFY))
|
||||
}
|
||||
|
||||
evidence_detail = {
|
||||
etype.value: registry.get_by_evidence_type(etype)
|
||||
for etype in EvidenceType
|
||||
}
|
||||
|
||||
# Validation report
|
||||
validation_issues = registry.validate_all()
|
||||
|
||||
return {
|
||||
"flat": flat,
|
||||
"by_category": by_category,
|
||||
"evidence_report": {
|
||||
"statistics": evidence_stats,
|
||||
"detail": evidence_detail
|
||||
},
|
||||
"validation_report": validation_issues
|
||||
}
|
||||
|
||||
|
||||
def merge_registry_with_legacy_export(
|
||||
registry_data: Dict,
|
||||
legacy_data: Dict,
|
||||
resolved_values: Dict[str, str]
|
||||
) -> Dict:
|
||||
"""
|
||||
Merge registry metadata with legacy export data.
|
||||
|
||||
Args:
|
||||
registry_data: Data from get_registry_metadata_for_export()
|
||||
legacy_data: Existing legacy export structure
|
||||
resolved_values: Resolved placeholder values (key -> value)
|
||||
|
||||
Returns:
|
||||
Merged export with registry enhancements
|
||||
"""
|
||||
# Start with legacy structure
|
||||
merged = legacy_data.copy()
|
||||
|
||||
# Add registry metadata section
|
||||
merged["registry_metadata"] = {
|
||||
"flat": registry_data["flat"],
|
||||
"by_category": registry_data["by_category"],
|
||||
"evidence_report": registry_data["evidence_report"],
|
||||
"validation_report": registry_data["validation_report"]
|
||||
}
|
||||
|
||||
# Populate runtime values in registry metadata
|
||||
for meta_dict in merged["registry_metadata"]["flat"]:
|
||||
key = meta_dict["key"]
|
||||
if key in resolved_values:
|
||||
meta_dict["value_display"] = resolved_values[key]
|
||||
# Note: value_raw extraction can be added here if needed
|
||||
|
||||
return merged
|
||||
|
||||
|
||||
def get_enhanced_export_with_registry(profile_id: str, legacy_export: Dict) -> Dict:
|
||||
"""
|
||||
Enhance legacy export with registry metadata.
|
||||
|
||||
Args:
|
||||
profile_id: User profile ID
|
||||
legacy_export: Existing legacy export structure
|
||||
|
||||
Returns:
|
||||
Enhanced export with registry metadata section
|
||||
"""
|
||||
# Get registry data
|
||||
registry_data = get_registry_metadata_for_export(profile_id)
|
||||
|
||||
# Get resolved values (for value_display population)
|
||||
from placeholder_resolver import get_placeholder_example_values
|
||||
resolved_values = get_placeholder_example_values(profile_id)
|
||||
cleaned_values = {
|
||||
key.replace('{{', '').replace('}}', ''): value
|
||||
for key, value in resolved_values.items()
|
||||
}
|
||||
|
||||
# Merge
|
||||
enhanced = merge_registry_with_legacy_export(
|
||||
registry_data,
|
||||
legacy_export,
|
||||
cleaned_values
|
||||
)
|
||||
|
||||
return enhanced
|
||||
|
|
@ -358,28 +358,13 @@ def export_placeholder_values_extended(
|
|||
metadata.missing_reason = "Placeholder not in resolver output"
|
||||
|
||||
# Generate gap report (collect unresolved fields)
|
||||
# Note: TimeWindow, OutputType, PlaceholderType are from old metadata system
|
||||
# Skip gap report for old metadata if not available
|
||||
gaps = {}
|
||||
try:
|
||||
from placeholder_metadata_complete import TimeWindow, OutputType, PlaceholderType
|
||||
gaps = {
|
||||
'unknown_time_window': [k for k, m in all_metadata.items() if hasattr(m, 'time_window') and m.time_window == TimeWindow.UNKNOWN],
|
||||
'unknown_output_type': [k for k, m in all_metadata.items() if hasattr(m, 'output_type') and m.output_type == OutputType.UNKNOWN],
|
||||
'legacy_unknown_type': [k for k, m in all_metadata.items() if hasattr(m, 'type') and m.type == PlaceholderType.LEGACY_UNKNOWN],
|
||||
'unresolved_fields': {k: m.unresolved_fields for k, m in all_metadata.items() if hasattr(m, 'unresolved_fields') and m.unresolved_fields},
|
||||
'legacy_mismatches': [k for k, m in all_metadata.items() if hasattr(m, 'legacy_contract_mismatch') and m.legacy_contract_mismatch],
|
||||
'orphaned': [k for k, m in all_metadata.items() if hasattr(m, 'orphaned_placeholder') and m.orphaned_placeholder],
|
||||
}
|
||||
except ImportError:
|
||||
# Old metadata system not available, use empty gaps
|
||||
gaps = {
|
||||
'unknown_time_window': [],
|
||||
'unknown_output_type': [],
|
||||
'legacy_unknown_type': [],
|
||||
'unresolved_fields': {},
|
||||
'legacy_mismatches': [],
|
||||
'orphaned': [],
|
||||
'unknown_time_window': [k for k, m in all_metadata.items() if m.time_window == TimeWindow.UNKNOWN],
|
||||
'unknown_output_type': [k for k, m in all_metadata.items() if m.output_type == OutputType.UNKNOWN],
|
||||
'legacy_unknown_type': [k for k, m in all_metadata.items() if m.type == PlaceholderType.LEGACY_UNKNOWN],
|
||||
'unresolved_fields': {k: m.unresolved_fields for k, m in all_metadata.items() if m.unresolved_fields},
|
||||
'legacy_mismatches': [k for k, m in all_metadata.items() if m.legacy_contract_mismatch],
|
||||
'orphaned': [k for k, m in all_metadata.items() if m.orphaned_placeholder],
|
||||
}
|
||||
|
||||
# Validation
|
||||
|
|
@ -481,24 +466,6 @@ def export_placeholder_values_extended(
|
|||
}
|
||||
}
|
||||
|
||||
# ── PART A: Registry Integration ─────────────────────────────────────────
|
||||
# Add registry metadata for Part A placeholders (kcal_avg, protein_avg, carb_avg, fat_avg)
|
||||
try:
|
||||
import placeholder_registrations # Auto-registers Part A placeholders
|
||||
from placeholder_registry_export import get_registry_metadata_for_export
|
||||
|
||||
registry_data = get_registry_metadata_for_export(profile_id)
|
||||
export_data['registry_metadata'] = registry_data
|
||||
except Exception as e:
|
||||
# Graceful degradation if registry not available
|
||||
export_data['registry_metadata'] = {
|
||||
"error": f"Registry not available: {str(e)}",
|
||||
"flat": [],
|
||||
"by_category": {},
|
||||
"evidence_report": {},
|
||||
"validation_report": {}
|
||||
}
|
||||
|
||||
# Fill validation
|
||||
for key, violations in validation_results.items():
|
||||
errors = [v for v in violations if v.severity == "error"]
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Script zum Anlegen des Gitea Issues ohne jq dependency
|
||||
#
|
||||
|
||||
GITEA_TOKEN="b3d27c7d87d2acf39490d0c58f26922164edb4e8"
|
||||
GITEA_URL="http://192.168.2.144:3000"
|
||||
REPO_OWNER="Lars"
|
||||
REPO_NAME="mitai-jinkendo"
|
||||
|
||||
# Issue Body aus Datei lesen (erste 29 Zeilen überspringen = Metadaten)
|
||||
ISSUE_BODY=$(tail -n +30 "c:/Dev/mitai-jinkendo/.claude/task/rework_0b_placeholder/ISSUE_METADATEN_REVIEW.md" | python3 -c "import sys, json; print(json.dumps(sys.stdin.read()))")
|
||||
|
||||
# JSON Payload mit Python erstellen
|
||||
python3 << PYEOF > /tmp/issue_payload.json
|
||||
import json
|
||||
|
||||
body = $ISSUE_BODY
|
||||
|
||||
payload = {
|
||||
"title": "Placeholder Registry: UNRESOLVED & TO_VERIFY Metadaten prüfen",
|
||||
"body": body,
|
||||
"labels": [2, 3]
|
||||
}
|
||||
|
||||
print(json.dumps(payload, ensure_ascii=False))
|
||||
PYEOF
|
||||
|
||||
echo "Erstelle Gitea Issue..."
|
||||
echo "Repository: $REPO_OWNER/$REPO_NAME"
|
||||
echo ""
|
||||
|
||||
# Issue via API anlegen
|
||||
RESPONSE=$(curl -s -X POST \
|
||||
"$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @/tmp/issue_payload.json)
|
||||
|
||||
# Response mit Python parsen
|
||||
python3 << PYEOF
|
||||
import json
|
||||
|
||||
response = '''$RESPONSE'''
|
||||
try:
|
||||
data = json.loads(response)
|
||||
if 'number' in data:
|
||||
print(f"✓ Issue erfolgreich erstellt!")
|
||||
print(f"")
|
||||
print(f"Issue #{data['number']}")
|
||||
print(f"URL: {data['html_url']}")
|
||||
print(f"")
|
||||
print(f"✓ Fertig!")
|
||||
else:
|
||||
print(f"✗ Fehler beim Erstellen des Issues:")
|
||||
print(json.dumps(data, indent=2))
|
||||
except Exception as e:
|
||||
print(f"✗ Fehler: {e}")
|
||||
print(response)
|
||||
PYEOF
|
||||
|
||||
# Cleanup
|
||||
rm -f /tmp/issue_payload.json
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Script zum Anlegen des Gitea Issues:
|
||||
# "Placeholder Registry: UNRESOLVED & TO_VERIFY Metadaten prüfen"
|
||||
#
|
||||
# Usage: ./create_metadaten_review_issue.sh YOUR_GITEA_TOKEN
|
||||
#
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "ERROR: Gitea Token erforderlich"
|
||||
echo "Usage: $0 YOUR_GITEA_TOKEN"
|
||||
echo ""
|
||||
echo "Token erstellen:"
|
||||
echo " 1. Gitea öffnen: http://192.168.2.144:3000"
|
||||
echo " 2. Settings → Applications → Generate New Token"
|
||||
echo " 3. Name: 'Claude Code Issue Management'"
|
||||
echo " 4. Scope: issue (read/write)"
|
||||
echo " 5. Token kopieren und als Argument übergeben"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
GITEA_TOKEN="$1"
|
||||
GITEA_URL="http://192.168.2.144:3000"
|
||||
REPO_OWNER="Lars"
|
||||
REPO_NAME="mitai-jinkendo"
|
||||
|
||||
# Issue Body aus Datei lesen (erste 30 Zeilen überspringen = Metadaten)
|
||||
ISSUE_BODY=$(tail -n +30 .claude/task/rework_0b_placeholder/ISSUE_METADATEN_REVIEW.md)
|
||||
|
||||
# JSON Payload erstellen
|
||||
cat > /tmp/gitea_issue_payload.json << EOF
|
||||
{
|
||||
"title": "Placeholder Registry: UNRESOLVED & TO_VERIFY Metadaten prüfen",
|
||||
"body": $(echo "$ISSUE_BODY" | jq -Rs .),
|
||||
"labels": [1, 2, 3],
|
||||
"priority": 2
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Erstelle Gitea Issue..."
|
||||
echo "Repository: $REPO_OWNER/$REPO_NAME"
|
||||
echo ""
|
||||
|
||||
# Issue via API anlegen
|
||||
RESPONSE=$(curl -s -X POST \
|
||||
"$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @/tmp/gitea_issue_payload.json)
|
||||
|
||||
# Response prüfen
|
||||
if echo "$RESPONSE" | grep -q '"number"'; then
|
||||
ISSUE_NUMBER=$(echo "$RESPONSE" | jq -r '.number')
|
||||
ISSUE_URL=$(echo "$RESPONSE" | jq -r '.html_url')
|
||||
|
||||
echo "✓ Issue erfolgreich erstellt!"
|
||||
echo ""
|
||||
echo "Issue #$ISSUE_NUMBER"
|
||||
echo "URL: $ISSUE_URL"
|
||||
echo ""
|
||||
|
||||
# Labels setzen (falls nicht automatisch gesetzt)
|
||||
echo "Setze Labels..."
|
||||
curl -s -X POST \
|
||||
"$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues/$ISSUE_NUMBER/labels" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"labels": [1, 2, 3]}' > /dev/null
|
||||
|
||||
echo "✓ Fertig!"
|
||||
else
|
||||
echo "✗ Fehler beim Erstellen des Issues:"
|
||||
echo "$RESPONSE" | jq .
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
rm -f /tmp/gitea_issue_payload.json
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
75
package-lock.json
generated
75
package-lock.json
generated
|
|
@ -1,75 +0,0 @@
|
|||
{
|
||||
"name": "mitai-jinkendo",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.58.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
|
||||
"integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.58.2"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
||||
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.58.2"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
|
||||
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.58.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
module.exports = {
|
||||
testDir: './tests',
|
||||
timeout: 30000,
|
||||
use: {
|
||||
channel: 'chrome',
|
||||
headless: true,
|
||||
viewport: { width: 390, height: 844 },
|
||||
screenshot: 'only-on-failure',
|
||||
baseURL: 'https://dev.mitai.jinkendo.de',
|
||||
},
|
||||
reporter: 'list',
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"status": "failed",
|
||||
"failedTests": [
|
||||
"d6ae548bbe32e0652471-816c0db33a38f27f1eaf"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,202 +0,0 @@
|
|||
"""
|
||||
Test script to verify Activity Cluster placeholder registration.
|
||||
|
||||
Verifies:
|
||||
1. All 17 Activity placeholders are registered
|
||||
2. All have complete metadata (22 mandatory fields)
|
||||
3. Evidence distribution is correct
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add backend to path
|
||||
sys.path.insert(0, str(Path(__file__).parent / 'backend'))
|
||||
|
||||
# Import registrations (triggers auto-registration)
|
||||
print("Importing placeholder_registry...")
|
||||
from placeholder_registry import EvidenceType, get_registry
|
||||
print("Importing activity_metrics...")
|
||||
try:
|
||||
from placeholder_registrations import activity_metrics
|
||||
print("Activity metrics imported successfully")
|
||||
except Exception as e:
|
||||
print(f"ERROR importing activity_metrics: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
METADATA_REGISTRY = get_registry()
|
||||
print(f"Registry size after import: {len(METADATA_REGISTRY.get_all())}")
|
||||
|
||||
# Expected placeholders
|
||||
EXPECTED_ACTIVITY_PLACEHOLDERS = [
|
||||
'activity_summary',
|
||||
'activity_detail',
|
||||
'trainingstyp_verteilung',
|
||||
'training_minutes_week',
|
||||
'training_frequency_7d',
|
||||
'quality_sessions_pct',
|
||||
'ability_balance_strength',
|
||||
'ability_balance_endurance',
|
||||
'ability_balance_mental',
|
||||
'ability_balance_coordination',
|
||||
'ability_balance_mobility',
|
||||
'proxy_internal_load_7d',
|
||||
'monotony_score',
|
||||
'strain_score',
|
||||
'rest_day_compliance',
|
||||
'vo2max_trend_28d',
|
||||
'activity_score',
|
||||
]
|
||||
|
||||
def test_registration():
|
||||
"""Test that all Activity placeholders are registered."""
|
||||
print("=== Activity Cluster Registration Test ===\n")
|
||||
|
||||
# Check all expected placeholders
|
||||
registered = []
|
||||
missing = []
|
||||
|
||||
for key in EXPECTED_ACTIVITY_PLACEHOLDERS:
|
||||
if METADATA_REGISTRY.get(key) is not None:
|
||||
registered.append(key)
|
||||
else:
|
||||
missing.append(key)
|
||||
|
||||
print(f"OK Registered: {len(registered)}/17")
|
||||
if missing:
|
||||
print(f"FAIL Missing: {len(missing)}/17")
|
||||
for key in missing:
|
||||
print(f" - {key}")
|
||||
return False
|
||||
|
||||
print(f"OK All 17 Activity placeholders registered\n")
|
||||
return True
|
||||
|
||||
|
||||
def test_metadata_completeness():
|
||||
"""Test that all registered placeholders have complete metadata."""
|
||||
print("=== Metadata Completeness Test ===\n")
|
||||
|
||||
mandatory_fields = [
|
||||
'key', 'category', 'name_de', 'name_en', 'description_de', 'description_en',
|
||||
'placeholder_type', 'output_type', 'unit', 'time_window', 'semantic_contract',
|
||||
'calculation_method', 'source_info', 'data_lineage', 'confidence_logic',
|
||||
'missing_value_policy', 'known_limitations', 'dependencies',
|
||||
'layer_2b_reuse_possible', 'example_value'
|
||||
]
|
||||
|
||||
incomplete = []
|
||||
|
||||
for key in EXPECTED_ACTIVITY_PLACEHOLDERS:
|
||||
metadata = METADATA_REGISTRY.get(key)
|
||||
if metadata is None:
|
||||
continue
|
||||
|
||||
missing_fields = []
|
||||
|
||||
for field in mandatory_fields:
|
||||
value = getattr(metadata, field, None)
|
||||
if value is None or value == '' or value == []:
|
||||
missing_fields.append(field)
|
||||
|
||||
if missing_fields:
|
||||
incomplete.append((key, missing_fields))
|
||||
|
||||
if incomplete:
|
||||
print(f"FAIL Incomplete metadata: {len(incomplete)}/17")
|
||||
for key, fields in incomplete:
|
||||
print(f" - {key}: missing {fields}")
|
||||
return False
|
||||
|
||||
print(f"OK All 17 placeholders have complete metadata (20 mandatory fields)\n")
|
||||
return True
|
||||
|
||||
|
||||
def test_evidence_distribution():
|
||||
"""Test evidence tagging distribution."""
|
||||
print("=== Evidence Distribution Test ===\n")
|
||||
|
||||
evidence_counts = {
|
||||
EvidenceType.CODE_DERIVED: 0,
|
||||
EvidenceType.DRAFT_DERIVED: 0,
|
||||
EvidenceType.MIXED: 0,
|
||||
EvidenceType.TO_VERIFY: 0,
|
||||
EvidenceType.UNRESOLVED: 0,
|
||||
}
|
||||
|
||||
total_tags = 0
|
||||
|
||||
for key in EXPECTED_ACTIVITY_PLACEHOLDERS:
|
||||
metadata = METADATA_REGISTRY.get(key)
|
||||
if metadata is None:
|
||||
continue
|
||||
|
||||
# Count evidence tags (22 fields)
|
||||
for field in metadata.get_all_evidence_fields():
|
||||
evidence = metadata.get_evidence(field)
|
||||
if evidence:
|
||||
evidence_counts[evidence] = evidence_counts.get(evidence, 0) + 1
|
||||
total_tags += 1
|
||||
|
||||
print(f"Total evidence tags: {total_tags} (expected ~374 = 17 × 22)")
|
||||
print("\nDistribution:")
|
||||
for evidence_type, count in evidence_counts.items():
|
||||
percentage = (count / total_tags * 100) if total_tags > 0 else 0
|
||||
print(f" {evidence_type.value:15s}: {count:3d} ({percentage:5.1f}%)")
|
||||
|
||||
print("\nExpected distribution:")
|
||||
print(" CODE_DERIVED: ~60% (directly from code)")
|
||||
print(" DRAFT_DERIVED: ~15% (from canonical draft)")
|
||||
print(" MIXED: ~15% (combined sources)")
|
||||
print(" TO_VERIFY: ~10% (needs verification)")
|
||||
print()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def dump_sample_placeholder():
|
||||
"""Dump one complete placeholder as sample."""
|
||||
print("=== Sample Placeholder: activity_score ===\n")
|
||||
|
||||
metadata = METADATA_REGISTRY.get('activity_score')
|
||||
if metadata is None:
|
||||
print("FAIL activity_score not found in registry")
|
||||
return False
|
||||
|
||||
print(f"Key: {metadata.key}")
|
||||
print(f"Category: {metadata.category}")
|
||||
print(f"Name (DE): {metadata.name_de}")
|
||||
print(f"Name (EN): {metadata.name_en}")
|
||||
print(f"Type: {metadata.placeholder_type.value}")
|
||||
print(f"Output: {metadata.output_type.value}")
|
||||
print(f"Unit: {metadata.unit}")
|
||||
print(f"Time Window: {metadata.time_window}")
|
||||
print(f"\nDescription (DE):")
|
||||
print(f" {metadata.description_de[:100]}...")
|
||||
print(f"\nSemantic Contract:")
|
||||
print(f" {metadata.semantic_contract[:100]}...")
|
||||
print(f"\nCalculation Method:")
|
||||
print(f" {metadata.calculation_method[:100]}...")
|
||||
print(f"\nKnown Limitations:")
|
||||
print(f" {metadata.known_limitations[:150]}...")
|
||||
print(f"\nDependencies: {len(metadata.dependencies)} items")
|
||||
print(f"Layer 2b Reuse: {metadata.layer_2b_reuse_possible}")
|
||||
print()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
success = True
|
||||
|
||||
success &= test_registration()
|
||||
success &= test_metadata_completeness()
|
||||
success &= test_evidence_distribution()
|
||||
success &= dump_sample_placeholder()
|
||||
|
||||
if success:
|
||||
print("OK All tests passed - Activity Cluster registration is complete and valid")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("FAIL Some tests failed - see output above")
|
||||
sys.exit(1)
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
const TEST_EMAIL = process.env.TEST_EMAIL || 'lars@stommer.com';
|
||||
const TEST_PASSWORD = process.env.TEST_PASSWORD || '5112';
|
||||
|
||||
async function login(page) {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.fill('input[type="email"]', TEST_EMAIL);
|
||||
await page.fill('input[type="password"]', TEST_PASSWORD);
|
||||
await page.click('button:has-text("Anmelden")');
|
||||
await page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
test('1. Login funktioniert', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.fill('input[type="email"]', TEST_EMAIL);
|
||||
await page.fill('input[type="password"]', TEST_PASSWORD);
|
||||
await page.click('button:has-text("Anmelden")');
|
||||
await page.waitForLoadState('networkidle');
|
||||
const loginButton = page.locator('button:has-text("Anmelden")');
|
||||
await expect(loginButton).toHaveCount(0, { timeout: 10000 });
|
||||
await page.screenshot({ path: 'screenshots/01-nach-login.png' });
|
||||
console.log('Login erfolgreich');
|
||||
});
|
||||
|
||||
test('2. Dashboard laedt ohne Fehler', async ({ page }) => {
|
||||
await login(page);
|
||||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||||
await page.screenshot({ path: 'screenshots/02-dashboard.png' });
|
||||
console.log('Dashboard OK');
|
||||
});
|
||||
|
||||
test('3. Erfassung erreichbar', async ({ page }) => {
|
||||
await login(page);
|
||||
await page.click('text=Erfassung');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.screenshot({ path: 'screenshots/03-erfassung.png' });
|
||||
console.log('Erfassung OK');
|
||||
});
|
||||
|
||||
test('4. Analyse erreichbar', async ({ page }) => {
|
||||
await login(page);
|
||||
await page.click('text=Analyse');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.screenshot({ path: 'screenshots/04-analyse.png' });
|
||||
console.log('Analyse OK');
|
||||
});
|
||||
|
||||
test('5. Keine kritischen Console-Fehler', async ({ page }) => {
|
||||
const errors = [];
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error') errors.push(msg.text());
|
||||
});
|
||||
await login(page);
|
||||
await page.waitForLoadState('networkidle');
|
||||
const kritisch = errors.filter(e =>
|
||||
!e.includes('favicon') && !e.includes('sourceMap') && !e.includes('404')
|
||||
);
|
||||
if (kritisch.length > 0) {
|
||||
console.log('Console-Fehler:', kritisch.join(', '));
|
||||
} else {
|
||||
console.log('Keine kritischen Console-Fehler');
|
||||
}
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user